Skip to content

api-proxy injects stream_options into Copilot Messages API (/v1/messages) requests, causing 400 #7201

Description

@anticomputer

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions