diff --git a/open-sse/services/provider.js b/open-sse/services/provider.js index 495c3533..1b02cd83 100644 --- a/open-sse/services/provider.js +++ b/open-sse/services/provider.js @@ -159,12 +159,10 @@ export function hasThinkingConfig(body) { return !!(body.reasoning_effort || body.thinking?.type === "enabled"); } -// Normalize thinking config based on last message role -// - If lastMessage is not user → remove thinking config -// - If lastMessage is user AND has thinking config → keep it (force enable) +// Normalize provider-native thinking config based on last message role. +// OpenAI reasoning_effort is request-level and must survive tool-result turns. export function normalizeThinkingConfig(body) { if (!isLastMessageFromUser(body)) { - delete body.reasoning_effort; delete body.thinking; } return body; diff --git a/open-sse/translator/concerns/thinkingUnified.js b/open-sse/translator/concerns/thinkingUnified.js index 4603c99b..1cf44384 100644 --- a/open-sse/translator/concerns/thinkingUnified.js +++ b/open-sse/translator/concerns/thinkingUnified.js @@ -168,7 +168,7 @@ function applyFormat(fmt, body, cfg, caps) { case "openai": { if (none && canDisable) { body.reasoning_effort = "none"; break; } const level = toLevel(eff); - if (level) body.reasoning_effort = level === "xhigh" || level === "max" ? "high" : level; + if (level) body.reasoning_effort = level; break; } case "claude-adaptive": { diff --git a/open-sse/translator/request/claude-to-openai.js b/open-sse/translator/request/claude-to-openai.js index f5a5602c..dc54bac5 100644 --- a/open-sse/translator/request/claude-to-openai.js +++ b/open-sse/translator/request/claude-to-openai.js @@ -80,6 +80,16 @@ export function claudeToOpenAIRequest(model, body, stream) { result.tool_choice = convertToolChoice(body.tool_choice); } + if (body.reasoning_effort !== undefined) { + result.reasoning_effort = body.reasoning_effort; + } else if (body.reasoning?.effort !== undefined) { + result.reasoning_effort = body.reasoning.effort; + } + + if (body.reasoning !== undefined) { + result.reasoning = body.reasoning; + } + return result; } diff --git a/open-sse/translator/request/openai-responses.js b/open-sse/translator/request/openai-responses.js index 0f49c059..98c516cd 100644 --- a/open-sse/translator/request/openai-responses.js +++ b/open-sse/translator/request/openai-responses.js @@ -316,6 +316,8 @@ export function openaiToOpenAIResponsesRequest(model, body, stream, credentials) if (body.temperature !== undefined) result.temperature = body.temperature; if (body.max_tokens !== undefined) result.max_tokens = body.max_tokens; if (body.top_p !== undefined) result.top_p = body.top_p; + if (body.reasoning !== undefined) result.reasoning = body.reasoning; + if (body.reasoning_effort !== undefined) result.reasoning = { effort: body.reasoning_effort, summary: "auto" }; return result; } diff --git a/tests/translator/thinking-unified.test.js b/tests/translator/thinking-unified.test.js index 37c70511..bae22c33 100644 --- a/tests/translator/thinking-unified.test.js +++ b/tests/translator/thinking-unified.test.js @@ -123,6 +123,10 @@ describe("applyThinking per provider format", () => { const out = apply("openai", "gpt-5(low)", { reasoning_effort: "high" }, "openai"); expect(out.reasoning_effort).toBe("low"); }); + it("openai keeps xhigh for reasoning models", () => { + const out = apply("openai", "gpt-5.3-codex", { reasoning_effort: "xhigh" }, "codex"); + expect(out.reasoning_effort).toBe("xhigh"); + }); }); describe("extractReasoningText (response shapes)", () => { diff --git a/tests/unit/provider-thinking-config.test.js b/tests/unit/provider-thinking-config.test.js new file mode 100644 index 00000000..d21cc6f1 --- /dev/null +++ b/tests/unit/provider-thinking-config.test.js @@ -0,0 +1,17 @@ +import { describe, it, expect } from "vitest"; +import { normalizeThinkingConfig } from "../../open-sse/services/provider.js"; + +describe("normalizeThinkingConfig", () => { + it("keeps openai reasoning_effort on non-user turns", () => { + const body = { + messages: [{ role: "assistant", content: "ok" }], + reasoning_effort: "xhigh", + thinking: { type: "enabled" }, + }; + + normalizeThinkingConfig(body); + + expect(body.reasoning_effort).toBe("xhigh"); + expect(body.thinking).toBeUndefined(); + }); +});