use crate::config::write_json_file;
use crate::error::AppError;
use crate::provider::OpenCodeProviderConfig;
use crate::settings::get_opencode_override_dir;
use indexmap::IndexMap;
use serde_json::{json, Map, Value};
use std::path::PathBuf;
pub fn get_opencode_dir() -> PathBuf {
if let Some(override_dir) = get_opencode_override_dir() {
return override_dir;
}
dirs::home_dir()
.map(|home| home.join(".config").join("opencode"))
.unwrap_or_else(|| PathBuf::from(".config").join("opencode"))
}
pub fn get_opencode_config_path() -> PathBuf {
get_opencode_dir().join("opencode.json")
}
pub fn read_opencode_config() -> Result<Value, AppError> {
let path = get_opencode_config_path();
if !path.exists() {
return Ok(json!({ "$schema": "https://opencode.ai/config.json" }));
}
let content = std::fs::read_to_string(&path).map_err(|e| AppError::io(&path, e))?;
serde_json::from_str(&content).map_err(|e| AppError::json(&path, e))
}
pub fn write_opencode_config(config: &Value) -> Result<(), AppError> {
let path = get_opencode_config_path();
write_json_file(&path, config)
}
pub fn get_providers() -> Result<Map<String, Value>, AppError> {
let config = read_opencode_config()?;
Ok(config
.get("provider")
.and_then(Value::as_object)
.cloned()
.unwrap_or_default())
}
pub fn set_provider(id: &str, provider: Value) -> Result<(), AppError> {
let mut full_config = read_opencode_config()?;
if full_config.get("provider").is_none() {
full_config["provider"] = json!({});
}
if let Some(providers) = full_config
.get_mut("provider")
.and_then(Value::as_object_mut)
{
providers.insert(id.to_string(), provider);
}
write_opencode_config(&full_config)
}
pub fn remove_provider(id: &str) -> Result<(), AppError> {
let mut full_config = read_opencode_config()?;
if let Some(providers) = full_config
.get_mut("provider")
.and_then(Value::as_object_mut)
{
providers.remove(id);
}
write_opencode_config(&full_config)
}
pub fn get_typed_providers() -> Result<IndexMap<String, OpenCodeProviderConfig>, AppError> {
let mut result = IndexMap::new();
for (id, value) in get_providers()? {
match serde_json::from_value::<OpenCodeProviderConfig>(value) {
Ok(config) => {
result.insert(id, config);
}
Err(err) => {
log::warn!("Failed to parse OpenCode provider '{id}': {err}");
}
}
}
Ok(result)
}
pub fn set_typed_provider(id: &str, config: &OpenCodeProviderConfig) -> Result<(), AppError> {
let value =
serde_json::to_value(config).map_err(|source| AppError::JsonSerialize { source })?;
set_provider(id, value)
}
pub fn get_mcp_servers() -> Result<Map<String, Value>, AppError> {
let config = read_opencode_config()?;
Ok(config
.get("mcp")
.and_then(Value::as_object)
.cloned()
.unwrap_or_default())
}
pub fn set_mcp_server(id: &str, server: Value) -> Result<(), AppError> {
let mut full_config = read_opencode_config()?;
if full_config.get("mcp").is_none() {
full_config["mcp"] = json!({});
}
if let Some(mcp) = full_config.get_mut("mcp").and_then(Value::as_object_mut) {
mcp.insert(id.to_string(), server);
}
write_opencode_config(&full_config)
}
pub fn remove_mcp_server(id: &str) -> Result<(), AppError> {
let mut config = read_opencode_config()?;
if let Some(mcp) = config.get_mut("mcp").and_then(Value::as_object_mut) {
mcp.remove(id);
}
write_opencode_config(&config)
}