Skip to content

Commit 29668ce

Browse files
committed
feat: support copilot reasoning_opaque and reasoning_text
1 parent 0ea08fe commit 29668ce

File tree

7 files changed

+280
-96
lines changed

7 files changed

+280
-96
lines changed

src/routes/messages/anthropic-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export interface AnthropicStreamState {
196196
messageStartSent: boolean
197197
contentBlockIndex: number
198198
contentBlockOpen: boolean
199+
thinkingBlockOpen: boolean
199200
toolCalls: {
200201
[openAIToolIndex: number]: {
201202
id: string

src/routes/messages/handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export async function handleCompletion(c: Context) {
6060
contentBlockIndex: 0,
6161
contentBlockOpen: false,
6262
toolCalls: {},
63+
thinkingBlockOpen: false,
6364
}
6465

6566
for await (const rawEvent of response) {

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

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -139,25 +139,26 @@ function handleAssistantMessage(
139139
(block): block is AnthropicToolUseBlock => block.type === "tool_use",
140140
)
141141

142-
const textBlocks = message.content.filter(
143-
(block): block is AnthropicTextBlock => block.type === "text",
144-
)
145-
146142
const thinkingBlocks = message.content.filter(
147143
(block): block is AnthropicThinkingBlock => block.type === "thinking",
148144
)
149145

150-
// Combine text and thinking blocks, as OpenAI doesn't have separate thinking blocks
151-
const allTextContent = [
152-
...textBlocks.map((b) => b.text),
153-
...thinkingBlocks.map((b) => b.thinking),
154-
].join("\n\n")
146+
const allThinkingContent = thinkingBlocks
147+
.filter((b) => b.thinking && b.thinking.length > 0)
148+
.map((b) => b.thinking)
149+
.join("\n\n")
150+
151+
const signature = thinkingBlocks.find(
152+
(b) => b.signature && b.signature.length > 0,
153+
)?.signature
155154

156155
return toolUseBlocks.length > 0 ?
157156
[
158157
{
159158
role: "assistant",
160-
content: allTextContent || null,
159+
content: mapContent(message.content),
160+
reasoning_text: allThinkingContent,
161+
reasoning_opaque: signature,
161162
tool_calls: toolUseBlocks.map((toolUse) => ({
162163
id: toolUse.id,
163164
type: "function",
@@ -172,6 +173,8 @@ function handleAssistantMessage(
172173
{
173174
role: "assistant",
174175
content: mapContent(message.content),
176+
reasoning_text: allThinkingContent,
177+
reasoning_opaque: signature,
175178
},
176179
]
177180
}
@@ -191,11 +194,8 @@ function mapContent(
191194
const hasImage = content.some((block) => block.type === "image")
192195
if (!hasImage) {
193196
return content
194-
.filter(
195-
(block): block is AnthropicTextBlock | AnthropicThinkingBlock =>
196-
block.type === "text" || block.type === "thinking",
197-
)
198-
.map((block) => (block.type === "text" ? block.text : block.thinking))
197+
.filter((block): block is AnthropicTextBlock => block.type === "text")
198+
.map((block) => block.text)
199199
.join("\n\n")
200200
}
201201

@@ -204,12 +204,6 @@ function mapContent(
204204
switch (block.type) {
205205
case "text": {
206206
contentParts.push({ type: "text", text: block.text })
207-
208-
break
209-
}
210-
case "thinking": {
211-
contentParts.push({ type: "text", text: block.thinking })
212-
213207
break
214208
}
215209
case "image": {
@@ -219,7 +213,6 @@ function mapContent(
219213
url: `data:${block.source.media_type};base64,${block.source.data}`,
220214
},
221215
})
222-
223216
break
224217
}
225218
// No default
@@ -282,34 +275,32 @@ export function translateToAnthropic(
282275
response: ChatCompletionResponse,
283276
): AnthropicResponse {
284277
// Merge content from all choices
285-
const allTextBlocks: Array<AnthropicTextBlock> = []
286-
const allToolUseBlocks: Array<AnthropicToolUseBlock> = []
287-
let stopReason: "stop" | "length" | "tool_calls" | "content_filter" | null =
288-
null // default
289-
stopReason = response.choices[0]?.finish_reason ?? stopReason
278+
const assistantContentBlocks: Array<AnthropicAssistantContentBlock> = []
279+
let stopReason = response.choices[0]?.finish_reason ?? null
290280

291281
// Process all choices to extract text and tool use blocks
292282
for (const choice of response.choices) {
293283
const textBlocks = getAnthropicTextBlocks(choice.message.content)
284+
const thingBlocks = getAnthropicThinkBlocks(
285+
choice.message.reasoning_text,
286+
choice.message.reasoning_opaque,
287+
)
294288
const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls)
295289

296-
allTextBlocks.push(...textBlocks)
297-
allToolUseBlocks.push(...toolUseBlocks)
290+
assistantContentBlocks.push(...textBlocks, ...thingBlocks, ...toolUseBlocks)
298291

299292
// Use the finish_reason from the first choice, or prioritize tool_calls
300293
if (choice.finish_reason === "tool_calls" || stopReason === "stop") {
301294
stopReason = choice.finish_reason
302295
}
303296
}
304297

305-
// Note: GitHub Copilot doesn't generate thinking blocks, so we don't include them in responses
306-
307298
return {
308299
id: response.id,
309300
type: "message",
310301
role: "assistant",
311302
model: response.model,
312-
content: [...allTextBlocks, ...allToolUseBlocks],
303+
content: assistantContentBlocks,
313304
stop_reason: mapOpenAIStopReasonToAnthropic(stopReason),
314305
stop_sequence: null,
315306
usage: {
@@ -342,6 +333,31 @@ function getAnthropicTextBlocks(
342333
return []
343334
}
344335

336+
function getAnthropicThinkBlocks(
337+
reasoningText: string | null | undefined,
338+
reasoningOpaque: string | null | undefined,
339+
): Array<AnthropicThinkingBlock> {
340+
if (reasoningText) {
341+
return [
342+
{
343+
type: "thinking",
344+
thinking: reasoningText,
345+
signature: "",
346+
},
347+
]
348+
}
349+
if (reasoningOpaque) {
350+
return [
351+
{
352+
type: "thinking",
353+
thinking: "",
354+
signature: reasoningOpaque,
355+
},
356+
]
357+
}
358+
return []
359+
}
360+
345361
function getAnthropicToolUseBlocks(
346362
toolCalls: Array<ToolCall> | undefined,
347363
): Array<AnthropicToolUseBlock> {

0 commit comments

Comments
 (0)