1pub mod agents;
4pub mod attachments;
5pub mod claiming;
6pub mod deps;
7pub mod files;
8pub mod query;
9pub mod schema;
10pub mod search;
11pub mod skills;
12pub mod tasks;
13pub mod tracking;
14
15use crate::config::{
16 AttachmentsConfig, AutoAdvanceConfig, DependenciesConfig, Prompts, ServerPaths, StatesConfig,
17};
18use crate::db::Database;
19use crate::error::ToolError;
20use crate::format::{OutputFormat, ToolResult};
21use anyhow::Result;
22use rmcp::model::Tool;
23use serde_json::Value;
24use std::path::PathBuf;
25use std::sync::Arc;
26
27pub struct ToolHandler {
29 pub db: Arc<Database>,
30 pub media_dir: PathBuf,
31 pub skills_dir: PathBuf,
32 pub server_paths: Arc<ServerPaths>,
33 pub prompts: Arc<Prompts>,
34 pub states_config: Arc<StatesConfig>,
35 pub deps_config: Arc<DependenciesConfig>,
36 pub auto_advance: Arc<AutoAdvanceConfig>,
37 pub attachments_config: Arc<AttachmentsConfig>,
38 pub default_format: OutputFormat,
39}
40
41impl ToolHandler {
42 #[allow(clippy::too_many_arguments)]
43 pub fn new(
44 db: Arc<Database>,
45 media_dir: PathBuf,
46 skills_dir: PathBuf,
47 server_paths: Arc<ServerPaths>,
48 prompts: Arc<Prompts>,
49 states_config: Arc<StatesConfig>,
50 deps_config: Arc<DependenciesConfig>,
51 auto_advance: Arc<AutoAdvanceConfig>,
52 attachments_config: Arc<AttachmentsConfig>,
53 default_format: OutputFormat,
54 ) -> Self {
55 Self {
56 db,
57 media_dir,
58 skills_dir,
59 server_paths,
60 prompts,
61 states_config,
62 deps_config,
63 auto_advance,
64 attachments_config,
65 default_format,
66 }
67 }
68
69 pub fn get_tools(&self) -> Vec<Tool> {
71 let mut tools = Vec::new();
72
73 tools.extend(agents::get_tools(&self.prompts));
75
76 tools.extend(tasks::get_tools(&self.prompts, &self.states_config));
78
79 tools.extend(tracking::get_tools(&self.prompts, &self.states_config));
81
82 tools.extend(deps::get_tools(&self.prompts, &self.deps_config));
84
85 tools.extend(claiming::get_tools(&self.prompts, &self.states_config));
87
88 tools.extend(files::get_tools(&self.prompts));
90
91 tools.extend(attachments::get_tools(&self.prompts));
93
94 tools.extend(skills::get_tools());
96
97 tools.extend(schema::get_tools());
99
100 tools.extend(search::get_tools(&self.prompts));
102
103 tools.extend(query::get_tools());
105
106 tools
107 }
108
109 pub async fn call_tool(&self, name: &str, arguments: Value) -> Result<ToolResult> {
112 let json = |r: Result<Value>| r.map(ToolResult::Json);
114
115 match name {
116 "connect" => json(agents::connect(&self.db, &self.server_paths, arguments)),
118 "disconnect" => json(agents::disconnect(&self.db, &self.states_config, arguments)),
119 "list_agents" => agents::list_agents(
120 &self.db,
121 &self.states_config,
122 self.default_format,
123 arguments,
124 ),
125 "cleanup_stale" => json(agents::cleanup_stale(
126 &self.db,
127 &self.states_config,
128 arguments,
129 )),
130
131 "create" => json(tasks::create(&self.db, &self.states_config, arguments)),
133 "create_tree" => json(tasks::create_tree(&self.db, &self.states_config, arguments)),
134 "get" => json(tasks::get(&self.db, self.default_format, arguments)),
135 "list_tasks" => json(tasks::list_tasks(
136 &self.db,
137 &self.states_config,
138 &self.deps_config,
139 self.default_format,
140 arguments,
141 )),
142 "update" => json(tasks::update(
143 &self.db,
144 &self.attachments_config,
145 &self.states_config,
146 &self.deps_config,
147 &self.auto_advance,
148 arguments,
149 )),
150 "delete" => json(tasks::delete(&self.db, arguments)),
151 "scan" => json(tasks::scan(&self.db, self.default_format, arguments)),
152
153 "thinking" => json(tracking::thinking(&self.db, arguments)),
155 "task_history" => json(tracking::task_history(
156 &self.db,
157 &self.states_config,
158 self.default_format,
159 arguments,
160 )),
161 "log_metrics" => json(tracking::log_metrics(&self.db, arguments)),
162 "get_metrics" => json(tracking::get_metrics(&self.db, arguments)),
163 "project_history" => json(tracking::project_history(
164 &self.db,
165 self.default_format,
166 arguments,
167 )),
168
169 "link" => json(deps::link(&self.db, &self.deps_config, arguments)),
171 "unlink" => json(deps::unlink(&self.db, arguments)),
172 "relink" => json(deps::relink(&self.db, &self.deps_config, arguments)),
173
174 "claim" => json(claiming::claim(
176 &self.db,
177 &self.states_config,
178 &self.deps_config,
179 &self.auto_advance,
180 arguments,
181 )),
182
183 "mark_file" => json(files::mark_file(&self.db, arguments)),
185 "unmark_file" => json(files::unmark_file(&self.db, arguments)),
186 "list_marks" => json(files::list_marks(&self.db, self.default_format, arguments)),
187 "mark_updates" => {
188 json(files::mark_updates_async(std::sync::Arc::clone(&self.db), arguments).await)
189 }
190
191 "attach" => json(attachments::attach(
193 &self.db,
194 &self.media_dir,
195 &self.attachments_config,
196 arguments,
197 )),
198 "attachments" => json(attachments::attachments(
199 &self.db,
200 &self.media_dir,
201 self.default_format,
202 arguments,
203 )),
204 "detach" => json(attachments::detach(&self.db, &self.media_dir, arguments)),
205
206 name if skills::is_skill_tool(name) => {
208 json(skills::call_tool(&self.skills_dir, name, &arguments))
209 }
210
211 "get_schema" => json(schema::get_schema(&self.db, arguments)),
213
214 "search" => json(search::search(&self.db, arguments)),
216
217 "query" => query::query(&self.db, self.default_format, arguments),
219
220 _ => Err(ToolError::unknown_tool(name).into()),
221 }
222 }
223}
224
225pub fn make_tool(name: &str, description: &str, properties: Value, required: Vec<&str>) -> Tool {
227 let input_schema = rmcp::model::JsonObject::from_iter([
228 ("type".to_string(), serde_json::json!("object")),
229 ("properties".to_string(), properties),
230 ("required".to_string(), serde_json::json!(required)),
231 ]);
232
233 Tool::new(name.to_string(), description.to_string(), input_schema)
234}
235
236pub fn make_tool_with_prompts(
239 name: &str,
240 default_description: &str,
241 properties: Value,
242 required: Vec<&str>,
243 prompts: &Prompts,
244) -> Tool {
245 let description = prompts
246 .get_tool_description(name)
247 .unwrap_or(default_description);
248 make_tool(name, description, properties, required)
249}
250
251pub fn get_string(args: &Value, key: &str) -> Option<String> {
253 args.get(key).and_then(|v| v.as_str().map(String::from))
254}
255
256pub fn get_i32(args: &Value, key: &str) -> Option<i32> {
258 args.get(key).and_then(|v| v.as_i64().map(|n| n as i32))
259}
260
261pub fn get_i64(args: &Value, key: &str) -> Option<i64> {
263 args.get(key).and_then(|v| v.as_i64())
264}
265
266pub fn get_f64(args: &Value, key: &str) -> Option<f64> {
268 args.get(key).and_then(|v| v.as_f64())
269}
270
271pub fn get_bool(args: &Value, key: &str) -> Option<bool> {
273 args.get(key).and_then(|v| v.as_bool())
274}
275
276pub fn get_string_array(args: &Value, key: &str) -> Option<Vec<String>> {
278 args.get(key).and_then(|v| {
279 v.as_array().map(|arr| {
280 arr.iter()
281 .filter_map(|v| v.as_str().map(String::from))
282 .collect()
283 })
284 })
285}
286
287pub fn get_string_or_array(args: &Value, key: &str) -> Option<Vec<String>> {
290 args.get(key).and_then(|v| {
291 if let Some(s) = v.as_str() {
292 Some(vec![s.to_string()])
294 } else { v.as_array().map(|arr| arr.iter()
295 .filter_map(|item| item.as_str().map(String::from))
296 .collect()) }
297 })
298}