From 82d2552360bf7b7b267ecdc6320fef34b0e93ac7 Mon Sep 17 00:00:00 2001 From: wucm667 Date: Mon, 28 Sep 2026 08:40:41 +0800 Subject: [PATCH] fix(apicompat): mark converted role items as messages --- .../chatcompletions_responses_test.go | 50 +++++++++++++++++++ .../apicompat/chatcompletions_to_responses.go | 6 +-- backend/internal/pkg/apicompat/types.go | 2 +- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/backend/internal/pkg/apicompat/chatcompletions_responses_test.go b/backend/internal/pkg/apicompat/chatcompletions_responses_test.go index 1731892735..1a27ef2374 100644 --- a/backend/internal/pkg/apicompat/chatcompletions_responses_test.go +++ b/backend/internal/pkg/apicompat/chatcompletions_responses_test.go @@ -92,6 +92,56 @@ func TestChatCompletionsToResponses_SystemMessage(t *testing.T) { assert.Equal(t, "user", items[1].Role) } +func TestChatCompletionsToResponses_MessageTypesWithReasoningAndTools(t *testing.T) { + req := &ChatCompletionsRequest{ + Model: "step-5-preview", + Messages: []ChatMessage{ + {Role: "system", Content: json.RawMessage(`"You are helpful."`)}, + {Role: "user", Content: json.RawMessage(`"Check the directory"`)}, + { + Role: "assistant", + Content: json.RawMessage(`"I will check."`), + ReasoningContent: "Need to inspect the directory.", + ToolCalls: []ChatToolCall{{ + ID: "call_1", Type: "function", + Function: ChatFunctionCall{Name: "bash", Arguments: `{"cmd":"pwd"}`}, + }}, + }, + {Role: "tool", ToolCallID: "call_1", Content: json.RawMessage(`"/tmp"`)}, + {Role: "assistant", ReasoningContent: "The tool returned /tmp."}, + {Role: "user", Content: json.RawMessage(`"Continue"`)}, + }, + } + + resp, err := ChatCompletionsToResponses(req) + require.NoError(t, err) + + var items []ResponsesInputItem + require.NoError(t, json.Unmarshal(resp.Input, &items)) + require.Len(t, items, 7) + for i, want := range []struct{ typ, role string }{ + {"message", "system"}, + {"message", "user"}, + {"message", "assistant"}, + {"function_call", ""}, + {"function_call_output", ""}, + {"message", "assistant"}, + {"message", "user"}, + } { + assert.Equal(t, want.typ, items[i].Type, "input item %d type", i) + assert.Equal(t, want.role, items[i].Role, "input item %d role", i) + } + var assistantContent, reasoningOnlyContent []ResponsesContentPart + require.NoError(t, json.Unmarshal(items[2].Content, &assistantContent)) + require.NoError(t, json.Unmarshal(items[5].Content, &reasoningOnlyContent)) + require.Len(t, assistantContent, 1) + require.Len(t, reasoningOnlyContent, 1) + assert.Equal(t, "Need to inspect the directory.\nI will check.", assistantContent[0].Text) + assert.Equal(t, "The tool returned /tmp.", reasoningOnlyContent[0].Text) + assert.Equal(t, "call_1", items[3].CallID) + assert.Equal(t, "call_1", items[4].CallID) +} + func TestChatCompletionsToResponses_ToolCalls(t *testing.T) { req := &ChatCompletionsRequest{ Model: "gpt-4o", diff --git a/backend/internal/pkg/apicompat/chatcompletions_to_responses.go b/backend/internal/pkg/apicompat/chatcompletions_to_responses.go index 773b12b69a..c6fdcb7352 100644 --- a/backend/internal/pkg/apicompat/chatcompletions_to_responses.go +++ b/backend/internal/pkg/apicompat/chatcompletions_to_responses.go @@ -143,7 +143,7 @@ func chatSystemToResponses(m ChatMessage) ([]ResponsesInputItem, error) { if err != nil { return nil, err } - return []ResponsesInputItem{{Role: "system", Content: content}}, nil + return []ResponsesInputItem{{Type: "message", Role: "system", Content: content}}, nil } // chatUserToResponses converts a user message, handling both plain strings and @@ -157,7 +157,7 @@ func chatUserToResponses(m ChatMessage) ([]ResponsesInputItem, error) { if err != nil { return nil, err } - return []ResponsesInputItem{{Role: "user", Content: content}}, nil + return []ResponsesInputItem{{Type: "message", Role: "user", Content: content}}, nil } // chatAssistantToResponses converts an assistant message. If there is both @@ -192,7 +192,7 @@ func chatAssistantToResponses(m ChatMessage) ([]ResponsesInputItem, error) { if err != nil { return nil, err } - items = append(items, ResponsesInputItem{Role: "assistant", Content: partsJSON}) + items = append(items, ResponsesInputItem{Type: "message", Role: "assistant", Content: partsJSON}) } // Emit one function_call item per tool_call. diff --git a/backend/internal/pkg/apicompat/types.go b/backend/internal/pkg/apicompat/types.go index 44a5b4c79c..00b6e85c4d 100644 --- a/backend/internal/pkg/apicompat/types.go +++ b/backend/internal/pkg/apicompat/types.go @@ -263,7 +263,7 @@ type ResponsesText struct { // The Type field determines which other fields are populated. type ResponsesInputItem struct { // Common - Type string `json:"type,omitempty"` // "" for role-based messages + Type string `json:"type,omitempty"` // "message" for role-based messages // Role-based messages (developer/system/user/assistant) Role string `json:"role,omitempty"`