diff --git a/open-sse/executors/github.js b/open-sse/executors/github.js index 17af588a..ea0905ed 100644 --- a/open-sse/executors/github.js +++ b/open-sse/executors/github.js @@ -121,6 +121,19 @@ export class GithubExecutor extends BaseExecutor { return !/claude/i.test(model); } + // reasoning_effort works for GPT-5 family AND Claude Opus 4.6 / Sonnet 4.6 + // on GitHub Copilot. Only strip for models that don't support it: + // Claude Haiku 4.5, Claude Opus 4.7 (rejected upstream). + supportsReasoningEffort(model) { + const m = model.toLowerCase(); + // Claude models that DO support reasoning_effort + if (/claude.*opus.*4\.6/i.test(m) || /claude.*sonnet.*4\.6/i.test(m)) return true; + // All other Claude models: strip + if (/claude/i.test(model)) return false; + // GPT-5 family, Gemini, etc.: keep + return true; + } + transformRequest(model, body, stream, credentials) { const transformed = { ...body }; if (this.requiresMaxCompletionTokens(model) && transformed.max_tokens !== undefined) { @@ -131,9 +144,16 @@ export class GithubExecutor extends BaseExecutor { if (!this.supportsTemperature(model) && transformed.temperature !== undefined) { delete transformed.temperature; } - // Strip thinking/reasoning_effort — unsupported on /chat/completions + // Always strip Claude-style thinking payload (Copilot doesn't understand it) if (!this.supportsThinking(model)) { delete transformed.thinking; + } + // "none" means no thinking — strip it so models that don't support "none" don't 400 + if (transformed.reasoning_effort === "none") { + delete transformed.reasoning_effort; + } + // Strip reasoning_effort only for models that reject it + if (!this.supportsReasoningEffort(model) && transformed.reasoning_effort !== undefined) { delete transformed.reasoning_effort; } return transformed; diff --git a/open-sse/translator/request/openai-to-claude.js b/open-sse/translator/request/openai-to-claude.js index 41be550e..f1a9baae 100644 --- a/open-sse/translator/request/openai-to-claude.js +++ b/open-sse/translator/request/openai-to-claude.js @@ -179,6 +179,25 @@ Respond ONLY with the JSON object, no other text.`); }; } + // Map OpenAI reasoning_effort → Claude thinking.budget_tokens + // When client sends reasoning_effort (OpenAI format) but no explicit thinking block, + // translate to Claude's native format. + if (body.reasoning_effort && !result.thinking) { + const effortToBudget = { + none: 0, + low: 4096, + medium: 8192, + high: 16384, + xhigh: 32768, + }; + const budget = effortToBudget[body.reasoning_effort.toLowerCase()]; + if (budget === 0) { + // none → no thinking + } else if (budget) { + result.thinking = { type: "enabled", budget_tokens: budget }; + } + } + // Attach toolNameMap to result for response translation if (toolNameMap.size > 0) { result._toolNameMap = toolNameMap;