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::{AttachmentsConfig, AutoAdvanceConfig, DependenciesConfig, Prompts, ServerPaths, StatesConfig};
16use crate::db::Database;
17use crate::error::ToolError;
18use crate::format::{OutputFormat, ToolResult};
19use anyhow::Result;
20use rmcp::model::Tool;
21use serde_json::Value;
22use std::path::PathBuf;
23use std::sync::Arc;
24
25pub struct ToolHandler {
27 pub db: Arc<Database>,
28 pub media_dir: PathBuf,
29 pub skills_dir: PathBuf,
30 pub server_paths: Arc<ServerPaths>,
31 pub prompts: Arc<Prompts>,
32 pub states_config: Arc<StatesConfig>,
33 pub deps_config: Arc<DependenciesConfig>,
34 pub auto_advance: Arc<AutoAdvanceConfig>,
35 pub attachments_config: Arc<AttachmentsConfig>,
36 pub default_format: OutputFormat,
37}
38
39impl ToolHandler {
40 pub fn new(
41 db: Arc<Database>,
42 media_dir: PathBuf,
43 skills_dir: PathBuf,
44 server_paths: Arc<ServerPaths>,
45 prompts: Arc<Prompts>,
46 states_config: Arc<StatesConfig>,
47 deps_config: Arc<DependenciesConfig>,
48 auto_advance: Arc<AutoAdvanceConfig>,
49 attachments_config: Arc<AttachmentsConfig>,
50 default_format: OutputFormat,
51 ) -> Self {
52 Self {
53 db,
54 media_dir,
55 skills_dir,
56 server_paths,
57 prompts,
58 states_config,
59 deps_config,
60 auto_advance,
61 attachments_config,
62 default_format,
63 }
64 }
65
66 pub fn get_tools(&self) -> Vec<Tool> {
68 let mut tools = Vec::new();
69
70 tools.extend(agents::get_tools(&self.prompts));
72
73 tools.extend(tasks::get_tools(&self.prompts, &self.states_config));
75
76 tools.extend(tracking::get_tools(&self.prompts, &self.states_config));
78
79 tools.extend(deps::get_tools(&self.prompts, &self.deps_config));
81
82 tools.extend(claiming::get_tools(&self.prompts, &self.states_config));
84
85 tools.extend(files::get_tools(&self.prompts));
87
88 tools.extend(attachments::get_tools(&self.prompts));
90
91 tools.extend(skills::get_tools());
93
94 tools.extend(schema::get_tools());
96
97 tools.extend(search::get_tools(&self.prompts));
99
100 tools.extend(query::get_tools());
102
103 tools
104 }
105
106 pub async fn call_tool(&self, name: &str, arguments: Value) -> Result<ToolResult> {
109 let json = |r: Result<Value>| r.map(ToolResult::Json);
111
112 match name {
113 "connect" => json(agents::connect(&self.db, &self.server_paths, arguments)),
115 "disconnect" => json(agents::disconnect(&self.db, &self.states_config, arguments)),
116 "list_agents" => agents::list_agents(&self.db, &self.states_config, self.default_format, arguments),
117 "cleanup_stale" => json(agents::cleanup_stale(&self.db, &self.states_config, arguments)),
118
119 "create" => json(tasks::create(&self.db, &self.states_config, arguments)),
121 "create_tree" => json(tasks::create_tree(&self.db, &self.states_config, arguments)),
122 "get" => json(tasks::get(&self.db, self.default_format, arguments)),
123 "list_tasks" => {
124 json(tasks::list_tasks(&self.db, &self.states_config, &self.deps_config, self.default_format, arguments))
125 }
126 "update" => json(tasks::update(&self.db, &self.attachments_config, &self.states_config, &self.deps_config, &self.auto_advance, arguments)),
127 "delete" => json(tasks::delete(&self.db, arguments)),
128 "scan" => json(tasks::scan(&self.db, self.default_format, arguments)),
129
130 "thinking" => json(tracking::thinking(&self.db, arguments)),
132 "task_history" => {
133 json(tracking::task_history(&self.db, &self.states_config, self.default_format, arguments))
134 }
135 "log_metrics" => json(tracking::log_metrics(&self.db, arguments)),
136 "get_metrics" => json(tracking::get_metrics(&self.db, arguments)),
137 "project_history" => {
138 json(tracking::project_history(&self.db, self.default_format, arguments))
139 }
140
141 "link" => json(deps::link(&self.db, &self.deps_config, arguments)),
143 "unlink" => json(deps::unlink(&self.db, arguments)),
144 "relink" => json(deps::relink(&self.db, &self.deps_config, arguments)),
145
146 "claim" => json(claiming::claim(&self.db, &self.states_config, &self.deps_config, &self.auto_advance, arguments)),
148
149 "mark_file" => json(files::mark_file(&self.db, arguments)),
151 "unmark_file" => json(files::unmark_file(&self.db, arguments)),
152 "list_marks" => json(files::list_marks(&self.db, self.default_format, arguments)),
153 "mark_updates" => json(files::mark_updates_async(std::sync::Arc::clone(&self.db), arguments).await),
154
155 "attach" => json(attachments::attach(&self.db, &self.media_dir, &self.attachments_config, arguments)),
157 "attachments" => json(attachments::attachments(&self.db, &self.media_dir, self.default_format, arguments)),
158 "detach" => json(attachments::detach(&self.db, &self.media_dir, arguments)),
159
160 name if skills::is_skill_tool(name) => {
162 json(skills::call_tool(&self.skills_dir, name, &arguments))
163 }
164
165 "get_schema" => json(schema::get_schema(&self.db, arguments)),
167
168 "search" => json(search::search(&self.db, arguments)),
170
171 "query" => query::query(&self.db, self.default_format, arguments),
173
174 _ => Err(ToolError::unknown_tool(name).into()),
175 }
176 }
177}
178
179pub fn make_tool(name: &str, description: &str, properties: Value, required: Vec<&str>) -> Tool {
181 let input_schema = rmcp::model::JsonObject::from_iter([
182 ("type".to_string(), serde_json::json!("object")),
183 ("properties".to_string(), properties),
184 (
185 "required".to_string(),
186 serde_json::json!(required),
187 ),
188 ]);
189
190 Tool::new(name.to_string(), description.to_string(), input_schema)
191}
192
193pub fn make_tool_with_prompts(
196 name: &str,
197 default_description: &str,
198 properties: Value,
199 required: Vec<&str>,
200 prompts: &Prompts,
201) -> Tool {
202 let description = prompts
203 .get_tool_description(name)
204 .unwrap_or(default_description);
205 make_tool(name, description, properties, required)
206}
207
208pub fn get_string(args: &Value, key: &str) -> Option<String> {
210 args.get(key).and_then(|v| v.as_str().map(String::from))
211}
212
213pub fn get_i32(args: &Value, key: &str) -> Option<i32> {
215 args.get(key).and_then(|v| v.as_i64().map(|n| n as i32))
216}
217
218pub fn get_i64(args: &Value, key: &str) -> Option<i64> {
220 args.get(key).and_then(|v| v.as_i64())
221}
222
223pub fn get_f64(args: &Value, key: &str) -> Option<f64> {
225 args.get(key).and_then(|v| v.as_f64())
226}
227
228pub fn get_bool(args: &Value, key: &str) -> Option<bool> {
230 args.get(key).and_then(|v| v.as_bool())
231}
232
233pub fn get_string_array(args: &Value, key: &str) -> Option<Vec<String>> {
235 args.get(key).and_then(|v| {
236 v.as_array().map(|arr| {
237 arr.iter()
238 .filter_map(|v| v.as_str().map(String::from))
239 .collect()
240 })
241 })
242}
243
244pub fn get_string_or_array(args: &Value, key: &str) -> Option<Vec<String>> {
247 args.get(key).and_then(|v| {
248 if let Some(s) = v.as_str() {
249 Some(vec![s.to_string()])
251 } else if let Some(arr) = v.as_array() {
252 Some(
254 arr.iter()
255 .filter_map(|item| item.as_str().map(String::from))
256 .collect(),
257 )
258 } else {
259 None
260 }
261 })
262}