Skip to content

feat(github_copilot): route /v1/messages to Copilot native Anthropic endpoint - #31802

Merged
mateo-berri merged 9 commits into
litellm_internal_stagingfrom
litellm_copilot_anthropic_messages_native
Jul 2, 2026
Merged

feat(github_copilot): route /v1/messages to Copilot native Anthropic endpoint#31802
mateo-berri merged 9 commits into
litellm_internal_stagingfrom
litellm_copilot_anthropic_messages_native

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

This is a copy of #28054 by @ririnto, rebased onto the current litellm_internal_staging so it can run through our CI. All credit for the implementation goes to @ririnto; this branch only resolves the merge conflict against the moved base and normalizes formatting to the current ruff

Linear ticket

n/a

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

Screenshots / Proof of Fix

Direct upstream verification from the original PR (independent of LiteLLM, to isolate Copilot's behaviour):

# Before: Copilot /chat/completions cannot emit thinking
POST .../chat/completions  reasoning_effort=high
  -> 400 invalid_reasoning_effort
POST .../chat/completions  thinking={type:enabled, budget_tokens:16000}
  -> 200 plain text only

# After: Copilot /v1/messages emits thinking blocks
POST .../v1/messages  thinking={type:enabled, budget_tokens:16000}
  -> 200 content=[{type:"thinking", thinking:"..."}, {type:"text", text:"..."}]
          usage.output_tokens=8413  (thinking_chars=9094)
# Streaming:
POST .../v1/messages  stream=true  thinking={type:enabled, budget_tokens:16000}
  -> SSE: message_start, content_block_start(type=thinking), content_block_delta(thinking_delta)...,
          content_block_stop, content_block_start(type=text), ...

This PR makes LiteLLM produce the "After" response when a client calls POST /v1/messages against a github_copilot Claude model. Unit tests cover config wiring, URL construction, header merging precedence, and auth error mapping

Type

New Feature

Changes

  • New litellm/llms/github_copilot/messages/__init__.py
  • New litellm/llms/github_copilot/messages/transformation.py
    • GithubCopilotAnthropicMessagesConfig(AnthropicMessagesConfig)
    • Overrides validate_anthropic_messages_environment (Copilot auth + integration headers + anthropic-version) and get_complete_url (/v1/messages)
    • Caller-supplied api_base is intentionally ignored so the Copilot bearer token can never be routed to a caller-controlled URL
  • litellm/integrations/websearch_interception/handler.py; keep the web-search short-circuit for github_copilot even though it now has a BaseAnthropicMessagesConfig, since Copilot does not handle web_search tools natively
  • litellm/utils.py; ProviderConfigManager._get_provider_anthropic_messages_config_cached() returns the new config when provider == GITHUB_COPILOT and "claude" in model_lower
  • litellm/model_prices_and_context_window_backup.json; add /v1/messages to supported_endpoints for the 3 base Claude models this routing enables
  • 5+ new unit tests under tests/test_litellm/llms/github_copilot/messages/

Generated by Claude Code


Note

Medium Risk
Changes request routing and auth for GitHub Copilot Claude on /v1/messages (including deliberate api_base pinning); behavior is well-tested but affects thinking, streaming, beta headers, and web-search interception for that provider.

Overview
Adds GithubCopilotAnthropicMessagesConfig so github_copilot Claude models on POST /v1/messages go to Copilot’s native /v1/messages path (auth, messages-proxy headers, URL building) instead of the chat-completions conversion that drops thinking and native streaming blocks.

ProviderConfigManager returns this config when the provider is GITHUB_COPILOT and the model name contains claude; non-Claude Copilot models stay off this path. Caller-supplied api_base is ignored in validation so the Copilot bearer token is only sent to the authenticated Copilot host.

Copilot is treated as an Anthropic passthrough for beta headers: should_filter_anthropic_beta_headers() is overridden to False so injected anthropic-beta values (e.g. context management, structured outputs) are not stripped. Model metadata now lists /v1/messages on the affected Claude Copilot entries.

Web-search interception no longer skips short-circuit merely because an Anthropic Messages config exists; it checks new handles_web_search_natively() (default True on the base config, False for Copilot) so Copilot still gets the synthetic web-search short-circuit even with the new messages config.

Reviewed by Cursor Bugbot for commit 54bff77. Bugbot is set up for automated code reviews on this repo. Configure here.

ririnto added 6 commits July 1, 2026 04:12
…endpoint

Add a GitHub Copilot Anthropic Messages transformation that routes supported Claude models through the native /v1/messages endpoint. This covers request URL construction, default headers, and supported model metadata.
Tighten the Anthropic Messages environment validation and web search interception behavior after review feedback. Avoid treating non-web-search requests as web-search-only paths.
Apply Black formatting to the GitHub Copilot Anthropic Messages tests.
…pic Messages

Add coverage for ProviderConfigManager dispatch when GitHub Copilot models use the Anthropic Messages API, including non-Anthropic models returning no config.
Set the messages-proxy interaction header for GitHub Copilot Anthropic Messages requests so /v1/messages uses the expected Copilot intent.
Replace legacy typing generics in the GitHub Copilot Anthropic Messages transformation so the strict Ruff budget gate stays within its ceiling.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai


Generated by Claude Code

@greptile-apps

greptile-apps Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds GithubCopilotAnthropicMessagesConfig so GitHub Copilot Claude models on POST /v1/messages are routed to Copilot's native /v1/messages path instead of the chat-completions conversion that strips thinking blocks and native streaming events. The implementation correctly pins the request host to the authenticated Copilot endpoint, normalises trailing slashes, disables the provider-scoped beta-header filter, and keeps the web-search interception short-circuit active via a new handles_web_search_natively() hook.

  • New GithubCopilotAnthropicMessagesConfig (litellm/llms/github_copilot/messages/transformation.py): overrides URL construction, auth header injection, and beta-header filtering; caller-supplied api_base is intentionally discarded to prevent bearer-token leakage.
  • ProviderConfigManager (litellm/utils.py): returns the new config for any github_copilot model whose name contains "claude"; non-Claude Copilot models are unaffected.
  • BaseAnthropicMessagesConfig gains handles_web_search_natively() -> bool (default True), letting the web-search handler remain provider-agnostic while Copilot overrides it to False.

Confidence Score: 5/5

Safe to merge; the new routing path is isolated to GitHub Copilot Claude models on /v1/messages, existing providers are untouched, and the auth pinning prevents token leakage.

The implementation is well-contained and thoroughly tested with mock-only unit tests. The routing guard in utils.py is slightly broader than the three models whose metadata was updated, but this results in Copilot returning a model-not-supported error rather than any data corruption or security issue.

litellm/utils.py — the "claude" in model_lower routing guard routes all six Copilot Claude models through the new config, while only three received the /v1/messages metadata update.

Important Files Changed

Filename Overview
litellm/llms/github_copilot/messages/transformation.py New GithubCopilotAnthropicMessagesConfig class; correctly pins api_base to the authenticated Copilot host, normalises trailing slashes, and overrides beta-header filtering and web-search dispatch.
litellm/utils.py Adds GITHUB_COPILOT branch to _get_provider_anthropic_messages_config_cached; routing guard ("claude" in model_lower) is broader than the three models that received /v1/messages in supported_endpoints metadata.
litellm/integrations/websearch_interception/handler.py Short-circuit logic now checks handles_web_search_natively() instead of testing for config presence alone; correctly keeps Copilot on the short-circuit path even with its new messages config.
litellm/llms/base_llm/anthropic_messages/transformation.py Adds handles_web_search_natively() hook to BaseAnthropicMessagesConfig with a safe default of True; no behaviour change for existing providers.
litellm/model_prices_and_context_window_backup.json Adds /v1/messages to supported_endpoints for three Claude models (haiku-4.5, opus-4.5, sonnet-4.5); three other Copilot Claude models (opus-4.6-fast, opus-41, sonnet-4) are not updated but will still be routed through the new config.
tests/test_litellm/llms/github_copilot/messages/test_github_copilot_messages_transformation.py Comprehensive mock-only unit tests covering URL construction, header injection, beta-header filtering, auth errors, and provider config dispatch; no real network calls.

Reviews (6): Last reviewed commit: "test(github_copilot): remove dead branch..." | Re-trigger Greptile

Comment thread litellm/integrations/websearch_interception/handler.py Outdated
Comment thread litellm/llms/github_copilot/messages/transformation.py Outdated
Comment thread litellm/llms/github_copilot/messages/transformation.py
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…n /v1/messages URL

Address review feedback on the Copilot native Anthropic messages path.

Replace the hardcoded LlmProviders.GITHUB_COPILOT check in the web-search
interception handler with a handles_web_search_natively() method on
BaseAnthropicMessagesConfig (default True), overridden to False in
GithubCopilotAnthropicMessagesConfig. Provider-specific behavior now lives in
llms/ and the handler stays provider-agnostic, so a future provider in the same
situation needs no carve-out here.

In get_complete_url, reuse the already-resolved api_base returned by
validate_anthropic_messages_environment instead of reading the authenticator a
second time, removing redundant I/O and the mid-request inconsistency window.
The caller-supplied base is still discarded in validate, which is the security
boundary. Normalize a trailing slash on the base in both methods so a
tenant-specific host never yields a double-slash //v1/messages URL.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Addressed all three findings in ada5fcc

Provider-specific check outside llms/: replaced the hardcoded LlmProviders.GITHUB_COPILOT check in the web-search interception handler with a handles_web_search_natively() method on BaseAnthropicMessagesConfig (default True), overridden to False in GithubCopilotAnthropicMessagesConfig. The handler is now provider-agnostic and a future provider in the same spot needs no carve-out here; this mirrors the existing should_filter_anthropic_beta_headers() pattern on the same base class

get_complete_url re-reading from disk: it now reuses the api_base it is handed, which in the request flow is the value already resolved by validate_anthropic_messages_environment (llm_http_handler.py reassigns api_base from that return before calling get_complete_url), so the redundant authenticator read and the mid-request inconsistency window are gone. The caller-supplied base is still discarded in validate, which is the security boundary; get_complete_url only falls back to a fresh resolution if no base was passed

Double-slash URL: both methods now rstrip("/") the base before appending /v1/messages, so a tenant host with a trailing slash no longer yields //v1/messages

Added regression tests for the new method contract (Copilot False, base config True) and for trailing-slash normalization in both get_complete_url and validate_anthropic_messages_environment

@greptileai


Generated by Claude Code

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Beta headers stripped for Copilot
    • Overrode should_filter_anthropic_beta_headers() to return False on GithubCopilotAnthropicMessagesConfig so injected anthropic-beta values (context_management, structured outputs, etc.) reach Copilot's native /v1/messages verbatim instead of being dropped by the provider-scoped filter, and added a regression test.

You can send follow-ups to the cloud agent here.

Comment thread litellm/llms/github_copilot/messages/transformation.py
The Copilot config inherited should_filter_anthropic_beta_headers()==True
from BaseAnthropicMessagesConfig, so update_headers_with_filtered_beta
stripped every anthropic-beta value after validate_anthropic_messages_environment
injected them (github_copilot has no mapping in
anthropic_beta_headers_config.json). That silently disabled header-gated
features like context_management and structured outputs on the native
passthrough. Override the hook to False, matching OpenAILikeAnthropicMessagesConfig.
@CLAassistant

CLAassistant commented Jul 1, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
2 out of 3 committers have signed the CLA.

✅ ririnto
✅ mateo-berri
❌ cursoragent
You have signed the CLA already but the status is still pending? Let us recheck it.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

Bugbot's autofix in 210bc94 is correct; Copilot's /v1/messages is a native Anthropic passthrough, so overriding should_filter_anthropic_beta_headers() to False is the right call (the default filter would strip every anthropic-beta value since github_copilot has no entry in anthropic_beta_headers_config.json), and it comes with a regression test. Re-requesting review on the latest commit

@greptileai


Generated by Claude Code

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 210bc94. Configure here.

@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

The anthropic-beta filtering test guarded the fix with an if branch on
should_filter_anthropic_beta_headers(), which is always False, so the branch was
unreachable. Replace it with a direct assertion that running the provider-scoped
filter for github_copilot strips every beta value, proving why the override is
load-bearing and catching a regression that flips it back on.
@mateo-berri

Copy link
Copy Markdown
Contributor Author

Addressed the test quality nit in 54bff77; the beta-header regression test had an unreachable if should_filter_anthropic_beta_headers() branch (always False). Replaced it with a direct assertion that running the provider-scoped filter for github_copilot strips every beta value, which proves why the override is load-bearing and fails if someone flips it back on

@greptileai


Generated by Claude Code

@mateo-berri

Copy link
Copy Markdown
Contributor Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 54bff77. Configure here.

@mateo-berri
mateo-berri marked this pull request as ready for review July 1, 2026 15:18
@mateo-berri

Copy link
Copy Markdown
Contributor Author

On the note about the utils.py routing guard being broader than the three models with /v1/messages metadata: this is intentional and benign, so leaving it as-is

The "claude" in model_lower dispatch is the original design; it routes every Copilot Claude model to the native endpoint. Worth clarifying that supported_endpoints does not gate this path: in anthropic/experimental_pass_through/messages/handler.py, ProviderConfigManager.get_provider_anthropic_messages_config(...) is resolved first, and only if it returns None does the _deployment_passes_through_anthropic_messages(model_info) (supported_endpoints) fallback run. Since the Copilot config is non-None for any Claude model, the metadata on the three base models is informational; it does not produce a model-not-supported rejection for the others. Those models still route natively, and the worst case for a Claude SKU that Copilot does not serve on /v1/messages is a clear upstream error, no data corruption or security impact, consistent with the 5/5 assessment

I am deliberately not widening the metadata here: declaring /v1/messages for the other Claude SKUs would assert upstream support that was only verified on the base models, and it changes nothing at runtime since the dispatch already covers them. If the team later wants dispatch and advertised metadata to be strictly 1:1, the cleaner move is the one the original PR already calls out, switching the guard to supported_endpoints membership, which is a follow-up design change rather than something to slip into this copy


Generated by Claude Code

@mateo-berri
mateo-berri requested review from Sameerlite and tin-berri and removed request for Sameerlite July 1, 2026 15:28
@mateo-berri
mateo-berri merged commit 0b0fd6a into litellm_internal_staging Jul 2, 2026
124 checks passed
@mateo-berri
mateo-berri deleted the litellm_copilot_anthropic_messages_native branch July 2, 2026 00:16
duanhongyi pushed a commit to duanhongyi/litellm that referenced this pull request Jul 2, 2026
…endpoint (BerriAI#31802)

* feat(github_copilot): route /v1/messages to Copilot native Anthropic endpoint

Add a GitHub Copilot Anthropic Messages transformation that routes supported Claude models through the native /v1/messages endpoint. This covers request URL construction, default headers, and supported model metadata.

* fix(github_copilot): address PR review feedback

Tighten the Anthropic Messages environment validation and web search interception behavior after review feedback. Avoid treating non-web-search requests as web-search-only paths.

* style(github_copilot): apply black formatting

Apply Black formatting to the GitHub Copilot Anthropic Messages tests.

* test(github_copilot): cover ProviderConfigManager dispatch for Anthropic Messages

Add coverage for ProviderConfigManager dispatch when GitHub Copilot models use the Anthropic Messages API, including non-Anthropic models returning no config.

* fix(github_copilot): apply messages-proxy intent header to /v1/messages

Set the messages-proxy interaction header for GitHub Copilot Anthropic Messages requests so /v1/messages uses the expected Copilot intent.

* fix(github_copilot): use modern generic annotations

Replace legacy typing generics in the GitHub Copilot Anthropic Messages transformation so the strict Ruff budget gate stays within its ceiling.

* refactor(github_copilot): decouple web-search short-circuit and harden /v1/messages URL

Address review feedback on the Copilot native Anthropic messages path.

Replace the hardcoded LlmProviders.GITHUB_COPILOT check in the web-search
interception handler with a handles_web_search_natively() method on
BaseAnthropicMessagesConfig (default True), overridden to False in
GithubCopilotAnthropicMessagesConfig. Provider-specific behavior now lives in
llms/ and the handler stays provider-agnostic, so a future provider in the same
situation needs no carve-out here.

In get_complete_url, reuse the already-resolved api_base returned by
validate_anthropic_messages_environment instead of reading the authenticator a
second time, removing redundant I/O and the mid-request inconsistency window.
The caller-supplied base is still discarded in validate, which is the security
boundary. Normalize a trailing slash on the base in both methods so a
tenant-specific host never yields a double-slash //v1/messages URL.

* fix(github_copilot): forward anthropic-beta headers on /v1/messages

The Copilot config inherited should_filter_anthropic_beta_headers()==True
from BaseAnthropicMessagesConfig, so update_headers_with_filtered_beta
stripped every anthropic-beta value after validate_anthropic_messages_environment
injected them (github_copilot has no mapping in
anthropic_beta_headers_config.json). That silently disabled header-gated
features like context_management and structured outputs on the native
passthrough. Override the hook to False, matching OpenAILikeAnthropicMessagesConfig.

* test(github_copilot): remove dead branch in beta-header regression test

The anthropic-beta filtering test guarded the fix with an if branch on
should_filter_anthropic_beta_headers(), which is always False, so the branch was
unreachable. Replace it with a direct assertion that running the provider-scoped
filter for github_copilot strips every beta value, proving why the override is
load-bearing and catching a regression that flips it back on.

---------

Co-authored-by: ririnto <ririnto@kakao.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Rodrigo-Palma pushed a commit to Rodrigo-Palma/litellm that referenced this pull request Jul 3, 2026
…endpoint (BerriAI#31802)

* feat(github_copilot): route /v1/messages to Copilot native Anthropic endpoint

Add a GitHub Copilot Anthropic Messages transformation that routes supported Claude models through the native /v1/messages endpoint. This covers request URL construction, default headers, and supported model metadata.

* fix(github_copilot): address PR review feedback

Tighten the Anthropic Messages environment validation and web search interception behavior after review feedback. Avoid treating non-web-search requests as web-search-only paths.

* style(github_copilot): apply black formatting

Apply Black formatting to the GitHub Copilot Anthropic Messages tests.

* test(github_copilot): cover ProviderConfigManager dispatch for Anthropic Messages

Add coverage for ProviderConfigManager dispatch when GitHub Copilot models use the Anthropic Messages API, including non-Anthropic models returning no config.

* fix(github_copilot): apply messages-proxy intent header to /v1/messages

Set the messages-proxy interaction header for GitHub Copilot Anthropic Messages requests so /v1/messages uses the expected Copilot intent.

* fix(github_copilot): use modern generic annotations

Replace legacy typing generics in the GitHub Copilot Anthropic Messages transformation so the strict Ruff budget gate stays within its ceiling.

* refactor(github_copilot): decouple web-search short-circuit and harden /v1/messages URL

Address review feedback on the Copilot native Anthropic messages path.

Replace the hardcoded LlmProviders.GITHUB_COPILOT check in the web-search
interception handler with a handles_web_search_natively() method on
BaseAnthropicMessagesConfig (default True), overridden to False in
GithubCopilotAnthropicMessagesConfig. Provider-specific behavior now lives in
llms/ and the handler stays provider-agnostic, so a future provider in the same
situation needs no carve-out here.

In get_complete_url, reuse the already-resolved api_base returned by
validate_anthropic_messages_environment instead of reading the authenticator a
second time, removing redundant I/O and the mid-request inconsistency window.
The caller-supplied base is still discarded in validate, which is the security
boundary. Normalize a trailing slash on the base in both methods so a
tenant-specific host never yields a double-slash //v1/messages URL.

* fix(github_copilot): forward anthropic-beta headers on /v1/messages

The Copilot config inherited should_filter_anthropic_beta_headers()==True
from BaseAnthropicMessagesConfig, so update_headers_with_filtered_beta
stripped every anthropic-beta value after validate_anthropic_messages_environment
injected them (github_copilot has no mapping in
anthropic_beta_headers_config.json). That silently disabled header-gated
features like context_management and structured outputs on the native
passthrough. Override the hook to False, matching OpenAILikeAnthropicMessagesConfig.

* test(github_copilot): remove dead branch in beta-header regression test

The anthropic-beta filtering test guarded the fix with an if branch on
should_filter_anthropic_beta_headers(), which is always False, so the branch was
unreachable. Replace it with a direct assertion that running the provider-scoped
filter for github_copilot strips every beta value, proving why the override is
load-bearing and catching a regression that flips it back on.

---------

Co-authored-by: ririnto <ririnto@kakao.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
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.

[Feature]: github_copilot — route /v1/messages to Copilot native Anthropic endpoint (restores Claude thinking)

5 participants