From f704ee7255769822d2bac7d7c2f8f348f64ca615 Mon Sep 17 00:00:00 2001 From: PriNova Date: Wed, 18 Mar 2026 11:23:27 +0100 Subject: [PATCH] fix(ai): use OpenRouter reasoning payload (#2298) * fix(ai): use OpenRouter reasoning payload * fix(coding-agent): stop updating packages on startup closes #1963 * fix(ai): add openrouter thinkingFormat compat --------- Co-authored-by: Mario Zechner --- .../ai/src/providers/openai-completions.ts | 12 ++++++- packages/ai/src/types.ts | 4 +-- .../openai-completions-tool-choice.test.ts | 32 +++++++++++++++++++ .../coding-agent/src/core/model-registry.ts | 1 + 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index bd2ccf20..2714e91c 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -408,6 +408,12 @@ function buildParams(model: Model<"openai-completions">, context: Context, optio (params as any).enable_thinking = !!options?.reasoningEffort; } else if (compat.thinkingFormat === "qwen-chat-template" && model.reasoning) { (params as any).chat_template_kwargs = { enable_thinking: !!options?.reasoningEffort }; + } else if (compat.thinkingFormat === "openrouter" && options?.reasoningEffort && model.reasoning) { + // OpenRouter normalizes reasoning across providers via a nested reasoning object. + const openRouterParams = params as typeof params & { reasoning?: { effort?: string } }; + openRouterParams.reasoning = { + effort: mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap), + }; } else if (options?.reasoningEffort && model.reasoning && compat.supportsReasoningEffort) { // OpenAI-style reasoning_effort (params as any).reasoning_effort = mapReasoningEffort(options.reasoningEffort, compat.reasoningEffortMap); @@ -816,7 +822,11 @@ function detectCompat(model: Model<"openai-completions">): Required delimiters. Default: auto-detected from URL. */ requiresThinkingAsText?: boolean; - /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */ - thinkingFormat?: "openai" | "zai" | "qwen" | "qwen-chat-template"; + /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "openrouter" uses reasoning: { effort }, "zai" uses top-level enable_thinking: boolean, "qwen" uses top-level enable_thinking: boolean, and "qwen-chat-template" uses chat_template_kwargs.enable_thinking. Default: "openai". */ + thinkingFormat?: "openai" | "openrouter" | "zai" | "qwen" | "qwen-chat-template"; /** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */ openRouterRouting?: OpenRouterRouting; /** Vercel AI Gateway routing preferences. Only used when baseUrl points to Vercel AI Gateway. */ diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index 315832b9..50bf0da0 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -233,4 +233,36 @@ describe("openai-completions tool_choice", () => { expect(response.stopReason).toBe("error"); expect(response.errorMessage).toBe("Provider finish_reason: network_error"); }); + + it("uses OpenRouter reasoning object instead of reasoning_effort", async () => { + const model = getModel("openrouter", "deepseek/deepseek-r1")!; + let payload: unknown; + + await streamSimple( + model, + { + messages: [ + { + role: "user", + content: "Hi", + timestamp: Date.now(), + }, + ], + }, + { + apiKey: "test", + reasoning: "high", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { + reasoning?: { effort?: string }; + reasoning_effort?: string; + }; + expect(params.reasoning).toEqual({ effort: "high" }); + expect(params.reasoning_effort).toBeUndefined(); + }); }); diff --git a/packages/coding-agent/src/core/model-registry.ts b/packages/coding-agent/src/core/model-registry.ts index aeb80684..ca9b09b8 100644 --- a/packages/coding-agent/src/core/model-registry.ts +++ b/packages/coding-agent/src/core/model-registry.ts @@ -63,6 +63,7 @@ const OpenAICompletionsCompatSchema = Type.Object({ thinkingFormat: Type.Optional( Type.Union([ Type.Literal("openai"), + Type.Literal("openrouter"), Type.Literal("zai"), Type.Literal("qwen"), Type.Literal("qwen-chat-template"),