{"repo": "chesser-y/noteclaw", "files": {"nanobot/nanobot/providers/registry.py": {"e93321748de7b073e943b94157f9226113957756": {"t": 1785595187.5422544, "n": 16786, "h": "2ebb538015870610", "c": "\"\"\"\nProvider Registry — single source of truth for LLM provider metadata.\n\nAdding a new provider:\n 1. Add a ProviderSpec to PROVIDERS below.\n 2. Add a field to ProvidersConfig in config/schema.py.\n Done. Env vars, config matching, status display all derive from here.\n\nOrder matters — it controls match priority and fallback. Gateways first.\nEvery entry writes out all fields so you can copy-paste as a template.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any\n\nfrom pydantic.alias_generators import to_snake\n\n\n@dataclass(frozen=True)\nclass ProviderSpec:\n \"\"\"One LLM provider's metadata. See PROVIDERS below for real examples.\n\n Placeholders in env_extras values:\n {api_key} — the user's API key\n {api_base} — api_base from config, or this spec's default_api_base\n \"\"\"\n\n # identity\n name: str # config field name, e.g. \"dashscope\"\n keywords: tuple[str, ...] # model-name keywords for matching (lowercase)\n env_key: str # env var for API key, e.g. \"DASHSCOPE_API_KEY\"\n display_name: str = \"\" # shown in `nanobot status`\n\n # which provider implementation to use\n # \"openai_compat\" | \"anthropic\" | \"azure_openai\" | \"openai_codex\" | \"github_copilot\" | \"bedrock\"\n backend: str = \"openai_compat\"\n\n # extra env vars, e.g. ((\"ZHIPUAI_API_KEY\", \"{api_key}\"),)\n env_extras: tuple[tuple[str, str], ...] = ()\n\n # gateway / local detection\n is_gateway: bool = False # routes any model (OpenRouter, AiHubMix)\n is_local: bool = False # local deployment (vLLM, Ollama)\n detect_by_key_prefix: str = \"\" # match api_key prefix, e.g. \"sk-or-\"\n detect_by_base_keyword: str = \"\" # match substring in api_base URL\n default_api_base: str = \"\" # OpenAI-compatible base URL for this provider\n\n # gateway behavior\n strip_model_prefix: bool = False # strip \"provider/\" before sending to gateway\n supports_max_completion_tokens: bool = False\n\n # per-model param overrides, e.g. ((\"kimi-k2.5\", {\"temperature\": 1.0}),)\n model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()\n\n # OAuth-based providers (e.g., OpenAI Codex) don't use API keys\n is_oauth: bool = False\n\n # Direct providers skip API-key validation (user supplies everything)\n is_direct: bool = False\n\n # Provider supports cache_control on content blocks (e.g. Anthropic prompt caching)\n supports_prompt_caching: bool = False\n\n # How to inject the thinking on/off toggle into extra_body.\n # \"\" — no extra_body needed (default)\n # \"thinking_type\" — {\"thinking\": {\"type\": \"enabled\"/\"disabled\"}}\n # (DeepSeek, VolcEngine, BytePlus)\n # \"enable_thinking\" — {\"enable_thinking\": true/false} (DashScope)\n # \"reasoning_split\" — {\"reasoning_split\": true/false} (MiniMax)\n thinking_style: str = \"\"\n\n # When True, treat the \"reasoning\" response field as formal content\n # when \"content\" is empty. Only set this for providers (e.g. StepFun)\n # whose API returns the actual answer in \"reasoning\" instead of \"content\".\n reasoning_as_content: bool = False\n\n @property\n def label(self) -> str:\n return self.display_name or self.name.title()\n\n\n# ---------------------------------------------------------------------------\n# PROVIDERS — the registry. Order = priority. Copy any entry as template.\n# ---------------------------------------------------------------------------\n\nPROVIDERS: tuple[ProviderSpec, ...] = (\n # === Custom (direct OpenAI-compatible endpoint) ========================\n ProviderSpec(\n name=\"custom\",\n keywords=(),\n env_key=\"\",\n display_name=\"Custom\",\n backend=\"openai_compat\",\n is_direct=True,\n ),\n\n # === Azure OpenAI (direct API calls with API version 2024-10-21) =====\n ProviderSpec(\n name=\"azure_openai\",\n keywords=(\"azure\", \"azure-openai\"),\n env_key=\"\",\n display_name=\"Azure OpenAI\",\n backend=\"azure_openai\",\n is_direct=True,\n ),\n # === AWS Bedrock (native Converse API via bedrock-runtime) =============\n ProviderSpec(\n name=\"bedrock\",\n keywords=(\n \"bedrock\",\n \"anthropic.claude\",\n \"amazon.nova\",\n \"meta.\",\n \"mistral.\",\n \"cohere.\",\n \"qwen.\",\n \"deepseek.\",\n \"openai.gpt-oss\",\n \"ai21.\",\n \"moonshot.\",\n \"writer.\",\n \"zai.\",\n ),\n env_key=\"AWS_BEARER_TOKEN_BEDROCK\",\n display_name=\"AWS Bedrock\",\n backend=\"bedrock\",\n is_direct=True,\n ),\n # === Gateways (detected by api_key / api_base, not model name) =========\n # Gateways can route any model, so they win in fallback.\n # OpenRouter: global gateway, keys start with \"sk-or-\"\n ProviderSpec(\n name=\"openrouter\",\n keywords=(\"openrouter\",),\n env_key=\"OPENROUTER_API_KEY\",\n display_name=\"OpenRouter\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_key_prefix=\"sk-or-\",\n detect_by_base_keyword=\"openrouter\",\n default_api_base=\"https://openrouter.ai/api/v1\",\n supports_prompt_caching=True,\n ),\n # Hugging Face Inference Providers: OpenAI-compatible router for chat models.\n ProviderSpec(\n name=\"huggingface\",\n keywords=(\"huggingface\", \"hugging-face\"),\n env_key=\"HF_TOKEN\",\n display_name=\"Hugging Face\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_key_prefix=\"hf_\",\n detect_by_base_keyword=\"huggingface\",\n default_api_base=\"https://router.huggingface.co/v1\",\n ),\n # AiHubMix: global gateway, OpenAI-compatible interface.\n # strip_model_prefix=True: doesn't understand \"anthropic/claude-3\",\n # strips to bare \"claude-3\".\n ProviderSpec(\n name=\"aihubmix\",\n keywords=(\"aihubmix\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"AiHubMix\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"aihubmix\",\n default_api_base=\"https://aihubmix.com/v1\",\n strip_model_prefix=True,\n ),\n # SiliconFlow (硅基流动): OpenAI-compatible gateway, model names keep org prefix\n ProviderSpec(\n name=\"siliconflow\",\n keywords=(\"siliconflow\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"SiliconFlow\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"siliconflow\",\n default_api_base=\"https://api.siliconflow.cn/v1\",\n ),\n\n # VolcEngine (火山引擎): OpenAI-compatible gateway, pay-per-use models\n ProviderSpec(\n name=\"volcengine\",\n keywords=(\"volcengine\", \"volces\", \"ark\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"VolcEngine\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"volces\",\n default_api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n thinking_style=\"thinking_type\",\n supports_max_completion_tokens=True,\n ),\n\n # VolcEngine Coding Plan (火山引擎 Coding Plan): same key as volcengine\n ProviderSpec(\n name=\"volcengine_coding_plan\",\n keywords=(\"volcengine-plan\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"VolcEngine Coding Plan\",\n backend=\"openai_compat\",\n is_gateway=True,\n default_api_base=\"https://ark.cn-beijing.volces.com/api/coding/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n supports_max_completion_tokens=True,\n ),\n\n # BytePlus: VolcEngine international, pay-per-use models\n ProviderSpec(\n name=\"byteplus\",\n keywords=(\"byteplus\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"BytePlus\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"bytepluses\",\n default_api_base=\"https://ark.ap-southeast.bytepluses.com/api/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n ),\n\n # BytePlus Coding Plan: same key as byteplus\n ProviderSpec(\n name=\"byteplus_coding_plan\",\n keywords=(\"byteplus-plan\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"BytePlus Coding Plan\",\n backend=\"openai_compat\",\n is_gateway=True,\n default_api_base=\"https://ark.ap-southeast.bytepluses.com/api/coding/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n ),\n\n\n # === Standard providers (matched by model-name keywords) ===============\n # Anthropic: native Anthropic SDK\n ProviderSpec(\n name=\"anthropic\",\n keywords=(\"anthropic\", \"claude\"),\n env_key=\"ANTHROPIC_API_KEY\",\n display_name=\"Anthropic\",\n backend=\"anthropic\",\n supports_prompt_caching=True,\n ),\n # OpenAI: SDK default base URL (no override needed)\n ProviderSpec(\n name=\"openai\",\n keywords=(\"openai\", \"gpt\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"OpenAI\",\n backend=\"openai_compat\",\n supports_max_completion_tokens=True,\n ),\n # OpenAI Codex: OAuth-based, dedicated provider\n ProviderSpec(\n name=\"openai_codex\",\n keywords=(\"openai-codex\",),\n env_key=\"\",\n display_name=\"OpenAI Codex\",\n backend=\"openai_codex\",\n detect_by_base_keyword=\"codex\",\n default_api_base=\"https://chatgpt.com/backend-api\",\n is_oauth=True,\n ),\n # GitHub Copilot: OAuth-based\n ProviderSpec(\n name=\"github_copilot\",\n keywords=(\"github_copilot\", \"copilot\"),\n env_key=\"\",\n display_name=\"Github Copilot\",\n backend=\"github_copilot\",\n default_api_base=\"https://api.githubcopilot.com\",\n strip_model_prefix=True,\n is_oauth=True,\n supports_max_completion_tokens=True,\n ),\n # DeepSeek: OpenAI-compatible at api.deepseek.com\n ProviderSpec(\n name=\"deepseek\",\n keywords=(\"deepseek\",),\n env_key=\"DEEPSEEK_API_KEY\",\n display_name=\"DeepSeek\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.deepseek.com\",\n thinking_style=\"thinking_type\",\n ),\n # Gemini: Google's OpenAI-compatible endpoint\n ProviderSpec(\n name=\"gemini\",\n keywords=(\"gemini\", \"gemma\"),\n env_key=\"GEMINI_API_KEY\",\n display_name=\"Gemini\",\n backend=\"openai_compat\",\n default_api_base=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n ),\n # Zhipu (智谱): OpenAI-compatible at open.bigmodel.cn\n ProviderSpec(\n name=\"zhipu\",\n keywords=(\"zhipu\", \"glm\", \"zai\"),\n env_key=\"ZAI_API_KEY\",\n display_name=\"Zhipu AI\",\n backend=\"openai_compat\",\n env_extras=((\"ZHIPUAI_API_KEY\", \"{api_key}\"),),\n default_api_base=\"https://open.bigmodel.cn/api/paas/v4\",\n ),\n # DashScope (通义): Qwen models, OpenAI-compatible endpoint\n ProviderSpec(\n name=\"dashscope\",\n keywords=(\"qwen\", \"dashscope\"),\n env_key=\"DASHSCOPE_API_KEY\",\n display_name=\"DashScope\",\n backend=\"openai_compat\",\n default_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n thinking_style=\"enable_thinking\",\n ),\n # Moonshot (月之暗面): Kimi K2.5 / K2.6 enforce temperature >= 1.0.\n ProviderSpec(\n name=\"moonshot\",\n keywords=(\"moonshot\", \"kimi\"),\n env_key=\"MOONSHOT_API_KEY\",\n display_name=\"Moonshot\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.moonshot.ai/v1\",\n model_overrides=(\n (\"kimi-k2.5\", {\"temperature\": 1.0}),\n (\"kimi-k2.6\", {\"temperature\": 1.0}),\n ),\n ),\n # MiniMax: OpenAI-compatible API\n ProviderSpec(\n name=\"minimax\",\n keywords=(\"minimax\",),\n env_key=\"MINIMAX_API_KEY\",\n display_name=\"MiniMax\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.minimax.io/v1\",\n thinking_style=\"reasoning_split\",\n ),\n # MiniMax Anthropic-compatible endpoint: supports thinking mode\n ProviderSpec(\n name=\"minimax_anthropic\",\n keywords=(\"minimax_anthropic\",),\n env_key=\"MINIMAX_API_KEY\",\n display_name=\"MiniMax (Anthropic)\",\n backend=\"anthropic\",\n default_api_base=\"https://api.minimax.io/anthropic\",\n ),\n # Mistral AI: OpenAI-compatible API\n ProviderSpec(\n name=\"mistral\",\n keywords=(\"mistral\",),\n env_key=\"MISTRAL_API_KEY\",\n display_name=\"Mistral\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.mistral.ai/v1\",\n ),\n # Step Fun (阶跃星辰): OpenAI-compatible API\n ProviderSpec(\n name=\"stepfun\",\n keywords=(\"stepfun\", \"step\"),\n env_key=\"STEPFUN_API_KEY\",\n display_name=\"Step Fun\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.stepfun.com/v1\",\n reasoning_as_content=True,\n ),\n # Xiaomi MIMO (小米): OpenAI-compatible API\n # Hosted API (api.xiaomimimo.com) accepts {\"thinking\": {\"type\": \"enabled\"|\"disabled\"}}\n # to toggle reasoning, matching the existing thinking_type style.\n ProviderSpec(\n name=\"xiaomi_mimo\",\n keywords=(\"xiaomi_mimo\", \"mimo\"),\n env_key=\"XIAOMIMIMO_API_KEY\",\n display_name=\"Xiaomi MIMO\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.xiaomimimo.com/v1\",\n thinking_style=\"thinking_type\",\n ),\n # LongCat: OpenAI-compatible API\n ProviderSpec(\n name=\"longcat\",\n keywords=(\"longcat\",),\n env_key=\"LONGCAT_API_KEY\",\n display_name=\"LongCat\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.longcat.chat/openai/v1\",\n ),\n # === Local deployment (matched by config key, NOT by api_base) =========\n # vLLM / any OpenAI-compatible local server\n ProviderSpec(\n name=\"vllm\",\n keywords=(\"vllm\",),\n env_key=\"HOSTED_VLLM_API_KEY\",\n display_name=\"vLLM/Local\",\n backend=\"openai_compat\",\n is_local=True,\n ),\n # Ollama (local, OpenAI-compatible)\n ProviderSpec(\n name=\"ollama\",\n keywords=(\"ollama\", \"nemotron\"),\n env_key=\"OLLAMA_API_KEY\",\n display_name=\"Ollama\",\n backend=\"openai_compat\",\n is_local=True,\n detect_by_base_keyword=\"11434\",\n default_api_base=\"http://localhost:11434/v1\",\n ),\n # LM Studio (local, OpenAI-compatible)\n ProviderSpec(\n name=\"lm_studio\",\n keywords=(\"lm-studio\", \"lmstudio\", \"lm_studio\"),\n env_key=\"LM_STUDIO_API_KEY\",\n display_name=\"LM Studio\",\n backend=\"openai_compat\",\n is_local=True,\n detect_by_base_keyword=\"1234\",\n default_api_base=\"http://localhost:1234/v1\",\n ),\n # === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) ===\n ProviderSpec(\n name=\"ovms\",\n keywords=(\"openvino\", \"ovms\"),\n env_key=\"\",\n display_name=\"OpenVINO Model Server\",\n backend=\"openai_compat\",\n is_direct=True,\n is_local=True,\n default_api_base=\"http://localhost:8000/v3\",\n ),\n # === NVIDIA NIM (NVIDIA Inference Microservices) =======================\n # Keys start with \"nvapi-\", base URL at integrate.api.nvidia.com\n ProviderSpec(\n name=\"nvidia\",\n keywords=(\"nvidia\", \"nemotron\", \"nvapi\"),\n env_key=\"NVIDIA_NIM_API_KEY\",\n display_name=\"NVIDIA NIM\",\n backend=\"openai_compat\",\n is_gateway=False,\n detect_by_key_prefix=\"nvapi-\",\n detect_by_base_keyword=\"nvidia.com\",\n default_api_base=\"https://integrate.api.nvidia.com/v1\",\n ),\n # === Auxiliary (not a primary LLM provider) ============================\n # Groq: mainly used for Whisper voice transcription, also usable for LLM\n ProviderSpec(\n name=\"groq\",\n keywords=(\"groq\",),\n env_key=\"GROQ_API_KEY\",\n display_name=\"Groq\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.groq.com/openai/v1\",\n ),\n # Qianfan (百度千帆): OpenAI-compatible API\n ProviderSpec(\n name=\"qianfan\",\n keywords=(\"qianfan\", \"ernie\"),\n env_key=\"QIANFAN_API_KEY\",\n display_name=\"Qianfan\",\n backend=\"openai_compat\",\n default_api_base=\"https://qianfan.baidubce.com/v2\"\n ),\n)\n\n\n# ---------------------------------------------------------------------------\n# Lookup helpers\n# ---------------------------------------------------------------------------\n\n\ndef find_by_name(name: str) -> ProviderSpec | None:\n \"\"\"Find a provider spec by config field name, e.g. \"dashscope\".\"\"\"\n normalized = to_snake(name.replace(\"-\", \"_\"))\n for spec in PROVIDERS:\n if spec.name == normalized:\n return spec\n return None\n"}, "06489b8595e2c2a06bb17047c8ddd726384dff53": {"t": 1785763739.137999, "n": 16786, "h": "2ebb538015870610", "c": "\"\"\"\nProvider Registry — single source of truth for LLM provider metadata.\n\nAdding a new provider:\n 1. Add a ProviderSpec to PROVIDERS below.\n 2. Add a field to ProvidersConfig in config/schema.py.\n Done. Env vars, config matching, status display all derive from here.\n\nOrder matters — it controls match priority and fallback. Gateways first.\nEvery entry writes out all fields so you can copy-paste as a template.\n\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\nfrom typing import Any\n\nfrom pydantic.alias_generators import to_snake\n\n\n@dataclass(frozen=True)\nclass ProviderSpec:\n \"\"\"One LLM provider's metadata. See PROVIDERS below for real examples.\n\n Placeholders in env_extras values:\n {api_key} — the user's API key\n {api_base} — api_base from config, or this spec's default_api_base\n \"\"\"\n\n # identity\n name: str # config field name, e.g. \"dashscope\"\n keywords: tuple[str, ...] # model-name keywords for matching (lowercase)\n env_key: str # env var for API key, e.g. \"DASHSCOPE_API_KEY\"\n display_name: str = \"\" # shown in `nanobot status`\n\n # which provider implementation to use\n # \"openai_compat\" | \"anthropic\" | \"azure_openai\" | \"openai_codex\" | \"github_copilot\" | \"bedrock\"\n backend: str = \"openai_compat\"\n\n # extra env vars, e.g. ((\"ZHIPUAI_API_KEY\", \"{api_key}\"),)\n env_extras: tuple[tuple[str, str], ...] = ()\n\n # gateway / local detection\n is_gateway: bool = False # routes any model (OpenRouter, AiHubMix)\n is_local: bool = False # local deployment (vLLM, Ollama)\n detect_by_key_prefix: str = \"\" # match api_key prefix, e.g. \"sk-or-\"\n detect_by_base_keyword: str = \"\" # match substring in api_base URL\n default_api_base: str = \"\" # OpenAI-compatible base URL for this provider\n\n # gateway behavior\n strip_model_prefix: bool = False # strip \"provider/\" before sending to gateway\n supports_max_completion_tokens: bool = False\n\n # per-model param overrides, e.g. ((\"kimi-k2.5\", {\"temperature\": 1.0}),)\n model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()\n\n # OAuth-based providers (e.g., OpenAI Codex) don't use API keys\n is_oauth: bool = False\n\n # Direct providers skip API-key validation (user supplies everything)\n is_direct: bool = False\n\n # Provider supports cache_control on content blocks (e.g. Anthropic prompt caching)\n supports_prompt_caching: bool = False\n\n # How to inject the thinking on/off toggle into extra_body.\n # \"\" — no extra_body needed (default)\n # \"thinking_type\" — {\"thinking\": {\"type\": \"enabled\"/\"disabled\"}}\n # (DeepSeek, VolcEngine, BytePlus)\n # \"enable_thinking\" — {\"enable_thinking\": true/false} (DashScope)\n # \"reasoning_split\" — {\"reasoning_split\": true/false} (MiniMax)\n thinking_style: str = \"\"\n\n # When True, treat the \"reasoning\" response field as formal content\n # when \"content\" is empty. Only set this for providers (e.g. StepFun)\n # whose API returns the actual answer in \"reasoning\" instead of \"content\".\n reasoning_as_content: bool = False\n\n @property\n def label(self) -> str:\n return self.display_name or self.name.title()\n\n\n# ---------------------------------------------------------------------------\n# PROVIDERS — the registry. Order = priority. Copy any entry as template.\n# ---------------------------------------------------------------------------\n\nPROVIDERS: tuple[ProviderSpec, ...] = (\n # === Custom (direct OpenAI-compatible endpoint) ========================\n ProviderSpec(\n name=\"custom\",\n keywords=(),\n env_key=\"\",\n display_name=\"Custom\",\n backend=\"openai_compat\",\n is_direct=True,\n ),\n\n # === Azure OpenAI (direct API calls with API version 2024-10-21) =====\n ProviderSpec(\n name=\"azure_openai\",\n keywords=(\"azure\", \"azure-openai\"),\n env_key=\"\",\n display_name=\"Azure OpenAI\",\n backend=\"azure_openai\",\n is_direct=True,\n ),\n # === AWS Bedrock (native Converse API via bedrock-runtime) =============\n ProviderSpec(\n name=\"bedrock\",\n keywords=(\n \"bedrock\",\n \"anthropic.claude\",\n \"amazon.nova\",\n \"meta.\",\n \"mistral.\",\n \"cohere.\",\n \"qwen.\",\n \"deepseek.\",\n \"openai.gpt-oss\",\n \"ai21.\",\n \"moonshot.\",\n \"writer.\",\n \"zai.\",\n ),\n env_key=\"AWS_BEARER_TOKEN_BEDROCK\",\n display_name=\"AWS Bedrock\",\n backend=\"bedrock\",\n is_direct=True,\n ),\n # === Gateways (detected by api_key / api_base, not model name) =========\n # Gateways can route any model, so they win in fallback.\n # OpenRouter: global gateway, keys start with \"sk-or-\"\n ProviderSpec(\n name=\"openrouter\",\n keywords=(\"openrouter\",),\n env_key=\"OPENROUTER_API_KEY\",\n display_name=\"OpenRouter\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_key_prefix=\"sk-or-\",\n detect_by_base_keyword=\"openrouter\",\n default_api_base=\"https://openrouter.ai/api/v1\",\n supports_prompt_caching=True,\n ),\n # Hugging Face Inference Providers: OpenAI-compatible router for chat models.\n ProviderSpec(\n name=\"huggingface\",\n keywords=(\"huggingface\", \"hugging-face\"),\n env_key=\"HF_TOKEN\",\n display_name=\"Hugging Face\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_key_prefix=\"hf_\",\n detect_by_base_keyword=\"huggingface\",\n default_api_base=\"https://router.huggingface.co/v1\",\n ),\n # AiHubMix: global gateway, OpenAI-compatible interface.\n # strip_model_prefix=True: doesn't understand \"anthropic/claude-3\",\n # strips to bare \"claude-3\".\n ProviderSpec(\n name=\"aihubmix\",\n keywords=(\"aihubmix\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"AiHubMix\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"aihubmix\",\n default_api_base=\"https://aihubmix.com/v1\",\n strip_model_prefix=True,\n ),\n # SiliconFlow (硅基流动): OpenAI-compatible gateway, model names keep org prefix\n ProviderSpec(\n name=\"siliconflow\",\n keywords=(\"siliconflow\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"SiliconFlow\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"siliconflow\",\n default_api_base=\"https://api.siliconflow.cn/v1\",\n ),\n\n # VolcEngine (火山引擎): OpenAI-compatible gateway, pay-per-use models\n ProviderSpec(\n name=\"volcengine\",\n keywords=(\"volcengine\", \"volces\", \"ark\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"VolcEngine\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"volces\",\n default_api_base=\"https://ark.cn-beijing.volces.com/api/v3\",\n thinking_style=\"thinking_type\",\n supports_max_completion_tokens=True,\n ),\n\n # VolcEngine Coding Plan (火山引擎 Coding Plan): same key as volcengine\n ProviderSpec(\n name=\"volcengine_coding_plan\",\n keywords=(\"volcengine-plan\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"VolcEngine Coding Plan\",\n backend=\"openai_compat\",\n is_gateway=True,\n default_api_base=\"https://ark.cn-beijing.volces.com/api/coding/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n supports_max_completion_tokens=True,\n ),\n\n # BytePlus: VolcEngine international, pay-per-use models\n ProviderSpec(\n name=\"byteplus\",\n keywords=(\"byteplus\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"BytePlus\",\n backend=\"openai_compat\",\n is_gateway=True,\n detect_by_base_keyword=\"bytepluses\",\n default_api_base=\"https://ark.ap-southeast.bytepluses.com/api/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n ),\n\n # BytePlus Coding Plan: same key as byteplus\n ProviderSpec(\n name=\"byteplus_coding_plan\",\n keywords=(\"byteplus-plan\",),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"BytePlus Coding Plan\",\n backend=\"openai_compat\",\n is_gateway=True,\n default_api_base=\"https://ark.ap-southeast.bytepluses.com/api/coding/v3\",\n strip_model_prefix=True,\n thinking_style=\"thinking_type\",\n ),\n\n\n # === Standard providers (matched by model-name keywords) ===============\n # Anthropic: native Anthropic SDK\n ProviderSpec(\n name=\"anthropic\",\n keywords=(\"anthropic\", \"claude\"),\n env_key=\"ANTHROPIC_API_KEY\",\n display_name=\"Anthropic\",\n backend=\"anthropic\",\n supports_prompt_caching=True,\n ),\n # OpenAI: SDK default base URL (no override needed)\n ProviderSpec(\n name=\"openai\",\n keywords=(\"openai\", \"gpt\"),\n env_key=\"OPENAI_API_KEY\",\n display_name=\"OpenAI\",\n backend=\"openai_compat\",\n supports_max_completion_tokens=True,\n ),\n # OpenAI Codex: OAuth-based, dedicated provider\n ProviderSpec(\n name=\"openai_codex\",\n keywords=(\"openai-codex\",),\n env_key=\"\",\n display_name=\"OpenAI Codex\",\n backend=\"openai_codex\",\n detect_by_base_keyword=\"codex\",\n default_api_base=\"https://chatgpt.com/backend-api\",\n is_oauth=True,\n ),\n # GitHub Copilot: OAuth-based\n ProviderSpec(\n name=\"github_copilot\",\n keywords=(\"github_copilot\", \"copilot\"),\n env_key=\"\",\n display_name=\"Github Copilot\",\n backend=\"github_copilot\",\n default_api_base=\"https://api.githubcopilot.com\",\n strip_model_prefix=True,\n is_oauth=True,\n supports_max_completion_tokens=True,\n ),\n # DeepSeek: OpenAI-compatible at api.deepseek.com\n ProviderSpec(\n name=\"deepseek\",\n keywords=(\"deepseek\",),\n env_key=\"DEEPSEEK_API_KEY\",\n display_name=\"DeepSeek\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.deepseek.com\",\n thinking_style=\"thinking_type\",\n ),\n # Gemini: Google's OpenAI-compatible endpoint\n ProviderSpec(\n name=\"gemini\",\n keywords=(\"gemini\", \"gemma\"),\n env_key=\"GEMINI_API_KEY\",\n display_name=\"Gemini\",\n backend=\"openai_compat\",\n default_api_base=\"https://generativelanguage.googleapis.com/v1beta/openai/\",\n ),\n # Zhipu (智谱): OpenAI-compatible at open.bigmodel.cn\n ProviderSpec(\n name=\"zhipu\",\n keywords=(\"zhipu\", \"glm\", \"zai\"),\n env_key=\"ZAI_API_KEY\",\n display_name=\"Zhipu AI\",\n backend=\"openai_compat\",\n env_extras=((\"ZHIPUAI_API_KEY\", \"{api_key}\"),),\n default_api_base=\"https://open.bigmodel.cn/api/paas/v4\",\n ),\n # DashScope (通义): Qwen models, OpenAI-compatible endpoint\n ProviderSpec(\n name=\"dashscope\",\n keywords=(\"qwen\", \"dashscope\"),\n env_key=\"DASHSCOPE_API_KEY\",\n display_name=\"DashScope\",\n backend=\"openai_compat\",\n default_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n thinking_style=\"enable_thinking\",\n ),\n # Moonshot (月之暗面): Kimi K2.5 / K2.6 enforce temperature >= 1.0.\n ProviderSpec(\n name=\"moonshot\",\n keywords=(\"moonshot\", \"kimi\"),\n env_key=\"MOONSHOT_API_KEY\",\n display_name=\"Moonshot\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.moonshot.ai/v1\",\n model_overrides=(\n (\"kimi-k2.5\", {\"temperature\": 1.0}),\n (\"kimi-k2.6\", {\"temperature\": 1.0}),\n ),\n ),\n # MiniMax: OpenAI-compatible API\n ProviderSpec(\n name=\"minimax\",\n keywords=(\"minimax\",),\n env_key=\"MINIMAX_API_KEY\",\n display_name=\"MiniMax\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.minimax.io/v1\",\n thinking_style=\"reasoning_split\",\n ),\n # MiniMax Anthropic-compatible endpoint: supports thinking mode\n ProviderSpec(\n name=\"minimax_anthropic\",\n keywords=(\"minimax_anthropic\",),\n env_key=\"MINIMAX_API_KEY\",\n display_name=\"MiniMax (Anthropic)\",\n backend=\"anthropic\",\n default_api_base=\"https://api.minimax.io/anthropic\",\n ),\n # Mistral AI: OpenAI-compatible API\n ProviderSpec(\n name=\"mistral\",\n keywords=(\"mistral\",),\n env_key=\"MISTRAL_API_KEY\",\n display_name=\"Mistral\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.mistral.ai/v1\",\n ),\n # Step Fun (阶跃星辰): OpenAI-compatible API\n ProviderSpec(\n name=\"stepfun\",\n keywords=(\"stepfun\", \"step\"),\n env_key=\"STEPFUN_API_KEY\",\n display_name=\"Step Fun\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.stepfun.com/v1\",\n reasoning_as_content=True,\n ),\n # Xiaomi MIMO (小米): OpenAI-compatible API\n # Hosted API (api.xiaomimimo.com) accepts {\"thinking\": {\"type\": \"enabled\"|\"disabled\"}}\n # to toggle reasoning, matching the existing thinking_type style.\n ProviderSpec(\n name=\"xiaomi_mimo\",\n keywords=(\"xiaomi_mimo\", \"mimo\"),\n env_key=\"XIAOMIMIMO_API_KEY\",\n display_name=\"Xiaomi MIMO\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.xiaomimimo.com/v1\",\n thinking_style=\"thinking_type\",\n ),\n # LongCat: OpenAI-compatible API\n ProviderSpec(\n name=\"longcat\",\n keywords=(\"longcat\",),\n env_key=\"LONGCAT_API_KEY\",\n display_name=\"LongCat\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.longcat.chat/openai/v1\",\n ),\n # === Local deployment (matched by config key, NOT by api_base) =========\n # vLLM / any OpenAI-compatible local server\n ProviderSpec(\n name=\"vllm\",\n keywords=(\"vllm\",),\n env_key=\"HOSTED_VLLM_API_KEY\",\n display_name=\"vLLM/Local\",\n backend=\"openai_compat\",\n is_local=True,\n ),\n # Ollama (local, OpenAI-compatible)\n ProviderSpec(\n name=\"ollama\",\n keywords=(\"ollama\", \"nemotron\"),\n env_key=\"OLLAMA_API_KEY\",\n display_name=\"Ollama\",\n backend=\"openai_compat\",\n is_local=True,\n detect_by_base_keyword=\"11434\",\n default_api_base=\"http://localhost:11434/v1\",\n ),\n # LM Studio (local, OpenAI-compatible)\n ProviderSpec(\n name=\"lm_studio\",\n keywords=(\"lm-studio\", \"lmstudio\", \"lm_studio\"),\n env_key=\"LM_STUDIO_API_KEY\",\n display_name=\"LM Studio\",\n backend=\"openai_compat\",\n is_local=True,\n detect_by_base_keyword=\"1234\",\n default_api_base=\"http://localhost:1234/v1\",\n ),\n # === OpenVINO Model Server (direct, local, OpenAI-compatible at /v3) ===\n ProviderSpec(\n name=\"ovms\",\n keywords=(\"openvino\", \"ovms\"),\n env_key=\"\",\n display_name=\"OpenVINO Model Server\",\n backend=\"openai_compat\",\n is_direct=True,\n is_local=True,\n default_api_base=\"http://localhost:8000/v3\",\n ),\n # === NVIDIA NIM (NVIDIA Inference Microservices) =======================\n # Keys start with \"nvapi-\", base URL at integrate.api.nvidia.com\n ProviderSpec(\n name=\"nvidia\",\n keywords=(\"nvidia\", \"nemotron\", \"nvapi\"),\n env_key=\"NVIDIA_NIM_API_KEY\",\n display_name=\"NVIDIA NIM\",\n backend=\"openai_compat\",\n is_gateway=False,\n detect_by_key_prefix=\"nvapi-\",\n detect_by_base_keyword=\"nvidia.com\",\n default_api_base=\"https://integrate.api.nvidia.com/v1\",\n ),\n # === Auxiliary (not a primary LLM provider) ============================\n # Groq: mainly used for Whisper voice transcription, also usable for LLM\n ProviderSpec(\n name=\"groq\",\n keywords=(\"groq\",),\n env_key=\"GROQ_API_KEY\",\n display_name=\"Groq\",\n backend=\"openai_compat\",\n default_api_base=\"https://api.groq.com/openai/v1\",\n ),\n # Qianfan (百度千帆): OpenAI-compatible API\n ProviderSpec(\n name=\"qianfan\",\n keywords=(\"qianfan\", \"ernie\"),\n env_key=\"QIANFAN_API_KEY\",\n display_name=\"Qianfan\",\n backend=\"openai_compat\",\n default_api_base=\"https://qianfan.baidubce.com/v2\"\n ),\n)\n\n\n# ---------------------------------------------------------------------------\n# Lookup helpers\n# ---------------------------------------------------------------------------\n\n\ndef find_by_name(name: str) -> ProviderSpec | None:\n \"\"\"Find a provider spec by config field name, e.g. \"dashscope\".\"\"\"\n normalized = to_snake(name.replace(\"-\", \"_\"))\n for spec in PROVIDERS:\n if spec.name == normalized:\n return spec\n return None\n"}}}}