Add tool use to the orchestrator (#4)
Add tool use without sandboxing. Currently available tools are list dir, read file, write file and exec bash. Reviewed-on: #4 Co-authored-by: Drew Galbraith <drew@tiramisu.one> Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
parent
6b85ff3cb8
commit
797d7564b7
20 changed files with 1822 additions and 129 deletions
|
|
@ -11,11 +11,14 @@ use crate::core::types::{Role, UIEvent};
|
|||
/// This is non-blocking: it processes all currently-available events and returns
|
||||
/// immediately when the channel is empty.
|
||||
///
|
||||
/// | Event | Effect |
|
||||
/// |--------------------|------------------------------------------------------------|
|
||||
/// | `StreamDelta(s)` | Append `s` to last message if it's `Assistant`; else push new |
|
||||
/// | `TurnComplete` | No structural change; logged at debug level |
|
||||
/// | `Error(msg)` | Push `(Assistant, "[error] {msg}")` |
|
||||
/// | Event | Effect |
|
||||
/// |------------------------|------------------------------------------------------------|
|
||||
/// | `StreamDelta(s)` | Append `s` to last message if it's `Assistant`; else push |
|
||||
/// | `ToolApprovalRequest` | Set `pending_approval` in state |
|
||||
/// | `ToolExecuting` | Display tool execution info |
|
||||
/// | `ToolResult` | Display tool result |
|
||||
/// | `TurnComplete` | No structural change; logged at debug level |
|
||||
/// | `Error(msg)` | Push `(Assistant, "[error] {msg}")` |
|
||||
pub(super) fn drain_ui_events(event_rx: &mut mpsc::Receiver<UIEvent>, state: &mut AppState) {
|
||||
while let Ok(event) = event_rx.try_recv() {
|
||||
match event {
|
||||
|
|
@ -26,6 +29,36 @@ pub(super) fn drain_ui_events(event_rx: &mut mpsc::Receiver<UIEvent>, state: &mu
|
|||
state.messages.push((Role::Assistant, chunk));
|
||||
}
|
||||
}
|
||||
UIEvent::ToolApprovalRequest {
|
||||
tool_use_id,
|
||||
tool_name,
|
||||
input_summary,
|
||||
} => {
|
||||
state.pending_approval = Some(PendingApproval {
|
||||
tool_use_id,
|
||||
tool_name,
|
||||
input_summary,
|
||||
});
|
||||
}
|
||||
UIEvent::ToolExecuting {
|
||||
tool_name,
|
||||
input_summary,
|
||||
} => {
|
||||
state
|
||||
.messages
|
||||
.push((Role::Assistant, format!("[{tool_name}] {input_summary}")));
|
||||
}
|
||||
UIEvent::ToolResult {
|
||||
tool_name,
|
||||
output_summary,
|
||||
is_error,
|
||||
} => {
|
||||
let prefix = if is_error { "error" } else { "result" };
|
||||
state.messages.push((
|
||||
Role::Assistant,
|
||||
format!("[{tool_name} {prefix}] {output_summary}"),
|
||||
));
|
||||
}
|
||||
UIEvent::TurnComplete => {
|
||||
debug!("turn complete");
|
||||
}
|
||||
|
|
@ -38,6 +71,14 @@ pub(super) fn drain_ui_events(event_rx: &mut mpsc::Receiver<UIEvent>, state: &mu
|
|||
}
|
||||
}
|
||||
|
||||
/// A pending tool approval request waiting for user input.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingApproval {
|
||||
pub tool_use_id: String,
|
||||
pub tool_name: String,
|
||||
pub input_summary: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -69,4 +110,39 @@ mod tests {
|
|||
assert_eq!(state.messages[1].0, Role::Assistant);
|
||||
assert_eq!(state.messages[1].1, "hello");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn drain_tool_approval_sets_pending() {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(8);
|
||||
let mut state = AppState::new();
|
||||
tx.send(UIEvent::ToolApprovalRequest {
|
||||
tool_use_id: "t1".to_string(),
|
||||
tool_name: "write_file".to_string(),
|
||||
input_summary: "path: foo.txt".to_string(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
drop(tx);
|
||||
drain_ui_events(&mut rx, &mut state);
|
||||
assert!(state.pending_approval.is_some());
|
||||
let approval = state.pending_approval.unwrap();
|
||||
assert_eq!(approval.tool_name, "write_file");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn drain_tool_result_adds_message() {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(8);
|
||||
let mut state = AppState::new();
|
||||
tx.send(UIEvent::ToolResult {
|
||||
tool_name: "read_file".to_string(),
|
||||
output_summary: "file contents...".to_string(),
|
||||
is_error: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
drop(tx);
|
||||
drain_ui_events(&mut rx, &mut state);
|
||||
assert_eq!(state.messages.len(), 1);
|
||||
assert!(state.messages[0].1.contains("read_file result"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue