From ab801fe48c05c57718a59ebdc09c220b20e174f6 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 19 Sep 2026 18:24:49 -0400 Subject: [PATCH] Ask the Gateway again after a 503 before paying OpenRouter A quarter of judge calls came back 503 from the Gateway in about a second and were paid for on OpenRouter. They arrive in short spells and clear on a wait of under two seconds. Co-Authored-By: Claude Fable 5.1 --- src/lib/jev.ts | 23 +++++++++++++++++++++++ tests/jev.test.ts | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/lib/jev.ts b/src/lib/jev.ts index da78305..1cf403f 100644 --- a/src/lib/jev.ts +++ b/src/lib/jev.ts @@ -31,6 +31,15 @@ export const GATEWAY_FREE_UNTIL = new Date("2026-09-26T07:00:00Z"); */ const GATEWAY_TIMEOUT_MS = 15_000; +/** + * The waits before the Gateway is asked again after a 503. Measured 2026-09-19 + * on 2,047 judge calls, 507 came back 503 in about a second and were paid for + * on OpenRouter; they arrive in short spells, whatever the request's size, and + * of 9 met in a probe of 80 calls 8 cleared on the first wait and the last on + * the second. Nothing else is retried here: a 429 or a timeout moves on. + */ +const GATEWAY_503_WAITS_MS = [400, 1200]; + const ENDPOINT = "https://openrouter.ai/api/alpha/decisions"; export type NoulQuestion = { @@ -193,6 +202,20 @@ export async function askJev(call: JevCall): Promise { } async function askGateway(key: string, call: JevCall): Promise { + for (let attempt = 0; ; attempt += 1) { + try { + return await askGatewayOnce(key, call); + } catch (error) { + const wait = GATEWAY_503_WAITS_MS[attempt]; + if (wait === undefined || !GatewayError.isInstance(error) || error.statusCode !== 503) { + throw error; + } + await new Promise((resolve) => setTimeout(resolve, wait)); + } + } +} + +async function askGatewayOnce(key: string, call: JevCall): Promise { const { JEV_GATEWAY_MODEL } = config(); const gateway = createGateway({ apiKey: key }); const startedAt = Date.now(); diff --git a/tests/jev.test.ts b/tests/jev.test.ts index 6752645..ecee942 100644 --- a/tests/jev.test.ts +++ b/tests/jev.test.ts @@ -179,6 +179,29 @@ describe("the Gateway first, OpenRouter behind it", () => { expect(recorded[1]).toMatchObject({ provider: "typesafe", finishReason: "answered" }); }); + it("asks the Gateway again after a 503 before paying OpenRouter", async () => { + recorded.length = 0; + settings.AI_GATEWAY_API_KEY = "vck_test"; + let asked = 0; + const fetch = twoRoutes(() => { + asked += 1; + return asked === 1 + ? new Response(JSON.stringify({ error: { message: "unavailable", type: "service_unavailable" } }), { status: 503 }) + : new Response( + JSON.stringify({ + answers: { p0__audience: { type: "boolean", probability: 0.9 } }, + usage: { inputTokens: 500, outputTokens: 0 }, + }), + ); + }, openrouterAnswer); + vi.stubGlobal("fetch", fetch); + + const answers = await askJev(call); + + expect(answers.p0__audience).toEqual({ type: "noul", noul: 0.9 }); + expect(recorded.map((row) => `${row.provider}:${row.finishReason}`)).toEqual(["vercel:http_503", "vercel:answered"]); + }); + it("asks the Gateway first every time, even after it rate-limits", async () => { recorded.length = 0; settings.AI_GATEWAY_API_KEY = "vck_test";