Skip to main content

vv_agent/tools/
executor.rs

1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::time::Duration;
5
6use crate::tools::{ToolContext, ToolSpec};
7use crate::types::{ToolCall, ToolExecutionResult};
8
9pub type ToolFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T, ToolError>> + Send + 'a>>;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ToolExposure {
13    Direct,
14    Deferred,
15    DirectModelOnly,
16    Hidden,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ApprovalRequirement {
21    NotRequired,
22    Required,
23    Provider,
24}
25
26#[derive(Debug, Clone, Default)]
27pub struct ToolSpecContext;
28
29pub struct ToolRunContext<'a> {
30    pub context: &'a mut ToolContext,
31}
32
33impl<'a> ToolRunContext<'a> {
34    pub fn new(context: &'a mut ToolContext) -> Self {
35        Self { context }
36    }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct ToolError {
41    message: String,
42}
43
44impl ToolError {
45    pub fn new(message: impl Into<String>) -> Self {
46        Self {
47            message: message.into(),
48        }
49    }
50}
51
52impl std::fmt::Display for ToolError {
53    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        formatter.write_str(&self.message)
55    }
56}
57
58impl std::error::Error for ToolError {}
59
60pub trait ToolExecutor: Send + Sync {
61    fn name(&self) -> &str;
62    fn description(&self) -> &str;
63    fn exposure(&self) -> ToolExposure {
64        ToolExposure::Direct
65    }
66    fn supports_parallel(&self) -> bool {
67        false
68    }
69    fn timeout(&self) -> Option<Duration> {
70        None
71    }
72    fn spec(&self, _ctx: &ToolSpecContext) -> Result<ToolSpec, ToolError>;
73    fn approval_requirement(
74        &self,
75        _call: &ToolCall,
76        _ctx: &ToolRunContext<'_>,
77    ) -> ApprovalRequirement {
78        ApprovalRequirement::Provider
79    }
80    fn run<'a>(
81        &'a self,
82        call: ToolCall,
83        ctx: ToolRunContext<'a>,
84    ) -> ToolFuture<'a, ToolExecutionResult>;
85}
86
87#[derive(Clone)]
88pub struct ToolSpecExecutor {
89    spec: ToolSpec,
90    exposure: ToolExposure,
91}
92
93impl ToolSpecExecutor {
94    pub fn new(spec: ToolSpec) -> Self {
95        Self {
96            spec,
97            exposure: ToolExposure::Direct,
98        }
99    }
100
101    pub fn with_exposure(mut self, exposure: ToolExposure) -> Self {
102        self.exposure = exposure;
103        self
104    }
105
106    pub fn into_arc(self) -> Arc<dyn ToolExecutor> {
107        Arc::new(self)
108    }
109}
110
111impl ToolExecutor for ToolSpecExecutor {
112    fn name(&self) -> &str {
113        &self.spec.name
114    }
115
116    fn description(&self) -> &str {
117        &self.spec.description
118    }
119
120    fn exposure(&self) -> ToolExposure {
121        self.exposure
122    }
123
124    fn spec(&self, _ctx: &ToolSpecContext) -> Result<ToolSpec, ToolError> {
125        Ok(self.spec.clone())
126    }
127
128    fn run<'a>(
129        &'a self,
130        call: ToolCall,
131        ctx: ToolRunContext<'a>,
132    ) -> ToolFuture<'a, ToolExecutionResult> {
133        Box::pin(async move {
134            let mut result = (self.spec.handler)(ctx.context, &call.arguments);
135            if result.tool_call_id.trim().is_empty() || result.tool_call_id == "pending" {
136                result.tool_call_id = call.id;
137            }
138            Ok(result)
139        })
140    }
141}