1+ import type { Model } from "~/services/copilot/get-models"
2+
3+ import { state } from "~/lib/state"
14import {
25 type ChatCompletionResponse ,
36 type ChatCompletionsPayload ,
@@ -29,11 +32,15 @@ import { mapOpenAIStopReasonToAnthropic } from "./utils"
2932export function translateToOpenAI (
3033 payload : AnthropicMessagesPayload ,
3134) : ChatCompletionsPayload {
35+ const modelId = translateModelName ( payload . model )
36+ const model = state . models ?. data . find ( ( m ) => m . id === modelId )
37+ const thinkingBudget = getThinkingBudget ( payload , model )
3238 return {
33- model : translateModelName ( payload . model ) ,
39+ model : modelId ,
3440 messages : translateAnthropicMessagesToOpenAI (
3541 payload . messages ,
3642 payload . system ,
43+ modelId ,
3744 ) ,
3845 max_tokens : payload . max_tokens ,
3946 stop : payload . stop_sequences ,
@@ -43,14 +50,36 @@ export function translateToOpenAI(
4350 user : payload . metadata ?. user_id ,
4451 tools : translateAnthropicToolsToOpenAI ( payload . tools ) ,
4552 tool_choice : translateAnthropicToolChoiceToOpenAI ( payload . tool_choice ) ,
53+ thinking_budget : thinkingBudget ,
4654 }
4755}
4856
57+ function getThinkingBudget (
58+ payload : AnthropicMessagesPayload ,
59+ model : Model | undefined ,
60+ ) : number | undefined {
61+ const thinking = payload . thinking
62+ if ( model && thinking ) {
63+ const maxThinkingBudget = Math . min (
64+ model . capabilities . supports . max_thinking_budget ?? 0 ,
65+ ( model . capabilities . limits . max_output_tokens ?? 0 ) - 1 ,
66+ )
67+ if ( maxThinkingBudget > 0 && thinking . budget_tokens !== undefined ) {
68+ const budgetTokens = Math . min ( thinking . budget_tokens , maxThinkingBudget )
69+ return Math . max (
70+ budgetTokens ,
71+ model . capabilities . supports . min_thinking_budget ?? 1024 ,
72+ )
73+ }
74+ }
75+ return undefined
76+ }
77+
4978function translateModelName ( model : string ) : string {
5079 // Subagent requests use a specific model number which Copilot doesn't support
5180 if ( model . startsWith ( "claude-sonnet-4-" ) ) {
5281 return model . replace ( / ^ c l a u d e - s o n n e t - 4 - .* / , "claude-sonnet-4" )
53- } else if ( model . startsWith ( "claude-opus-" ) ) {
82+ } else if ( model . startsWith ( "claude-opus-4- " ) ) {
5483 return model . replace ( / ^ c l a u d e - o p u s - 4 - .* / , "claude-opus-4" )
5584 }
5685 return model
@@ -59,13 +88,14 @@ function translateModelName(model: string): string {
5988function translateAnthropicMessagesToOpenAI (
6089 anthropicMessages : Array < AnthropicMessage > ,
6190 system : string | Array < AnthropicTextBlock > | undefined ,
91+ modelId : string ,
6292) : Array < Message > {
6393 const systemMessages = handleSystemPrompt ( system )
6494
6595 const otherMessages = anthropicMessages . flatMap ( ( message ) =>
6696 message . role === "user" ?
6797 handleUserMessage ( message )
68- : handleAssistantMessage ( message ) ,
98+ : handleAssistantMessage ( message , modelId ) ,
6999 )
70100
71101 return [ ...systemMessages , ...otherMessages ]
@@ -125,6 +155,7 @@ function handleUserMessage(message: AnthropicUserMessage): Array<Message> {
125155
126156function handleAssistantMessage (
127157 message : AnthropicAssistantMessage ,
158+ modelId : string ,
128159) : Array < Message > {
129160 if ( ! Array . isArray ( message . content ) ) {
130161 return [
@@ -139,14 +170,28 @@ function handleAssistantMessage(
139170 ( block ) : block is AnthropicToolUseBlock => block . type === "tool_use" ,
140171 )
141172
142- const thinkingBlocks = message . content . filter (
173+ let thinkingBlocks = message . content . filter (
143174 ( block ) : block is AnthropicThinkingBlock => block . type === "thinking" ,
144175 )
145176
146- const allThinkingContent = thinkingBlocks
177+ if ( modelId . startsWith ( "claude" ) ) {
178+ thinkingBlocks = thinkingBlocks . filter (
179+ ( b ) =>
180+ b . thinking
181+ && b . thinking . length > 0
182+ && b . signature
183+ && b . signature . length > 0
184+ // gpt signature has @ in it, so filter those out for claude models
185+ && ! b . signature . includes ( "@" ) ,
186+ )
187+ }
188+
189+ const thinkingContents = thinkingBlocks
147190 . filter ( ( b ) => b . thinking && b . thinking . length > 0 )
148191 . map ( ( b ) => b . thinking )
149- . join ( "\n\n" )
192+
193+ const allThinkingContent =
194+ thinkingContents . length > 0 ? thinkingContents . join ( "\n\n" ) : undefined
150195
151196 const signature = thinkingBlocks . find (
152197 ( b ) => b . signature && b . signature . length > 0 ,
@@ -281,13 +326,13 @@ export function translateToAnthropic(
281326 // Process all choices to extract text and tool use blocks
282327 for ( const choice of response . choices ) {
283328 const textBlocks = getAnthropicTextBlocks ( choice . message . content )
284- const thingBlocks = getAnthropicThinkBlocks (
329+ const thinkBlocks = getAnthropicThinkBlocks (
285330 choice . message . reasoning_text ,
286331 choice . message . reasoning_opaque ,
287332 )
288333 const toolUseBlocks = getAnthropicToolUseBlocks ( choice . message . tool_calls )
289334
290- assistantContentBlocks . push ( ...thingBlocks , ...textBlocks , ...toolUseBlocks )
335+ assistantContentBlocks . push ( ...thinkBlocks , ...textBlocks , ...toolUseBlocks )
291336
292337 // Use the finish_reason from the first choice, or prioritize tool_calls
293338 if ( choice . finish_reason === "tool_calls" || stopReason === "stop" ) {
0 commit comments