diff --git a/litellm/__init__.py b/litellm/__init__.py index 8e5dd68a186..cb453b83a4e 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -685,6 +685,7 @@ qwen_ai_platform_models: Set = set() moonshot_models: Set = set() publicai_models: Set = set() darkbloom_models: Set = set() +token_kiosk_models: Final[Set[str]] = set() openinfer_models: Final[Set[str]] = set() kyma_models: Final[set[str]] = set() # mutable-ok: populated when the shared model catalog loads streamlake_models: Final[Set[str]] = set() @@ -979,6 +980,8 @@ def _populate_provider_model_sets(model_cost_map: Mapping[str, Mapping[str, obje publicai_models.add(key) elif value.get("litellm_provider") == "darkbloom": darkbloom_models.add(key) + elif value.get("litellm_provider") == "token_kiosk": + token_kiosk_models.add(key) elif value.get("litellm_provider") == "openinfer": openinfer_models.add(key) elif value.get("litellm_provider") == "kyma": @@ -1165,6 +1168,7 @@ model_list = list( | moonshot_models | publicai_models | darkbloom_models + | token_kiosk_models | openinfer_models | kyma_models | streamlake_models @@ -1299,6 +1303,7 @@ def _build_models_by_provider() -> dict: "moonshot": moonshot_models, "publicai": publicai_models, "darkbloom": darkbloom_models, + "token_kiosk": token_kiosk_models, "openinfer": openinfer_models, "kyma": kyma_models, "streamlake": streamlake_models, diff --git a/litellm/constants.py b/litellm/constants.py index b43f6d9f358..9ad8e9bb0d6 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1132,6 +1132,7 @@ openai_compatible_providers: Final[list[str]] = [ "lm_studio", "galadriel", "zerogpu", + "token_kiosk", "github_copilot", # GitHub Copilot Chat API "chatgpt", # ChatGPT subscription API "novita", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index f937e4d2f75..0606bff39fe 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -314,5 +314,14 @@ "supports_function_calling": true, "supports_reasoning": true } + }, + "token_kiosk": { + "base_url": "https://api-token-kiosk.gaib.ai/v1", + "api_key_env": "TOKEN_KIOSK_API_KEY", + "api_base_env": "TOKEN_KIOSK_API_BASE", + "provider_info": { + "supports_function_calling": true, + "supports_reasoning": true + } } } diff --git a/litellm/types/utils.py b/litellm/types/utils.py index b082198d3f2..f247b926b7d 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4785,6 +4785,7 @@ class LlmProviders(str, Enum): INFERENCEAPIS = "inferenceapis" HOSTINGER_ROUTER = "hostinger_router" ZEROGPU = "zerogpu" + TOKEN_KIOSK = "token_kiosk" DARKBLOOM = "darkbloom" META = "meta" SAIL = "sail" diff --git a/tests/unit/llms/openai_like/test_token_kiosk_provider.py b/tests/unit/llms/openai_like/test_token_kiosk_provider.py new file mode 100644 index 00000000000..64c5d9b5200 --- /dev/null +++ b/tests/unit/llms/openai_like/test_token_kiosk_provider.py @@ -0,0 +1,64 @@ +import json +from typing import Final + +import pytest +import respx + +import litellm +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + +def test_token_kiosk_resolves_environment_and_explicit_credentials(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("TOKEN_KIOSK_API_KEY", "fake-token-kiosk-key") + assert get_llm_provider(model="token_kiosk/claude-3-5-sonnet") == ( + "claude-3-5-sonnet", + "token_kiosk", + "fake-token-kiosk-key", + "https://api-token-kiosk.gaib.ai/v1", + ) + assert get_llm_provider( + model="token_kiosk/claude-3-5-sonnet", + api_key="explicit-key", + api_base="https://custom-router.example/v1", + ) == ( + "claude-3-5-sonnet", + "token_kiosk", + "explicit-key", + "https://custom-router.example/v1", + ) + + +def test_token_kiosk_chat_completion_dispatch() -> None: + with respx.mock() as upstream: + route: Final = upstream.post("https://api-token-kiosk.gaib.ai/v1/chat/completions").respond( + 200, + json={ + "id": "chatcmpl-token-kiosk-test", + "object": "chat.completion", + "created": 1724000000, + "model": "claude-3-5-sonnet", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello from Token Kiosk!"}, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 5, + "total_tokens": 15, + }, + }, + ) + response: Final = litellm.completion( + model="token_kiosk/claude-3-5-sonnet", + api_key="test-key", + messages=[{"role": "user", "content": "Hello"}], + ) + + assert route.called + assert route.calls.last.request.headers["authorization"] == "Bearer test-key" + body = json.loads(route.calls.last.request.content) + assert body["model"] == "claude-3-5-sonnet" + assert response.choices[0].message.content == "Hello from Token Kiosk!"