fixup: consolidate tool types, fix build after reorganization
Move FunctionCall, FunctionDef, FunctionCallDelta from user/types to agent/tools. Re-export from user/types for backward compat. Merge duplicate dispatch functions in tools/mod.rs into dispatch (agent-specific) + dispatch_shared (with provenance). Fix orphaned derive, missing imports, runner→agent module path. Co-Developed-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
474b66c834
commit
17a018ff12
9 changed files with 1356 additions and 1380 deletions
1156
src/agent/mod.rs
1156
src/agent/mod.rs
File diff suppressed because it is too large
Load diff
1162
src/agent/runner.rs
1162
src/agent/runner.rs
File diff suppressed because it is too large
Load diff
|
|
@ -10,12 +10,9 @@ use anyhow::{Context, Result};
|
|||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::process::Stdio;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::ToolDef;
|
||||
use super::{ToolDef, ProcessTracker, default_timeout};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Args {
|
||||
|
|
@ -24,63 +21,6 @@ struct Args {
|
|||
timeout_secs: u64,
|
||||
}
|
||||
|
||||
fn default_timeout() -> u64 { 120 }
|
||||
|
||||
/// Info about a running child process, visible to the TUI.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub command: String,
|
||||
pub started: Instant,
|
||||
}
|
||||
|
||||
/// Shared tracker for running child processes. Allows the TUI to
|
||||
/// display what's running and kill processes by PID.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessTracker {
|
||||
inner: Arc<Mutex<Vec<ProcessInfo>>>,
|
||||
}
|
||||
|
||||
impl ProcessTracker {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
async fn register(&self, pid: u32, command: &str) {
|
||||
self.inner.lock().await.push(ProcessInfo {
|
||||
pid,
|
||||
command: if command.len() > 120 {
|
||||
format!("{}...", &command[..120])
|
||||
} else {
|
||||
command.to_string()
|
||||
},
|
||||
started: Instant::now(),
|
||||
});
|
||||
}
|
||||
|
||||
async fn unregister(&self, pid: u32) {
|
||||
self.inner.lock().await.retain(|p| p.pid != pid);
|
||||
}
|
||||
|
||||
/// Snapshot of currently running processes.
|
||||
pub async fn list(&self) -> Vec<ProcessInfo> {
|
||||
self.inner.lock().await.clone()
|
||||
}
|
||||
|
||||
/// Kill a process by PID. Returns true if the signal was sent.
|
||||
pub async fn kill(&self, pid: u32) -> bool {
|
||||
// SIGTERM the process group (negative PID kills the group)
|
||||
let ret = unsafe { libc::kill(-(pid as i32), libc::SIGTERM) };
|
||||
if ret != 0 {
|
||||
// Try just the process
|
||||
unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
||||
}
|
||||
// Don't unregister — let the normal exit path do that
|
||||
// so the tool result says "killed by user"
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn definition() -> ToolDef {
|
||||
ToolDef::new(
|
||||
"bash",
|
||||
|
|
|
|||
|
|
@ -18,8 +18,161 @@ mod control;
|
|||
mod vision;
|
||||
pub mod working_stack;
|
||||
|
||||
// Re-export
|
||||
pub use crate::agent::{ToolDef, ToolOutput, ProcessTracker, truncate_output};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
fn default_timeout() -> u64 { 120 }
|
||||
|
||||
/// Function call within a tool call — name + JSON arguments.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionCall {
|
||||
pub name: String,
|
||||
pub arguments: String,
|
||||
}
|
||||
|
||||
/// Function definition for tool schema.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionDef {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub parameters: serde_json::Value,
|
||||
}
|
||||
|
||||
/// Partial function call within a streaming delta.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FunctionCallDelta {
|
||||
pub name: Option<String>,
|
||||
pub arguments: Option<String>,
|
||||
}
|
||||
|
||||
/// Tool definition sent to the model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDef {
|
||||
#[serde(rename = "type")]
|
||||
pub tool_type: String,
|
||||
pub function: FunctionDef,
|
||||
}
|
||||
|
||||
/// A tool call requested by the model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolCall {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: String,
|
||||
pub function: FunctionCall,
|
||||
}
|
||||
|
||||
/// A partial tool call within a streaming delta. The first chunk for a
|
||||
/// given tool call carries the id and function name; subsequent chunks
|
||||
/// carry argument fragments.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ToolCallDelta {
|
||||
pub index: usize,
|
||||
pub id: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: Option<String>,
|
||||
pub function: Option<FunctionCallDelta>,
|
||||
}
|
||||
|
||||
/// Result of dispatching a tool call.
|
||||
pub struct ToolOutput {
|
||||
pub text: String,
|
||||
pub is_yield: bool,
|
||||
/// Base64 data URIs for images to attach to the next message.
|
||||
pub images: Vec<String>,
|
||||
/// Model name to switch to (deferred to session level).
|
||||
pub model_switch: Option<String>,
|
||||
/// Agent requested DMN pause (deferred to session level).
|
||||
pub dmn_pause: bool,
|
||||
}
|
||||
|
||||
impl ToolOutput {
|
||||
pub fn error(e: impl std::fmt::Display) -> Self {
|
||||
Self {
|
||||
text: format!("Error: {}", e),
|
||||
is_yield: false,
|
||||
images: Vec::new(),
|
||||
model_switch: None,
|
||||
dmn_pause: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(s: String) -> Self {
|
||||
Self {
|
||||
text: s,
|
||||
is_yield: false,
|
||||
images: Vec::new(),
|
||||
model_switch: None,
|
||||
dmn_pause: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Info about a running child process, visible to the TUI.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pub pid: u32,
|
||||
pub command: String,
|
||||
pub started: Instant,
|
||||
}
|
||||
|
||||
/// Shared tracker for running child processes. Allows the TUI to
|
||||
/// display what's running and kill processes by PID.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ProcessTracker {
|
||||
inner: Arc<Mutex<Vec<ProcessInfo>>>,
|
||||
}
|
||||
|
||||
impl ProcessTracker {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
async fn register(&self, pid: u32, command: &str) {
|
||||
self.inner.lock().await.push(ProcessInfo {
|
||||
pid,
|
||||
command: if command.len() > 120 {
|
||||
format!("{}...", &command[..120])
|
||||
} else {
|
||||
command.to_string()
|
||||
},
|
||||
started: Instant::now(),
|
||||
});
|
||||
}
|
||||
|
||||
async fn unregister(&self, pid: u32) {
|
||||
self.inner.lock().await.retain(|p| p.pid != pid);
|
||||
}
|
||||
|
||||
/// Snapshot of currently running processes.
|
||||
pub async fn list(&self) -> Vec<ProcessInfo> {
|
||||
self.inner.lock().await.clone()
|
||||
}
|
||||
|
||||
/// Kill a process by PID. Returns true if the signal was sent.
|
||||
pub async fn kill(&self, pid: u32) -> bool {
|
||||
// SIGTERM the process group (negative PID kills the group)
|
||||
let ret = unsafe { libc::kill(-(pid as i32), libc::SIGTERM) };
|
||||
if ret != 0 {
|
||||
// Try just the process
|
||||
unsafe { libc::kill(pid as i32, libc::SIGTERM) };
|
||||
}
|
||||
// Don't unregister — let the normal exit path do that
|
||||
// so the tool result says "killed by user"
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate output if it exceeds max length, appending a truncation notice.
|
||||
pub fn truncate_output(mut s: String, max: usize) -> String {
|
||||
if s.len() > max {
|
||||
s.truncate(max);
|
||||
s.push_str("\n... (output truncated)");
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Dispatch a tool call by name.
|
||||
///
|
||||
|
|
@ -28,12 +181,14 @@ pub use crate::agent::{ToolDef, ToolOutput, ProcessTracker, truncate_output};
|
|||
///
|
||||
/// Note: working_stack is handled in runner.rs before reaching this
|
||||
/// function (it needs mutable context access).
|
||||
/// Dispatch a tool call by name. Handles all tools:
|
||||
/// agent-specific (control, vision), memory/journal, file/bash.
|
||||
pub async fn dispatch(
|
||||
name: &str,
|
||||
args: &serde_json::Value,
|
||||
tracker: &ProcessTracker,
|
||||
) -> ToolOutput {
|
||||
// Agent-specific tools that return Result<ToolOutput> directly
|
||||
// Agent-specific tools
|
||||
let rich_result = match name {
|
||||
"pause" => Some(control::pause(args)),
|
||||
"switch_model" => Some(control::switch_model(args)),
|
||||
|
|
@ -45,21 +200,125 @@ pub async fn dispatch(
|
|||
return result.unwrap_or_else(ToolOutput::error);
|
||||
}
|
||||
|
||||
// Delegate to shared thought layer (poc-agent uses default provenance)
|
||||
if let Some(output) = crate::agent::dispatch(name, args, tracker, None).await {
|
||||
if let Some(output) = dispatch_shared(name, args, tracker, None).await {
|
||||
return output;
|
||||
}
|
||||
|
||||
ToolOutput::error(format!("Unknown tool: {}", name))
|
||||
}
|
||||
|
||||
/// Return all tool definitions (agent-specific + shared).
|
||||
/// Dispatch shared tools (memory, file, bash). Used by both the
|
||||
/// interactive agent and subconscious agents. Provenance tracks
|
||||
/// which agent made the call for memory attribution.
|
||||
pub async fn dispatch_shared(
|
||||
name: &str,
|
||||
args: &serde_json::Value,
|
||||
tracker: &ProcessTracker,
|
||||
provenance: Option<&str>,
|
||||
) -> Option<ToolOutput> {
|
||||
// Memory and journal tools
|
||||
if name.starts_with("memory_") || name.starts_with("journal_") || name == "output" {
|
||||
let result = memory::dispatch(name, args, provenance);
|
||||
return Some(match result {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
});
|
||||
}
|
||||
|
||||
// File and execution tools
|
||||
let result = match name {
|
||||
"read_file" => read::read_file(args),
|
||||
"write_file" => write::write_file(args),
|
||||
"edit_file" => edit::edit_file(args),
|
||||
"bash" => bash::run_bash(args, tracker).await,
|
||||
"grep" => grep::grep(args),
|
||||
"glob" => glob::glob_search(args),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(match result {
|
||||
Ok(s) => ToolOutput::text(s),
|
||||
Err(e) => ToolOutput::error(e),
|
||||
})
|
||||
}
|
||||
|
||||
/// Return all tool definitions (agent-specific + shared + memory).
|
||||
pub fn definitions() -> Vec<ToolDef> {
|
||||
let mut defs = vec![
|
||||
vision::definition(),
|
||||
working_stack::definition(),
|
||||
read::definition(),
|
||||
write::definition(),
|
||||
edit::definition(),
|
||||
bash::definition(),
|
||||
grep::definition(),
|
||||
glob::definition(),
|
||||
];
|
||||
defs.extend(control::definitions());
|
||||
defs.extend(crate::agent::all_definitions());
|
||||
defs.extend(memory::definitions());
|
||||
defs
|
||||
}
|
||||
|
||||
/// Return memory + journal tool definitions only.
|
||||
pub fn memory_and_journal_definitions() -> Vec<ToolDef> {
|
||||
let mut defs = memory::definitions();
|
||||
defs.extend(memory::journal_definitions());
|
||||
defs
|
||||
}
|
||||
|
||||
/// Create a short summary of tool args for the tools pane header.
|
||||
pub fn summarize_args(tool_name: &str, args: &serde_json::Value) -> String {
|
||||
match tool_name {
|
||||
"read_file" | "write_file" | "edit_file" => args["file_path"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
"bash" => {
|
||||
let cmd = args["command"].as_str().unwrap_or("");
|
||||
if cmd.len() > 60 {
|
||||
let end = cmd.char_indices()
|
||||
.map(|(i, _)| i)
|
||||
.take_while(|&i| i <= 60)
|
||||
.last()
|
||||
.unwrap_or(0);
|
||||
format!("{}...", &cmd[..end])
|
||||
} else {
|
||||
cmd.to_string()
|
||||
}
|
||||
}
|
||||
"grep" => {
|
||||
let pattern = args["pattern"].as_str().unwrap_or("");
|
||||
let path = args["path"].as_str().unwrap_or(".");
|
||||
format!("{} in {}", pattern, path)
|
||||
}
|
||||
"glob" => args["pattern"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
"view_image" => {
|
||||
if let Some(pane) = args["pane_id"].as_str() {
|
||||
format!("pane {}", pane)
|
||||
} else {
|
||||
args["file_path"].as_str().unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
"journal" => {
|
||||
let entry = args["entry"].as_str().unwrap_or("");
|
||||
if entry.len() > 60 {
|
||||
format!("{}...", &entry[..60])
|
||||
} else {
|
||||
entry.to_string()
|
||||
}
|
||||
}
|
||||
"yield_to_user" => args["message"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
"switch_model" => args["model"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string(),
|
||||
"pause" => String::new(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#![warn(unreachable_pub)]
|
||||
// poc-agent — Substrate-independent AI agent
|
||||
//
|
||||
// A minimal but complete agent framework designed for identity
|
||||
|
|
@ -32,7 +33,7 @@ use clap::Parser;
|
|||
use poc_memory::dbglog;
|
||||
|
||||
use poc_memory::user::*;
|
||||
use poc_memory::agent::{tools, runner::{Agent, TurnResult}};
|
||||
use poc_memory::agent::{tools, Agent, TurnResult};
|
||||
use poc_memory::user::api::ApiClient;
|
||||
use poc_memory::user::tui::HotkeyAction;
|
||||
use poc_memory::config::{self, AppConfig, SessionConfig};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
use crate::user::api::ApiClient;
|
||||
use crate::user::types::*;
|
||||
use crate::agent::{self, ProcessTracker};
|
||||
use crate::agent::tools::{self as agent_tools, ProcessTracker, ToolOutput};
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ pub async fn call_api_with_tools(
|
|||
let (ui_tx, mut ui_rx) = crate::user::ui_channel::channel();
|
||||
|
||||
// All available native tools for subconscious agents
|
||||
let all_tools = agent::memory_and_journal_definitions();
|
||||
let all_tools = agent_tools::memory_and_journal_definitions();
|
||||
// If agent header specifies a tools whitelist, filter to only those
|
||||
let tool_defs: Vec<_> = if tools.is_empty() {
|
||||
all_tools
|
||||
|
|
@ -175,9 +175,9 @@ pub async fn call_api_with_tools(
|
|||
};
|
||||
|
||||
let prov = provenance.borrow().clone();
|
||||
let output = match agent::dispatch(&call.function.name, &args, &tracker, Some(&prov)).await {
|
||||
let output = match agent_tools::dispatch_shared(&call.function.name, &args, &tracker, Some(&prov)).await {
|
||||
Some(out) => out,
|
||||
None => agent::ToolOutput::error(format!("Unknown tool: {}", call.function.name)),
|
||||
None => ToolOutput::error(format!("Unknown tool: {}", call.function.name)),
|
||||
};
|
||||
|
||||
if std::env::var("POC_AGENT_VERBOSE").is_ok() {
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ fn run_one_agent_inner(
|
|||
_llm_tag: &str,
|
||||
log: &(dyn Fn(&str) + Sync),
|
||||
) -> Result<AgentResult, String> {
|
||||
let all_tools = crate::agent::memory_and_journal_definitions();
|
||||
let all_tools = crate::agent::tools::memory_and_journal_definitions();
|
||||
let effective_tools: Vec<String> = if def.tools.is_empty() {
|
||||
all_tools.iter().map(|t| t.function.name.clone()).collect()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -307,10 +307,6 @@ pub(crate) fn parse_markdown(md: &str) -> Vec<Line<'static>> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// A tool call currently in flight — shown above the status bar.
|
||||
// ActiveTool moved to ui_channel — shared between Agent and TUI
|
||||
pub(crate) use crate::user::ui_channel::ActiveTool;
|
||||
|
||||
/// Main TUI application state.
|
||||
pub struct App {
|
||||
pub(crate) autonomous: PaneState,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Re-export tool types that moved to agent::tools
|
||||
pub use crate::agent::tools::{
|
||||
ToolDef, ToolCall, ToolCallDelta, ToolOutput,
|
||||
FunctionCall, FunctionDef, FunctionCallDelta,
|
||||
};
|
||||
|
||||
/// Message content — either plain text or an array of content parts
|
||||
/// (for multimodal messages with images). Serializes as a JSON string
|
||||
/// for text-only, or a JSON array for multimodal.
|
||||
|
|
@ -79,35 +85,7 @@ pub enum Role {
|
|||
Tool,
|
||||
}
|
||||
|
||||
/// A tool call requested by the model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolCall {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: String,
|
||||
pub function: FunctionCall,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionCall {
|
||||
pub name: String,
|
||||
pub arguments: String, // JSON string
|
||||
}
|
||||
|
||||
/// Tool definition sent to the model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolDef {
|
||||
#[serde(rename = "type")]
|
||||
pub tool_type: String,
|
||||
pub function: FunctionDef,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FunctionDef {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub parameters: serde_json::Value,
|
||||
}
|
||||
// FunctionCall, FunctionDef moved to agent::tools
|
||||
|
||||
/// Chat completion request.
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -202,23 +180,7 @@ pub struct Delta {
|
|||
pub tool_calls: Option<Vec<ToolCallDelta>>,
|
||||
}
|
||||
|
||||
/// A partial tool call within a streaming delta. The first chunk for a
|
||||
/// given tool call carries the id and function name; subsequent chunks
|
||||
/// carry argument fragments.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ToolCallDelta {
|
||||
pub index: usize,
|
||||
pub id: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub call_type: Option<String>,
|
||||
pub function: Option<FunctionCallDelta>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FunctionCallDelta {
|
||||
pub name: Option<String>,
|
||||
pub arguments: Option<String>,
|
||||
}
|
||||
// FunctionCallDelta moved to agent::tools
|
||||
|
||||
// --- Convenience constructors ---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue