diff --git a/internal/backend/agent/model/openai.go b/internal/backend/agent/model/openai.go index 4a255a3..025c1d3 100644 --- a/internal/backend/agent/model/openai.go +++ b/internal/backend/agent/model/openai.go @@ -49,7 +49,8 @@ type openAIResponsesRequestBody struct { } type openAIResponsesReasoning struct { - Effort string `json:"effort,omitempty"` + Effort string `json:"effort,omitempty"` + Summary string `json:"summary,omitempty"` } type openAIToolAccumulator struct { @@ -944,7 +945,7 @@ func (adapter *OpenAIAdapter) streamResponses(ctx context.Context, req StreamReq requestBody.Tools = tools } if effort := strings.TrimSpace(req.ReasoningEffort); effort != "" { - requestBody.Reasoning = &openAIResponsesReasoning{Effort: effort} + requestBody.Reasoning = &openAIResponsesReasoning{Effort: effort, Summary: "auto"} requestBody.Include = []string{"reasoning.encrypted_content"} } body = requestBody diff --git a/internal/backend/agent/model/openai_stream_test.go b/internal/backend/agent/model/openai_stream_test.go index 5da8fac..8cd105a 100644 --- a/internal/backend/agent/model/openai_stream_test.go +++ b/internal/backend/agent/model/openai_stream_test.go @@ -2,12 +2,67 @@ package modeladapter import ( "context" + "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" ) +func TestOpenAIResponsesRequestsReasoningSummary(t *testing.T) { + var requestBody map[string]any + server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + if err := json.NewDecoder(request.Body).Decode(&requestBody); err != nil { + http.Error(writer, err.Error(), http.StatusBadRequest) + return + } + writer.Header().Set("Content-Type", "text/event-stream") + _, _ = fmt.Fprint(writer, "data: {\"type\":\"response.reasoning_summary_text.delta\",\"delta\":\"check the request\"}\n\n") + _, _ = fmt.Fprint(writer, "data: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp-1\",\"model\":\"gpt-5.6\",\"status\":\"completed\",\"output_text\":\"done\"}}\n\n") + _, _ = fmt.Fprint(writer, "data: [DONE]\n\n") + })) + defer server.Close() + + adapter := &OpenAIAdapter{client: server.Client()} + events := make([]ModelEvent, 0, 4) + err := adapter.Stream(context.Background(), StreamRequest{ + RequestID: "request-1", + RunID: "run-1", + ModelCallID: "model-call-1", + BaseURL: server.URL, + APIKey: "test-key", + ProviderModelID: "gpt-5.6", + OpenAIEndpoint: "/v1/responses", + ReasoningEffort: "high", + Messages: []Message{{Role: "user", Content: "hello"}}, + MaxTokens: 128, + }, func(event ModelEvent) error { + events = append(events, event) + return nil + }) + if err != nil { + t.Fatalf("stream failed: %v", err) + } + + reasoning, ok := requestBody["reasoning"].(map[string]any) + if !ok { + t.Fatalf("reasoning request body missing: %#v", requestBody) + } + if got := reasoning["effort"]; got != "high" { + t.Fatalf("reasoning.effort = %#v, want high", got) + } + if got := reasoning["summary"]; got != "auto" { + t.Fatalf("reasoning.summary = %#v, want auto", got) + } + include, ok := requestBody["include"].([]any) + if !ok || len(include) != 1 || include[0] != "reasoning.encrypted_content" { + t.Fatalf("reasoning include = %#v, want encrypted content", requestBody["include"]) + } + assertOpenAIEventKindCount(t, events, ModelEventKindThinkingDelta, 1) + assertOpenAIEventKindCount(t, events, ModelEventKindThinkingCompleted, 1) + assertOpenAIEventKindCount(t, events, ModelEventKindTextDelta, 1) +} + func TestOpenAIChatCompletionsIgnoresBlankFinishReason(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Content-Type", "text/event-stream")