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:
@@ -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
@@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user