1use crate::tool_registry::{ToolInvoker, ToolMetadata};
7use serde_json::json;
8use tracing::{debug, info};
9
10pub struct WebfetchToolInvoker;
14
15#[async_trait::async_trait]
16impl ToolInvoker for WebfetchToolInvoker {
17 async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
18 debug!("Invoking webfetch tool");
19
20 let url = input
22 .get("url")
23 .and_then(|v| v.as_str())
24 .ok_or_else(|| "Missing 'url' field in input".to_string())?;
25
26 info!(url = %url, "Fetching web content");
27
28 Ok(json!({
31 "success": true,
32 "content": "Web content would be fetched here",
33 "url": url,
34 "metadata": {
35 "provider": "builtin",
36 "truncated": false,
37 "size": 0
38 }
39 }))
40 }
41
42 fn metadata(&self) -> ToolMetadata {
43 ToolMetadata {
44 id: "webfetch".to_string(),
45 name: "Webfetch".to_string(),
46 description: "Fetch and process web content from URLs".to_string(),
47 input_schema: json!({
48 "type": "object",
49 "properties": {
50 "url": {
51 "type": "string",
52 "description": "URL to fetch"
53 },
54 "max_size": {
55 "type": "integer",
56 "description": "Maximum content size in bytes (optional)"
57 }
58 },
59 "required": ["url"]
60 }),
61 output_schema: json!({
62 "type": "object",
63 "properties": {
64 "success": { "type": "boolean" },
65 "content": { "type": "string" },
66 "url": { "type": "string" },
67 "metadata": {
68 "type": "object",
69 "properties": {
70 "provider": { "type": "string" },
71 "truncated": { "type": "boolean" },
72 "size": { "type": "integer" }
73 }
74 }
75 }
76 }),
77 available: true,
78 }
79 }
80}
81
82pub struct PatchToolInvoker;
86
87#[async_trait::async_trait]
88impl ToolInvoker for PatchToolInvoker {
89 async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
90 debug!("Invoking patch tool");
91
92 let file_path = input
94 .get("file_path")
95 .and_then(|v| v.as_str())
96 .ok_or_else(|| "Missing 'file_path' field in input".to_string())?;
97
98 let _patch_content = input
99 .get("patch_content")
100 .and_then(|v| v.as_str())
101 .ok_or_else(|| "Missing 'patch_content' field in input".to_string())?;
102
103 info!(file_path = %file_path, "Applying patch");
104
105 Ok(json!({
108 "success": true,
109 "applied_hunks": 0,
110 "failed_hunks": 0,
111 "file_path": file_path,
112 "metadata": {
113 "provider": "builtin"
114 }
115 }))
116 }
117
118 fn metadata(&self) -> ToolMetadata {
119 ToolMetadata {
120 id: "patch".to_string(),
121 name: "Patch".to_string(),
122 description: "Apply unified diff patches to files safely".to_string(),
123 input_schema: json!({
124 "type": "object",
125 "properties": {
126 "file_path": {
127 "type": "string",
128 "description": "Path to the file to patch"
129 },
130 "patch_content": {
131 "type": "string",
132 "description": "Unified diff patch content"
133 }
134 },
135 "required": ["file_path", "patch_content"]
136 }),
137 output_schema: json!({
138 "type": "object",
139 "properties": {
140 "success": { "type": "boolean" },
141 "applied_hunks": { "type": "integer" },
142 "failed_hunks": { "type": "integer" },
143 "file_path": { "type": "string" },
144 "metadata": {
145 "type": "object",
146 "properties": {
147 "provider": { "type": "string" }
148 }
149 }
150 }
151 }),
152 available: true,
153 }
154 }
155}
156
157pub struct TodowriteToolInvoker;
161
162#[async_trait::async_trait]
163impl ToolInvoker for TodowriteToolInvoker {
164 async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
165 debug!("Invoking todowrite tool");
166
167 let todos = input
169 .get("todos")
170 .ok_or_else(|| "Missing 'todos' field in input".to_string())?;
171
172 info!(todo_count = todos.as_array().map(|a| a.len()).unwrap_or(0), "Writing todos");
173
174 Ok(json!({
177 "success": true,
178 "created": 0,
179 "updated": 0,
180 "metadata": {
181 "provider": "builtin"
182 }
183 }))
184 }
185
186 fn metadata(&self) -> ToolMetadata {
187 ToolMetadata {
188 id: "todowrite".to_string(),
189 name: "Todowrite".to_string(),
190 description: "Create or update todos in the task list".to_string(),
191 input_schema: json!({
192 "type": "object",
193 "properties": {
194 "todos": {
195 "type": "array",
196 "description": "List of todos to create or update",
197 "items": {
198 "type": "object",
199 "properties": {
200 "id": { "type": "string" },
201 "title": { "type": "string" },
202 "description": { "type": "string" },
203 "status": { "type": "string" },
204 "priority": { "type": "string" }
205 }
206 }
207 }
208 },
209 "required": ["todos"]
210 }),
211 output_schema: json!({
212 "type": "object",
213 "properties": {
214 "success": { "type": "boolean" },
215 "created": { "type": "integer" },
216 "updated": { "type": "integer" },
217 "metadata": {
218 "type": "object",
219 "properties": {
220 "provider": { "type": "string" }
221 }
222 }
223 }
224 }),
225 available: true,
226 }
227 }
228}
229
230pub struct TodoreadToolInvoker;
234
235#[async_trait::async_trait]
236impl ToolInvoker for TodoreadToolInvoker {
237 async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
238 debug!("Invoking todoread tool");
239
240 let status_filter = input.get("status").and_then(|v| v.as_str());
242 let priority_filter = input.get("priority").and_then(|v| v.as_str());
243
244 info!(
245 status_filter = ?status_filter,
246 priority_filter = ?priority_filter,
247 "Reading todos"
248 );
249
250 Ok(json!({
253 "success": true,
254 "todos": [],
255 "metadata": {
256 "provider": "builtin"
257 }
258 }))
259 }
260
261 fn metadata(&self) -> ToolMetadata {
262 ToolMetadata {
263 id: "todoread".to_string(),
264 name: "Todoread".to_string(),
265 description: "Read todos from the task list with optional filtering".to_string(),
266 input_schema: json!({
267 "type": "object",
268 "properties": {
269 "status": {
270 "type": "string",
271 "description": "Filter by status (optional)"
272 },
273 "priority": {
274 "type": "string",
275 "description": "Filter by priority (optional)"
276 }
277 }
278 }),
279 output_schema: json!({
280 "type": "object",
281 "properties": {
282 "success": { "type": "boolean" },
283 "todos": {
284 "type": "array",
285 "items": {
286 "type": "object",
287 "properties": {
288 "id": { "type": "string" },
289 "title": { "type": "string" },
290 "description": { "type": "string" },
291 "status": { "type": "string" },
292 "priority": { "type": "string" }
293 }
294 }
295 },
296 "metadata": {
297 "type": "object",
298 "properties": {
299 "provider": { "type": "string" }
300 }
301 }
302 }
303 }),
304 available: true,
305 }
306 }
307}
308
309pub struct WebsearchToolInvoker;
313
314#[async_trait::async_trait]
315impl ToolInvoker for WebsearchToolInvoker {
316 async fn invoke(&self, input: serde_json::Value) -> Result<serde_json::Value, String> {
317 debug!("Invoking websearch tool");
318
319 let query = input
321 .get("query")
322 .and_then(|v| v.as_str())
323 .ok_or_else(|| "Missing 'query' field in input".to_string())?;
324
325 let limit = input
326 .get("limit")
327 .and_then(|v| v.as_u64())
328 .unwrap_or(10);
329
330 let offset = input
331 .get("offset")
332 .and_then(|v| v.as_u64())
333 .unwrap_or(0);
334
335 info!(query = %query, limit = %limit, offset = %offset, "Searching web");
336
337 Ok(json!({
340 "success": true,
341 "results": [],
342 "total_count": 0,
343 "query": query,
344 "metadata": {
345 "provider": "builtin",
346 "limit": limit,
347 "offset": offset
348 }
349 }))
350 }
351
352 fn metadata(&self) -> ToolMetadata {
353 ToolMetadata {
354 id: "websearch".to_string(),
355 name: "Websearch".to_string(),
356 description: "Search the web and return ranked results".to_string(),
357 input_schema: json!({
358 "type": "object",
359 "properties": {
360 "query": {
361 "type": "string",
362 "description": "Search query"
363 },
364 "limit": {
365 "type": "integer",
366 "description": "Maximum number of results (optional, default: 10)"
367 },
368 "offset": {
369 "type": "integer",
370 "description": "Result offset for pagination (optional, default: 0)"
371 }
372 },
373 "required": ["query"]
374 }),
375 output_schema: json!({
376 "type": "object",
377 "properties": {
378 "success": { "type": "boolean" },
379 "results": {
380 "type": "array",
381 "items": {
382 "type": "object",
383 "properties": {
384 "title": { "type": "string" },
385 "url": { "type": "string" },
386 "snippet": { "type": "string" },
387 "rank": { "type": "integer" }
388 }
389 }
390 },
391 "total_count": { "type": "integer" },
392 "query": { "type": "string" },
393 "metadata": {
394 "type": "object",
395 "properties": {
396 "provider": { "type": "string" },
397 "limit": { "type": "integer" },
398 "offset": { "type": "integer" }
399 }
400 }
401 }
402 }),
403 available: true,
404 }
405 }
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[tokio::test]
413 async fn test_webfetch_invoker() {
414 let invoker = WebfetchToolInvoker;
415 let input = json!({
416 "url": "https://example.com"
417 });
418
419 let result = invoker.invoke(input).await;
420 assert!(result.is_ok());
421
422 let output = result.unwrap();
423 assert_eq!(output["success"], true);
424 assert_eq!(output["url"], "https://example.com");
425 }
426
427 #[tokio::test]
428 async fn test_webfetch_missing_url() {
429 let invoker = WebfetchToolInvoker;
430 let input = json!({});
431
432 let result = invoker.invoke(input).await;
433 assert!(result.is_err());
434 }
435
436 #[test]
437 fn test_webfetch_metadata() {
438 let invoker = WebfetchToolInvoker;
439 let metadata = invoker.metadata();
440
441 assert_eq!(metadata.id, "webfetch");
442 assert_eq!(metadata.name, "Webfetch");
443 assert!(metadata.available);
444 }
445
446 #[tokio::test]
447 async fn test_patch_invoker() {
448 let invoker = PatchToolInvoker;
449 let input = json!({
450 "file_path": "src/main.rs",
451 "patch_content": "--- a/src/main.rs\n+++ b/src/main.rs\n"
452 });
453
454 let result = invoker.invoke(input).await;
455 assert!(result.is_ok());
456
457 let output = result.unwrap();
458 assert_eq!(output["success"], true);
459 }
460
461 #[tokio::test]
462 async fn test_patch_missing_fields() {
463 let invoker = PatchToolInvoker;
464 let input = json!({
465 "file_path": "src/main.rs"
466 });
467
468 let result = invoker.invoke(input).await;
469 assert!(result.is_err());
470 }
471
472 #[test]
473 fn test_patch_metadata() {
474 let invoker = PatchToolInvoker;
475 let metadata = invoker.metadata();
476
477 assert_eq!(metadata.id, "patch");
478 assert_eq!(metadata.name, "Patch");
479 assert!(metadata.available);
480 }
481
482 #[tokio::test]
483 async fn test_todowrite_invoker() {
484 let invoker = TodowriteToolInvoker;
485 let input = json!({
486 "todos": [
487 {
488 "id": "1",
489 "title": "Task 1",
490 "description": "Description",
491 "status": "pending",
492 "priority": "high"
493 }
494 ]
495 });
496
497 let result = invoker.invoke(input).await;
498 assert!(result.is_ok());
499
500 let output = result.unwrap();
501 assert_eq!(output["success"], true);
502 }
503
504 #[tokio::test]
505 async fn test_todowrite_missing_todos() {
506 let invoker = TodowriteToolInvoker;
507 let input = json!({});
508
509 let result = invoker.invoke(input).await;
510 assert!(result.is_err());
511 }
512
513 #[test]
514 fn test_todowrite_metadata() {
515 let invoker = TodowriteToolInvoker;
516 let metadata = invoker.metadata();
517
518 assert_eq!(metadata.id, "todowrite");
519 assert_eq!(metadata.name, "Todowrite");
520 assert!(metadata.available);
521 }
522
523 #[tokio::test]
524 async fn test_todoread_invoker() {
525 let invoker = TodoreadToolInvoker;
526 let input = json!({
527 "status": "pending"
528 });
529
530 let result = invoker.invoke(input).await;
531 assert!(result.is_ok());
532
533 let output = result.unwrap();
534 assert_eq!(output["success"], true);
535 }
536
537 #[tokio::test]
538 async fn test_todoread_no_filters() {
539 let invoker = TodoreadToolInvoker;
540 let input = json!({});
541
542 let result = invoker.invoke(input).await;
543 assert!(result.is_ok());
544 }
545
546 #[test]
547 fn test_todoread_metadata() {
548 let invoker = TodoreadToolInvoker;
549 let metadata = invoker.metadata();
550
551 assert_eq!(metadata.id, "todoread");
552 assert_eq!(metadata.name, "Todoread");
553 assert!(metadata.available);
554 }
555
556 #[tokio::test]
557 async fn test_websearch_invoker() {
558 let invoker = WebsearchToolInvoker;
559 let input = json!({
560 "query": "rust programming",
561 "limit": 10
562 });
563
564 let result = invoker.invoke(input).await;
565 assert!(result.is_ok());
566
567 let output = result.unwrap();
568 assert_eq!(output["success"], true);
569 assert_eq!(output["query"], "rust programming");
570 }
571
572 #[tokio::test]
573 async fn test_websearch_missing_query() {
574 let invoker = WebsearchToolInvoker;
575 let input = json!({});
576
577 let result = invoker.invoke(input).await;
578 assert!(result.is_err());
579 }
580
581 #[test]
582 fn test_websearch_metadata() {
583 let invoker = WebsearchToolInvoker;
584 let metadata = invoker.metadata();
585
586 assert_eq!(metadata.id, "websearch");
587 assert_eq!(metadata.name, "Websearch");
588 assert!(metadata.available);
589 }
590}