vv_agent/tools/
orchestrator.rs1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use serde_json::json;
5
6use crate::tools::{ToolContext, ToolExecutor, ToolRunContext};
7use crate::types::{ToolCall, ToolDirective, ToolExecutionResult, ToolResultStatus};
8
9#[derive(Clone, Default)]
10pub struct ToolRunOptions {
11 allowed_tools: Option<Vec<String>>,
12 disallowed_tools: Vec<String>,
13}
14
15impl ToolRunOptions {
16 pub fn allow_only(mut self, tools: impl IntoIterator<Item = impl Into<String>>) -> Self {
17 self.allowed_tools = Some(tools.into_iter().map(Into::into).collect());
18 self
19 }
20
21 pub fn disallow(mut self, tool: impl Into<String>) -> Self {
22 self.disallowed_tools.push(tool.into());
23 self
24 }
25}
26
27#[derive(Clone, Default)]
28pub struct ToolOrchestrator {
29 tools: BTreeMap<String, Arc<dyn ToolExecutor>>,
30}
31
32impl ToolOrchestrator {
33 pub fn from_tools(tools: Vec<Arc<dyn ToolExecutor>>) -> Self {
34 let tools = tools
35 .into_iter()
36 .map(|tool| (tool.name().to_string(), tool))
37 .collect();
38 Self { tools }
39 }
40
41 pub async fn run_one(
42 &self,
43 call: ToolCall,
44 context: &mut ToolContext,
45 options: ToolRunOptions,
46 ) -> Result<ToolExecutionResult, crate::tools::ToolError> {
47 if let Some(allowed) = options.allowed_tools.as_ref() {
48 if !allowed.iter().any(|tool| tool == &call.name) {
49 return Ok(tool_error(
50 &call,
51 "tool_not_allowed",
52 "Tool is not in the allowed tool list.",
53 ));
54 }
55 }
56 if options
57 .disallowed_tools
58 .iter()
59 .any(|tool| tool == &call.name)
60 {
61 return Ok(tool_error(
62 &call,
63 "tool_disallowed",
64 "Tool is disallowed by policy.",
65 ));
66 }
67
68 let Some(tool) = self.tools.get(&call.name) else {
69 return Ok(tool_error(
70 &call,
71 "tool_not_found",
72 format!("Unknown tool: {}", call.name),
73 ));
74 };
75
76 let mut result = tool.run(call.clone(), ToolRunContext::new(context)).await?;
77 if result.tool_call_id.trim().is_empty() || result.tool_call_id == "pending" {
78 result.tool_call_id = call.id;
79 }
80 Ok(result)
81 }
82}
83
84fn tool_error(
85 call: &ToolCall,
86 error_code: &str,
87 message: impl Into<String>,
88) -> ToolExecutionResult {
89 let error_code = error_code.to_string();
90 ToolExecutionResult {
91 tool_call_id: call.id.clone(),
92 content: json!({
93 "ok": false,
94 "error": message.into(),
95 "error_code": error_code,
96 "tool_name": call.name,
97 })
98 .to_string(),
99 status: ToolResultStatus::Error,
100 directive: ToolDirective::Continue,
101 error_code: Some(error_code),
102 metadata: BTreeMap::new(),
103 image_url: None,
104 image_path: None,
105 }
106}