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
+43
View File
@@ -371,6 +371,14 @@ impl SamplingClient {
)
})?;
headers.insert(HeaderName::from_static("x-api-key"), header_value);
if config.api_backend == kigi_sampling_types::ApiBackend::Messages {
// The real Anthropic Messages wire rejects requests
// without this; compatible endpoints ignore it.
headers.insert(
HeaderName::from_static("anthropic-version"),
HeaderValue::from_static(kigi_sampling_types::ANTHROPIC_VERSION),
);
}
}
AuthScheme::Bearer => {
let bearer = format!("Bearer {}", api_key);
@@ -1890,6 +1898,41 @@ mod tests {
}
}
/// The real Anthropic Messages wire rejects requests without
/// `anthropic-version`; the XApiKey+Messages client must carry it in its
/// default headers, and Bearer/ChatCompletions clients must NOT.
#[test]
fn x_api_key_messages_client_sends_anthropic_version() {
let mut config = minimal_config();
config.auth_scheme = AuthScheme::XApiKey;
config.api_backend = ApiBackend::Messages;
let client = SamplingClient::new(config).expect("client builds");
assert_eq!(
client
.default_headers
.get("anthropic-version")
.and_then(|v| v.to_str().ok()),
Some(kigi_sampling_types::ANTHROPIC_VERSION)
);
assert!(client.default_headers.get("x-api-key").is_some());
let bearer = SamplingClient::new(minimal_config()).expect("client builds");
assert!(
bearer.default_headers.get("anthropic-version").is_none(),
"non-anthropic clients must not grow the header"
);
let mut x_api_chat = minimal_config();
x_api_chat.auth_scheme = AuthScheme::XApiKey;
let x_api_chat = SamplingClient::new(x_api_chat).expect("client builds");
assert!(
x_api_chat
.default_headers
.get("anthropic-version")
.is_none(),
"XApiKey without the Messages backend must not grow the header"
);
}
/// Verify the serialized shape of StreamingChatRequest matches the
/// expected wire format: all ChatCompletionRequest fields flattened at
/// top level, plus `stream: true` and `stream_options.include_usage: true`.