Skip to main content

mentra/tool/
descriptor.rs

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/// High-level capability labels used for runtime metadata and policy decisions.
12#[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/// Declares how much side effect a tool may have when executed.
28#[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/// Declares whether a tool call is safe to replay or persist.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
39pub enum ToolDurability {
40    #[default]
41    Ephemeral,
42    Persistent,
43    ReplaySafe,
44}
45
46/// Declares which scheduler/orchestrator lane a tool call belongs to.
47#[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/// Coarse authorization grouping for runtime policy and review systems.
64#[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/// Runtime-facing descriptor that wraps the provider-visible tool definition.
76#[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    /// Marks this tool as a terminal action (see
86    /// [`RuntimeToolDescriptorBuilder::terminal`]). A terminal-marked tool is
87    /// never scheduled in a parallel batch: the scheduler coerces it to an
88    /// exclusive execution category regardless of `execution_category`.
89    #[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    /// Marks this tool as a terminal action: a call is never scheduled in a
214    /// parallel batch regardless of `execution_category` — the scheduler
215    /// coerces it to an exclusive category, so it can never race parallel
216    /// retrieval calls scheduled in the same round.
217    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}