roboticus_cli/cli/admin/
models.rs1use super::*;
2
3pub async fn cmd_models_list(base_url: &str, json: bool) -> Result<(), Box<dyn std::error::Error>> {
6 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
7 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
8 let resp = super::http_client()?
9 .get(format!("{base_url}/api/config"))
10 .send()
11 .await?;
12 let config: serde_json::Value = resp.json().await?;
13 if json {
14 println!("{}", serde_json::to_string_pretty(&config)?);
15 return Ok(());
16 }
17
18 println!("\n {BOLD}Configured Models{RESET}\n");
19
20 let primary = config
21 .pointer("/models/primary")
22 .and_then(|v| v.as_str())
23 .unwrap_or("not set");
24 println!(" {:<12} {}", format!("{GREEN}primary{RESET}"), primary);
25
26 if let Some(fallbacks) = config
27 .pointer("/models/fallbacks")
28 .and_then(|v| v.as_array())
29 {
30 for (i, fb) in fallbacks.iter().enumerate() {
31 let name = fb.as_str().unwrap_or("?");
32 println!(
33 " {:<12} {}",
34 format!("{YELLOW}fallback {}{RESET}", i + 1),
35 name
36 );
37 }
38 }
39
40 let mode = config
41 .pointer("/models/routing/mode")
42 .and_then(|v| v.as_str())
43 .unwrap_or("rule");
44 let threshold = config
45 .pointer("/models/routing/confidence_threshold")
46 .and_then(|v| v.as_f64())
47 .unwrap_or(0.9);
48 let local_first = config
49 .pointer("/models/routing/local_first")
50 .and_then(|v| v.as_bool())
51 .unwrap_or(true);
52
53 println!();
54 println!(
55 " {DIM}Routing: mode={mode}, threshold={threshold}, local_first={local_first}{RESET}"
56 );
57 println!();
58 Ok(())
59}
60
61pub async fn cmd_models_scan(
62 base_url: &str,
63 provider: Option<&str>,
64) -> Result<(), Box<dyn std::error::Error>> {
65 let (DIM, BOLD, ACCENT, GREEN, YELLOW, RED, CYAN, RESET, MONO) = colors();
66 let (OK, ACTION, WARN, DETAIL, ERR) = icons();
67 println!("\n {BOLD}Scanning for available models...{RESET}\n");
68
69 let resp = super::http_client()?
70 .get(format!("{base_url}/api/config"))
71 .send()
72 .await?;
73 let config: serde_json::Value = resp.json().await?;
74
75 let providers = config
76 .get("providers")
77 .and_then(|v| v.as_object())
78 .cloned()
79 .unwrap_or_default();
80
81 if providers.is_empty() {
82 println!(" No providers configured.");
83 println!();
84 return Ok(());
85 }
86
87 let client = reqwest::Client::builder()
88 .timeout(std::time::Duration::from_secs(10))
89 .build()?;
90
91 for (name, prov_config) in &providers {
92 if let Some(filter) = provider
93 && name != filter
94 {
95 continue;
96 }
97
98 let url = prov_config
99 .get("url")
100 .and_then(|v| v.as_str())
101 .unwrap_or("");
102
103 if url.is_empty() {
104 println!(" {YELLOW}{name}{RESET}: no URL configured");
105 continue;
106 }
107
108 let name_l = name.to_lowercase();
109 let url_l = url.to_lowercase();
110 let ollama_like = name_l.contains("ollama") || url_l.contains("11434");
111 let models_url = if ollama_like {
112 format!("{url}/api/tags")
113 } else {
114 format!("{url}/v1/models")
115 };
116
117 let scan_result =
118 super::spin_while(&format!("Probing {name}"), client.get(&models_url).send()).await;
119
120 print!(" {CYAN}{name}{RESET} ({url}): ");
121 match scan_result {
122 Ok(resp) if resp.status().is_success() => {
123 let body: serde_json::Value = resp.json().await.unwrap_or_default();
124 let models: Vec<String> =
125 if let Some(arr) = body.get("models").and_then(|v| v.as_array()) {
126 arr.iter()
127 .filter_map(|m| {
128 m.get("name")
129 .or_else(|| m.get("model"))
130 .and_then(|v| v.as_str())
131 })
132 .map(String::from)
133 .collect()
134 } else if let Some(arr) = body.get("data").and_then(|v| v.as_array()) {
135 arr.iter()
136 .filter_map(|m| m.get("id").and_then(|v| v.as_str()))
137 .map(String::from)
138 .collect()
139 } else {
140 vec![]
141 };
142
143 if models.is_empty() {
144 println!("no models found");
145 } else {
146 println!("{} model(s)", models.len());
147 for model in &models {
148 println!(" - {model}");
149 }
150 }
151 }
152 Ok(resp) => {
153 println!("{RED}error: {}{RESET}", resp.status());
154 }
155 Err(e) => {
156 println!("{RED}unreachable: {e}{RESET}");
157 }
158 }
159 }
160
161 println!();
162 Ok(())
163}