mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
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.
This commit is contained in:
parent
9c3ad1b094
commit
3063124e37
6 changed files with 211 additions and 3 deletions
0
litellm/llms/github_copilot/messages/__init__.py
Normal file
0
litellm/llms/github_copilot/messages/__init__.py
Normal file
81
litellm/llms/github_copilot/messages/transformation.py
Normal file
81
litellm/llms/github_copilot/messages/transformation.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
from typing import Any, List, Optional, Tuple
|
||||
|
||||
from litellm.exceptions import AuthenticationError
|
||||
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
|
||||
AnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
from ..authenticator import Authenticator
|
||||
from ..common_utils import (
|
||||
DEFAULT_GITHUB_COPILOT_API_BASE,
|
||||
GetAPIKeyError,
|
||||
get_copilot_default_headers,
|
||||
)
|
||||
|
||||
|
||||
class GithubCopilotAnthropicMessagesConfig(AnthropicMessagesConfig):
|
||||
"""
|
||||
GitHub Copilot implementation of Anthropic messages API.
|
||||
Routes requests to Copilot's /v1/messages endpoint with appropriate authentication and headers.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.authenticator = Authenticator()
|
||||
|
||||
def validate_anthropic_messages_environment(
|
||||
self,
|
||||
headers: dict,
|
||||
model: str,
|
||||
messages: List[Any],
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
api_key: Optional[str] = None,
|
||||
api_base: Optional[str] = None,
|
||||
) -> Tuple[dict, Optional[str]]:
|
||||
"""
|
||||
Validate environment for GitHub Copilot and add Copilot-specific headers.
|
||||
"""
|
||||
# Get Copilot auth credentials
|
||||
dynamic_api_base = (
|
||||
api_base
|
||||
or self.authenticator.get_api_base()
|
||||
or DEFAULT_GITHUB_COPILOT_API_BASE
|
||||
)
|
||||
try:
|
||||
dynamic_api_key = self.authenticator.get_api_key()
|
||||
except GetAPIKeyError as e:
|
||||
raise AuthenticationError(
|
||||
model=model,
|
||||
llm_provider="github_copilot",
|
||||
message=str(e),
|
||||
)
|
||||
|
||||
# Merge Copilot headers with provided headers
|
||||
copilot_headers = get_copilot_default_headers(dynamic_api_key)
|
||||
for key, value in copilot_headers.items():
|
||||
if key not in headers:
|
||||
headers[key] = value
|
||||
|
||||
# Set Anthropic version for messages API
|
||||
if "anthropic-version" not in headers:
|
||||
headers["anthropic-version"] = "2023-06-01"
|
||||
|
||||
return headers, dynamic_api_base
|
||||
|
||||
def get_complete_url(
|
||||
self,
|
||||
api_base: Optional[str],
|
||||
api_key: Optional[str],
|
||||
model: str,
|
||||
optional_params: dict,
|
||||
litellm_params: dict,
|
||||
stream: Optional[bool] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Return the complete URL for GitHub Copilot /v1/messages endpoint.
|
||||
"""
|
||||
api_base = api_base or DEFAULT_GITHUB_COPILOT_API_BASE
|
||||
if not api_base.endswith("/v1/messages"):
|
||||
api_base = f"{api_base}/v1/messages"
|
||||
return api_base
|
||||
|
|
@ -18951,7 +18951,8 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
@ -18964,7 +18965,8 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
@ -19015,7 +19017,8 @@
|
|||
"max_tokens": 16000,
|
||||
"mode": "chat",
|
||||
"supported_endpoints": [
|
||||
"/v1/chat/completions"
|
||||
"/v1/chat/completions",
|
||||
"/v1/messages"
|
||||
],
|
||||
"supports_function_calling": true,
|
||||
"supports_parallel_function_calling": true,
|
||||
|
|
|
|||
|
|
@ -8893,6 +8893,13 @@ class ProviderConfigManager:
|
|||
)
|
||||
|
||||
return DeepSeekAnthropicMessagesConfig()
|
||||
elif litellm.LlmProviders.GITHUB_COPILOT == provider:
|
||||
if "claude" in model_lower:
|
||||
from litellm.llms.github_copilot.messages.transformation import (
|
||||
GithubCopilotAnthropicMessagesConfig,
|
||||
)
|
||||
|
||||
return GithubCopilotAnthropicMessagesConfig()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
from litellm.llms.github_copilot.messages.transformation import (
|
||||
GithubCopilotAnthropicMessagesConfig,
|
||||
)
|
||||
from litellm.llms.github_copilot.common_utils import GetAPIKeyError
|
||||
|
||||
|
||||
def test_github_copilot_anthropic_messages_config_init():
|
||||
"""Test GithubCopilotAnthropicMessagesConfig initialization."""
|
||||
config = GithubCopilotAnthropicMessagesConfig()
|
||||
assert config is not None
|
||||
assert hasattr(config, "authenticator")
|
||||
|
||||
|
||||
def test_github_copilot_anthropic_messages_get_complete_url():
|
||||
"""Test URL construction for GitHub Copilot messages endpoint."""
|
||||
config = GithubCopilotAnthropicMessagesConfig()
|
||||
|
||||
# Test with default api_base
|
||||
url = config.get_complete_url(
|
||||
api_base=None,
|
||||
api_key=None,
|
||||
model="github_copilot/claude-haiku-4.5",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://api.githubcopilot.com/v1/messages"
|
||||
|
||||
# Test with custom api_base
|
||||
url = config.get_complete_url(
|
||||
api_base="https://custom.api.com",
|
||||
api_key=None,
|
||||
model="github_copilot/claude-haiku-4.5",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://custom.api.com/v1/messages"
|
||||
|
||||
# Test with api_base already ending with /v1/messages
|
||||
url = config.get_complete_url(
|
||||
api_base="https://custom.api.com/v1/messages",
|
||||
api_key=None,
|
||||
model="github_copilot/claude-haiku-4.5",
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
)
|
||||
assert url == "https://custom.api.com/v1/messages"
|
||||
|
||||
|
||||
def test_github_copilot_anthropic_messages_validate_environment():
|
||||
"""Test environment validation and header injection."""
|
||||
config = GithubCopilotAnthropicMessagesConfig()
|
||||
|
||||
# Mock the authenticator
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.return_value = "gh.test-key-123"
|
||||
config.authenticator.get_api_base.return_value = None
|
||||
|
||||
headers = {}
|
||||
validated_headers, api_base = config.validate_anthropic_messages_environment(
|
||||
headers=headers,
|
||||
model="github_copilot/claude-haiku-4.5",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
# Check that Copilot headers were added
|
||||
assert "copilot-integration-id" in validated_headers
|
||||
assert validated_headers["copilot-integration-id"] == "vscode-chat"
|
||||
assert "Authorization" in validated_headers
|
||||
assert "anthropic-version" in validated_headers
|
||||
assert validated_headers["anthropic-version"] == "2023-06-01"
|
||||
assert api_base == "https://api.githubcopilot.com"
|
||||
|
||||
|
||||
def test_github_copilot_anthropic_messages_validate_environment_auth_error():
|
||||
"""Test error handling when authentication fails."""
|
||||
config = GithubCopilotAnthropicMessagesConfig()
|
||||
|
||||
# Mock the authenticator to raise an error
|
||||
config.authenticator = MagicMock()
|
||||
config.authenticator.get_api_key.side_effect = GetAPIKeyError(
|
||||
status_code=401, message="No valid API key found"
|
||||
)
|
||||
|
||||
with pytest.raises(Exception): # AuthenticationError
|
||||
config.validate_anthropic_messages_environment(
|
||||
headers={},
|
||||
model="github_copilot/claude-haiku-4.5",
|
||||
messages=[{"role": "user", "content": "Hello"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key=None,
|
||||
api_base=None,
|
||||
)
|
||||
|
||||
|
||||
def test_github_copilot_anthropic_messages_supported_params():
|
||||
"""Test supported parameters list."""
|
||||
config = GithubCopilotAnthropicMessagesConfig()
|
||||
params = config.get_supported_anthropic_messages_params("github_copilot/claude-haiku-4.5")
|
||||
|
||||
# Should inherit from AnthropicMessagesConfig
|
||||
assert "messages" in params
|
||||
assert "model" in params
|
||||
assert "max_tokens" in params
|
||||
assert "thinking" in params
|
||||
Loading…
Add table
Reference in a new issue