diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index f76fd214682..67fffc13e4b 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -397,6 +397,7 @@ router_settings: | AUDIO_SPEECH_CHUNK_SIZE | Chunk size for audio speech processing. Default is 1024 | ANTHROPIC_API_KEY | API key for Anthropic service | ANTHROPIC_API_BASE | Base URL for Anthropic API. Default is https://api.anthropic.com +| ANTHROPIC_TOKEN_COUNTING_BETA_VERSION | Beta version header for Anthropic token counting API. Default is `token-counting-2024-11-01` | AWS_ACCESS_KEY_ID | Access Key ID for AWS services | AWS_BATCH_ROLE_ARN | ARN of the AWS IAM role for batch operations | AWS_DEFAULT_REGION | Default AWS region for service interactions when AWS_REGION is not set @@ -412,6 +413,8 @@ router_settings: | AWS_WEB_IDENTITY_TOKEN | Web identity token for AWS | AWS_WEB_IDENTITY_TOKEN_FILE | Path to file containing web identity token for AWS | AZURE_API_VERSION | Version of the Azure API being used +| AZURE_AI_API_BASE | Base URL for Azure AI services (e.g., Azure AI Anthropic) +| AZURE_AI_API_KEY | API key for Azure AI services (e.g., Azure AI Anthropic) | AZURE_AUTHORITY_HOST | Azure authority host URL | AZURE_CERTIFICATE_PASSWORD | Password for Azure OpenAI certificate | AZURE_CLIENT_ID | Client ID for Azure services diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index ccfbc36c080..437a972f4df 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -14,6 +14,8 @@ from litellm.llms.base_llm.base_utils import BaseLLMModelInfo, BaseTokenCounter from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.types.llms.anthropic import ( ANTHROPIC_HOSTED_TOOLS, + ANTHROPIC_OAUTH_BETA_HEADER, + ANTHROPIC_OAUTH_TOKEN_PREFIX, AllAnthropicToolsValues, AnthropicMcpServerTool, ) @@ -371,6 +373,8 @@ class AnthropicModelInfo(BaseLLMModelInfo): api_key: Optional[str] = None, api_base: Optional[str] = None, ) -> Dict: + # Check for Anthropic OAuth token in headers + headers, api_key = optionally_handle_anthropic_oauth(headers=headers, api_key=api_key) if api_key is None: raise litellm.AuthenticationError( message="Missing Anthropic API Key - A call is being made to anthropic but no key is set either in the environment variables or via params. Please set `ANTHROPIC_API_KEY` in your environment vars", diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py index f67e4c8382c..7135102db01 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py @@ -17,7 +17,11 @@ from litellm.types.llms.anthropic_messages.anthropic_response import ( from litellm.types.llms.anthropic_tool_search import get_tool_search_beta_header from litellm.types.router import GenericLiteLLMParams -from ...common_utils import AnthropicError, AnthropicModelInfo +from ...common_utils import ( + AnthropicError, + AnthropicModelInfo, + optionally_handle_anthropic_oauth, +) DEFAULT_ANTHROPIC_API_BASE = "https://api.anthropic.com" DEFAULT_ANTHROPIC_API_VERSION = "2023-06-01" @@ -68,8 +72,11 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): ) -> Tuple[dict, Optional[str]]: import os + # Check for Anthropic OAuth token in Authorization header + headers, api_key = optionally_handle_anthropic_oauth(headers=headers, api_key=api_key) if api_key is None: api_key = os.getenv("ANTHROPIC_API_KEY") + if "x-api-key" not in headers and api_key: headers["x-api-key"] = api_key if "anthropic-version" not in headers: diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index 779a6950d92..0e687be660f 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -642,4 +642,8 @@ ANTHROPIC_TOOL_SEARCH_BETA_HEADER = "advanced-tool-use-2025-11-20" # Effort beta header constant ANTHROPIC_EFFORT_BETA_HEADER = "effort-2025-11-24" +# OAuth constants +ANTHROPIC_OAUTH_TOKEN_PREFIX = "sk-ant-oat" +ANTHROPIC_OAUTH_BETA_HEADER = "oauth-2025-04-20" + diff --git a/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py new file mode 100644 index 00000000000..0a397d116e7 --- /dev/null +++ b/tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py @@ -0,0 +1,84 @@ +""" +Tests for Anthropic OAuth token handling for Claude Code Max integration. +""" + +import os +import sys + +# Add litellm to path +sys.path.insert(0, os.path.abspath("../../../../..")) + +# Fake OAuth token for testing (not a real secret) +FAKE_OAUTH_TOKEN = "sk-ant-oat01-fake-token-for-testing-123456789abcdef" + + +def test_oauth_detection_in_common_utils(): + """Test 1: OAuth token detection in common_utils""" + from litellm.llms.anthropic.common_utils import optionally_handle_anthropic_oauth + + headers = {"authorization": f"Bearer {FAKE_OAUTH_TOKEN}"} + updated_headers, extracted_api_key = optionally_handle_anthropic_oauth(headers, None) + + assert extracted_api_key == FAKE_OAUTH_TOKEN + assert updated_headers["anthropic-beta"] == "oauth-2025-04-20" + assert updated_headers["anthropic-dangerous-direct-browser-access"] == "true" + + +def test_oauth_integration_in_validate_environment(): + """Test 2: OAuth integration in AnthropicConfig validate_environment""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + config = AnthropicModelInfo() + headers = {"authorization": f"Bearer {FAKE_OAUTH_TOKEN}"} + + updated_headers = config.validate_environment( + headers=headers, + model="claude-3-haiku-20240307", + messages=[{"role": "user", "content": "Hello"}], + optional_params={}, + litellm_params={}, + api_key=None, + api_base=None, + ) + + assert updated_headers["x-api-key"] == FAKE_OAUTH_TOKEN + assert updated_headers["anthropic-dangerous-direct-browser-access"] == "true" + + +def test_oauth_detection_in_messages_transformation(): + """Test 3: OAuth detection in messages transformation""" + from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( + AnthropicMessagesConfig, + ) + + config = AnthropicMessagesConfig() + headers = {"authorization": f"Bearer {FAKE_OAUTH_TOKEN}"} + + updated_headers, _ = config.validate_anthropic_messages_environment( + headers=headers, + model="claude-3-haiku-20240307", + messages=[{"role": "user", "content": "Hello"}], + optional_params={}, + litellm_params={}, + api_key=None, + api_base=None, + ) + + assert updated_headers["x-api-key"] == FAKE_OAUTH_TOKEN + assert "oauth-2025-04-20" in updated_headers["anthropic-beta"] + assert updated_headers["anthropic-dangerous-direct-browser-access"] == "true" + + +def test_regular_api_keys_still_work(): + """Test 4: Regular API keys still work (regression test)""" + from litellm.llms.anthropic.common_utils import optionally_handle_anthropic_oauth + + regular_key = "sk-ant-api03-regular-key-123" + headers = {"authorization": f"Bearer {regular_key}"} + + updated_headers, extracted_api_key = optionally_handle_anthropic_oauth(headers, regular_key) + + # Regular key should be unchanged + assert extracted_api_key == regular_key + # OAuth headers should NOT be added + assert "anthropic-dangerous-direct-browser-access" not in updated_headers \ No newline at end of file diff --git a/tests/unified_google_tests/test_vertex_ai_native.py b/tests/unified_google_tests/test_vertex_ai_native.py index 5c8f8575c42..c2f378523ef 100644 --- a/tests/unified_google_tests/test_vertex_ai_native.py +++ b/tests/unified_google_tests/test_vertex_ai_native.py @@ -6,5 +6,5 @@ class TestVertexAIGenerateContent(BaseGoogleGenAITest): @property def model_config(self): return { - "model": "vertex_ai/gemini-2.5-flash-lite", + "model": "vertex_ai/gemini-3-flash-preview", } \ No newline at end of file