agent: Skip empty assistant messages for Mistral provider (#47579)

This small change *may* help with #39211, #35025 or #39031 although I'm
not positive it will fix it entirely. At a minimum, it should prevent
sending invalid requests to the Mistral API, which return a 400 error
and can prevent the thread from progressing if it gets stuck in a retry
loop.

This should prevent sending a message like `{ "role": "assistant",
"content": "" }` if for some reason the thread contains a message with
an empty text content and no tool calls.

Some users (including myself) have encountered this with the Devstral 2
models, although it doesn't seem trivial to reproduce:
https://github.com/zed-industries/zed/issues/37021#issuecomment-3728902562

When it occurs, error logs look something like this:
```
2026-01-24T19:53:35-08:00 ERROR [crates/agent/src/thread.rs:984] invalid type: string "Failed to connect to Mistral API: 400 Bad Request {\"object\":\"error\",\"message\":\"Assistant message must have either content or tool_calls, but not none.\",\"type\":\"invalid_request_assistant_message\",\"param\":null,\"code\":\"3240\"}", expected struct EditFileToolOutput
2026-01-24T19:53:35-08:00 ERROR [crates/agent/src/thread.rs:984] invalid type: string "Failed to connect to Mistral API: 400 Bad Request {\"object\":\"error\",\"message\":\"Assistant message must have either content or tool_calls, but not none.\",\"type\":\"invalid_request_assistant_message\",\"param\":null,\"code\":\"3240\"}", expected struct EditFileToolOutput
2026-01-24T19:53:35-08:00 ERROR [crates/agent/src/thread.rs:984] invalid type: string "Failed to connect to Mistral API: 400 Bad Request {\"object\":\"error\",\"message\":\"Assistant message must have either content or tool_calls, but not none.\",\"type\":\"invalid_request_assistant_message\",\"param\":null,\"code\":\"3240\"}", expected struct EditFileToolOutput
```

Release Notes:

- Fixed agent sometimes sending invalid messages to Mistral API
This commit is contained in:
Ian Chamberlain authored and GitHub committed 2026-02-13 11:49:56 +00:00
1 parent c981245a72
commit 49dee40073
1 file changed
+10
@@ -405,6 +405,9 @@ pub fn into_mistral(
Role::Assistant => {
for content in &message.content {
match content {
MessageContent::Text(text) if text.is_empty() => {
// Mistral API returns a 400 if there's neither content nor tool_calls
}
MessageContent::Text(text) => {
messages.push(mistral::RequestMessage::Assistant {
content: Some(mistral::MessageContent::Plain {
@@ -853,6 +856,13 @@ mod tests {
cache: false,
reasoning_details: None,
},
// should skip empty assistant messages
LanguageModelRequestMessage {
role: Role::Assistant,
content: vec![MessageContent::Text("".into())],
cache: false,
reasoning_details: None,
},
],
temperature: Some(0.5),
tools: vec![],