Skip to main content

agent_base/tool/
mod.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use async_trait::async_trait;
5use serde_json::{Value, json};
6use tokio::sync::mpsc;
7
8use crate::engine::SessionStore;
9use crate::llm::LlmClient;
10use crate::types::{AgentError, AgentResult, SessionId, UserEvent};
11
12pub mod auto_continue;
13pub mod policy;
14pub mod subagent;
15pub mod update_plan;
16
17pub use auto_continue::AutoContinueTool;
18pub use subagent::{SubAgentSessionPolicy, SubAgentTool};
19pub use update_plan::UpdatePlanTool;
20
21pub use policy::ToolPolicy;
22
23#[derive(Clone, Debug, Default)]
24pub struct ToolOutput {
25    pub summary: String,
26    pub raw: Option<Value>,
27    pub control_flow: ToolControlFlow,
28    pub truncation: Option<TruncationInfo>,
29}
30
31#[derive(Clone, Debug)]
32pub struct TruncationInfo {
33    pub original_summary_len: usize,
34    pub original_raw_len: Option<usize>,
35    pub max_allowed_chars: usize,
36}
37
38#[derive(Clone, Debug, Default)]
39pub enum ToolControlFlow {
40    #[default]
41    Break,
42    Continue,
43}
44
45#[derive(Clone)]
46pub struct ToolContext {
47    pub session_id: SessionId,
48    /// Channel for sending user-space events (progress, sub-agent, structured).
49    /// Tools should use `emit_user_event()` or `emit_progress()`.
50    pub user_event_tx: mpsc::UnboundedSender<UserEvent>,
51    pub llm_client: Option<Arc<dyn LlmClient>>,
52    pub session_store: Option<Arc<dyn SessionStore>>,
53    /// Language preference for tool output.
54    /// Defaults to `Language::En` if not set.
55    pub language: crate::types::Language,
56    /// Cancellation token for checking if the operation should be cancelled.
57    pub cancel_token: tokio_util::sync::CancellationToken,
58}
59
60impl ToolContext {
61    /// Send a user-space event (progress, sub-agent forwarding, structured data).
62    pub fn emit_user_event(&self, event: UserEvent) {
63        let _ = self.user_event_tx.send(event);
64    }
65
66    /// Convenience: send a progress event with text.
67    pub fn emit_progress(&self, text: impl Into<String>) {
68        self.emit_user_event(UserEvent::Progress { text: text.into() });
69    }
70}
71
72#[async_trait]
73pub trait Tool: Send + Sync {
74    fn name(&self) -> &'static str;
75    fn definition(&self) -> Value;
76    async fn call(&self, args: &Value, ctx: &ToolContext) -> AgentResult<ToolOutput>;
77
78    /// Return `Some(&dyn FrameworkTool)` if this tool is a framework-internal tool
79    /// that needs engine infrastructure injection (EventBus).
80    /// Default returns `None` — user-defined tools need not override this.
81    #[allow(private_interfaces)]
82    fn as_framework_tool(&self) -> Option<&dyn FrameworkTool> {
83        None
84    }
85}
86
87/// Marker trait for framework-internal tools that require engine infrastructure.
88///
89/// Framework tools implement this trait to receive `EventBus`
90/// references during `AgentBuilder::build()`. User-defined tools do not need this.
91///
92/// All methods have default no-op implementations so only the needed injection
93/// points need to be overridden.
94pub(crate) trait FrameworkTool: Tool {
95    /// Inject the internal event bus. Called once during build.
96    fn set_event_bus(&self, _event_bus: crate::engine::EventBus) {}
97}
98
99#[async_trait]
100pub trait TypedTool: Send + Sync {
101    type Args: serde::de::DeserializeOwned;
102    type Output: serde::Serialize;
103
104    fn name(&self) -> &'static str;
105    fn description(&self) -> &'static str;
106    fn parameters_schema(&self) -> Value;
107    async fn call_typed(&self, args: Self::Args, ctx: &ToolContext) -> AgentResult<Self::Output>;
108
109    fn control_flow() -> ToolControlFlow
110    where
111        Self: Sized,
112    {
113        ToolControlFlow::Break
114    }
115
116    fn format_output(&self, output: Self::Output) -> String {
117        serde_json::to_string(&output).unwrap_or_default()
118    }
119}
120
121#[async_trait]
122impl<T: TypedTool + Send + Sync + 'static> Tool for T {
123    fn name(&self) -> &'static str {
124        TypedTool::name(self)
125    }
126
127    fn definition(&self) -> Value {
128        json!({
129            "type": "function",
130            "function": {
131                "name": self.name(),
132                "description": self.description(),
133                "parameters": self.parameters_schema(),
134            }
135        })
136    }
137
138    async fn call(&self, args: &Value, ctx: &ToolContext) -> AgentResult<ToolOutput> {
139        let typed_args: T::Args =
140            serde_json::from_value(args.clone()).map_err(|_| AgentError::ToolArgsInvalid {
141                name: self.name().to_string(),
142                raw: args.to_string(),
143            })?;
144        let output = self.call_typed(typed_args, ctx).await?;
145        let output_json = serde_json::to_value(&output).ok();
146        let summary = self.format_output(output);
147        Ok(ToolOutput {
148            summary,
149            raw: output_json,
150            control_flow: T::control_flow(),
151            truncation: None,
152        })
153    }
154}
155
156pub(crate) type ToolRef = Arc<dyn Tool>;
157
158#[derive(Clone, Default)]
159pub struct ToolRegistry {
160    tools: HashMap<String, ToolRef>,
161}
162
163impl ToolRegistry {
164    pub fn register(&mut self, tool: impl Tool + 'static) {
165        self.tools.insert(tool.name().to_string(), Arc::new(tool));
166    }
167
168    pub fn register_arc(&mut self, tool: Arc<dyn Tool>) {
169        self.tools.insert(tool.name().to_string(), tool);
170    }
171
172    pub fn update(&mut self, tool: impl Tool + 'static) {
173        self.tools.insert(tool.name().to_string(), Arc::new(tool));
174    }
175
176    /// Remove a tool from the registry by name.
177    pub fn remove(&mut self, name: &str) {
178        self.tools.remove(name);
179    }
180
181    pub fn get(&self, name: &str) -> Option<ToolRef> {
182        self.tools.get(name).cloned()
183    }
184
185    pub fn definitions(&self) -> Vec<Value> {
186        self.tools.values().map(|tool| tool.definition()).collect()
187    }
188
189    pub fn len(&self) -> usize {
190        self.tools.len()
191    }
192
193    pub fn is_empty(&self) -> bool {
194        self.tools.is_empty()
195    }
196
197    /// Inject the internal `EventBus` into framework-provided tools.
198    pub fn inject_event_bus(&self, event_bus: &crate::engine::EventBus) {
199        for tool in self.tools.values() {
200            if let Some(fw) = tool.as_framework_tool() {
201                fw.set_event_bus(event_bus.clone());
202            }
203        }
204    }
205}