fix(kimi): send Hermes attribution headers instead of claude-code/0.1.0

The Kimi team noticed that traffic from Hermes Coding Plan users
identifies itself as Claude (User-Agent: claude-code/0.1.0) rather
than the actual client. They asked us to update the UA so they can
properly attribute traffic and understand how their services are
accessed — especially important as they open up to more third-party
agents.

Three code paths were sending wrong/attribution-less headers to Kimi:

1. run_agent.py — _apply_client_headers_for_base_url sent
   {"User-Agent": "claude-code/0.1.0"} for api.kimi.com. Now sends
   the same _AI_GATEWAY_HEADERS set used for Vercel AI Gateway:
   HTTP-Referer + X-Title + HermesAgent/{version} User-Agent.

2. agent/anthropic_adapter.py — the Anthropic Messages path for
   api.kimi.com/coding sent 'claude-code/0.1.0'. Now sends the same
   three-header attribution set.

3. plugins/model-providers/kimi-coding/__init__.py — both kimi and
   kimi_cn profiles sent a static 'hermes-agent/1.0' with no
   HTTP-Referer or X-Title. Now sends the full three-header set with
   a dynamic version, matching the pattern used by the gmi, fireworks,
   xai, and ai-gateway provider profiles.

The attribution header set (HTTP-Referer + X-Title + User-Agent) is
the canonical Hermes pattern used for OpenRouter, Vercel AI Gateway,
Fireworks, and other providers that read these headers for traffic
attribution.
This commit is contained in:
kshitij
2026-08-10 17:25:04 +05:30
parent 3ee1bb323e
commit c0106e50e7
3 changed files with 30 additions and 7 deletions
+17 -4
View File
@@ -26,6 +26,13 @@ from typing import Any, Dict, List, Optional, Tuple
from utils import base_url_host_matches, base_url_hostname, normalize_proxy_env_vars
from agent.secret_scope import get_secret as _get_secret
try:
import hermes_cli as _hermes_cli
_HERMES_VERSION = str(_hermes_cli.__version__)
except Exception:
_HERMES_VERSION = "0.0.0"
def _getenv(name: str, default: str = "") -> str:
"""Profile-scoped replacement for os.getenv on credential reads.
@@ -857,12 +864,18 @@ def build_anthropic_client(
)
if _is_kimi_coding_endpoint(base_url):
# Kimi's /coding endpoint requires User-Agent: claude-code/0.1.0
# to be recognized as a valid Coding Agent. Without it, returns 403.
# Check this BEFORE _requires_bearer_auth since both match api.kimi.com/coding.
# Kimi's /coding endpoint requires a non-empty User-Agent to be
# recognized as a valid Coding Agent. Originally we sent
# ``claude-code/0.1.0`` (the minimum that avoided a 403), but the Kimi
# team asked us to identify ourselves properly so they can attribute
# traffic correctly. Send the same attribution header set we send to
# OpenRouter, Vercel AI Gateway, and Fireworks:
# HTTP-Referer + X-Title + HermesAgent User-Agent.
kwargs["api_key"] = api_key
kwargs["default_headers"] = {
"User-Agent": "claude-code/0.1.0",
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
"X-Title": "Hermes Agent",
"User-Agent": f"HermesAgent/{_HERMES_VERSION}",
**( {"anthropic-beta": ",".join(common_betas)} if common_betas else {} )
}
elif _requires_bearer_auth(normalized_base_url):
@@ -10,6 +10,7 @@ This module covers the chat_completions path (/v1 endpoint).
from typing import Any
from urllib.parse import urlparse
from hermes_cli import __version__ as _HERMES_VERSION
from providers import register_provider
from providers.base import OMIT_TEMPERATURE, ProviderProfile
@@ -102,7 +103,11 @@ kimi = KimiProfile(
base_url="https://api.moonshot.ai/v1",
fixed_temperature=OMIT_TEMPERATURE,
default_max_tokens=32000,
default_headers={"User-Agent": "hermes-agent/1.0"},
default_headers={
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
"X-Title": "Hermes Agent",
"User-Agent": f"HermesAgent/{_HERMES_VERSION}",
},
default_aux_model="kimi-k2-turbo-preview",
)
@@ -113,7 +118,11 @@ kimi_cn = KimiProfile(
base_url="https://api.moonshot.cn/v1",
fixed_temperature=OMIT_TEMPERATURE,
default_max_tokens=32000,
default_headers={"User-Agent": "hermes-agent/1.0"},
default_headers={
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
"X-Title": "Hermes Agent",
"User-Agent": f"HermesAgent/{_HERMES_VERSION}",
},
default_aux_model="kimi-k2-turbo-preview",
)
+2 -1
View File
@@ -5936,7 +5936,8 @@ class AIAgent:
self._client_kwargs["default_headers"] = copilot_default_headers()
elif base_url_host_matches(base_url, "api.kimi.com"):
self._client_kwargs["default_headers"] = {"User-Agent": "claude-code/0.1.0"}
from agent.auxiliary_client import _AI_GATEWAY_HEADERS
self._client_kwargs["default_headers"] = dict(_AI_GATEWAY_HEADERS)
elif base_url_host_matches(base_url, "portal.qwen.ai"):
self._client_kwargs["default_headers"] = _qwen_portal_headers()
elif base_url_host_matches(base_url, "chatgpt.com"):