From f53daec09f40a664297535bdb61496dea744c2bf Mon Sep 17 00:00:00 2001 From: can1357 Date: Fri, 19 Jun 2026 14:49:05 +0200 Subject: [PATCH] fix(ai): improved reasoning fallback logic for equidistant tiers - Update fallback selection to prefer higher effort tiers when distance is equal. - Add test case verifying fallback from unsupported "medium" effort to "high". --- .../providers/openai-reasoning-fallback.ts | 4 +- .../openai-reasoning-effort-fallback.test.ts | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/providers/openai-reasoning-fallback.ts b/packages/ai/src/providers/openai-reasoning-fallback.ts index c6ee99561..89f8c85a5 100644 --- a/packages/ai/src/providers/openai-reasoning-fallback.ts +++ b/packages/ai/src/providers/openai-reasoning-fallback.ts @@ -230,14 +230,16 @@ function nearestEnabledReasoningFallback(currentEffort: string, allowed: Set bestRank)) { best = candidate; bestDistance = distance; + bestRank = candidateRank; } } return best; diff --git a/packages/ai/test/openai-reasoning-effort-fallback.test.ts b/packages/ai/test/openai-reasoning-effort-fallback.test.ts index 5dce3a905..cdedabb21 100644 --- a/packages/ai/test/openai-reasoning-effort-fallback.test.ts +++ b/packages/ai/test/openai-reasoning-effort-fallback.test.ts @@ -71,6 +71,19 @@ function invalidReasoningResponse(param: "reasoning_effort" | "reasoning.effort" { status: 400, headers: { "content-type": "application/json" } }, ); } +function invalidMediumReasoningResponse(): Response { + return new Response( + JSON.stringify({ + error: { + message: 'reasoning.effort: Invalid option: expected one of "high"|"low"|"minimal"|"none"', + type: "invalid_request_error", + param: "reasoning.effort", + }, + }), + { status: 400, headers: { "content-type": "application/json" } }, + ); +} + function pipeDelimitedReasoningEffortResponse(): Response { return new Response( JSON.stringify({ @@ -271,6 +284,30 @@ describe("OpenAI reasoning effort fallback retry", () => { expect(bodies.map(body => (body.reasoning as { effort?: string } | undefined)?.effort)).toEqual(["max", "xhigh"]); }); + it("retries medium as high when medium is missing and high is the closest upper tier", async () => { + const bodies: Record[] = []; + const fetchMock: FetchImpl = Object.assign( + async (_input: string | URL | Request, init?: RequestInit): Promise => { + const body = parseJsonBody(init); + bodies.push(body); + return bodies.length === 1 ? invalidMediumReasoningResponse() : createResponsesSseResponse(); + }, + { preconnect: fetch.preconnect }, + ); + + const result = await streamOpenAIResponses(createResponsesModel(), testContext, { + apiKey: "test-key", + fetch: fetchMock, + reasoning: "medium", + }).result(); + + expect(result.stopReason).toBe("stop"); + expect(bodies.map(body => (body.reasoning as { effort?: string } | undefined)?.effort)).toEqual([ + "medium", + "high", + ]); + }); + it("retries Azure Responses xhigh as provider max", async () => { const bodies: Record[] = []; const fetchMock: FetchImpl = Object.assign(