chasm_cli/commands/
providers.rs1use anyhow::Result;
6use colored::*;
7
8use crate::providers::{
9 config::{CsmConfig, ProviderConfig},
10 discovery::print_provider_summary,
11 ProviderRegistry, ProviderType,
12};
13
14pub fn list_providers() -> Result<()> {
16 let registry = ProviderRegistry::new();
17 print_provider_summary(®istry);
18 Ok(())
19}
20
21pub fn provider_info(provider_name: &str) -> Result<()> {
23 let provider_type = parse_provider_name(provider_name)?;
24 let registry = ProviderRegistry::new();
25
26 if let Some(provider) = registry.get_provider(provider_type) {
27 println!("{}", format!("Provider: {}", provider.name()).bold());
28 println!();
29
30 println!(" Type: {}", provider_type.display_name());
31 println!(
32 " Available: {}",
33 if provider.is_available() {
34 "Yes".green()
35 } else {
36 "No".red()
37 }
38 );
39
40 if let Some(path) = provider.sessions_path() {
41 println!(" Data Path: {}", path.display());
42 }
43
44 if let Some(endpoint) = provider_type.default_endpoint() {
45 println!(" Endpoint: {}", endpoint);
46 }
47
48 println!(
49 " OpenAI Compatible: {}",
50 if provider_type.is_openai_compatible() {
51 "Yes".green()
52 } else {
53 "No".dimmed()
54 }
55 );
56
57 println!(
58 " File Storage: {}",
59 if provider_type.uses_file_storage() {
60 "Yes".green()
61 } else {
62 "No".dimmed()
63 }
64 );
65
66 if provider.is_available() {
68 match provider.list_sessions() {
69 Ok(sessions) => {
70 println!();
71 println!(" Sessions: {}", sessions.len());
72
73 if !sessions.is_empty() {
74 println!();
75 println!(" Recent sessions:");
76 for session in sessions.iter().take(5) {
77 println!(" - {}", session.title());
78 }
79 }
80 }
81 Err(_) => {
82 println!(" Sessions: (unable to list)");
83 }
84 }
85 }
86 } else {
87 eprintln!("{} Provider not found: {}", "Error:".red(), provider_name);
88 eprintln!();
89 eprintln!("Available providers:");
90 list_provider_types();
91 return Err(anyhow::anyhow!("Provider not found"));
92 }
93
94 Ok(())
95}
96
97pub fn configure_provider(
99 provider_name: &str,
100 endpoint: Option<&str>,
101 api_key: Option<&str>,
102 model: Option<&str>,
103 enabled: Option<bool>,
104) -> Result<()> {
105 let provider_type = parse_provider_name(provider_name)?;
106 let mut config = CsmConfig::load()?;
107
108 let mut provider_config = config
110 .get_provider(provider_type)
111 .cloned()
112 .unwrap_or_else(|| ProviderConfig::new(provider_type));
113
114 if let Some(endpoint) = endpoint {
116 provider_config.endpoint = Some(endpoint.to_string());
117 }
118
119 if let Some(api_key) = api_key {
120 provider_config.api_key = Some(api_key.to_string());
121 }
122
123 if let Some(model) = model {
124 provider_config.model = Some(model.to_string());
125 }
126
127 if let Some(enabled) = enabled {
128 provider_config.enabled = enabled;
129 }
130
131 config.set_provider(provider_config.clone());
133 config.save()?;
134
135 println!("{} Configured provider: {}", "+".green(), provider_name);
136 println!();
137 println!(
138 " Endpoint: {}",
139 provider_config.endpoint.as_deref().unwrap_or("(default)")
140 );
141 println!(
142 " API Key: {}",
143 if provider_config.api_key.is_some() {
144 "(set)".green().to_string()
145 } else {
146 "(none)".dimmed().to_string()
147 }
148 );
149 println!(
150 " Model: {}",
151 provider_config.model.as_deref().unwrap_or("(default)")
152 );
153 println!(
154 " Enabled: {}",
155 if provider_config.enabled {
156 "Yes".green()
157 } else {
158 "No".red()
159 }
160 );
161
162 Ok(())
163}
164
165pub fn import_from_provider(
167 from_provider: &str,
168 target_path: Option<&str>,
169 session_id: Option<&str>,
170) -> Result<()> {
171 let provider_type = parse_provider_name(from_provider)?;
172 let registry = ProviderRegistry::new();
173
174 let provider = registry
175 .get_provider(provider_type)
176 .ok_or_else(|| anyhow::anyhow!("Provider not found: {}", from_provider))?;
177
178 if !provider.is_available() {
179 return Err(anyhow::anyhow!(
180 "Provider {} is not available",
181 from_provider
182 ));
183 }
184
185 let project_path = target_path
186 .map(String::from)
187 .or_else(|| {
188 std::env::current_dir()
189 .ok()
190 .map(|p| p.to_string_lossy().to_string())
191 })
192 .ok_or_else(|| anyhow::anyhow!("Could not determine target path"))?;
193
194 if let Some(session_id) = session_id {
195 println!(
197 "Importing session {} from {}...",
198 session_id,
199 provider.name()
200 );
201
202 let session = provider.import_session(session_id)?;
203
204 let workspace = crate::workspace::get_workspace_by_path(&project_path)?
206 .ok_or_else(|| anyhow::anyhow!("Workspace not found for path: {}", project_path))?;
207 let sessions_dir = workspace.chat_sessions_path;
208 std::fs::create_dir_all(&sessions_dir)?;
209
210 let session_file = sessions_dir.join(format!("{}.json", session_id));
211 let content = serde_json::to_string_pretty(&session)?;
212 std::fs::write(&session_file, content)?;
213
214 println!("{} Imported session: {}", "+".green(), session.title());
215 } else {
216 println!("Importing all sessions from {}...", provider.name());
218
219 let sessions = provider.list_sessions()?;
220
221 if sessions.is_empty() {
222 println!(" No sessions found");
223 return Ok(());
224 }
225
226 let workspace = crate::workspace::get_workspace_by_path(&project_path)?
227 .ok_or_else(|| anyhow::anyhow!("Workspace not found for path: {}", project_path))?;
228 let sessions_dir = workspace.chat_sessions_path;
229 std::fs::create_dir_all(&sessions_dir)?;
230
231 let mut imported = 0;
232 for session in &sessions {
233 let id = session
234 .session_id
235 .clone()
236 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
237 let session_file = sessions_dir.join(format!("{}.json", id));
238
239 if !session_file.exists() {
240 let content = serde_json::to_string_pretty(&session)?;
241 std::fs::write(&session_file, content)?;
242 imported += 1;
243 println!(" {} {}", "+".green(), session.title());
244 }
245 }
246
247 println!();
248 println!(
249 "Imported {} of {} sessions",
250 imported.to_string().green(),
251 sessions.len()
252 );
253 }
254
255 Ok(())
256}
257
258pub fn test_provider(provider_name: &str) -> Result<()> {
260 let provider_type = parse_provider_name(provider_name)?;
261 let registry = ProviderRegistry::new();
262
263 print!("Testing {} connection... ", provider_type.display_name());
264
265 if let Some(provider) = registry.get_provider(provider_type) {
266 if provider.is_available() {
267 println!("{}", "OK".green());
268
269 match provider.list_sessions() {
271 Ok(sessions) => {
272 println!(" Found {} sessions", sessions.len());
273 }
274 Err(e) => {
275 println!(" {}: {}", "Warning".yellow(), e);
276 }
277 }
278
279 Ok(())
280 } else {
281 println!("{}", "FAILED".red());
282 println!();
283
284 if let Some(endpoint) = provider_type.default_endpoint() {
285 println!(" Expected endpoint: {}", endpoint);
286 println!(" Make sure the service is running.");
287 }
288
289 Err(anyhow::anyhow!("Provider not available"))
290 }
291 } else {
292 println!("{}", "NOT FOUND".red());
293 Err(anyhow::anyhow!("Provider not found"))
294 }
295}
296
297fn parse_provider_name(name: &str) -> Result<ProviderType> {
299 match name.to_lowercase().as_str() {
300 "copilot" | "github-copilot" | "vscode" => Ok(ProviderType::Copilot),
301 "cursor" => Ok(ProviderType::Cursor),
302 "ollama" => Ok(ProviderType::Ollama),
303 "vllm" => Ok(ProviderType::Vllm),
304 "foundry" | "azure-foundry" | "foundry-local" | "ai-foundry" => Ok(ProviderType::Foundry),
305 "openai" => Ok(ProviderType::OpenAI),
306 "lm-studio" | "lmstudio" => Ok(ProviderType::LmStudio),
307 "localai" | "local-ai" => Ok(ProviderType::LocalAI),
308 "text-gen-webui" | "textgenwebui" | "oobabooga" => Ok(ProviderType::TextGenWebUI),
309 "jan" | "jan-ai" | "janai" => Ok(ProviderType::Jan),
310 "gpt4all" => Ok(ProviderType::Gpt4All),
311 "llamafile" => Ok(ProviderType::Llamafile),
312 "custom" => Ok(ProviderType::Custom),
313 _ => {
314 eprintln!("{} Unknown provider: {}", "Error:".red(), name);
315 eprintln!();
316 list_provider_types();
317 Err(anyhow::anyhow!("Unknown provider"))
318 }
319 }
320}
321
322fn list_provider_types() {
324 eprintln!("Supported providers:");
325 eprintln!(" copilot - GitHub Copilot (VS Code)");
326 eprintln!(" cursor - Cursor IDE");
327 eprintln!(" ollama - Ollama local models");
328 eprintln!(" vllm - vLLM server");
329 eprintln!(" foundry - Azure AI Foundry / Foundry Local");
330 eprintln!(" openai - OpenAI API");
331 eprintln!(" lm-studio - LM Studio");
332 eprintln!(" localai - LocalAI");
333 eprintln!(" text-gen-webui - Text Generation WebUI");
334 eprintln!(" jan - Jan.ai");
335 eprintln!(" gpt4all - GPT4All");
336 eprintln!(" llamafile - Llamafile");
337 eprintln!(" custom - Custom OpenAI-compatible endpoint");
338}