1#![allow(dead_code, unused_imports)]
6
7use super::types::*;
8use serde_json::json;
9
10pub fn list_resources() -> Vec<Resource> {
12 vec![
13 Resource {
15 uri: "csm://workspaces".to_string(),
16 name: "VS Code Workspaces".to_string(),
17 description: Some("All VS Code workspaces with chat sessions".to_string()),
18 mime_type: Some("application/json".to_string()),
19 },
20 Resource {
21 uri: "csm://sessions".to_string(),
22 name: "VS Code Sessions".to_string(),
23 description: Some("All chat sessions from VS Code workspaces".to_string()),
24 mime_type: Some("application/json".to_string()),
25 },
26 Resource {
27 uri: "csm://orphaned".to_string(),
28 name: "Orphaned Sessions".to_string(),
29 description: Some("Sessions on disk but not in VS Code's index".to_string()),
30 mime_type: Some("application/json".to_string()),
31 },
32 Resource {
33 uri: "csm://providers".to_string(),
34 name: "Providers".to_string(),
35 description: Some("Available LLM providers".to_string()),
36 mime_type: Some("application/json".to_string()),
37 },
38 Resource {
40 uri: "csm://db/workspaces".to_string(),
41 name: "CSM-Web Workspaces".to_string(),
42 description: Some("Workspaces from the csm-web database".to_string()),
43 mime_type: Some("application/json".to_string()),
44 },
45 Resource {
46 uri: "csm://db/sessions".to_string(),
47 name: "CSM-Web Sessions".to_string(),
48 description: Some("Chat sessions from the csm-web database".to_string()),
49 mime_type: Some("application/json".to_string()),
50 },
51 Resource {
52 uri: "csm://db/stats".to_string(),
53 name: "CSM-Web Statistics".to_string(),
54 description: Some("Database statistics and session counts by provider".to_string()),
55 mime_type: Some("application/json".to_string()),
56 },
57 ]
58}
59
60pub fn read_resource(uri: &str) -> ReadResourceResult {
62 match uri {
63 "csm://workspaces" => read_workspaces_resource(),
65 "csm://sessions" => read_sessions_resource(),
66 "csm://orphaned" => read_orphaned_resource(),
67 "csm://providers" => read_providers_resource(),
68 "csm://db/workspaces" => read_db_workspaces_resource(),
70 "csm://db/sessions" => read_db_sessions_resource(),
71 "csm://db/stats" => read_db_stats_resource(),
72 _ => {
73 if let Some(hash) = uri.strip_prefix("csm://workspace/") {
75 read_workspace_resource(hash)
76 } else if let Some(id) = uri.strip_prefix("csm://session/") {
77 read_session_resource(id)
78 } else if let Some(id) = uri.strip_prefix("csm://db/session/") {
79 read_db_session_resource(id)
80 } else {
81 ReadResourceResult {
82 contents: vec![ResourceContent {
83 uri: uri.to_string(),
84 mime_type: Some("text/plain".to_string()),
85 text: Some(format!("Unknown resource: {}", uri)),
86 blob: None,
87 }],
88 }
89 }
90 }
91 }
92}
93
94fn read_workspaces_resource() -> ReadResourceResult {
95 use crate::workspace::discover_workspaces;
96
97 match discover_workspaces() {
98 Ok(workspaces) => {
99 let infos: Vec<serde_json::Value> = workspaces
100 .iter()
101 .map(|ws| {
102 json!({
103 "hash": ws.hash,
104 "project_path": ws.project_path,
105 "session_count": ws.chat_session_count,
106 "has_chats": ws.has_chat_sessions,
107 "workspace_path": ws.workspace_path.display().to_string()
108 })
109 })
110 .collect();
111
112 ReadResourceResult {
113 contents: vec![ResourceContent {
114 uri: "csm://workspaces".to_string(),
115 mime_type: Some("application/json".to_string()),
116 text: Some(
117 serde_json::to_string_pretty(&json!({
118 "workspaces": infos,
119 "total": infos.len()
120 }))
121 .unwrap_or_default(),
122 ),
123 blob: None,
124 }],
125 }
126 }
127 Err(e) => ReadResourceResult {
128 contents: vec![ResourceContent {
129 uri: "csm://workspaces".to_string(),
130 mime_type: Some("text/plain".to_string()),
131 text: Some(format!("Error: {}", e)),
132 blob: None,
133 }],
134 },
135 }
136}
137
138fn read_sessions_resource() -> ReadResourceResult {
139 use crate::workspace::{discover_workspaces, get_chat_sessions_from_workspace};
140
141 match discover_workspaces() {
142 Ok(workspaces) => {
143 let mut all_sessions = Vec::new();
144
145 for ws in &workspaces {
146 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws.workspace_path) {
147 for s in sessions {
148 all_sessions.push(json!({
149 "id": s.session.session_id,
150 "title": s.session.title(),
151 "message_count": s.session.requests.len(),
152 "last_message_date": s.session.last_message_date,
153 "workspace_hash": ws.hash,
154 "project_path": ws.project_path,
155 "file_path": s.path.display().to_string()
156 }));
157 }
158 }
159 }
160
161 ReadResourceResult {
162 contents: vec![ResourceContent {
163 uri: "csm://sessions".to_string(),
164 mime_type: Some("application/json".to_string()),
165 text: Some(
166 serde_json::to_string_pretty(&json!({
167 "sessions": all_sessions,
168 "total": all_sessions.len()
169 }))
170 .unwrap_or_default(),
171 ),
172 blob: None,
173 }],
174 }
175 }
176 Err(e) => ReadResourceResult {
177 contents: vec![ResourceContent {
178 uri: "csm://sessions".to_string(),
179 mime_type: Some("text/plain".to_string()),
180 text: Some(format!("Error: {}", e)),
181 blob: None,
182 }],
183 },
184 }
185}
186
187fn read_orphaned_resource() -> ReadResourceResult {
188 ReadResourceResult {
190 contents: vec![ResourceContent {
191 uri: "csm://orphaned".to_string(),
192 mime_type: Some("application/json".to_string()),
193 text: Some(json!({
194 "message": "Use csm_list_orphaned tool with a specific path to find orphaned sessions"
195 }).to_string()),
196 blob: None,
197 }],
198 }
199}
200
201fn read_providers_resource() -> ReadResourceResult {
202 let providers = json!({
203 "providers": [
204 {"name": "copilot", "type": "vscode", "description": "GitHub Copilot (VS Code)"},
205 {"name": "cursor", "type": "ide", "description": "Cursor IDE"},
206 {"name": "ollama", "type": "local", "description": "Ollama local LLM"},
207 {"name": "vllm", "type": "local", "description": "vLLM server"},
208 {"name": "lm-studio", "type": "local", "description": "LM Studio"},
209 {"name": "jan", "type": "local", "description": "Jan.ai"},
210 {"name": "gpt4all", "type": "local", "description": "GPT4All"},
211 {"name": "localai", "type": "local", "description": "LocalAI"},
212 {"name": "llamafile", "type": "local", "description": "Llamafile"},
213 {"name": "text-gen-webui", "type": "local", "description": "Text Generation WebUI"},
214 {"name": "azure-foundry", "type": "cloud", "description": "Azure AI Foundry"},
215 {"name": "chatgpt", "type": "web", "description": "ChatGPT (share links)"},
216 {"name": "claude", "type": "web", "description": "Claude (share links)"},
217 {"name": "gemini", "type": "web", "description": "Gemini (share links)"},
218 {"name": "perplexity", "type": "web", "description": "Perplexity (share links)"},
219 {"name": "deepseek", "type": "web", "description": "DeepSeek (share links)"}
220 ]
221 });
222
223 ReadResourceResult {
224 contents: vec![ResourceContent {
225 uri: "csm://providers".to_string(),
226 mime_type: Some("application/json".to_string()),
227 text: Some(serde_json::to_string_pretty(&providers).unwrap_or_default()),
228 blob: None,
229 }],
230 }
231}
232
233fn read_workspace_resource(hash: &str) -> ReadResourceResult {
234 use crate::workspace::{discover_workspaces, get_chat_sessions_from_workspace};
235
236 match discover_workspaces() {
237 Ok(workspaces) => {
238 for ws in &workspaces {
239 if ws.hash.starts_with(hash) || ws.hash == hash {
240 let sessions =
241 get_chat_sessions_from_workspace(&ws.workspace_path).unwrap_or_default();
242
243 let session_infos: Vec<serde_json::Value> = sessions
244 .iter()
245 .map(|s| {
246 json!({
247 "id": s.session.session_id,
248 "title": s.session.title(),
249 "message_count": s.session.requests.len(),
250 "file_path": s.path.display().to_string()
251 })
252 })
253 .collect();
254
255 return ReadResourceResult {
256 contents: vec![ResourceContent {
257 uri: format!("csm://workspace/{}", ws.hash),
258 mime_type: Some("application/json".to_string()),
259 text: Some(
260 serde_json::to_string_pretty(&json!({
261 "hash": ws.hash,
262 "project_path": ws.project_path,
263 "workspace_path": ws.workspace_path.display().to_string(),
264 "session_count": ws.chat_session_count,
265 "sessions": session_infos
266 }))
267 .unwrap_or_default(),
268 ),
269 blob: None,
270 }],
271 };
272 }
273 }
274
275 ReadResourceResult {
276 contents: vec![ResourceContent {
277 uri: format!("csm://workspace/{}", hash),
278 mime_type: Some("text/plain".to_string()),
279 text: Some(format!("Workspace not found: {}", hash)),
280 blob: None,
281 }],
282 }
283 }
284 Err(e) => ReadResourceResult {
285 contents: vec![ResourceContent {
286 uri: format!("csm://workspace/{}", hash),
287 mime_type: Some("text/plain".to_string()),
288 text: Some(format!("Error: {}", e)),
289 blob: None,
290 }],
291 },
292 }
293}
294
295fn read_session_resource(session_id: &str) -> ReadResourceResult {
296 use crate::workspace::{discover_workspaces, get_chat_sessions_from_workspace};
297
298 match discover_workspaces() {
299 Ok(workspaces) => {
300 for ws in &workspaces {
301 if let Ok(sessions) = get_chat_sessions_from_workspace(&ws.workspace_path) {
302 for s in sessions {
303 let sid = s.session.session_id.clone().unwrap_or_default();
304 if sid.starts_with(session_id) || sid == session_id {
305 let messages: Vec<serde_json::Value> = s
307 .session
308 .requests
309 .iter()
310 .map(|r| {
311 let user_msg = r
312 .message
313 .as_ref()
314 .map(|m| m.get_text())
315 .unwrap_or_default();
316 let response_text = r
317 .response
318 .as_ref()
319 .and_then(|v| v.get("text"))
320 .and_then(|t| t.as_str())
321 .unwrap_or("");
322 json!({
323 "message": user_msg,
324 "response": response_text,
325 "timestamp": r.timestamp
326 })
327 })
328 .collect();
329
330 return ReadResourceResult {
331 contents: vec![ResourceContent {
332 uri: format!("csm://session/{}", sid),
333 mime_type: Some("application/json".to_string()),
334 text: Some(
335 serde_json::to_string_pretty(&json!({
336 "id": sid,
337 "title": s.session.title(),
338 "message_count": s.session.requests.len(),
339 "last_message_date": s.session.last_message_date,
340 "is_imported": s.session.is_imported,
341 "workspace_hash": ws.hash,
342 "project_path": ws.project_path,
343 "file_path": s.path.display().to_string(),
344 "messages": messages
345 }))
346 .unwrap_or_default(),
347 ),
348 blob: None,
349 }],
350 };
351 }
352 }
353 }
354 }
355
356 ReadResourceResult {
357 contents: vec![ResourceContent {
358 uri: format!("csm://session/{}", session_id),
359 mime_type: Some("text/plain".to_string()),
360 text: Some(format!("Session not found: {}", session_id)),
361 blob: None,
362 }],
363 }
364 }
365 Err(e) => ReadResourceResult {
366 contents: vec![ResourceContent {
367 uri: format!("csm://session/{}", session_id),
368 mime_type: Some("text/plain".to_string()),
369 text: Some(format!("Error: {}", e)),
370 blob: None,
371 }],
372 },
373 }
374}
375
376fn read_db_workspaces_resource() -> ReadResourceResult {
381 use super::db;
382
383 if !db::csm_db_exists() {
384 return ReadResourceResult {
385 contents: vec![ResourceContent {
386 uri: "csm://db/workspaces".to_string(),
387 mime_type: Some("application/json".to_string()),
388 text: Some(
389 json!({
390 "error": "CSM database not found",
391 "message": "Initialize csm-web database first",
392 "db_path": db::get_csm_db_path().display().to_string()
393 })
394 .to_string(),
395 ),
396 blob: None,
397 }],
398 };
399 }
400
401 match db::list_db_workspaces() {
402 Ok(workspaces) => {
403 let infos: Vec<serde_json::Value> = workspaces
404 .iter()
405 .map(|ws| {
406 json!({
407 "id": ws.id,
408 "name": ws.name,
409 "path": ws.path,
410 "provider": ws.provider,
411 "created_at": ws.created_at,
412 "updated_at": ws.updated_at
413 })
414 })
415 .collect();
416
417 ReadResourceResult {
418 contents: vec![ResourceContent {
419 uri: "csm://db/workspaces".to_string(),
420 mime_type: Some("application/json".to_string()),
421 text: Some(
422 serde_json::to_string_pretty(&json!({
423 "workspaces": infos,
424 "total": infos.len(),
425 "source": "csm-web database"
426 }))
427 .unwrap_or_default(),
428 ),
429 blob: None,
430 }],
431 }
432 }
433 Err(e) => ReadResourceResult {
434 contents: vec![ResourceContent {
435 uri: "csm://db/workspaces".to_string(),
436 mime_type: Some("text/plain".to_string()),
437 text: Some(format!("Error: {}", e)),
438 blob: None,
439 }],
440 },
441 }
442}
443
444fn read_db_sessions_resource() -> ReadResourceResult {
445 use super::db;
446
447 if !db::csm_db_exists() {
448 return ReadResourceResult {
449 contents: vec![ResourceContent {
450 uri: "csm://db/sessions".to_string(),
451 mime_type: Some("application/json".to_string()),
452 text: Some(
453 json!({
454 "error": "CSM database not found"
455 })
456 .to_string(),
457 ),
458 blob: None,
459 }],
460 };
461 }
462
463 match db::list_db_sessions(None, None, 100) {
464 Ok(sessions) => {
465 let infos: Vec<serde_json::Value> = sessions
466 .iter()
467 .map(|s| {
468 json!({
469 "id": s.id,
470 "workspace_id": s.workspace_id,
471 "provider": s.provider,
472 "title": s.title,
473 "model": s.model,
474 "message_count": s.message_count,
475 "created_at": s.created_at,
476 "updated_at": s.updated_at
477 })
478 })
479 .collect();
480
481 ReadResourceResult {
482 contents: vec![ResourceContent {
483 uri: "csm://db/sessions".to_string(),
484 mime_type: Some("application/json".to_string()),
485 text: Some(
486 serde_json::to_string_pretty(&json!({
487 "sessions": infos,
488 "total": infos.len(),
489 "source": "csm-web database"
490 }))
491 .unwrap_or_default(),
492 ),
493 blob: None,
494 }],
495 }
496 }
497 Err(e) => ReadResourceResult {
498 contents: vec![ResourceContent {
499 uri: "csm://db/sessions".to_string(),
500 mime_type: Some("text/plain".to_string()),
501 text: Some(format!("Error: {}", e)),
502 blob: None,
503 }],
504 },
505 }
506}
507
508fn read_db_stats_resource() -> ReadResourceResult {
509 use super::db;
510
511 if !db::csm_db_exists() {
512 return ReadResourceResult {
513 contents: vec![ResourceContent {
514 uri: "csm://db/stats".to_string(),
515 mime_type: Some("application/json".to_string()),
516 text: Some(
517 json!({
518 "error": "CSM database not found",
519 "db_path": db::get_csm_db_path().display().to_string()
520 })
521 .to_string(),
522 ),
523 blob: None,
524 }],
525 };
526 }
527
528 match db::count_sessions_by_provider() {
529 Ok(counts) => {
530 let provider_counts: serde_json::Value = counts
531 .iter()
532 .map(|(provider, count)| (provider.clone(), *count))
533 .collect();
534
535 let total: i64 = counts.iter().map(|(_, c)| c).sum();
536
537 ReadResourceResult {
538 contents: vec![ResourceContent {
539 uri: "csm://db/stats".to_string(),
540 mime_type: Some("application/json".to_string()),
541 text: Some(
542 serde_json::to_string_pretty(&json!({
543 "total_sessions": total,
544 "by_provider": provider_counts,
545 "db_path": db::get_csm_db_path().display().to_string(),
546 "source": "csm-web database"
547 }))
548 .unwrap_or_default(),
549 ),
550 blob: None,
551 }],
552 }
553 }
554 Err(e) => ReadResourceResult {
555 contents: vec![ResourceContent {
556 uri: "csm://db/stats".to_string(),
557 mime_type: Some("text/plain".to_string()),
558 text: Some(format!("Error: {}", e)),
559 blob: None,
560 }],
561 },
562 }
563}
564
565fn read_db_session_resource(session_id: &str) -> ReadResourceResult {
566 use super::db;
567
568 if !db::csm_db_exists() {
569 return ReadResourceResult {
570 contents: vec![ResourceContent {
571 uri: format!("csm://db/session/{}", session_id),
572 mime_type: Some("application/json".to_string()),
573 text: Some(
574 json!({
575 "error": "CSM database not found"
576 })
577 .to_string(),
578 ),
579 blob: None,
580 }],
581 };
582 }
583
584 match db::get_db_session(session_id) {
585 Ok(Some(session)) => {
586 let messages = db::get_db_messages(session_id).unwrap_or_default();
587
588 let message_infos: Vec<serde_json::Value> = messages
589 .iter()
590 .map(|m| {
591 json!({
592 "id": m.id,
593 "role": m.role,
594 "content": m.content,
595 "model": m.model,
596 "created_at": m.created_at
597 })
598 })
599 .collect();
600
601 ReadResourceResult {
602 contents: vec![ResourceContent {
603 uri: format!("csm://db/session/{}", session_id),
604 mime_type: Some("application/json".to_string()),
605 text: Some(
606 serde_json::to_string_pretty(&json!({
607 "session": {
608 "id": session.id,
609 "workspace_id": session.workspace_id,
610 "provider": session.provider,
611 "title": session.title,
612 "model": session.model,
613 "message_count": session.message_count,
614 "created_at": session.created_at,
615 "updated_at": session.updated_at
616 },
617 "messages": message_infos,
618 "source": "csm-web database"
619 }))
620 .unwrap_or_default(),
621 ),
622 blob: None,
623 }],
624 }
625 }
626 Ok(None) => ReadResourceResult {
627 contents: vec![ResourceContent {
628 uri: format!("csm://db/session/{}", session_id),
629 mime_type: Some("text/plain".to_string()),
630 text: Some(format!("Session not found: {}", session_id)),
631 blob: None,
632 }],
633 },
634 Err(e) => ReadResourceResult {
635 contents: vec![ResourceContent {
636 uri: format!("csm://db/session/{}", session_id),
637 mime_type: Some("text/plain".to_string()),
638 text: Some(format!("Error: {}", e)),
639 blob: None,
640 }],
641 },
642 }
643}