DeepSeek's reasoning API enforces: if an assistant with tool_calls sits
between two user messages, its reasoning_content must be passed back.
The synthetic todo nag pair creates an AssistantChatMessage with tool_calls
but no reasoning_content, which triggered a 400 error:
"The reasoning_content in the thinking mode must be passed back to the API."
Fix:
1. _append_synthetic_todo_list now sets reasoning_content="" so NEW pairs
carry the field.
2. refresh_synthetic_todo_nags runs _inject_reasoning_content_for_synthetic
to fix EXISTING pairs (in DB) even if reasoning_content was UNSET.
3. The earlier _inject_thinking change is reverted in the next commit since
the real root cause was the missing reasoning_content on forged tool_call
messages, not the thinking parameter itself.
Adds 7 unit tests for _synthetic_todo_pair_after and committed_prefix_end
with synthetic todo nag pairs: survival across rollback, detection at
correct index, immunity to real tool calls, and multiple rollbacks.
These directly verify the guard that prevents duplicate turn-start
nag injection on /retry when a synthetic pair from a previous failed
attempt survived rollback (committed tool batch).
DeepSeek's reasoning model requires the `thinking` parameter to be
explicitly set in every request (default is enabled). The agent loop
constructs the base ChatCompletionsParam which lacks this field, so the
DeepseekOpenAIClient now injects `thinking: {type: "enabled"}` into the
JSON request body after msgspec encoding.
Fixes the HTTP 400 error:
"The `reasoning_content` in the thinking mode must be passed back to the API."
which occurred when a cancelled turn was retried and the request history
contained assistant messages with reasoning_content but no thinking param.
needs_review is true for any non-empty stack so end-of-turn inject
keeps firing under synthetic_tool. Message history keeps only the last
todo-nag tool pair (dedupe by todo-nag- tool_call_id).