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
@@ -53,6 +53,7 @@ impl SessionActor {
temperature: sampling_config.temperature,
top_p: sampling_config.top_p,
api_backend: sampling_config.api_backend.clone(),
chat_compat: sampling_config.chat_compat,
extra_headers: sampling_config.extra_headers.clone(),
context_window: new_context_window,
reasoning_effort: sampling_config.reasoning_effort,
@@ -260,6 +260,7 @@ impl SessionActor {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(256_000).unwrap(),
reasoning_effort: None,
@@ -312,6 +313,7 @@ impl SessionActor {
top_p: cfg.top_p,
api_backend: cfg.api_backend,
auth_scheme,
chat_compat: cfg.chat_compat,
extra_headers,
context_window: cfg.context_window.get(),
reasoning_effort: cfg.reasoning_effort,
@@ -346,6 +346,7 @@ pub(crate) async fn spawn_session_actor(
temperature: sampling_config.temperature,
top_p: sampling_config.top_p,
api_backend: sampling_config.api_backend.clone(),
chat_compat: sampling_config.chat_compat,
extra_headers: sampling_config.extra_headers.clone(),
context_window: context_window_override.unwrap_or(baseline_context_window),
reasoning_effort: sampling_config.reasoning_effort,
@@ -847,6 +847,7 @@ async fn set_session_model_invalidates_byok_memo_for_same_model_id() {
temperature: None,
top_p: None,
api_backend: crate::sampling::ApiBackend::ChatCompletions,
chat_compat: Default::default(),
auth_scheme: Default::default(),
extra_headers: Default::default(),
context_window: 256_000,
@@ -44,6 +44,7 @@ async fn persist_ack_waits_for_disk_flush_before_success() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
auth_scheme: Default::default(),
extra_headers: Default::default(),
context_window: 100_000,
@@ -87,6 +88,7 @@ async fn persist_ack_waits_for_disk_flush_before_success() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(100_000).unwrap(),
reasoning_effort: None,
@@ -338,6 +340,7 @@ async fn first_turn_memory_injection_persists_to_chat_history() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
auth_scheme: Default::default(),
context_window: 100_000,
force_http1: false,
@@ -381,6 +384,7 @@ async fn first_turn_memory_injection_persists_to_chat_history() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(100_000).unwrap(),
reasoning_effort: None,
@@ -466,6 +470,7 @@ async fn first_turn_memory_injection_disabled_does_not_persist_to_chat_history()
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
auth_scheme: Default::default(),
context_window: 100_000,
force_http1: false,
@@ -513,6 +518,7 @@ async fn first_turn_memory_injection_disabled_does_not_persist_to_chat_history()
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(100_000).unwrap(),
reasoning_effort: None,
@@ -1732,6 +1738,7 @@ async fn cancel_propagates_to_sampler_handle_so_no_further_emission() {
temperature: None,
top_p: None,
api_backend: kigi_sampler::ApiBackend::Responses,
chat_compat: Default::default(),
auth_scheme: Default::default(),
extra_headers: Default::default(),
context_window: 100_000,
@@ -104,6 +104,7 @@ async fn test_e2e_idle_resume_refreshes_model_metadata() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(200_000).unwrap(),
reasoning_effort: None,
@@ -52,6 +52,7 @@ async fn create_test_actor(
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(context_window)
.expect("test context_window must be non-zero"),
@@ -486,6 +487,7 @@ async fn create_test_actor_with_memory(
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(context_window)
.expect("test context_window must be non-zero"),
@@ -1240,6 +1242,7 @@ async fn test_e2e_idle_resume_refreshes_model_metadata() {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(200_000).unwrap(),
reasoning_effort: None,
@@ -104,6 +104,7 @@ async fn create_test_actor_with_memory(
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(context_window)
.expect("test context_window must be non-zero"),
@@ -168,6 +168,7 @@ pub(crate) async fn create_test_actor_ex(
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(context_window)
.expect("test context_window must be non-zero"),
@@ -2149,6 +2149,7 @@ mod inline_auto_compact_flow_tests {
temperature: None,
top_p: None,
api_backend: Default::default(),
chat_compat: Default::default(),
extra_headers: Default::default(),
context_window: std::num::NonZeroU64::new(context_window)
.expect("test context_window must be non-zero"),
@@ -1588,6 +1588,7 @@ mod reasoning_compaction_regression_tests {
temperature: Some(0.7),
top_p: None,
api_backend: ApiBackend::ChatCompletions,
chat_compat: Default::default(),
auth_scheme: Default::default(),
extra_headers: Default::default(),
context_window: 256_000,