fix(chat): BYOK dialect defaults to Passthrough; dedicated Mistral dialect

C1 (decision): custom/BYOK ChatCompletions entries defaulted to the Kimi
dialect, leaking Kimi-specific body mutations (thinking:{…} control,
replayed reasoning_content, schema rewrites) to arbitrary third-party
OpenAI-compatible servers. The default is now Passthrough (vanilla
OpenAI semantics), with ONE exception mirroring Pi's base-url quirk
sniffing: entries pointed at the house/Kimi coding endpoint keep the
Kimi dialect. Registry platforms are unaffected (all declare
explicitly).

C2 (Pi mistral-conversations normalizer): Mistral requires tool-call ids
of EXACTLY nine [a-zA-Z0-9] chars; even same-session synthesized UUIDs
violate it. New ChatCompat::Mistral = StrictOpenAi behavior + the
normalizer — strip non-alphanumerics, keep exact-9 ids, else FNV-1a →
base36 (build-stable, deterministic across requests for prefix-cache
stability) with collision retry; ONE map covers tool_calls[].id and
tool_call_id so pairing survives. The mistral registry row and the
persisted 'mistral' serde value both resolve to it (pre-rename Mistral
sessions gain the contract automatically); Cerebras/NVIDIA stay on
StrictOpenAi untouched.

Verified: models+sampling-types+sampler+chat-state+shell all green,
clippy clean.
This commit is contained in:
2026-07-23 10:31:45 -04:00
parent d3c9380307
commit 301eb61de4
5 changed files with 247 additions and 15 deletions
+10 -5
View File
@@ -70,9 +70,12 @@ pub enum PlatformChatCompat {
Kimi,
DeepSeek,
Passthrough,
/// Strict OpenAI-compatible validator (Mistral, Cerebras) — strips
/// Strict OpenAI-compatible validator (Cerebras, NVIDIA) — strips
/// `stream_options` and private fields.
StrictOpenAi,
/// Mistral: StrictOpenAi plus its exactly-9-alphanumeric tool-call id
/// contract (foreign/OpenAI-style ids are deterministically remapped).
Mistral,
}
/// How a platform's API key rides requests (listing, validation, inference).
@@ -599,10 +602,12 @@ const MISTRAL_SPEC: PlatformSpec = PlatformSpec {
wire_serves_metadata: false,
wire_api: PlatformWireApi::ChatCompletions,
listing: ListingDialect::OpenAi,
// Mistral's strict validator 422s on `stream_options`, and its reasoning
// models return array content — the StrictOpenAi dialect strips
// stream_options; the response deserializer handles arrays universally.
chat_compat: PlatformChatCompat::StrictOpenAi,
// Mistral's strict validator 422s on `stream_options`, its reasoning
// models return array content, and tool-call ids must be EXACTLY nine
// `[a-zA-Z0-9]` chars — the Mistral dialect strips stream_options and
// deterministically remaps non-conforming (foreign/OpenAI-style) ids;
// the response deserializer handles arrays universally.
chat_compat: PlatformChatCompat::Mistral,
key_header: PlatformKeyHeader::Bearer,
// The listing carries embed/moderation/OCR entries; keep tool-calling
// chat models only.