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}