feat(ui): add TypeSafe and Strands Decider to the Add Model provider list (#45335)

* feat(ui): add TypeSafe and Strands Decider to the Add Model provider list

* test(public_endpoints): type the provider fields helper against the route's model

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-08 04:04:07 -07:00 • committed by GitHub
parent 0e26edfdb9
commit 3a3b8cad80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 164 additions and 2 deletions

View file

@ -3189,6 +3189,34 @@
],
"default_model_placeholder": "soniox/stt-async-v5"
},
{
"provider": "StrandsDecider",
"provider_display_name": "Strands Decider",
"litellm_provider": "strands_decider",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "http://localhost:8000",
"tooltip": "URL of your self-hosted Strands Decider server",
"required": true,
"field_type": "text",
"options": null,
"default_value": null
},
{
"key": "api_key",
"label": "API Key",
"placeholder": null,
"tooltip": null,
"required": false,
"field_type": "password",
"options": null,
"default_value": null
}
],
"default_model_placeholder": "strands_decider/strands-decider-2B-hobson-v19"
},
{
"provider": "Tencent",
"provider_display_name": "Tencent",
@ -3319,6 +3347,34 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "TypeSafe",
"provider_display_name": "TypeSafe",
"litellm_provider": "typesafe",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.typesafe.ai",
"tooltip": null,
"required": false,
"field_type": "text",
"options": null,
"default_value": null
},
{
"key": "api_key",
"label": "API Key",
"placeholder": null,
"tooltip": null,
"required": true,
"field_type": "password",
"options": null,
"default_value": null
}
],
"default_model_placeholder": "typesafe/jev-latest"
},
{
"provider": "V0",
"provider_display_name": "V0",

View file

@ -9,6 +9,7 @@ import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import TypeAdapter
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.public_endpoints import router
@ -16,6 +17,7 @@ from litellm.router_strategy.complexity_router.fuse_presets import get_fuse_pres
from litellm.types.proxy.management_endpoints.model_management_endpoints import (
ModelGroupInfoProxy,
)
from litellm.types.proxy.public_endpoints.public_endpoints import ProviderCreateInfo
from litellm.types.utils import LlmProviders
@ -402,6 +404,53 @@ def test_tencent_provider_fields():
assert fields_by_key["api_base"]["required"] is False
def _decisions_provider_entry(provider: str) -> ProviderCreateInfo:
app_instance: Final = FastAPI()
app_instance.include_router(router)
test_client: Final = TestClient(app_instance)
response: Final = test_client.get("/public/providers/fields")
assert response.status_code == 200
providers: Final = TypeAdapter(list[ProviderCreateInfo]).validate_python(response.json())
entry: Final = next((p for p in providers if p.provider == provider), None)
assert entry is not None, f"{provider} provider entry not found"
return entry
def test_typesafe_provider_fields():
typesafe: Final = _decisions_provider_entry("TypeSafe")
assert typesafe.provider_display_name == "TypeSafe"
assert typesafe.litellm_provider == LlmProviders.TYPESAFE.value
assert typesafe.default_model_placeholder is not None
assert typesafe.default_model_placeholder.startswith("typesafe/")
fields_by_key: Final = {f.key: f for f in typesafe.credential_fields}
assert fields_by_key["api_key"].required is True
assert fields_by_key["api_key"].field_type == "password"
assert fields_by_key["api_base"].required is False
assert fields_by_key["api_base"].field_type == "text"
def test_strands_decider_provider_fields():
strands: Final = _decisions_provider_entry("StrandsDecider")
assert strands.provider_display_name == "Strands Decider"
assert strands.litellm_provider == LlmProviders.STRANDS_DECIDER.value
assert strands.default_model_placeholder is not None
assert strands.default_model_placeholder.startswith("strands_decider/")
fields_by_key: Final = {f.key: f for f in strands.credential_fields}
assert fields_by_key["api_base"].required is True
assert fields_by_key["api_base"].field_type == "text"
assert fields_by_key["api_key"].required is False
assert fields_by_key["api_key"].field_type == "password"
ADD_MODEL_UNLISTED_PROVIDERS: Final = frozenset(
{
"a2a",
@ -436,12 +485,10 @@ ADD_MODEL_UNLISTED_PROVIDERS: Final = frozenset(
"sagemaker_nova",
"scaleway",
"stability",
"strands_decider",
"synthetic",
"tensormesh",
"text-completion-inception",
"transcribe",
"typesafe",
"valkey",
"xiaomi_mimo",
"zai",

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -214,6 +214,26 @@ describe("provider_info_helpers", () => {
const { logo } = getProviderLogoAndName("tencent");
expect(logo).toContain("tencent");
});
it("should resolve the typesafe slug and TypeSafe enum key to the TypeSafe name and bundled logo", () => {
const fromSlug = getProviderLogoAndName("typesafe");
expect(fromSlug.displayName).toBe(Providers.TypeSafe);
expect(fromSlug.logo).toContain("typesafe");
const fromEnumKey = getProviderLogoAndName("TypeSafe");
expect(fromEnumKey.displayName).toBe(Providers.TypeSafe);
expect(fromEnumKey.logo).toBe(fromSlug.logo);
});
it("should resolve the strands_decider slug and StrandsDecider enum key to the Strands Decider name and bundled logo", () => {
const fromSlug = getProviderLogoAndName("strands_decider");
expect(fromSlug.displayName).toBe("Strands Decider");
expect(fromSlug.logo).toContain("strands");
const fromEnumKey = getProviderLogoAndName("StrandsDecider");
expect(fromEnumKey.displayName).toBe(Providers.StrandsDecider);
expect(fromEnumKey.logo).toBe(fromSlug.logo);
});
});
describe("getPlaceholder", () => {
@ -318,6 +338,11 @@ describe("provider_info_helpers", () => {
expect(getPlaceholder(Providers.Tencent)).toBe("tencent/deepseek-v4-pro");
});
it("should return decision model placeholders for the TypeSafe and StrandsDecider dropdown keys", () => {
expect(getPlaceholder("TypeSafe")).toBe("typesafe/jev-latest");
expect(getPlaceholder("StrandsDecider")).toBe("strands_decider/strands-decider-2B-hobson-v19");
});
it("should return default gpt-3.5-turbo placeholder for unknown provider", () => {
expect(getPlaceholder("UnknownProvider" as any)).toBe("gpt-3.5-turbo");
});
@ -421,6 +446,30 @@ describe("provider_info_helpers", () => {
expect(getProviderModels("Sail" as Providers, modelMap)).toEqual(["sail/openai/gpt-oss-120b"]);
});
it("should list only typesafe decision models for the 'TypeSafe' provider key, not the OpenRouter-hosted one", () => {
const modelMap = {
"typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" },
"typesafe/jev-preview": { litellm_provider: "typesafe", mode: "evaluation" },
"openrouter/typesafe/jev-1.13": { litellm_provider: "openrouter", mode: "evaluation" },
"strands_decider/strands-decider-2B-hobson-v19": { litellm_provider: "strands_decider", mode: "evaluation" },
};
expect(getProviderModels("TypeSafe" as Providers, modelMap)).toEqual([
"typesafe/jev-latest",
"typesafe/jev-preview",
]);
});
it("should list only strands_decider models for the 'StrandsDecider' provider key", () => {
const modelMap = {
"strands_decider/strands-decider-2B-hobson-v19": { litellm_provider: "strands_decider", mode: "evaluation" },
"typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" },
"openrouter/typesafe/jev-1.13": { litellm_provider: "openrouter", mode: "evaluation" },
};
expect(getProviderModels("StrandsDecider" as Providers, modelMap)).toEqual([
"strands_decider/strands-decider-2B-hobson-v19",
]);
});
it("should include bedrock converse but exclude standalone bedrock_mantle when called with 'Bedrock' provider key", () => {
const modelMap = {
"bedrock-base": { litellm_provider: "bedrock" },

View file

@ -55,9 +55,11 @@ import sapLogo from "../../public/assets/logos/sap.png";
import scxAiLogo from "../../public/assets/logos/scx_ai.svg";
import snowflakeLogo from "../../public/assets/logos/snowflake.svg";
import sonioxLogo from "../../public/assets/logos/soniox.svg";
import strandsLogo from "../../public/assets/logos/strands.svg";
import tencentLogo from "../../public/assets/logos/tencent.svg";
import togetheraiLogo from "../../public/assets/logos/togetherai.svg";
import topazLogo from "../../public/assets/logos/topaz.svg";
import typesafeLogo from "../../public/assets/logos/typesafe.png";
import v0Logo from "../../public/assets/logos/v0.svg";
import vercelLogo from "../../public/assets/logos/vercel.svg";
import vllmLogo from "../../public/assets/logos/vllm.png";
@ -167,11 +169,13 @@ export enum Providers {
SCX_AI = "SCX.ai",
Snowflake = "Snowflake",
Soniox = "Soniox",
StrandsDecider = "Strands Decider",
TEXT_COMPLETION_CODESTRAL = "Text-Completion-Codestral",
Tencent = "Tencent",
TogetherAI = "TogetherAI",
TOPAZ = "Topaz",
Triton = "Triton",
TypeSafe = "TypeSafe",
V0 = "V0",
VERCEL_AI_GATEWAY = "Vercel Ai Gateway",
Vertex_AI = "Vertex AI (Anthropic, Gemini, etc.)",
@ -287,11 +291,13 @@ export const provider_map: Record<string, string> = {
SCX_AI: "scx-ai",
Snowflake: "snowflake",
Soniox: "soniox",
StrandsDecider: "strands_decider",
TEXT_COMPLETION_CODESTRAL: "text-completion-codestral",
Tencent: "tencent",
TogetherAI: "together_ai",
TOPAZ: "topaz",
Triton: "triton",
TypeSafe: "typesafe",
V0: "v0",
VERCEL_AI_GATEWAY: "vercel_ai_gateway",
Vertex_AI: "vertex_ai",
@ -387,11 +393,13 @@ export const providerLogoMap: Partial<Record<Providers, string>> = {
[Providers.SCX_AI]: scxAiLogo.src,
[Providers.Snowflake]: snowflakeLogo.src,
[Providers.Soniox]: sonioxLogo.src,
[Providers.StrandsDecider]: strandsLogo.src,
[Providers.Tencent]: tencentLogo.src,
[Providers.TEXT_COMPLETION_CODESTRAL]: mistralLogo.src,
[Providers.TogetherAI]: togetheraiLogo.src,
[Providers.TOPAZ]: topazLogo.src,
[Providers.Triton]: nvidiaTritonLogo.src,
[Providers.TypeSafe]: typesafeLogo.src,
[Providers.V0]: v0Logo.src,
[Providers.VERCEL_AI_GATEWAY]: vercelLogo.src,
[Providers.Vertex_AI]: googleLogo.src,
@ -457,7 +465,9 @@ const providerPlaceholderMap: Partial<Record<Providers, string>> = {
[Providers.Sail]: "sail/openai/gpt-oss-120b",
[Providers.SCX_AI]: "scx-ai/GLM-5.2",
[Providers.Snowflake]: "snowflake/mistral-7b",
[Providers.StrandsDecider]: "strands_decider/strands-decider-2B-hobson-v19",
[Providers.Tencent]: "tencent/deepseek-v4-pro",
[Providers.TypeSafe]: "typesafe/jev-latest",
[Providers.Vertex_AI]: "gemini-pro",
[Providers.VolcEngine]: "volcengine/<any-model-on-volcengine>",
[Providers.Voyage]: "voyage/",