2026-03-11 22:10:22 +08:00
package apicompat
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ---------------------------------------------------------------------------
// ChatCompletionsToResponses tests
// ---------------------------------------------------------------------------
func TestChatCompletionsToResponses_BasicText ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hello" ` ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
assert . Equal ( t , "gpt-4o" , resp . Model )
assert . True ( t , resp . Stream ) // always forced true
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 TestChatCompletionsToResponses_SystemMessage ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "system" , Content : json . RawMessage ( ` "You are helpful." ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( 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 )
assert . Equal ( t , "user" , items [ 1 ] . Role )
}
func TestChatCompletionsToResponses_ToolCalls ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Call the function" ` ) } ,
{
Role : "assistant" ,
ToolCalls : [ ] ChatToolCall {
{
ID : "call_1" ,
Type : "function" ,
Function : ChatFunctionCall {
Name : "ping" ,
Arguments : ` { "host":"example.com"} ` ,
} ,
} ,
} ,
} ,
{
Role : "tool" ,
ToolCallID : "call_1" ,
Content : json . RawMessage ( ` "pong" ` ) ,
} ,
} ,
Tools : [ ] ChatTool {
{
Type : "function" ,
Function : & ChatFunction {
Name : "ping" ,
Description : "Ping a host" ,
Parameters : json . RawMessage ( ` { "type":"object"} ` ) ,
} ,
} ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + function_call + function_call_output = 3
// (assistant message with empty content + tool_calls → only function_call items emitted)
require . Len ( t , items , 3 )
// Check function_call item
assert . Equal ( t , "function_call" , items [ 1 ] . Type )
assert . Equal ( t , "call_1" , items [ 1 ] . CallID )
2026-03-14 12:12:08 +08:00
assert . Empty ( t , items [ 1 ] . ID )
2026-03-11 22:10:22 +08:00
assert . Equal ( t , "ping" , items [ 1 ] . Name )
// Check function_call_output item
assert . Equal ( t , "function_call_output" , items [ 2 ] . Type )
assert . Equal ( t , "call_1" , items [ 2 ] . CallID )
assert . Equal ( t , "pong" , items [ 2 ] . Output )
// Check tools
require . Len ( t , resp . Tools , 1 )
assert . Equal ( t , "function" , resp . Tools [ 0 ] . Type )
assert . Equal ( t , "ping" , resp . Tools [ 0 ] . Name )
}
func TestChatCompletionsToResponses_MaxTokens ( t * testing . T ) {
t . Run ( "max_tokens" , func ( t * testing . T ) {
maxTokens := 100
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
MaxTokens : & maxTokens ,
Messages : [ ] ChatMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . MaxOutputTokens )
// Below minMaxOutputTokens (128), should be clamped
assert . Equal ( t , minMaxOutputTokens , * resp . MaxOutputTokens )
} )
t . Run ( "max_completion_tokens_preferred" , func ( t * testing . T ) {
maxTokens := 100
maxCompletion := 500
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
MaxTokens : & maxTokens ,
MaxCompletionTokens : & maxCompletion ,
Messages : [ ] ChatMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
require . NotNil ( t , resp . MaxOutputTokens )
assert . Equal ( t , 500 , * resp . MaxOutputTokens )
} )
}
func TestChatCompletionsToResponses_ReasoningEffort ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
ReasoningEffort : "high" ,
Messages : [ ] ChatMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := ChatCompletionsToResponses ( 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 TestChatCompletionsToResponses_ImageURL ( t * testing . T ) {
content := ` [ { "type":"text","text":"Describe this"}, { "type":"image_url","image_url": { "url":"data:image/png;base64,abc123"}}] `
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( content ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( 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 , 2 )
assert . Equal ( t , "input_text" , parts [ 0 ] . Type )
assert . Equal ( t , "Describe this" , parts [ 0 ] . Text )
assert . Equal ( t , "input_image" , parts [ 1 ] . Type )
assert . Equal ( t , "data:image/png;base64,abc123" , parts [ 1 ] . ImageURL )
}
2026-03-21 15:26:58 +08:00
func TestChatCompletionsToResponses_SystemArrayContent ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "system" , Content : json . RawMessage ( ` [ { "type":"text","text":"You are a careful visual assistant."}] ` ) } ,
{ Role : "user" , Content : json . RawMessage ( ` [ { "type":"text","text":"Describe this image"}, { "type":"image_url","image_url": { "url":"data:image/png;base64,abc123"}}] ` ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 2 )
var systemParts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 0 ] . Content , & systemParts ) )
require . Len ( t , systemParts , 1 )
assert . Equal ( t , "input_text" , systemParts [ 0 ] . Type )
assert . Equal ( t , "You are a careful visual assistant." , systemParts [ 0 ] . Text )
var userParts [ ] ResponsesContentPart
require . NoError ( t , json . Unmarshal ( items [ 1 ] . Content , & userParts ) )
require . Len ( t , userParts , 2 )
assert . Equal ( t , "input_image" , userParts [ 1 ] . Type )
assert . Equal ( t , "data:image/png;base64,abc123" , userParts [ 1 ] . ImageURL )
}
2026-03-11 22:10:22 +08:00
func TestChatCompletionsToResponses_LegacyFunctions ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } ,
} ,
Functions : [ ] ChatFunction {
{
Name : "get_weather" ,
Description : "Get weather" ,
Parameters : json . RawMessage ( ` { "type":"object"} ` ) ,
} ,
} ,
FunctionCall : json . RawMessage ( ` { "name":"get_weather"} ` ) ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
require . Len ( t , resp . Tools , 1 )
assert . Equal ( t , "function" , resp . Tools [ 0 ] . Type )
assert . Equal ( t , "get_weather" , resp . Tools [ 0 ] . Name )
// tool_choice should be converted
require . NotNil ( t , resp . ToolChoice )
var tc map [ string ] any
require . NoError ( t , json . Unmarshal ( resp . ToolChoice , & tc ) )
assert . Equal ( t , "function" , tc [ "type" ] )
}
func TestChatCompletionsToResponses_ServiceTier ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
ServiceTier : "flex" ,
Messages : [ ] ChatMessage { { Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } } ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
assert . Equal ( t , "flex" , resp . ServiceTier )
}
func TestChatCompletionsToResponses_AssistantWithTextAndToolCalls ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Do something" ` ) } ,
{
Role : "assistant" ,
Content : json . RawMessage ( ` "Let me call a function." ` ) ,
ToolCalls : [ ] ChatToolCall {
{
ID : "call_abc" ,
Type : "function" ,
Function : ChatFunctionCall {
Name : "do_thing" ,
Arguments : ` { } ` ,
} ,
} ,
} ,
} ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
// user + assistant message (with text) + function_call
require . Len ( t , items , 3 )
assert . Equal ( t , "user" , items [ 0 ] . Role )
assert . Equal ( t , "assistant" , items [ 1 ] . Role )
assert . Equal ( t , "function_call" , items [ 2 ] . Type )
2026-03-14 12:12:08 +08:00
assert . Empty ( t , items [ 2 ] . ID )
}
func TestChatCompletionsToResponses_AssistantArrayContentPreserved ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"text","text":"A"}, { "type":"text","text":"B"}] ` ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 2 )
assert . Equal ( t , "assistant" , items [ 1 ] . Role )
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 , "AB" , parts [ 0 ] . Text )
}
func TestChatCompletionsToResponses_AssistantThinkingTagPreserved ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Hi" ` ) } ,
{ Role : "assistant" , Content : json . RawMessage ( ` [ { "type":"thinking","thinking":"internal plan"}, { "type":"text","text":"final answer"}] ` ) } ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 2 )
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 . Contains ( t , parts [ 0 ] . Text , "<thinking>internal plan</thinking>" )
assert . Contains ( t , parts [ 0 ] . Text , "final answer" )
2026-03-11 22:10:22 +08:00
}
// ---------------------------------------------------------------------------
// ResponsesToChatCompletions tests
// ---------------------------------------------------------------------------
func TestResponsesToChatCompletions_BasicText ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_123" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "Hello, world!" } ,
} ,
} ,
} ,
Usage : & ResponsesUsage {
InputTokens : 10 ,
OutputTokens : 5 ,
TotalTokens : 15 ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
assert . Equal ( t , "chat.completion" , chat . Object )
assert . Equal ( t , "gpt-4o" , chat . Model )
require . Len ( t , chat . Choices , 1 )
assert . Equal ( t , "stop" , chat . Choices [ 0 ] . FinishReason )
var content string
require . NoError ( t , json . Unmarshal ( chat . Choices [ 0 ] . Message . Content , & content ) )
assert . Equal ( t , "Hello, world!" , content )
require . NotNil ( t , chat . Usage )
assert . Equal ( t , 10 , chat . Usage . PromptTokens )
assert . Equal ( t , 5 , chat . Usage . CompletionTokens )
assert . Equal ( t , 15 , chat . Usage . TotalTokens )
}
func TestResponsesToChatCompletions_ToolCalls ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_456" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "function_call" ,
CallID : "call_xyz" ,
Name : "get_weather" ,
Arguments : ` { "city":"NYC"} ` ,
} ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
require . Len ( t , chat . Choices , 1 )
assert . Equal ( t , "tool_calls" , chat . Choices [ 0 ] . FinishReason )
msg := chat . Choices [ 0 ] . Message
require . Len ( t , msg . ToolCalls , 1 )
assert . Equal ( t , "call_xyz" , msg . ToolCalls [ 0 ] . ID )
assert . Equal ( t , "function" , msg . ToolCalls [ 0 ] . Type )
assert . Equal ( t , "get_weather" , msg . ToolCalls [ 0 ] . Function . Name )
assert . Equal ( t , ` { "city":"NYC"} ` , msg . ToolCalls [ 0 ] . Function . Arguments )
}
func TestResponsesToChatCompletions_Reasoning ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_789" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "reasoning" ,
Summary : [ ] ResponsesSummary {
{ Type : "summary_text" , Text : "I thought about it." } ,
} ,
} ,
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "The answer is 42." } ,
} ,
} ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
require . Len ( t , chat . Choices , 1 )
var content string
require . NoError ( t , json . Unmarshal ( chat . Choices [ 0 ] . Message . Content , & content ) )
2026-03-14 12:12:08 +08:00
assert . Equal ( t , "The answer is 42." , content )
assert . Equal ( t , "I thought about it." , chat . Choices [ 0 ] . Message . ReasoningContent )
2026-03-11 22:10:22 +08:00
}
2026-03-21 15:26:58 +08:00
func TestChatCompletionsToResponses_ToolArrayContent ( t * testing . T ) {
req := & ChatCompletionsRequest {
Model : "gpt-4o" ,
Messages : [ ] ChatMessage {
{ Role : "user" , Content : json . RawMessage ( ` "Use the tool" ` ) } ,
{
Role : "assistant" ,
ToolCalls : [ ] ChatToolCall {
{
ID : "call_1" ,
Type : "function" ,
Function : ChatFunctionCall {
Name : "inspect_image" ,
Arguments : ` { } ` ,
} ,
} ,
} ,
} ,
{
Role : "tool" ,
ToolCallID : "call_1" ,
Content : json . RawMessage (
` [ { "type":"text","text":"image width: 100"}, { "type":"image_url","image_url": { "url":"data:image/png;base64,ignored"}}, { "type":"text","text":"; image height: 200"}] ` ,
) ,
} ,
} ,
}
resp , err := ChatCompletionsToResponses ( req )
require . NoError ( t , err )
var items [ ] ResponsesInputItem
require . NoError ( t , json . Unmarshal ( resp . Input , & items ) )
require . Len ( t , items , 3 )
assert . Equal ( t , "function_call_output" , items [ 2 ] . Type )
assert . Equal ( t , "call_1" , items [ 2 ] . CallID )
assert . Equal ( t , "image width: 100; image height: 200" , items [ 2 ] . Output )
}
2026-03-11 22:10:22 +08:00
func TestResponsesToChatCompletions_Incomplete ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_inc" ,
Status : "incomplete" ,
IncompleteDetails : & ResponsesIncompleteDetails { Reason : "max_output_tokens" } ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart {
{ Type : "output_text" , Text : "partial..." } ,
} ,
} ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
require . Len ( t , chat . Choices , 1 )
assert . Equal ( t , "length" , chat . Choices [ 0 ] . FinishReason )
}
func TestResponsesToChatCompletions_CachedTokens ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_cache" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "message" ,
Content : [ ] ResponsesContentPart { { Type : "output_text" , Text : "cached" } } ,
} ,
} ,
Usage : & ResponsesUsage {
InputTokens : 100 ,
OutputTokens : 10 ,
TotalTokens : 110 ,
InputTokensDetails : & ResponsesInputTokensDetails {
CachedTokens : 80 ,
} ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
require . NotNil ( t , chat . Usage )
require . NotNil ( t , chat . Usage . PromptTokensDetails )
assert . Equal ( t , 80 , chat . Usage . PromptTokensDetails . CachedTokens )
}
func TestResponsesToChatCompletions_WebSearch ( t * testing . T ) {
resp := & ResponsesResponse {
ID : "resp_ws" ,
Status : "completed" ,
Output : [ ] ResponsesOutput {
{
Type : "web_search_call" ,
Action : & WebSearchAction { Type : "search" , Query : "test" } ,
} ,
{
Type : "message" ,
Content : [ ] ResponsesContentPart { { Type : "output_text" , Text : "search results" } } ,
} ,
} ,
}
chat := ResponsesToChatCompletions ( resp , "gpt-4o" )
require . Len ( t , chat . Choices , 1 )
assert . Equal ( t , "stop" , chat . Choices [ 0 ] . FinishReason )
var content string
require . NoError ( t , json . Unmarshal ( chat . Choices [ 0 ] . Message . Content , & content ) )
assert . Equal ( t , "search results" , content )
}
// ---------------------------------------------------------------------------
// Streaming: ResponsesEventToChatChunks tests
// ---------------------------------------------------------------------------
func TestResponsesEventToChatChunks_TextDelta ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
// response.created → role chunk
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse {
ID : "resp_stream" ,
} ,
} , state )
require . Len ( t , chunks , 1 )
assert . Equal ( t , "assistant" , chunks [ 0 ] . Choices [ 0 ] . Delta . Role )
assert . True ( t , state . SentRole )
// response.output_text.delta → content chunk
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "Hello" ,
} , state )
require . Len ( t , chunks , 1 )
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . Delta . Content )
assert . Equal ( t , "Hello" , * chunks [ 0 ] . Choices [ 0 ] . Delta . Content )
}
func TestResponsesEventToChatChunks_ToolCallDelta ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . SentRole = true
// response.output_item.added (function_call) — output_index=1 (e.g. after a message item at 0)
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.output_item.added" ,
OutputIndex : 1 ,
Item : & ResponsesOutput {
Type : "function_call" ,
CallID : "call_1" ,
Name : "get_weather" ,
} ,
} , state )
require . Len ( t , chunks , 1 )
require . Len ( t , chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls , 1 )
tc := chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls [ 0 ]
assert . Equal ( t , "call_1" , tc . ID )
assert . Equal ( t , "get_weather" , tc . Function . Name )
require . NotNil ( t , tc . Index )
assert . Equal ( t , 0 , * tc . Index )
// response.function_call_arguments.delta — uses output_index (NOT call_id) to find tool
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.function_call_arguments.delta" ,
OutputIndex : 1 , // matches the output_index from output_item.added above
Delta : ` { "city": ` ,
} , state )
require . Len ( t , chunks , 1 )
tc = chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls [ 0 ]
require . NotNil ( t , tc . Index )
assert . Equal ( t , 0 , * tc . Index , "argument delta must use same index as the tool call" )
assert . Equal ( t , ` { "city": ` , tc . Function . Arguments )
// Add a second function call at output_index=2
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.output_item.added" ,
OutputIndex : 2 ,
Item : & ResponsesOutput {
Type : "function_call" ,
CallID : "call_2" ,
Name : "get_time" ,
} ,
} , state )
require . Len ( t , chunks , 1 )
tc = chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls [ 0 ]
require . NotNil ( t , tc . Index )
assert . Equal ( t , 1 , * tc . Index , "second tool call should get index 1" )
// Argument delta for second tool call
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.function_call_arguments.delta" ,
OutputIndex : 2 ,
Delta : ` { "tz":"UTC"} ` ,
} , state )
require . Len ( t , chunks , 1 )
tc = chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls [ 0 ]
require . NotNil ( t , tc . Index )
assert . Equal ( t , 1 , * tc . Index , "second tool arg delta must use index 1" )
// Argument delta for first tool call (interleaved)
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.function_call_arguments.delta" ,
OutputIndex : 1 ,
Delta : ` "Tokyo"} ` ,
} , state )
require . Len ( t , chunks , 1 )
tc = chunks [ 0 ] . Choices [ 0 ] . Delta . ToolCalls [ 0 ]
require . NotNil ( t , tc . Index )
assert . Equal ( t , 0 , * tc . Index , "first tool arg delta must still use index 0" )
}
func TestResponsesEventToChatChunks_Completed ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . IncludeUsage = true
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage {
InputTokens : 50 ,
OutputTokens : 20 ,
TotalTokens : 70 ,
InputTokensDetails : & ResponsesInputTokensDetails {
CachedTokens : 30 ,
} ,
} ,
} ,
} , state )
// finish chunk + usage chunk
require . Len ( t , chunks , 2 )
// First chunk: finish_reason
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . FinishReason )
assert . Equal ( t , "stop" , * chunks [ 0 ] . Choices [ 0 ] . FinishReason )
// Second chunk: usage
require . NotNil ( t , chunks [ 1 ] . Usage )
assert . Equal ( t , 50 , chunks [ 1 ] . Usage . PromptTokens )
assert . Equal ( t , 20 , chunks [ 1 ] . Usage . CompletionTokens )
assert . Equal ( t , 70 , chunks [ 1 ] . Usage . TotalTokens )
require . NotNil ( t , chunks [ 1 ] . Usage . PromptTokensDetails )
assert . Equal ( t , 30 , chunks [ 1 ] . Usage . PromptTokensDetails . CachedTokens )
}
func TestResponsesEventToChatChunks_CompletedWithToolCalls ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . SawToolCall = true
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
} ,
} , state )
require . Len ( t , chunks , 1 )
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . FinishReason )
assert . Equal ( t , "tool_calls" , * chunks [ 0 ] . Choices [ 0 ] . FinishReason )
}
func TestResponsesEventToChatChunks_ReasoningDelta ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . SentRole = true
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.reasoning_summary_text.delta" ,
Delta : "Thinking..." ,
} , state )
require . Len ( t , chunks , 1 )
2026-03-14 12:12:08 +08:00
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . Delta . ReasoningContent )
assert . Equal ( t , "Thinking..." , * chunks [ 0 ] . Choices [ 0 ] . Delta . ReasoningContent )
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.reasoning_summary_text.done" ,
} , state )
require . Len ( t , chunks , 0 )
}
func TestResponsesEventToChatChunks_ReasoningThenTextAutoCloseTag ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . SentRole = true
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.reasoning_summary_text.delta" ,
Delta : "plan" ,
} , state )
require . Len ( t , chunks , 1 )
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . Delta . ReasoningContent )
assert . Equal ( t , "plan" , * chunks [ 0 ] . Choices [ 0 ] . Delta . ReasoningContent )
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : "answer" ,
} , state )
require . Len ( t , chunks , 1 )
2026-03-11 22:10:22 +08:00
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . Delta . Content )
2026-03-14 12:12:08 +08:00
assert . Equal ( t , "answer" , * chunks [ 0 ] . Choices [ 0 ] . Delta . Content )
2026-03-11 22:10:22 +08:00
}
func TestFinalizeResponsesChatStream ( t * testing . T ) {
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . IncludeUsage = true
state . Usage = & ChatUsage {
PromptTokens : 100 ,
CompletionTokens : 50 ,
TotalTokens : 150 ,
}
chunks := FinalizeResponsesChatStream ( state )
require . Len ( t , chunks , 2 )
// Finish chunk
require . NotNil ( t , chunks [ 0 ] . Choices [ 0 ] . FinishReason )
assert . Equal ( t , "stop" , * chunks [ 0 ] . Choices [ 0 ] . FinishReason )
// Usage chunk
require . NotNil ( t , chunks [ 1 ] . Usage )
assert . Equal ( t , 100 , chunks [ 1 ] . Usage . PromptTokens )
// Idempotent: second call returns nil
assert . Nil ( t , FinalizeResponsesChatStream ( state ) )
}
func TestFinalizeResponsesChatStream_AfterCompleted ( t * testing . T ) {
// If response.completed already emitted the finish chunk, FinalizeResponsesChatStream
// must be a no-op (prevents double finish_reason being sent to the client).
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . IncludeUsage = true
// Simulate response.completed
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage {
InputTokens : 10 ,
OutputTokens : 5 ,
TotalTokens : 15 ,
} ,
} ,
} , state )
require . NotEmpty ( t , chunks ) // finish + usage chunks
// Now FinalizeResponsesChatStream should return nil — already finalized.
assert . Nil ( t , FinalizeResponsesChatStream ( state ) )
}
func TestChatChunkToSSE ( t * testing . T ) {
chunk := ChatCompletionsChunk {
ID : "chatcmpl-test" ,
Object : "chat.completion.chunk" ,
Created : 1700000000 ,
Model : "gpt-4o" ,
Choices : [ ] ChatChunkChoice {
{
Index : 0 ,
Delta : ChatDelta { Role : "assistant" } ,
FinishReason : nil ,
} ,
} ,
}
sse , err := ChatChunkToSSE ( chunk )
require . NoError ( t , err )
assert . Contains ( t , sse , "data: " )
assert . Contains ( t , sse , "chatcmpl-test" )
assert . Contains ( t , sse , "assistant" )
assert . True ( t , len ( sse ) > 10 )
}
// ---------------------------------------------------------------------------
// Stream round-trip test
// ---------------------------------------------------------------------------
func TestChatCompletionsStreamRoundTrip ( t * testing . T ) {
// Simulate: client sends chat completions request, upstream returns Responses SSE events.
// Verify that the streaming state machine produces correct chat completions chunks.
state := NewResponsesEventToChatState ( )
state . Model = "gpt-4o"
state . IncludeUsage = true
var allChunks [ ] ChatCompletionsChunk
// 1. response.created
chunks := ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.created" ,
Response : & ResponsesResponse { ID : "resp_rt" } ,
} , state )
allChunks = append ( allChunks , chunks ... )
// 2. text deltas
for _ , text := range [ ] string { "Hello" , ", " , "world" , "!" } {
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.output_text.delta" ,
Delta : text ,
} , state )
allChunks = append ( allChunks , chunks ... )
}
// 3. response.completed
chunks = ResponsesEventToChatChunks ( & ResponsesStreamEvent {
Type : "response.completed" ,
Response : & ResponsesResponse {
Status : "completed" ,
Usage : & ResponsesUsage {
InputTokens : 10 ,
OutputTokens : 4 ,
TotalTokens : 14 ,
} ,
} ,
} , state )
allChunks = append ( allChunks , chunks ... )
// Verify: role chunk + 4 text chunks + finish chunk + usage chunk = 7
require . Len ( t , allChunks , 7 )
// First chunk has role
assert . Equal ( t , "assistant" , allChunks [ 0 ] . Choices [ 0 ] . Delta . Role )
// Text chunks
var fullText string
for i := 1 ; i <= 4 ; i ++ {
require . NotNil ( t , allChunks [ i ] . Choices [ 0 ] . Delta . Content )
fullText += * allChunks [ i ] . Choices [ 0 ] . Delta . Content
}
assert . Equal ( t , "Hello, world!" , fullText )
// Finish chunk
require . NotNil ( t , allChunks [ 5 ] . Choices [ 0 ] . FinishReason )
assert . Equal ( t , "stop" , * allChunks [ 5 ] . Choices [ 0 ] . FinishReason )
// Usage chunk
require . NotNil ( t , allChunks [ 6 ] . Usage )
assert . Equal ( t , 10 , allChunks [ 6 ] . Usage . PromptTokens )
assert . Equal ( t , 4 , allChunks [ 6 ] . Usage . CompletionTokens )
// All chunks share the same ID
for _ , c := range allChunks {
assert . Equal ( t , "resp_rt" , c . ID )
}
}