Skip to content

Commit b8eb9bc

Browse files
committed
Merge thrown context failure classification
2 parents 19e5511 + b4f0650 commit b8eb9bc

4 files changed

Lines changed: 46 additions & 8 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@gitcommit90/rerouted",
33
"productName": "ReRouted",
4-
"version": "0.5.3",
4+
"version": "0.5.4",
55
"description": "A local AI router for connected accounts, models, named routes, and automatic fallback.",
66
"author": "gitcommit90",
77
"license": "MIT",

src/lib/router.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,13 +1110,15 @@ function createRouter({ store, fetchImpl = fetch, requestLog, timeoutMs, usage,
11101110
defaultCooldownMs: COOLDOWN_MS.transient,
11111111
};
11121112
}
1113+
const error = e.message || String(e);
1114+
const classification = classifyFailure(502, error);
11131115
return {
11141116
ok: false,
1115-
status: 502,
1116-
error: e.message || String(e),
1117-
cooldownEligible: true,
1118-
failureKind: "transient",
1119-
defaultCooldownMs: COOLDOWN_MS.transient,
1117+
status: classification.kind === "request" ? 400 : 502,
1118+
error,
1119+
cooldownEligible: classification.eligible,
1120+
failureKind: classification.kind,
1121+
defaultCooldownMs: classification.defaultCooldownMs,
11201122
};
11211123
} finally {
11221124
if (!cleanupDeferred) bound.cleanup();

tests/router-fallback.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,42 @@ describe("same-provider OAuth account fallback", () => {
199199
assert.equal(logger.entries.find((entry) => entry.meta?.event === "accounts_exhausted").meta.status, 400);
200200
});
201201

202+
it("does not lock accounts when a context failure is thrown while preparing a stream", async () => {
203+
const store = createStore(tmpConfig());
204+
store.seed({
205+
providers: [
206+
chatgptAccount("prov_a", "token-a", 100),
207+
chatgptAccount("prov_b", "token-b", 200),
208+
],
209+
});
210+
const calls = [];
211+
const router = createRouter({
212+
store,
213+
logger: captureLogger(),
214+
fetchImpl: async (_url, options) => {
215+
calls.push(authToken(options));
216+
throw new Error(
217+
"Your input exceeds the context window of this model. Please adjust your input and try again."
218+
);
219+
},
220+
});
221+
222+
const result = await router.chatCompletions({
223+
body: {
224+
model: "chatgpt/gpt-5.4",
225+
messages: [{ role: "user", content: "oversized request" }],
226+
stream: true,
227+
},
228+
});
229+
230+
assert.equal(result.ok, false);
231+
assert.equal(result.status, 400);
232+
assert.deepEqual(calls, ["token-a", "token-b"]);
233+
for (const provider of store.load().providers) {
234+
assert.equal(provider.modelLocks?.["gpt-5.4"], undefined);
235+
}
236+
});
237+
202238
it("assigns monotonic oauth aliases and advertises only shared model ids", () => {
203239
const configPath = tmpConfig();
204240
const store = createStore(configPath);

0 commit comments

Comments
 (0)