Skip to main content

talos_core/tool/
agent_tool.rs

1use std::collections::HashSet;
2
3use async_trait::async_trait;
4use serde_json::Value;
5
6use super::{
7    ToolBackend, ToolExecutionAuthorization, ToolExecutionOutput, ToolFamily, ToolNature,
8    ToolPermissionFacet, ToolProvenance, ToolResult, ToolResultProjection,
9};
10
11/// A pluggable agent tool that can be registered and invoked dynamically.
12///
13/// Implementors must provide a name, description, parameter schema, and
14/// execution logic. The trait is object-safe and can be used as
15/// `dyn AgentTool` behind an `Arc`.
16#[async_trait]
17pub trait AgentTool: Send + Sync {
18    /// Returns the unique name of this tool.
19    fn name(&self) -> &str;
20
21    /// Returns a human-readable description of what this tool does.
22    fn description(&self) -> &str;
23
24    /// Returns the JSON Schema describing the expected input parameters.
25    ///
26    /// The default implementation uses `schemars` to generate a schema from
27    /// the associated `Parameters` type. Override this method to provide a
28    /// custom schema.
29    fn parameters(&self) -> Value;
30
31    /// Executes the tool with the given input and returns a result.
32    ///
33    /// The `input` is expected to conform to the schema returned by
34    /// [`parameters`](Self::parameters).
35    async fn execute(&self, input: Value) -> ToolResult;
36
37    /// Executes with concrete authorizations produced by a permission-aware
38    /// composition root.
39    ///
40    /// Most tools do not need a path capability and retain their existing
41    /// behavior. File tools override this method to validate external paths.
42    async fn execute_authorized(
43        &self,
44        input: Value,
45        _authorizations: &[ToolExecutionAuthorization],
46    ) -> ToolResult {
47        self.execute(input).await
48    }
49
50    /// Executes with concrete authorizations and returns an output that
51    /// may carry a provider-neutral continuation artifact (ADR-051).
52    ///
53    /// The default implementation delegates to [`execute_authorized`]
54    /// and returns the result with no continuation parts. Tools that
55    /// produce one-shot provider artifacts (e.g. `read_image`) override
56    /// this method.
57    ///
58    /// Permission wrappers MUST forward this method after obtaining the
59    /// same authorizations they would use for [`execute_authorized`].
60    async fn execute_authorized_with_output(
61        &self,
62        input: Value,
63        authorizations: &[ToolExecutionAuthorization],
64    ) -> ToolExecutionOutput {
65        ToolExecutionOutput::from_result(self.execute_authorized(input, authorizations).await)
66    }
67
68    /// Executes the tool and returns an output that may carry a
69    /// provider-neutral continuation artifact (ADR-051).
70    ///
71    /// The default implementation delegates to [`execute`] and returns
72    /// no continuation parts. Permission wrappers override this to
73    /// perform the same approval flow as [`execute`] and return the
74    /// full [`ToolExecutionOutput`] including any continuation parts
75    /// produced by the inner tool's [`execute_authorized_with_output`].
76    async fn execute_with_output(&self, input: Value) -> ToolExecutionOutput {
77        ToolExecutionOutput::from_result(self.execute(input).await)
78    }
79
80    /// Returns the observer-safe form of a tool input.
81    ///
82    /// Execution and permission evaluation always receive the original input.
83    /// This projection is used only for UI events, approval presentation, and
84    /// durable replay. The default preserves the complete input.
85    fn project_input(&self, input: &Value) -> Value {
86        input.clone()
87    }
88
89    /// Splits one execution result into model, display, and persistence views.
90    ///
91    /// The default keeps existing tools fully backward compatible.
92    fn project_result(&self, result: &ToolResult) -> ToolResultProjection {
93        ToolResultProjection::shared(result.content.clone())
94    }
95
96    /// Returns whether this tool is read-only (does not modify external state).
97    ///
98    /// The default implementation returns `false`. Override for tools that
99    /// only read data (e.g., file readers, web fetchers).
100    fn is_read_only(&self) -> bool {
101        false
102    }
103
104    fn nature(&self) -> ToolNature {
105        if self.is_read_only() {
106            ToolNature::Read
107        } else {
108            ToolNature::Write
109        }
110    }
111
112    /// Returns the stable presentation family for this tool.
113    fn family(&self) -> ToolFamily {
114        ToolFamily::Extension
115    }
116
117    /// Returns whether this tool belongs to the always-on presentation set.
118    fn is_always_on(&self) -> bool {
119        false
120    }
121
122    /// Returns conditional backends supported by this tool.
123    ///
124    /// Tools with no conditional execution paths should rely on the default
125    /// empty list.
126    fn conditional_backends(&self) -> Vec<ToolBackend> {
127        Vec::new()
128    }
129
130    /// Returns the backend selected by this concrete input, if any.
131    ///
132    /// The agent runtime checks this value against the presentation policy
133    /// before permission evaluation or execution. Returning `None` means the
134    /// tool is using its base path.
135    fn backend_for_input(&self, _input: &Value) -> Option<String> {
136        None
137    }
138
139    /// Returns a model-facing description for the disclosed backend set.
140    fn description_for_backends(&self, _backends: &HashSet<String>) -> String {
141        self.description().to_string()
142    }
143
144    /// Returns an input schema for the disclosed backend set.
145    fn parameters_for_backends(&self, _backends: &HashSet<String>) -> Value {
146        self.parameters()
147    }
148
149    /// Returns the permission facets touched by this concrete invocation.
150    ///
151    /// Tools that only touch one risk surface can rely on the default
152    /// single-facet profile derived from [`nature`](Self::nature). Hybrid tools
153    /// should override this to expose every relevant risk surface.
154    fn permission_profile(&self, _input: &Value) -> Vec<ToolPermissionFacet> {
155        vec![ToolPermissionFacet::new(self.nature())]
156    }
157
158    fn summary_fields(&self) -> &'static [&'static str] {
159        &[]
160    }
161
162    /// Returns the provenance of this tool.
163    ///
164    /// The default implementation returns [`ToolProvenance::Native`].
165    /// Override for tools that live in another process or behind a
166    /// network boundary (e.g., MCP remote tools) so consumers can
167    /// render an origin marker in the UI.
168    fn provenance(&self) -> ToolProvenance {
169        ToolProvenance::Native
170    }
171}