# NousResearch/hermes-agent · ZAI and Kimi provider endpoint routing fixes

## Open now

- 4 issues, 8 PRs open on GitHub.
- #5908: kimi-coding credential pool base_url not re-resolved from key prefix on load
- #18302: \[Bug\]: ZAI/GLM provider base_url causes 429/404 errors — config ignored by 5 code paths
- #42536: \[bug\] Z.AI/GLM provider endpoint probe order causes Coding Plan users to be charged against wallet balance instead of subscription quota
- #55112: \[Bug\]: hardcoded zai vision base_urls (paas/v4) bypass the coding-plan endpoint → silent metered billing
- PR #13993: fix(providers): add moonshotai provider, disambiguate from kimi-for-coding
- PR #18694: fix(auth): re-resolve Kimi pool base URLs
- PR #37816: Respect configured ZAI base URL
- PR #42547: fix(zai): probe Coding Plan endpoints before generic to prevent silent wallet drain
- PR #55116: fix(auxiliary): prepend zai coding-plan vision endpoint before metered paas/v4 (fixes #55112)
- PR #55156: fix(auxiliary): try zai coding-plan vision endpoint before metered fallbacks (#55112)
- PR #83537: fix(auth): re-resolve stale kimi pool base_url on load (#5908)
- PR #114111: fix(auth): route Kimi Code credentials by key

These issues and PRs address incorrect endpoint selection, ignored base URL configuration, and wrong billing paths for the ZAI/GLM and Kimi/Kimi-Coding providers across authentication, credential pools, and vision auxiliary clients. A recurring theme is hardcoded or incorrectly ordered endpoint probing causing users to be charged against metered quotas instead of subscription/coding-plan quotas.

## TL;DR

- 31 member(s): 4 open issue(s), 8 closed issue(s), 8 open PR(s), 1 merged PR(s), 10 PR(s) closed without merging.
- A fix was merged to main in PR #9907. Verify it addresses your case.
- **Recommended:** read PR #9907 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-9907`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.05`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Open related issues

- #5908: kimi-coding credential pool base_url not re-resolved from key prefix on load
- #18302: \[Bug\]: ZAI/GLM provider base_url causes 429/404 errors — config ignored by 5 code paths
- #42536: \[bug\] Z.AI/GLM provider endpoint probe order causes Coding Plan users to be charged against wallet balance instead of subscription quota
- #55112: \[Bug\]: hardcoded zai vision base_urls (paas/v4) bypass the coding-plan endpoint → silent metered billing

## Closed / duplicate issues

- #966: \[Feature\]: Add Kimi Coding as a direct inference provider
- #5561: kimi-coding credential pool seeds wrong base_url for sk-kimi- keys, causing 401 on first request
- #9888: \[Feature\]: Glm-5v-turbo for GLM Coding Plan
- #13935: \[Bug/UX\]: kimi-coding provider misleading — points to kimi.com/coding not platform.moonshot.ai
- #43617: \[Bug\]:   kimi-coding provider uses wrong endpoint + User-Agent for sk-kimi-* keys, breaking all API calls including vision
- #58071: \[Bug\] zai provider ignores config.yaml base_url and always probes endpoints in hardcoded order — standard endpoint always wins over coding endpoint
- #61563: zai manual-pool credentials always routed to metered /api/paas/v4 instead of /api/coding/paas/v4 (hermes auth add cannot override base_url)
- #68239: hermes auth add stamps registry-default base_url for kimi-coding — sk-kimi- keys paired with api.moonshot.ai/v1, instant 401

## Prior & competing PRs

- **#967: feat(providers): add Kimi Coding endpoint with auto-detection**. closed without merge · fix tier `suggested`
  - maintainer direction: category `merged_via`; implication `superseded`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#5566: fix(credential_pool): use _resolve_kimi_base_url when seeding kimi-coding pool**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#5669`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#5669: fix(auth): kimi-coding pool base_url seeding + PKCE endpoint fallback**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#9907: feat(zai): add GLM-5V-Turbo support for coding plan**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `agent/auxiliary_client.py`, `hermes_cli/auth.py`, `hermes_cli/models.py`
  - fix hunks:
  - `agent/auxiliary_client.py`

    ```diff
    @@ -112,6 +112,7 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:
     # "exotic provider" branch checks this before falling back to the main model.
     _PROVIDER_VISION_MODELS: Dict[str, str] = {
         "xiaomi": "mimo-v2-omni",
    +    "zai": "glm-5v-turbo",
     }
     
     # OpenRouter app attribution headers
    ```
  - `hermes_cli/auth.py`

    ```diff
    @@ -383,13 +383,16 @@ def _resolve_api_key_provider_secret(
     # Z.AI has separate billing for general vs coding plans, and global vs China
     # endpoints.  A key that works on one may return "Insufficient balance" on
     # another.  We probe at setup time and store the working endpoint.
    +# Each entry lists candidate models to try in order — newer coding plan accounts
    +# may only have access to recent models (glm-5.1, glm-5v-turbo) while older
    +# ones still use glm-4.7.
     
     ZAI_ENDPOINTS = [
    -    # (id, base_url, default_model, label)
    -    ("global",        "https://api.z.ai/api/paas/v4",        "glm-5",   "Global"),
    -    ("cn",            "https://open.bigmodel.cn/api/paas/v4", "glm-5",   "China"),
    -    ("coding-global", "https://api.z.ai/api/coding/paas/v4",  "glm-4.7", "Global (Coding Plan)"),
    -    ("coding-cn",     "https://open.bigmodel.cn/api/coding/paas/v4", "glm-4.7", "China (Coding Plan)"),
    +    # (id, base_url, probe_models, label)
    +    ("global",        "https://api.z.ai/api/paas/v4",        ["glm-5"],   "Global"),
    +    ("cn",            "https://open.bigmodel.cn/api/paas/v4", ["glm-5"],   "China"),
    +    ("coding-global", "https://api.z.ai/api/coding/paas/v4",  ["glm-5.1", "glm-5v-turbo", "glm-4.7"], "Global (Coding Plan)"),
    +    ("coding-cn",     "https://open.bigmodel.cn/api/coding/paas/v4", ["glm-5.1", "glm-5v-turbo", "glm-4.7"], "China (Coding Plan)"),
     ]
     
     
    ```

    ```diff
    @@ -397,35 +400,37 @@ def detect_zai_endpoint(api_key: str, timeout: float = 8.0) -> Optional[Dict[str
         """Probe z.ai endpoints to find one that accepts this API key.
     
         Returns {"id": ..., "base_url": ..., "model": ..., "label": ...} for the
    -    first working endpoint, or None if all fail.
    +    first working endpoint, or None if all fail.  For endpoints with multiple
    +    candidate models, tries each in order and returns the first that succeeds.
         """
    -    for ep_id, base_url, model, label in ZAI_ENDPOINTS:
    -        try:
    -            resp = httpx.post(
    -                f"{base_url}/chat/completions",
    -                headers={
    -                    "Authorization": f"Bearer {api_key}",
    -                    "Content-Type": "application/json",
    -                },
    -                json={
    -                    "model": model,
    -                    "stream": False,
    -                    "max_tokens": 1,
    -                    "messages": [{"role": "user", "content": "ping"}],
    -                },
    -                timeout=timeout,
    -            )
    -            if resp.status_code == 200:
    -                logger.debug("Z.AI endpoint probe: %s (%s) OK", ep_id, base_url)
    -                return {
    -                    "id": ep_id,
    -                    "base_url": base_url,
    -                    "model": model,
    -                    "label": label,
    -                }
    -            logger.debug("Z.AI endpoint probe: %s returned %s", ep_id, resp.status_code)
    -        except Exception as exc:
    -            logger.debug("Z.AI endpoint probe: %s failed: %s", ep_id, exc)
    +    for ep_id, base_url, probe_models, label in ZAI_ENDPOINTS:
    +        for model in probe_models:
    +            try:
    +                resp = httpx.post(
    +                    f"{base_url}/chat/completions",
    +                    headers={
    +                        "Authorization": f"Bearer {api_key}",
    +                        "Content-Type": "application/json",
    +                    },
    +                    json={
    +                        "model": model,
    +                        "stream": False,
    +                        "max_tokens": 1,
    +                        "messages": [{"role": "user", "content": "ping"}],
    +                    },
    +                    timeout=timeout,
    +                )
    +                if resp.status_code == 200:
    +                    logger.debug("Z.AI endpoint probe: %s (%s) model=%s OK", ep_id, base_url, model)
    +                    return {
    +                        "id": ep_id,
    +                        "base_url": base_url,
    +                        "model": model,
    +                        "label": label,
    +                    }
    +                logger.debug("Z.AI endpoint probe: %s model=%s returned %s", ep_id, model, resp.status_code)
    +            except Exception as exc:
    +                logger.debug("Z.AI endpoint probe: %s model=%s failed: %s", ep_id, model, exc)
         return None
     
     
    ```
  - `hermes_cli/models.py`

    ```diff
    @@ -44,6 +44,7 @@
         ("minimax/minimax-m2.7",            ""),
         ("minimax/minimax-m2.5",            ""),
         ("z-ai/glm-5.1",                    ""),
    +    ("z-ai/glm-5v-turbo",               ""),
         ("z-ai/glm-5-turbo",                ""),
         ("moonshotai/kimi-k2.5",            ""),
         ("x-ai/grok-4.20",                  ""),
    ```

    ```diff
    @@ -89,6 +90,7 @@ def _codex_curated_models() -> list[str]:
             "minimax/minimax-m2.7",
             "minimax/minimax-m2.5",
             "z-ai/glm-5.1",
    +        "z-ai/glm-5v-turbo",
             "z-ai/glm-5-turbo",
             "moonshotai/kimi-k2.5",
             "x-ai/grok-4.20-beta",
    ```

    ```diff
    @@ -134,6 +136,7 @@ def _codex_curated_models() -> list[str]:
         "zai": [
             "glm-5.1",
             "glm-5",
    +        "glm-5v-turbo",
             "glm-5-turbo",
             "glm-4.7",
             "glm-4.5",
    ```
- **#13954: fix(setup): clarify Moonshot/Kimi provider labels and accept MOONSHOT_API_KEY (#13935)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#43629: fix(provider): use api.kimi.com/coding endpoint for kimi-coding provider**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#43617`
- **#48509: fix(auxiliary_client): resolve API keys for inferred custom endpoints**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `a8841e2a6877f7b5e7b4b6ab0122ef2fde229fe2`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#61575: fix(agent): route Z.AI manual-pool entries through _resolve_zai_base_url**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#13993: fix(providers): add moonshotai provider, disambiguate from kimi-for-coding**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/auth.py`, `hermes_cli/providers.py`
- **#18694: fix(auth): re-resolve Kimi pool base URLs**. open
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#83537`, `#5908`, `#83537`, `#83537`
  - files: `hermes_cli/auth.py`, `tests/hermes_cli/test_api_key_providers.py`
- **#37816: Respect configured ZAI base URL**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `agent/auxiliary_client.py`, `hermes_cli/auth.py`, `tests/agent/test_auxiliary_client.py`, `tests/hermes_cli/test_api_key_providers.py`
- **#42547: fix(zai): probe Coding Plan endpoints before generic to prevent silent wallet drain**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/auth.py`, `tests/hermes_cli/test_api_key_providers.py`
- **#42678: fix(auth): reorder Z.AI endpoint probe to try coding plan first (#42536)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#42547`, `#42536`
- **#43813: fix(provider): update kimi-coding endpoint and User-Agent (#43617)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#43629`, `#43617`
- **#55116: fix(auxiliary): prepend zai coding-plan vision endpoint before metered paas/v4 (fixes #55112)**. open
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#62467`
  - files: `agent/auxiliary_client.py`
- **#55156: fix(auxiliary): try zai coding-plan vision endpoint before metered fallbacks (#55112)**. open
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#55116`, `#55112`, `#55116`, `#55112`
  - files: `agent/auxiliary_client.py`, `tests/agent/test_vision_zai_coding_plan_55112.py`
- **#58088: fix(zai): honor configured base url**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `23d3edac4e`
- **#83537: fix(auth): re-resolve stale kimi pool base_url on load (#5908)**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/auth.py`, `tests/hermes_cli/test_auth_profile_fallback.py`
- **#114111: fix(auth): route Kimi Code credentials by key**. open
  - maintainer direction: category `silent_close`; implication `active`
  - files: `hermes_cli/auth.py`, `hermes_cli/auth_commands.py`, `tests/hermes_cli/test_auth_commands.py`

## Fix status

- Merged to main in PR #9907 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-14`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
- Subsystem: `agent`
