diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index fac732bac68..fcc6aac1c14 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -209,6 +209,114 @@ ], "default_model_placeholder": "claude-3-opus" }, + { + "provider": "BedrockMantle", + "provider_display_name": "Amazon Bedrock Mantle", + "litellm_provider": "bedrock_mantle", + "credential_fields": [ + { + "key": "api_key", + "label": "Bedrock Mantle API Key", + "placeholder": null, + "tooltip": "Bearer token for the Bedrock Mantle OpenAI-compatible endpoint. You can provide the raw token or the environment variable (e.g. `os.environ/BEDROCK_MANTLE_API_KEY`). Leave blank to authenticate with AWS SigV4 credentials instead.", + "required": false, + "field_type": "password", + "options": null, + "default_value": null + }, + { + "key": "aws_access_key_id", + "label": "AWS Access Key ID", + "placeholder": null, + "tooltip": "Used for AWS SigV4 auth when no API key is set. You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + "required": false, + "field_type": "password", + "options": null, + "default_value": null + }, + { + "key": "aws_secret_access_key", + "label": "AWS Secret Access Key", + "placeholder": null, + "tooltip": "Used for AWS SigV4 auth when no API key is set. You can provide the raw key or the environment variable (e.g. `os.environ/MY_SECRET_KEY`).", + "required": false, + "field_type": "password", + "options": null, + "default_value": null + }, + { + "key": "aws_session_token", + "label": "AWS Session Token", + "placeholder": null, + "tooltip": "Temporary credentials session token. You can provide the raw token or the environment variable (e.g. `os.environ/MY_SESSION_TOKEN`).", + "required": false, + "field_type": "password", + "options": null, + "default_value": null + }, + { + "key": "aws_region_name", + "label": "AWS Region Name", + "placeholder": "us-east-1", + "tooltip": "Region of the Bedrock Mantle endpoint. Defaults to us-east-1. You can provide the raw value or the environment variable (e.g. `os.environ/AWS_REGION_NAME`).", + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "aws_session_name", + "label": "AWS Session Name", + "placeholder": "my-session", + "tooltip": "Name for the AWS session. You can provide the raw value or the environment variable (e.g. `os.environ/MY_SESSION_NAME`).", + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "aws_profile_name", + "label": "AWS Profile Name", + "placeholder": "default", + "tooltip": "AWS profile name to use for authentication. You can provide the raw value or the environment variable (e.g. `os.environ/MY_PROFILE_NAME`).", + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "aws_role_name", + "label": "AWS Role Name", + "placeholder": "MyRole", + "tooltip": "AWS IAM role name to assume. You can provide the raw value or the environment variable (e.g. `os.environ/MY_ROLE_NAME`).", + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "aws_web_identity_token", + "label": "AWS Web Identity Token", + "placeholder": null, + "tooltip": "Web identity token for OIDC authentication. You can provide the raw token or the environment variable (e.g. `os.environ/MY_WEB_IDENTITY_TOKEN`).", + "required": false, + "field_type": "password", + "options": null, + "default_value": null + }, + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://bedrock-mantle.us-east-1.api.aws", + "tooltip": "Optional. Custom Bedrock Mantle endpoint. Defaults to https://bedrock-mantle..api.aws. You can provide the raw value or the environment variable (e.g. `os.environ/BEDROCK_MANTLE_API_BASE`).", + "required": false, + "field_type": "text", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "bedrock_mantle/openai.gpt-oss-120b" + }, { "provider": "Anthropic", "provider_display_name": "Anthropic", diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index 1ca28e1c4e8..d920488c352 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -201,6 +201,48 @@ def test_anthropic_provider_fields_support_byok(): ), "api_base must appear before api_key in credential_fields (matches AI21 and ANTHROPIC_TEXT convention)." +def test_bedrock_mantle_provider_fields(): + """Amazon Bedrock Mantle must be a selectable provider in the Add Model flow. + + The dropdown is driven entirely by /public/providers/fields, so a missing + entry means Mantle cannot be added through the UI at all (regression guard + for LIT-3885). The credential fields must match what the backend actually + honors: an optional bearer api_key (BYOK), the AWS SigV4 chain, a region, + and an api_base override. + """ + app_instance = FastAPI() + app_instance.include_router(router) + test_client = TestClient(app_instance) + + response = test_client.get("/public/providers/fields") + assert response.status_code == 200 + providers = response.json() + + mantle = next((p for p in providers if p["provider"] == "BedrockMantle"), None) + assert mantle is not None, "Bedrock Mantle provider entry not found" + + # provider must equal the UI provider_map key so the model dropdown resolves + # bedrock_mantle models; litellm_provider must be the backend slug. + assert mantle["provider_display_name"] == "Amazon Bedrock Mantle" + assert mantle["litellm_provider"] == "bedrock_mantle" + assert mantle["default_model_placeholder"].startswith("bedrock_mantle/") + + fields_by_key = {f["key"]: f for f in mantle["credential_fields"]} + + # Bearer-token auth is BYOK: optional and masked. + assert "api_key" in fields_by_key + assert fields_by_key["api_key"]["required"] is False + assert fields_by_key["api_key"]["field_type"] == "password" + + # AWS SigV4 fallback credentials. + assert fields_by_key["aws_access_key_id"]["field_type"] == "password" + assert fields_by_key["aws_secret_access_key"]["field_type"] == "password" + assert "aws_region_name" in fields_by_key + + # api_base override so admins can target a custom Mantle host without env access. + assert fields_by_key["api_base"]["field_type"] == "text" + + def test_google_ai_studio_provider_fields_expose_api_base(): """The Google AI Studio (gemini) credential form must let admins set a custom api_base so they can point at a Gemini-compatible gateway (e.g. a self-hosted diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index a73cf699ac8..acc77d1263b 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -62,6 +62,22 @@ describe("provider_info_helpers", () => { expect(result.logo).toBe(providerLogoMap[Providers.Groq]); }); + it("should map bedrock_mantle slug to Bedrock Mantle display name and logo", () => { + const result = getProviderLogoAndName("bedrock_mantle"); + expect(result.displayName).toBe(Providers.BedrockMantle); + expect(result.logo).toBe(providerLogoMap[Providers.BedrockMantle]); + }); + + it("should resolve the BedrockMantle enum key to the Bedrock Mantle logo", () => { + // The Add Model dropdown passes the provider_map key ("BedrockMantle"), + // not the slug ("bedrock_mantle"). Unlike "Bedrock", the key does not + // lowercase-match its slug, so without the enum-key fallback this would + // render a blank fallback logo for a Bedrock variant (LIT-3885). + const result = getProviderLogoAndName("BedrockMantle"); + expect(result.displayName).toBe(Providers.BedrockMantle); + expect(result.logo).toBe(providerLogoMap[Providers.BedrockMantle]); + }); + it("should handle provider values case-insensitively", () => { const result = getProviderLogoAndName("OPENAI"); expect(result.displayName).toBe(Providers.OpenAI); @@ -306,6 +322,23 @@ describe("provider_info_helpers", () => { expect(result).not.toContain("openai-model"); }); + it("should return only bedrock_mantle models when called with 'BedrockMantle' provider key", () => { + // Selecting "Amazon Bedrock Mantle" in the dropdown must populate the + // model field with the Mantle models and exclude the regular Bedrock + // ones, so onboarding a gpt-oss model is a one-click flow (LIT-3885). + const modelMap = { + "bedrock_mantle/openai.gpt-oss-120b": { litellm_provider: "bedrock_mantle" }, + "bedrock_mantle/openai.gpt-5.5": { litellm_provider: "bedrock_mantle" }, + "bedrock-base": { litellm_provider: "bedrock" }, + "bedrock-converse-model": { litellm_provider: "bedrock_converse" }, + }; + const result = getProviderModels("BedrockMantle" as Providers, modelMap); + expect(result).toContain("bedrock_mantle/openai.gpt-oss-120b"); + expect(result).toContain("bedrock_mantle/openai.gpt-5.5"); + expect(result).not.toContain("bedrock-base"); + expect(result).not.toContain("bedrock-converse-model"); + }); + it("should include fireworks_ai-embedding-models when called with 'FireworksAI' provider key", () => { const modelMap = { "fireworks-base": { litellm_provider: "fireworks_ai" }, diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 727eebfe951..e8b0b62d3c4 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -318,10 +318,12 @@ export const getProviderLogoAndName = (providerValue: string): { logo: string; d return { logo, displayName }; } - // Find the enum key by matching provider_map values - const enumKey = Object.keys(provider_map).find( - (key) => provider_map[key].toLowerCase() === providerValue.toLowerCase(), - ); + // Resolve by the litellm provider slug (e.g. "bedrock_mantle"); fall back to + // the enum key (e.g. "BedrockMantle") for callers like the Add Model dropdown + // that pass the key instead of the slug. + const enumKey = + Object.keys(provider_map).find((key) => provider_map[key].toLowerCase() === providerValue.toLowerCase()) ?? + Object.keys(provider_map).find((key) => key.toLowerCase() === providerValue.toLowerCase()); if (!enumKey) { return { logo: "", displayName: providerValue };