mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-06-16 13:34:04 +08:00
aa0f191420
OpenCode uses ~/.config/opencode on all platforms, not %APPDATA%\opencode on Windows. Remove the platform-specific path handling to use unified ~/.config/opencode path across all operating systems.
211 lines
6.4 KiB
Rust
211 lines
6.4 KiB
Rust
//! OpenCode 配置文件读写模块
|
|
//!
|
|
//! 处理 `~/.config/opencode/opencode.json` 配置文件的读写操作。
|
|
//! OpenCode 使用累加式供应商管理,所有供应商配置共存于同一配置文件中。
|
|
//!
|
|
//! ## 配置文件格式
|
|
//!
|
|
//! ```json
|
|
//! {
|
|
//! "$schema": "https://opencode.ai/config.json",
|
|
//! "provider": {
|
|
//! "my-provider": {
|
|
//! "npm": "@ai-sdk/openai-compatible",
|
|
//! "options": { "baseURL": "...", "apiKey": "{env:API_KEY}" },
|
|
//! "models": { "gpt-4o": { "name": "GPT-4o" } }
|
|
//! }
|
|
//! },
|
|
//! "mcp": {
|
|
//! "my-server": { "type": "local", "command": ["..."] }
|
|
//! }
|
|
//! }
|
|
//! ```
|
|
|
|
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;
|
|
|
|
// ============================================================================
|
|
// Path Functions
|
|
// ============================================================================
|
|
|
|
/// 获取 OpenCode 配置目录
|
|
///
|
|
/// 默认路径: `~/.config/opencode/`
|
|
/// 可通过 settings.opencode_config_dir 覆盖
|
|
pub fn get_opencode_dir() -> PathBuf {
|
|
if let Some(override_dir) = get_opencode_override_dir() {
|
|
return override_dir;
|
|
}
|
|
|
|
// 所有平台统一使用 ~/.config/opencode
|
|
dirs::home_dir()
|
|
.map(|h| h.join(".config").join("opencode"))
|
|
.unwrap_or_else(|| PathBuf::from(".config").join("opencode"))
|
|
}
|
|
|
|
/// 获取 OpenCode 配置文件路径
|
|
///
|
|
/// 返回 `~/.config/opencode/opencode.json`
|
|
pub fn get_opencode_config_path() -> PathBuf {
|
|
get_opencode_dir().join("opencode.json")
|
|
}
|
|
|
|
/// 获取 OpenCode 环境变量文件路径(如果存在)
|
|
///
|
|
/// 返回 `~/.config/opencode/.env`
|
|
#[allow(dead_code)]
|
|
pub fn get_opencode_env_path() -> PathBuf {
|
|
get_opencode_dir().join(".env")
|
|
}
|
|
|
|
// ============================================================================
|
|
// Core Read/Write Functions
|
|
// ============================================================================
|
|
|
|
/// 读取 OpenCode 配置文件
|
|
///
|
|
/// 返回完整的配置 JSON 对象
|
|
pub fn read_opencode_config() -> Result<Value, AppError> {
|
|
let path = get_opencode_config_path();
|
|
|
|
if !path.exists() {
|
|
// Return empty config with schema
|
|
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))
|
|
}
|
|
|
|
/// 写入 OpenCode 配置文件(原子写入)
|
|
///
|
|
/// 使用临时文件 + 重命名确保原子性
|
|
pub fn write_opencode_config(config: &Value) -> Result<(), AppError> {
|
|
let path = get_opencode_config_path();
|
|
// 复用统一的原子写入逻辑(兼容 Windows 上目标文件已存在的情况)
|
|
write_json_file(&path, config)?;
|
|
|
|
log::debug!("OpenCode config written to {path:?}");
|
|
Ok(())
|
|
}
|
|
|
|
// ============================================================================
|
|
// Provider Functions (Untyped - for raw JSON operations)
|
|
// ============================================================================
|
|
|
|
/// 获取所有供应商配置(原始 JSON)
|
|
pub fn get_providers() -> Result<Map<String, Value>, AppError> {
|
|
let config = read_opencode_config()?;
|
|
Ok(config
|
|
.get("provider")
|
|
.and_then(|v| v.as_object())
|
|
.cloned()
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
/// 设置供应商配置(原始 JSON)
|
|
pub fn set_provider(id: &str, config: 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(|v| v.as_object_mut())
|
|
{
|
|
providers.insert(id.to_string(), config);
|
|
}
|
|
|
|
write_opencode_config(&full_config)
|
|
}
|
|
|
|
/// 删除供应商配置
|
|
pub fn remove_provider(id: &str) -> Result<(), AppError> {
|
|
let mut config = read_opencode_config()?;
|
|
|
|
if let Some(providers) = config.get_mut("provider").and_then(|v| v.as_object_mut()) {
|
|
providers.remove(id);
|
|
}
|
|
|
|
write_opencode_config(&config)
|
|
}
|
|
|
|
// ============================================================================
|
|
// Provider Functions (Typed - using OpenCodeProviderConfig)
|
|
// ============================================================================
|
|
|
|
/// 获取所有供应商配置(类型化)
|
|
pub fn get_typed_providers() -> Result<IndexMap<String, OpenCodeProviderConfig>, AppError> {
|
|
let providers = get_providers()?;
|
|
let mut result = IndexMap::new();
|
|
|
|
for (id, value) in providers {
|
|
match serde_json::from_value::<OpenCodeProviderConfig>(value.clone()) {
|
|
Ok(config) => {
|
|
result.insert(id, config);
|
|
}
|
|
Err(e) => {
|
|
log::warn!("Failed to parse provider '{id}': {e}");
|
|
// Skip invalid providers but continue
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// 设置供应商配置(类型化)
|
|
pub fn set_typed_provider(id: &str, config: &OpenCodeProviderConfig) -> Result<(), AppError> {
|
|
let value = serde_json::to_value(config).map_err(|e| AppError::JsonSerialize { source: e })?;
|
|
set_provider(id, value)
|
|
}
|
|
|
|
// ============================================================================
|
|
// MCP Functions
|
|
// ============================================================================
|
|
|
|
/// 获取所有 MCP 服务器配置
|
|
pub fn get_mcp_servers() -> Result<Map<String, Value>, AppError> {
|
|
let config = read_opencode_config()?;
|
|
Ok(config
|
|
.get("mcp")
|
|
.and_then(|v| v.as_object())
|
|
.cloned()
|
|
.unwrap_or_default())
|
|
}
|
|
|
|
/// 设置 MCP 服务器配置
|
|
pub fn set_mcp_server(id: &str, config: 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(|v| v.as_object_mut()) {
|
|
mcp.insert(id.to_string(), config);
|
|
}
|
|
|
|
write_opencode_config(&full_config)
|
|
}
|
|
|
|
/// 删除 MCP 服务器配置
|
|
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(|v| v.as_object_mut()) {
|
|
mcp.remove(id);
|
|
}
|
|
|
|
write_opencode_config(&config)
|
|
}
|