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