feat(ui): add Amazon Bedrock Mantle to the Add Model provider dropdown (#31034)

The Add Model provider dropdown is driven by provider_create_fields.json
(served at /public/providers/fields), and Bedrock Mantle had no entry, so it
could not be selected even though the backend provider, its models, and the UI
enum/logo mappings already existed.

Add a bedrock_mantle entry exposing the credential fields the provider actually
honors: an optional bearer api_key for BYOK, the AWS SigV4 chain, a region, and
an api_base override. Selecting it now populates the bedrock_mantle models from
the cost map via the existing getProviderModels filter.

Also resolve the provider logo when getProviderLogoAndName is given the enum key
(e.g. BedrockMantle) rather than the slug, which the dropdown passes; previously
only slugs that lowercase-matched their key (like bedrock) resolved a logo.
This commit is contained in:
ryan-crabbe-berri 2026-06-22 17:31:25 -07:00 committed by GitHub
parent 19a29e0579
commit 26da56fbb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 189 additions and 4 deletions

View file

@ -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.<region>.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",

View file

@ -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

View file

@ -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" },

View file

@ -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 };