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::StreamClient;
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 StreamClient>>,
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    /// Convenience: emit a partial result during long-running tool execution.
72    /// `is_partial: true` means more output is coming; `false` means final.
73    pub fn emit_partial_result(
74        &self,
75        tool_call_id: &str,
76        content: impl Into<String>,
77        is_partial: bool,
78    ) {
79        self.emit_user_event(UserEvent::ToolPartialResult {
80            tool_call_id: tool_call_id.to_string(),
81            content: content.into(),
82            is_partial,
83        });
84    }
85}
86
87/// Machine-readable metadata for a registered tool — origin, version, and
88/// runtime requirements in a stable shape consumers can inspect without
89/// parsing the LLM-facing definition JSON.
90#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
91pub struct ToolMetadata {
92    /// Tool name (matches [`Tool::name`]).
93    pub name: String,
94    /// Human-readable description (matches the description in [`Tool::definition`]).
95    pub description: String,
96    /// Where this tool comes from: a crate name (e.g. `"phi-tools"`), a
97    /// framework identifier (`"agent-base"`, `"agent-works"`), or
98    /// `"custom"` for user-defined tools.
99    pub origin: String,
100    /// Crate / package version, or `"unknown"` when built outside a crate.
101    pub version: String,
102    /// Optional runtime requirements or capabilities this tool depends on
103    /// (e.g. `["chrome-cdp"]` for browser tools). Empty when there are
104    /// none.
105    pub requirements: Vec<String>,
106}
107
108#[async_trait]
109pub trait Tool: Send + Sync {
110    fn name(&self) -> &'static str;
111    fn definition(&self) -> Value;
112    async fn call(&self, args: &Value, ctx: &ToolContext) -> AgentResult<ToolOutput>;
113
114    /// Return `Some(&dyn FrameworkTool)` if this tool is a framework-internal tool
115    /// that needs engine infrastructure injection (EventBus).
116    /// Default returns `None` — user-defined tools need not override this.
117    #[allow(private_interfaces)]
118    fn as_framework_tool(&self) -> Option<&dyn FrameworkTool> {
119        None
120    }
121
122    /// Machine-readable metadata for tool introspection.
123    ///
124    /// The default implementation extracts `name` and `description` from
125    /// [`Tool::name`] and [`Tool::definition`], sets `origin` to `"custom"`,
126    /// and leaves `requirements` empty. Tool authors are encouraged to
127    /// override this to provide an accurate `origin` and `version`.
128    fn metadata(&self) -> ToolMetadata {
129        let name = self.name().to_string();
130        let description = self
131            .definition()
132            .get("function")
133            .and_then(|f| f.get("description"))
134            .and_then(|d| d.as_str())
135            .unwrap_or("")
136            .to_string();
137        ToolMetadata {
138            name,
139            description,
140            origin: "custom".to_string(),
141            version: "unknown".to_string(),
142            requirements: vec![],
143        }
144    }
145}
146
147/// Marker trait for framework-internal tools that require engine infrastructure.
148///
149/// Framework tools implement this trait to receive `EventBus`
150/// references during `AgentBuilder::build()`. User-defined tools do not need this.
151///
152/// All methods have default no-op implementations so only the needed injection
153/// points need to be overridden.
154pub(crate) trait FrameworkTool: Tool {
155    /// Inject the internal event bus. Called once during build.
156    fn set_event_bus(&self, _event_bus: crate::engine::EventBus) {}
157}
158
159#[async_trait]
160pub trait TypedTool: Send + Sync {
161    type Args: serde::de::DeserializeOwned;
162    type Output: serde::Serialize;
163
164    fn name(&self) -> &'static str;
165    fn description(&self) -> &'static str;
166    fn parameters_schema(&self) -> Value;
167    async fn call_typed(&self, args: Self::Args, ctx: &ToolContext) -> AgentResult<Self::Output>;
168
169    fn control_flow() -> ToolControlFlow
170    where
171        Self: Sized,
172    {
173        ToolControlFlow::Break
174    }
175
176    fn format_output(&self, output: Self::Output) -> String {
177        serde_json::to_string(&output).unwrap_or_default()
178    }
179}
180
181#[async_trait]
182impl<T: TypedTool + Send + Sync + 'static> Tool for T {
183    fn name(&self) -> &'static str {
184        TypedTool::name(self)
185    }
186
187    fn definition(&self) -> Value {
188        json!({
189            "type": "function",
190            "function": {
191                "name": self.name(),
192                "description": self.description(),
193                "parameters": self.parameters_schema(),
194            }
195        })
196    }
197
198    async fn call(&self, args: &Value, ctx: &ToolContext) -> AgentResult<ToolOutput> {
199        let typed_args: T::Args =
200            serde_json::from_value(args.clone()).map_err(|_| AgentError::ToolArgsInvalid {
201                name: self.name().to_string(),
202                raw: args.to_string(),
203            })?;
204        let output = self.call_typed(typed_args, ctx).await?;
205        let output_json = serde_json::to_value(&output).ok();
206        let summary = self.format_output(output);
207        Ok(ToolOutput {
208            summary,
209            raw: output_json,
210            control_flow: T::control_flow(),
211            truncation: None,
212        })
213    }
214}
215
216pub(crate) type ToolRef = Arc<dyn Tool>;
217
218#[derive(Clone, Default)]
219pub struct ToolRegistry {
220    tools: HashMap<String, ToolRef>,
221}
222
223impl ToolRegistry {
224    pub fn register(&mut self, tool: impl Tool + 'static) {
225        self.tools.insert(tool.name().to_string(), Arc::new(tool));
226    }
227
228    pub fn register_arc(&mut self, tool: Arc<dyn Tool>) {
229        self.tools.insert(tool.name().to_string(), tool);
230    }
231
232    pub fn update(&mut self, tool: impl Tool + 'static) {
233        self.tools.insert(tool.name().to_string(), Arc::new(tool));
234    }
235
236    /// Remove a tool from the registry by name.
237    pub fn remove(&mut self, name: &str) {
238        self.tools.remove(name);
239    }
240
241    pub fn get(&self, name: &str) -> Option<ToolRef> {
242        self.tools.get(name).cloned()
243    }
244
245    pub fn definitions(&self) -> Vec<Value> {
246        self.tools.values().map(|tool| tool.definition()).collect()
247    }
248
249    pub fn len(&self) -> usize {
250        self.tools.len()
251    }
252
253    pub fn is_empty(&self) -> bool {
254        self.tools.is_empty()
255    }
256
257    /// Collect metadata for every registered tool, sorted by name.
258    ///
259    /// This is the preferred introspection API for consumers — it returns a
260    /// stable `ToolMetadata` struct per tool instead of having callers parse
261    /// the LLM-facing JSON definitions.
262    pub fn metadatas(&self) -> Vec<ToolMetadata> {
263        let mut list: Vec<_> = self.tools.values().map(|tool| tool.metadata()).collect();
264        list.sort_by(|a, b| a.name.cmp(&b.name));
265        list
266    }
267
268    /// Inject the internal `EventBus` into framework-provided tools.
269    pub(crate) fn inject_event_bus(&self, event_bus: &crate::engine::EventBus) {
270        for tool in self.tools.values() {
271            if let Some(fw) = tool.as_framework_tool() {
272                fw.set_event_bus(event_bus.clone());
273            }
274        }
275    }
276}