fix(coding-agent): size title/commit/speech/classifier budgets for backends that ignore disableReasoning

Sizing `maxTokens` off the static `model.reasoning` catalog flag cannot
distinguish a thinking model catalogued `reasoning: false` (e.g. Qwen3
served locally via llama.cpp, whose bundled jinja chat template defaults
`enable_thinking: true`) from a model that never emits thinking. The
tight non-reasoning budget was consumed by the thinking preamble before
the useful output could be emitted, so every affected call silently
failed with `stopReason: "length"`.

Drop the `model.reasoning` conditional across every affected online call
site and always reserve the reasoning-safe budget. `maxTokens` is a hard
cap, not a target — non-thinking completions still return in the tiny
happy-path budget.

Sites fixed:
- utils/title-generator.ts       (30   -> 1024)
- utils/commit-message-generator (60   -> 1024)
- tts/speech-enhancer            (512  -> 1536)
- auto-thinking/classifier       online path (8  -> 1024); classifyLocal
                                  keeps its separate LOCAL_ANSWER_MAX_TOKENS
- session/unexpected-stop-classifier online path (16 -> 1024);
                                  classifyLocal keeps ANSWER_MAX_TOKENS

Fixes #4355
This commit is contained in:
roboomp
2026-07-03 00:47:20 +00:00
parent afc79e4cda
commit 8886a528dc
7 changed files with 72 additions and 25 deletions
@@ -16,8 +16,11 @@ const CLASSIFIER_SYSTEM_PROMPT = prompt.render(unexpectedStopClassifierPrompt);
*/
const ANSWER_MAX_TOKENS = 16;
/**
* Reasoning backends ignore `disableReasoning` on some providers, so reserve
* enough output room for the keyword to still land after unavoidable thinking.
* Online classifier budget. Sized to survive backends that ignore
* `disableReasoning` (e.g. Qwen3 via llama.cpp catalogued `reasoning: false`
* but still emitting thinking): the yes/no keyword needs to land after any
* unavoidable thinking preamble. `maxTokens` is a hard cap — non-thinking
* completions still return in a single word (issue #4355).
*/
const REASONING_SAFE_MAX_TOKENS = 1024;
@@ -74,7 +77,7 @@ async function classifyOnline(text: string, deps: ClassifyUnexpectedStopDeps): P
throw new Error(`unexpected-stop: no API key for ${model.provider}/${model.id}`);
}
const metadata = deps.metadataResolver?.(model.provider);
const maxTokens = model.reasoning ? Math.max(ANSWER_MAX_TOKENS, REASONING_SAFE_MAX_TOKENS) : ANSWER_MAX_TOKENS;
const maxTokens = REASONING_SAFE_MAX_TOKENS;
const response = await completeSimple(
model,