fix(kimi): reconcile sk-kimi- routing with Anthropic SDK URL semantics
Follow-ups after salvaging xiaoqiang243's kimi-for-coding patches: - KIMI_CODE_BASE_URL: drop trailing /v1 (was /coding/v1). The /coding endpoint speaks Anthropic Messages, and the Anthropic SDK appends /v1/messages internally. /coding/v1 + SDK suffix produced /coding/v1/v1/messages (a 404). /coding + SDK suffix now yields /coding/v1/messages correctly. - kimi-coding ProviderConfig: keep legacy default api.moonshot.ai/v1 so non-sk-kimi- moonshot keys still authenticate. sk-kimi- keys are already redirected to api.kimi.com/coding via _resolve_kimi_base_url. - doctor.py: update Kimi UA to claude-code/0.1.0 (was KimiCLI/1.30.0) and rewrite /coding base URLs to /coding/v1 for the /models health check (Anthropic surface has no /models). - test_kimi_env_vars: accept KIMI_CODING_API_KEY as a secondary env var. E2E verified: sk-kimi-<key> → https://api.kimi.com/coding/v1/messages (Anthropic) sk-<legacy> → https://api.moonshot.ai/v1/chat/completions (OpenAI) UA: claude-code/0.1.0, x-api-key: <sk-kimi-*>
This commit is contained in:
@@ -168,7 +168,10 @@ PROVIDER_REGISTRY: Dict[str, ProviderConfig] = {
|
|||||||
id="kimi-coding",
|
id="kimi-coding",
|
||||||
name="Kimi / Moonshot",
|
name="Kimi / Moonshot",
|
||||||
auth_type="api_key",
|
auth_type="api_key",
|
||||||
inference_base_url="https://api.kimi.com/coding",
|
# Legacy platform.moonshot.ai keys use this endpoint (OpenAI-compat).
|
||||||
|
# sk-kimi- (Kimi Code) keys are auto-redirected to api.kimi.com/coding
|
||||||
|
# by _resolve_kimi_base_url() below.
|
||||||
|
inference_base_url="https://api.moonshot.ai/v1",
|
||||||
api_key_env_vars=("KIMI_API_KEY", "KIMI_CODING_API_KEY"),
|
api_key_env_vars=("KIMI_API_KEY", "KIMI_CODING_API_KEY"),
|
||||||
base_url_env_var="KIMI_BASE_URL",
|
base_url_env_var="KIMI_BASE_URL",
|
||||||
),
|
),
|
||||||
@@ -340,10 +343,16 @@ def get_anthropic_key() -> str:
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Kimi Code (kimi.com/code) issues keys prefixed "sk-kimi-" that only work
|
# Kimi Code (kimi.com/code) issues keys prefixed "sk-kimi-" that only work
|
||||||
# on api.kimi.com/coding/v1. Legacy keys from platform.moonshot.ai work on
|
# on api.kimi.com/coding. Legacy keys from platform.moonshot.ai work on
|
||||||
# api.moonshot.ai/v1 (the default). Auto-detect when user hasn't set
|
# api.moonshot.ai/v1 (the old default). Auto-detect when user hasn't set
|
||||||
# KIMI_BASE_URL explicitly.
|
# KIMI_BASE_URL explicitly.
|
||||||
KIMI_CODE_BASE_URL = "https://api.kimi.com/coding/v1"
|
#
|
||||||
|
# Note: the base URL intentionally has NO /v1 suffix. The /coding endpoint
|
||||||
|
# speaks the Anthropic Messages protocol, and the anthropic SDK appends
|
||||||
|
# "/v1/messages" internally — so "/coding" + SDK suffix → "/coding/v1/messages"
|
||||||
|
# (the correct target). Using "/coding/v1" here would produce
|
||||||
|
# "/coding/v1/v1/messages" (a 404).
|
||||||
|
KIMI_CODE_BASE_URL = "https://api.kimi.com/coding"
|
||||||
|
|
||||||
|
|
||||||
def _resolve_kimi_base_url(api_key: str, default_url: str, env_override: str) -> str:
|
def _resolve_kimi_base_url(api_key: str, default_url: str, env_override: str) -> str:
|
||||||
|
|||||||
@@ -943,18 +943,22 @@ def run_doctor(args):
|
|||||||
try:
|
try:
|
||||||
import httpx
|
import httpx
|
||||||
_base = os.getenv(_base_env, "") if _base_env else ""
|
_base = os.getenv(_base_env, "") if _base_env else ""
|
||||||
# Auto-detect Kimi Code keys (sk-kimi-) → api.kimi.com
|
# Auto-detect Kimi Code keys (sk-kimi-) → api.kimi.com/coding/v1
|
||||||
|
# (OpenAI-compat surface, which exposes /models for health check).
|
||||||
if not _base and _key.startswith("sk-kimi-"):
|
if not _base and _key.startswith("sk-kimi-"):
|
||||||
_base = "https://api.kimi.com/coding/v1"
|
_base = "https://api.kimi.com/coding/v1"
|
||||||
# Anthropic-compat endpoints (/anthropic) don't support /models.
|
# Anthropic-compat endpoints (/anthropic, api.kimi.com/coding
|
||||||
# Rewrite to the OpenAI-compat /v1 surface for health checks.
|
# with no /v1) don't support /models. Rewrite to the OpenAI-compat
|
||||||
|
# /v1 surface for health checks.
|
||||||
if _base and _base.rstrip("/").endswith("/anthropic"):
|
if _base and _base.rstrip("/").endswith("/anthropic"):
|
||||||
from agent.auxiliary_client import _to_openai_base_url
|
from agent.auxiliary_client import _to_openai_base_url
|
||||||
_base = _to_openai_base_url(_base)
|
_base = _to_openai_base_url(_base)
|
||||||
|
if base_url_host_matches(_base, "api.kimi.com") and _base.rstrip("/").endswith("/coding"):
|
||||||
|
_base = _base.rstrip("/") + "/v1"
|
||||||
_url = (_base.rstrip("/") + "/models") if _base else _default_url
|
_url = (_base.rstrip("/") + "/models") if _base else _default_url
|
||||||
_headers = {"Authorization": f"Bearer {_key}"}
|
_headers = {"Authorization": f"Bearer {_key}"}
|
||||||
if base_url_host_matches(_base, "api.kimi.com"):
|
if base_url_host_matches(_base, "api.kimi.com"):
|
||||||
_headers["User-Agent"] = "KimiCLI/1.30.0"
|
_headers["User-Agent"] = "claude-code/0.1.0"
|
||||||
_resp = httpx.get(
|
_resp = httpx.get(
|
||||||
_url,
|
_url,
|
||||||
headers=_headers,
|
headers=_headers,
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ class TestProviderRegistry:
|
|||||||
|
|
||||||
def test_kimi_env_vars(self):
|
def test_kimi_env_vars(self):
|
||||||
pconfig = PROVIDER_REGISTRY["kimi-coding"]
|
pconfig = PROVIDER_REGISTRY["kimi-coding"]
|
||||||
assert pconfig.api_key_env_vars == ("KIMI_API_KEY",)
|
# KIMI_API_KEY is the primary env var; KIMI_CODING_API_KEY is a
|
||||||
|
# secondary fallback for Kimi Code sk-kimi- keys so users don't
|
||||||
|
# have to overload the same variable.
|
||||||
|
assert "KIMI_API_KEY" in pconfig.api_key_env_vars
|
||||||
|
assert "KIMI_CODING_API_KEY" in pconfig.api_key_env_vars
|
||||||
assert pconfig.base_url_env_var == "KIMI_BASE_URL"
|
assert pconfig.base_url_env_var == "KIMI_BASE_URL"
|
||||||
|
|
||||||
def test_minimax_env_vars(self):
|
def test_minimax_env_vars(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user