vv_agent/tools/
executor.rs1use 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}