1#![allow(dead_code, unused_imports)]
6
7use super::types::*;
8use serde::{Deserialize, Serialize};
9use serde_json::json;
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14struct McpWorkspaceInfo {
15 hash: String,
16 project_path: String,
17 session_count: usize,
18 has_chats: bool,
19}
20
21pub fn list_tools() -> Vec<Tool> {
23 vec![
24 Tool {
25 name: "csm_list_workspaces".to_string(),
26 description: Some("List all VS Code workspaces with chat sessions".to_string()),
27 input_schema: json!({
28 "type": "object",
29 "properties": {},
30 "required": []
31 }),
32 },
33 Tool {
34 name: "csm_find_workspace".to_string(),
35 description: Some(
36 "Find workspaces matching a pattern (project name or path)".to_string(),
37 ),
38 input_schema: json!({
39 "type": "object",
40 "properties": {
41 "pattern": {
42 "type": "string",
43 "description": "Search pattern to match workspace names or paths"
44 }
45 },
46 "required": ["pattern"]
47 }),
48 },
49 Tool {
50 name: "csm_list_sessions".to_string(),
51 description: Some(
52 "List all chat sessions, optionally filtered by project path".to_string(),
53 ),
54 input_schema: json!({
55 "type": "object",
56 "properties": {
57 "project_path": {
58 "type": "string",
59 "description": "Optional project path to filter sessions"
60 }
61 },
62 "required": []
63 }),
64 },
65 Tool {
66 name: "csm_list_orphaned".to_string(),
67 description: Some(
68 "List sessions on disk that are not in VS Code's index (invisible sessions)"
69 .to_string(),
70 ),
71 input_schema: json!({
72 "type": "object",
73 "properties": {
74 "path": {
75 "type": "string",
76 "description": "Project path (defaults to current directory)"
77 }
78 },
79 "required": []
80 }),
81 },
82 Tool {
83 name: "csm_register_all".to_string(),
84 description: Some(
85 "Register all sessions from a workspace into VS Code's index".to_string(),
86 ),
87 input_schema: json!({
88 "type": "object",
89 "properties": {
90 "path": {
91 "type": "string",
92 "description": "Project path (defaults to current directory)"
93 },
94 "merge": {
95 "type": "boolean",
96 "description": "Merge all sessions into one before registering"
97 },
98 "force": {
99 "type": "boolean",
100 "description": "Force registration even if VS Code is running"
101 }
102 },
103 "required": []
104 }),
105 },
106 Tool {
107 name: "csm_register_sessions".to_string(),
108 description: Some("Register specific sessions by ID or title".to_string()),
109 input_schema: json!({
110 "type": "object",
111 "properties": {
112 "ids": {
113 "type": "array",
114 "items": { "type": "string" },
115 "description": "Session IDs to register"
116 },
117 "titles": {
118 "type": "array",
119 "items": { "type": "string" },
120 "description": "Session titles to match (partial match)"
121 },
122 "path": {
123 "type": "string",
124 "description": "Project path (defaults to current directory)"
125 },
126 "force": {
127 "type": "boolean",
128 "description": "Force registration even if VS Code is running"
129 }
130 },
131 "required": []
132 }),
133 },
134 Tool {
135 name: "csm_show_session".to_string(),
136 description: Some("Show details of a specific chat session".to_string()),
137 input_schema: json!({
138 "type": "object",
139 "properties": {
140 "session_id": {
141 "type": "string",
142 "description": "Session ID (supports partial prefix match)"
143 }
144 },
145 "required": ["session_id"]
146 }),
147 },
148 Tool {
149 name: "csm_show_history".to_string(),
150 description: Some("Show chat history timeline for a project".to_string()),
151 input_schema: json!({
152 "type": "object",
153 "properties": {
154 "path": {
155 "type": "string",
156 "description": "Project path (defaults to current directory)"
157 }
158 },
159 "required": []
160 }),
161 },
162 Tool {
163 name: "csm_merge_sessions".to_string(),
164 description: Some("Merge multiple chat sessions into one unified history".to_string()),
165 input_schema: json!({
166 "type": "object",
167 "properties": {
168 "path": {
169 "type": "string",
170 "description": "Project path to merge sessions from"
171 },
172 "title": {
173 "type": "string",
174 "description": "Title for the merged session"
175 },
176 "force": {
177 "type": "boolean",
178 "description": "Force merge even if VS Code is running"
179 }
180 },
181 "required": []
182 }),
183 },
184 Tool {
185 name: "csm_search".to_string(),
186 description: Some("Full-text search across all harvested chat sessions".to_string()),
187 input_schema: json!({
188 "type": "object",
189 "properties": {
190 "query": {
191 "type": "string",
192 "description": "Search query"
193 },
194 "limit": {
195 "type": "integer",
196 "description": "Maximum number of results (default: 20)"
197 }
198 },
199 "required": ["query"]
200 }),
201 },
202 Tool {
203 name: "csm_detect".to_string(),
204 description: Some(
205 "Auto-detect workspace and available providers for a path".to_string(),
206 ),
207 input_schema: json!({
208 "type": "object",
209 "properties": {
210 "path": {
211 "type": "string",
212 "description": "Path to detect workspace for"
213 }
214 },
215 "required": []
216 }),
217 },
218 Tool {
220 name: "csm_db_list_workspaces".to_string(),
221 description: Some("List all workspaces from the CSM database (csm-web)".to_string()),
222 input_schema: json!({
223 "type": "object",
224 "properties": {},
225 "required": []
226 }),
227 },
228 Tool {
229 name: "csm_db_list_sessions".to_string(),
230 description: Some("List chat sessions from the CSM database (csm-web)".to_string()),
231 input_schema: json!({
232 "type": "object",
233 "properties": {
234 "workspace_id": {
235 "type": "string",
236 "description": "Filter by workspace ID"
237 },
238 "provider": {
239 "type": "string",
240 "description": "Filter by provider (e.g., 'copilot', 'ollama', 'chatgpt')"
241 },
242 "limit": {
243 "type": "integer",
244 "description": "Maximum results (default: 100)"
245 }
246 },
247 "required": []
248 }),
249 },
250 Tool {
251 name: "csm_db_get_session".to_string(),
252 description: Some(
253 "Get a specific session with all its messages from CSM database".to_string(),
254 ),
255 input_schema: json!({
256 "type": "object",
257 "properties": {
258 "session_id": {
259 "type": "string",
260 "description": "Session ID to retrieve"
261 }
262 },
263 "required": ["session_id"]
264 }),
265 },
266 Tool {
267 name: "csm_db_search".to_string(),
268 description: Some("Search sessions in CSM database by title".to_string()),
269 input_schema: json!({
270 "type": "object",
271 "properties": {
272 "query": {
273 "type": "string",
274 "description": "Search query for session titles"
275 },
276 "limit": {
277 "type": "integer",
278 "description": "Maximum results (default: 20)"
279 }
280 },
281 "required": ["query"]
282 }),
283 },
284 Tool {
285 name: "csm_db_stats".to_string(),
286 description: Some(
287 "Get statistics about the CSM database (session counts by provider)".to_string(),
288 ),
289 input_schema: json!({
290 "type": "object",
291 "properties": {},
292 "required": []
293 }),
294 },
295 ]
296}
297
298pub fn call_tool(name: &str, arguments: &HashMap<String, serde_json::Value>) -> CallToolResult {
300 let result = match name {
301 "csm_list_workspaces" => execute_list_workspaces(),
302 "csm_find_workspace" => {
303 let pattern = arguments
304 .get("pattern")
305 .and_then(|v| v.as_str())
306 .unwrap_or("");
307 execute_find_workspace(pattern)
308 }
309 "csm_list_sessions" => {
310 let project_path = arguments.get("project_path").and_then(|v| v.as_str());
311 execute_list_sessions(project_path)
312 }
313 "csm_list_orphaned" => {
314 let path = arguments.get("path").and_then(|v| v.as_str());
315 execute_list_orphaned(path)
316 }
317 "csm_register_all" => {
318 let path = arguments.get("path").and_then(|v| v.as_str());
319 let merge = arguments
320 .get("merge")
321 .and_then(|v| v.as_bool())
322 .unwrap_or(false);
323 let force = arguments
324 .get("force")
325 .and_then(|v| v.as_bool())
326 .unwrap_or(false);
327 execute_register_all(path, merge, force)
328 }
329 "csm_register_sessions" => {
330 let ids: Vec<String> = arguments
331 .get("ids")
332 .and_then(|v| v.as_array())
333 .map(|arr| {
334 arr.iter()
335 .filter_map(|v| v.as_str().map(|s| s.to_string()))
336 .collect()
337 })
338 .unwrap_or_default();
339 let titles: Option<Vec<String>> = arguments
340 .get("titles")
341 .and_then(|v| v.as_array())
342 .map(|arr| {
343 arr.iter()
344 .filter_map(|v| v.as_str().map(|s| s.to_string()))
345 .collect()
346 });
347 let path = arguments.get("path").and_then(|v| v.as_str());
348 let force = arguments
349 .get("force")
350 .and_then(|v| v.as_bool())
351 .unwrap_or(false);
352 execute_register_sessions(&ids, titles.as_deref(), path, force)
353 }
354 "csm_show_session" => {
355 let session_id = arguments
356 .get("session_id")
357 .and_then(|v| v.as_str())
358 .unwrap_or("");
359 execute_show_session(session_id)
360 }
361 "csm_show_history" => {
362 let path = arguments.get("path").and_then(|v| v.as_str());
363 execute_show_history(path)
364 }
365 "csm_merge_sessions" => {
366 let path = arguments.get("path").and_then(|v| v.as_str());
367 let title = arguments.get("title").and_then(|v| v.as_str());
368 let force = arguments
369 .get("force")
370 .and_then(|v| v.as_bool())
371 .unwrap_or(false);
372 execute_merge_sessions(path, title, force)
373 }
374 "csm_search" => {
375 let query = arguments
376 .get("query")
377 .and_then(|v| v.as_str())
378 .unwrap_or("");
379 let limit = arguments
380 .get("limit")
381 .and_then(|v| v.as_u64())
382 .map(|n| n as usize);
383 execute_search(query, limit)
384 }
385 "csm_detect" => {
386 let path = arguments.get("path").and_then(|v| v.as_str());
387 execute_detect(path)
388 }
389 "csm_db_list_workspaces" => execute_db_list_workspaces(),
391 "csm_db_list_sessions" => {
392 let workspace_id = arguments.get("workspace_id").and_then(|v| v.as_str());
393 let provider = arguments.get("provider").and_then(|v| v.as_str());
394 let limit = arguments
395 .get("limit")
396 .and_then(|v| v.as_u64())
397 .map(|n| n as usize)
398 .unwrap_or(100);
399 execute_db_list_sessions(workspace_id, provider, limit)
400 }
401 "csm_db_get_session" => {
402 let session_id = arguments
403 .get("session_id")
404 .and_then(|v| v.as_str())
405 .unwrap_or("");
406 execute_db_get_session(session_id)
407 }
408 "csm_db_search" => {
409 let query = arguments
410 .get("query")
411 .and_then(|v| v.as_str())
412 .unwrap_or("");
413 let limit = arguments
414 .get("limit")
415 .and_then(|v| v.as_u64())
416 .map(|n| n as usize)
417 .unwrap_or(20);
418 execute_db_search(query, limit)
419 }
420 "csm_db_stats" => execute_db_stats(),
421 _ => CallToolResult {
422 content: vec![ToolContent::Text {
423 text: format!("Unknown tool: {}", name),
424 }],
425 is_error: Some(true),
426 },
427 };
428
429 result
430}
431
432fn execute_list_workspaces() -> CallToolResult {
437 use crate::workspace::discover_workspaces;
438
439 match discover_workspaces() {
440 Ok(workspaces) => {
441 let infos: Vec<McpWorkspaceInfo> = workspaces
442 .iter()
443 .map(|ws| McpWorkspaceInfo {
444 hash: ws.hash.clone(),
445 project_path: ws.project_path.clone().unwrap_or_default(),
446 session_count: ws.chat_session_count,
447 has_chats: ws.has_chat_sessions,
448 })
449 .collect();
450
451 CallToolResult {
452 content: vec![ToolContent::Text {
453 text: serde_json::to_string_pretty(&json!({
454 "workspaces": infos,
455 "total": infos.len()
456 }))
457 .unwrap_or_default(),
458 }],
459 is_error: None,
460 }
461 }
462 Err(e) => CallToolResult {
463 content: vec![ToolContent::Text {
464 text: format!("Error listing workspaces: {}", e),
465 }],
466 is_error: Some(true),
467 },
468 }
469}
470
471fn execute_find_workspace(pattern: &str) -> CallToolResult {
472 use crate::workspace::{find_all_workspaces_for_project, get_chat_sessions_from_workspace};
473
474 match find_all_workspaces_for_project(pattern) {
475 Ok(workspaces) => {
476 let infos: Vec<serde_json::Value> = workspaces
477 .iter()
478 .map(|(hash, workspace_path, project_path, _last_modified)| {
479 let sessions = get_chat_sessions_from_workspace(workspace_path)
480 .unwrap_or_default()
481 .iter()
482 .map(|s| {
483 json!({
484 "id": s.session.session_id,
485 "title": s.session.title(),
486 "path": s.path.display().to_string()
487 })
488 })
489 .collect::<Vec<_>>();
490 json!({
491 "hash": hash,
492 "project_path": project_path,
493 "workspace_path": workspace_path.display().to_string(),
494 "session_count": sessions.len(),
495 "sessions": sessions
496 })
497 })
498 .collect();
499
500 CallToolResult {
501 content: vec![ToolContent::Text {
502 text: serde_json::to_string_pretty(&json!({
503 "pattern": pattern,
504 "workspaces": infos,
505 "total": infos.len()
506 }))
507 .unwrap_or_default(),
508 }],
509 is_error: None,
510 }
511 }
512 Err(e) => CallToolResult {
513 content: vec![ToolContent::Text {
514 text: format!("Error finding workspaces: {}", e),
515 }],
516 is_error: Some(true),
517 },
518 }
519}
520
521fn execute_list_sessions(project_path: Option<&str>) -> CallToolResult {
522 use crate::workspace::{discover_workspaces, get_chat_sessions_from_workspace};
523
524 match discover_workspaces() {
525 Ok(workspaces) => {
526 let mut all_sessions = Vec::new();
527
528 for ws in &workspaces {
529 if let Some(filter_path) = project_path {
531 if let Some(ref ws_path) = ws.project_path {
532 if !ws_path.to_lowercase().contains(&filter_path.to_lowercase()) {
533 continue;
534 }
535 } else {
536 continue;
537 }
538 }
539
540 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws.workspace_path) {
541 for s in sessions {
542 all_sessions.push(json!({
543 "id": s.session.session_id,
544 "title": s.session.title(),
545 "message_count": s.session.requests.len(),
546 "workspace_hash": ws.hash,
547 "project_path": ws.project_path,
548 "file_path": s.path.display().to_string()
549 }));
550 }
551 }
552 }
553
554 CallToolResult {
555 content: vec![ToolContent::Text {
556 text: serde_json::to_string_pretty(&json!({
557 "sessions": all_sessions,
558 "total": all_sessions.len()
559 }))
560 .unwrap_or_default(),
561 }],
562 is_error: None,
563 }
564 }
565 Err(e) => CallToolResult {
566 content: vec![ToolContent::Text {
567 text: format!("Error listing sessions: {}", e),
568 }],
569 is_error: Some(true),
570 },
571 }
572}
573
574fn execute_list_orphaned(path: Option<&str>) -> CallToolResult {
575 use crate::commands::list_orphaned;
576
577 match list_orphaned(path) {
580 Ok(_) => CallToolResult {
581 content: vec![ToolContent::Text {
582 text: json!({
583 "status": "success",
584 "message": "Orphaned session check completed. See console output for details."
585 })
586 .to_string(),
587 }],
588 is_error: None,
589 },
590 Err(e) => CallToolResult {
591 content: vec![ToolContent::Text {
592 text: format!("Error listing orphaned sessions: {}", e),
593 }],
594 is_error: Some(true),
595 },
596 }
597}
598
599fn execute_register_all(path: Option<&str>, merge: bool, force: bool) -> CallToolResult {
600 use crate::commands::register_all;
601
602 match register_all(path, merge, force) {
603 Ok(_) => CallToolResult {
604 content: vec![ToolContent::Text {
605 text: json!({
606 "status": "success",
607 "message": "Sessions registered successfully",
608 "merge": merge,
609 "force": force
610 })
611 .to_string(),
612 }],
613 is_error: None,
614 },
615 Err(e) => CallToolResult {
616 content: vec![ToolContent::Text {
617 text: format!("Error registering sessions: {}", e),
618 }],
619 is_error: Some(true),
620 },
621 }
622}
623
624fn execute_register_sessions(
625 ids: &[String],
626 titles: Option<&[String]>,
627 path: Option<&str>,
628 force: bool,
629) -> CallToolResult {
630 use crate::commands::register_sessions;
631
632 match register_sessions(ids, titles, path, force) {
633 Ok(_) => CallToolResult {
634 content: vec![ToolContent::Text {
635 text: json!({
636 "status": "success",
637 "message": "Sessions registered successfully",
638 "ids": ids,
639 "titles": titles,
640 "force": force
641 })
642 .to_string(),
643 }],
644 is_error: None,
645 },
646 Err(e) => CallToolResult {
647 content: vec![ToolContent::Text {
648 text: format!("Error registering sessions: {}", e),
649 }],
650 is_error: Some(true),
651 },
652 }
653}
654
655fn execute_show_session(session_id: &str) -> CallToolResult {
656 use crate::workspace::{discover_workspaces, get_chat_sessions_from_workspace};
657
658 match discover_workspaces() {
659 Ok(workspaces) => {
660 for ws in &workspaces {
661 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws.workspace_path) {
662 for s in sessions {
663 let sid = s.session.session_id.clone().unwrap_or_default();
664 if sid.starts_with(session_id) || sid == session_id {
665 return CallToolResult {
666 content: vec![ToolContent::Text {
667 text: serde_json::to_string_pretty(&json!({
668 "id": sid,
669 "title": s.session.title(),
670 "message_count": s.session.requests.len(),
671 "last_message_date": s.session.last_message_date,
672 "is_imported": s.session.is_imported,
673 "workspace_hash": ws.hash,
674 "project_path": ws.project_path,
675 "file_path": s.path.display().to_string(),
676 "messages": s.session.requests.iter().take(10).map(|r| {
677 let user_msg = r.message.as_ref()
678 .map(|m| m.get_text())
679 .unwrap_or_default();
680 let response_text = r.response.as_ref()
681 .and_then(|v| v.get("text"))
682 .and_then(|t| t.as_str())
683 .unwrap_or("");
684 json!({
685 "message": user_msg,
686 "response": response_text
687 })
688 }).collect::<Vec<_>>()
689 }))
690 .unwrap_or_default(),
691 }],
692 is_error: None,
693 };
694 }
695 }
696 }
697 }
698
699 CallToolResult {
700 content: vec![ToolContent::Text {
701 text: format!("Session not found: {}", session_id),
702 }],
703 is_error: Some(true),
704 }
705 }
706 Err(e) => CallToolResult {
707 content: vec![ToolContent::Text {
708 text: format!("Error finding session: {}", e),
709 }],
710 is_error: Some(true),
711 },
712 }
713}
714
715fn execute_show_history(path: Option<&str>) -> CallToolResult {
716 use crate::commands::history_show;
717
718 match history_show(path) {
719 Ok(_) => CallToolResult {
720 content: vec![ToolContent::Text {
721 text: json!({
722 "status": "success",
723 "message": "History displayed. See console output for details."
724 })
725 .to_string(),
726 }],
727 is_error: None,
728 },
729 Err(e) => CallToolResult {
730 content: vec![ToolContent::Text {
731 text: format!("Error showing history: {}", e),
732 }],
733 is_error: Some(true),
734 },
735 }
736}
737
738fn execute_merge_sessions(path: Option<&str>, title: Option<&str>, force: bool) -> CallToolResult {
739 use crate::commands::history_merge;
740
741 match history_merge(path, title, force, false) {
742 Ok(_) => CallToolResult {
743 content: vec![ToolContent::Text {
744 text: json!({
745 "status": "success",
746 "message": "Sessions merged successfully",
747 "title": title,
748 "force": force
749 })
750 .to_string(),
751 }],
752 is_error: None,
753 },
754 Err(e) => CallToolResult {
755 content: vec![ToolContent::Text {
756 text: format!("Error merging sessions: {}", e),
757 }],
758 is_error: Some(true),
759 },
760 }
761}
762
763fn execute_search(query: &str, limit: Option<usize>) -> CallToolResult {
764 use crate::commands::harvest_search;
765
766 let limit = limit.unwrap_or(20);
767
768 match harvest_search(None, query, None, limit) {
769 Ok(_) => CallToolResult {
770 content: vec![ToolContent::Text {
771 text: json!({
772 "status": "success",
773 "query": query,
774 "limit": limit,
775 "message": "Search completed. See console output for results."
776 })
777 .to_string(),
778 }],
779 is_error: None,
780 },
781 Err(e) => CallToolResult {
782 content: vec![ToolContent::Text {
783 text: format!("Error searching: {}", e),
784 }],
785 is_error: Some(true),
786 },
787 }
788}
789
790fn execute_detect(path: Option<&str>) -> CallToolResult {
791 use crate::commands::detect_all;
792
793 match detect_all(path, false) {
794 Ok(_) => CallToolResult {
795 content: vec![ToolContent::Text {
796 text: json!({
797 "status": "success",
798 "message": "Detection completed. See console output for details."
799 })
800 .to_string(),
801 }],
802 is_error: None,
803 },
804 Err(e) => CallToolResult {
805 content: vec![ToolContent::Text {
806 text: format!("Error detecting: {}", e),
807 }],
808 is_error: Some(true),
809 },
810 }
811}
812
813fn execute_db_list_workspaces() -> CallToolResult {
818 use super::db;
819
820 if !db::csm_db_exists() {
821 return CallToolResult {
822 content: vec![ToolContent::Text {
823 text: json!({
824 "error": "CSM database not found",
825 "message": "The csm-web database has not been initialized. Run 'csm api' to start the API server first.",
826 "db_path": db::get_csm_db_path().display().to_string()
827 }).to_string(),
828 }],
829 is_error: Some(true),
830 };
831 }
832
833 match db::list_db_workspaces() {
834 Ok(workspaces) => {
835 let infos: Vec<serde_json::Value> = workspaces
836 .iter()
837 .map(|ws| {
838 json!({
839 "id": ws.id,
840 "name": ws.name,
841 "path": ws.path,
842 "provider": ws.provider,
843 "created_at": ws.created_at,
844 "updated_at": ws.updated_at
845 })
846 })
847 .collect();
848
849 CallToolResult {
850 content: vec![ToolContent::Text {
851 text: serde_json::to_string_pretty(&json!({
852 "workspaces": infos,
853 "total": infos.len(),
854 "source": "csm-web database"
855 }))
856 .unwrap_or_default(),
857 }],
858 is_error: None,
859 }
860 }
861 Err(e) => CallToolResult {
862 content: vec![ToolContent::Text {
863 text: format!("Error listing workspaces from CSM database: {}", e),
864 }],
865 is_error: Some(true),
866 },
867 }
868}
869
870fn execute_db_list_sessions(
871 workspace_id: Option<&str>,
872 provider: Option<&str>,
873 limit: usize,
874) -> CallToolResult {
875 use super::db;
876
877 if !db::csm_db_exists() {
878 return CallToolResult {
879 content: vec![ToolContent::Text {
880 text: json!({
881 "error": "CSM database not found",
882 "message": "The csm-web database has not been initialized."
883 })
884 .to_string(),
885 }],
886 is_error: Some(true),
887 };
888 }
889
890 match db::list_db_sessions(workspace_id, provider, limit) {
891 Ok(sessions) => {
892 let infos: Vec<serde_json::Value> = sessions
893 .iter()
894 .map(|s| {
895 json!({
896 "id": s.id,
897 "workspace_id": s.workspace_id,
898 "provider": s.provider,
899 "title": s.title,
900 "model": s.model,
901 "message_count": s.message_count,
902 "created_at": s.created_at,
903 "updated_at": s.updated_at,
904 "archived": s.archived
905 })
906 })
907 .collect();
908
909 CallToolResult {
910 content: vec![ToolContent::Text {
911 text: serde_json::to_string_pretty(&json!({
912 "sessions": infos,
913 "total": infos.len(),
914 "filters": {
915 "workspace_id": workspace_id,
916 "provider": provider,
917 "limit": limit
918 },
919 "source": "csm-web database"
920 }))
921 .unwrap_or_default(),
922 }],
923 is_error: None,
924 }
925 }
926 Err(e) => CallToolResult {
927 content: vec![ToolContent::Text {
928 text: format!("Error listing sessions from CSM database: {}", e),
929 }],
930 is_error: Some(true),
931 },
932 }
933}
934
935fn execute_db_get_session(session_id: &str) -> CallToolResult {
936 use super::db;
937
938 if !db::csm_db_exists() {
939 return CallToolResult {
940 content: vec![ToolContent::Text {
941 text: json!({
942 "error": "CSM database not found"
943 })
944 .to_string(),
945 }],
946 is_error: Some(true),
947 };
948 }
949
950 match db::get_db_session(session_id) {
951 Ok(Some(session)) => {
952 let messages = db::get_db_messages(session_id).unwrap_or_default();
954
955 let message_infos: Vec<serde_json::Value> = messages
956 .iter()
957 .map(|m| {
958 json!({
959 "id": m.id,
960 "role": m.role,
961 "content": m.content,
962 "model": m.model,
963 "created_at": m.created_at
964 })
965 })
966 .collect();
967
968 CallToolResult {
969 content: vec![ToolContent::Text {
970 text: serde_json::to_string_pretty(&json!({
971 "session": {
972 "id": session.id,
973 "workspace_id": session.workspace_id,
974 "provider": session.provider,
975 "title": session.title,
976 "model": session.model,
977 "message_count": session.message_count,
978 "created_at": session.created_at,
979 "updated_at": session.updated_at,
980 "archived": session.archived
981 },
982 "messages": message_infos,
983 "source": "csm-web database"
984 }))
985 .unwrap_or_default(),
986 }],
987 is_error: None,
988 }
989 }
990 Ok(None) => CallToolResult {
991 content: vec![ToolContent::Text {
992 text: format!("Session not found: {}", session_id),
993 }],
994 is_error: Some(true),
995 },
996 Err(e) => CallToolResult {
997 content: vec![ToolContent::Text {
998 text: format!("Error getting session: {}", e),
999 }],
1000 is_error: Some(true),
1001 },
1002 }
1003}
1004
1005fn execute_db_search(query: &str, limit: usize) -> CallToolResult {
1006 use super::db;
1007
1008 if !db::csm_db_exists() {
1009 return CallToolResult {
1010 content: vec![ToolContent::Text {
1011 text: json!({
1012 "error": "CSM database not found"
1013 })
1014 .to_string(),
1015 }],
1016 is_error: Some(true),
1017 };
1018 }
1019
1020 match db::search_db_sessions(query, limit) {
1021 Ok(sessions) => {
1022 let infos: Vec<serde_json::Value> = sessions
1023 .iter()
1024 .map(|s| {
1025 json!({
1026 "id": s.id,
1027 "title": s.title,
1028 "provider": s.provider,
1029 "message_count": s.message_count,
1030 "updated_at": s.updated_at
1031 })
1032 })
1033 .collect();
1034
1035 CallToolResult {
1036 content: vec![ToolContent::Text {
1037 text: serde_json::to_string_pretty(&json!({
1038 "query": query,
1039 "results": infos,
1040 "total": infos.len(),
1041 "source": "csm-web database"
1042 }))
1043 .unwrap_or_default(),
1044 }],
1045 is_error: None,
1046 }
1047 }
1048 Err(e) => CallToolResult {
1049 content: vec![ToolContent::Text {
1050 text: format!("Error searching: {}", e),
1051 }],
1052 is_error: Some(true),
1053 },
1054 }
1055}
1056
1057fn execute_db_stats() -> CallToolResult {
1058 use super::db;
1059
1060 if !db::csm_db_exists() {
1061 return CallToolResult {
1062 content: vec![ToolContent::Text {
1063 text: json!({
1064 "error": "CSM database not found",
1065 "db_path": db::get_csm_db_path().display().to_string()
1066 })
1067 .to_string(),
1068 }],
1069 is_error: Some(true),
1070 };
1071 }
1072
1073 match db::count_sessions_by_provider() {
1074 Ok(counts) => {
1075 let provider_counts: serde_json::Value = counts
1076 .iter()
1077 .map(|(provider, count)| (provider.clone(), *count))
1078 .collect();
1079
1080 let total: i64 = counts.iter().map(|(_, c)| c).sum();
1081
1082 CallToolResult {
1083 content: vec![ToolContent::Text {
1084 text: serde_json::to_string_pretty(&json!({
1085 "total_sessions": total,
1086 "by_provider": provider_counts,
1087 "db_path": db::get_csm_db_path().display().to_string(),
1088 "source": "csm-web database"
1089 }))
1090 .unwrap_or_default(),
1091 }],
1092 is_error: None,
1093 }
1094 }
1095 Err(e) => CallToolResult {
1096 content: vec![ToolContent::Text {
1097 text: format!("Error getting stats: {}", e),
1098 }],
1099 is_error: Some(true),
1100 },
1101 }
1102}