mirror of https://github.com/buster-so/buster.git
Merge branch 'evals' of https://github.com/buster-so/buster into evals
This commit is contained in:
commit
ab61b21f48
|
@ -13,13 +13,15 @@ use std::time::Instant;
|
|||
use thiserror::Error;
|
||||
use tokio::sync::broadcast;
|
||||
use uuid::Uuid;
|
||||
use std::process::Command;
|
||||
|
||||
// --- Agent Imports ---
|
||||
use agents::{AgentError, AgentExt, AgentThread, BusterCliAgent};
|
||||
|
||||
// Ratatui / Crossterm related imports
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
|
||||
MouseEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
@ -105,6 +107,15 @@ pub async fn run_chat(args: ChatArgs) -> Result<()> {
|
|||
.map(|p| p.display().to_string())
|
||||
.unwrap_or_else(|_| "<unknown>".to_string());
|
||||
|
||||
// --- Git Repository Check ---
|
||||
let git_check = Command::new("git")
|
||||
.args(["rev-parse", "--is-inside-work-tree"])
|
||||
.output(); // Use output to capture status and stderr/stdout if needed
|
||||
|
||||
if git_check.is_err() || !git_check.unwrap().status.success() {
|
||||
println!("{}", colored::Colorize::yellow("Warning: Buster operates best in a git repository."));
|
||||
}
|
||||
|
||||
// --- Get Credentials ---
|
||||
let (base_url, api_key) = get_api_credentials(&args)?;
|
||||
if api_key.is_none() {
|
||||
|
@ -177,7 +188,8 @@ pub async fn run_chat(args: ChatArgs) -> Result<()> {
|
|||
.unwrap_or_else(|| std::time::Duration::from_secs(0));
|
||||
|
||||
if crossterm::event::poll(timeout)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match event::read()? {
|
||||
Event::Key(key) => {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
// Quit handlers
|
||||
if key.modifiers.contains(event::KeyModifiers::CONTROL)
|
||||
|
@ -241,6 +253,19 @@ pub async fn run_chat(args: ChatArgs) -> Result<()> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse_event) => {
|
||||
match mouse_event.kind {
|
||||
MouseEventKind::ScrollUp => {
|
||||
app_state.scroll_up();
|
||||
}
|
||||
MouseEventKind::ScrollDown => {
|
||||
app_state.scroll_down();
|
||||
}
|
||||
_ => {} // Ignore other mouse events (clicks, moves, etc.)
|
||||
}
|
||||
}
|
||||
_ => {} // Ignore other event types (like resize for now)
|
||||
}
|
||||
}
|
||||
|
||||
// Tick update
|
||||
|
|
|
@ -1,6 +1,26 @@
|
|||
use agents::{AgentError, AgentThread};
|
||||
use litellm::{AgentMessage, MessageProgress, ToolCall};
|
||||
use uuid::Uuid;
|
||||
use std::time::Instant;
|
||||
use serde_json;
|
||||
use serde_json::Value;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// --- Structs for specific tool results (add more as needed) ---
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ListDirectoryEntry {
|
||||
name: String,
|
||||
path: String,
|
||||
is_dir: bool,
|
||||
size: Option<u64>,
|
||||
// modified_at: Option<String>, // Ignoring for now
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ListDirectoryResult {
|
||||
entries: Vec<ListDirectoryEntry>,
|
||||
}
|
||||
|
||||
// --- Application State Structs ---
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ActiveToolCall {
|
||||
|
@ -195,10 +215,17 @@ impl AppState {
|
|||
|
||||
for tc in calls {
|
||||
if !existing_tool_ids.contains(&tc.id) {
|
||||
// Format arguments nicely
|
||||
let args_json = serde_json::to_string_pretty(&tc.function.arguments)
|
||||
.unwrap_or_else(|_| "<failed to format args>".to_string());
|
||||
self.messages.push(AgentMessage::Developer {
|
||||
id: Some(tc.id.clone()),
|
||||
content: format!("Executing: {}...", tc.function.name),
|
||||
name: Some("Tool".to_string()),
|
||||
content: format!(
|
||||
"Executing: {}\nArgs:\n{}",
|
||||
tc.function.name,
|
||||
args_json
|
||||
),
|
||||
name: Some("Tool Call".to_string()), // Change name for clarity
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -293,10 +320,17 @@ impl AppState {
|
|||
|
||||
for tc in calls {
|
||||
if !existing_tool_ids.contains(&tc.id) {
|
||||
// Format arguments nicely
|
||||
let args_json = serde_json::to_string_pretty(&tc.function.arguments)
|
||||
.unwrap_or_else(|_| "<failed to format args>".to_string());
|
||||
self.messages.push(AgentMessage::Developer {
|
||||
id: Some(tc.id.clone()),
|
||||
content: format!("Executing: {}...", tc.function.name), // Or maybe "Called:"?
|
||||
name: Some("Tool".to_string()),
|
||||
content: format!(
|
||||
"Executing: {}\nArgs:\n{}",
|
||||
tc.function.name,
|
||||
args_json
|
||||
),
|
||||
name: Some("Tool Call".to_string()), // Change name for clarity
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -321,16 +355,37 @@ impl AppState {
|
|||
|
||||
// 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 let AgentMessage::Developer { id: msg_id, content: msg_content, name: msg_name, .. } = msg {
|
||||
if msg_id.as_ref() == Some(&tool_call_id) {
|
||||
*msg_name = Some(format!("{} Result", name)); // Update name to indicate result
|
||||
*msg_content = match progress {
|
||||
MessageProgress::InProgress => format!("Running {}: {}...", name, content), // Show partial content?
|
||||
MessageProgress::Complete => format!("Result ({}): {}", name, content),
|
||||
MessageProgress::InProgress => {
|
||||
// Try to show formatted partial content if possible, else raw
|
||||
format!("Running {}: {}...", name, content)
|
||||
},
|
||||
MessageProgress::Complete => {
|
||||
// Attempt to parse and format known tool outputs
|
||||
match name.as_str() {
|
||||
"list_directory" => {
|
||||
match serde_json::from_str::<ListDirectoryResult>(&content) {
|
||||
Ok(parsed_result) => {
|
||||
let mut formatted = String::from("Entries:\n");
|
||||
for entry in parsed_result.entries {
|
||||
formatted.push_str(&format!(
|
||||
" - {} ({})\n",
|
||||
entry.name,
|
||||
if entry.is_dir { "directory" } else { "file" }
|
||||
));
|
||||
}
|
||||
formatted.trim_end().to_string() // Remove trailing newline
|
||||
}
|
||||
Err(_) => format!("Result ({}):\n{}", name, content), // Fallback to raw JSON on parse error
|
||||
}
|
||||
}
|
||||
// Add more known tool formatters here
|
||||
_ => format!("Result ({}):\n{}", name, content), // Default: show raw JSON
|
||||
}
|
||||
},
|
||||
};
|
||||
found_message = true;
|
||||
break;
|
||||
|
|
|
@ -85,7 +85,9 @@ fn render_welcome(frame: &mut Frame, area: Rect, cwd: &str) {
|
|||
fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
||||
use litellm::{AgentMessage, MessageProgress};
|
||||
let mut message_lines: Vec<Line> = Vec::new();
|
||||
for msg in app.messages.iter() {
|
||||
let num_messages = app.messages.len();
|
||||
|
||||
for (msg_index, msg) in app.messages.iter().enumerate() {
|
||||
match msg {
|
||||
AgentMessage::User { content, .. } => {
|
||||
message_lines.push(Line::from(vec![
|
||||
|
@ -95,7 +97,18 @@ fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
|||
}
|
||||
AgentMessage::Assistant { content, progress, name, .. } => {
|
||||
let is_in_progress = *progress == MessageProgress::InProgress;
|
||||
let prefix = Span::styled(
|
||||
let is_last_message = msg_index == num_messages - 1;
|
||||
let prev_msg_was_tool_result = if msg_index > 0 {
|
||||
matches!(app.messages.get(msg_index - 1), Some(AgentMessage::Developer { .. }))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Omit prefix if it's the last message AND the previous was a tool result
|
||||
let show_prefix = !(is_last_message && prev_msg_was_tool_result && !is_in_progress);
|
||||
|
||||
let prefix = if show_prefix {
|
||||
Some(Span::styled(
|
||||
format!(
|
||||
"{} {}: ",
|
||||
if is_in_progress { "…" } else { "•" },
|
||||
|
@ -104,23 +117,34 @@ fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
|||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
))
|
||||
} else {
|
||||
None // Don't show prefix
|
||||
};
|
||||
|
||||
if let Some(c) = content {
|
||||
let lines: Vec<&str> = c.split('\n').collect();
|
||||
for (i, line_content) in lines.iter().enumerate() {
|
||||
let line_span = Span::styled(*line_content, Style::default().fg(Color::White));
|
||||
if i == 0 {
|
||||
message_lines.push(Line::from(vec![prefix.clone(), line_span]));
|
||||
if let Some(p) = prefix.clone() {
|
||||
message_lines.push(Line::from(vec![p, line_span]));
|
||||
} else {
|
||||
// No prefix, just the content line
|
||||
message_lines.push(Line::from(line_span));
|
||||
}
|
||||
} else {
|
||||
// Indent subsequent lines (relative to prefix or start)
|
||||
let indent = if prefix.is_some() { " " } else { "" };
|
||||
message_lines.push(Line::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::raw(indent),
|
||||
line_span,
|
||||
]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message_lines.push(Line::from(prefix));
|
||||
} else if let Some(p) = prefix {
|
||||
// Show prefix even if content is None (e.g., for InProgress)
|
||||
message_lines.push(Line::from(p));
|
||||
}
|
||||
|
||||
if !is_in_progress {
|
||||
|
@ -168,7 +192,11 @@ fn render_messages(frame: &mut Frame, app: &AppState, area: Rect) {
|
|||
let content_height = message_lines.len() as u16;
|
||||
let view_height = area.height;
|
||||
let max_scroll = content_height.saturating_sub(view_height);
|
||||
let current_scroll = app.scroll_offset.min(max_scroll);
|
||||
let current_scroll = if app.scroll_offset == 0 {
|
||||
max_scroll
|
||||
} else {
|
||||
app.scroll_offset.min(max_scroll)
|
||||
};
|
||||
|
||||
let messages_widget = Paragraph::new(message_lines)
|
||||
.scroll((current_scroll, 0))
|
||||
|
@ -247,7 +275,7 @@ fn render_input(frame: &mut Frame, app: &AppState, area: Rect) {
|
|||
// --- Cursor ---
|
||||
if !is_input_disabled {
|
||||
frame.set_cursor(
|
||||
area.x + input_prefix.len() as u16 + app.input.chars().count() as u16,
|
||||
area.x + 1 + input_prefix.len() as u16 + app.input.chars().count() as u16, // +1 for left border
|
||||
area.y + 1,
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue