use crate::agent::types::{ToolCall, ToolExecutionMode};
use crate::tui::Theme;
use async_trait::async_trait;
use std::borrow::Cow;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use tokio::sync::mpsc::UnboundedSender;
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum BlockReason {
Security(String),
Policy(String),
Other(String),
}
#[derive(Debug, Clone)]
pub struct AutocompleteItem {
pub value: String,
pub label: String,
pub description: Option<String>,
}
pub trait CommandHandler: Send + Sync {
fn execute(&self, args: &str) -> anyhow::Result<CommandResult>;
fn argument_completions(&self, _prefix: &str) -> Vec<AutocompleteItem> {
vec![]
}
}
#[derive(Debug, Clone)]
pub enum CommandResult {
Info(String),
Quit,
ModelChanged(String),
ShowHelp,
Reloaded,
NewSession,
SessionSwitched { path: std::path::PathBuf },
SessionInfo {
session_id: String,
file_path: Option<std::path::PathBuf>,
name: Option<String>,
message_count: usize,
},
OpenSessionSelector,
SessionNamed { name: String },
}
pub struct SlashCommand {
pub name: String,
pub description: String,
pub handler: Box<dyn CommandHandler>,
}
#[derive(Debug, Clone)]
pub struct Cancel {
flag: Arc<AtomicBool>,
}
impl Cancel {
pub fn new() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
}
pub fn is_cancelled(&self) -> bool {
self.flag.load(Ordering::Relaxed)
}
pub fn cancel(&self) {
self.flag.store(true, Ordering::Relaxed);
}
pub fn check(&self) -> anyhow::Result<()> {
if self.is_cancelled() {
Err(anyhow::anyhow!("Operation cancelled"))
} else {
Ok(())
}
}
}
impl Default for Cancel {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ToolOutput {
pub content: String,
pub compact: Option<String>,
pub is_error: bool,
pub terminate: bool,
}
impl ToolOutput {
pub fn ok(content: impl Into<String>) -> Self {
Self {
content: content.into(),
compact: None,
is_error: false,
terminate: false,
}
}
pub fn ok_with_compact(content: impl Into<String>, compact: impl Into<String>) -> Self {
Self {
content: content.into(),
compact: Some(compact.into()),
is_error: false,
terminate: false,
}
}
pub fn err(message: impl Into<String>) -> Self {
Self {
content: message.into(),
compact: None,
is_error: true,
terminate: false,
}
}
pub fn with_terminate(mut self, terminate: bool) -> Self {
self.terminate = terminate;
self
}
}
#[derive(Debug, Clone)]
pub struct ToolRenderContext {
pub expanded: bool,
pub args_complete: bool,
pub is_partial: bool,
pub is_error: bool,
pub cwd: String,
pub duration_secs: Option<f64>,
pub exit_code: Option<i32>,
pub cancelled: bool,
pub was_truncated: bool,
pub full_output_path: Option<String>,
pub file_path: Option<String>,
pub expand_key: String,
}
pub trait ToolRenderer: Send + Sync {
fn render_call(
&self,
args: &serde_json::Value,
width: usize,
theme: &dyn Theme,
ctx: &ToolRenderContext,
) -> Vec<String>;
fn render_result(
&self,
content: &str,
width: usize,
theme: &dyn Theme,
ctx: &ToolRenderContext,
) -> Vec<String>;
fn render_self(&self) -> bool {
false
}
}
#[async_trait]
pub trait AgentTool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> serde_json::Value;
#[allow(dead_code)]
fn label(&self) -> &str;
fn execution_mode(&self) -> ToolExecutionMode {
ToolExecutionMode::Parallel
}
fn prepare_arguments(&self, args: serde_json::Value) -> serde_json::Value {
args
}
fn renderer(&self) -> Option<Box<dyn ToolRenderer>> {
None
}
fn prompt_guidelines(&self) -> Vec<String> {
vec![]
}
async fn execute(
&self,
tool_call_id: String,
args: serde_json::Value,
cancel: Cancel,
on_update: Option<UnboundedSender<ToolOutput>>,
) -> anyhow::Result<ToolOutput>;
}
#[async_trait]
#[allow(dead_code)]
pub trait Extension: Send + Sync {
fn name(&self) -> Cow<'static, str>;
fn tools(&self) -> Vec<Box<dyn AgentTool>> {
vec![]
}
fn commands(&self) -> Vec<SlashCommand> {
vec![]
}
async fn before_tool_call(&self, _tc: &ToolCall) -> Option<BlockReason> {
None
}
async fn after_tool_call(&self, _tc: &ToolCall, _result: &str) -> Option<String> {
None
}
}