Skip to content

Commit 0fda9ab

Browse files
authored
Merge pull request #20 from caozhiyuan/all
czy
2 parents aefb29b + 72cea14 commit 0fda9ab

6 files changed

Lines changed: 104 additions & 15 deletions

File tree

src/lib/api-config.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`
1414
const API_VERSION = "2025-10-01"
1515

1616
export const copilotBaseUrl = (state: State) =>
17-
`https://api.${state.accountType}.githubcopilot.com`
18-
17+
state.accountType === "individual" ?
18+
"https://api.githubcopilot.com"
19+
: `https://api.${state.accountType}.githubcopilot.com`
1920
export const copilotHeaders = (state: State, vision: boolean = false) => {
2021
const headers: Record<string, string> = {
2122
Authorization: `Bearer ${state.copilotToken}`,
@@ -24,7 +25,7 @@ export const copilotHeaders = (state: State, vision: boolean = false) => {
2425
"editor-version": `vscode/${state.vsCodeVersion}`,
2526
"editor-plugin-version": EDITOR_PLUGIN_VERSION,
2627
"user-agent": USER_AGENT,
27-
"openai-intent": "conversation-panel",
28+
"openai-intent": "conversation-agent",
2829
"x-github-api-version": API_VERSION,
2930
"x-request-id": randomUUID(),
3031
"x-vscode-user-agent-library-version": "electron-fetch",

src/lib/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface AppConfig {
1010
string,
1111
"none" | "minimal" | "low" | "medium" | "high" | "xhigh"
1212
>
13+
useFunctionApplyPatch?: boolean
1314
}
1415

1516
const gpt5ExplorationPrompt = `## Exploration and reading files
@@ -28,6 +29,7 @@ const defaultConfig: AppConfig = {
2829
modelReasoningEfforts: {
2930
"gpt-5-mini": "low",
3031
},
32+
useFunctionApplyPatch: true,
3133
}
3234

3335
let cachedConfig: AppConfig | null = null

src/routes/messages/non-stream-translation.ts

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
import {
1515
type AnthropicAssistantContentBlock,
1616
type AnthropicAssistantMessage,
17-
type AnthropicMessage,
1817
type AnthropicMessagesPayload,
1918
type AnthropicResponse,
2019
type AnthropicTextBlock,
@@ -38,9 +37,9 @@ export function translateToOpenAI(
3837
return {
3938
model: modelId,
4039
messages: translateAnthropicMessagesToOpenAI(
41-
payload.messages,
42-
payload.system,
40+
payload,
4341
modelId,
42+
thinkingBudget,
4443
),
4544
max_tokens: payload.max_tokens,
4645
stop: payload.stop_sequences,
@@ -86,32 +85,74 @@ function translateModelName(model: string): string {
8685
}
8786

8887
function translateAnthropicMessagesToOpenAI(
89-
anthropicMessages: Array<AnthropicMessage>,
90-
system: string | Array<AnthropicTextBlock> | undefined,
88+
payload: AnthropicMessagesPayload,
9189
modelId: string,
90+
thinkingBudget: number | undefined,
9291
): Array<Message> {
93-
const systemMessages = handleSystemPrompt(system)
94-
95-
const otherMessages = anthropicMessages.flatMap((message) =>
92+
const systemMessages = handleSystemPrompt(
93+
payload.system,
94+
modelId,
95+
thinkingBudget,
96+
)
97+
const otherMessages = payload.messages.flatMap((message) =>
9698
message.role === "user" ?
9799
handleUserMessage(message)
98100
: handleAssistantMessage(message, modelId),
99101
)
100-
102+
if (modelId.startsWith("claude") && thinkingBudget) {
103+
const reminder =
104+
"<system-reminder>you MUST follow interleaved_thinking_protocol</system-reminder>"
105+
const firstUserIndex = otherMessages.findIndex((m) => m.role === "user")
106+
if (firstUserIndex !== -1) {
107+
const userMessage = otherMessages[firstUserIndex]
108+
if (typeof userMessage.content === "string") {
109+
userMessage.content = reminder + "\n\n" + userMessage.content
110+
} else if (Array.isArray(userMessage.content)) {
111+
userMessage.content = [
112+
{ type: "text", text: reminder },
113+
...userMessage.content,
114+
] as Array<ContentPart>
115+
}
116+
}
117+
}
101118
return [...systemMessages, ...otherMessages]
102119
}
103120

104121
function handleSystemPrompt(
105122
system: string | Array<AnthropicTextBlock> | undefined,
123+
modelId: string,
124+
thinkingBudget: number | undefined,
106125
): Array<Message> {
107126
if (!system) {
108127
return []
109128
}
110129

130+
let extraPrompt = ""
131+
if (modelId.startsWith("claude") && thinkingBudget) {
132+
extraPrompt = `
133+
<interleaved_thinking_protocol>
134+
ABSOLUTE REQUIREMENT - NON-NEGOTIABLE:
135+
The current thinking_mode is interleaved, Whenever you have the result of a function call, think carefully , MUST output a thinking block
136+
RULES:
137+
Tool result → thinking block (ALWAYS, no exceptions)
138+
This is NOT optional - it is a hard requirement
139+
The thinking block must contain substantive reasoning (minimum 3-5 sentences)
140+
Think about: what the results mean, what to do next, how to answer the user
141+
NEVER skip this step, even if the result seems simple or obvious
142+
</interleaved_thinking_protocol>`
143+
}
144+
111145
if (typeof system === "string") {
112-
return [{ role: "system", content: system }]
146+
return [{ role: "system", content: system + extraPrompt }]
113147
} else {
114-
const systemText = system.map((block) => block.text).join("\n\n")
148+
const systemText = system
149+
.map((block, index) => {
150+
if (index === 0) {
151+
return block.text + extraPrompt
152+
}
153+
return block.text
154+
})
155+
.join("\n\n")
115156
return [{ role: "system", content: systemText }]
116157
}
117158
}

src/routes/messages/stream-translation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ function handleContent(
218218
delta.content === ""
219219
&& delta.reasoning_opaque
220220
&& delta.reasoning_opaque.length > 0
221+
&& state.thinkingBlockOpen
221222
) {
222223
events.push(
223224
{
@@ -317,6 +318,15 @@ function handleThinkingText(
317318
events: Array<AnthropicStreamEventData>,
318319
) {
319320
if (delta.reasoning_text && delta.reasoning_text.length > 0) {
321+
// compatible with copilot API returning content->reasoning_text->reasoning_opaque in different deltas
322+
// this is an extremely abnormal situation, probably a server-side bug
323+
// only occurs in the claude model, with a very low probability of occurrence
324+
if (state.contentBlockOpen) {
325+
delta.content = delta.reasoning_text
326+
delta.reasoning_text = undefined
327+
return
328+
}
329+
320330
if (!state.thinkingBlockOpen) {
321331
events.push({
322332
type: "content_block_start",

src/routes/responses/handler.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import consola from "consola"
44
import { streamSSE } from "hono/streaming"
55

66
import { awaitApproval } from "~/lib/approval"
7+
import { getConfig } from "~/lib/config"
78
import { createHandlerLogger } from "~/lib/logger"
89
import { checkRateLimit } from "~/lib/rate-limit"
910
import { state } from "~/lib/state"
@@ -70,6 +71,8 @@ export const handleResponses = async (c: Context) => {
7071
const payload = await c.req.json<ResponsesPayload>()
7172
logger.debug("Responses request payload:", JSON.stringify(payload))
7273

74+
useFunctionApplyPatch(payload)
75+
7376
const selectedModel = state.models?.data.find(
7477
(model) => model.id === payload.model,
7578
)
@@ -146,3 +149,35 @@ const isAsyncIterable = <T>(value: unknown): value is AsyncIterable<T> =>
146149

147150
const isStreamingRequested = (payload: ResponsesPayload): boolean =>
148151
Boolean(payload.stream)
152+
153+
const useFunctionApplyPatch = (payload: ResponsesPayload): void => {
154+
const config = getConfig()
155+
const useFunctionApplyPatch = config.useFunctionApplyPatch ?? true
156+
if (useFunctionApplyPatch) {
157+
logger.debug("Using function tool apply_patch for responses")
158+
if (Array.isArray(payload.tools)) {
159+
const toolsArr = payload.tools
160+
for (let i = 0; i < toolsArr.length; i++) {
161+
const t = toolsArr[i]
162+
if (t.type === "custom" && t.name === "apply_patch") {
163+
toolsArr[i] = {
164+
type: "function",
165+
name: t.name,
166+
description: "Use the `apply_patch` tool to edit files",
167+
parameters: {
168+
type: "object",
169+
properties: {
170+
input: {
171+
type: "string",
172+
description: "The entire contents of the apply_patch command",
173+
},
174+
},
175+
required: ["input"],
176+
},
177+
strict: false,
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}

src/services/copilot/create-responses.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export interface ToolChoiceFunction {
3333
type: "function"
3434
}
3535

36-
export type Tool = FunctionTool
36+
export type Tool = FunctionTool | Record<string, unknown>
3737

3838
export interface FunctionTool {
3939
name: string

0 commit comments

Comments
 (0)