From dcafbe1b434e5526f49fd49876896c6026f70d5e Mon Sep 17 00:00:00 2001 From: caozhiyuan <568022847@qq.com> Date: Sat, 13 Dec 2025 09:22:54 +0800 Subject: [PATCH 1/7] fix: simplify copilotBaseUrl logic and correct openai-intent header value --- src/lib/api-config.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 7124930ec..5b2c611fa 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -14,9 +14,8 @@ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` const API_VERSION = "2025-10-01" export const copilotBaseUrl = (state: State) => - state.accountType === "individual" ? - "https://api.githubcopilot.com" - : `https://api.${state.accountType}.githubcopilot.com` + `https://api.${state.accountType}.githubcopilot.com` + export const copilotHeaders = (state: State, vision: boolean = false) => { const headers: Record = { Authorization: `Bearer ${state.copilotToken}`, @@ -25,7 +24,7 @@ export const copilotHeaders = (state: State, vision: boolean = false) => { "editor-version": `vscode/${state.vsCodeVersion}`, "editor-plugin-version": EDITOR_PLUGIN_VERSION, "user-agent": USER_AGENT, - "openai-intent": "conversation-panel", + "openai-intent": "conversation-agent", "x-github-api-version": API_VERSION, "x-request-id": randomUUID(), "x-vscode-user-agent-library-version": "electron-fetch", From 51752450381c9b101544e09877000e5b8b5c58d0 Mon Sep 17 00:00:00 2001 From: "Jeffrey.Cao" Date: Wed, 31 Dec 2025 12:54:31 +0800 Subject: [PATCH 2/7] feat: interleaved thinking support --- src/routes/messages/non-stream-translation.ts | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index e5a59a10e..b0795d693 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -14,7 +14,6 @@ import { import { type AnthropicAssistantContentBlock, type AnthropicAssistantMessage, - type AnthropicMessage, type AnthropicMessagesPayload, type AnthropicResponse, type AnthropicTextBlock, @@ -38,9 +37,9 @@ export function translateToOpenAI( return { model: modelId, messages: translateAnthropicMessagesToOpenAI( - payload.messages, - payload.system, + payload, modelId, + thinkingBudget, ), max_tokens: payload.max_tokens, stop: payload.stop_sequences, @@ -86,32 +85,53 @@ function translateModelName(model: string): string { } function translateAnthropicMessagesToOpenAI( - anthropicMessages: Array, - system: string | Array | undefined, + payload: AnthropicMessagesPayload, modelId: string, + thinkingBudget: number | undefined, ): Array { - const systemMessages = handleSystemPrompt(system) - - const otherMessages = anthropicMessages.flatMap((message) => + const systemMessages = handleSystemPrompt(payload.system, modelId) + const otherMessages = payload.messages.flatMap((message) => message.role === "user" ? handleUserMessage(message) : handleAssistantMessage(message, modelId), ) - + if (modelId.startsWith("claude") && thinkingBudget) { + const thinkingMessage = { + role: "user", + content: "Please strictly follow Interleaved thinking", + } as Message + return [...systemMessages, thinkingMessage, ...otherMessages] + } return [...systemMessages, ...otherMessages] } function handleSystemPrompt( system: string | Array | undefined, + modelId: string, ): Array { if (!system) { return [] } + let extraPrompt = ` + ## Interleaved thinking + - Interleaved thinking is enabled + - You MUST think after receiving tool results before deciding the next action or final answer. + ` + if (!modelId.startsWith("claude")) { + extraPrompt = "" + } if (typeof system === "string") { - return [{ role: "system", content: system }] + return [{ role: "system", content: system + extraPrompt }] } else { - const systemText = system.map((block) => block.text).join("\n\n") + const systemText = system + .map((block, index) => { + if (index === 0) { + return block.text + extraPrompt + } + return block.text + }) + .join("\n\n") return [{ role: "system", content: systemText }] } } From dd80c8d9ddf75afa797ced2d2a56ecbee409be62 Mon Sep 17 00:00:00 2001 From: "Jeffrey.Cao" Date: Wed, 31 Dec 2025 14:39:57 +0800 Subject: [PATCH 3/7] feat: enhance system prompt handling for interleaved thinking with thinking budget integration --- src/routes/messages/non-stream-translation.ts | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index b0795d693..57edfad27 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -89,7 +89,11 @@ function translateAnthropicMessagesToOpenAI( modelId: string, thinkingBudget: number | undefined, ): Array { - const systemMessages = handleSystemPrompt(payload.system, modelId) + const systemMessages = handleSystemPrompt( + payload.system, + modelId, + thinkingBudget, + ) const otherMessages = payload.messages.flatMap((message) => message.role === "user" ? handleUserMessage(message) @@ -98,7 +102,8 @@ function translateAnthropicMessagesToOpenAI( if (modelId.startsWith("claude") && thinkingBudget) { const thinkingMessage = { role: "user", - content: "Please strictly follow Interleaved thinking", + content: + "Please strictly follow Interleaved thinking", } as Message return [...systemMessages, thinkingMessage, ...otherMessages] } @@ -108,19 +113,21 @@ function translateAnthropicMessagesToOpenAI( function handleSystemPrompt( system: string | Array | undefined, modelId: string, + thinkingBudget: number | undefined, ): Array { if (!system) { return [] } - let extraPrompt = ` - ## Interleaved thinking - - Interleaved thinking is enabled - - You MUST think after receiving tool results before deciding the next action or final answer. - ` - if (!modelId.startsWith("claude")) { - extraPrompt = "" + let extraPrompt = "" + if (modelId.startsWith("claude") && thinkingBudget) { + extraPrompt = ` +## Interleaved thinking +- Interleaved thinking is enabled +- You MUST think after receiving tool results before deciding the next action or final answer. +` } + if (typeof system === "string") { return [{ role: "system", content: system + extraPrompt }] } else { From e45c6db3dfb54194986926ac74439bde97033250 Mon Sep 17 00:00:00 2001 From: caozhiyuan <568022847@qq.com> Date: Fri, 2 Jan 2026 22:21:35 +0800 Subject: [PATCH 4/7] feat: compatible with copilot API returning content->reasoning_text->reasoning_opaque in different deltas --- src/routes/messages/stream-translation.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/routes/messages/stream-translation.ts b/src/routes/messages/stream-translation.ts index b492d10fc..c62ce3ab4 100644 --- a/src/routes/messages/stream-translation.ts +++ b/src/routes/messages/stream-translation.ts @@ -218,6 +218,7 @@ function handleContent( delta.content === "" && delta.reasoning_opaque && delta.reasoning_opaque.length > 0 + && state.thinkingBlockOpen ) { events.push( { @@ -317,6 +318,15 @@ function handleThinkingText( events: Array, ) { if (delta.reasoning_text && delta.reasoning_text.length > 0) { + // compatible with copilot API returning content->reasoning_text->reasoning_opaque in different deltas + // this is an extremely abnormal situation, probably a server-side bug + // only occurs in the claude model, with a very low probability of occurrence + if (state.contentBlockOpen) { + delta.content = delta.reasoning_text + delta.reasoning_text = undefined + return + } + if (!state.thinkingBlockOpen) { events.push({ type: "content_block_start", From 0afccfab6f2ff1cf67348dadaa4b49b6a8d6f204 Mon Sep 17 00:00:00 2001 From: Hyunggyu Jang Date: Thu, 1 Jan 2026 11:38:40 +0900 Subject: [PATCH 5/7] fix(api-config): use default API URL when account type is individual When account type is not specified or set to 'individual', use the default api.githubcopilot.com URL instead of constructing a subdomain-based URL. This restores previous behavior where business users could work without explicitly specifying their account type, as the default URL works for both individual and business accounts. Only constructs account-type-specific URLs (api.business.githubcopilot.com, api.enterprise.githubcopilot.com) when those account types are explicitly specified. --- src/lib/api-config.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts index 5b2c611fa..2c8084559 100644 --- a/src/lib/api-config.ts +++ b/src/lib/api-config.ts @@ -14,8 +14,9 @@ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` const API_VERSION = "2025-10-01" export const copilotBaseUrl = (state: State) => - `https://api.${state.accountType}.githubcopilot.com` - + state.accountType === "individual" ? + "https://api.githubcopilot.com" + : `https://api.${state.accountType}.githubcopilot.com` export const copilotHeaders = (state: State, vision: boolean = false) => { const headers: Record = { Authorization: `Bearer ${state.copilotToken}`, From 67b357a5e5a859804260bf7570e9313c1d620903 Mon Sep 17 00:00:00 2001 From: "Jeffrey.Cao" Date: Sun, 4 Jan 2026 18:35:54 +0800 Subject: [PATCH 6/7] feat: enforce interleaved thinking protocol in message handling --- src/routes/messages/non-stream-translation.ts | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts index 57edfad27..586448503 100644 --- a/src/routes/messages/non-stream-translation.ts +++ b/src/routes/messages/non-stream-translation.ts @@ -100,12 +100,20 @@ function translateAnthropicMessagesToOpenAI( : handleAssistantMessage(message, modelId), ) if (modelId.startsWith("claude") && thinkingBudget) { - const thinkingMessage = { - role: "user", - content: - "Please strictly follow Interleaved thinking", - } as Message - return [...systemMessages, thinkingMessage, ...otherMessages] + const reminder = + "you MUST follow interleaved_thinking_protocol" + const firstUserIndex = otherMessages.findIndex((m) => m.role === "user") + if (firstUserIndex !== -1) { + const userMessage = otherMessages[firstUserIndex] + if (typeof userMessage.content === "string") { + userMessage.content = reminder + "\n\n" + userMessage.content + } else if (Array.isArray(userMessage.content)) { + userMessage.content = [ + { type: "text", text: reminder }, + ...userMessage.content, + ] as Array + } + } } return [...systemMessages, ...otherMessages] } @@ -122,10 +130,16 @@ function handleSystemPrompt( let extraPrompt = "" if (modelId.startsWith("claude") && thinkingBudget) { extraPrompt = ` -## Interleaved thinking -- Interleaved thinking is enabled -- You MUST think after receiving tool results before deciding the next action or final answer. -` + +ABSOLUTE REQUIREMENT - NON-NEGOTIABLE: +The current thinking_mode is interleaved, Whenever you have the result of a function call, think carefully , MUST output a thinking block +RULES: +Tool result → thinking block (ALWAYS, no exceptions) +This is NOT optional - it is a hard requirement +The thinking block must contain substantive reasoning (minimum 3-5 sentences) +Think about: what the results mean, what to do next, how to answer the user +NEVER skip this step, even if the result seems simple or obvious +` } if (typeof system === "string") { From afb7a5c77bdd8a04e57f1c8d210a8659cd28b1f8 Mon Sep 17 00:00:00 2001 From: caozhiyuan <568022847@qq.com> Date: Sat, 10 Jan 2026 18:08:02 +0800 Subject: [PATCH 7/7] feat(config): add useFunctionApplyPatch option and implement patch handling in responses --- src/lib/config.ts | 2 ++ src/routes/responses/handler.ts | 35 ++++++++++++++++++++++++ src/services/copilot/create-responses.ts | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/config.ts b/src/lib/config.ts index dff63eb5c..e44953852 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -10,6 +10,7 @@ export interface AppConfig { string, "none" | "minimal" | "low" | "medium" | "high" | "xhigh" > + useFunctionApplyPatch?: boolean } const gpt5ExplorationPrompt = `## Exploration and reading files @@ -28,6 +29,7 @@ const defaultConfig: AppConfig = { modelReasoningEfforts: { "gpt-5-mini": "low", }, + useFunctionApplyPatch: true, } let cachedConfig: AppConfig | null = null diff --git a/src/routes/responses/handler.ts b/src/routes/responses/handler.ts index 574d61fcf..14a841ac4 100644 --- a/src/routes/responses/handler.ts +++ b/src/routes/responses/handler.ts @@ -3,6 +3,7 @@ import type { Context } from "hono" import { streamSSE } from "hono/streaming" import { awaitApproval } from "~/lib/approval" +import { getConfig } from "~/lib/config" import { createHandlerLogger } from "~/lib/logger" import { checkRateLimit } from "~/lib/rate-limit" import { state } from "~/lib/state" @@ -24,6 +25,8 @@ export const handleResponses = async (c: Context) => { const payload = await c.req.json() logger.debug("Responses request payload:", JSON.stringify(payload)) + useFunctionApplyPatch(payload) + const selectedModel = state.models?.data.find( (model) => model.id === payload.model, ) @@ -78,3 +81,35 @@ const isAsyncIterable = (value: unknown): value is AsyncIterable => const isStreamingRequested = (payload: ResponsesPayload): boolean => Boolean(payload.stream) + +const useFunctionApplyPatch = (payload: ResponsesPayload): void => { + const config = getConfig() + const useFunctionApplyPatch = config.useFunctionApplyPatch ?? true + if (useFunctionApplyPatch) { + logger.debug("Using function tool apply_patch for responses") + if (Array.isArray(payload.tools)) { + const toolsArr = payload.tools + for (let i = 0; i < toolsArr.length; i++) { + const t = toolsArr[i] + if (t.type === "custom" && t.name === "apply_patch") { + toolsArr[i] = { + type: "function", + name: t.name, + description: "Use the `apply_patch` tool to edit files", + parameters: { + type: "object", + properties: { + input: { + type: "string", + description: "The entire contents of the apply_patch command", + }, + }, + required: ["input"], + }, + strict: false, + } + } + } + } + } +} diff --git a/src/services/copilot/create-responses.ts b/src/services/copilot/create-responses.ts index bc24ce544..9982a4d98 100644 --- a/src/services/copilot/create-responses.ts +++ b/src/services/copilot/create-responses.ts @@ -33,7 +33,7 @@ export interface ToolChoiceFunction { type: "function" } -export type Tool = FunctionTool +export type Tool = FunctionTool | Record export interface FunctionTool { name: string