fix(providers): handle tool messages in text_only message converter
Some checks failed
CI / Check (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Release Build (push) Has been cancelled

messages_to_openai_json_text_only() was missing tool-calling support,
causing DeepSeek 400 errors when conversations included tool turns.
Now mirrors messages_to_openai_json() logic for tool-role messages
(tool_call_id, name) and assistant tool_calls, with images replaced
by "[Image]" text.
This commit is contained in:
2026-03-02 11:30:38 -05:00
parent d386820d16
commit 9c01b97f82

View File

@@ -84,11 +84,41 @@ pub async fn messages_to_openai_json(messages: &[UnifiedMessage]) -> Result<Vec<
/// Convert messages to OpenAI-compatible JSON, but replace images with a
/// text placeholder "[Image]". Useful for providers that don't support
/// multimodal in streaming mode or at all.
///
/// Handles tool-calling messages identically to `messages_to_openai_json`:
/// assistant messages with `tool_calls`, and tool-role messages with
/// `tool_call_id`/`name`.
pub async fn messages_to_openai_json_text_only(
messages: &[UnifiedMessage],
) -> Result<Vec<serde_json::Value>, AppError> {
let mut result = Vec::new();
for m in messages {
// Tool-role messages: { role: "tool", content: "...", tool_call_id: "...", name: "..." }
if m.role == "tool" {
let text_content = m
.content
.first()
.map(|p| match p {
ContentPart::Text { text } => text.clone(),
ContentPart::Image(_) => "[Image]".to_string(),
})
.unwrap_or_default();
let mut msg = serde_json::json!({
"role": "tool",
"content": text_content
});
if let Some(tool_call_id) = &m.tool_call_id {
msg["tool_call_id"] = serde_json::json!(tool_call_id);
}
if let Some(name) = &m.name {
msg["name"] = serde_json::json!(name);
}
result.push(msg);
continue;
}
// Build content parts for non-tool messages (images become "[Image]" text)
let mut parts = Vec::new();
for p in &m.content {
match p {
@@ -100,10 +130,26 @@ pub async fn messages_to_openai_json_text_only(
}
}
}
result.push(serde_json::json!({
"role": m.role,
"content": parts
}));
let mut msg = serde_json::json!({ "role": m.role });
// For assistant messages with tool_calls, content can be null
if let Some(tool_calls) = &m.tool_calls {
if parts.is_empty() {
msg["content"] = serde_json::Value::Null;
} else {
msg["content"] = serde_json::json!(parts);
}
msg["tool_calls"] = serde_json::json!(tool_calls);
} else {
msg["content"] = serde_json::json!(parts);
}
if let Some(name) = &m.name {
msg["name"] = serde_json::json!(name);
}
result.push(msg);
}
Ok(result)
}