Add DeepSeek platform + ChatCompletions dialect system (provider 3)

The 6th registry row: id "deepseek", DEEPSEEK_API_KEY > auth.json
"deepseek" scope, base https://api.deepseek.com (chat rides
{base}/chat/completions per official docs) with KIGI_DEEPSEEK_BASE_URL
override, enrichment-backed metadata (1M context, 384k output cap,
high/max effort menu).

Structural fix the cycle exposed: kigi's Kimi-specific body adaptation
ran UNCONDITIONALLY on every ChatCompletions request. New ChatCompat
dialect, declared per platform row and threaded through SamplerConfig,
ClientDefaults, and the session-persisted SamplingConfig (serde-default
Kimi keeps restored pre-field sessions and BYOK endpoints byte-identical;
production persist seams copy it; subagents inherit it):
- Kimi: full legacy pipeline (dispatch ≡ legacy pinned)
- DeepSeek: thinking:{type, reasoning_effort} per api-docs.deepseek.com
  (server maps low/medium→high, xhigh→max itself; none disables; absent
  leaves the server default)
- Passthrough: OpenAI-style reasoning_effort scalar untouched (unblocks
  Groq and the rest of the OpenAI-compatible list)

Review-confirmed release blocker fixed: kigi replays Kimi's
reasoning_content (and its private model_id) on input assistant
messages — Kimi consumes these, but DeepSeek documents input
reasoning_content as prefix-mode-only (historically a 400) and other
providers don't know either field. The DeepSeek and Passthrough arms now
strip both; Kimi's own pipeline is untouched. Pinned on both message
shapes.
This commit is contained in:
2026-07-21 07:21:05 -04:00
parent b86722f508
commit 7efb4b07cc
30 changed files with 423 additions and 11 deletions
@@ -938,6 +938,26 @@ pub fn normalize_effort_echo(value: &mut Value) {
/// wires (Messages inference and the /v1/models listing).
pub const ANTHROPIC_VERSION: &str = "2023-06-01";
/// ChatCompletions request-body adaptation dialect. Providers disagree on
/// how thinking rides an OpenAI-compatible body: Kimi wants
/// `thinking:{type,effort}`, DeepSeek wants
/// `thinking:{type,reasoning_effort}`, most others take the OpenAI-style
/// `reasoning_effort` scalar untouched.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChatCompat {
/// Kimi wire (`thinking:{type,effort}`, canonical xhigh spelled max).
/// The default: BYOK/custom ChatCompletions endpoints keep the
/// historical kigi behavior.
#[default]
Kimi,
/// DeepSeek wire (`thinking:{type,reasoning_effort}`, official docs:
/// low/medium map to high and xhigh to max server-side).
DeepSeek,
/// Leave the body as-is (OpenAI-style `reasoning_effort` passes through).
Passthrough,
}
pub const REASONING_EFFORT_META_KEY: &str = "reasoningEffort";
pub const SUPPORTS_REASONING_EFFORT_META_KEY: &str = "supportsReasoningEffort";
@@ -1126,6 +1146,11 @@ pub struct SamplingConfig {
pub max_completion_tokens: Option<u32>,
pub temperature: Option<f32>,
pub top_p: Option<f32>,
/// ChatCompletions body-adaptation dialect (per-platform; serde-default
/// Kimi keeps pre-field sessions and BYOK endpoints on the historical
/// behavior).
#[serde(default)]
pub chat_compat: ChatCompat,
/// Which API backend to use for this model
#[serde(default)]
pub api_backend: ApiBackend,