@@ -371,6 +371,72 @@ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_MixedMessageAndTo
371371 }
372372}
373373
374+ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_CompletedOmitsTopLevelOutputText (t * testing.T ) {
375+ in := []string {
376+ `data: {"id":"resp_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":"hello ","reasoning_content":null,"tool_calls":null},"finish_reason":null}]}` ,
377+ `data: {"id":"resp_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":"world","reasoning_content":null,"tool_calls":null},"finish_reason":"stop"}],"usage":{"completion_tokens":2,"total_tokens":4,"prompt_tokens":2}}` ,
378+ `data: [DONE]` ,
379+ }
380+
381+ request := []byte (`{"model":"gpt-5.4"}` )
382+
383+ var param any
384+ var completed gjson.Result
385+ for _ , line := range in {
386+ for _ , chunk := range ConvertOpenAIChatCompletionsResponseToOpenAIResponses (context .Background (), "model" , request , request , []byte (line ), & param ) {
387+ ev , data := parseOpenAIResponsesSSEEvent (t , chunk )
388+ if ev == "response.completed" {
389+ completed = data
390+ }
391+ }
392+ }
393+
394+ if ! completed .Exists () {
395+ t .Fatal ("expected response.completed event" )
396+ }
397+ if completed .Get ("response.output_text" ).Exists () {
398+ t .Fatalf ("response.output_text should be omitted to match native Responses output: %s" , completed .Get ("response.output_text" ).Raw )
399+ }
400+ if got := completed .Get ("response.output.0.content.0.text" ).String (); got != "hello world" {
401+ t .Fatalf ("response.output text = %q, want %q" , got , "hello world" )
402+ }
403+ }
404+
405+ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_ToolCallCompletedOmitsTopLevelOutputText (t * testing.T ) {
406+ in := []string {
407+ `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":"I will call the weather tool.","reasoning_content":null,"tool_calls":null},"finish_reason":null}]}` ,
408+ `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":null,"tool_calls":[{"index":0,"id":"call_weather","type":"function","function":{"name":"get_weather","arguments":""}}]},"finish_reason":null}]}` ,
409+ `data: {"id":"resp_tool_output_text","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":null,"reasoning_content":null,"tool_calls":[{"index":0,"function":{"arguments":"{\"location\":\"北京\",\"unit\":\"celsius\"}"}}]},"finish_reason":"tool_calls"}],"usage":{"completion_tokens":10,"total_tokens":20,"prompt_tokens":10}}` ,
410+ `data: [DONE]` ,
411+ }
412+
413+ request := []byte (`{"model":"gpt-5.4","tool_choice":"auto","parallel_tool_calls":true}` )
414+
415+ var param any
416+ var completed gjson.Result
417+ for _ , line := range in {
418+ for _ , chunk := range ConvertOpenAIChatCompletionsResponseToOpenAIResponses (context .Background (), "model" , request , request , []byte (line ), & param ) {
419+ ev , data := parseOpenAIResponsesSSEEvent (t , chunk )
420+ if ev == "response.completed" {
421+ completed = data
422+ }
423+ }
424+ }
425+
426+ if ! completed .Exists () {
427+ t .Fatal ("expected response.completed event" )
428+ }
429+ if completed .Get ("response.output_text" ).Exists () {
430+ t .Fatalf ("response.output_text should be omitted to match native Responses output: %s" , completed .Get ("response.output_text" ).Raw )
431+ }
432+ if got := completed .Get ("response.output.0.content.0.text" ).String (); got != "I will call the weather tool." {
433+ t .Fatalf ("response output text = %q, want %q" , got , "I will call the weather tool." )
434+ }
435+ if got := completed .Get ("response.output.1.arguments" ).String (); ! strings .Contains (got , "北京" ) {
436+ t .Fatalf ("response function call arguments = %q, want Beijing argument" , got )
437+ }
438+ }
439+
374440func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_FunctionCallDoneAndCompletedOutputStayAscending (t * testing.T ) {
375441 in := []string {
376442 `data: {"id":"resp_order","object":"chat.completion.chunk","created":1773896263,"model":"model","choices":[{"index":0,"delta":{"role":"assistant","content":null,"reasoning_content":null,"tool_calls":[{"index":0,"id":"call_glob","type":"function","function":{"name":"glob","arguments":""}}]},"finish_reason":null}]}` ,
@@ -421,3 +487,18 @@ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponses_FunctionCallDoneA
421487 t .Fatalf ("unexpected completed function_call order: %v" , completedOrder )
422488 }
423489}
490+
491+ func TestConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream_OmitsTopLevelOutputText (t * testing.T ) {
492+ request := []byte (`{"model":"gpt-5.4"}` )
493+ raw := []byte (`{"id":"chatcmpl_output_text","object":"chat.completion","created":1773896263,"model":"model","choices":[{"index":0,"message":{"role":"assistant","content":"ping"},"finish_reason":"stop"}],"usage":{"prompt_tokens":2,"completion_tokens":1,"total_tokens":3}}` )
494+
495+ resp := ConvertOpenAIChatCompletionsResponseToOpenAIResponsesNonStream (context .Background (), "model" , request , request , raw , nil )
496+ data := gjson .ParseBytes (resp )
497+
498+ if data .Get ("output_text" ).Exists () {
499+ t .Fatalf ("output_text should be omitted to match native Responses output: %s" , resp )
500+ }
501+ if got := data .Get ("output.0.content.0.text" ).String (); got != "ping" {
502+ t .Fatalf ("output text = %q, want %q; response=%s" , got , "ping" , resp )
503+ }
504+ }
0 commit comments