fix(openrouter): ensure GPT-5 reasoning effort passed and include_reasoning set for OpenRouter

This commit is contained in:
Roo Code 2025-08-22 14:58:58 +00:00
parent 9b8f3b95ec
commit 039e59fa75
2 changed files with 98 additions and 1 deletions

View file

@ -9,6 +9,7 @@ import OpenAI from "openai"
import { OpenRouterHandler } from "../openrouter"
import { ApiHandlerOptions } from "../../../shared/api"
import { Package } from "../../../shared/package"
import { getModels } from "../fetchers/modelCache"
// Mock dependencies
vitest.mock("openai")
@ -44,6 +45,9 @@ vitest.mock("../fetchers/modelCache", () => ({
})
}),
}))
vitest.mock("../fetchers/modelEndpointCache", () => ({
getModelEndpoints: vitest.fn().mockResolvedValue({}),
}))
describe("OpenRouterHandler", () => {
const mockOptions: ApiHandlerOptions = {
@ -267,6 +271,88 @@ describe("OpenRouterHandler", () => {
const generator = handler.createMessage("test", [])
await expect(generator.next()).rejects.toThrow("OpenRouter API Error 500: API Error")
})
it("passes reasoning effort and include_reasoning for GPT-5 models via OpenRouter", async () => {
;(getModels as any).mockResolvedValueOnce({
"openai/gpt-5-2025-08-07": {
maxTokens: 8192,
contextWindow: 128000,
supportsPromptCache: false,
supportsReasoningEffort: true,
description: "GPT-5 via OpenRouter",
},
})
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: "openai/gpt-5-2025-08-07",
choices: [{ delta: { reasoning: "Thinking...", content: "Hello" } }],
usage: { prompt_tokens: 1, completion_tokens: 2, cost: 0.0 },
}
},
}
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
;(OpenAI as any).prototype.chat = { completions: { create: mockCreate } } as any
const handler = new OpenRouterHandler({
openRouterApiKey: "test-key",
openRouterModelId: "openai/gpt-5-2025-08-07",
enableReasoningEffort: true,
reasoningEffort: "minimal" as any,
})
const gen = handler.createMessage("sys", [{ role: "user", content: "hi" } as any])
for await (const _ of gen) {
// drain
}
const call = (mockCreate as any).mock.calls[0][0]
expect(call.model).toBe("openai/gpt-5-2025-08-07")
expect(call.include_reasoning).toBe(true)
expect(call.reasoning).toEqual({ effort: "minimal" })
})
it('defaults GPT-5 reasoning effort to "medium" when enabled but not specified', async () => {
;(getModels as any).mockResolvedValueOnce({
"openai/gpt-5-2025-08-07": {
maxTokens: 8192,
contextWindow: 128000,
supportsPromptCache: false,
supportsReasoningEffort: true,
description: "GPT-5 via OpenRouter",
},
})
const mockStream = {
async *[Symbol.asyncIterator]() {
yield {
id: "openai/gpt-5-2025-08-07",
choices: [{ delta: { content: "Hi" } }],
usage: { prompt_tokens: 1, completion_tokens: 2, cost: 0.0 },
}
},
}
const mockCreate = vitest.fn().mockResolvedValue(mockStream)
;(OpenAI as any).prototype.chat = { completions: { create: mockCreate } } as any
const handler = new OpenRouterHandler({
openRouterApiKey: "test-key",
openRouterModelId: "openai/gpt-5-2025-08-07",
enableReasoningEffort: true,
})
const gen = handler.createMessage("sys", [{ role: "user", content: "hi" } as any])
for await (const _ of gen) {
// drain
}
const call = (mockCreate as any).mock.calls[0][0]
expect(call.include_reasoning).toBe(true)
expect(call.reasoning).toEqual({ effort: "medium" })
})
})
describe("completePrompt", () => {

View file

@ -121,6 +121,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
messages: openAiMessages,
stream: true,
stream_options: { include_usage: true },
// For GPT-5 via OpenRouter, request reasoning content in the stream explicitly
...(modelId.startsWith("openai/gpt-5") && { include_reasoning: true }),
// Only include provider if openRouterSpecificProvider is not "[default]".
...(this.options.openRouterSpecificProvider &&
this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && {
@ -208,7 +210,14 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
defaultTemperature: isDeepSeekR1 ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0,
})
return { id, info, topP: isDeepSeekR1 ? 0.95 : undefined, ...params }
// Apply GPT-5 defaults for OpenRouter: default reasoning effort to "medium" when enabled
let adjustedParams = params
if (id.startsWith("openai/gpt-5") && !params.reasoning && this.options.enableReasoningEffort !== false) {
const effort = (this.options.reasoningEffort as any) ?? "medium"
adjustedParams = { ...params, reasoning: { effort } as OpenRouterReasoningParams }
}
return { id, info, topP: isDeepSeekR1 ? 0.95 : undefined, ...adjustedParams }
}
async completePrompt(prompt: string) {
@ -220,6 +229,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
temperature,
messages: [{ role: "user", content: prompt }],
stream: false,
// For GPT-5 via OpenRouter, request reasoning details explicitly as well
...(modelId.startsWith("openai/gpt-5") && { include_reasoning: true }),
// Only include provider if openRouterSpecificProvider is not "[default]".
...(this.options.openRouterSpecificProvider &&
this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && {