From 7f8187b63bd9509e1b54e17c1d4d13fd9377ad13 Mon Sep 17 00:00:00 2001 From: caozhiyuan <568022847@qq.com> Date: Wed, 3 Dec 2025 14:46:08 +0800 Subject: [PATCH 1/4] feat: support claude model thinking block --- src/routes/messages/non-stream-translation.ts | 53 ++++++++++++++++--- src/routes/messages/stream-translation.ts | 43 ++++++++------- .../copilot/create-chat-completions.ts | 1 + src/services/copilot/get-models.ts | 2 + 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index 94a0f7e12..29be0d7fd 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -1,3 +1,6 @@ +import type { Model } from "~/services/copilot/get-models" + +import { state } from "~/lib/state" import { type ChatCompletionResponse, type ChatCompletionsPayload, @@ -29,11 +32,15 @@ import { mapOpenAIStopReasonToAnthropic } from "./utils" export function translateToOpenAI( payload: AnthropicMessagesPayload, ): ChatCompletionsPayload { + const modelId = translateModelName(payload.model) + const model = state.models?.data.find((m) => m.id === modelId) + const thinkingBudget = getThinkingBudget(payload, model) return { - model: translateModelName(payload.model), + model: modelId, messages: translateAnthropicMessagesToOpenAI( payload.messages, payload.system, + modelId, ), max_tokens: payload.max_tokens, stop: payload.stop_sequences, @@ -43,14 +50,32 @@ export function translateToOpenAI( user: payload.metadata?.user_id, tools: translateAnthropicToolsToOpenAI(payload.tools), tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice), + thinking_budget: thinkingBudget, } } +function getThinkingBudget( + payload: AnthropicMessagesPayload, + model: Model | undefined, +): number | undefined { + const thinking = payload.thinking + if (model && thinking) { + const maxThinkingBudget = Math.min( + model.capabilities.supports.max_thinking_budget ?? 0, + (model.capabilities.limits.max_output_tokens ?? 0) - 1, + ) + if (maxThinkingBudget > 0 && thinking.budget_tokens !== undefined) { + return Math.min(thinking.budget_tokens, maxThinkingBudget) + } + } + return undefined +} + function translateModelName(model: string): string { // Subagent requests use a specific model number which Copilot doesn't support if (model.startsWith("claude-sonnet-4-")) { return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4") - } else if (model.startsWith("claude-opus-")) { + } else if (model.startsWith("claude-opus-4-")) { return model.replace(/^claude-opus-4-.*/, "claude-opus-4") } return model @@ -59,13 +84,14 @@ function translateModelName(model: string): string { function translateAnthropicMessagesToOpenAI( anthropicMessages: Array, system: string | Array | undefined, + modelId: string, ): Array { const systemMessages = handleSystemPrompt(system) const otherMessages = anthropicMessages.flatMap((message) => message.role === "user" ? handleUserMessage(message) - : handleAssistantMessage(message), + : handleAssistantMessage(message, modelId), ) return [...systemMessages, ...otherMessages] @@ -125,6 +151,7 @@ function handleUserMessage(message: AnthropicUserMessage): Array { function handleAssistantMessage( message: AnthropicAssistantMessage, + modelId: string, ): Array { if (!Array.isArray(message.content)) { return [ @@ -139,14 +166,28 @@ function handleAssistantMessage( (block): block is AnthropicToolUseBlock => block.type === "tool_use", ) - const thinkingBlocks = message.content.filter( + let thinkingBlocks = message.content.filter( (block): block is AnthropicThinkingBlock => block.type === "thinking", ) - const allThinkingContent = thinkingBlocks + if (modelId.startsWith("claude")) { + thinkingBlocks = thinkingBlocks.filter( + (b) => + b.thinking + && b.thinking.length > 0 + && b.signature + && b.signature.length > 0 + // gpt signature has @ in it, so filter those out for claude models + && !b.signature.includes("@"), + ) + } + + const thinkingContents = thinkingBlocks .filter((b) => b.thinking && b.thinking.length > 0) .map((b) => b.thinking) - .join("\n\n") + + const allThinkingContent = + thinkingContents.length > 0 ? thinkingContents.join("\n\n") : undefined const signature = thinkingBlocks.find( (b) => b.signature && b.signature.length > 0, diff --git a/src/routes/messages/stream-translation.ts b/src/routes/messages/stream-translation.ts index 6002d5104..b492d10fc 100644 --- a/src/routes/messages/stream-translation.ts +++ b/src/routes/messages/stream-translation.ts @@ -212,6 +212,30 @@ function handleContent( }, }) } + + // handle for claude model + if ( + delta.content === "" + && delta.reasoning_opaque + && delta.reasoning_opaque.length > 0 + ) { + events.push( + { + type: "content_block_delta", + index: state.contentBlockIndex, + delta: { + type: "signature_delta", + signature: delta.reasoning_opaque, + }, + }, + { + type: "content_block_stop", + index: state.contentBlockIndex, + }, + ) + state.contentBlockIndex++ + state.thinkingBlockOpen = false + } } function handleMessageStart( @@ -313,25 +337,6 @@ function handleThinkingText( thinking: delta.reasoning_text, }, }) - - if (delta.reasoning_opaque && delta.reasoning_opaque.length > 0) { - events.push( - { - type: "content_block_delta", - index: state.contentBlockIndex, - delta: { - type: "signature_delta", - signature: delta.reasoning_opaque, - }, - }, - { - type: "content_block_stop", - index: state.contentBlockIndex, - }, - ) - state.contentBlockIndex++ - state.thinkingBlockOpen = false - } } } diff --git a/src/services/copilot/create-chat-completions.ts b/src/services/copilot/create-chat-completions.ts index e848b27a3..2713a80fd 100644 --- a/src/services/copilot/create-chat-completions.ts +++ b/src/services/copilot/create-chat-completions.ts @@ -152,6 +152,7 @@ export interface ChatCompletionsPayload { | { type: "function"; function: { name: string } } | null user?: string | null + thinking_budget?: number } export interface Tool { diff --git a/src/services/copilot/get-models.ts b/src/services/copilot/get-models.ts index 3cfa30af0..a7fffe938 100644 --- a/src/services/copilot/get-models.ts +++ b/src/services/copilot/get-models.ts @@ -25,6 +25,8 @@ interface ModelLimits { } interface ModelSupports { + max_thinking_budget?: number + min_thinking_budget?: number tool_calls?: boolean parallel_tool_calls?: boolean dimensions?: boolean From cbe12eb850bd4e65f36c885d8ea68e07789ce522 Mon Sep 17 00:00:00 2001 From: caozhiyuan <568022847@qq.com> Date: Wed, 10 Dec 2025 22:53:10 +0800 Subject: [PATCH 2/4] feat: enhance thinking budget calculation and rename variables for clarity --- src/routes/messages/non-stream-translation.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index 29be0d7fd..e5a59a10e 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -65,7 +65,11 @@ function getThinkingBudget( (model.capabilities.limits.max_output_tokens ?? 0) - 1, ) if (maxThinkingBudget > 0 && thinking.budget_tokens !== undefined) { - return Math.min(thinking.budget_tokens, maxThinkingBudget) + const budgetTokens = Math.min(thinking.budget_tokens, maxThinkingBudget) + return Math.max( + budgetTokens, + model.capabilities.supports.min_thinking_budget ?? 1024, + ) } } return undefined @@ -322,13 +326,13 @@ export function translateToAnthropic( // Process all choices to extract text and tool use blocks for (const choice of response.choices) { const textBlocks = getAnthropicTextBlocks(choice.message.content) - const thingBlocks = getAnthropicThinkBlocks( + const thinkBlocks = getAnthropicThinkBlocks( choice.message.reasoning_text, choice.message.reasoning_opaque, ) const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls) - assistantContentBlocks.push(...thingBlocks, ...textBlocks, ...toolUseBlocks) + assistantContentBlocks.push(...thinkBlocks, ...textBlocks, ...toolUseBlocks) // Use the finish_reason from the first choice, or prioritize tool_calls if (choice.finish_reason === "tool_calls" || stopReason === "stop") { From ebcacb20c5c287972879149595535c50c5d6d0b8 Mon Sep 17 00:00:00 2001 From: "Jeffrey.Cao" Date: Thu, 11 Dec 2025 10:12:41 +0800 Subject: [PATCH 3/4] feat: update Copilot version and API version in api-config; adjust fallback VSCode version --- src/lib/api-config.ts | 4 ++-- src/services/get-vscode-version.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 83bce92ad..2006c57c2 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -7,11 +7,11 @@ export const standardHeaders = () => ({ accept: "application/json", }) -const COPILOT_VERSION = "0.26.7" +const COPILOT_VERSION = "0.33.5" const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}` const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` -const API_VERSION = "2025-04-01" +const API_VERSION = "2025-10-01" export const copilotBaseUrl = (state: State) => state.accountType === "individual" ? diff --git a/src/services/get-vscode-version.ts b/src/services/get-vscode-version.ts index 6078f09b5..5e3cef796 100644 --- a/src/services/get-vscode-version.ts +++ b/src/services/get-vscode-version.ts @@ -1,4 +1,4 @@ -const FALLBACK = "1.104.3" +const FALLBACK = "1.106.3" export async function getVSCodeVersion() { const controller = new AbortController() From 0d6f7aa99af92a748c4109d1e669442f672cc458 Mon Sep 17 00:00:00 2001 From: "Jeffrey.Cao" Date: Thu, 11 Dec 2025 13:21:38 +0800 Subject: [PATCH 4/4] feat: update Copilot version to 0.35.0 and fallback VSCode version to 1.107.0 --- src/lib/api-config.ts | 2 +- src/services/get-vscode-version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 2006c57c2..7124930ec 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -7,7 +7,7 @@ export const standardHeaders = () => ({ accept: "application/json", }) -const COPILOT_VERSION = "0.33.5" +const COPILOT_VERSION = "0.35.0" const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}` const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` diff --git a/src/services/get-vscode-version.ts b/src/services/get-vscode-version.ts index 5e3cef796..89c39dc07 100644 --- a/src/services/get-vscode-version.ts +++ b/src/services/get-vscode-version.ts @@ -1,4 +1,4 @@ -const FALLBACK = "1.106.3" +const FALLBACK = "1.107.0" export async function getVSCodeVersion() { const controller = new AbortController()