Skip to content

fix(gom-43): remove stream_options injection from Responses API handler#85

Merged
SantiagoDePolonia merged 2 commits intomainfrom
worktree-gom-43-fix-stream-options
Feb 24, 2026
Merged

fix(gom-43): remove stream_options injection from Responses API handler#85
SantiagoDePolonia merged 2 commits intomainfrom
worktree-gom-43-fix-stream-options

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented Feb 23, 2026

The Responses API does not accept stream_options.include_usage — it's a Chat Completions-only parameter. The Responses API already returns usage data unconditionally in the response.done SSE event.

Summary by CodeRabbit

  • Bug Fixes

    • Streaming responses no longer automatically include usage data based on configuration.
  • Tests

    • Added tests to verify streaming behavior and ensure usage data is not injected into streamed responses.

The Responses API does not accept stream_options.include_usage — it's a
Chat Completions-only parameter. The Responses API already returns usage
data unconditionally in the response.done SSE event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Removes automatic injection of StreamOptions.IncludeUsage for Responses streaming; chat completion streaming still injects usage. Unit and end-to-end tests added to assert the difference and prevent regressions.

Changes

Cohort / File(s) Summary
Stream Options Injection Removal
internal/server/handlers.go
Removes the conditional that initialized req.StreamOptions with IncludeUsage = true for Responses streaming when EnforceReturningUsageData was set.
Unit Test Infrastructure & Coverage
internal/server/handlers_test.go
Adds mockUsageLogger, capturingProvider, and tests TestStreamingResponses_DoesNotInjectStreamOptions and TestStreamingChatCompletion_InjectsStreamOptions to verify presence/absence of StreamOptions and IncludeUsage.
End-to-End Regression Test
tests/e2e/responses_test.go
Adds subtest "streaming does not inject stream_options" to ensure Responses streaming excludes stream_options and still emits valid SSE events (including done event).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Provider
    participant UsageLogger

    Client->>Server: POST /v1/responses (stream=true)
    Note over Server: OLD BEHAVIOR (before change)
    Server->>Server: EnforceReturningUsageData? -> set req.StreamOptions.IncludeUsage = true
    Server->>Provider: StreamResponses(req with StreamOptions)
    Provider->>UsageLogger: Emit usage events (via IncludeUsage)
    Provider->>Client: SSE stream events ... done
Loading
sequenceDiagram
    participant Client
    participant Server
    participant Provider
    participant UsageLogger

    Client->>Server: POST /v1/responses (stream=true)
    Note over Server: NEW BEHAVIOR (after change)
    Server->>Server: Does not inject req.StreamOptions for Responses
    Server->>Provider: StreamResponses(req without StreamOptions)
    Provider-->>UsageLogger: (no automatic usage emission triggered by IncludeUsage)
    Provider->>Client: SSE stream events ... done

    Note over Server,Provider: Chat completions path still injects StreamOptions.IncludeUsage
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hopping through streams with a jubilant cheer,
No hidden options whispering near,
Responses run clean, chat keeps its light,
Tests stand guard through day and night,
A small change, a tidy delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: removing stream_options injection from the Responses API handler, which aligns with the changeset modifications across handlers, tests, and e2e tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch worktree-gom-43-fix-stream-options

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/server/handlers_test.go`:
- Around line 611-617: The test currently uses t.Errorf for the status check and
then dereferences provider.capturedResponsesReq.StreamOptions which can panic if
capturedResponsesReq is nil; change the status assertion to t.Fatalf so the test
stops on non-200, and add a nil guard before inspecting
provider.capturedResponsesReq (e.g., check if provider.capturedResponsesReq ==
nil and fail with t.Fatalf or t.Errorf accordingly) before accessing
StreamOptions; reference the identifiers provider, capturedResponsesReq,
StreamOptions and the status assertion around rec.Code to locate the code to
change.
- Around line 651-661: The status check in TestStreamingResponses (the t.Errorf
that asserts rec.Code == http.StatusOK) must be made fatal or otherwise guarded
so we don't continue when the response is not OK and provider.capturedChatReq
may be nil; change the non-fatal assertion to t.Fatalf (or add an immediate
nil-check on provider.capturedChatReq before accessing StreamOptions) so that
accessing provider.capturedChatReq.StreamOptions cannot panic, and ensure the
test still verifies StreamOptions and IncludeUsage afterward.

In `@tests/e2e/responses_test.go`:
- Around line 143-162: The subtest currently named "streaming does not inject
stream_options" never asserts absence of stream_options; update the test to
actually verify the outgoing request body does not include "stream_options" (or
"include_usage") by either having sendResponsesRequest return the captured HTTP
request (or exposing a mock server helper to fetch the lastRequest) and assert
the request JSON does not contain the stream_options key, or delete this
redundant subtest and update the comment to reference the unit test
TestStreamingResponses_DoesNotInjectStreamOptions in handlers_test.go as the
regression guard; use the payload variable and helpers
sendResponsesRequest/readResponsesStream/hasDoneEvent to locate and adjust the
test accordingly.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9963f9 and 4085ee6.

📒 Files selected for processing (3)
  • internal/server/handlers.go
  • internal/server/handlers_test.go
  • tests/e2e/responses_test.go
💤 Files with no reviewable changes (1)
  • internal/server/handlers.go

Comment on lines +651 to +661
if rec.Code != http.StatusOK {
t.Errorf("expected status 200, got %d", rec.Code)
}

if provider.capturedChatReq.StreamOptions == nil {
t.Fatal("ChatCompletion streaming should have StreamOptions injected")
}

if !provider.capturedChatReq.StreamOptions.IncludeUsage {
t.Error("ChatCompletion streaming should have IncludeUsage=true")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Same nil-guard / non-fatal status assertion issue as in TestStreamingResponses_DoesNotInjectStreamOptions.

t.Errorf at line 651 allows execution to continue to line 655, where provider.capturedChatReq.StreamOptions panics if capturedChatReq is nil.

🛡️ Proposed fix
-	if rec.Code != http.StatusOK {
-		t.Errorf("expected status 200, got %d", rec.Code)
-	}
-
-	if provider.capturedChatReq.StreamOptions == nil {
-		t.Fatal("ChatCompletion streaming should have StreamOptions injected")
-	}
-
-	if !provider.capturedChatReq.StreamOptions.IncludeUsage {
-		t.Error("ChatCompletion streaming should have IncludeUsage=true")
-	}
+	if rec.Code != http.StatusOK {
+		t.Fatalf("expected status 200, got %d", rec.Code)
+	}
+
+	if provider.capturedChatReq == nil {
+		t.Fatal("StreamChatCompletion was not called")
+	}
+	if provider.capturedChatReq.StreamOptions == nil {
+		t.Fatal("ChatCompletion streaming should have StreamOptions injected")
+	}
+	if !provider.capturedChatReq.StreamOptions.IncludeUsage {
+		t.Error("ChatCompletion streaming should have IncludeUsage=true")
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/handlers_test.go` around lines 651 - 661, The status check in
TestStreamingResponses (the t.Errorf that asserts rec.Code == http.StatusOK)
must be made fatal or otherwise guarded so we don't continue when the response
is not OK and provider.capturedChatReq may be nil; change the non-fatal
assertion to t.Fatalf (or add an immediate nil-check on provider.capturedChatReq
before accessing StreamOptions) so that accessing
provider.capturedChatReq.StreamOptions cannot panic, and ensure the test still
verifies StreamOptions and IncludeUsage afterward.

Comment on lines +143 to +162
t.Run("streaming does not inject stream_options", func(t *testing.T) {
// Regression test for GOM-43: streaming Responses API must not include
// stream_options.include_usage, which is a Chat Completions-only parameter.
// The Responses API returns usage in the response.done event by default.
payload := core.ResponsesRequest{
Model: "gpt-4.1",
Input: "Hello",
Stream: true,
}

resp := sendResponsesRequest(t, payload)
defer closeBody(resp)

require.Equal(t, http.StatusOK, resp.StatusCode, "Streaming responses should succeed without stream_options")
assert.Equal(t, "text/event-stream", resp.Header.Get("Content-Type"))

events := readResponsesStream(t, resp.Body)
require.Greater(t, len(events), 0, "Should receive at least one SSE event")
assert.True(t, hasDoneEvent(events), "Should receive done event")
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The E2E subtest doesn't assert what its name implies.

The subtest is named "streaming does not inject stream_options" and the comment calls it a regression test for GOM-43, but none of the assertions verify the absence of stream_options in the outgoing request. The four checks (status 200, text/event-stream, at least one event, done event) are identical to those in the "basic streaming" sibling test — differing only in the input string. If the in-process mock LLM server doesn't return a non-2xx response when it receives stream_options.include_usage, this test will pass regardless of whether the regression is reintroduced.

The unit test TestStreamingResponses_DoesNotInjectStreamOptions in handlers_test.go directly captures the request and is the real regression guard. Consider either:

  • Adding an assertion here that the mock records/rejects the parameter (if the mock server supports it), or
  • Removing this subtest as redundant and calling out in the test comment that the unit test covers the regression.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/responses_test.go` around lines 143 - 162, The subtest currently
named "streaming does not inject stream_options" never asserts absence of
stream_options; update the test to actually verify the outgoing request body
does not include "stream_options" (or "include_usage") by either having
sendResponsesRequest return the captured HTTP request (or exposing a mock server
helper to fetch the lastRequest) and assert the request JSON does not contain
the stream_options key, or delete this redundant subtest and update the comment
to reference the unit test TestStreamingResponses_DoesNotInjectStreamOptions in
handlers_test.go as the regression guard; use the payload variable and helpers
sendResponsesRequest/readResponsesStream/hasDoneEvent to locate and adjust the
test accordingly.

Use t.Fatalf for status assertion and add nil guard on
capturedResponsesReq before accessing StreamOptions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/server/handlers_test.go (1)

654-664: ⚠️ Potential issue | 🟡 Minor

Nil-dereference risk on capturedChatReq still present — same issue as previous review.

t.Errorf on line 655 is non-fatal. If the handler short-circuits before calling StreamChatCompletion (e.g., routing/auth fails), capturedChatReq stays nil and provider.capturedChatReq.StreamOptions on line 658 panics instead of failing cleanly. Promote the status check to t.Fatalf and add a nil guard mirroring the fix already applied to TestStreamingResponses_DoesNotInjectStreamOptions.

🛡️ Proposed fix
 	if rec.Code != http.StatusOK {
-		t.Errorf("expected status 200, got %d", rec.Code)
+		t.Fatalf("expected status 200, got %d", rec.Code)
 	}

+	if provider.capturedChatReq == nil {
+		t.Fatal("StreamChatCompletion was not called")
+	}
 	if provider.capturedChatReq.StreamOptions == nil {
 		t.Fatal("ChatCompletion streaming should have StreamOptions injected")
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/handlers_test.go` around lines 654 - 664, Change the
non-fatal status assertion to a fatal one and guard against a nil
capturedChatReq before dereferencing its StreamOptions: replace the
t.Errorf("expected status 200...") with t.Fatalf(...) and add a nil check for
provider.capturedChatReq (matching the pattern used in
TestStreamingResponses_DoesNotInjectStreamOptions) so the test exits cleanly if
StreamChatCompletion was never called before asserting
provider.capturedChatReq.StreamOptions and IncludeUsage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/server/handlers_test.go`:
- Around line 554-578: The capture methods on capturingProvider
(StreamChatCompletion and StreamResponses) ignore the error field defined on
mockProvider (c.err) causing inconsistent mock behavior; update both methods in
capturingProvider to propagate the underlying mock error by checking c.err (or
delegating to mockProvider.StreamChatCompletion/StreamResponses) and returning
that error when set, otherwise capture the request into
capturedChatReq/capturedResponsesReq and return the stream as before.

---

Duplicate comments:
In `@internal/server/handlers_test.go`:
- Around line 654-664: Change the non-fatal status assertion to a fatal one and
guard against a nil capturedChatReq before dereferencing its StreamOptions:
replace the t.Errorf("expected status 200...") with t.Fatalf(...) and add a nil
check for provider.capturedChatReq (matching the pattern used in
TestStreamingResponses_DoesNotInjectStreamOptions) so the test exits cleanly if
StreamChatCompletion was never called before asserting
provider.capturedChatReq.StreamOptions and IncludeUsage.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4085ee6 and 06b6da1.

📒 Files selected for processing (1)
  • internal/server/handlers_test.go

Comment on lines +554 to +578
// mockUsageLogger implements usage.LoggerInterface for testing.
type mockUsageLogger struct {
config usage.Config
}

func (m *mockUsageLogger) Write(_ *usage.UsageEntry) {}
func (m *mockUsageLogger) Config() usage.Config { return m.config }
func (m *mockUsageLogger) Close() error { return nil }

// capturingProvider is a mockProvider that captures the request passed to StreamResponses/StreamChatCompletion.
type capturingProvider struct {
mockProvider
capturedChatReq *core.ChatRequest
capturedResponsesReq *core.ResponsesRequest
}

func (c *capturingProvider) StreamChatCompletion(_ context.Context, req *core.ChatRequest) (io.ReadCloser, error) {
c.capturedChatReq = req
return io.NopCloser(strings.NewReader(c.streamData)), nil
}

func (c *capturingProvider) StreamResponses(_ context.Context, req *core.ResponsesRequest) (io.ReadCloser, error) {
c.capturedResponsesReq = req
return io.NopCloser(strings.NewReader(c.streamData)), nil
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

capturingProvider capture methods silently ignore m.err

StreamChatCompletion and StreamResponses on capturingProvider always return nil error regardless of c.mockProvider.err, while the base mockProvider versions honour it. This is harmless for the current tests (no error is set), but makes the mock inconsistent and could silently mask failures if a future test sets err and expects an error from those paths.

♻️ Optional: propagate err in capture methods
 func (c *capturingProvider) StreamChatCompletion(_ context.Context, req *core.ChatRequest) (io.ReadCloser, error) {
 	c.capturedChatReq = req
+	if c.err != nil {
+		return nil, c.err
+	}
 	return io.NopCloser(strings.NewReader(c.streamData)), nil
 }

 func (c *capturingProvider) StreamResponses(_ context.Context, req *core.ResponsesRequest) (io.ReadCloser, error) {
 	c.capturedResponsesReq = req
+	if c.err != nil {
+		return nil, c.err
+	}
 	return io.NopCloser(strings.NewReader(c.streamData)), nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/handlers_test.go` around lines 554 - 578, The capture methods
on capturingProvider (StreamChatCompletion and StreamResponses) ignore the error
field defined on mockProvider (c.err) causing inconsistent mock behavior; update
both methods in capturingProvider to propagate the underlying mock error by
checking c.err (or delegating to
mockProvider.StreamChatCompletion/StreamResponses) and returning that error when
set, otherwise capture the request into capturedChatReq/capturedResponsesReq and
return the stream as before.

@SantiagoDePolonia SantiagoDePolonia merged commit 9e534de into main Feb 24, 2026
12 checks passed
@SantiagoDePolonia SantiagoDePolonia deleted the worktree-gom-43-fix-stream-options branch March 22, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant