opencode_provider_manager/app/
actions.rs1use super::error::Result;
4use super::state::AppState;
5use crate::config_core::{ConfigLayer, OpenCodeConfig, ProviderConfig};
6use std::collections::HashMap;
7
8impl AppState {
10 pub fn add_provider(
12 &mut self,
13 provider_id: String,
14 config: ProviderConfig,
15 layer: ConfigLayer,
16 ) -> Result<()> {
17 let target_config = self.config_for_layer_mut(layer)?;
18 target_config
19 .provider
20 .get_or_insert_with(HashMap::new)
21 .insert(provider_id.clone(), config);
22 self.recompute_merged();
23 self.mark_dirty();
24 Ok(())
25 }
26
27 pub fn remove_provider(&mut self, provider_id: &str, layer: ConfigLayer) -> Result<()> {
29 let target_config = self.config_for_layer_mut(layer)?;
30 if let Some(ref mut providers) = target_config.provider {
31 providers.remove(provider_id);
32 }
33 self.recompute_merged();
34 self.mark_dirty();
35 Ok(())
36 }
37
38 pub fn edit_provider_field(
40 &mut self,
41 provider_id: &str,
42 field: &str,
43 value: serde_json::Value,
44 layer: ConfigLayer,
45 ) -> Result<()> {
46 let target_config = self.config_for_layer_mut(layer)?;
47 if let Some(ref mut providers) = target_config.provider {
48 if let Some(provider) = providers.get_mut(provider_id) {
49 match field {
50 "name" => {
51 if let serde_json::Value::String(s) = value {
52 provider.name = Some(s);
53 }
54 }
55 "npm" => {
56 if let serde_json::Value::String(s) = value {
57 provider.npm = Some(s);
58 }
59 }
60 _ => {
61 provider
63 .options
64 .get_or_insert_with(HashMap::new)
65 .insert(field.to_string(), value);
66 }
67 }
68 }
69 }
70 self.recompute_merged();
71 self.mark_dirty();
72 Ok(())
73 }
74
75 pub fn add_model(
77 &mut self,
78 provider_id: &str,
79 model_id: String,
80 model_config: crate::config_core::ModelConfig,
81 layer: ConfigLayer,
82 ) -> Result<()> {
83 let target_config = self.config_for_layer_mut(layer)?;
84 if let Some(ref mut providers) = target_config.provider {
85 if let Some(provider) = providers.get_mut(provider_id) {
86 provider
87 .models
88 .get_or_insert_with(HashMap::new)
89 .insert(model_id, model_config);
90 }
91 }
92 self.recompute_merged();
93 self.mark_dirty();
94 Ok(())
95 }
96
97 pub fn remove_model(
99 &mut self,
100 provider_id: &str,
101 model_id: &str,
102 layer: ConfigLayer,
103 ) -> Result<()> {
104 let target_config = self.config_for_layer_mut(layer)?;
105 if let Some(ref mut providers) = target_config.provider {
106 if let Some(provider) = providers.get_mut(provider_id) {
107 if let Some(ref mut models) = provider.models {
108 models.remove(model_id);
109 }
110 }
111 }
112 self.recompute_merged();
113 self.mark_dirty();
114 Ok(())
115 }
116
117 pub fn save(&mut self, layer: ConfigLayer) -> Result<()> {
123 let path_buf = match layer {
126 ConfigLayer::Project => match self.paths.project.clone() {
127 Some(p) => p,
128 None => {
129 let cwd = std::env::current_dir().map_err(|e| {
130 super::error::AppError::State(format!("Cannot read cwd: {e}"))
131 })?;
132 let fallback = cwd.join("opencode.json");
133 self.paths.project = Some(fallback.clone());
134 fallback
135 }
136 },
137 other => self.paths.path_for_layer(other).cloned().ok_or_else(|| {
138 super::error::AppError::State(format!("No config path for layer {other:?}"))
139 })?,
140 };
141
142 let config = self.config_for_layer(layer)?;
143 crate::config_core::jsonc::write_config(config, &path_buf)?;
144 self.dirty = false;
145 Ok(())
146 }
147
148 fn config_for_layer(&self, layer: ConfigLayer) -> Result<&OpenCodeConfig> {
151 match layer {
152 ConfigLayer::Global => self.global_config.as_ref().ok_or_else(|| {
153 super::error::AppError::State("No global config loaded".to_string())
154 }),
155 ConfigLayer::Project => self.project_config.as_ref().ok_or_else(|| {
156 super::error::AppError::State("No project config loaded".to_string())
157 }),
158 ConfigLayer::Custom => self.custom_config.as_ref().ok_or_else(|| {
159 super::error::AppError::State("No custom config loaded".to_string())
160 }),
161 }
162 }
163
164 fn config_for_layer_mut(&mut self, layer: ConfigLayer) -> Result<&mut OpenCodeConfig> {
165 match layer {
166 ConfigLayer::Global => {
167 if self.global_config.is_none() {
168 self.global_config = Some(OpenCodeConfig::default());
169 }
170 Ok(self.global_config.as_mut().unwrap())
171 }
172 ConfigLayer::Project => {
173 if self.project_config.is_none() {
174 self.project_config = Some(OpenCodeConfig::default());
175 }
176 Ok(self.project_config.as_mut().unwrap())
177 }
178 ConfigLayer::Custom => {
179 if self.custom_config.is_none() {
180 self.custom_config = Some(OpenCodeConfig::default());
181 }
182 Ok(self.custom_config.as_mut().unwrap())
183 }
184 }
185 }
186
187 pub fn recompute_merged(&mut self) {
188 let mut configs = Vec::new();
189 if let Some(global) = &self.global_config {
190 configs.push(global.clone());
191 }
192 if let Some(custom) = &self.custom_config {
193 configs.push(custom.clone());
194 }
195 if let Some(project) = &self.project_config {
196 configs.push(project.clone());
197 }
198 self.merged_config = crate::config_core::merge_configs(&configs);
199 }
200}