1use std::{ops::Deref, time::Duration};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6pub use mentra_provider::{
7 ProviderToolKind, ToolLoadingPolicy, ToolSpec as ProviderToolSpec,
8 ToolSpecBuilder as ProviderToolSpecBuilder,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub enum ToolCapability {
14 ReadOnly,
15 FilesystemRead,
16 FilesystemWrite,
17 ProcessExec,
18 BackgroundExec,
19 TaskMutation,
20 TeamCoordination,
21 Delegation,
22 ContextCompaction,
23 SkillLoad,
24 Custom(String),
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
29pub enum ToolSideEffectLevel {
30 #[default]
31 None,
32 LocalState,
33 Process,
34 External,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
39pub enum ToolDurability {
40 #[default]
41 Ephemeral,
42 Persistent,
43 ReplaySafe,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
48pub enum ToolExecutionCategory {
49 ReadOnlyParallel,
50 #[default]
51 ExclusiveLocalMutation,
52 ExclusivePersistentMutation,
53 BackgroundJob,
54 Delegation,
55}
56
57impl ToolExecutionCategory {
58 pub fn allows_parallel(self) -> bool {
59 matches!(self, Self::ReadOnlyParallel)
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
65pub enum ToolApprovalCategory {
66 #[default]
67 Default,
68 ReadOnly,
69 Filesystem,
70 Process,
71 Background,
72 Delegation,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct RuntimeToolDescriptor {
78 pub provider: ProviderToolSpec,
79 pub capabilities: Vec<ToolCapability>,
80 pub side_effect_level: ToolSideEffectLevel,
81 pub durability: ToolDurability,
82 pub execution_category: ToolExecutionCategory,
83 pub approval_category: ToolApprovalCategory,
84 pub execution_timeout: Option<Duration>,
85 #[serde(default)]
90 pub terminal: bool,
91}
92
93impl RuntimeToolDescriptor {
94 pub fn builder(name: impl Into<String>) -> RuntimeToolDescriptorBuilder {
95 RuntimeToolDescriptorBuilder {
96 provider: ProviderToolSpec::builder(name),
97 capabilities: Vec::new(),
98 side_effect_level: ToolSideEffectLevel::None,
99 durability: ToolDurability::Ephemeral,
100 execution_category: ToolExecutionCategory::ExclusiveLocalMutation,
101 approval_category: ToolApprovalCategory::Default,
102 execution_timeout: None,
103 terminal: false,
104 }
105 }
106
107 pub fn provider_spec(&self) -> &ProviderToolSpec {
108 &self.provider
109 }
110}
111
112impl Deref for RuntimeToolDescriptor {
113 type Target = ProviderToolSpec;
114
115 fn deref(&self) -> &Self::Target {
116 &self.provider
117 }
118}
119
120#[derive(Debug, Clone)]
121pub struct RuntimeToolDescriptorBuilder {
122 provider: ProviderToolSpecBuilder,
123 capabilities: Vec<ToolCapability>,
124 side_effect_level: ToolSideEffectLevel,
125 durability: ToolDurability,
126 execution_category: ToolExecutionCategory,
127 approval_category: ToolApprovalCategory,
128 execution_timeout: Option<Duration>,
129 terminal: bool,
130}
131
132impl RuntimeToolDescriptorBuilder {
133 pub fn description(mut self, description: impl Into<String>) -> Self {
134 self.provider = self.provider.description(description);
135 self
136 }
137
138 pub fn input_schema(mut self, input_schema: Value) -> Self {
139 self.provider = self.provider.input_schema(input_schema);
140 self
141 }
142
143 pub fn output_schema(mut self, output_schema: Value) -> Self {
144 self.provider = self.provider.output_schema(output_schema);
145 self
146 }
147
148 pub fn provider_kind(mut self, kind: ProviderToolKind) -> Self {
149 self.provider = self.provider.kind(kind);
150 self
151 }
152
153 pub fn provider_options(mut self, options: Value) -> Self {
154 self.provider = self.provider.options(options);
155 self
156 }
157
158 pub fn loading_policy(mut self, loading_policy: ToolLoadingPolicy) -> Self {
159 self.provider = self.provider.loading_policy(loading_policy);
160 self
161 }
162
163 pub fn strict(mut self, strict: bool) -> Self {
164 self.provider = self.provider.strict(strict);
165 self
166 }
167
168 pub fn non_strict(mut self) -> Self {
169 self.provider = self.provider.non_strict();
170 self
171 }
172
173 pub fn defer_loading(mut self, defer_loading: bool) -> Self {
174 self.provider = self.provider.defer_loading(defer_loading);
175 self
176 }
177
178 pub fn capability(mut self, capability: ToolCapability) -> Self {
179 self.capabilities.push(capability);
180 self
181 }
182
183 pub fn capabilities(mut self, capabilities: impl IntoIterator<Item = ToolCapability>) -> Self {
184 self.capabilities = capabilities.into_iter().collect();
185 self
186 }
187
188 pub fn side_effect_level(mut self, side_effect_level: ToolSideEffectLevel) -> Self {
189 self.side_effect_level = side_effect_level;
190 self
191 }
192
193 pub fn durability(mut self, durability: ToolDurability) -> Self {
194 self.durability = durability;
195 self
196 }
197
198 pub fn execution_category(mut self, execution_category: ToolExecutionCategory) -> Self {
199 self.execution_category = execution_category;
200 self
201 }
202
203 pub fn approval_category(mut self, approval_category: ToolApprovalCategory) -> Self {
204 self.approval_category = approval_category;
205 self
206 }
207
208 pub fn execution_timeout(mut self, execution_timeout: Duration) -> Self {
209 self.execution_timeout = Some(execution_timeout);
210 self
211 }
212
213 pub fn terminal(mut self) -> Self {
218 self.terminal = true;
219 self
220 }
221
222 pub fn build(self) -> RuntimeToolDescriptor {
223 RuntimeToolDescriptor {
224 provider: self.provider.build(),
225 capabilities: self.capabilities,
226 side_effect_level: self.side_effect_level,
227 durability: self.durability,
228 execution_category: self.execution_category,
229 approval_category: self.approval_category,
230 execution_timeout: self.execution_timeout,
231 terminal: self.terminal,
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn descriptor_defaults_to_non_terminal() {
242 let descriptor = RuntimeToolDescriptor::builder("plain_tool").build();
243 assert!(!descriptor.terminal);
244 }
245
246 #[test]
247 fn terminal_flag_is_independent_of_declared_execution_category() {
248 let descriptor = RuntimeToolDescriptor::builder("finish_tool")
249 .execution_category(ToolExecutionCategory::ReadOnlyParallel)
250 .terminal()
251 .build();
252 assert!(descriptor.terminal);
253 assert_eq!(
254 descriptor.execution_category,
255 ToolExecutionCategory::ReadOnlyParallel
256 );
257 }
258}