Fix DeepSeek Reasoner tool-call handling and add reasoning_content support (#44301)

Peter Kรถnig and Ben Brandt created

## Closes #43887

## Release Notes:

### Problem
DeepSeek's reasoning mode API requires `reasoning_content` to be
included in assistant messages that precede tool calls. Without it, the
API returns a 400 error:

```
Missing `reasoning_content` field in the assistant message at message index 2
```

### Added/Fixed/Improved
- Add `reasoning_content` field to `RequestMessage::Assistant` in
`crates/deepseek/src/deepseek.rs`
- Accumulate thinking content from `MessageContent::Thinking` and attach
it to the next assistant/tool-call message
- Wire reasoning content through the language model provider in
`crates/language_models/src/provider/deepseek.rs`

### Testing
- Verified with DeepSeek Reasoner model using tool calls
- Confirmed reasoning content is properly included in API requests

Fixes tool-call errors when using DeepSeek's reasoning mode.

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>

Change summary

crates/deepseek/src/deepseek.rs                 |  2 ++
crates/language_models/src/provider/deepseek.rs | 11 +++++++++--
2 files changed, 11 insertions(+), 2 deletions(-)

Detailed changes

crates/deepseek/src/deepseek.rs ๐Ÿ”—

@@ -155,6 +155,8 @@ pub enum RequestMessage {
         content: Option<String>,
         #[serde(default, skip_serializing_if = "Vec::is_empty")]
         tool_calls: Vec<ToolCall>,
+        #[serde(default, skip_serializing_if = "Option::is_none")]
+        reasoning_content: Option<String>,
     },
     User {
         content: String,

crates/language_models/src/provider/deepseek.rs ๐Ÿ”—

@@ -332,9 +332,11 @@ pub fn into_deepseek(
     model: &deepseek::Model,
     max_output_tokens: Option<u64>,
 ) -> deepseek::Request {
-    let is_reasoner = *model == deepseek::Model::Reasoner;
+    let is_reasoner = model == &deepseek::Model::Reasoner;
 
     let mut messages = Vec::new();
+    let mut current_reasoning: Option<String> = None;
+
     for message in request.messages {
         for content in message.content {
             match content {
@@ -343,10 +345,14 @@ pub fn into_deepseek(
                     Role::Assistant => deepseek::RequestMessage::Assistant {
                         content: Some(text),
                         tool_calls: Vec::new(),
+                        reasoning_content: current_reasoning.take(),
                     },
                     Role::System => deepseek::RequestMessage::System { content: text },
                 }),
-                MessageContent::Thinking { .. } => {}
+                MessageContent::Thinking { text, .. } => {
+                    // Accumulate reasoning content for next assistant message
+                    current_reasoning.get_or_insert_default().push_str(&text);
+                }
                 MessageContent::RedactedThinking(_) => {}
                 MessageContent::Image(_) => {}
                 MessageContent::ToolUse(tool_use) => {
@@ -369,6 +375,7 @@ pub fn into_deepseek(
                         messages.push(deepseek::RequestMessage::Assistant {
                             content: None,
                             tool_calls: vec![tool_call],
+                            reasoning_content: current_reasoning.take(),
                         });
                     }
                 }