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(