package protocol import ( "bytes" "context" "encoding/json" "strings" "testing" ) func TestChatOutputReasoning(t *testing.T) { for _, tool := range []bool{false, true} { message := object{"role": "assistant", "content": "OK", "reasoning_content": "PRIVATE"} delta := object{"content": "OK", "reasoning_content": "PRIVATE"} finish := "stop" if tool { call := object{"index": 0, "id": "call_1", "type": "function", "function": object{"name": "weather", "arguments": "{}"}} message["content"] = nil message["tool_calls"] = []any{call} delta = object{"tool_calls": []any{call}, "reasoning_content": nil} finish = "tool_calls" } usage := object{"prompt_tokens": 9, "completion_tokens": 7} response := object{"id": "c", "object": "chat.completion", "model": "m", "choices": []any{object{"index": 0, "message": message, "finish_reason": finish}}, "usage": usage} body, _ := json.Marshal(response) chunk, _ := json.Marshal(object{"id": "c", "choices": []any{object{"index": 0, "delta": delta, "finish_reason": finish}}, "usage": usage}) reasoning, _ := json.Marshal(object{"id": "c", "choices": []any{object{"index": 0, "delta": object{"role": "assistant", "reasoning_content": "PRIVATE"}, "finish_reason": nil}}}) sep := string([]byte{10, 10}) src := "data: " + string(reasoning) + sep + "data: " + string(chunk) + sep + "data: [DONE]" + sep for _, target := range []Kind{Chat, Responses, Anthropic} { out, err := ConvertResponse(Chat, target, body) if err != nil { t.Fatal(err) } var dst bytes.Buffer u, err := Stream(context.Background(), &dst, strings.NewReader(src), Chat, target, "m") if err != nil { t.Fatal(err) } if !u.Known || u.Input != 9 || u.Output != 7 { t.Fatalf("usage: %+v", u) } if target == Chat { if !bytes.Equal(out, body) || dst.String() != src { t.Fatal("native bytes changed") } continue } for _, text := range []string{string(out), dst.String()} { if strings.Contains(text, "PRIVATE") || strings.Contains(text, "reasoning_content") || strings.Contains(text, "thinking") { t.Fatal("reasoning leaked") } want := "OK" if tool { want = "weather" } if !strings.Contains(text, want) { t.Fatal("lost output") } } terminal := "response.completed" if target == Anthropic { terminal = "message_stop" } if !strings.Contains(dst.String(), terminal) { t.Fatal("no terminal") } } } } func TestChatOutputMetadataValidation(t *testing.T) { for _, val := range []any{nil, "", "private"} { if _, err := chatOutput(object{"reasoning_content": val}); err != nil { t.Fatal(err) } } for _, val := range []any{true, object{}, []any{}} { if _, err := chatOutput(object{"reasoning_content": val}); err == nil { t.Fatal("accepted malformed reasoning") } } for _, index := range []any{json.Number("1"), json.Number("-1"), json.Number("0.5"), "0", nil} { if _, err := chatResponseMessage(object{"tool_calls": []any{object{"index": index}}}); err == nil { t.Fatal("accepted malformed index") } } body := []byte(`{"id":"c","choices":[{"message":{"role":"assistant","content":"OK","reasoning_content":"hidden","unknown_field":true},"finish_reason":"stop"}]}`) if _, err := ConvertResponse(Chat, Responses, body); err == nil { t.Fatal("accepted unknown field") } }