2026-03-06 14:29:22 +08:00
package apicompat
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ---------------------------------------------------------------------------
// AnthropicToResponses tests
// ---------------------------------------------------------------------------
func TestAnthropicToResponses_BasicText ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Stream : true ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
assert . Equal ( t , "gpt-5.2" , resp . Model )
assert . True ( t , resp . Stream )
assert . Equal ( t , 1024 , * resp . MaxOutputTokens )
assert . False ( t , * resp . Store )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 1 )
assert . Equal ( t , "user" , items [ 0 ] . Role )
}
func TestAnthropicToResponses_SystemPrompt ( t * testing . T ) {
t . Run ( "string" , func ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 100 ,
System : json . RawMessage ( ` "You are helpful." ` ) ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 2 )
assert . Equal ( t , "system" , items [ 0 ] . Role )
} )
t . Run ( "array" , func ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 100 ,
System : json . RawMessage ( ` [ { "type":"text","text":"Part 1"}, { "type":"text","text":"Part 2"}] ` ) ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 2 )
assert . Equal ( t , "system" , items [ 0 ] . Role )
// System text should be joined with double newline.
var text string
require . NoError ( t , json . Unmarshal ( items [ 0 ] . Content , & text ) )
assert . Equal ( t , "Part 1\n\nPart 2" , text )
} )
}
func TestAnthropicToResponses_ToolUse ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "What is the weather?" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"text","text":"Let me check."}, { "type":"tool_use","id":"call_1","name":"get_weather","input": { "city":"NYC"}}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` [ { "type":"tool_result","tool_use_id":"call_1","content":"Sunny, 72°F"}] ` ) } ,
} ,
Tools : [ ] AnthropicTool {
{ Name : "get_weather" , Description : "Get weather" , InputSchema : json . RawMessage ( ` { "type":"object","properties": { "city": { "type":"string"}}} ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
// Check tools
require . Len ( t , resp . Tools , 1 )
assert . Equal ( t , "function" , resp . Tools [ 0 ] . Type )
assert . Equal ( t , "get_weather" , resp . Tools [ 0 ] . Name )
// Check input items
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + assistant + function_call + function_call_output = 4
require . Len ( t , items , 4 )
assert . Equal ( t , "user" , items [ 0 ] . Role )
assert . Equal ( t , "assistant" , items [ 1 ] . Role )
assert . Equal ( t , "function_call" , items [ 2 ] . Type )
assert . Equal ( t , "fc_call_1" , items [ 2 ] . CallID )
assert . Equal ( t , "function_call_output" , items [ 3 ] . Type )
assert . Equal ( t , "fc_call_1" , items [ 3 ] . CallID )
assert . Equal ( t , "Sunny, 72°F" , items [ 3 ] . Output )
}
func TestAnthropicToResponses_ThinkingIgnored ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"thinking","thinking":"deep thought"}, { "type":"text","text":"Hi!"}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` "More" ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + assistant(text only, thinking ignored) + user = 3
require . Len ( t , items , 3 )
assert . Equal ( t , "assistant" , items [ 1 ] . Role )
// Assistant content should only have text, not thinking.
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 1 ] . Content , & parts ) )
require . Len ( t , parts , 1 )
assert . Equal ( t , "output_text" , parts [ 0 ] . Type )
assert . Equal ( t , "Hi!" , parts [ 0 ] . Text )
}
func TestAnthropicToResponses_MaxTokensFloor ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 10 , // below minMaxOutputTokens (128)
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
assert . Equal ( t , 128 , * resp . MaxOutputTokens )
}
// ---------------------------------------------------------------------------
// ResponsesToAnthropic (non-streaming) tests
// ---------------------------------------------------------------------------
func TestResponsesToAnthropic_TextOnly ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_123" ,
Model : "gpt-5.2" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "Hello there!" } ,
} ,
} ,
} ,
Usage : & ResponsesUsage { InputTokens : 10 , OutputTokens : 5 , TotalTokens : 15 } ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
assert . Equal ( t , "resp_123" , anth . ID )
assert . Equal ( t , "claude-opus-4-6" , anth . Model )
assert . Equal ( t , "end_turn" , anth . StopReason )
require . Len ( t , anth . Content , 1 )
assert . Equal ( t , "text" , anth . Content [ 0 ] . Type )
assert . Equal ( t , "Hello there!" , anth . Content [ 0 ] . Text )
assert . Equal ( t , 10 , anth . Usage . InputTokens )
assert . Equal ( t , 5 , anth . Usage . OutputTokens )
}
func TestResponsesToAnthropic_ToolUse ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_456" ,
Model : "gpt-5.2" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "Let me check." } ,
} ,
} ,
{
Type : "function_call" ,
CallID : "call_1" ,
Name : "get_weather" ,
Arguments : ` { "city":"NYC"} ` ,
} ,
} ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
assert . Equal ( t , "tool_use" , anth . StopReason )
require . Len ( t , anth . Content , 2 )
assert . Equal ( t , "text" , anth . Content [ 0 ] . Type )
assert . Equal ( t , "tool_use" , anth . Content [ 1 ] . Type )
assert . Equal ( t , "call_1" , anth . Content [ 1 ] . ID )
assert . Equal ( t , "get_weather" , anth . Content [ 1 ] . Name )
}
func TestResponsesToAnthropic_Reasoning ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_789" ,
Model : "gpt-5.2" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "reasoning" ,
Summary : [ ] ResponsesSummary {
{ Type : "summary_text" , Text : "Thinking about the answer..." } ,
} ,
} ,
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "42" } ,
} ,
} ,
} ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
require . Len ( t , anth . Content , 2 )
assert . Equal ( t , "thinking" , anth . Content [ 0 ] . Type )
assert . Equal ( t , "Thinking about the answer..." , anth . Content [ 0 ] . Thinking )
assert . Equal ( t , "text" , anth . Content [ 1 ] . Type )
assert . Equal ( t , "42" , anth . Content [ 1 ] . Text )
}
func TestResponsesToAnthropic_Incomplete ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_inc" ,
Model : "gpt-5.2" ,
Status : "incomplete" ,
IncompleteDetails : & ResponsesIncompleteDetails {
Reason : "max_output_tokens" ,
} ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart { { Type : "output_text" , Text : "Partial..." } } ,
} ,
} ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
assert . Equal ( t , "max_tokens" , anth . StopReason )
}
func TestResponsesToAnthropic_EmptyOutput ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_empty" ,
Model : "gpt-5.2" ,
Status : "completed" ,
Output : [ ] ResponsesOutput { } ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
require . Len ( t , anth . Content , 1 )
assert . Equal ( t , "text" , anth . Content [ 0 ] . Type )
assert . Equal ( t , "" , anth . Content [ 0 ] . Text )
}
// ---------------------------------------------------------------------------
// Streaming: ResponsesEventToAnthropicEvents tests
// ---------------------------------------------------------------------------
func TestStreamingTextOnly ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
// 1. response.created
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse {
ID : "resp_1" ,
Model : "gpt-5.2" ,
} ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "message_start" , events [ 0 ] . Type )
// 2. output_item.added (message)
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_item.added" ,
OutputIndex : 0 ,
Item : & ResponsesOutput { Type : "message" } ,
} , state )
assert . Len ( t , events , 0 ) // message item doesn't emit events
// 3. text delta
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "Hello" ,
} , state )
require . Len ( t , events , 2 ) // content_block_start + content_block_delta
assert . Equal ( t , "content_block_start" , events [ 0 ] . Type )
assert . Equal ( t , "text" , events [ 0 ] . ContentBlock . Type )
assert . Equal ( t , "content_block_delta" , events [ 1 ] . Type )
assert . Equal ( t , "text_delta" , events [ 1 ] . Delta . Type )
assert . Equal ( t , "Hello" , events [ 1 ] . Delta . Text )
// 4. more text
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : " world" ,
} , state )
require . Len ( t , events , 1 ) // only delta, no new block start
assert . Equal ( t , "content_block_delta" , events [ 0 ] . Type )
// 5. text done
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.done" ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
// 6. completed
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage { InputTokens : 10 , OutputTokens : 5 } ,
} ,
} , state )
require . Len ( t , events , 2 ) // message_delta + message_stop
assert . Equal ( t , "message_delta" , events [ 0 ] . Type )
assert . Equal ( t , "end_turn" , events [ 0 ] . Delta . StopReason )
assert . Equal ( t , 10 , events [ 0 ] . Usage . InputTokens )
assert . Equal ( t , 5 , events [ 0 ] . Usage . OutputTokens )
assert . Equal ( t , "message_stop" , events [ 1 ] . Type )
}
func TestStreamingToolCall ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
// 1. response.created
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_2" , Model : "gpt-5.2" } ,
} , state )
// 2. function_call added
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_item.added" ,
OutputIndex : 0 ,
Item : & ResponsesOutput { Type : "function_call" , CallID : "call_1" , Name : "get_weather" } ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_start" , events [ 0 ] . Type )
assert . Equal ( t , "tool_use" , events [ 0 ] . ContentBlock . Type )
assert . Equal ( t , "call_1" , events [ 0 ] . ContentBlock . ID )
// 3. arguments delta
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.function_call_arguments.delta" ,
OutputIndex : 0 ,
Delta : ` { "city": ` ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_delta" , events [ 0 ] . Type )
assert . Equal ( t , "input_json_delta" , events [ 0 ] . Delta . Type )
assert . Equal ( t , ` { "city": ` , events [ 0 ] . Delta . PartialJSON )
// 4. arguments done
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.function_call_arguments.done" ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
// 5. completed with tool_calls
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage { InputTokens : 20 , OutputTokens : 10 } ,
} ,
} , state )
require . Len ( t , events , 2 )
assert . Equal ( t , "tool_use" , events [ 0 ] . Delta . StopReason )
}
func TestStreamingReasoning ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_3" , Model : "gpt-5.2" } ,
} , state )
// reasoning item added
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_item.added" ,
OutputIndex : 0 ,
Item : & ResponsesOutput { Type : "reasoning" } ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_start" , events [ 0 ] . Type )
assert . Equal ( t , "thinking" , events [ 0 ] . ContentBlock . Type )
// reasoning text delta
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.reasoning_summary_text.delta" ,
OutputIndex : 0 ,
Delta : "Let me think..." ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_delta" , events [ 0 ] . Type )
assert . Equal ( t , "thinking_delta" , events [ 0 ] . Delta . Type )
assert . Equal ( t , "Let me think..." , events [ 0 ] . Delta . Thinking )
// reasoning done
events = ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.reasoning_summary_text.done" ,
} , state )
require . Len ( t , events , 1 )
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
}
func TestStreamingIncomplete ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_4" , Model : "gpt-5.2" } ,
} , state )
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "Partial output..." ,
} , state )
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.incomplete" ,
Response : & ResponsesResponse {
Status : "incomplete" ,
IncompleteDetails : & ResponsesIncompleteDetails { Reason : "max_output_tokens" } ,
Usage : & ResponsesUsage { InputTokens : 100 , OutputTokens : 4096 } ,
} ,
} , state )
// Should close the text block + message_delta + message_stop
require . Len ( t , events , 3 )
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
assert . Equal ( t , "message_delta" , events [ 1 ] . Type )
assert . Equal ( t , "max_tokens" , events [ 1 ] . Delta . StopReason )
assert . Equal ( t , "message_stop" , events [ 2 ] . Type )
}
func TestFinalizeStream_NeverStarted ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
events := FinalizeResponsesAnthropicStream ( state )
assert . Nil ( t , events )
}
func TestFinalizeStream_AlreadyCompleted ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
state . MessageStartSent = true
state . MessageStopSent = true
events := FinalizeResponsesAnthropicStream ( state )
assert . Nil ( t , events )
}
func TestFinalizeStream_AbnormalTermination ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
// Simulate a stream that started but never completed
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_5" , Model : "gpt-5.2" } ,
} , state )
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "Interrupted..." ,
} , state )
// Stream ends without response.completed
events := FinalizeResponsesAnthropicStream ( state )
require . Len ( t , events , 3 ) // content_block_stop + message_delta + message_stop
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
assert . Equal ( t , "message_delta" , events [ 1 ] . Type )
assert . Equal ( t , "end_turn" , events [ 1 ] . Delta . StopReason )
assert . Equal ( t , "message_stop" , events [ 2 ] . Type )
}
func TestStreamingEmptyResponse ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_6" , Model : "gpt-5.2" } ,
} , state )
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage { InputTokens : 5 , OutputTokens : 0 } ,
} ,
} , state )
require . Len ( t , events , 2 ) // message_delta + message_stop
assert . Equal ( t , "message_delta" , events [ 0 ] . Type )
assert . Equal ( t , "end_turn" , events [ 0 ] . Delta . StopReason )
}
func TestResponsesAnthropicEventToSSE ( t * testing . T ) {
evt := AnthropicStreamEvent {
Type : "message_start" ,
Message : & AnthropicResponse {
ID : "resp_1" ,
Type : "message" ,
Role : "assistant" ,
} ,
}
sse , err := ResponsesAnthropicEventToSSE ( evt )
require . NoError ( t , err )
assert . Contains ( t , sse , "event: message_start\n" )
assert . Contains ( t , sse , "data: " )
assert . Contains ( t , sse , ` "resp_1" ` )
}
2026-03-06 22:39:33 +08:00
// ---------------------------------------------------------------------------
// response.failed tests
// ---------------------------------------------------------------------------
func TestStreamingFailed ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
// 1. response.created
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_fail_1" , Model : "gpt-5.2" } ,
} , state )
// 2. Some text output before failure
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "Partial output before failure" ,
} , state )
// 3. response.failed
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.failed" ,
Response : & ResponsesResponse {
Status : "failed" ,
Error : & ResponsesError { Code : "server_error" , Message : "Internal error" } ,
Usage : & ResponsesUsage { InputTokens : 50 , OutputTokens : 10 } ,
} ,
} , state )
// Should close text block + message_delta + message_stop
require . Len ( t , events , 3 )
assert . Equal ( t , "content_block_stop" , events [ 0 ] . Type )
assert . Equal ( t , "message_delta" , events [ 1 ] . Type )
assert . Equal ( t , "end_turn" , events [ 1 ] . Delta . StopReason )
assert . Equal ( t , 50 , events [ 1 ] . Usage . InputTokens )
assert . Equal ( t , 10 , events [ 1 ] . Usage . OutputTokens )
assert . Equal ( t , "message_stop" , events [ 2 ] . Type )
}
func TestStreamingFailedNoOutput ( t * testing . T ) {
state := NewResponsesEventToAnthropicState ( )
// 1. response.created
ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_fail_2" , Model : "gpt-5.2" } ,
} , state )
// 2. response.failed with no prior output
events := ResponsesEventToAnthropicEvents ( & ResponsesStreamEvent {
Type : "response.failed" ,
Response : & ResponsesResponse {
Status : "failed" ,
Error : & ResponsesError { Code : "rate_limit_error" , Message : "Too many requests" } ,
Usage : & ResponsesUsage { InputTokens : 20 , OutputTokens : 0 } ,
} ,
} , state )
// Should emit message_delta + message_stop (no block to close)
require . Len ( t , events , 2 )
assert . Equal ( t , "message_delta" , events [ 0 ] . Type )
assert . Equal ( t , "end_turn" , events [ 0 ] . Delta . StopReason )
assert . Equal ( t , "message_stop" , events [ 1 ] . Type )
}
func TestResponsesToAnthropic_Failed ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_fail_3" ,
Model : "gpt-5.2" ,
Status : "failed" ,
Error : & ResponsesError { Code : "server_error" , Message : "Something went wrong" } ,
Output : [ ] ResponsesOutput { } ,
Usage : & ResponsesUsage { InputTokens : 30 , OutputTokens : 0 } ,
}
anth := ResponsesToAnthropic ( resp , "claude-opus-4-6" )
// Failed status defaults to "end_turn" stop reason
assert . Equal ( t , "end_turn" , anth . StopReason )
// Should have at least an empty text block
require . Len ( t , anth . Content , 1 )
assert . Equal ( t , "text" , anth . Content [ 0 ] . Type )
}
// ---------------------------------------------------------------------------
// thinking → reasoning conversion tests
// ---------------------------------------------------------------------------
func TestAnthropicToResponses_ThinkingEnabled ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
Thinking : & AnthropicThinking { Type : "enabled" , BudgetTokens : 10000 } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
2026-03-09 11:42:35 +08:00
// thinking.type is ignored for effort; default xhigh applies.
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
2026-03-06 22:39:33 +08:00
assert . Equal ( t , "auto" , resp . Reasoning . Summary )
assert . Contains ( t , resp . Include , "reasoning.encrypted_content" )
assert . NotContains ( t , resp . Include , "reasoning.summary" )
}
func TestAnthropicToResponses_ThinkingAdaptive ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
Thinking : & AnthropicThinking { Type : "adaptive" , BudgetTokens : 5000 } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
2026-03-09 11:42:35 +08:00
// thinking.type is ignored for effort; default xhigh applies.
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
2026-03-06 22:39:33 +08:00
assert . Equal ( t , "auto" , resp . Reasoning . Summary )
assert . NotContains ( t , resp . Include , "reasoning.summary" )
}
func TestAnthropicToResponses_ThinkingDisabled ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
Thinking : & AnthropicThinking { Type : "disabled" } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
2026-03-09 11:42:35 +08:00
// Default effort applies (high → xhigh) even when thinking is disabled.
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
2026-03-06 22:39:33 +08:00
}
func TestAnthropicToResponses_NoThinking ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
2026-03-09 11:42:35 +08:00
// Default effort applies (high → xhigh) when no thinking/output_config is set.
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
}
// ---------------------------------------------------------------------------
// output_config.effort override tests
// ---------------------------------------------------------------------------
func TestAnthropicToResponses_OutputConfigOverridesDefault ( t * testing . T ) {
// Default is xhigh, but output_config.effort="low" overrides. low→low after mapping.
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
Thinking : & AnthropicThinking { Type : "enabled" , BudgetTokens : 10000 } ,
OutputConfig : & AnthropicOutputConfig { Effort : "low" } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "low" , resp . Reasoning . Effort )
assert . Equal ( t , "auto" , resp . Reasoning . Summary )
}
func TestAnthropicToResponses_OutputConfigWithoutThinking ( t * testing . T ) {
// No thinking field, but output_config.effort="medium" → creates reasoning.
// medium→high after mapping.
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
OutputConfig : & AnthropicOutputConfig { Effort : "medium" } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "high" , resp . Reasoning . Effort )
assert . Equal ( t , "auto" , resp . Reasoning . Summary )
}
func TestAnthropicToResponses_OutputConfigHigh ( t * testing . T ) {
// output_config.effort="high" → mapped to "xhigh".
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
OutputConfig : & AnthropicOutputConfig { Effort : "high" } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
assert . Equal ( t , "auto" , resp . Reasoning . Summary )
}
func TestAnthropicToResponses_NoOutputConfig ( t * testing . T ) {
// No output_config → default xhigh regardless of thinking.type.
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
Thinking : & AnthropicThinking { Type : "enabled" , BudgetTokens : 10000 } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
}
func TestAnthropicToResponses_OutputConfigWithoutEffort ( t * testing . T ) {
// output_config present but effort empty (e.g. only format set) → default xhigh.
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
OutputConfig : & AnthropicOutputConfig { } ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . Reasoning )
assert . Equal ( t , "xhigh" , resp . Reasoning . Effort )
2026-03-06 22:39:33 +08:00
}
// ---------------------------------------------------------------------------
// tool_choice conversion tests
// ---------------------------------------------------------------------------
func TestAnthropicToResponses_ToolChoiceAuto ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
ToolChoice : json . RawMessage ( ` { "type":"auto"} ` ) ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var tc string
require . NoError ( t , json . Unmarshal ( resp . ToolChoice , & tc ) )
assert . Equal ( t , "auto" , tc )
}
func TestAnthropicToResponses_ToolChoiceAny ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
ToolChoice : json . RawMessage ( ` { "type":"any"} ` ) ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var tc string
require . NoError ( t , json . Unmarshal ( resp . ToolChoice , & tc ) )
assert . Equal ( t , "required" , tc )
}
func TestAnthropicToResponses_ToolChoiceSpecific ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage { { Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } } ,
ToolChoice : json . RawMessage ( ` { "type":"tool","name":"get_weather"} ` ) ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var tc map [ string ] any
require . NoError ( t , json . Unmarshal ( resp . ToolChoice , & tc ) )
assert . Equal ( t , "function" , tc [ "type" ] )
fn , ok := tc [ "function" ] . ( map [ string ] any )
require . True ( t , ok )
assert . Equal ( t , "get_weather" , fn [ "name" ] )
}
2026-03-08 23:16:58 +08:00
// ---------------------------------------------------------------------------
// Image content block conversion tests
// ---------------------------------------------------------------------------
func TestAnthropicToResponses_UserImageBlock ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "text" , "text" : "What is in this image?" } ,
{ "type" : "image" , "source" : { "type" : "base64" , "media_type" : "image/png" , "data" : "iVBOR" } }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 1 )
assert . Equal ( t , "user" , items [ 0 ] . Role )
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 0 ] . Content , & parts ) )
require . Len ( t , parts , 2 )
assert . Equal ( t , "input_text" , parts [ 0 ] . Type )
assert . Equal ( t , "What is in this image?" , parts [ 0 ] . Text )
assert . Equal ( t , "input_image" , parts [ 1 ] . Type )
assert . Equal ( t , "data:image/png;base64,iVBOR" , parts [ 1 ] . ImageURL )
}
func TestAnthropicToResponses_ImageOnlyUserMessage ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "image" , "source" : { "type" : "base64" , "media_type" : "image/jpeg" , "data" : "/9j/4AAQ" } }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 1 )
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 0 ] . Content , & parts ) )
require . Len ( t , parts , 1 )
assert . Equal ( t , "input_image" , parts [ 0 ] . Type )
assert . Equal ( t , "data:image/jpeg;base64,/9j/4AAQ" , parts [ 0 ] . ImageURL )
}
func TestAnthropicToResponses_ToolResultWithImage ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Read the screenshot" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"tool_use","id":"toolu_1","name":"Read","input": { "file_path":"/tmp/screen.png"}}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "tool_result" , "tool_use_id" : "toolu_1" , "content" : [
{ "type" : "image" , "source" : { "type" : "base64" , "media_type" : "image/png" , "data" : "iVBOR" } }
] }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + function_call + function_call_output + user(image) = 4
require . Len ( t , items , 4 )
// function_call_output should have text-only output (no image).
assert . Equal ( t , "function_call_output" , items [ 2 ] . Type )
assert . Equal ( t , "fc_toolu_1" , items [ 2 ] . CallID )
assert . Equal ( t , "(empty)" , items [ 2 ] . Output )
// Image should be in a separate user message.
assert . Equal ( t , "user" , items [ 3 ] . Role )
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 3 ] . Content , & parts ) )
require . Len ( t , parts , 1 )
assert . Equal ( t , "input_image" , parts [ 0 ] . Type )
assert . Equal ( t , "data:image/png;base64,iVBOR" , parts [ 0 ] . ImageURL )
}
func TestAnthropicToResponses_ToolResultMixed ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Describe the file" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"tool_use","id":"toolu_2","name":"Read","input": { "file_path":"/tmp/photo.png"}}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "tool_result" , "tool_use_id" : "toolu_2" , "content" : [
{ "type" : "text" , "text" : "File metadata: 800x600 PNG" } ,
{ "type" : "image" , "source" : { "type" : "base64" , "media_type" : "image/png" , "data" : "AAAA" } }
] }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + function_call + function_call_output + user(image) = 4
require . Len ( t , items , 4 )
// function_call_output should have text-only output.
assert . Equal ( t , "function_call_output" , items [ 2 ] . Type )
assert . Equal ( t , "File metadata: 800x600 PNG" , items [ 2 ] . Output )
// Image should be in a separate user message.
assert . Equal ( t , "user" , items [ 3 ] . Role )
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 3 ] . Content , & parts ) )
require . Len ( t , parts , 1 )
assert . Equal ( t , "input_image" , parts [ 0 ] . Type )
assert . Equal ( t , "data:image/png;base64,AAAA" , parts [ 0 ] . ImageURL )
}
func TestAnthropicToResponses_TextOnlyToolResultBackwardCompat ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Check weather" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"tool_use","id":"call_1","name":"get_weather","input": { "city":"NYC"}}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "tool_result" , "tool_use_id" : "call_1" , "content" : [
{ "type" : "text" , "text" : "Sunny, 72°F" }
] }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + function_call + function_call_output = 3
require . Len ( t , items , 3 )
// Text-only tool_result should produce a plain string.
assert . Equal ( t , "Sunny, 72°F" , items [ 2 ] . Output )
}
func TestAnthropicToResponses_ImageEmptyMediaType ( t * testing . T ) {
req := & AnthropicRequest {
Model : "gpt-5.2" ,
MaxTokens : 1024 ,
Messages : [ ] AnthropicMessage {
{ Role : "user" , Content : json . RawMessage ( ` [
{ "type" : "image" , "source" : { "type" : "base64" , "media_type" : "" , "data" : "iVBOR" } }
] ` ) } ,
} ,
}
resp , err := AnthropicToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 1 )
var parts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 0 ] . Content , & parts ) )
require . Len ( t , parts , 1 )
assert . Equal ( t , "input_image" , parts [ 0 ] . Type )
// Should default to image/png when media_type is empty.
assert . Equal ( t , "data:image/png;base64,iVBOR" , parts [ 0 ] . ImageURL )
}