Skip to content

Fix anthropic stream and turn off Encoding when SSE#11

Merged
jhaynie merged 4 commits intomainfrom
fix-anthropic-stream-metadata
May 4, 2026
Merged

Fix anthropic stream and turn off Encoding when SSE#11
jhaynie merged 4 commits intomainfrom
fix-anthropic-stream-metadata

Conversation

@jhaynie
Copy link
Copy Markdown
Member

@jhaynie jhaynie commented May 4, 2026

Summary by CodeRabbit

  • Bug Fixes

    • HTTP response header forwarding now omits encoding/length headers to prevent conflicts; upstream streaming requests now force identity encoding.
  • New Features

    • Incoming request "stream" flag is propagated through processing so streaming requests are handled correctly.
  • Tests

    • Added tests validating header filtering behavior and parsing of the stream flag.
  • Chores

    • Updated module dependencies.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90d31d06-2c4d-4f06-a5e0-27a530846086

📥 Commits

Reviewing files that changed from the base of the PR and between af0395e and 8a49ae7.

📒 Files selected for processing (1)
  • autorouter.go

📝 Walkthrough

Walkthrough

Adds an internal header-copy helper that omits Content-Encoding and Content-Length, forces upstream Accept-Encoding: identity for streaming, propagates Anthropic’s stream flag into parsed metadata, updates tests to cover these behaviors, and updates go module requirements.

Changes

Header forwarding & Anthropic stream flag

Layer / File(s) Summary
Data Shape
providers/anthropic/parser.go
Request gains Stream bool \json:"stream,omitempty"`to represent the Anthropicstream` flag.
Parsing / Propagation
providers/anthropic/parser.go
Parse assigns meta.Stream = req.Stream so parsed metadata carries the stream flag.
Core Helper
autorouter.go
Added unexported copyResponseHeaders(w, headers) and skipHeaders to copy response headers while omitting Content-Encoding and Content-Length (case-insensitive).
Streaming Behavior
autorouter.go
ForwardStreaming sets upstream Accept-Encoding: identity and uses copyResponseHeaders to forward upstream response headers before writing status.
Non-stream path
autorouter.go
ServeHTTP now uses copyResponseHeaders instead of copying all response headers directly.
Tests
providers/anthropic/parser_test.go, autorouter_test.go
Added subtest verifying meta.Stream parses from "stream": true; added TestAutoRouter_copyResponseHeaders asserting header copy excludes Content-Encoding/Content-Length case-insensitively and preserves other headers.

Dependency Updates

Layer / File(s) Summary
Module Requirements
go.mod
Added github.com/agentuity/go-common v1.0.231 to require; bumped indirect golang.org/x/sys from an older pseudo-version to v0.42.0.
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


Review rate limit: 2/5 reviews remaining, refill in 29 minutes and 36 seconds.

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

Copy link
Copy Markdown

@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: 2

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

Inline comments:
In `@autorouter.go`:
- Around line 16-27: The copyResponseHeaders function is currently removing
headers in skipHeaders (which always includes "Content-Encoding") causing
encoded responses to be forwarded without the encoding header; update the logic
so Content-Encoding is not stripped unless the proxy actually decoded the body:
modify the skipHeaders usage or list so it does not include "Content-Encoding"
by default, or add a condition in copyResponseHeaders that preserves
"Content-Encoding" unless a body-decoding flag/state (e.g., a decodedBody
boolean passed into copyResponseHeaders or a response-processing function like
the upstream decoder) indicates the payload was decompressed—ensure you adjust
references to skipHeaders and the call sites of copyResponseHeaders accordingly.
- Around line 21-24: Remove the debugging stdout print from the header
forwarding loop: in the section where the code iterates over v and calls
header.Add(k, val) (the loop containing fmt.Println("SETTING HEADER", k, "=>",
val)), delete the fmt.Println call so only header.Add(k, val) remains; ensure no
other stdout logging remains in that proxy header forwarding path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 02227d8d-58cc-41ac-b24b-c584c7709041

📥 Commits

Reviewing files that changed from the base of the PR and between b5bd117 and 3d57434.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (4)
  • autorouter.go
  • go.mod
  • providers/anthropic/parser.go
  • providers/anthropic/parser_test.go
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (2)
providers/anthropic/parser.go (1)

44-49: Good stream propagation.

This keeps BodyMetadata.Stream aligned with Anthropic requests and avoids leaking stream into Custom.

Also applies to: 87-93, 151-155

providers/anthropic/parser_test.go (1)

48-59: Nice regression test for the stream bit.

This covers the behavior added in the parser and would catch the original miss quickly.

Comment thread autorouter.go
Comment on lines +16 to +27
func copyResponseHeaders(w http.ResponseWriter, headers http.Header) {
header := w.Header()

for k, v := range headers {
if !slice.Contains(skipHeaders, k, slice.WithCaseInsensitive()) {
for _, val := range v {
header.Add(k, val)
fmt.Println("SETTING HEADER", k, "=>", val)
}
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't strip Content-Encoding unless this proxy has already decoded the body.

These paths forward upstream bytes back to the client, but the shared skip list now always removes Content-Encoding. If any upstream response still comes back encoded, clients will receive compressed bytes labeled as a plain payload.

Suggested change
-var skipHeaders = []string{"Content-Encoding", "Content-Length"}
+var skipHeaders = []string{"Content-Length"}

Also applies to: 440-440

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autorouter.go` around lines 16 - 27, The copyResponseHeaders function is
currently removing headers in skipHeaders (which always includes
"Content-Encoding") causing encoded responses to be forwarded without the
encoding header; update the logic so Content-Encoding is not stripped unless the
proxy actually decoded the body: modify the skipHeaders usage or list so it does
not include "Content-Encoding" by default, or add a condition in
copyResponseHeaders that preserves "Content-Encoding" unless a body-decoding
flag/state (e.g., a decodedBody boolean passed into copyResponseHeaders or a
response-processing function like the upstream decoder) indicates the payload
was decompressed—ensure you adjust references to skipHeaders and the call sites
of copyResponseHeaders accordingly.

Comment thread autorouter.go
Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
autorouter_test.go (1)

826-894: ⚡ Quick win

Add regression cases for multi-value and preexisting destination headers.

Current coverage validates filtering, but it does not protect against repeated headers (Set-Cookie) or pre-set response headers (e.g., Trailer) being overwritten. Adding those two cases will lock in the expected proxy semantics.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autorouter_test.go` around lines 826 - 894, The test
TestAutoRouter_copyResponseHeaders lacks regression cases for multi-value
headers and for not overwriting preexisting destination headers; add two
subcases that call copyResponseHeaders: one where the source header includes a
multi-valued header like "Set-Cookie" (ensure all values are copied, not
collapsed or lost), and one where the destination recorder already has a header
such as "Trailer" pre-set (ensure copyResponseHeaders does not overwrite or drop
the existing value on the ResponseRecorder). Locate the test function
TestAutoRouter_copyResponseHeaders and extend it with assertions that verify all
Set-Cookie values appear and that preexisting Trailer (or similar) header values
remain intact after calling copyResponseHeaders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autorouter.go`:
- Around line 23-28: The copyResponseHeaders loop currently uses header.Add for
the first value and header.Set for subsequent values causing multi-value headers
to be collapsed and existing response headers (e.g., Trailer) to be overwritten;
update the loop in copyResponseHeaders to always call header.Add for every value
v in range v (for each k, v := range ... and for _, val := range v) so that all
upstream header values are preserved and existing w.Header() entries are not
clobbered.

---

Nitpick comments:
In `@autorouter_test.go`:
- Around line 826-894: The test TestAutoRouter_copyResponseHeaders lacks
regression cases for multi-value headers and for not overwriting preexisting
destination headers; add two subcases that call copyResponseHeaders: one where
the source header includes a multi-valued header like "Set-Cookie" (ensure all
values are copied, not collapsed or lost), and one where the destination
recorder already has a header such as "Trailer" pre-set (ensure
copyResponseHeaders does not overwrite or drop the existing value on the
ResponseRecorder). Locate the test function TestAutoRouter_copyResponseHeaders
and extend it with assertions that verify all Set-Cookie values appear and that
preexisting Trailer (or similar) header values remain intact after calling
copyResponseHeaders.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e9b33a3c-0ba0-4483-bec4-326e4758eeee

📥 Commits

Reviewing files that changed from the base of the PR and between 3d57434 and aadbab7.

📒 Files selected for processing (2)
  • autorouter.go
  • autorouter_test.go
📜 Review details
🔇 Additional comments (2)
autorouter.go (2)

16-16: Content-Encoding is still being stripped globally (previously raised).

Line 16 still excludes Content-Encoding for all proxied responses, while Line 327 only forces Accept-Encoding: identity in the streaming path. Non-stream responses can still carry encoded payloads, so this remains the same unresolved risk noted earlier.

Also applies to: 366-366, 491-491


326-327: Good SSE safeguard with Accept-Encoding: identity.

This is a solid change for stream stability and parser consistency when proxying SSE.

Comment thread autorouter.go Outdated
Copy link
Copy Markdown

@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

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

Inline comments:
In `@autorouter_test.go`:
- Around line 894-895: The test contains a dead reassignment "w =
httptest.NewRecorder()" after "sw.Reset()" where the variable w is never used
later (SA4006); remove the redundant line assigning to w (or if intended,
replace with a real use of the new recorder) so the unused reassignment is
eliminated and lint stops flagging w as unused.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91cf0150-63b1-44dd-b3fb-46911181ea8e

📥 Commits

Reviewing files that changed from the base of the PR and between aadbab7 and af0395e.

📒 Files selected for processing (2)
  • autorouter.go
  • autorouter_test.go
✅ Files skipped from review due to trivial changes (1)
  • autorouter.go
📜 Review details
🧰 Additional context used
🪛 golangci-lint (2.11.4)
autorouter_test.go

[error] 895-895: SA4006: this value of w is never used

(staticcheck)

🔇 Additional comments (1)
autorouter_test.go (1)

826-893: Nice coverage of header-copy behavior.

This test cleanly covers empty headers, normal header pass-through, and case-insensitive filtering of reserved headers.

Comment thread autorouter_test.go
Comment on lines +894 to +895
sw.Reset()
w = httptest.NewRecorder()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove dead code at the end of the test.

Line 895 reassigns w but it is never read afterward (SA4006), which can fail lint.

✂️ Proposed fix
-	sw.Reset()
-	w = httptest.NewRecorder()
+	sw.Reset()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sw.Reset()
w = httptest.NewRecorder()
sw.Reset()
🧰 Tools
🪛 golangci-lint (2.11.4)

[error] 895-895: SA4006: this value of w is never used

(staticcheck)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autorouter_test.go` around lines 894 - 895, The test contains a dead
reassignment "w = httptest.NewRecorder()" after "sw.Reset()" where the variable
w is never used later (SA4006); remove the redundant line assigning to w (or if
intended, replace with a real use of the new recorder) so the unused
reassignment is eliminated and lint stops flagging w as unused.

@jhaynie jhaynie merged commit 5dacc0a into main May 4, 2026
1 of 2 checks passed
@jhaynie jhaynie deleted the fix-anthropic-stream-metadata branch May 4, 2026 01:30
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