diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 32bed28e094..3c412d10724 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -47,7 +47,6 @@ from openai import OpenAI from agent.credential_pool import load_pool from hermes_cli.config import get_hermes_home -from hermes_cli.provider_contracts import PROVIDER_AUX_MODELS from hermes_constants import OPENROUTER_BASE_URL from utils import base_url_host_matches, base_url_hostname, normalize_proxy_env_vars @@ -149,8 +148,6 @@ _API_KEY_PROVIDER_AUX_MODELS: Dict[str, str] = { "opencode-go": "glm-5", "kilocode": "google/gemini-3-flash-preview", "ollama-cloud": "nemotron-3-nano:30b", - "volcengine": PROVIDER_AUX_MODELS["volcengine"], - "byteplus": PROVIDER_AUX_MODELS["byteplus"], } # Vision-specific model overrides for direct providers. diff --git a/agent/model_metadata.py b/agent/model_metadata.py index 7dd9fd4e314..c20cca3797a 100644 --- a/agent/model_metadata.py +++ b/agent/model_metadata.py @@ -14,7 +14,7 @@ from urllib.parse import urlparse import requests import yaml -from hermes_cli.provider_contracts import model_context_window +from hermes_cli.volcengine_byteplus import model_context_window from utils import base_url_host_matches, base_url_hostname from hermes_constants import OPENROUTER_MODELS_URL diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index c90dd62ec52..6f4236f6a58 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -39,7 +39,7 @@ import httpx import yaml from hermes_cli.config import get_hermes_home, get_config_path, read_raw_config -from hermes_cli.provider_contracts import ( +from hermes_cli.volcengine_byteplus import ( VOLCENGINE_PROVIDER, BYTEPLUS_PROVIDER, VOLCENGINE_STANDARD_BASE_URL, diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 81275a7f9a9..9410a2955d9 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -1281,6 +1281,20 @@ OPTIONAL_ENV_VARS = { "category": "provider", "advanced": True, }, + "VOLCENGINE_API_KEY": { + "description": "Volcengine API key for Doubao / Seed models (standard + Coding Plan catalogs)", + "prompt": "Volcengine API Key", + "url": "https://www.volcengine.com/product/ark", + "password": True, + "category": "provider", + }, + "BYTEPLUS_API_KEY": { + "description": "BytePlus API key for Seed / Dola models (standard + Coding Plan catalogs)", + "prompt": "BytePlus API Key", + "url": "https://www.byteplus.com/en/product/modelark", + "password": True, + "category": "provider", + }, "AWS_REGION": { "description": "AWS region for Bedrock API calls (e.g. us-east-1, eu-central-1)", "prompt": "AWS Region", diff --git a/hermes_cli/main.py b/hermes_cli/main.py index f1d4be64df0..c5b2bb2d569 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -2945,7 +2945,7 @@ def _model_flow_named_custom(config, provider_info): # Curated model lists for direct API-key providers — single source in models.py from hermes_cli.models import _PROVIDER_MODELS -from hermes_cli.provider_contracts import ( +from hermes_cli.volcengine_byteplus import ( base_url_for_provider_model, provider_models, ) diff --git a/hermes_cli/models.py b/hermes_cli/models.py index 528341034cb..576ecae2d04 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -22,7 +22,7 @@ from hermes_cli import __version__ as _HERMES_VERSION # Check (error 1010) don't reject the default ``Python-urllib/*`` signature. _HERMES_USER_AGENT = f"hermes-cli/{_HERMES_VERSION}" -from hermes_cli.provider_contracts import ( +from hermes_cli.volcengine_byteplus import ( BYTEPLUS_PROVIDER, VOLCENGINE_PROVIDER, provider_models, @@ -1337,36 +1337,21 @@ def _get_custom_base_url() -> str: def provider_for_base_url(base_url: str) -> Optional[str]: - """Return a known built-in provider for a configured base URL, if any.""" + """Return a known built-in provider for a configured base URL, if any. + + Uses the canonical _URL_TO_PROVIDER mapping from model_metadata plus + additional entries for providers not in that dict. + """ normalized = str(base_url or "").strip().rstrip("/") if not normalized or "openrouter.ai" in normalized.lower(): return None url_lower = normalized.lower() - host_to_provider = { - "ark.cn-beijing.volces.com": VOLCENGINE_PROVIDER, - "ark.ap-southeast.bytepluses.com": BYTEPLUS_PROVIDER, - "api.z.ai": "zai", - "api.moonshot.ai": "kimi-coding", - "api.kimi.com": "kimi-coding", - "api.minimax.io": "minimax", - "api.minimaxi.com": "minimax-cn", - "dashscope.aliyuncs.com": "alibaba", - "dashscope-intl.aliyuncs.com": "alibaba", - "portal.qwen.ai": "qwen-oauth", - "router.huggingface.co": "huggingface", - "generativelanguage.googleapis.com": "gemini", - "api.deepseek.com": "deepseek", - "api.githubcopilot.com": "copilot", - "models.github.ai": "copilot", - "opencode.ai": "opencode-go", - "api.x.ai": "xai", - "api.xiaomimimo.com": "xiaomi", - "xiaomimimo.com": "xiaomi", - "api.anthropic.com": "anthropic", - "inference-api.nousresearch.com": "nous", - } - for host, provider_id in host_to_provider.items(): + + # Primary source — shared with context-length resolution + from agent.model_metadata import _URL_TO_PROVIDER + + for host, provider_id in _URL_TO_PROVIDER.items(): if host in url_lower: canonical = normalize_provider(provider_id) if canonical in _PROVIDER_LABELS and canonical != "custom": diff --git a/hermes_cli/providers.py b/hermes_cli/providers.py index d6312766fb6..91c3893526d 100644 --- a/hermes_cli/providers.py +++ b/hermes_cli/providers.py @@ -23,7 +23,7 @@ import logging from dataclasses import dataclass from typing import Any, Dict, List, Optional, Tuple -from hermes_cli.provider_contracts import ( +from hermes_cli.volcengine_byteplus import ( BYTEPLUS_PROVIDER, BYTEPLUS_STANDARD_BASE_URL, VOLCENGINE_PROVIDER, diff --git a/hermes_cli/runtime_provider.py b/hermes_cli/runtime_provider.py index 922946e2ad0..c6fbde29d4e 100644 --- a/hermes_cli/runtime_provider.py +++ b/hermes_cli/runtime_provider.py @@ -643,7 +643,7 @@ def _resolve_explicit_runtime( base_url = explicit_base_url if not base_url: - if provider in ("kimi-coding", "kimi-coding-cn"): + if provider in ("kimi-coding", "kimi-coding-cn", "volcengine", "byteplus"): creds = resolve_api_key_provider_credentials(provider) base_url = creds.get("base_url", "").rstrip("/") else: diff --git a/hermes_cli/provider_contracts.py b/hermes_cli/volcengine_byteplus.py similarity index 96% rename from hermes_cli/provider_contracts.py rename to hermes_cli/volcengine_byteplus.py index 189ac582685..fa91c08d024 100644 --- a/hermes_cli/provider_contracts.py +++ b/hermes_cli/volcengine_byteplus.py @@ -68,11 +68,6 @@ PROVIDER_MODEL_CATALOGS: Dict[str, Tuple[str, ...]] = { BYTEPLUS_PROVIDER: BYTEPLUS_STANDARD_MODEL_REFS + BYTEPLUS_CODING_PLAN_MODEL_REFS, } -PROVIDER_AUX_MODELS: Dict[str, str] = { - VOLCENGINE_PROVIDER: "volcengine/doubao-seed-2-0-lite-260215", - BYTEPLUS_PROVIDER: "byteplus/seed-2-0-lite-260228", -} - MODEL_CONTEXT_WINDOWS: Dict[str, int] = { "doubao-seed-2-0-pro-260215": 256000, "doubao-seed-2-0-lite-260215": 256000, diff --git a/scripts/release.py b/scripts/release.py index 8d213ea070d..3e9d581cb2d 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -105,6 +105,7 @@ AUTHOR_MAP = { "134848055+UNLINEARITY@users.noreply.github.com": "UNLINEARITY", "ben.burtenshaw@gmail.com": "burtenshaw", "roopaknijhara@gmail.com": "rnijhara", + "Maaannnn@users.noreply.github.com": "Maaannnn", # contributors (manual mapping from git names) "ahmedsherif95@gmail.com": "asheriif", "liujinkun@bytedance.com": "liujinkun2025", diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index 9a13d307f54..70db17a8bb2 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -805,11 +805,12 @@ class TestModelDefaultElimination: assert isinstance(model, str) and model.strip(), \ f"{provider_id} should have a non-empty model string" - def test_contract_providers_have_aux_models(self): + def test_volcengine_byteplus_use_main_model_first(self): + """Volcengine/BytePlus use main-model-first — no entry in _API_KEY_PROVIDER_AUX_MODELS.""" from agent.auxiliary_client import _API_KEY_PROVIDER_AUX_MODELS - assert _API_KEY_PROVIDER_AUX_MODELS["volcengine"] == "volcengine/doubao-seed-2-0-lite-260215" - assert _API_KEY_PROVIDER_AUX_MODELS["byteplus"] == "byteplus/seed-2-0-lite-260228" + assert "volcengine" not in _API_KEY_PROVIDER_AUX_MODELS + assert "byteplus" not in _API_KEY_PROVIDER_AUX_MODELS class TestContractProviderAliases: diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 886db482c42..4fcd4edf49b 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -44,6 +44,8 @@ All variables go in `~/.hermes/.env`. You can also set them with `hermes config | `KILOCODE_BASE_URL` | Override Kilo Code base URL (default: `https://api.kilo.ai/api/gateway`) | | `XIAOMI_API_KEY` | Xiaomi MiMo API key ([platform.xiaomimimo.com](https://platform.xiaomimimo.com)) | | `XIAOMI_BASE_URL` | Override Xiaomi MiMo base URL (default: `https://api.xiaomimimo.com/v1`) | +| `VOLCENGINE_API_KEY` | Volcengine API key for Doubao / Seed models ([volcengine.com/product/ark](https://www.volcengine.com/product/ark)) | +| `BYTEPLUS_API_KEY` | BytePlus API key for Seed / Dola models ([byteplus.com/en/product/modelark](https://www.byteplus.com/en/product/modelark)) | | `HF_TOKEN` | Hugging Face token for Inference Providers ([huggingface.co/settings/tokens](https://huggingface.co/settings/tokens)) | | `HF_BASE_URL` | Override Hugging Face base URL (default: `https://router.huggingface.co/v1`) | | `GOOGLE_API_KEY` | Google AI Studio API key ([aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)) | diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index 1c491a48ce2..6fc86d5b21a 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -628,7 +628,7 @@ Every model slot in Hermes — auxiliary tasks, compression, fallback — uses t When `base_url` is set, Hermes ignores the provider and calls that endpoint directly (using `api_key` or `OPENAI_API_KEY` for auth). When only `provider` is set, Hermes uses that provider's built-in auth and base URL. -Available providers for auxiliary tasks: `auto`, `main`, plus any provider in the [provider registry](/docs/reference/environment-variables) — `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `gemini`, `google-gemini-cli`, `qwen-oauth`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, `minimax-cn`, `deepseek`, `nvidia`, `xai`, `ollama-cloud`, `alibaba`, `bedrock`, `huggingface`, `arcee`, `xiaomi`, `kilocode`, `opencode-zen`, `opencode-go`, `ai-gateway` — or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`). +Available providers for auxiliary tasks: `auto`, `main`, plus any provider in the [provider registry](/docs/reference/environment-variables) — `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `gemini`, `google-gemini-cli`, `qwen-oauth`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, `minimax-cn`, `deepseek`, `nvidia`, `xai`, `ollama-cloud`, `alibaba`, `bedrock`, `huggingface`, `arcee`, `xiaomi`, `volcengine`, `byteplus`, `kilocode`, `opencode-zen`, `opencode-go`, `ai-gateway` — or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`). :::warning `"main"` is for auxiliary tasks only The `"main"` provider option means "use whatever provider my main agent uses" — it's only valid inside `auxiliary:`, `compression:`, and `fallback_model:` configs. It is **not** a valid value for your top-level `model.provider` setting. If you use a custom OpenAI-compatible endpoint, set `provider: custom` in your `model:` section. See [AI Providers](/docs/integrations/providers) for all main model provider options. diff --git a/website/docs/user-guide/features/fallback-providers.md b/website/docs/user-guide/features/fallback-providers.md index de89acc7113..654c64af92e 100644 --- a/website/docs/user-guide/features/fallback-providers.md +++ b/website/docs/user-guide/features/fallback-providers.md @@ -58,6 +58,8 @@ Both `provider` and `model` are **required**. If either is missing, the fallback | OpenCode Go | `opencode-go` | `OPENCODE_GO_API_KEY` | | Kilo Code | `kilocode` | `KILOCODE_API_KEY` | | Xiaomi MiMo | `xiaomi` | `XIAOMI_API_KEY` | +| Volcengine | `volcengine` | `VOLCENGINE_API_KEY` | +| BytePlus | `byteplus` | `BYTEPLUS_API_KEY` | | Arcee AI | `arcee` | `ARCEEAI_API_KEY` | | Alibaba / DashScope | `alibaba` | `DASHSCOPE_API_KEY` | | Hugging Face | `huggingface` | `HF_TOKEN` |