Feat/english thinking when hidden#1843
Conversation
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
Replace "To save tokens" with a rationale based on the user's intent: when thinking is hidden, there is no reason to localize it.
There was a problem hiding this comment.
Code Review
This pull request introduces a show_thinking configuration option across the TUI engine, prompts, and UI components. When disabled, the system prompt is updated to instruct the model to perform its internal reasoning in English to save tokens, while still responding in the user's preferred language. Feedback was provided regarding the formatting of the multi-line system prompt string in crates/tui/src/prompts.rs, where the use of line continuation characters might introduce unintended whitespace.
| full_prompt.push_str( | ||
| "\n\n## Thinking Language\n\n\ | ||
| 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.", | ||
| ); |
There was a problem hiding this comment.
The use of \ for line continuation in this string literal will include all the leading whitespace from the subsequent lines. This results in a single long line with multiple spaces between words, which is likely not the intended formatting for the prompt. Using string literal concatenation is a cleaner way to format this multi-line string while maintaining readability in the code.
full_prompt.push_str(
"\n\n## Thinking Language\n\n"
"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."
);
Problem
When
show_thinkingis disabled, thinking blocks are hidden from theUI (
HistoryCell::Thinkingis filtered out inhistory.rs), but theAPI still generates
reasoning_content(controlled separately byreasoning_effort). The## Languagerule inbase.mdforces thethinking chain to match the user's input language — so a Chinese user
pays for Chinese thinking they never see.
The token-count difference between Chinese and English reasoning is
modest (DeepSeek's tokenizer handles CJK efficiently), but there is
still waste: the surrounding system prompt is English, mixed-language
reasoning fragments the token stream, and invisible content has no
reason to be localized at all.
Solution
Inject a
## Thinking Languageoverride into the system prompt whenshow_thinkingis false. The model is told:Benefits:
English system prompt, improving prefix-cache hit rates.
identifiers, file paths, and API names with natural language.
Switching between CJK and ASCII mid-stream creates unnecessary
token-boundary breaks.
no user-facing reason to localize it.
Changes (6 files, +53 lines)
crates/tui/src/prompts.rsshow_thinkingtoPromptSessionContext; inject## Thinking Languageoverride|
|
crates/tui/src/core/ops.rs| Addshow_thinkingtoOp::SendMessage||
crates/tui/src/core/engine.rs| Add toEngineConfig; thread throughhandle_send_messageandrefresh_system_prompt||
crates/tui/src/tui/ui.rs| Wireapp.show_thinkingthroughbuild_engine_configand prompt construction ||
crates/tui/src/main.rs| Passshow_thinkingin CLI exec path ||
crates/tui/src/runtime_threads.rs|show_thinking: falsefor background agent threads |Data flow:
Settings::show_thinking→App::show_thinking→Op::SendMessage→EngineConfig::show_thinking→PromptSessionContext::show_thinking→ override injection.Behavior
show_thinkingtrue(default)falseTesting
cargo test --all-features— 3120 passed; 6 pre-existingfailures unrelated to this change (API key / temp directory /
AGENTS.md path)
cargo fmt --all -- --check— cleancargo clippy --all-targets --all-features— clean (2pre-existing
needless_returnwarnings unrelated to this change)Checklist
updated for the new
PromptSessionContextfield)change; the entire path is a backend-only data flow)