Skip to main content

vv_agent/tools/
agent_tool.rs

1use std::sync::Arc;
2
3use serde_json::{json, Value};
4
5use crate::agent::Agent;
6use crate::run_config::RunConfig;
7use crate::runner::Runner;
8use crate::tools::{Tool, ToolContext, ToolOutput, ToolSpec};
9use crate::types::{SubTaskRequest, ToolArguments};
10
11#[derive(Clone)]
12pub struct AgentTool {
13    agent: Agent,
14    name: String,
15    description: String,
16    parameters_schema: Value,
17}
18
19impl AgentTool {
20    pub fn request_from_arguments(&self, raw_arguments: Value) -> Result<SubTaskRequest, String> {
21        let object = raw_arguments
22            .as_object()
23            .ok_or_else(|| "agent tool arguments must be an object".to_string())?;
24        let task_description = object
25            .get("task_description")
26            .or_else(|| object.get("task"))
27            .or_else(|| object.get("input"))
28            .and_then(Value::as_str)
29            .map(str::trim)
30            .filter(|value| !value.is_empty())
31            .ok_or_else(|| "agent tool requires task_description".to_string())?;
32        let mut request = SubTaskRequest::new(self.agent.name(), task_description);
33        request.output_requirements = object
34            .get("output_requirements")
35            .and_then(Value::as_str)
36            .unwrap_or_default()
37            .to_string();
38        request.include_main_summary = object
39            .get("include_main_summary")
40            .and_then(Value::as_bool)
41            .unwrap_or(false);
42        Ok(request)
43    }
44}
45
46impl Tool for AgentTool {
47    fn name(&self) -> &str {
48        &self.name
49    }
50
51    fn description(&self) -> &str {
52        &self.description
53    }
54
55    fn parameters_schema(&self) -> &Value {
56        &self.parameters_schema
57    }
58
59    fn as_tool_spec(&self) -> ToolSpec {
60        let agent_tool = self.clone();
61        let mut spec = ToolSpec::new(
62            self.name.clone(),
63            self.description.clone(),
64            Arc::new(
65                move |context: &mut ToolContext, arguments: &ToolArguments| {
66                    let raw_arguments = Value::Object(arguments.clone().into_iter().collect());
67                    let request = match agent_tool.request_from_arguments(raw_arguments) {
68                        Ok(request) => request,
69                        Err(error) => return ToolOutput::error(error).to_result(""),
70                    };
71                    if let Some(provider) = context.model_provider.clone() {
72                        let runner = Runner::builder()
73                            .model_provider_arc(provider)
74                            .workspace(context.workspace.clone())
75                            .build();
76                        let runner = match runner {
77                            Ok(runner) => runner,
78                            Err(error) => return ToolOutput::error(error).to_result(""),
79                        };
80                        let result = runner.run_blocking(
81                            &agent_tool.agent,
82                            crate::runner::NormalizedInput::from(request.task_description.clone()),
83                            RunConfig::builder()
84                                .workspace_backend(context.workspace_backend.clone())
85                                .build(),
86                            None,
87                        );
88                        return match result {
89                            Ok(result) => {
90                                let output = result.final_output().unwrap_or_default();
91                                ToolOutput::text(output).to_result("")
92                            }
93                            Err(error) => ToolOutput::error(error).to_result(""),
94                        };
95                    }
96                    let Some(runner) = context.sub_task_runner.clone() else {
97                        return ToolOutput::error("sub-agent runtime is not available")
98                            .with_code("sub_agents_not_enabled")
99                            .to_result("");
100                    };
101                    let outcome = runner(request);
102                    ToolOutput::json(outcome.to_value()).to_result("")
103                },
104            ),
105        );
106        spec.schema = json!({
107            "type": "function",
108            "function": {
109                "name": self.name,
110                "description": self.description,
111                "parameters": self.parameters_schema,
112            }
113        });
114        spec
115    }
116}
117
118pub struct AgentToolBuilder {
119    agent: Agent,
120    name: Option<String>,
121    description: Option<String>,
122}
123
124impl AgentToolBuilder {
125    pub fn new(agent: Agent) -> Self {
126        Self {
127            agent,
128            name: None,
129            description: None,
130        }
131    }
132
133    pub fn name(mut self, name: impl Into<String>) -> Self {
134        self.name = Some(name.into());
135        self
136    }
137
138    pub fn description(mut self, description: impl Into<String>) -> Self {
139        self.description = Some(description.into());
140        self
141    }
142
143    pub fn build(self) -> Result<AgentTool, String> {
144        let name = self.name.unwrap_or_else(|| self.agent.name().to_string());
145        if name.trim().is_empty() {
146            return Err("agent tool name cannot be empty".to_string());
147        }
148        let description = self
149            .description
150            .unwrap_or_else(|| format!("Run the {} agent as a delegated task.", self.agent.name()));
151        Ok(AgentTool {
152            agent: self.agent,
153            name,
154            description,
155            parameters_schema: json!({
156                "type": "object",
157                "properties": {
158                    "task_description": {
159                        "type": "string",
160                        "description": "Task for the delegated agent."
161                    },
162                    "output_requirements": {
163                        "type": "string",
164                        "description": "Optional output requirements for the delegated agent."
165                    },
166                    "include_main_summary": {
167                        "type": "boolean",
168                        "description": "Whether to include parent task summary."
169                    }
170                },
171                "required": ["task_description"]
172            }),
173        })
174    }
175}