Summary
When a streaming request to the Anthropic Messages API (POST /v1/messages) is routed through the Copilot provider, the api-proxy injects stream_options: { include_usage: true } into the body. The Copilot Messages endpoint does not accept stream_options, so the upstream returns a 400 and the request fails.
Version
Observed on 0.27.31. The relevant code is unchanged on main and in the latest release (0.27.44).
Root cause
The Copilot provider (containers/api-proxy/providers/copilot.js, provider: 'copilot') fronts an endpoint that serves the Anthropic Messages API at /v1/messages alongside the OpenAI-compatible routes. body-handler.js calls injectStreamOptions(body, provider, req.url), and injectStreamOptions in body-transform.js only skips injection when the provider is anthropic or gemini, or when the path is the OpenAI Responses route.
A Messages request through the Copilot provider has provider === 'copilot' and path /v1/messages, so neither guard fires and stream_options is injected into a body the Messages endpoint rejects.
Relevant guards in injectStreamOptions:
if (provider === 'anthropic' || provider === 'gemini') return null;
// ...
if (/^\/?(?:v\d+\/)?responses(?:\/|$)/.test(pathOnly)) return null;
Reproduction
The Copilot Messages endpoint accepts a streaming request without stream_options, but rejects the same request once stream_options is added. stream_options is accepted on the OpenAI-compatible /chat/completions route, which is why the injection exists and why the fix should be scoped by route rather than removed.
# Works: streaming /v1/messages without stream_options -> HTTP 200
curl -sS -o /dev/null -w "%{http_code}\n" https://api.githubcopilot.com/v1/messages \
-H "Authorization: Bearer $COPILOT_TOKEN" \
-H "Copilot-Integration-Id: copilot-developer-cli" \
-H "Content-Type: application/json" -H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-haiku-4.5","max_tokens":16,"stream":true,"messages":[{"role":"user","content":"hi"}]}'
# Fails: same request with stream_options -> HTTP 400
curl -sS -w "\n%{http_code}\n" https://api.githubcopilot.com/v1/messages \
-H "Authorization: Bearer $COPILOT_TOKEN" \
-H "Copilot-Integration-Id: copilot-developer-cli" \
-H "Content-Type: application/json" -H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-haiku-4.5","max_tokens":16,"stream":true,"stream_options":{"include_usage":true},"messages":[{"role":"user","content":"hi"}]}'
Observed 400 body:
{"type":"error","error":{"type":"invalid_request_error","message":"stream_options: Extra inputs are not permitted"}}
Suggested fix
Add a path guard for the Anthropic Messages route, mirroring the existing /responses guard, so injection is skipped for /messages and /v1/messages regardless of provider. A path-based guard is provider-agnostic and covers the Copilot provider serving the Messages API, while keeping injection on the OpenAI-compatible routes that accept stream_options.
Summary
When a streaming request to the Anthropic Messages API (
POST /v1/messages) is routed through the Copilot provider, the api-proxy injectsstream_options: { include_usage: true }into the body. The Copilot Messages endpoint does not acceptstream_options, so the upstream returns a 400 and the request fails.Version
Observed on 0.27.31. The relevant code is unchanged on
mainand in the latest release (0.27.44).Root cause
The Copilot provider (
containers/api-proxy/providers/copilot.js,provider: 'copilot') fronts an endpoint that serves the Anthropic Messages API at/v1/messagesalongside the OpenAI-compatible routes.body-handler.jscallsinjectStreamOptions(body, provider, req.url), andinjectStreamOptionsinbody-transform.jsonly skips injection when the provider isanthropicorgemini, or when the path is the OpenAI Responses route.A Messages request through the Copilot provider has
provider === 'copilot'and path/v1/messages, so neither guard fires andstream_optionsis injected into a body the Messages endpoint rejects.Relevant guards in
injectStreamOptions:Reproduction
The Copilot Messages endpoint accepts a streaming request without
stream_options, but rejects the same request oncestream_optionsis added.stream_optionsis accepted on the OpenAI-compatible/chat/completionsroute, which is why the injection exists and why the fix should be scoped by route rather than removed.Observed 400 body:
{"type":"error","error":{"type":"invalid_request_error","message":"stream_options: Extra inputs are not permitted"}}Suggested fix
Add a path guard for the Anthropic Messages route, mirroring the existing
/responsesguard, so injection is skipped for/messagesand/v1/messagesregardless of provider. A path-based guard is provider-agnostic and covers the Copilot provider serving the Messages API, while keeping injection on the OpenAI-compatible routes that acceptstream_options.