@@ -51,6 +51,10 @@ func (s *Server) handleAnthropicMessages(w http.ResponseWriter, r *http.Request)
5151 s .handleAnthropicMessagesWithTools (w , r , req , eng , opts , inputTokens , id )
5252 return
5353 }
54+ if proxy , ok := eng .(inference.ChatCompletionProxier ); ok {
55+ s .handleAnthropicMessagesProxy (w , r , req , proxy , opts , inputTokens , id )
56+ return
57+ }
5458
5559 messages := anthropicMessagesToInference (req )
5660
@@ -117,6 +121,76 @@ func (s *Server) handleAnthropicMessages(w http.ResponseWriter, r *http.Request)
117121 writeJSON (w , http .StatusOK , buildAnthropicMessageResponse (id , req .Model , response , inputTokens ))
118122}
119123
124+ func (s * Server ) handleAnthropicMessagesProxy (
125+ w http.ResponseWriter ,
126+ r * http.Request ,
127+ req api.AnthropicMessageRequest ,
128+ proxy inference.ChatCompletionProxier ,
129+ opts inference.Options ,
130+ inputTokens int ,
131+ id string ,
132+ ) {
133+ reqBody , err := anthropicRequestToProxyBody (req , opts , false )
134+ if err != nil {
135+ writeAnthropicError (w , http .StatusBadRequest , err .Error ())
136+ return
137+ }
138+
139+ resp , err := proxy .ChatCompletion (r .Context (), reqBody )
140+ if err != nil {
141+ if req .Stream {
142+ w .Header ().Set ("Content-Type" , "text/event-stream" )
143+ w .Header ().Set ("Cache-Control" , "no-cache" )
144+ w .Header ().Set ("Connection" , "keep-alive" )
145+ writeAnthropicSSE (w , "error" , anthropicErrorPayloadFromInferenceError (err ))
146+ return
147+ }
148+ writeAnthropicInferenceError (w , err )
149+ return
150+ }
151+ defer resp .Body .Close ()
152+
153+ var openAIResp api.OpenAIChatResponse
154+ if err := json .NewDecoder (resp .Body ).Decode (& openAIResp ); err != nil {
155+ message := "decoding response: " + err .Error ()
156+ if req .Stream {
157+ w .Header ().Set ("Content-Type" , "text/event-stream" )
158+ w .Header ().Set ("Cache-Control" , "no-cache" )
159+ w .Header ().Set ("Connection" , "keep-alive" )
160+ writeAnthropicSSE (w , "error" , anthropicErrorPayloadWithType ("api_error" , message ))
161+ return
162+ }
163+ writeAnthropicErrorWithType (w , http .StatusInternalServerError , "api_error" , message )
164+ return
165+ }
166+ if openAIResp .Model == "" {
167+ openAIResp .Model = req .Model
168+ }
169+
170+ anthropicResp , err := anthropicMessageResponseFromOpenAI (id , req .Model , openAIResp , inputTokens )
171+ if err != nil {
172+ if req .Stream {
173+ w .Header ().Set ("Content-Type" , "text/event-stream" )
174+ w .Header ().Set ("Cache-Control" , "no-cache" )
175+ w .Header ().Set ("Connection" , "keep-alive" )
176+ writeAnthropicSSE (w , "error" , anthropicErrorPayloadWithType ("api_error" , err .Error ()))
177+ return
178+ }
179+ writeAnthropicErrorWithType (w , http .StatusInternalServerError , "api_error" , err .Error ())
180+ return
181+ }
182+
183+ if ! req .Stream {
184+ writeJSON (w , http .StatusOK , anthropicResp )
185+ return
186+ }
187+
188+ w .Header ().Set ("Content-Type" , "text/event-stream" )
189+ w .Header ().Set ("Cache-Control" , "no-cache" )
190+ w .Header ().Set ("Connection" , "keep-alive" )
191+ writeAnthropicStreamedMessage (w , anthropicResp )
192+ }
193+
120194func (s * Server ) handleAnthropicMessagesWithTools (
121195 w http.ResponseWriter ,
122196 r * http.Request ,
@@ -293,10 +367,11 @@ func anthropicMessagesToInference(req api.AnthropicMessageRequest) []inference.M
293367 messages = append (messages , inference.Message {Role : "system" , Content : system })
294368 }
295369 for _ , item := range req .Messages {
296- text := anthropicContentText ( item .Content )
370+ text , reasoning := anthropicMessageTextAndReasoning ( item . Role , item .Content )
297371 messages = append (messages , inference.Message {
298- Role : item .Role ,
299- Content : text ,
372+ Role : item .Role ,
373+ Content : text ,
374+ ReasoningContent : reasoning ,
300375 })
301376 }
302377 return messages
@@ -378,6 +453,7 @@ func anthropicMessageToOpenAIEntries(message api.AnthropicMessage, messageIndex
378453func anthropicContentBlockEntriesToOpenAI (role string , blocks []interface {}, messageIndex int , pendingToolNames map [string ]string ) ([]map [string ]interface {}, error ) {
379454 entries := make ([]map [string ]interface {}, 0 , len (blocks ))
380455 textParts := make ([]string , 0 , len (blocks ))
456+ reasoningParts := make ([]string , 0 )
381457 assistantToolCalls := make ([]map [string ]interface {}, 0 )
382458 assistantToolIndex := 0
383459
@@ -406,7 +482,11 @@ func anthropicContentBlockEntriesToOpenAI(role string, blocks []interface{}, mes
406482 }
407483 case "thinking" :
408484 if thinking , _ := block ["thinking" ].(string ); strings .TrimSpace (thinking ) != "" {
409- textParts = append (textParts , thinking )
485+ if role == "assistant" {
486+ reasoningParts = append (reasoningParts , thinking )
487+ } else {
488+ textParts = append (textParts , thinking )
489+ }
410490 }
411491 case "tool_use" :
412492 if role != "assistant" {
@@ -460,7 +540,7 @@ func anthropicContentBlockEntriesToOpenAI(role string, blocks []interface{}, mes
460540 }
461541
462542 if role == "assistant" {
463- if len (textParts ) == 0 && len (assistantToolCalls ) == 0 {
543+ if len (textParts ) == 0 && len (reasoningParts ) == 0 && len ( assistantToolCalls ) == 0 {
464544 return entries , nil
465545 }
466546 assistant := map [string ]interface {}{"role" : "assistant" }
@@ -472,6 +552,9 @@ func anthropicContentBlockEntriesToOpenAI(role string, blocks []interface{}, mes
472552 if len (assistantToolCalls ) > 0 {
473553 assistant ["tool_calls" ] = assistantToolCalls
474554 }
555+ if len (reasoningParts ) > 0 {
556+ assistant ["reasoning_content" ] = strings .Join (reasoningParts , "\n " )
557+ }
475558 entries = append (entries , assistant )
476559 return entries , nil
477560 }
@@ -605,7 +688,13 @@ func anthropicContentText(content interface{}) string {
605688}
606689
607690func anthropicContentBlocksFromOpenAIMessage (msg * api.Message ) []api.AnthropicContentBlock {
608- blocks := make ([]api.AnthropicContentBlock , 0 , 1 + len (msg .ToolCalls ))
691+ blocks := make ([]api.AnthropicContentBlock , 0 , 2 + len (msg .ToolCalls ))
692+ if reasoning := strings .TrimSpace (msg .ReasoningContent ); reasoning != "" {
693+ blocks = append (blocks , api.AnthropicContentBlock {
694+ Type : "thinking" ,
695+ Thinking : reasoning ,
696+ })
697+ }
609698 if text := contentAsString (msg .Content ); strings .TrimSpace (text ) != "" {
610699 blocks = append (blocks , api.AnthropicContentBlock {
611700 Type : "text" ,
@@ -631,6 +720,48 @@ func anthropicContentBlocksFromOpenAIMessage(msg *api.Message) []api.AnthropicCo
631720 return blocks
632721}
633722
723+ func anthropicMessageTextAndReasoning (role string , content interface {}) (string , string ) {
724+ if role != "assistant" {
725+ return anthropicContentText (content ), ""
726+ }
727+ switch value := content .(type ) {
728+ case nil :
729+ return "" , ""
730+ case string :
731+ return value , ""
732+ case []interface {}:
733+ textParts := make ([]string , 0 , len (value ))
734+ reasoningParts := make ([]string , 0 )
735+ for _ , raw := range value {
736+ text , reasoning := anthropicContentPartTextAndReasoning (raw )
737+ if text != "" {
738+ textParts = append (textParts , text )
739+ }
740+ if reasoning != "" {
741+ reasoningParts = append (reasoningParts , reasoning )
742+ }
743+ }
744+ return strings .Join (textParts , "\n " ), strings .Join (reasoningParts , "\n " )
745+ case map [string ]interface {}:
746+ return anthropicContentPartTextAndReasoning (value )
747+ default :
748+ return anthropicContentText (value ), ""
749+ }
750+ }
751+
752+ func anthropicContentPartTextAndReasoning (content interface {}) (string , string ) {
753+ switch value := content .(type ) {
754+ case map [string ]interface {}:
755+ if stringValue (value ["type" ]) == "thinking" {
756+ thinking , _ := value ["thinking" ].(string )
757+ return "" , thinking
758+ }
759+ return anthropicContentText (value ), ""
760+ default :
761+ return anthropicContentText (value ), ""
762+ }
763+ }
764+
634765func anthropicContentBlocksText (blocks []api.AnthropicContentBlock ) string {
635766 parts := make ([]string , 0 , len (blocks ))
636767 for _ , block := range blocks {
@@ -731,6 +862,17 @@ func writeAnthropicStreamedMessage(w http.ResponseWriter, resp api.AnthropicMess
731862 },
732863 })
733864 }
865+ case "thinking" :
866+ if strings .TrimSpace (block .Thinking ) != "" {
867+ writeAnthropicSSE (w , "content_block_delta" , map [string ]interface {}{
868+ "type" : "content_block_delta" ,
869+ "index" : i ,
870+ "delta" : map [string ]interface {}{
871+ "type" : "thinking_delta" ,
872+ "thinking" : block .Thinking ,
873+ },
874+ })
875+ }
734876 }
735877 writeAnthropicSSE (w , "content_block_stop" , map [string ]interface {}{
736878 "type" : "content_block_stop" ,
0 commit comments