mirror of https://github.com/buster-so/buster.git
show tool calls
This commit is contained in:
parent
3cd42527ec
commit
7eca5c9362
|
@ -1,8 +1,6 @@
|
||||||
|
use agents::{AgentError, AgentThread};
|
||||||
use litellm::{AgentMessage, MessageProgress, ToolCall};
|
use litellm::{AgentMessage, MessageProgress, ToolCall};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use agents::{AgentThread, AgentError};
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
// --- Application State Structs ---
|
// --- Application State Structs ---
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ActiveToolCall {
|
pub struct ActiveToolCall {
|
||||||
|
@ -66,7 +64,12 @@ impl AppState {
|
||||||
name,
|
name,
|
||||||
} => {
|
} => {
|
||||||
self.handle_assistant_message(
|
self.handle_assistant_message(
|
||||||
id, content, tool_calls, progress, Some(initial), name,
|
id,
|
||||||
|
content,
|
||||||
|
tool_calls,
|
||||||
|
progress,
|
||||||
|
Some(initial),
|
||||||
|
name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
AgentMessage::Tool {
|
AgentMessage::Tool {
|
||||||
|
@ -95,7 +98,7 @@ impl AppState {
|
||||||
name: Some("System".to_string()),
|
name: Some("System".to_string()),
|
||||||
tool_calls: None,
|
tool_calls: None,
|
||||||
progress: MessageProgress::Complete,
|
progress: MessageProgress::Complete,
|
||||||
initial: false,
|
initial: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +118,57 @@ impl AppState {
|
||||||
match progress {
|
match progress {
|
||||||
MessageProgress::InProgress => {
|
MessageProgress::InProgress => {
|
||||||
self.is_agent_processing = true;
|
self.is_agent_processing = true;
|
||||||
|
|
||||||
|
// Clone values needed in both update/add paths *before* potential move
|
||||||
|
let _id_clone = _id.clone();
|
||||||
|
let content_clone = content.clone();
|
||||||
|
let progress_clone = progress.clone();
|
||||||
|
|
||||||
|
// --- Update or add the main Assistant message ---
|
||||||
|
let last_assistant_msg_index = self
|
||||||
|
.messages
|
||||||
|
.iter()
|
||||||
|
.rposition(|msg| matches!(msg, AgentMessage::Assistant { .. }));
|
||||||
|
let mut updated_existing = false;
|
||||||
|
|
||||||
|
if let Some(index) = last_assistant_msg_index {
|
||||||
|
if let Some(AgentMessage::Assistant {
|
||||||
|
progress: existing_progress,
|
||||||
|
..
|
||||||
|
}) = self.messages.get_mut(index)
|
||||||
|
{
|
||||||
|
if *existing_progress == MessageProgress::InProgress {
|
||||||
|
// Update existing InProgress message
|
||||||
|
*self.messages.get_mut(index).unwrap() = AgentMessage::Assistant {
|
||||||
|
id: _id,
|
||||||
|
content,
|
||||||
|
tool_calls: tool_calls.clone(),
|
||||||
|
progress,
|
||||||
|
initial: false,
|
||||||
|
name: Some(agent_name.clone()),
|
||||||
|
};
|
||||||
|
updated_existing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated_existing {
|
||||||
|
// Push a new Assistant message if none was updated
|
||||||
|
self.messages.push(AgentMessage::Assistant {
|
||||||
|
id: _id_clone,
|
||||||
|
content: content_clone,
|
||||||
|
tool_calls: tool_calls.clone(),
|
||||||
|
progress: progress_clone,
|
||||||
|
initial: false,
|
||||||
|
name: Some(agent_name.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Handle tool calls (update status bar and add placeholders) ---
|
||||||
|
// Use the original tool_calls value passed to the function
|
||||||
if let Some(calls) = &tool_calls {
|
if let Some(calls) = &tool_calls {
|
||||||
|
// BORROW original tool_calls
|
||||||
|
// Update active_tool_calls for status bar
|
||||||
self.active_tool_calls = calls
|
self.active_tool_calls = calls
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tc| ActiveToolCall {
|
.map(|tc| ActiveToolCall {
|
||||||
|
@ -125,87 +178,130 @@ impl AppState {
|
||||||
content: None,
|
content: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
}
|
|
||||||
if let Some(AgentMessage::Assistant {
|
// Add placeholders *after* the assistant message
|
||||||
content: existing_content,
|
// Check if placeholders for these specific calls already exist? (More robust)
|
||||||
progress: existing_progress,
|
let existing_tool_ids: std::collections::HashSet<String> = self
|
||||||
..
|
.messages
|
||||||
}) = self.messages.last_mut()
|
.iter()
|
||||||
{
|
.filter_map(|msg| {
|
||||||
if *existing_progress == MessageProgress::InProgress {
|
if let AgentMessage::Developer { id: Some(id), .. } = msg {
|
||||||
if let Some(new_content) = content {
|
Some(id.clone())
|
||||||
*existing_content = Some(new_content);
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for tc in calls {
|
||||||
|
if !existing_tool_ids.contains(&tc.id) {
|
||||||
|
self.messages.push(AgentMessage::Developer {
|
||||||
|
id: Some(tc.id.clone()),
|
||||||
|
content: format!("Executing: {}...", tc.function.name),
|
||||||
|
name: Some("Tool".to_string()),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.messages.push(AgentMessage::Assistant {
|
|
||||||
id: _id,
|
|
||||||
content,
|
|
||||||
tool_calls,
|
|
||||||
progress,
|
|
||||||
initial: false, // Assuming false for new in-progress messages
|
|
||||||
name: Some(agent_name),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.messages.push(AgentMessage::Assistant {
|
// If no tool calls in this specific message, clear the active list only if agent isn't processing anymore?
|
||||||
id: _id,
|
// Let's keep the logic simple: don't clear here. Complete/Done will handle it.
|
||||||
content,
|
// self.active_tool_calls.clear();
|
||||||
tool_calls,
|
|
||||||
progress,
|
|
||||||
initial: false, // Assuming false for the first message
|
|
||||||
name: Some(agent_name),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageProgress::Complete => {
|
MessageProgress::Complete => {
|
||||||
|
// --- This block needs similar review, but the error was in InProgress ---
|
||||||
|
// --- For now, let's assume the existing Complete logic is mostly okay, ---
|
||||||
|
// --- but ensure tool_calls is cloned if moved into a message. ---
|
||||||
|
|
||||||
if tool_calls.is_none() {
|
if tool_calls.is_none() {
|
||||||
if let Some(AgentMessage::Assistant {
|
// === Case 1: No tool calls, final assistant response ===
|
||||||
progress: existing_progress,
|
// (Simplified logic: Assume we always update/add the last message)
|
||||||
..
|
let last_assistant_msg_index = self
|
||||||
}) = self.messages.last_mut()
|
.messages
|
||||||
{
|
.iter()
|
||||||
if *existing_progress == MessageProgress::InProgress {
|
.rposition(|msg| matches!(msg, AgentMessage::Assistant { .. }));
|
||||||
*self.messages.last_mut().unwrap() = AgentMessage::Assistant {
|
|
||||||
id: _id,
|
if let Some(index) = last_assistant_msg_index {
|
||||||
content,
|
// Update existing message (might be InProgress or already Complete)
|
||||||
tool_calls,
|
*self.messages.get_mut(index).unwrap() = AgentMessage::Assistant {
|
||||||
progress,
|
id: _id,
|
||||||
initial: false, // Assuming final message is not initial
|
content,
|
||||||
name: Some(agent_name),
|
tool_calls: None,
|
||||||
};
|
progress,
|
||||||
} else {
|
initial: false,
|
||||||
self.messages.push(AgentMessage::Assistant {
|
name: Some(agent_name),
|
||||||
id: _id,
|
};
|
||||||
content,
|
|
||||||
tool_calls,
|
|
||||||
progress,
|
|
||||||
initial: false,
|
|
||||||
name: Some(agent_name),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// Add new final message
|
||||||
self.messages.push(AgentMessage::Assistant {
|
self.messages.push(AgentMessage::Assistant {
|
||||||
id: _id,
|
id: _id,
|
||||||
content,
|
content,
|
||||||
tool_calls,
|
tool_calls: None,
|
||||||
progress,
|
progress,
|
||||||
initial: false,
|
initial: false,
|
||||||
name: Some(agent_name),
|
name: Some(agent_name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_agent_processing = false;
|
self.is_agent_processing = false;
|
||||||
self.active_tool_calls.clear();
|
self.active_tool_calls.clear();
|
||||||
} else {
|
} else {
|
||||||
self.active_tool_calls = tool_calls
|
// === Case 2: Tool calls were generated, assistant text part is complete ===
|
||||||
.unwrap_or_default()
|
// Update the assistant message that initiated tools (if it was InProgress)
|
||||||
|
let last_assistant_msg_index = self
|
||||||
|
.messages
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tc| ActiveToolCall {
|
.rposition(|msg| matches!(msg, AgentMessage::Assistant { .. }));
|
||||||
id: tc.id.clone(),
|
if let Some(index) = last_assistant_msg_index {
|
||||||
name: tc.function.name.clone(),
|
if let Some(AgentMessage::Assistant {
|
||||||
status: "Pending Execution".to_string(),
|
progress: existing_progress,
|
||||||
content: None,
|
..
|
||||||
})
|
}) = self.messages.get_mut(index)
|
||||||
.collect();
|
{
|
||||||
|
if *existing_progress == MessageProgress::InProgress {
|
||||||
|
*self.messages.get_mut(index).unwrap() = AgentMessage::Assistant {
|
||||||
|
id: _id,
|
||||||
|
content, // Update final content
|
||||||
|
tool_calls: tool_calls.clone(), // CLONE for message
|
||||||
|
progress, // Mark as Complete
|
||||||
|
initial: false,
|
||||||
|
name: Some(agent_name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If no InProgress found, we don't add a new Complete message here.
|
||||||
|
|
||||||
|
// Update status bar (borrow original tool_calls)
|
||||||
|
if let Some(calls) = &tool_calls {
|
||||||
|
// BORROW original
|
||||||
|
self.active_tool_calls = calls
|
||||||
|
.iter()
|
||||||
|
.map(|tc| ActiveToolCall {
|
||||||
|
id: tc.id.clone(),
|
||||||
|
name: tc.function.name.clone(),
|
||||||
|
status: "Pending Execution".to_string(),
|
||||||
|
content: None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// --- ADD PLACEHOLDERS HERE TOO ---
|
||||||
|
// Covers the case where only a Complete message with tool_calls is sent.
|
||||||
|
let existing_tool_ids: std::collections::HashSet<String> = self.messages.iter().filter_map(|msg| {
|
||||||
|
if let AgentMessage::Developer { id: Some(id), .. } = msg { Some(id.clone()) } else { None }
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
for tc in calls {
|
||||||
|
if !existing_tool_ids.contains(&tc.id) {
|
||||||
|
self.messages.push(AgentMessage::Developer {
|
||||||
|
id: Some(tc.id.clone()),
|
||||||
|
content: format!("Executing: {}...", tc.function.name), // Or maybe "Called:"?
|
||||||
|
name: Some("Tool".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Placeholders were added by InProgress or just now. Keep processing.
|
||||||
self.is_agent_processing = true;
|
self.is_agent_processing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +316,37 @@ impl AppState {
|
||||||
tool_name: Option<String>,
|
tool_name: Option<String>,
|
||||||
progress: MessageProgress,
|
progress: MessageProgress,
|
||||||
) {
|
) {
|
||||||
let _name = tool_name.unwrap_or_else(|| "Unknown Tool".to_string()); // Use _name to avoid warning
|
let name = tool_name.unwrap_or_else(|| "Unknown Tool".to_string());
|
||||||
|
let mut found_message = false;
|
||||||
|
|
||||||
|
// Update the placeholder message in the main history
|
||||||
|
for msg in self.messages.iter_mut() {
|
||||||
|
if let AgentMessage::Developer {
|
||||||
|
id: msg_id,
|
||||||
|
content: msg_content,
|
||||||
|
..
|
||||||
|
} = msg
|
||||||
|
{
|
||||||
|
if msg_id.as_ref() == Some(&tool_call_id) {
|
||||||
|
*msg_content = match progress {
|
||||||
|
MessageProgress::InProgress => format!("Running {}: {}...", name, content), // Show partial content?
|
||||||
|
MessageProgress::Complete => format!("Result ({}): {}", name, content),
|
||||||
|
};
|
||||||
|
found_message = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found_message {
|
||||||
|
eprintln!(
|
||||||
|
"Warning: Could not find placeholder message for tool call ID: {}",
|
||||||
|
tool_call_id
|
||||||
|
);
|
||||||
|
// Optionally, add a new Developer message anyway?
|
||||||
|
// self.messages.push(AgentMessage::Developer { ... });
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Original logic to update status bar ---
|
||||||
if let Some(tool) = self
|
if let Some(tool) = self
|
||||||
.active_tool_calls
|
.active_tool_calls
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -229,13 +355,17 @@ impl AppState {
|
||||||
match progress {
|
match progress {
|
||||||
MessageProgress::InProgress => {
|
MessageProgress::InProgress => {
|
||||||
tool.status = "Running".to_string();
|
tool.status = "Running".to_string();
|
||||||
tool.content = Some(content);
|
tool.content = Some(content); // Update status bar content too
|
||||||
self.is_agent_processing = true;
|
self.is_agent_processing = true; // Keep processing
|
||||||
}
|
}
|
||||||
MessageProgress::Complete => {
|
MessageProgress::Complete => {
|
||||||
|
// Remove from active list, but don't change is_agent_processing yet.
|
||||||
|
// The agent needs to process the result.
|
||||||
self.active_tool_calls.retain(|t| t.id != tool_call_id);
|
self.active_tool_calls.retain(|t| t.id != tool_call_id);
|
||||||
|
// Only set is_agent_processing = true if this was the *last* active tool call.
|
||||||
|
// The agent will send AgentMessage::Done or a final Assistant message later.
|
||||||
if self.active_tool_calls.is_empty() {
|
if self.active_tool_calls.is_empty() {
|
||||||
self.is_agent_processing = true;
|
self.is_agent_processing = true; // Still true, agent needs to process results
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,4 +384,4 @@ impl AppState {
|
||||||
pub fn scroll_down(&mut self) {
|
pub fn scroll_down(&mut self) {
|
||||||
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
self.scroll_offset = self.scroll_offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,10 +127,44 @@ fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
||||||
message_lines.push(Line::from(""));
|
message_lines.push(Line::from(""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AgentMessage::Developer { content, name, .. } => {
|
||||||
|
// Render tool messages/results
|
||||||
|
let prefix = Span::styled(
|
||||||
|
format!("{} {}: ", "🔩", name.as_deref().unwrap_or("Tool")), // Use a gear icon
|
||||||
|
Style::default().fg(Color::DarkGray).add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
let content_style = Style::default().fg(Color::DarkGray);
|
||||||
|
|
||||||
|
let lines: Vec<&str> = content.split('\n').collect();
|
||||||
|
for (i, line_content) in lines.iter().enumerate() {
|
||||||
|
let line_span = Span::styled(*line_content, content_style);
|
||||||
|
if i == 0 {
|
||||||
|
message_lines.push(Line::from(vec![prefix.clone(), line_span]));
|
||||||
|
} else {
|
||||||
|
message_lines.push(Line::from(vec![
|
||||||
|
Span::raw(" "), // Indent subsequent lines
|
||||||
|
line_span,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message_lines.push(Line::from("")); // Add space after tool message
|
||||||
|
}
|
||||||
_ => {} // Ignore other message types for history display
|
_ => {} // Ignore other message types for history display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add "Thinking..." indicator if processing and no tool calls active
|
||||||
|
if app.is_agent_processing && app.active_tool_calls.is_empty() && !matches!(app.messages.last(), Some(AgentMessage::Developer { .. })) {
|
||||||
|
// Check if the last message isn't already an InProgress Assistant message
|
||||||
|
let last_msg_is_thinking = matches!(app.messages.last(), Some(AgentMessage::Assistant { progress: MessageProgress::InProgress, .. }));
|
||||||
|
if !last_msg_is_thinking {
|
||||||
|
message_lines.push(Line::from(Span::styled(
|
||||||
|
"🤔 Thinking...",
|
||||||
|
Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let content_height = message_lines.len() as u16;
|
let content_height = message_lines.len() as u16;
|
||||||
let view_height = area.height;
|
let view_height = area.height;
|
||||||
let max_scroll = content_height.saturating_sub(view_height);
|
let max_scroll = content_height.saturating_sub(view_height);
|
||||||
|
|
Loading…
Reference in New Issue