fix: include tool results in conversation context
When OpenCode sends follow-up messages with tool results, include the full conversation (user message, assistant tool calls, and tool results) in the text sent to the model. Previously only the user message was extracted, causing the model to never see tool results and call the same tool repeatedly in an infinite loop. Also add tool_calls and tool_call_id fields to CompletionMessage.
This commit is contained in:
@@ -18,20 +18,26 @@ use super::AppState;
|
||||
// ─── Input extraction ────────────────────────────────────────────────────────
|
||||
|
||||
/// Extract user text from Chat Completions messages array.
|
||||
///
|
||||
/// When tool results are present, builds the full conversation including
|
||||
/// tool call results so the model can continue after tool use.
|
||||
fn extract_chat_input(messages: &[CompletionMessage]) -> String {
|
||||
let has_tool_results = messages.iter().any(|m| m.role == "tool");
|
||||
|
||||
if has_tool_results {
|
||||
// Build full conversation context including tool results
|
||||
return build_conversation_with_tools(messages);
|
||||
}
|
||||
|
||||
// Simple path: no tools, just extract system + last user message
|
||||
let mut system_parts = Vec::new();
|
||||
let mut user_parts = Vec::new();
|
||||
|
||||
for msg in messages {
|
||||
let text = match &msg.content {
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
serde_json::Value::Array(arr) => arr
|
||||
.iter()
|
||||
.filter_map(|item| item["text"].as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
_ => continue,
|
||||
};
|
||||
let text = extract_message_text(&msg.content);
|
||||
if text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match msg.role.as_str() {
|
||||
"system" | "developer" => system_parts.push(text),
|
||||
"user" => user_parts.push(text),
|
||||
@@ -44,13 +50,87 @@ fn extract_chat_input(messages: &[CompletionMessage]) -> String {
|
||||
result.push_str(&system_parts.join("\n"));
|
||||
result.push_str("\n\n");
|
||||
}
|
||||
// Use the last user message
|
||||
if let Some(last) = user_parts.last() {
|
||||
result.push_str(last);
|
||||
}
|
||||
result.trim().to_string()
|
||||
}
|
||||
|
||||
/// Extract text content from a message's content field (string or array).
|
||||
fn extract_message_text(content: &serde_json::Value) -> String {
|
||||
match content {
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
serde_json::Value::Array(arr) => arr
|
||||
.iter()
|
||||
.filter_map(|item| item["text"].as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build conversation text that includes tool call results.
|
||||
///
|
||||
/// Format:
|
||||
/// [system prompt]
|
||||
/// [user message]
|
||||
/// [assistant called tool X with args Y]
|
||||
/// [tool result: Z]
|
||||
/// [user followup if any]
|
||||
fn build_conversation_with_tools(messages: &[CompletionMessage]) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
for msg in messages {
|
||||
match msg.role.as_str() {
|
||||
"system" | "developer" => {
|
||||
let text = extract_message_text(&msg.content);
|
||||
if !text.is_empty() {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
"user" => {
|
||||
let text = extract_message_text(&msg.content);
|
||||
if !text.is_empty() {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
"assistant" => {
|
||||
// Include assistant text if any
|
||||
let text = extract_message_text(&msg.content);
|
||||
if !text.is_empty() {
|
||||
parts.push(text);
|
||||
}
|
||||
// Include tool calls as context
|
||||
if let Some(ref tool_calls) = msg.tool_calls {
|
||||
for tc in tool_calls {
|
||||
if let Some(func) = tc.get("function") {
|
||||
let name = func["name"].as_str().unwrap_or("unknown");
|
||||
let args = func["arguments"].as_str().unwrap_or("{}");
|
||||
parts.push(format!(
|
||||
"[Tool call: {}({})]",
|
||||
name, args
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"tool" => {
|
||||
let text = extract_message_text(&msg.content);
|
||||
let tool_id = msg.tool_call_id.as_deref().unwrap_or("unknown");
|
||||
if !text.is_empty() {
|
||||
parts.push(format!(
|
||||
"[Tool result ({})]:\n{}",
|
||||
tool_id, text
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
parts.join("\n\n")
|
||||
}
|
||||
|
||||
// ─── Handler ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/// POST /v1/chat/completions — OpenAI Chat Completions API compatibility shim.
|
||||
|
||||
Reference in New Issue
Block a user