fix(aiand)!: show ai&'s name and logo, and sync ai&'s model list (#49158)

* feat(ui): show ai&'s name and logo in the dashboard

The Add Model form already lists ai& from its provider metadata, but the dashboard had no entry for it, so the provider showed no logo and model tables printed the raw aiand slug. Adds ai& to the dashboard provider list with its official mark, plated in dark mode so the red and black mark keep their colours, and the aiand/openai/gpt-oss-120b model placeholder

* fix(aiand): retire deepseek-v4-flash, glm-5.2 and kimi-k2.7-code

ai& retired these three models on 2026-10-27 and https://api.aiand.com/v1/models now lists ten. The rows keep their prices with a deprecation_date, so earlier spend still prices, and aiand/* stops listing them, the same way baseten handles retired rows. The other ten rows already match the live prices, context windows and output limits

* feat(aiand): add deepseek-v4.1-flash-1017

ai& launches deepseek-ai/deepseek-v4.1-flash-1017 on 2026-11-02. It is served on https://staging-api.aiand.com/v1/models with the same price, context window, output limit and capabilities as deepseek-v4.1-flash, so its row copies that one

* fix(aiand): retire deepseek-v4.1-flash

ai& retires deepseek-ai/deepseek-v4.1-flash on 2026-10-30, now that deepseek-v4.1-flash-1017 is live in production on https://api.aiand.com/v1/models with the same price, limits and capabilities. Its row keeps its prices with a deprecation_date, like the other three retired ai& rows

* fix(aiand): retire deepseek-v4.1-flash on 2026-11-06

https://api.aiand.com/v1/models now gives deepseek-v4.1-flash a deprecation_date of 2026-11-05T15:00Z, which is 2026-11-06 00:00 in Japan, so its row uses 2026-11-06 instead of 2026-10-30 and aiand/* keeps listing it until then. The wildcard test now leaves a model out only once its retirement date has passed

* test(aiand): derive retired models from the rows instead of pinning dates

The wildcard test now reads each row's deprecation_date, so it holds whichever dates ai& sets, and the test that only restated the JSON dates is gone

* fix(aiand): retire models on their UTC date while the proxy runs, and add muse-spark-open

The wildcard listing now checks each row's deprecation_date when it expands, against the UTC date the field
documents, so a model drops on its day without a restart. deepseek-v4.1-flash now carries 2026-11-05, the
UTC day ai& retires it, and meta/muse-spark-open gets its price row from ai&'s live /v1/models

* fix(aiand): hide retired models only from bundled listings, including the wildcard health check

* fix(aiand): share the UTC retirement check with Together AI and the health check sort

---------

Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
This commit is contained in:
islomjon-aiand 2026-10-31 02:55:26 +09:00 • committed by GitHub
parent 17d1f58336
commit 707ac1ffee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 296 additions and 36 deletions

View file

@ -7,7 +7,6 @@ _apply_process_defaults()
### INIT VARIABLES #########################
import threading
import os
import datetime
from collections.abc import Mapping, Sequence
from typing import (
@ -550,6 +549,7 @@ _key_management_system: Optional["KeyManagementSystem"] = None
output_parse_pii: bool = False
#############################################
from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map, mark_litellm_import_complete
from litellm.litellm_core_utils.model_retirement import is_hidden_as_retired
model_cost = get_model_cost_map(url=model_cost_map_url)
cost_discount_config: Dict[str, float] = {} # Provider-specific cost discounts {"vertex_ai": 0.05} = 5% discount
@ -756,16 +756,6 @@ def is_openai_finetune_model(key: str) -> bool:
return key.startswith("ft:") and not key.count(":") > 1
def _is_past_deprecation_date(value: Mapping[str, object]) -> bool:
deprecation_date: Final = value.get("deprecation_date")
if not isinstance(deprecation_date, str):
return False
try:
return datetime.date.fromisoformat(deprecation_date) <= datetime.date.today()
except ValueError:
return False
def _populate_provider_model_sets(model_cost_map: Mapping[str, Mapping[str, object]]) -> None:
for key, value in tuple(model_cost_map.items()):
if value.get("litellm_provider") == "openai" and not is_openai_finetune_model(key):
@ -873,12 +863,12 @@ def _populate_provider_model_sets(model_cost_map: Mapping[str, Mapping[str, obje
if "-to-" not in key and "fireworks-ai-default" not in key:
fireworks_ai_models.add(key)
elif value.get("litellm_provider") == "together_ai":
if _is_past_deprecation_date(value):
if is_hidden_as_retired(value):
together_ai_models.discard(key)
elif not key.startswith("together-ai-"):
together_ai_models.add(key)
elif value.get("litellm_provider") == "baseten":
if _is_past_deprecation_date(value):
if is_hidden_as_retired(value):
baseten_models.discard(key)
else:
baseten_models.add(key)
@ -1051,7 +1041,10 @@ def _populate_provider_model_sets(model_cost_map: Mapping[str, Mapping[str, obje
elif value.get("litellm_provider") == "coralbricks":
coralbricks_models.add(key)
elif value.get("litellm_provider") == "aiand":
aiand_models.add(key)
if is_hidden_as_retired(value):
aiand_models.discard(key)
else:
aiand_models.add(key)
elif value.get("litellm_provider") == "reducto":
reducto_models.add(key)
elif value.get("litellm_provider") == "bedrock_mantle":

View file

@ -1,4 +1,3 @@
import datetime
from collections.abc import Iterable, Iterator, Mapping
from dataclasses import dataclass
from typing import Final, cast
@ -9,6 +8,7 @@ from typing_extensions import NotRequired, ReadOnly, TypedDict
import litellm
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
from litellm.litellm_core_utils.get_llm_provider_logic import inferred_provider
from litellm.litellm_core_utils.model_retirement import is_past_deprecation_date
def _form_field_value(value: object) -> str:
@ -168,7 +168,9 @@ class RankedChatModel:
def rank_chat_models_from_llm_provider(custom_llm_provider: str) -> tuple[RankedChatModel, ...]:
known_models: Final[Iterable[str]] = litellm.models_by_provider.get(custom_llm_provider, ())
from litellm.utils import bundled_provider_models
known_models: Final = bundled_provider_models(custom_llm_provider)
ranked_models: Final = (
RankedChatModel(
model=model, route_rank=route_rank, cost=cost, is_past_deprecation_date=is_past_deprecation_date
@ -187,18 +189,11 @@ _COST_MAP_DEPRECATION: Final = TypeAdapter(_CostMapDeprecation)
def _is_past_deprecation_date(cost_map_key: str) -> bool:
deprecation_date: Final = _COST_MAP_DEPRECATION.validate_python(
litellm.model_cost.get(cost_map_key) or {} # pyright: ignore[reportUnknownMemberType] # untyped cost map JSON
).get("deprecation_date")
if not isinstance(deprecation_date, str):
return False
try:
return (
datetime.date.fromisoformat(deprecation_date)
<= datetime.datetime.now(datetime.timezone.utc).astimezone().date()
return is_past_deprecation_date(
_COST_MAP_DEPRECATION.validate_python(
litellm.model_cost.get(cost_map_key) or {} # pyright: ignore[reportUnknownMemberType] # untyped cost map JSON
)
except ValueError:
return False
)
def _chat_row_costs(models: Iterable[str], custom_llm_provider: str) -> Iterator[tuple[str, float, bool]]:

View file

@ -0,0 +1,19 @@
import datetime
from collections.abc import Mapping
from typing import Final
PROVIDERS_HIDING_RETIRED_MODELS: Final = frozenset({"aiand", "baseten", "together_ai"})
def is_past_deprecation_date(value: Mapping[str, object]) -> bool:
deprecation_date: Final = value.get("deprecation_date")
if not isinstance(deprecation_date, str):
return False
try:
return datetime.date.fromisoformat(deprecation_date) <= datetime.datetime.now(datetime.timezone.utc).date()
except ValueError:
return False
def is_hidden_as_retired(value: Mapping[str, object]) -> bool:
return value.get("litellm_provider") in PROVIDERS_HIDING_RETIRED_MODELS and is_past_deprecation_date(value)

View file

@ -72249,6 +72249,7 @@
},
"aiand/deepseek-ai/deepseek-v4-flash": {
"cache_read_input_token_cost": 8e-08,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 1.5e-07,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
@ -72278,6 +72279,22 @@
"supports_vision": false
},
"aiand/deepseek-ai/deepseek-v4.1-flash": {
"cache_read_input_token_cost": 2e-08,
"deprecation_date": "2026-11-05",
"input_cost_per_token": 3e-07,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 6e-07,
"source": "https://api.aiand.com/v1/models",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/deepseek-ai/deepseek-v4.1-flash-1017": {
"cache_read_input_token_cost": 2e-08,
"input_cost_per_token": 3e-07,
"litellm_provider": "aiand",
@ -72307,8 +72324,24 @@
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/meta/muse-spark-open": {
"cache_read_input_token_cost": 1.5e-07,
"input_cost_per_token": 1.25e-06,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
"max_output_tokens": 943718,
"mode": "chat",
"output_cost_per_token": 4.25e-06,
"source": "https://api.aiand.com/v1/models",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/moonshotai/kimi-k2.7-code": {
"cache_read_input_token_cost": 2e-07,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 7.5e-07,
"litellm_provider": "aiand",
"max_input_tokens": 262144,
@ -72399,6 +72432,7 @@
},
"aiand/zai-org/glm-5.2": {
"cache_read_input_token_cost": 3e-07,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 1e-06,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,

View file

@ -94,6 +94,7 @@ from litellm.litellm_core_utils.fallback_generalizations import (
)
from litellm.litellm_core_utils.get_model_cost_map import AliasedCostEntry, GetModelCostMap
from litellm.litellm_core_utils.model_param_helper import peek_reasoning_summary_aliases
from litellm.litellm_core_utils.model_retirement import is_hidden_as_retired
from litellm.litellm_core_utils.offline_mode import is_offline_mode
from litellm.litellm_core_utils.sensitive_data_masker import redact_credentials_in_payload
from litellm.litellm_core_utils.tokenizer import strip_special_tokens
@ -8806,6 +8807,12 @@ def _get_valid_models_from_provider_api(
return []
def bundled_provider_models(provider: str) -> tuple[str, ...]:
bundled: Final[tuple[str, ...]] = tuple(litellm.models_by_provider.get(provider, ()))
cost_map: Final = _builtin_cost_map()
return tuple(model for model in bundled if not is_hidden_as_retired(cost_map.get(model, {})))
def get_valid_models(
check_provider_endpoint: bool | None = None,
custom_llm_provider: str | None = None,
@ -8871,8 +8878,7 @@ def get_valid_models(
)
)
else:
models_for_provider = copy.deepcopy(litellm.models_by_provider.get(provider, []))
valid_models.extend(models_for_provider)
valid_models.extend(bundled_provider_models(provider))
return valid_models
except Exception as e:

View file

@ -72249,6 +72249,7 @@
},
"aiand/deepseek-ai/deepseek-v4-flash": {
"cache_read_input_token_cost": 8e-08,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 1.5e-07,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
@ -72278,6 +72279,22 @@
"supports_vision": false
},
"aiand/deepseek-ai/deepseek-v4.1-flash": {
"cache_read_input_token_cost": 2e-08,
"deprecation_date": "2026-11-05",
"input_cost_per_token": 3e-07,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
"max_output_tokens": 384000,
"mode": "chat",
"output_cost_per_token": 6e-07,
"source": "https://api.aiand.com/v1/models",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/deepseek-ai/deepseek-v4.1-flash-1017": {
"cache_read_input_token_cost": 2e-08,
"input_cost_per_token": 3e-07,
"litellm_provider": "aiand",
@ -72307,8 +72324,24 @@
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/meta/muse-spark-open": {
"cache_read_input_token_cost": 1.5e-07,
"input_cost_per_token": 1.25e-06,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,
"max_output_tokens": 943718,
"mode": "chat",
"output_cost_per_token": 4.25e-06,
"source": "https://api.aiand.com/v1/models",
"supports_function_calling": true,
"supports_prompt_caching": true,
"supports_reasoning": true,
"supports_tool_choice": true,
"supports_vision": true
},
"aiand/moonshotai/kimi-k2.7-code": {
"cache_read_input_token_cost": 2e-07,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 7.5e-07,
"litellm_provider": "aiand",
"max_input_tokens": 262144,
@ -72399,6 +72432,7 @@
},
"aiand/zai-org/glm-5.2": {
"cache_read_input_token_cost": 3e-07,
"deprecation_date": "2026-10-27",
"input_cost_per_token": 1e-06,
"litellm_provider": "aiand",
"max_input_tokens": 1048576,

View file

@ -1,3 +1,5 @@
import datetime
import time
from typing import Final
import httpx
@ -245,3 +247,39 @@ def test_rank_chat_models_flags_a_retired_model_the_provider_lists_without_its_c
(ranked_model.model, ranked_model.is_past_deprecation_date)
for ranked_model in rank_chat_models_from_llm_provider("vertex_ai")
] == [("fake-retired-vertex-chat", True), ("fake-live-vertex-chat", False)]
@pytest.mark.parametrize("host_zone", ["Etc/GMT+12", "Etc/GMT-14"])
def test_rank_chat_models_flags_retirement_by_the_utc_date_whatever_the_host_zone(
monkeypatch: pytest.MonkeyPatch, host_zone: str
) -> None:
utc_today: Final = datetime.datetime.now(datetime.timezone.utc).date()
retirement_dates: Final = {
"deepseek/fake-retires-today-chat": utc_today,
"deepseek/fake-retires-tomorrow-chat": utc_today + datetime.timedelta(days=1),
}
for name, retirement_date in retirement_dates.items():
monkeypatch.setitem(
litellm.model_cost,
name,
{
"input_cost_per_token": 1e-12,
"output_cost_per_token": 1e-12,
"mode": "chat",
"litellm_provider": PICKER_PROVIDER,
"deprecation_date": retirement_date.isoformat(),
},
)
monkeypatch.setitem(litellm.models_by_provider, PICKER_PROVIDER, tuple(retirement_dates))
monkeypatch.setenv("TZ", host_zone)
time.tzset()
try:
flags: Final = {
ranked_model.model: ranked_model.is_past_deprecation_date
for ranked_model in rank_chat_models_from_llm_provider(PICKER_PROVIDER)
}
finally:
monkeypatch.undo()
time.tzset()
assert flags == {"deepseek/fake-retires-today-chat": True, "deepseek/fake-retires-tomorrow-chat": False}

View file

@ -7,8 +7,9 @@ import pytest
import respx
import litellm
from litellm.litellm_core_utils.model_retirement import is_past_deprecation_date
from litellm.llms.baseten.chat import BasetenConfig
from litellm.utils import get_valid_models
from litellm.utils import _model_cache, get_valid_models
class TestBasetenRouting:
@ -65,7 +66,7 @@ class TestBasetenModelDiscovery:
servable_registry_models: Final = {
key
for key, value in litellm.model_cost.items()
if value.get("litellm_provider") == "baseten" and not litellm._is_past_deprecation_date(value)
if value.get("litellm_provider") == "baseten" and not is_past_deprecation_date(value)
}
assert servable_registry_models
@ -92,6 +93,20 @@ class TestBasetenModelDiscovery:
litellm.baseten_models.discard(model)
litellm.add_known_models({})
def test_wildcard_drops_a_model_whose_retirement_date_passes_while_running(self, monkeypatch: pytest.MonkeyPatch):
model: Final = "baseten/acme/retires-while-running"
monkeypatch.setitem(litellm.model_cost, model, {"litellm_provider": "baseten", "deprecation_date": "2999-01-01"})
try:
litellm.add_known_models({model: litellm.model_cost[model]})
assert model in get_valid_models(custom_llm_provider="baseten")
monkeypatch.setitem(litellm.model_cost, model, {"litellm_provider": "baseten", "deprecation_date": "2000-01-01"})
assert model not in get_valid_models(custom_llm_provider="baseten")
finally:
litellm.baseten_models.discard(model)
litellm.add_known_models({})
@pytest.mark.parametrize(
"value, expected",
[
@ -102,7 +117,7 @@ class TestBasetenModelDiscovery:
],
)
def test_is_past_deprecation_date(self, value, expected):
assert litellm._is_past_deprecation_date(value) is expected
assert is_past_deprecation_date(value) is expected
def test_get_models_calls_model_api(self, respx_mock: respx.MockRouter) -> None:
route: Final = respx_mock.get("https://inference.baseten.co/v1/models").mock(
@ -156,6 +171,18 @@ class TestBasetenModelDiscovery:
assert models == ["baseten/brand-new/model"]
@patch.dict(os.environ, {"BASETEN_API_KEY": "test-key"})
def test_live_catalog_keeps_a_model_the_cost_map_dates_as_retired(self, respx_mock: respx.MockRouter) -> None:
assert is_past_deprecation_date(litellm.model_cost["baseten/zai-org/GLM-5"])
_model_cache.flush_cache()
respx_mock.get("https://inference.baseten.co/v1/models").mock(
return_value=httpx.Response(200, json={"data": [{"id": "zai-org/GLM-5"}]})
)
models: Final = get_valid_models(check_provider_endpoint=True, custom_llm_provider="baseten")
assert models == ["baseten/zai-org/GLM-5"]
class TestBasetenReasoningEffort:
def test_reasoning_effort_passthrough_with_drop_params(self):

View file

@ -1,4 +1,6 @@
import datetime
import json
import time
from typing import Final
import httpx
@ -8,6 +10,8 @@ import respx
import litellm
from litellm.caching.llm_caching_handler import LLMClientCache
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
from litellm.litellm_core_utils.health_check_helpers import wildcard_health_check_models
from litellm.litellm_core_utils.model_retirement import is_past_deprecation_date
from litellm.llms.openai_like.json_loader import JSONProviderRegistry
from litellm.proxy.auth.model_checks import get_known_models_from_wildcard
from litellm.utils import ProviderConfigManager
@ -89,11 +93,99 @@ def _aiand_catalog() -> frozenset[str]:
return frozenset(name for name, info in litellm.model_cost.items() if info.get("litellm_provider") == "aiand")
def test_aiand_wildcard_lists_its_priced_models():
catalog: Final = _aiand_catalog()
def _utc_today() -> datetime.date:
return datetime.datetime.now(datetime.timezone.utc).date()
assert catalog
assert frozenset(get_known_models_from_wildcard("aiand/*")) == catalog
def test_aiand_wildcard_lists_only_models_not_yet_retired():
catalog: Final = _aiand_catalog()
retired: Final = frozenset(
name
for name in catalog
if (retired_on := litellm.model_cost[name].get("deprecation_date")) is not None
and datetime.date.fromisoformat(retired_on) <= _utc_today()
)
assert catalog - retired
assert frozenset(get_known_models_from_wildcard("aiand/*")) == catalog - retired
def test_aiand_wildcard_drops_a_model_whose_retirement_date_passes_while_running(monkeypatch: pytest.MonkeyPatch):
model: Final = "aiand/acme/retires-while-running"
monkeypatch.setitem(litellm.model_cost, model, {"litellm_provider": "aiand", "deprecation_date": "2999-01-01"})
try:
litellm.add_known_models({model: litellm.model_cost[model]})
assert model in get_known_models_from_wildcard("aiand/*")
monkeypatch.setitem(litellm.model_cost, model, {"litellm_provider": "aiand", "deprecation_date": "2000-01-01"})
assert model not in get_known_models_from_wildcard("aiand/*")
finally:
litellm.aiand_models.discard(model)
litellm.add_known_models({})
def test_aiand_models_by_provider_leaves_out_models_retired_at_load():
retired: Final = frozenset(
name
for name, row in litellm.model_cost.items()
if row.get("litellm_provider") == "aiand" and is_past_deprecation_date(row)
)
assert retired
assert retired.isdisjoint(litellm.models_by_provider["aiand"])
def test_aiand_wildcard_health_check_skips_a_model_whose_retirement_date_passes_while_running(
monkeypatch: pytest.MonkeyPatch,
):
model: Final = "aiand/acme/free-and-retiring"
live_row: Final = {
"litellm_provider": "aiand",
"mode": "chat",
"input_cost_per_token": 0.0,
"output_cost_per_token": 0.0,
"deprecation_date": "2999-01-01",
}
monkeypatch.setitem(litellm.model_cost, model, live_row)
try:
litellm.add_known_models({model: live_row})
assert model in wildcard_health_check_models(wildcard="aiand/*", custom_llm_provider="aiand")
monkeypatch.setitem(litellm.model_cost, model, {**live_row, "deprecation_date": "2000-01-01"})
assert model not in wildcard_health_check_models(wildcard="aiand/*", custom_llm_provider="aiand")
finally:
litellm.aiand_models.discard(model)
litellm.add_known_models({})
@pytest.mark.parametrize("host_zone", ["Etc/GMT+12", "Etc/GMT-14"])
def test_aiand_models_retire_on_the_utc_date_whatever_the_host_zone(monkeypatch: pytest.MonkeyPatch, host_zone: str):
today: Final = _utc_today()
rows: Final = {
"aiand/acme/retires-today": {"litellm_provider": "aiand", "deprecation_date": today.isoformat()},
"aiand/acme/retires-tomorrow": {
"litellm_provider": "aiand",
"deprecation_date": (today + datetime.timedelta(days=1)).isoformat(),
},
}
for name, row in rows.items():
monkeypatch.setitem(litellm.model_cost, name, row)
monkeypatch.setenv("TZ", host_zone)
time.tzset()
try:
litellm.add_known_models(rows)
listed: Final = frozenset(get_known_models_from_wildcard("aiand/*"))
assert "aiand/acme/retires-today" not in listed
assert "aiand/acme/retires-tomorrow" in listed
finally:
monkeypatch.undo()
time.tzset()
for name in rows:
litellm.aiand_models.discard(name)
litellm.add_known_models({})
def test_aiand_calls_are_billed_from_their_cost_rows():

View file

@ -0,0 +1,4 @@
<svg width="154" height="170" viewBox="0 0 154 170" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M142.201 58.0338C147.015 54.3129 153.925 57.8127 153.925 63.9711V162.493C153.925 166.608 150.654 169.944 146.617 169.944H142.131C141.665 169.944 141.22 169.751 140.896 169.411L112.255 139.292C109.89 136.807 106.099 136.452 103.331 138.459L60.3523 169.62C60.0612 169.831 59.7129 169.944 59.3559 169.944H19.1938C12.1908 169.944 9.19832 160.871 14.7784 156.556L142.201 58.0338Z" fill="#151517"/>
<path d="M142.474 1.14602C146.28 -1.75046 151.759 1.18386 151.457 6.14031L149.665 35.5148C149.561 37.2177 148.728 38.7803 147.4 39.7908L8.99184 145.042C5.31127 147.841 0.000248842 145.196 0 140.407V112.48C2.31386e-05 110.648 0.848057 108.933 2.27475 107.846L142.474 1.14602Z" fill="#C70007"/>
</svg>

After

Width:  |  Height:  |  Size: 802 B

View file

@ -32,6 +32,18 @@ describe("provider_info_helpers", () => {
expect(getPlaceholder(Providers.BIGMODEL)).toMatch(/^bigmodel\//);
});
it("resolves ai& metadata, name, logo and model placeholder", () => {
const metadata = PROVIDER_CREATE_FIELDS.find((provider) => provider.provider === "AIAND");
expect(provider_map.AIAND).toBe(metadata?.litellm_provider);
expect(getProviderLogoAndName("aiand")).toEqual({
displayName: "ai&",
logo: providerLogoMap[Providers.AIAND],
});
expect(getProviderLogoAndName("AIAND")).toEqual(getProviderLogoAndName("aiand"));
expect(providerLogoMap[Providers.AIAND]).toContain("aiand");
expect(getPlaceholder(Providers.AIAND)).toBe("aiand/openai/gpt-oss-120b");
});
it("maps Microsoft 365 Copilot to its chat model provider and placeholder", () => {
expect(provider_map.MICROSOFT_365_COPILOT).toBe("microsoft_365_copilot");
expect(getPlaceholder(Providers.MICROSOFT_365_COPILOT)).toBe("microsoft_365_copilot/chat");

View file

@ -1,6 +1,7 @@
import { resolveLogoSrc } from "@/lib/assetPaths";
import a2aAgentLogo from "../../public/assets/logos/a2a_agent.png";
import ai21Logo from "../../public/assets/logos/ai21.svg";
import aiandLogo from "../../public/assets/logos/aiand.svg";
import aimlApiLogo from "../../public/assets/logos/aiml_api.svg";
import anthropicLogo from "../../public/assets/logos/anthropic.svg";
import assemblyaiSmallLogo from "../../public/assets/logos/assemblyai_small.png";
@ -76,6 +77,7 @@ export enum Providers {
A2A_Agent = "A2A Agent",
AI21 = "Ai21",
AI21_CHAT = "Ai21 Chat",
AIAND = "ai&",
AIML = "AI/ML API",
AIOHTTP_OPENAI = "Aiohttp Openai",
Anthropic = "Anthropic",
@ -207,6 +209,7 @@ export const provider_map: Record<string, string> = {
A2A_Agent: "a2a_agent",
AI21: "ai21",
AI21_CHAT: "ai21_chat",
AIAND: "aiand",
AIML: "aiml",
AIOHTTP_OPENAI: "aiohttp_openai",
Anthropic: "anthropic",
@ -344,6 +347,7 @@ export const providerLogoMap: Partial<Record<Providers, string>> = {
[Providers.A2A_Agent]: a2aAgentLogo.src,
[Providers.AI21]: ai21Logo.src,
[Providers.AI21_CHAT]: ai21Logo.src,
[Providers.AIAND]: aiandLogo.src,
[Providers.AIML]: aimlApiLogo.src,
[Providers.AIOHTTP_OPENAI]: openaiSmallLogo.src,
[Providers.Anthropic]: anthropicLogo.src,
@ -472,6 +476,7 @@ export const getProviderLogoAndName = (providerValue: string): { logo: string; d
const providerPlaceholderMap: Partial<Record<Providers, string>> = {
[Providers.AIML]: "aiml/flux-pro/v1.1",
[Providers.AIAND]: "aiand/openai/gpt-oss-120b",
[Providers.Anthropic]: "claude-3-opus",
[Providers.Azure]: "my-deployment",
[Providers.Azure_AI_Studio]: "azure_ai/command-r-plus",

View file

@ -32,6 +32,7 @@ const TREATMENT_BY_ASSET: Readonly<Record<string, LogoTreatment>> = {
"v0.svg": "invert",
"vercel.svg": "invert",
"watsonx.svg": "invert",
"aiand.svg": "plate",
"aiml_api.svg": "plate",
"akto.svg": "plate",
"aws.svg": "plate",