diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 4e543ecd..de94a9c3 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed OpenAI Responses requests for models that support disabling reasoning to send `reasoning.effort: "none"` when thinking is off. + ## [0.74.0] - 2026-05-07 ## [0.73.1] - 2026-05-07 diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index ab4cdd19..7ca0e410 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -86,6 +86,16 @@ const DEEPSEEK_V4_THINKING_LEVEL_MAP = { xhigh: "max", } as const; +const OPENAI_RESPONSES_NONE_REASONING_MODELS = new Set([ + "gpt-5.1", + "gpt-5.2", + "gpt-5.3-codex", + "gpt-5.4", + "gpt-5.4-mini", + "gpt-5.4-nano", + "gpt-5.5", +]); + function mergeThinkingLevelMap(model: Model, map: NonNullable["thinkingLevelMap"]>): void { model.thinkingLevelMap = { ...model.thinkingLevelMap, ...map }; } @@ -122,6 +132,13 @@ function applyThinkingLevelMetadata(model: Model): void { ) { mergeThinkingLevelMap(model, { off: null }); } + if ( + model.api === "openai-responses" && + model.provider === "openai" && + OPENAI_RESPONSES_NONE_REASONING_MODELS.has(model.id) + ) { + mergeThinkingLevelMap(model, { off: "none" }); + } if (supportsOpenAiXhigh(model.id)) { mergeThinkingLevelMap(model, { xhigh: "xhigh" }); } diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 4fbba0fc..9ad5d97b 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -4708,6 +4708,24 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 64000, } satisfies Model<"google-generative-ai">, + "gemini-3.1-flash-lite": { + id: "gemini-3.1-flash-lite", + name: "Gemini 3.1 Flash Lite", + api: "google-generative-ai", + provider: "google", + baseUrl: "https://generativelanguage.googleapis.com/v1beta", + reasoning: true, + thinkingLevelMap: {"off":null}, + input: ["text", "image"], + cost: { + input: 0.25, + output: 1.5, + cacheRead: 0.025, + cacheWrite: 1, + }, + contextWindow: 1048576, + maxTokens: 65536, + } satisfies Model<"google-generative-ai">, "gemini-3.1-flash-lite-preview": { id: "gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview", @@ -6948,7 +6966,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null}, + thinkingLevelMap: {"off":"none"}, input: ["text", "image"], cost: { input: 1.25, @@ -7038,7 +7056,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 1.75, @@ -7128,7 +7146,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 1.75, @@ -7164,7 +7182,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 2.5, @@ -7182,7 +7200,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 0.75, @@ -7200,7 +7218,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 0.2, @@ -7236,7 +7254,7 @@ export const MODELS = { provider: "openai", baseUrl: "https://api.openai.com/v1", reasoning: true, - thinkingLevelMap: {"off":null,"xhigh":"xhigh"}, + thinkingLevelMap: {"off":"none","xhigh":"xhigh"}, input: ["text", "image"], cost: { input: 5, @@ -9405,6 +9423,23 @@ export const MODELS = { contextWindow: 1048576, maxTokens: 65536, } satisfies Model<"openai-completions">, + "google/gemini-3.1-flash-lite": { + id: "google/gemini-3.1-flash-lite", + name: "Google: Gemini 3.1 Flash Lite", + api: "openai-completions", + provider: "openrouter", + baseUrl: "https://openrouter.ai/api/v1", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.25, + output: 1.5, + cacheRead: 0.024999999999999998, + cacheWrite: 0.08333333333333334, + }, + contextWindow: 1048576, + maxTokens: 65536, + } satisfies Model<"openai-completions">, "google/gemini-3.1-flash-lite-preview": { id: "google/gemini-3.1-flash-lite-preview", name: "Google: Gemini 3.1 Flash Lite Preview", @@ -9592,18 +9627,18 @@ export const MODELS = { contextWindow: 128000, maxTokens: 50000, } satisfies Model<"openai-completions">, - "inclusionai/ling-2.6-1t:free": { - id: "inclusionai/ling-2.6-1t:free", - name: "inclusionAI: Ling-2.6-1T (free)", + "inclusionai/ling-2.6-1t": { + id: "inclusionai/ling-2.6-1t", + name: "inclusionAI: Ling-2.6-1T", api: "openai-completions", provider: "openrouter", baseUrl: "https://openrouter.ai/api/v1", reasoning: false, input: ["text"], cost: { - input: 0, - output: 0, - cacheRead: 0, + input: 0.3, + output: 2.5, + cacheRead: 0.06, cacheWrite: 0, }, contextWindow: 262144, @@ -13994,6 +14029,23 @@ export const MODELS = { contextWindow: 1000000, maxTokens: 64000, } satisfies Model<"anthropic-messages">, + "google/gemini-3.1-flash-lite": { + id: "google/gemini-3.1-flash-lite", + name: "Gemini 3.1 Flash Lite", + api: "anthropic-messages", + provider: "vercel-ai-gateway", + baseUrl: "https://ai-gateway.vercel.sh", + reasoning: true, + input: ["text", "image"], + cost: { + input: 0.25, + output: 1.5, + cacheRead: 0.03, + cacheWrite: 0, + }, + contextWindow: 1000000, + maxTokens: 65000, + } satisfies Model<"anthropic-messages">, "google/gemini-3.1-flash-lite-preview": { id: "google/gemini-3.1-flash-lite-preview", name: "Gemini 3.1 Flash Lite Preview", diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-copilot-provider.test.ts index fc1093b7..c4140a5c 100644 --- a/packages/ai/test/openai-responses-copilot-provider.test.ts +++ b/packages/ai/test/openai-responses-copilot-provider.test.ts @@ -91,6 +91,80 @@ describe("openai-responses provider defaults", () => { }); }); + it.each(["gpt-5.1", "gpt-5.2", "gpt-5.3-codex", "gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano", "gpt-5.5"] as const)( + "sends none reasoning effort for OpenAI %s when no reasoning is requested", + async (modelId) => { + const model = getModel("openai", modelId); + let capturedPayload: unknown; + + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response("data: [DONE]\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" }, + }), + ); + + const stream = streamOpenAIResponses( + model, + { + systemPrompt: "sys", + messages: [{ role: "user", content: "hi", timestamp: Date.now() }], + }, + { + apiKey: "test-key", + onPayload: (payload) => { + capturedPayload = payload; + }, + }, + ); + + for await (const event of stream) { + if (event.type === "done" || event.type === "error") break; + } + + expect(capturedPayload).toMatchObject({ + reasoning: { effort: "none" }, + }); + }, + ); + + it.each(["gpt-5", "gpt-5-mini", "gpt-5-nano", "gpt-5-pro", "gpt-5.2-pro", "gpt-5.4-pro", "gpt-5.5-pro"] as const)( + "omits reasoning effort for OpenAI %s when off is unsupported", + async (modelId) => { + const model = getModel("openai", modelId); + let capturedPayload: unknown; + + vi.spyOn(globalThis, "fetch").mockResolvedValue( + new Response("data: [DONE]\n\n", { + status: 200, + headers: { "content-type": "text/event-stream" }, + }), + ); + + const stream = streamOpenAIResponses( + model, + { + systemPrompt: "sys", + messages: [{ role: "user", content: "hi", timestamp: Date.now() }], + }, + { + apiKey: "test-key", + onPayload: (payload) => { + capturedPayload = payload; + }, + }, + ); + + for await (const event of stream) { + if (event.type === "done" || event.type === "error") break; + } + + expect(capturedPayload).not.toMatchObject({ + reasoning: expect.anything(), + }); + }, + ); + it("sets cache-affinity headers for official OpenAI Responses requests with a sessionId", async () => { const captured = await captureOpenAIResponseHeaders({ sessionId: "session-123" });