systemprompt_cli/commands/admin/config/
provider.rs1use anyhow::Result;
8use clap::{Args, Subcommand};
9
10use super::types::{
11 ConfigSection, ProviderInfo, ProviderListOutput, ProviderSetOutput, read_yaml_file,
12 write_yaml_file,
13};
14use crate::CliConfig;
15use crate::shared::{CommandResult, render_result};
16
17#[derive(Debug, Subcommand)]
18pub enum ProviderCommands {
19 #[command(about = "List AI providers")]
20 List(ListArgs),
21
22 #[command(about = "Set default provider")]
23 Set(SetArgs),
24
25 #[command(about = "Enable a provider")]
26 Enable(EnableArgs),
27
28 #[command(about = "Disable a provider")]
29 Disable(DisableArgs),
30}
31
32#[derive(Debug, Clone, Copy, Args)]
33pub struct ListArgs;
34
35#[derive(Debug, Clone, Args)]
36pub struct SetArgs {
37 #[arg(value_name = "PROVIDER")]
38 pub provider: String,
39}
40
41#[derive(Debug, Clone, Args)]
42pub struct EnableArgs {
43 #[arg(value_name = "PROVIDER")]
44 pub provider: String,
45}
46
47#[derive(Debug, Clone, Args)]
48pub struct DisableArgs {
49 #[arg(value_name = "PROVIDER")]
50 pub provider: String,
51}
52
53pub fn execute(cmd: ProviderCommands, _config: &CliConfig) -> Result<()> {
54 match cmd {
55 ProviderCommands::List(_args) => {
56 let result = list_providers()?;
57 render_result(
58 &CommandResult::table(serde_json::to_value(result)?).with_title("AI Providers"),
59 );
60 },
61 ProviderCommands::Set(args) => {
62 let result = set_default_provider(&args.provider)?;
63 render_result(
64 &CommandResult::card(serde_json::to_value(result)?).with_title("Provider Updated"),
65 );
66 },
67 ProviderCommands::Enable(args) => {
68 let result = set_provider_enabled(&args.provider, true)?;
69 render_result(
70 &CommandResult::card(serde_json::to_value(result)?).with_title("Provider Enabled"),
71 );
72 },
73 ProviderCommands::Disable(args) => {
74 let result = set_provider_enabled(&args.provider, false)?;
75 render_result(
76 &CommandResult::card(serde_json::to_value(result)?).with_title("Provider Disabled"),
77 );
78 },
79 }
80 Ok(())
81}
82
83fn get_ai_config_path() -> Result<std::path::PathBuf> {
84 ConfigSection::Ai.file_path()
85}
86
87fn list_providers() -> Result<ProviderListOutput> {
88 let file_path = get_ai_config_path()?;
89 let content = read_yaml_file(&file_path)?;
90
91 let ai = content
92 .get("ai")
93 .ok_or_else(|| anyhow::anyhow!("Missing 'ai' section in config"))?;
94
95 let default_provider = ai
96 .get("default_provider")
97 .and_then(|v| v.as_str())
98 .unwrap_or("unknown")
99 .to_owned();
100
101 let providers_section = ai.get("providers");
102
103 let mut providers = Vec::new();
104
105 if let Some(serde_yaml::Value::Mapping(providers_map)) = providers_section {
106 for (name, config) in providers_map {
107 let name_str = name.as_str().unwrap_or("unknown").to_owned();
108
109 let enabled = config
110 .get("enabled")
111 .and_then(serde_yaml::Value::as_bool)
112 .unwrap_or(true);
113
114 let model = config
115 .get("default_model")
116 .and_then(|v| v.as_str())
117 .unwrap_or("unknown")
118 .to_owned();
119
120 let endpoint = config
121 .get("endpoint")
122 .and_then(|v| v.as_str())
123 .map(String::from);
124
125 providers.push(ProviderInfo {
126 name: name_str.clone(),
127 enabled,
128 is_default: name_str == default_provider,
129 model,
130 endpoint,
131 });
132 }
133 }
134
135 Ok(ProviderListOutput {
136 providers,
137 default_provider,
138 })
139}
140
141fn set_default_provider(provider: &str) -> Result<ProviderSetOutput> {
142 let file_path = get_ai_config_path()?;
143 let mut content = read_yaml_file(&file_path)?;
144
145 let providers = content
146 .get("ai")
147 .and_then(|ai| ai.get("providers"))
148 .ok_or_else(|| anyhow::anyhow!("Missing providers section"))?;
149
150 if !providers
151 .as_mapping()
152 .is_some_and(|m| m.contains_key(serde_yaml::Value::String(provider.to_owned())))
153 {
154 let available: Vec<String> = providers.as_mapping().map_or_else(Vec::new, |m| {
155 m.keys()
156 .filter_map(|k| k.as_str().map(String::from))
157 .collect()
158 });
159 anyhow::bail!(
160 "Unknown provider: '{}'. Available providers: {:?}",
161 provider,
162 available
163 );
164 }
165
166 if let Some(serde_yaml::Value::Mapping(ai_map)) = content.get_mut("ai") {
167 ai_map.insert(
168 serde_yaml::Value::String("default_provider".to_owned()),
169 serde_yaml::Value::String(provider.to_owned()),
170 );
171 }
172
173 write_yaml_file(&file_path, &content)?;
174
175 Ok(ProviderSetOutput {
176 provider: provider.to_owned(),
177 action: "set_default".to_owned(),
178 message: format!("Default provider set to '{}'", provider),
179 })
180}
181
182fn set_provider_enabled(provider: &str, enabled: bool) -> Result<ProviderSetOutput> {
183 let file_path = get_ai_config_path()?;
184 let mut content = read_yaml_file(&file_path)?;
185
186 let ai = content
187 .get_mut("ai")
188 .ok_or_else(|| anyhow::anyhow!("Missing 'ai' section"))?;
189
190 let providers = ai
191 .get_mut("providers")
192 .ok_or_else(|| anyhow::anyhow!("Missing 'providers' section"))?;
193
194 let provider_config = providers
195 .get_mut(provider)
196 .ok_or_else(|| anyhow::anyhow!("Unknown provider: '{}'", provider))?;
197
198 if let serde_yaml::Value::Mapping(config_map) = provider_config {
199 config_map.insert(
200 serde_yaml::Value::String("enabled".to_owned()),
201 serde_yaml::Value::Bool(enabled),
202 );
203 }
204
205 write_yaml_file(&file_path, &content)?;
206
207 let action = if enabled { "enabled" } else { "disabled" };
208
209 Ok(ProviderSetOutput {
210 provider: provider.to_owned(),
211 action: action.to_owned(),
212 message: format!("Provider '{}' {}", provider, action),
213 })
214}