Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/lib/jev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -193,6 +202,20 @@ export async function askJev(call: JevCall): Promise<Answers> {
}

async function askGateway(key: string, call: JevCall): Promise<Answers> {
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<Answers> {
const { JEV_GATEWAY_MODEL } = config();
const gateway = createGateway({ apiKey: key });
const startedAt = Date.now();
Expand Down
23 changes: 23 additions & 0 deletions tests/jev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading