From 35595f8edc539ddfb3bf88d5f51ebfb17c94625e Mon Sep 17 00:00:00 2001 From: Hunter Bown Date: Thu, 23 Apr 2026 23:08:44 -0500 Subject: [PATCH] fix: normalize legacy DeepSeek aliases to V4 flash --- CHANGELOG.md | 14 +++++------ config.example.toml | 2 +- crates/cli/src/main.rs | 12 ++++----- crates/config/src/lib.rs | 10 ++++---- crates/protocol/tests/parity_protocol.rs | 4 +-- crates/tui/src/client.rs | 32 ++++++++++++------------ crates/tui/src/commands/config.rs | 10 ++++---- crates/tui/src/commands/core.rs | 12 ++++----- crates/tui/src/commands/debug.rs | 2 +- crates/tui/src/commands/init.rs | 2 +- crates/tui/src/commands/mod.rs | 4 +-- crates/tui/src/commands/note.rs | 2 +- crates/tui/src/commands/queue.rs | 2 +- crates/tui/src/commands/review.rs | 2 +- crates/tui/src/commands/session.rs | 2 +- crates/tui/src/commands/skills.rs | 2 +- crates/tui/src/commands/task.rs | 2 +- crates/tui/src/config.rs | 25 +++++++++--------- crates/tui/src/models.rs | 14 +++++------ crates/tui/src/runtime_api.rs | 4 +-- crates/tui/src/settings.rs | 2 +- crates/tui/src/tui/session_picker.rs | 2 +- crates/tui/src/tui/ui/tests.rs | 10 ++++---- crates/tui/src/tui/views/mod.rs | 10 +++----- crates/tui/src/tui/widgets/header.rs | 12 ++++----- crates/tui/src/tui/widgets/mod.rs | 2 +- docs/CONFIGURATION.md | 4 +-- docs/RUNTIME_API.md | 6 ++--- 28 files changed, 101 insertions(+), 106 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f27ca0..387a913d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed -- DeepSeek V4 thinking-mode tool calls now preserve prior assistant `reasoning_content` whenever a tool call is replayed, matching DeepSeek's multi-turn contract and avoiding HTTP 400 rejections on later turns. -- Raw Chat Completions requests now send DeepSeek's top-level `thinking` parameter instead of the OpenAI SDK-only `extra_body` wrapper. -- Context-window budgeting now treats legacy `deepseek-chat` / `deepseek-reasoner` aliases as V4 Flash's 1M-token context window. -- npm wrapper first-run downloads now use process-unique temp files so concurrent `deepseek` / `deepseek-tui` invocations do not race on `*.download` files. - ## [0.4.0] - 2026-04-23 ### Added @@ -24,12 +18,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - **Default model flipped to `deepseek-v4-pro`** (from `deepseek-reasoner`). - `deepseek-chat` / `deepseek-reasoner` remain as silent aliases of `deepseek-v4-flash` for API compatibility; priced identically. -- **Context compaction**: raised `MAX_COMPACTION_MESSAGE_THRESHOLD` from 150 → 500 so 1M-context models can use proportionally more history before message-count compaction. Token-based compaction still triggers at 80% of the window and scales automatically. +- **Context compaction**: 1M-context V4 models now compact at 800k input tokens or 2,000 messages, so short/tool-heavy sessions do not compact as if they were 128k-context runs. - Cycling modes is now Tab-only; Shift+Tab is repurposed for reasoning-effort (reverse-mode cycle was low-value with only three modes). - Updated help/hint strings, validator error messages, and the model picker to reference V4 IDs. ### Fixed - `requires_reasoning_content` now recognizes `deepseek-v4*` so thinking streams render correctly on V4 models. +- DeepSeek V4 thinking-mode tool calls now preserve prior assistant `reasoning_content` whenever a tool call is replayed, matching DeepSeek's multi-turn contract and avoiding HTTP 400 rejections on later turns. +- Raw Chat Completions requests now send DeepSeek's top-level `thinking` parameter instead of the OpenAI SDK-only `extra_body` wrapper. +- Config, env, and UI model selection now normalize legacy DeepSeek aliases to `deepseek-v4-flash` instead of preserving old model labels. +- npm wrapper first-run downloads now use process-unique temp files so concurrent `deepseek` / `deepseek-tui` invocations do not race on `*.download` files. ## [0.3.33] - 2026-04-11 @@ -218,7 +216,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Expanded reasoning-model detection for chat history reconstruction (supports R-series and reasoner-style naming without hardcoding single versions). -- Aligned docs/config examples with actual runtime default model (`deepseek-v3.2`). +- Aligned docs/config examples with the then-current runtime default model. ## [0.3.14] - 2026-02-05 diff --git a/config.example.toml b/config.example.toml index bff40a73..89b6509a 100644 --- a/config.example.toml +++ b/config.example.toml @@ -97,7 +97,7 @@ exponential_base = 2.0 # enabled = false # Enable auto-compaction # token_threshold = 50000 # Trigger compaction above this token estimate # message_threshold = 50 # Or above this message count -# model = "deepseek-chat" # Model to use for summarization +# model = "deepseek-v4-flash" # Model to use for summarization # cache_summary = true # Keep summary blocks stable; DeepSeek context caching is automatic # ───────────────────────────────────────────────────────────────────────────────── diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 2d4355cb..621ed583 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -872,12 +872,12 @@ mod tests { })) if key == "provider" )); - let cli = parse_ok(&["deepseek", "config", "set", "model", "deepseek-chat"]); + let cli = parse_ok(&["deepseek", "config", "set", "model", "deepseek-v4-flash"]); assert!(matches!( cli.command, Some(Commands::Config(ConfigArgs { command: ConfigCommand::Set { ref key, ref value } - })) if key == "model" && value == "deepseek-chat" + })) if key == "model" && value == "deepseek-v4-flash" )); let cli = parse_ok(&["deepseek", "config", "unset", "model"]); @@ -922,7 +922,7 @@ mod tests { })) )); - let cli = parse_ok(&["deepseek", "model", "resolve", "deepseek-chat"]); + let cli = parse_ok(&["deepseek", "model", "resolve", "deepseek-v4-flash"]); assert!(matches!( cli.command, Some(Commands::Model(ModelArgs { @@ -930,7 +930,7 @@ mod tests { model: Some(ref model), provider: None } - })) if model == "deepseek-chat" + })) if model == "deepseek-v4-flash" )); let cli = parse_ok(&[ @@ -939,7 +939,7 @@ mod tests { "resolve", "--provider", "deepseek", - "deepseek-reasoner", + "deepseek-v4-pro", ]); assert!(matches!( cli.command, @@ -948,7 +948,7 @@ mod tests { model: Some(ref model), provider: Some(ProviderArg::Deepseek) } - })) if model == "deepseek-reasoner" + })) if model == "deepseek-v4-pro" )); } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 1b0513af..9fdaec0a 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -595,7 +595,7 @@ mod tests { let config = ConfigToml { api_key: Some("root-key".to_string()), base_url: Some("https://api.deepseek.com".to_string()), - default_text_model: Some("deepseek-chat".to_string()), + default_text_model: Some("deepseek-v4-pro".to_string()), ..ConfigToml::default() }; @@ -604,7 +604,7 @@ mod tests { assert_eq!(resolved.provider, ProviderKind::Deepseek); assert_eq!(resolved.api_key.as_deref(), Some("root-key")); assert_eq!(resolved.base_url, "https://api.deepseek.com"); - assert_eq!(resolved.model, "deepseek-chat"); + assert_eq!(resolved.model, "deepseek-v4-pro"); } #[test] @@ -614,18 +614,18 @@ mod tests { let mut config = ConfigToml { api_key: Some("root-key".to_string()), base_url: Some("https://api.deepseek.com".to_string()), - default_text_model: Some("deepseek-chat".to_string()), + default_text_model: Some("deepseek-v4-pro".to_string()), ..ConfigToml::default() }; config.providers.deepseek.api_key = Some("provider-key".to_string()); config.providers.deepseek.base_url = Some("https://api.deepseeki.com".to_string()); - config.providers.deepseek.model = Some("deepseek-reasoner".to_string()); + config.providers.deepseek.model = Some("deepseek-v4-flash".to_string()); let resolved = config.resolve_runtime_options(&CliRuntimeOverrides::default()); assert_eq!(resolved.api_key.as_deref(), Some("provider-key")); assert_eq!(resolved.base_url, "https://api.deepseeki.com"); - assert_eq!(resolved.model, "deepseek-reasoner"); + assert_eq!(resolved.model, "deepseek-v4-flash"); } #[test] diff --git a/crates/protocol/tests/parity_protocol.rs b/crates/protocol/tests/parity_protocol.rs index 12eceaf5..358eb060 100644 --- a/crates/protocol/tests/parity_protocol.rs +++ b/crates/protocol/tests/parity_protocol.rs @@ -6,7 +6,7 @@ fn thread_resume_params_round_trip() { thread_id: "thread-123".to_string(), history: None, path: None, - model: Some("deepseek-reasoner".to_string()), + model: Some("deepseek-v4-pro".to_string()), model_provider: Some("deepseek".to_string()), cwd: None, approval_policy: Some("on-request".to_string()), @@ -23,7 +23,7 @@ fn thread_resume_params_round_trip() { match decoded { ThreadRequest::Resume(params) => { assert_eq!(params.thread_id, "thread-123"); - assert_eq!(params.model.as_deref(), Some("deepseek-reasoner")); + assert_eq!(params.model.as_deref(), Some("deepseek-v4-pro")); assert!(params.persist_extended_history); } other => panic!("unexpected request: {other:?}"), diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 8be8823c..a2f2dcc0 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -2201,7 +2201,7 @@ mod tests { }, ], }; - let out = build_chat_messages(None, &[message], "deepseek-reasoner"); + let out = build_chat_messages(None, &[message], "deepseek-v4-pro"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2221,7 +2221,7 @@ mod tests { thinking: "plan".to_string(), }], }; - let out = build_chat_messages(None, &[message], "deepseek-chat"); + let out = build_chat_messages(None, &[message], "deepseek-v4-flash"); assert!( !out.iter() .any(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2236,7 +2236,7 @@ mod tests { thinking: "plan".to_string(), }], }; - let out = build_chat_messages(None, &[message], "deepseek-reasoner"); + let out = build_chat_messages(None, &[message], "deepseek-v4-pro"); assert!( !out.iter() .any(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2292,7 +2292,7 @@ mod tests { }], }, ]; - let out = build_chat_messages(None, &messages, "deepseek-reasoner"); + let out = build_chat_messages(None, &messages, "deepseek-v4-pro"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2352,7 +2352,7 @@ mod tests { }], }, ]; - let out = build_chat_messages(None, &messages, "deepseek-reasoner"); + let out = build_chat_messages(None, &messages, "deepseek-v4-pro"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2610,7 +2610,7 @@ mod tests { }], }]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); assert!( !out.iter() .any(|value| { value.get("role").and_then(Value::as_str) == Some("tool") }) @@ -2640,7 +2640,7 @@ mod tests { }, ]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); assert!( out.iter() .any(|value| { value.get("role").and_then(Value::as_str) == Some("tool") }) @@ -2675,7 +2675,7 @@ mod tests { }, ]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2718,7 +2718,7 @@ mod tests { }, ]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")); @@ -2759,7 +2759,7 @@ mod tests { }, ]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); let assistant = out .iter() .find(|value| value.get("role").and_then(Value::as_str) == Some("assistant")) @@ -2829,7 +2829,7 @@ mod tests { }, ]; - let out = build_chat_messages(None, &messages, "deepseek-chat"); + let out = build_chat_messages(None, &messages, "deepseek-v4-flash"); let assistant = out .iter() .find(|v| v.get("role").and_then(Value::as_str) == Some("assistant")); @@ -2849,9 +2849,9 @@ mod tests { let payload = r#"{ "object": "list", "data": [ - {"id": "deepseek-r1", "object": "model", "owned_by": "deepseek", "created": 1}, - {"id": "deepseek-chat", "object": "model"}, - {"id": "deepseek-r1", "object": "model", "owned_by": "deepseek", "created": 1} + {"id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek", "created": 1}, + {"id": "deepseek-v4-flash", "object": "model"}, + {"id": "deepseek-v4-pro", "object": "model", "owned_by": "deepseek", "created": 1} ] }"#; @@ -2860,12 +2860,12 @@ mod tests { models, vec![ AvailableModel { - id: "deepseek-chat".to_string(), + id: "deepseek-v4-flash".to_string(), owned_by: None, created: None }, AvailableModel { - id: "deepseek-r1".to_string(), + id: "deepseek-v4-pro".to_string(), owned_by: Some("deepseek".to_string()), created: Some(1) } diff --git a/crates/tui/src/commands/config.rs b/crates/tui/src/commands/config.rs index dffb501a..2d1a1714 100644 --- a/crates/tui/src/commands/config.rs +++ b/crates/tui/src/commands/config.rs @@ -400,11 +400,11 @@ mod tests { fn test_set_model_updates_app_state() { let mut app = create_test_app(); let _old_model = app.model.clone(); - let result = set_config(&mut app, Some("model deepseek-reasoner")); + let result = set_config(&mut app, Some("model deepseek-v4-flash")); assert!(result.message.is_some()); let msg = result.message.unwrap(); - assert!(msg.contains("model = deepseek-reasoner")); - assert_eq!(app.model, "deepseek-reasoner"); + assert!(msg.contains("model = deepseek-v4-flash")); + assert_eq!(app.model, "deepseek-v4-flash"); assert!(matches!( result.action, Some(AppAction::UpdateCompaction(_)) @@ -424,10 +424,10 @@ mod tests { #[test] fn test_set_model_with_save_flag() { let mut app = create_test_app(); - let _result = set_config(&mut app, Some("model deepseek-reasoner --save")); + let _result = set_config(&mut app, Some("model deepseek-v4-flash --save")); // Note: This test may fail in environments where settings can't be saved // The important thing is that the model is updated - assert_eq!(app.model, "deepseek-reasoner"); + assert_eq!(app.model, "deepseek-v4-flash"); } #[test] diff --git a/crates/tui/src/commands/core.rs b/crates/tui/src/commands/core.rs index 419c79eb..13e086f6 100644 --- a/crates/tui/src/commands/core.rs +++ b/crates/tui/src/commands/core.rs @@ -212,7 +212,7 @@ mod tests { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-reasoner".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("/tmp/test-workspace"), allow_shell: false, use_alt_screen: true, @@ -328,16 +328,16 @@ mod tests { fn test_model_change_updates_state() { let mut app = create_test_app(); let old_model = app.model.clone(); - let result = model(&mut app, Some("deepseek-reasoner")); + let result = model(&mut app, Some("deepseek-v4-flash")); assert!(result.message.is_some()); let msg = result.message.unwrap(); assert!(msg.contains(&old_model)); - assert!(msg.contains("deepseek-reasoner")); + assert!(msg.contains("deepseek-v4-flash")); assert!(matches!( result.action, Some(AppAction::UpdateCompaction(_)) )); - assert_eq!(app.model, "deepseek-reasoner"); + assert_eq!(app.model, "deepseek-v4-flash"); assert_eq!(app.last_prompt_tokens, None); assert_eq!(app.last_completion_tokens, None); } @@ -364,8 +364,8 @@ mod tests { let msg = result.message.unwrap(); assert!(msg.contains("Invalid model")); assert!(msg.contains("DeepSeek model ID")); - assert!(msg.contains("deepseek-chat")); - assert!(msg.contains("deepseek-reasoner")); + assert!(msg.contains("deepseek-v4-pro")); + assert!(msg.contains("deepseek-v4-flash")); assert!(result.action.is_none()); } diff --git a/crates/tui/src/commands/debug.rs b/crates/tui/src/commands/debug.rs index d12f8ae6..9cbe25dc 100644 --- a/crates/tui/src/commands/debug.rs +++ b/crates/tui/src/commands/debug.rs @@ -123,7 +123,7 @@ mod tests { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("/tmp/test-workspace"), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/init.rs b/crates/tui/src/commands/init.rs index f6c684ad..18efccfd 100644 --- a/crates/tui/src/commands/init.rs +++ b/crates/tui/src/commands/init.rs @@ -161,7 +161,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/mod.rs b/crates/tui/src/commands/mod.rs index eb7d5492..e62f38f4 100644 --- a/crates/tui/src/commands/mod.rs +++ b/crates/tui/src/commands/mod.rs @@ -505,7 +505,7 @@ mod tests { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-reasoner".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("."), allow_shell: false, use_alt_screen: true, @@ -562,7 +562,7 @@ mod tests { #[test] fn removed_set_and_deepseek_commands_show_migration_hints() { let mut app = create_test_app(); - let set_result = execute("/set model deepseek-reasoner", &mut app); + let set_result = execute("/set model deepseek-v4-pro", &mut app); let set_msg = set_result .message .expect("legacy command should return an error message"); diff --git a/crates/tui/src/commands/note.rs b/crates/tui/src/commands/note.rs index 822e1edb..0fcef277 100644 --- a/crates/tui/src/commands/note.rs +++ b/crates/tui/src/commands/note.rs @@ -58,7 +58,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/queue.rs b/crates/tui/src/commands/queue.rs index 1b79a86e..b47810b6 100644 --- a/crates/tui/src/commands/queue.rs +++ b/crates/tui/src/commands/queue.rs @@ -137,7 +137,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/review.rs b/crates/tui/src/commands/review.rs index e2eb2a17..a35a8a74 100644 --- a/crates/tui/src/commands/review.rs +++ b/crates/tui/src/commands/review.rs @@ -71,7 +71,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/session.rs b/crates/tui/src/commands/session.rs index cb8d6be6..6725e57d 100644 --- a/crates/tui/src/commands/session.rs +++ b/crates/tui/src/commands/session.rs @@ -195,7 +195,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/skills.rs b/crates/tui/src/commands/skills.rs index 88e25a34..7b6f0426 100644 --- a/crates/tui/src/commands/skills.rs +++ b/crates/tui/src/commands/skills.rs @@ -117,7 +117,7 @@ mod tests { fn create_test_app_with_tmpdir(tmpdir: &TempDir) -> App { let options = TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: tmpdir.path().to_path_buf(), allow_shell: false, use_alt_screen: true, diff --git a/crates/tui/src/commands/task.rs b/crates/tui/src/commands/task.rs index 6d66a70a..52850bf1 100644 --- a/crates/tui/src/commands/task.rs +++ b/crates/tui/src/commands/task.rs @@ -50,7 +50,7 @@ mod tests { fn app() -> App { App::new( TuiOptions { - model: "deepseek-v3.2".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("."), allow_shell: false, use_alt_screen: false, diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 59432916..45d7064f 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -17,24 +17,20 @@ pub const DEFAULT_MAX_SUBAGENTS: usize = 5; pub const MAX_SUBAGENTS: usize = 20; pub const DEFAULT_TEXT_MODEL: &str = "deepseek-v4-pro"; const API_KEYRING_SENTINEL: &str = "__KEYRING__"; -pub const COMMON_DEEPSEEK_MODELS: &[&str] = &[ - "deepseek-v4-pro", - "deepseek-v4-flash", - "deepseek-chat", - "deepseek-reasoner", -]; +pub const COMMON_DEEPSEEK_MODELS: &[&str] = &["deepseek-v4-pro", "deepseek-v4-flash"]; /// Canonicalize common model aliases to stable DeepSeek IDs. /// -/// Legacy `deepseek-chat` / `deepseek-reasoner` remain as silent aliases: they -/// resolve to themselves for API compatibility and are priced as `deepseek-v4-flash`. +/// Legacy `deepseek-chat` / `deepseek-reasoner` remain silent aliases for the +/// current fast V4 model. #[must_use] pub fn canonical_model_name(model: &str) -> Option<&'static str> { match model.trim().to_ascii_lowercase().as_str() { "deepseek-v4-pro" | "deepseek-v4pro" => Some("deepseek-v4-pro"), "deepseek-v4-flash" | "deepseek-v4flash" => Some("deepseek-v4-flash"), - "deepseek-chat" | "deepseek-v3" | "deepseek-v3.2" => Some("deepseek-chat"), - "deepseek-reasoner" | "deepseek-r1" => Some("deepseek-reasoner"), + "deepseek-chat" | "deepseek-reasoner" | "deepseek-r1" | "deepseek-v3" | "deepseek-v3.2" => { + Some("deepseek-v4-flash") + } _ => None, } } @@ -1295,11 +1291,11 @@ mod tests { fn normalize_model_name_handles_aliases_and_future_ids() { assert_eq!( normalize_model_name("deepseek-v3.2").as_deref(), - Some("deepseek-chat") + Some("deepseek-v4-flash") ); assert_eq!( normalize_model_name("deepseek-r1").as_deref(), - Some("deepseek-reasoner") + Some("deepseek-v4-flash") ); assert_eq!( normalize_model_name("DeepSeek-V4").as_deref(), @@ -1345,7 +1341,10 @@ mod tests { } let config = Config::load(None, None)?; - assert_eq!(config.default_text_model.as_deref(), Some("deepseek-chat")); + assert_eq!( + config.default_text_model.as_deref(), + Some("deepseek-v4-flash") + ); Ok(()) } } diff --git a/crates/tui/src/models.rs b/crates/tui/src/models.rs index 0ecdaae8..68fdfaac 100644 --- a/crates/tui/src/models.rs +++ b/crates/tui/src/models.rs @@ -7,8 +7,8 @@ pub const DEEPSEEK_V4_CONTEXT_WINDOW_TOKENS: u32 = 1_000_000; pub const DEFAULT_COMPACTION_TOKEN_THRESHOLD: usize = 50_000; pub const DEFAULT_COMPACTION_MESSAGE_THRESHOLD: usize = 50; const COMPACTION_THRESHOLD_PERCENT: u32 = 80; -const COMPACTION_MESSAGE_DIVISOR: u32 = 1200; -const MAX_COMPACTION_MESSAGE_THRESHOLD: usize = 500; +const COMPACTION_MESSAGE_DIVISOR: u32 = 500; +const MAX_COMPACTION_MESSAGE_THRESHOLD: usize = 2_000; // === Core Message Types === @@ -433,21 +433,21 @@ mod tests { fn compaction_message_threshold_scales_with_context_window() { assert_eq!( compaction_message_threshold_for_model("deepseek-v3.2-128k"), - 106 + 256 ); assert_eq!(compaction_message_threshold_for_model("unknown-model"), 50); - // 200k / 1200 = 166, within the raised cap of 500. - assert_eq!(compaction_message_threshold_for_model("claude-3"), 166); + // 200k / 500 = 400, within the 2k cap. + assert_eq!(compaction_message_threshold_for_model("claude-3"), 400); } #[test] fn compaction_scales_for_deepseek_v4_1m_context() { // 80% of 1M = 800k tokens before token-based compaction. assert_eq!(compaction_threshold_for_model("deepseek-v4-pro"), 800_000); - // 1M / 1200 = 833, clamped to the 500-message cap. + // 1M / 500 = 2k messages before message-count compaction. assert_eq!( compaction_message_threshold_for_model("deepseek-v4-pro"), - 500 + 2_000 ); } } diff --git a/crates/tui/src/runtime_api.rs b/crates/tui/src/runtime_api.rs index a7dc3523..cf1cfcf7 100644 --- a/crates/tui/src/runtime_api.rs +++ b/crates/tui/src/runtime_api.rs @@ -2646,7 +2646,7 @@ mod tests { "updated_at": "2025-01-01T00:10:00Z", "message_count": 2, "total_tokens": 100, - "model": "deepseek-chat", + "model": "deepseek-v4-pro", "workspace": "/tmp/test", "mode": "agent" }, @@ -2678,7 +2678,7 @@ mod tests { .post(format!( "http://{addr}/v1/sessions/{session_id}/resume-thread" )) - .json(&json!({ "model": "deepseek-chat" })) + .json(&json!({ "model": "deepseek-v4-pro" })) .send() .await?; assert_eq!(resp.status(), StatusCode::CREATED); diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index 0430be7e..5a4fa528 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -311,7 +311,7 @@ impl Settings { ("max_history", "Max input history entries"), ( "default_model", - "Default model: any DeepSeek model ID (e.g. deepseek-chat)", + "Default model: any DeepSeek model ID (e.g. deepseek-v4-pro)", ), ] } diff --git a/crates/tui/src/tui/session_picker.rs b/crates/tui/src/tui/session_picker.rs index 8ac6481b..8a6af13f 100644 --- a/crates/tui/src/tui/session_picker.rs +++ b/crates/tui/src/tui/session_picker.rs @@ -605,7 +605,7 @@ mod tests { updated_at: Utc::now(), message_count: idx + 1, total_tokens: 100, - model: "deepseek-reasoner".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: std::path::PathBuf::from("/tmp"), mode: Some("agent".to_string()), } diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index bf51a038..ca98f62d 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -108,7 +108,7 @@ fn transcript_scroll_percent_is_clamped_and_relative() { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-reasoner".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("."), allow_shell: false, use_alt_screen: true, @@ -291,25 +291,25 @@ fn footer_state_label_prefers_compacting_then_thinking() { #[test] fn footer_status_line_spans_show_mode_model_and_status() { let mut app = create_test_app(); - app.model = "deepseek-chat".to_string(); + app.model = "deepseek-v4-flash".to_string(); let idle = spans_text(&footer_status_line_spans(&app, 60)); assert!(idle.contains("agent")); - assert!(idle.contains("deepseek-chat")); + assert!(idle.contains("deepseek-v4-flash")); assert!(idle.contains("\u{00B7}")); assert!(!idle.contains("ready")); app.is_loading = true; let active = spans_text(&footer_status_line_spans(&app, 60)); assert!(active.contains("agent")); - assert!(active.contains("deepseek-chat")); + assert!(active.contains("deepseek-v4-flash")); assert!(active.contains("thinking")); } #[test] fn footer_status_line_spans_truncate_long_model_names() { let mut app = create_test_app(); - app.model = "deepseek-reasoner-with-an-extremely-long-model-name".to_string(); + app.model = "deepseek-v4-pro-with-an-extremely-long-model-name".to_string(); app.is_loading = true; let line = spans_text(&footer_status_line_spans(&app, 40)); diff --git a/crates/tui/src/tui/views/mod.rs b/crates/tui/src/tui/views/mod.rs index 3c808e92..3dcf7afd 100644 --- a/crates/tui/src/tui/views/mod.rs +++ b/crates/tui/src/tui/views/mod.rs @@ -579,9 +579,7 @@ impl ConfigView { fn config_hint_for_key(key: &str) -> &'static str { match key { - "model" => { - "deepseek-v4-pro | deepseek-v4-flash | deepseek-* (aliases: deepseek-chat, deepseek-reasoner, deepseek-v3, deepseek-v3.2, deepseek-r1)" - } + "model" => "deepseek-v4-pro | deepseek-v4-flash | deepseek-*", "approval_mode" => "auto | suggest | never", "auto_compact" | "calm_mode" | "low_motion" | "show_thinking" | "show_tool_details" | "composer_border" => "on/off, true/false, yes/no, 1/0", @@ -1510,7 +1508,7 @@ mod tests { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-reasoner".to_string(), + model: "deepseek-v4-pro".to_string(), workspace: PathBuf::from("."), allow_shell: false, use_alt_screen: true, @@ -1571,7 +1569,7 @@ mod tests { .expect("editing should remain active after Ctrl+U"); assert!(cleared.buffer.is_empty()); - for ch in "deepseek-chat".chars() { + for ch in "deepseek-v4-flash".chars() { let action = view.handle_key(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE)); assert!(matches!(action, ViewAction::None)); } @@ -1584,7 +1582,7 @@ mod tests { persist, }) => { assert_eq!(key, "model"); - assert_eq!(value, "deepseek-chat"); + assert_eq!(value, "deepseek-v4-flash"); assert!(!persist); } other => panic!("expected config update emit, got {other:?}"), diff --git a/crates/tui/src/tui/widgets/header.rs b/crates/tui/src/tui/widgets/header.rs index 6b482a38..70f6e691 100644 --- a/crates/tui/src/tui/widgets/header.rs +++ b/crates/tui/src/tui/widgets/header.rs @@ -448,7 +448,7 @@ mod tests { let rendered = render_header( HeaderData::new( AppMode::Agent, - "deepseek-v3.2", + "deepseek-v4-pro", "deepseek-tui", false, palette::DEEPSEEK_INK, @@ -458,7 +458,7 @@ mod tests { assert!(rendered.contains("Agent")); assert!(rendered.contains("deepseek-tui")); - assert!(rendered.contains("deepseek-v3.2")); + assert!(rendered.contains("deepseek-v4-pro")); assert!(!rendered.contains("Plan")); assert!(!rendered.contains("Yolo")); } @@ -468,7 +468,7 @@ mod tests { let rendered = render_header( HeaderData::new( AppMode::Plan, - "deepseek-reasoner", + "deepseek-v4-pro", "workspace", true, palette::DEEPSEEK_INK, @@ -502,7 +502,7 @@ mod tests { let rendered = render_header( HeaderData::new( AppMode::Yolo, - "deepseek-chat", + "deepseek-v4-flash", "repo", true, palette::DEEPSEEK_INK, @@ -521,7 +521,7 @@ mod tests { let rendered = render_header( HeaderData::new( AppMode::Agent, - "deepseek-chat", + "deepseek-v4-flash", "repo", false, palette::DEEPSEEK_INK, @@ -538,7 +538,7 @@ mod tests { let rendered = render_header( HeaderData::new( AppMode::Agent, - "deepseek-chat", + "deepseek-v4-flash", "repo", false, palette::DEEPSEEK_INK, diff --git a/crates/tui/src/tui/widgets/mod.rs b/crates/tui/src/tui/widgets/mod.rs index f831c861..7176bc79 100644 --- a/crates/tui/src/tui/widgets/mod.rs +++ b/crates/tui/src/tui/widgets/mod.rs @@ -1040,7 +1040,7 @@ mod tests { fn create_test_app() -> App { let options = TuiOptions { - model: "deepseek-chat".to_string(), + model: "deepseek-v4-flash".to_string(), workspace: PathBuf::from("."), allow_shell: false, use_alt_screen: true, diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 9a14aae4..28b39c69 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -18,7 +18,7 @@ If both are set, `--config` wins. Environment variable overrides are applied aft The `deepseek` facade and `deepseek-tui` binary share the same config file for DeepSeek auth and model defaults. `deepseek login --api-key ...` writes the root `api_key` field that `deepseek-tui` reads directly, and `deepseek --model -deepseek-chat` is forwarded to the TUI as `DEEPSEEK_MODEL`. +deepseek-v4-flash` is forwarded to the TUI as `DEEPSEEK_MODEL`. To bootstrap MCP and skills directories at their resolved paths, run `deepseek-tui setup`. To only scaffold MCP, run `deepseek-tui mcp init`. @@ -118,7 +118,7 @@ If you are upgrading from older releases: - Old: `/deepseek` New: `/links` (aliases: `/dashboard`, `/api`) - Old: `/set model deepseek-reasoner` - New: `/config` and edit the `model` row to `deepseek-reasoner` + New: `/config` and edit the `model` row to `deepseek-v4-pro` or `deepseek-v4-flash` - Old: visible `Normal` mode or `default_mode = "normal"` New: use `Agent` / `default_mode = "agent"`; legacy `normal` still maps to `agent` - Old: discover `/set` in slash UX/help diff --git a/docs/RUNTIME_API.md b/docs/RUNTIME_API.md index 62f33301..053b996b 100644 --- a/docs/RUNTIME_API.md +++ b/docs/RUNTIME_API.md @@ -69,7 +69,7 @@ Resume session request body (all fields optional): ```json { - "model": "deepseek-chat", + "model": "deepseek-v4-pro", "mode": "agent" } ``` @@ -96,7 +96,7 @@ Request body: ```json { "prompt": "Summarize recent commits", - "model": "deepseek-reasoner", + "model": "deepseek-v4-pro", "mode": "agent", "workspace": ".", "allow_shell": false, @@ -132,7 +132,7 @@ Create thread request example: ```json { - "model": "deepseek-reasoner", + "model": "deepseek-v4-pro", "workspace": ".", "mode": "agent", "allow_shell": false,