Add Anthropic platform: wire-served metadata via listing dialect (provider 2)

The 5th registry row: id "anthropic", ANTHROPIC_API_KEY > auth.json
"anthropic" scope, api.anthropic.com/v1 with KIGI_ANTHROPIC_BASE_URL
override, Messages dialect. Two new spec dimensions most future rows
reuse: ListingDialect (Anthropic's /v1/models wants x-api-key +
anthropic-version headers, ?limit=1000, and its own response shape) and
PlatformKeyHeader (Bearer vs x-api-key across listing/validation/
inference, with auth_scheme stamped onto entries).

The 2026 Anthropic listing serves real metadata: the adapter maps
max_input_tokens, per-level effort capabilities (low..max as the menu,
xhigh/max distinct), thinking/image flags — and enrichment fills only
genuine wire gaps (e2e pins wire-1M beating enrichment, and a zero
context filled to 200k).

Two review-confirmed defects fixed red-green:
- Output caps were dropped at three layers, so every sub-128K-output
  model (64k Haiku, legacy models) would 400 on EVERY request against
  the sampler's 128K max_tokens default. Wire max_tokens and enrichment
  limit.output now flow to entry.max_completion_tokens.
- An explicit wire effort-decline was indistinguishable from wire
  silence, letting enrichment inject effort menus pre-4.6 models reject
  (adaptive thinking 400). The adapter now emits a decline sentinel
  (support:false) that enrichment respects — proven end to end.

Also: the Messages client now sends anthropic-version (previously never
sent — real api.anthropic.com rejects such requests; pinned across all
three scheme/backend quadrants), key validation builds per-key-header
requests, missing listing data fails fast, empty-id ghosts drop with a
warning, kimi-import recognizes api.anthropic.com as built-in
automatically.
This commit is contained in:
2026-07-21 06:12:53 -04:00
parent 23e94939c0
commit b86722f508
7 changed files with 579 additions and 27 deletions
@@ -379,8 +379,9 @@ pub fn missing_platform_key_error(platform: kigi_models::PlatformId) -> String {
/// missing-key message. A present key is validated with
/// `GET {platform_base}/models` (the same endpoint the catalog fetch uses):
/// 401 → "invalid API key"; any other non-success status or network error
/// surfaces as-is. SECURITY: the key is only ever sent as the bearer header —
/// it must never appear in errors or logs.
/// surfaces as-is. SECURITY: the key is only ever sent as the platform's
/// key header (Bearer or x-api-key) — it must never appear in errors or
/// logs.
pub(crate) async fn authenticate_platform_api_key(
platform: kigi_models::PlatformId,
key: Option<&str>,
@@ -394,9 +395,16 @@ pub(crate) async fn authenticate_platform_api_key(
return Err(auth_err(missing_platform_key_error(platform)));
};
let url = format!("{}/models", platform.base_url().trim_end_matches('/'));
let response = crate::http::shared_client()
.get(&url)
.header("Authorization", format!("Bearer {key}"))
let request = match platform.key_header() {
kigi_models::PlatformKeyHeader::Bearer => crate::http::shared_client()
.get(&url)
.header("Authorization", format!("Bearer {key}")),
kigi_models::PlatformKeyHeader::XApiKey => crate::http::shared_client()
.get(&url)
.header("x-api-key", key)
.header("anthropic-version", kigi_sampling_types::ANTHROPIC_VERSION),
};
let response = request
.send()
.await
.map_err(|e| auth_err(format!("Couldn't reach {}: {e}", platform.as_str())))?;
@@ -584,7 +592,8 @@ mod tests {
KIMI_CODE_METHOD_ID,
MOONSHOT_CN_METHOD_ID,
MOONSHOT_AI_METHOD_ID,
"openai"
"openai",
"anthropic"
]
);
assert_eq!(default_id(&built), Some(XAI_API_KEY_METHOD_ID));
@@ -611,7 +620,8 @@ mod tests {
KIMI_CODE_METHOD_ID,
MOONSHOT_CN_METHOD_ID,
MOONSHOT_AI_METHOD_ID,
"openai"
"openai",
"anthropic"
]
);
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
@@ -631,7 +641,8 @@ mod tests {
KIMI_CODE_METHOD_ID,
MOONSHOT_CN_METHOD_ID,
MOONSHOT_AI_METHOD_ID,
"openai"
"openai",
"anthropic"
]
);
assert_eq!(default_id(&built), Some(CACHED_TOKEN_AUTH_METHOD_ID));
@@ -654,7 +665,8 @@ mod tests {
KIMI_CODE_METHOD_ID,
MOONSHOT_CN_METHOD_ID,
MOONSHOT_AI_METHOD_ID,
"openai"
"openai",
"anthropic"
]
);
assert_eq!(default_id(&built), None);