From 57aa4e5fc929d6ceb79392eb52a3ee0cb14b534e Mon Sep 17 00:00:00 2001 From: cmyyy Date: Wed, 20 May 2026 23:45:49 +0800 Subject: [PATCH 1/2] feat: force English reasoning_content when show_thinking is off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `show_thinking` is disabled in settings, thinking blocks are hidden from the UI but the API still generates `reasoning_content`. Because of the `## Language` rule in the system prompt, the thinking chain follows the user's input language — if the user writes in Chinese, the model thinks in Chinese for content they never see. The tokenizer-level savings are modest (DeepSeek's vocab handles Chinese efficiently), but the real benefit is keeping invisible reasoning in English for better prompt-cache locality and fewer token-boundary breaks in mixed-language (code + natural language) contexts. When thinking is hidden, there is no reason not to use the most cache-friendly language. Changes: - Add `show_thinking: bool` to `PromptSessionContext`, `EngineConfig`, and `Op::SendMessage` - Inject a `## Thinking Language` override when `show_thinking` is false, redirecting `reasoning_content` to English while the final reply still matches the user's language - Wire the field through the engine and TUI layers --- crates/tui/src/core/engine.rs | 12 ++++++++++++ crates/tui/src/core/ops.rs | 1 + crates/tui/src/main.rs | 2 ++ crates/tui/src/prompts.rs | 32 +++++++++++++++++++++++++++++++ crates/tui/src/runtime_threads.rs | 2 ++ crates/tui/src/tui/ui.rs | 3 +++ 6 files changed, 52 insertions(+) diff --git a/crates/tui/src/core/engine.rs b/crates/tui/src/core/engine.rs index 5bc237bad..3eed19c9a 100644 --- a/crates/tui/src/core/engine.rs +++ b/crates/tui/src/core/engine.rs @@ -99,6 +99,10 @@ pub struct EngineConfig { /// When true, the model is instructed to respond in the current locale /// and a post-hoc translation layer replaces remaining English output. pub translation_enabled: bool, + /// When false, thinking blocks are hidden in the TUI. Propagated to the + /// system prompt builder so `reasoning_content` stays in English (saving + /// tokens) while the final reply still matches the user's language. + pub show_thinking: bool, /// Maximum number of assistant steps before stopping. pub max_steps: u32, /// Maximum number of concurrently active subagents. @@ -181,6 +185,7 @@ impl Default for EngineConfig { instructions: Vec::new(), project_context_pack_enabled: true, translation_enabled: false, + show_thinking: true, max_steps: 100, max_subagents: DEFAULT_MAX_SUBAGENTS, features: Features::with_defaults(), @@ -434,6 +439,7 @@ impl Engine { project_context_pack_enabled: config.project_context_pack_enabled, locale_tag: &config.locale_tag, translation_enabled: config.translation_enabled, + show_thinking: config.show_thinking, }, session.approval_mode, ); @@ -589,6 +595,7 @@ impl Engine { auto_approve, approval_mode, translation_enabled, + show_thinking, } => { self.handle_send_message( content, @@ -603,6 +610,7 @@ impl Engine { auto_approve, approval_mode, translation_enabled, + show_thinking, ) .await; } @@ -802,6 +810,7 @@ impl Engine { self.session.auto_approve, self.session.approval_mode, self.config.translation_enabled, + self.config.show_thinking, ) .await; } @@ -890,6 +899,7 @@ impl Engine { auto_approve: bool, approval_mode: crate::tui::approval::ApprovalMode, translation_enabled: bool, + show_thinking: bool, ) { // Reset cancel token for fresh turn (in case previous was cancelled) self.reset_cancel_token(); @@ -973,6 +983,7 @@ impl Engine { self.session.trust_mode = trust_mode; self.config.trust_mode = trust_mode; self.config.translation_enabled = translation_enabled; + self.config.show_thinking = show_thinking; self.session.auto_approve = auto_approve; self.session.approval_mode = if auto_approve { crate::tui::approval::ApprovalMode::Auto @@ -1792,6 +1803,7 @@ impl Engine { project_context_pack_enabled: self.config.project_context_pack_enabled, locale_tag: &self.config.locale_tag, translation_enabled: self.config.translation_enabled, + show_thinking: self.config.show_thinking, }, self.session.approval_mode, ); diff --git a/crates/tui/src/core/ops.rs b/crates/tui/src/core/ops.rs index a77a26257..b40967060 100644 --- a/crates/tui/src/core/ops.rs +++ b/crates/tui/src/core/ops.rs @@ -31,6 +31,7 @@ pub enum Op { auto_approve: bool, approval_mode: ApprovalMode, translation_enabled: bool, + show_thinking: bool, }, /// Cancel the current request diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 857363102..3894d0f68 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -4636,6 +4636,7 @@ async fn run_exec_agent( instructions: config.instructions_paths(), project_context_pack_enabled: config.project_context_pack_enabled(), translation_enabled: false, + show_thinking: true, max_steps: 100, max_subagents, features: config.features(), @@ -4725,6 +4726,7 @@ async fn run_exec_agent( trust_mode, auto_approve, translation_enabled: false, + show_thinking: true, approval_mode: if auto_approve { crate::tui::approval::ApprovalMode::Auto } else { diff --git a/crates/tui/src/prompts.rs b/crates/tui/src/prompts.rs index f801ed596..9eb8a9ab2 100644 --- a/crates/tui/src/prompts.rs +++ b/crates/tui/src/prompts.rs @@ -28,6 +28,11 @@ pub struct PromptSessionContext<'a> { /// to the system prompt instructing the model to respond in /// the resolved session locale. pub translation_enabled: bool, + /// When false, the user has hidden thinking blocks in the UI. + /// The prompt builder injects a language override so + /// `reasoning_content` stays in English (saving tokens) while + /// the final reply still matches the user's language. + pub show_thinking: bool, } /// Conventional location for the structured session relay artifact (#32). @@ -549,6 +554,7 @@ pub fn system_prompt_for_mode_with_context_and_skills( project_context_pack_enabled: true, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) } @@ -641,6 +647,22 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval( ); } + // 2.3b. Thinking language override — when the user has hidden + // thinking blocks in the UI, redirect reasoning_content to + // English regardless of user language. This saves tokens + // (English is the most token-efficient language) while keeping + // the final reply in the user's language as described above. + if !session_context.show_thinking { + full_prompt.push_str( + "\n\n## Thinking Language\n\n\ + The user has disabled thinking display in settings. To save tokens, \ + your `reasoning_content` (internal thinking) MUST be in English \ + regardless of the user's language. This directive overrides the \ + `## Language` section above for reasoning_content only. \ + Your final reply must STILL match the user's language.", + ); + } + // 3. Skills block. #432: walks every candidate workspace // skills directory (`.agents/skills`, `skills`, // `.opencode/skills`, `.claude/skills`, `.cursor/skills`) plus global @@ -881,6 +903,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "zh-Hans", translation_enabled: false, + show_thinking: true, }, ApprovalMode::Suggest, ) { @@ -950,6 +973,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "zh-Hans", translation_enabled: false, + show_thinking: true, }, ApprovalMode::Suggest, ) { @@ -994,6 +1018,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ApprovalMode::Suggest, ) { @@ -1083,6 +1108,7 @@ mod tests { project_context_pack_enabled: true, locale_tag: "ja", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1118,6 +1144,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1145,6 +1172,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1174,6 +1202,7 @@ mod tests { project_context_pack_enabled: false, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1201,6 +1230,7 @@ mod tests { project_context_pack_enabled: true, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1395,6 +1425,7 @@ mod tests { project_context_pack_enabled: true, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, @@ -1428,6 +1459,7 @@ mod tests { project_context_pack_enabled: true, locale_tag: "en", translation_enabled: false, + show_thinking: true, }, ) { SystemPrompt::Text(text) => text, diff --git a/crates/tui/src/runtime_threads.rs b/crates/tui/src/runtime_threads.rs index 5ec3c8f6d..025ec3945 100644 --- a/crates/tui/src/runtime_threads.rs +++ b/crates/tui/src/runtime_threads.rs @@ -1616,6 +1616,7 @@ impl RuntimeThreadManager { trust_mode, auto_approve, translation_enabled: false, + show_thinking: false, approval_mode: if auto_approve { crate::tui::approval::ApprovalMode::Auto } else { @@ -1933,6 +1934,7 @@ impl RuntimeThreadManager { instructions: self.config.instructions_paths(), project_context_pack_enabled: self.config.project_context_pack_enabled(), translation_enabled: false, + show_thinking: false, max_steps: 100, max_subagents: self.config.max_subagents().clamp(1, MAX_SUBAGENTS), features: self.config.features(), diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 520bda732..931cd373d 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -676,6 +676,7 @@ fn build_engine_config(app: &App, config: &Config) -> EngineConfig { instructions: config.instructions_paths(), project_context_pack_enabled: config.project_context_pack_enabled(), translation_enabled: app.translation_enabled, + show_thinking: app.show_thinking, // Effectively unlimited. V4 has a 1M context window and the user // wants the model running until it's actually done. The previous cap // of 100 hit the ceiling on long multi-step plans (wide refactors, @@ -3882,6 +3883,7 @@ async fn dispatch_user_message( project_context_pack_enabled: config.project_context_pack_enabled(), locale_tag: app.ui_locale.tag(), translation_enabled: app.translation_enabled, + show_thinking: app.show_thinking, }, ), ); @@ -3978,6 +3980,7 @@ async fn dispatch_user_message( auto_approve: app.mode == AppMode::Yolo, approval_mode: app.approval_mode, translation_enabled: app.translation_enabled, + show_thinking: app.show_thinking, }) .await { From 7ab0c4dfe885f254dd4ef9a79f77ea183f9efeeb Mon Sep 17 00:00:00 2001 From: cmyyy Date: Thu, 21 May 2026 00:03:34 +0800 Subject: [PATCH 2/2] fixup: refine thinking language override wording Replace "To save tokens" with a rationale based on the user's intent: when thinking is hidden, there is no reason to localize it. --- crates/tui/src/prompts.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/tui/src/prompts.rs b/crates/tui/src/prompts.rs index 9eb8a9ab2..ccba3ca22 100644 --- a/crates/tui/src/prompts.rs +++ b/crates/tui/src/prompts.rs @@ -655,11 +655,12 @@ pub fn system_prompt_for_mode_with_context_skills_session_and_approval( if !session_context.show_thinking { full_prompt.push_str( "\n\n## Thinking Language\n\n\ - The user has disabled thinking display in settings. To save tokens, \ - your `reasoning_content` (internal thinking) MUST be in English \ - regardless of the user's language. This directive overrides the \ - `## Language` section above for reasoning_content only. \ - Your final reply must STILL match the user's language.", + The user has disabled thinking display in settings — they will \ + never see your `reasoning_content`. Therefore, your internal \ + thinking MUST be in English regardless of the user's language. \ + This directive overrides the `## Language` section above for \ + reasoning_content only. Your final reply must STILL match the \ + user's language.", ); }