feat(providers): add kimi-coding-cn provider for mainland China users

Cherry-picked from PR #7637 by hcshen0111.
Adds kimi-coding-cn provider with dedicated KIMI_CN_API_KEY env var
and api.moonshot.cn/v1 endpoint for China-region Moonshot users.
This commit is contained in:
hcshen0111 2026-04-13 11:13:09 -07:00 committed by Teknium
parent ef180880aa
commit 2b3aa36242
10 changed files with 43 additions and 7 deletions

View file

@ -43,6 +43,7 @@
# KIMI_BASE_URL=https://api.kimi.com/coding/v1 # Default for sk-kimi- keys # KIMI_BASE_URL=https://api.kimi.com/coding/v1 # Default for sk-kimi- keys
# KIMI_BASE_URL=https://api.moonshot.ai/v1 # For legacy Moonshot keys # KIMI_BASE_URL=https://api.moonshot.ai/v1 # For legacy Moonshot keys
# KIMI_BASE_URL=https://api.moonshot.cn/v1 # For Moonshot China keys # KIMI_BASE_URL=https://api.moonshot.cn/v1 # For Moonshot China keys
# KIMI_CN_API_KEY= # Dedicated Moonshot China key
# ============================================================================= # =============================================================================
# LLM PROVIDER (MiniMax) # LLM PROVIDER (MiniMax)

View file

@ -64,6 +64,8 @@ _PROVIDER_ALIASES = {
"zhipu": "zai", "zhipu": "zai",
"kimi": "kimi-coding", "kimi": "kimi-coding",
"moonshot": "kimi-coding", "moonshot": "kimi-coding",
"kimi-cn": "kimi-coding-cn",
"moonshot-cn": "kimi-coding-cn",
"minimax-china": "minimax-cn", "minimax-china": "minimax-cn",
"minimax_cn": "minimax-cn", "minimax_cn": "minimax-cn",
"claude": "anthropic", "claude": "anthropic",
@ -94,6 +96,7 @@ _API_KEY_PROVIDER_AUX_MODELS: Dict[str, str] = {
"gemini": "gemini-3-flash-preview", "gemini": "gemini-3-flash-preview",
"zai": "glm-4.5-flash", "zai": "glm-4.5-flash",
"kimi-coding": "kimi-k2-turbo-preview", "kimi-coding": "kimi-k2-turbo-preview",
"kimi-coding-cn": "kimi-k2-turbo-preview",
"minimax": "MiniMax-M2.7", "minimax": "MiniMax-M2.7",
"minimax-cn": "MiniMax-M2.7", "minimax-cn": "MiniMax-M2.7",
"anthropic": "claude-haiku-4-5-20251001", "anthropic": "claude-haiku-4-5-20251001",

View file

@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
# are preserved so the full model name reaches cache lookups and server queries. # are preserved so the full model name reaches cache lookups and server queries.
_PROVIDER_PREFIXES: frozenset[str] = frozenset({ _PROVIDER_PREFIXES: frozenset[str] = frozenset({
"openrouter", "nous", "openai-codex", "copilot", "copilot-acp", "openrouter", "nous", "openai-codex", "copilot", "copilot-acp",
"gemini", "zai", "kimi-coding", "minimax", "minimax-cn", "anthropic", "deepseek", "gemini", "zai", "kimi-coding", "kimi-coding-cn", "minimax", "minimax-cn", "anthropic", "deepseek",
"opencode-zen", "opencode-go", "ai-gateway", "kilocode", "alibaba", "opencode-zen", "opencode-go", "ai-gateway", "kilocode", "alibaba",
"qwen-oauth", "qwen-oauth",
"xiaomi", "xiaomi",
@ -32,7 +32,7 @@ _PROVIDER_PREFIXES: frozenset[str] = frozenset({
# Common aliases # Common aliases
"google", "google-gemini", "google-ai-studio", "google", "google-gemini", "google-ai-studio",
"glm", "z-ai", "z.ai", "zhipu", "github", "github-copilot", "glm", "z-ai", "z.ai", "zhipu", "github", "github-copilot",
"github-models", "kimi", "moonshot", "claude", "deep-seek", "github-models", "kimi", "moonshot", "kimi-cn", "moonshot-cn", "claude", "deep-seek",
"opencode", "zen", "go", "vercel", "kilo", "dashscope", "aliyun", "qwen", "opencode", "zen", "go", "vercel", "kilo", "dashscope", "aliyun", "qwen",
"mimo", "xiaomi-mimo", "mimo", "xiaomi-mimo",
"qwen-portal", "qwen-portal",
@ -211,6 +211,7 @@ _URL_TO_PROVIDER: Dict[str, str] = {
"api.anthropic.com": "anthropic", "api.anthropic.com": "anthropic",
"api.z.ai": "zai", "api.z.ai": "zai",
"api.moonshot.ai": "kimi-coding", "api.moonshot.ai": "kimi-coding",
"api.moonshot.cn": "kimi-coding-cn",
"api.kimi.com": "kimi-coding", "api.kimi.com": "kimi-coding",
"api.minimax": "minimax", "api.minimax": "minimax",
"dashscope.aliyuncs.com": "alibaba", "dashscope.aliyuncs.com": "alibaba",

View file

@ -160,6 +160,13 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = {
api_key_env_vars=("KIMI_API_KEY",), api_key_env_vars=("KIMI_API_KEY",),
base_url_env_var="KIMI_BASE_URL", base_url_env_var="KIMI_BASE_URL",
), ),
"kimi-coding-cn": ProviderConfig(
id="kimi-coding-cn",
name="Kimi / Moonshot (China)",
auth_type="api_key",
inference_base_url="https://api.moonshot.cn/v1",
api_key_env_vars=("KIMI_CN_API_KEY",),
),
"minimax": ProviderConfig( "minimax": ProviderConfig(
id="minimax", id="minimax",
name="MiniMax", name="MiniMax",
@ -892,6 +899,7 @@ def resolve_provider(
"glm": "zai", "z-ai": "zai", "z.ai": "zai", "zhipu": "zai", "glm": "zai", "z-ai": "zai", "z.ai": "zai", "zhipu": "zai",
"google": "gemini", "google-gemini": "gemini", "google-ai-studio": "gemini", "google": "gemini", "google-gemini": "gemini", "google-ai-studio": "gemini",
"kimi": "kimi-coding", "kimi-for-coding": "kimi-coding", "moonshot": "kimi-coding", "kimi": "kimi-coding", "kimi-for-coding": "kimi-coding", "moonshot": "kimi-coding",
"kimi-cn": "kimi-coding-cn", "moonshot-cn": "kimi-coding-cn",
"minimax-china": "minimax-cn", "minimax_cn": "minimax-cn", "minimax-china": "minimax-cn", "minimax_cn": "minimax-cn",
"claude": "anthropic", "claude-code": "anthropic", "claude": "anthropic", "claude-code": "anthropic",
"github": "copilot", "github-copilot": "copilot", "github": "copilot", "github-copilot": "copilot",

View file

@ -816,6 +816,14 @@ OPTIONAL_ENV_VARS = {
"category": "provider", "category": "provider",
"advanced": True, "advanced": True,
}, },
"KIMI_CN_API_KEY": {
"description": "Kimi / Moonshot China API key",
"prompt": "Kimi (China) API key",
"url": "https://platform.moonshot.cn/",
"password": True,
"category": "provider",
"advanced": True,
},
"MINIMAX_API_KEY": { "MINIMAX_API_KEY": {
"description": "MiniMax API key (international)", "description": "MiniMax API key (international)",
"prompt": "MiniMax API key", "prompt": "MiniMax API key",
@ -2452,6 +2460,7 @@ _FALLBACK_COMMENT = """
# nous (OAuth — hermes auth) — Nous Portal # nous (OAuth — hermes auth) — Nous Portal
# zai (ZAI_API_KEY) — Z.AI / GLM # zai (ZAI_API_KEY) — Z.AI / GLM
# kimi-coding (KIMI_API_KEY) — Kimi / Moonshot # kimi-coding (KIMI_API_KEY) — Kimi / Moonshot
# kimi-coding-cn (KIMI_CN_API_KEY) — Kimi / Moonshot (China)
# minimax (MINIMAX_API_KEY) — MiniMax # minimax (MINIMAX_API_KEY) — MiniMax
# minimax-cn (MINIMAX_CN_API_KEY) — MiniMax (China) # minimax-cn (MINIMAX_CN_API_KEY) — MiniMax (China)
# #
@ -2495,6 +2504,7 @@ _COMMENTED_SECTIONS = """
# nous (OAuth — hermes auth) — Nous Portal # nous (OAuth — hermes auth) — Nous Portal
# zai (ZAI_API_KEY) — Z.AI / GLM # zai (ZAI_API_KEY) — Z.AI / GLM
# kimi-coding (KIMI_API_KEY) — Kimi / Moonshot # kimi-coding (KIMI_API_KEY) — Kimi / Moonshot
# kimi-coding-cn (KIMI_CN_API_KEY) — Kimi / Moonshot (China)
# minimax (MINIMAX_API_KEY) — MiniMax # minimax (MINIMAX_API_KEY) — MiniMax
# minimax-cn (MINIMAX_CN_API_KEY) — MiniMax (China) # minimax-cn (MINIMAX_CN_API_KEY) — MiniMax (China)
# #

View file

@ -1045,6 +1045,7 @@ def select_provider_and_model(args=None):
"gemini": "Google AI Studio", "gemini": "Google AI Studio",
"zai": "Z.AI / GLM", "zai": "Z.AI / GLM",
"kimi-coding": "Kimi / Moonshot", "kimi-coding": "Kimi / Moonshot",
"kimi-coding-cn": "Kimi / Moonshot (China)",
"minimax": "MiniMax", "minimax": "MiniMax",
"minimax-cn": "MiniMax (China)", "minimax-cn": "MiniMax (China)",
"opencode-zen": "OpenCode Zen", "opencode-zen": "OpenCode Zen",
@ -1079,6 +1080,7 @@ def select_provider_and_model(args=None):
("gemini", "Google AI Studio (Gemini models — OpenAI-compatible endpoint)"), ("gemini", "Google AI Studio (Gemini models — OpenAI-compatible endpoint)"),
("zai", "Z.AI / GLM (Zhipu AI direct API)"), ("zai", "Z.AI / GLM (Zhipu AI direct API)"),
("kimi-coding", "Kimi / Moonshot (Moonshot AI direct API)"), ("kimi-coding", "Kimi / Moonshot (Moonshot AI direct API)"),
("kimi-coding-cn", "Kimi / Moonshot China (Moonshot CN direct API)"),
("minimax", "MiniMax (global direct API)"), ("minimax", "MiniMax (global direct API)"),
("minimax-cn", "MiniMax China (domestic direct API)"), ("minimax-cn", "MiniMax China (domestic direct API)"),
("kilocode", "Kilo Code (Kilo Gateway API)"), ("kilocode", "Kilo Code (Kilo Gateway API)"),
@ -1205,7 +1207,7 @@ def select_provider_and_model(args=None):
_model_flow_anthropic(config, current_model) _model_flow_anthropic(config, current_model)
elif selected_provider == "kimi-coding": elif selected_provider == "kimi-coding":
_model_flow_kimi(config, current_model) _model_flow_kimi(config, current_model)
elif selected_provider in ("gemini", "zai", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba", "huggingface", "xiaomi"): elif selected_provider in ("gemini", "zai", "kimi-coding-cn", "minimax", "minimax-cn", "kilocode", "opencode-zen", "opencode-go", "ai-gateway", "alibaba", "huggingface", "xiaomi"):
_model_flow_api_key_provider(config, selected_provider, current_model) _model_flow_api_key_provider(config, selected_provider, current_model)
# ── Post-switch cleanup: clear stale OPENAI_BASE_URL ────────────── # ── Post-switch cleanup: clear stale OPENAI_BASE_URL ──────────────
@ -4626,7 +4628,7 @@ For more help on a command:
) )
chat_parser.add_argument( chat_parser.add_argument(
"--provider", "--provider",
choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "xiaomi"], choices=["auto", "openrouter", "nous", "openai-codex", "copilot-acp", "copilot", "anthropic", "gemini", "huggingface", "zai", "kimi-coding", "kimi-coding-cn", "minimax", "minimax-cn", "kilocode", "xiaomi"],
default=None, default=None,
help="Inference provider (default: auto)" help="Inference provider (default: auto)"
) )

View file

@ -158,6 +158,12 @@ _PROVIDER_MODELS: dict[str, list[str]] = {
"kimi-k2-turbo-preview", "kimi-k2-turbo-preview",
"kimi-k2-0905-preview", "kimi-k2-0905-preview",
], ],
"kimi-coding-cn": [
"kimi-k2.5",
"kimi-k2-thinking",
"kimi-k2-turbo-preview",
"kimi-k2-0905-preview",
],
"moonshot": [ "moonshot": [
"kimi-k2.5", "kimi-k2.5",
"kimi-k2-thinking", "kimi-k2-thinking",
@ -488,6 +494,7 @@ _PROVIDER_LABELS = {
"gemini": "Google AI Studio", "gemini": "Google AI Studio",
"zai": "Z.AI / GLM", "zai": "Z.AI / GLM",
"kimi-coding": "Kimi / Moonshot", "kimi-coding": "Kimi / Moonshot",
"kimi-coding-cn": "Kimi / Moonshot (China)",
"minimax": "MiniMax", "minimax": "MiniMax",
"minimax-cn": "MiniMax (China)", "minimax-cn": "MiniMax (China)",
"anthropic": "Anthropic", "anthropic": "Anthropic",
@ -519,6 +526,8 @@ _PROVIDER_ALIASES = {
"google-ai-studio": "gemini", "google-ai-studio": "gemini",
"kimi": "kimi-coding", "kimi": "kimi-coding",
"moonshot": "kimi-coding", "moonshot": "kimi-coding",
"kimi-cn": "kimi-coding-cn",
"moonshot-cn": "kimi-coding-cn",
"minimax-china": "minimax-cn", "minimax-china": "minimax-cn",
"minimax_cn": "minimax-cn", "minimax_cn": "minimax-cn",
"claude": "anthropic", "claude": "anthropic",
@ -841,7 +850,7 @@ def list_available_providers() -> list[dict[str, str]]:
_PROVIDER_ORDER = [ _PROVIDER_ORDER = [
"openrouter", "nous", "openai-codex", "copilot", "copilot-acp", "openrouter", "nous", "openai-codex", "copilot", "copilot-acp",
"gemini", "huggingface", "gemini", "huggingface",
"zai", "kimi-coding", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba", "zai", "kimi-coding", "kimi-coding-cn", "minimax", "minimax-cn", "kilocode", "anthropic", "alibaba",
"qwen-oauth", "xiaomi", "qwen-oauth", "xiaomi",
"opencode-zen", "opencode-go", "opencode-zen", "opencode-go",
"ai-gateway", "deepseek", "custom", "ai-gateway", "deepseek", "custom",

View file

@ -51,6 +51,7 @@ hermes setup # Or configure everything at once
| **OpenRouter** | Multi-provider routing across many models | Enter your API key | | **OpenRouter** | Multi-provider routing across many models | Enter your API key |
| **Z.AI** | GLM / Zhipu-hosted models | Set `GLM_API_KEY` / `ZAI_API_KEY` | | **Z.AI** | GLM / Zhipu-hosted models | Set `GLM_API_KEY` / `ZAI_API_KEY` |
| **Kimi / Moonshot** | Moonshot-hosted coding and chat models | Set `KIMI_API_KEY` | | **Kimi / Moonshot** | Moonshot-hosted coding and chat models | Set `KIMI_API_KEY` |
| **Kimi / Moonshot China** | China-region Moonshot endpoint | Set `KIMI_CN_API_KEY` |
| **MiniMax** | International MiniMax endpoint | Set `MINIMAX_API_KEY` | | **MiniMax** | International MiniMax endpoint | Set `MINIMAX_API_KEY` |
| **MiniMax China** | China-region MiniMax endpoint | Set `MINIMAX_CN_API_KEY` | | **MiniMax China** | China-region MiniMax endpoint | Set `MINIMAX_CN_API_KEY` |
| **Alibaba Cloud** | Qwen models via DashScope | Set `DASHSCOPE_API_KEY` | | **Alibaba Cloud** | Qwen models via DashScope | Set `DASHSCOPE_API_KEY` |

View file

@ -31,6 +31,7 @@ All variables go in `~/.hermes/.env`. You can also set them with `hermes config
| `GLM_BASE_URL` | Override z.ai base URL (default: `https://api.z.ai/api/paas/v4`) | | `GLM_BASE_URL` | Override z.ai base URL (default: `https://api.z.ai/api/paas/v4`) |
| `KIMI_API_KEY` | Kimi / Moonshot AI API key ([moonshot.ai](https://platform.moonshot.ai)) | | `KIMI_API_KEY` | Kimi / Moonshot AI API key ([moonshot.ai](https://platform.moonshot.ai)) |
| `KIMI_BASE_URL` | Override Kimi base URL (default: `https://api.moonshot.ai/v1`) | | `KIMI_BASE_URL` | Override Kimi base URL (default: `https://api.moonshot.ai/v1`) |
| `KIMI_CN_API_KEY` | Kimi / Moonshot China API key ([moonshot.cn](https://platform.moonshot.cn)) |
| `MINIMAX_API_KEY` | MiniMax API key — global endpoint ([minimax.io](https://www.minimax.io)) | | `MINIMAX_API_KEY` | MiniMax API key — global endpoint ([minimax.io](https://www.minimax.io)) |
| `MINIMAX_BASE_URL` | Override MiniMax base URL (default: `https://api.minimax.io/v1`) | | `MINIMAX_BASE_URL` | Override MiniMax base URL (default: `https://api.minimax.io/v1`) |
| `MINIMAX_CN_API_KEY` | MiniMax API key — China endpoint ([minimaxi.com](https://www.minimaxi.com)) | | `MINIMAX_CN_API_KEY` | MiniMax API key — China endpoint ([minimaxi.com](https://www.minimaxi.com)) |
@ -67,7 +68,7 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe
| Variable | Description | | Variable | Description |
|----------|-------------| |----------|-------------|
| `HERMES_INFERENCE_PROVIDER` | Override provider selection: `auto`, `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `huggingface`, `zai`, `kimi-coding`, `minimax`, `minimax-cn`, `kilocode`, `xiaomi`, `alibaba`, `deepseek`, `opencode-zen`, `opencode-go`, `ai-gateway` (default: `auto`) | | `HERMES_INFERENCE_PROVIDER` | Override provider selection: `auto`, `openrouter`, `nous`, `openai-codex`, `copilot`, `copilot-acp`, `anthropic`, `huggingface`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, `minimax-cn`, `kilocode`, `xiaomi`, `alibaba`, `deepseek`, `opencode-zen`, `opencode-go`, `ai-gateway` (default: `auto`) |
| `HERMES_PORTAL_BASE_URL` | Override Nous Portal URL (for development/testing) | | `HERMES_PORTAL_BASE_URL` | Override Nous Portal URL (for development/testing) |
| `NOUS_INFERENCE_BASE_URL` | Override Nous inference API URL | | `NOUS_INFERENCE_BASE_URL` | Override Nous inference API URL |
| `HERMES_NOUS_MIN_KEY_TTL_SECONDS` | Min agent key TTL before re-mint (default: 1800 = 30min) | | `HERMES_NOUS_MIN_KEY_TTL_SECONDS` | Min agent key TTL before re-mint (default: 1800 = 30min) |

View file

@ -601,7 +601,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. 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`, `openrouter`, `nous`, `codex`, `copilot`, `anthropic`, `main`, `zai`, `kimi-coding`, `minimax`, any provider registered in the [provider registry](/docs/reference/environment-variables), or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`). Available providers for auxiliary tasks: `auto`, `openrouter`, `nous`, `codex`, `copilot`, `anthropic`, `main`, `zai`, `kimi-coding`, `kimi-coding-cn`, `minimax`, any provider registered in the [provider registry](/docs/reference/environment-variables), or any named custom provider from your `custom_providers` list (e.g. `provider: "beans"`).
:::warning `"main"` is for auxiliary tasks only :::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. 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.