fix: fall back to streamed delta text when completed Codex response is empty

- Prefer extracted completed text only when content stays non-empty
- Fall back to accumulated streamed delta text otherwise
- Preserve streamed response text before completed response evaluation
- Add regression test for empty completed output text
- Simulate SSE delta stream followed by blank completion
- Verify Send returns delta text when completion lacks content
This commit is contained in:
Kayvan Sylvan 2026-04-16 20:16:23 -07:00
parent 99dcff0f36
commit 78f9d7c1f7
2 changed files with 60 additions and 2 deletions

View file

@ -242,11 +242,14 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
if err := c.mapRequestError(stream.Err()); err != nil {
return "", err
}
streamedText := builder.String()
if completedResp != nil {
return c.ExtractText(completedResp), nil
if extractedText := c.ExtractText(completedResp); strings.TrimSpace(extractedText) != "" {
return extractedText, nil
}
}
return builder.String(), nil
return streamedText, nil
}
// SendStream sends a request to Codex and streams the response text updates.

View file

@ -429,6 +429,61 @@ func TestSendIncludesSourcesFromAnnotatedResponse(t *testing.T) {
}
}
func TestSendFallsBackToDeltaWhenCompletedResponseHasNoText(t *testing.T) {
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/responses" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/event-stream")
flusher, ok := w.(http.Flusher)
if !ok {
t.Fatalf("response writer does not implement http.Flusher")
}
fmt.Fprintf(w, "data: %s\n\n", marshalJSON(t, map[string]any{
"type": string(constant.ResponseOutputTextDelta("").Default()),
"delta": "hello from delta",
}))
flusher.Flush()
fmt.Fprintf(w, "data: %s\n\n", marshalJSON(t, map[string]any{
"type": "response.completed",
"response": map[string]any{
"output": []any{
map[string]any{
"type": "message",
"content": []any{
map[string]any{
"type": "output_text",
"text": "",
},
},
},
},
},
}))
flusher.Flush()
fmt.Fprint(w, "data: [DONE]\n\n")
}))
defer apiServer.Close()
client := newConfiguredTestClient(t, apiServer.URL, "acct_delta_fallback", testJWT("acct_delta_fallback", time.Now().Add(time.Hour)))
message, err := client.Send(context.Background(), []*chat.ChatCompletionMessage{
{Role: chat.ChatMessageRoleUser, Content: "Hello"},
}, &domain.ChatOptions{
Model: "gpt-5.4",
Temperature: 0.7,
})
if err != nil {
t.Fatalf("Send() error = %v", err)
}
if message != "hello from delta" {
t.Fatalf("Send() = %q, want %q", message, "hello from delta")
}
}
func TestSendStreamReadsCodexSSE(t *testing.T) {
apiServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/responses" {