From c0106e50e7ecedb3ce34e785d949725dc4e0e457 Mon Sep 17 00:00:00 2001 From: kshitij <82637225+kshitijk4poor@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:25:04 +0530 Subject: [PATCH] fix(kimi): send Hermes attribution headers instead of claude-code/0.1.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- agent/anthropic_adapter.py | 21 +++++++++++++++---- .../model-providers/kimi-coding/__init__.py | 13 ++++++++++-- run_agent.py | 3 ++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index 39edd74d00..52a86513fb 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -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): diff --git a/plugins/model-providers/kimi-coding/__init__.py b/plugins/model-providers/kimi-coding/__init__.py index fd0a24a714..4c1435003b 100644 --- a/plugins/model-providers/kimi-coding/__init__.py +++ b/plugins/model-providers/kimi-coding/__init__.py @@ -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", ) diff --git a/run_agent.py b/run_agent.py index 64d3491a84..9589606745 100644 --- a/run_agent.py +++ b/run_agent.py @@ -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"):