Stream Anthropic /messages responses in E2E fake handlers#1868
Conversation
The runtime's native Anthropic transport sends /messages requests with stream:true and drains any 2xx response as an SSE stream, then finalizes it to a Message. The E2E fake handlers returned buffered application/json for /messages, so the newer CLI raised 'stream ended without producing a Message with role=assistant' (MissingFinalMessage). Update all six SDK E2E fake handlers to return a proper Anthropic Messages SSE sequence (message_start through message_stop) when the request body requests streaming, keeping buffered JSON for non-streaming requests. This fixes the C# leg in copilot-agent-runtime CI and prevents the equivalent break in the other five SDKs when the pinned CLI is bumped. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the SDK E2E “fake upstream” request handlers across all language legs to correctly emulate Anthropic /messages streaming by returning a text/event-stream (SSE) sequence when the request body sets stream: true. This aligns the test stubs with the newer CLI/runtime behavior that always drains successful /messages responses as an SSE stream.
Changes:
- Add an Anthropic Messages SSE “message_start … message_stop” response path for streaming
/messagesrequests. - Preserve the existing buffered
application/jsonresponse for non-streaming/messagesrequests. - Thread request bodies into inference response builders where needed (e.g., Rust/Node/Java) to detect streaming requests.
Show a summary per file
| File | Description |
|---|---|
| rust/tests/e2e/session_config.rs | Adds SSE response helpers and returns Anthropic /messages SSE when the request asks for streaming. |
| python/e2e/_copilot_request_helpers.py | Returns Anthropic /messages SSE body for streaming requests; keeps JSON for non-streaming. |
| nodejs/test/e2e/session_config.e2e.test.ts | Adds SSE builder + Anthropic stream body, switching based on "stream": true in the request body. |
| java/src/test/java/com/github/copilot/CopilotRequestTestSupport.java | Introduces anthropicMessageSseBody and returns SSE for streaming /messages requests. |
| go/internal/e2e/copilot_request_helpers_test.go | Adds buildAnthropicMessageSSEBody and uses it for streaming /messages responses. |
| dotnet/test/E2E/CopilotRequestE2EProvider.cs | Switches /messages handler to return Anthropic SSE sequence when stream: true is detected. |
Review details
- Files reviewed: 6/6 changed files
- Comments generated: 0
- Review effort level: Low
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cross-SDK Consistency Review ✅This PR does an excellent job of maintaining consistency across all six SDK implementations. A quick audit: Verified consistent across all 6 SDKs (Node.js, Python, Go, .NET, Java, Rust)
Minor style note (no action required)Five SDKs extract the synthetic text into a named constant ( No cross-SDK consistency gaps introduced by this PR.
|
Why
The C# SDK leg in copilot-agent-runtime CI started failing with:
The runtime recently ported Anthropic model orchestration to a native Rust transport. That transport sends
/messagesrequests withstream: trueand unconditionally drains any 2xx response as an SSE stream, then finalizes it to aMessage. Our E2E fake handlers returned bufferedapplication/jsonfor/messages, which has no SSE events, so the newer CLI raisedMissingFinalMessage.This is a test-fidelity gap, not a production bug: real Anthropic returns
text/event-streamfor streaming requests. The SDK's own CI still passes because it runs against pinned CLI 1.0.67 (before the transport change), while the runtime leg builds the CLI frommain.What
Update all six SDK E2E fake request handlers so the
/messagesendpoint returns a proper Anthropic Messages SSE sequence (message_start->content_block_start->content_block_delta->content_block_stop->message_delta->message_stop) when the request body requests streaming, while keeping the buffered JSON response for non-streaming requests.Handlers updated:
dotnet/test/E2E/CopilotRequestE2EProvider.csnodejs/test/e2e/session_config.e2e.test.tspython/e2e/_copilot_request_helpers.pygo/internal/e2e/copilot_request_helpers_test.gorust/tests/e2e/session_config.rsjava/src/test/java/com/github/copilot/CopilotRequestTestSupport.javaThe change is backward compatible: with the pinned CLI (which does not stream Anthropic), the new branch is inert; with the newer CLI it satisfies the SSE expectation. Only C# was failing today, but the other five share the same stub and would break the moment the pinned CLI is bumped past the transport change, so all six are fixed together.
Verification
dotnet buildpassestsc --noEmitcleanpy_compilecleantest-compileBUILD SUCCESS, plusspotless:applygotoolchain; noclangfor Rust'sringbuild). Both edits were reviewed by hand against the existing streaming patterns in their files.Generated by Copilot