fix(gemini): persist settings json edits
This commit is contained in:
@@ -236,6 +236,17 @@ pub fn validate_gemini_settings(settings: &Value) -> Result<(), AppError> {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有 config 字段,验证它是对象或 null
|
||||
if let Some(config) = settings.get("config") {
|
||||
if !(config.is_object() || config.is_null()) {
|
||||
return Err(AppError::localized(
|
||||
"gemini.validation.invalid_config",
|
||||
"Gemini 配置格式错误: config 必须是对象",
|
||||
"Gemini config invalid: config must be an object",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -244,6 +255,9 @@ pub fn validate_gemini_settings(settings: &Value) -> Result<(), AppError> {
|
||||
/// 此函数在切换供应商时使用,确保配置包含所有必需的字段。
|
||||
/// 对于需要 API Key 的供应商(如 PackyCode),会验证 GEMINI_API_KEY 字段。
|
||||
pub fn validate_gemini_settings_strict(settings: &Value) -> Result<(), AppError> {
|
||||
// 先做基础格式验证(包含 env/config 类型)
|
||||
validate_gemini_settings(settings)?;
|
||||
|
||||
let env_map = json_to_env(settings)?;
|
||||
|
||||
// 如果 env 为空,表示使用 OAuth(如 Google 官方),跳过验证
|
||||
|
||||
@@ -229,42 +229,22 @@ impl ConfigService {
|
||||
provider_id: &str,
|
||||
provider: &Provider,
|
||||
) -> Result<(), AppError> {
|
||||
use crate::gemini_config::{
|
||||
env_to_json, json_to_env, read_gemini_env, write_gemini_env_atomic,
|
||||
};
|
||||
use crate::gemini_config::{env_to_json, read_gemini_env};
|
||||
|
||||
let env_path = crate::gemini_config::get_gemini_env_path();
|
||||
if let Some(parent) = env_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| AppError::io(parent, e))?;
|
||||
}
|
||||
ProviderService::write_gemini_live(provider)?;
|
||||
|
||||
// 转换 JSON 配置为 .env 格式
|
||||
let env_map = json_to_env(&provider.settings_config)?;
|
||||
|
||||
// Google 官方(OAuth): env 为空,写入空文件并设置安全标志后返回
|
||||
if env_map.is_empty() {
|
||||
write_gemini_env_atomic(&env_map)?;
|
||||
ProviderService::ensure_google_oauth_security_flag(provider)?;
|
||||
|
||||
let live_after_env = read_gemini_env()?;
|
||||
let live_after = env_to_json(&live_after_env);
|
||||
|
||||
if let Some(manager) = config.get_manager_mut(&AppType::Gemini) {
|
||||
if let Some(target) = manager.providers.get_mut(provider_id) {
|
||||
target.settings_config = live_after;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 非 OAuth:按常规写入,并在必要时设置 Packycode 安全标志
|
||||
write_gemini_env_atomic(&env_map)?;
|
||||
ProviderService::ensure_packycode_security_flag(provider)?;
|
||||
|
||||
// 读回实际写入的内容并更新到配置中
|
||||
// 读回实际写入的内容并更新到配置中(包含 settings.json)
|
||||
let live_after_env = read_gemini_env()?;
|
||||
let live_after = env_to_json(&live_after_env);
|
||||
let settings_path = crate::gemini_config::get_gemini_settings_path();
|
||||
let live_after_config = if settings_path.exists() {
|
||||
crate::config::read_json_file(&settings_path)?
|
||||
} else {
|
||||
serde_json::json!({})
|
||||
};
|
||||
let mut live_after = env_to_json(&live_after_env);
|
||||
if let Some(obj) = live_after.as_object_mut() {
|
||||
obj.insert("config".to_string(), live_after_config);
|
||||
}
|
||||
|
||||
if let Some(manager) = config.get_manager_mut(&AppType::Gemini) {
|
||||
if let Some(target) = manager.providers.get_mut(provider_id) {
|
||||
|
||||
@@ -30,6 +30,7 @@ enum LiveSnapshot {
|
||||
},
|
||||
Gemini {
|
||||
env: Option<HashMap<String, String>>, // 新增
|
||||
config: Option<Value>, // 新增:settings.json 内容
|
||||
},
|
||||
}
|
||||
|
||||
@@ -68,15 +69,30 @@ impl LiveSnapshot {
|
||||
delete_file(&config_path)?;
|
||||
}
|
||||
}
|
||||
LiveSnapshot::Gemini { env } => {
|
||||
LiveSnapshot::Gemini { env, .. } => {
|
||||
// 新增
|
||||
use crate::gemini_config::{get_gemini_env_path, write_gemini_env_atomic};
|
||||
use crate::gemini_config::{
|
||||
get_gemini_env_path, get_gemini_settings_path, write_gemini_env_atomic,
|
||||
};
|
||||
let path = get_gemini_env_path();
|
||||
if let Some(env_map) = env {
|
||||
write_gemini_env_atomic(env_map)?;
|
||||
} else if path.exists() {
|
||||
delete_file(&path)?;
|
||||
}
|
||||
|
||||
let settings_path = get_gemini_settings_path();
|
||||
match self {
|
||||
LiveSnapshot::Gemini {
|
||||
config: Some(cfg), ..
|
||||
} => {
|
||||
write_json_file(&settings_path, cfg)?;
|
||||
}
|
||||
LiveSnapshot::Gemini { config: None, .. } if settings_path.exists() => {
|
||||
delete_file(&settings_path)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -612,7 +628,9 @@ impl ProviderService {
|
||||
state.save()?;
|
||||
}
|
||||
AppType::Gemini => {
|
||||
use crate::gemini_config::{env_to_json, get_gemini_env_path, read_gemini_env};
|
||||
use crate::gemini_config::{
|
||||
env_to_json, get_gemini_env_path, get_gemini_settings_path, read_gemini_env,
|
||||
};
|
||||
|
||||
let env_path = get_gemini_env_path();
|
||||
if !env_path.exists() {
|
||||
@@ -623,7 +641,18 @@ impl ProviderService {
|
||||
));
|
||||
}
|
||||
let env_map = read_gemini_env()?;
|
||||
let live_after = env_to_json(&env_map);
|
||||
let mut live_after = env_to_json(&env_map);
|
||||
|
||||
let settings_path = get_gemini_settings_path();
|
||||
let config_value = if settings_path.exists() {
|
||||
read_json_file(&settings_path)?
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
|
||||
if let Some(obj) = live_after.as_object_mut() {
|
||||
obj.insert("config".to_string(), config_value);
|
||||
}
|
||||
|
||||
{
|
||||
let mut guard = state.config.write().map_err(AppError::from)?;
|
||||
@@ -670,14 +699,22 @@ impl ProviderService {
|
||||
}
|
||||
AppType::Gemini => {
|
||||
// 新增
|
||||
use crate::gemini_config::{get_gemini_env_path, read_gemini_env};
|
||||
use crate::gemini_config::{
|
||||
get_gemini_env_path, get_gemini_settings_path, read_gemini_env,
|
||||
};
|
||||
let path = get_gemini_env_path();
|
||||
let env = if path.exists() {
|
||||
Some(read_gemini_env()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(LiveSnapshot::Gemini { env })
|
||||
let settings_path = get_gemini_settings_path();
|
||||
let config = if settings_path.exists() {
|
||||
Some(read_json_file(&settings_path)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(LiveSnapshot::Gemini { env, config })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1461,7 +1498,9 @@ impl ProviderService {
|
||||
config: &mut MultiAppConfig,
|
||||
next_provider: &str,
|
||||
) -> Result<(), AppError> {
|
||||
use crate::gemini_config::{env_to_json, get_gemini_env_path, read_gemini_env};
|
||||
use crate::gemini_config::{
|
||||
env_to_json, get_gemini_env_path, get_gemini_settings_path, read_gemini_env,
|
||||
};
|
||||
|
||||
let env_path = get_gemini_env_path();
|
||||
if !env_path.exists() {
|
||||
@@ -1477,7 +1516,18 @@ impl ProviderService {
|
||||
}
|
||||
|
||||
let env_map = read_gemini_env()?;
|
||||
let live = env_to_json(&env_map);
|
||||
let mut live = env_to_json(&env_map);
|
||||
|
||||
let settings_path = get_gemini_settings_path();
|
||||
let config_value = if settings_path.exists() {
|
||||
read_json_file(&settings_path)?
|
||||
} else {
|
||||
json!({})
|
||||
};
|
||||
if let Some(obj) = live.as_object_mut() {
|
||||
obj.insert("config".to_string(), config_value);
|
||||
}
|
||||
|
||||
if let Some(manager) = config.get_manager_mut(&AppType::Gemini) {
|
||||
if let Some(current) = manager.providers.get_mut(¤t_id) {
|
||||
current.settings_config = live;
|
||||
@@ -1495,36 +1545,71 @@ impl ProviderService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_gemini_live(provider: &Provider) -> Result<(), AppError> {
|
||||
pub(crate) fn write_gemini_live(provider: &Provider) -> Result<(), AppError> {
|
||||
use crate::gemini_config::{
|
||||
json_to_env, validate_gemini_settings_strict, write_gemini_env_atomic,
|
||||
get_gemini_settings_path, json_to_env, validate_gemini_settings_strict,
|
||||
write_gemini_env_atomic,
|
||||
};
|
||||
|
||||
// 一次性检测认证类型,避免重复检测
|
||||
let auth_type = Self::detect_gemini_auth_type(provider);
|
||||
|
||||
let mut env_map = json_to_env(&provider.settings_config)?;
|
||||
|
||||
// 准备要写入 ~/.gemini/settings.json 的配置(缺省时保留现有文件内容)
|
||||
let mut config_to_write = if let Some(config_value) = provider.settings_config.get("config")
|
||||
{
|
||||
if config_value.is_null() {
|
||||
Some(json!({}))
|
||||
} else if config_value.is_object() {
|
||||
Some(config_value.clone())
|
||||
} else {
|
||||
return Err(AppError::localized(
|
||||
"gemini.validation.invalid_config",
|
||||
"Gemini 配置格式错误: config 必须是对象或 null",
|
||||
"Gemini config invalid: config must be an object or null",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if config_to_write.is_none() {
|
||||
let settings_path = get_gemini_settings_path();
|
||||
if settings_path.exists() {
|
||||
config_to_write = Some(read_json_file(&settings_path)?);
|
||||
}
|
||||
}
|
||||
|
||||
match auth_type {
|
||||
GeminiAuthType::GoogleOfficial => {
|
||||
// Google 官方使用 OAuth,清空 env
|
||||
let empty_env = std::collections::HashMap::new();
|
||||
write_gemini_env_atomic(&empty_env)?;
|
||||
Self::ensure_google_oauth_security_flag(provider)?;
|
||||
env_map.clear();
|
||||
write_gemini_env_atomic(&env_map)?;
|
||||
}
|
||||
GeminiAuthType::Packycode => {
|
||||
// PackyCode 供应商,使用 API Key(切换时严格验证)
|
||||
validate_gemini_settings_strict(&provider.settings_config)?;
|
||||
let env_map = json_to_env(&provider.settings_config)?;
|
||||
write_gemini_env_atomic(&env_map)?;
|
||||
Self::ensure_packycode_security_flag(provider)?;
|
||||
}
|
||||
GeminiAuthType::Generic => {
|
||||
// 通用供应商,使用 API Key(切换时严格验证)
|
||||
validate_gemini_settings_strict(&provider.settings_config)?;
|
||||
let env_map = json_to_env(&provider.settings_config)?;
|
||||
write_gemini_env_atomic(&env_map)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(config_value) = config_to_write {
|
||||
let settings_path = get_gemini_settings_path();
|
||||
write_json_file(&settings_path, &config_value)?;
|
||||
}
|
||||
|
||||
match auth_type {
|
||||
GeminiAuthType::GoogleOfficial => Self::ensure_google_oauth_security_flag(provider)?,
|
||||
GeminiAuthType::Packycode => Self::ensure_packycode_security_flag(provider)?,
|
||||
GeminiAuthType::Generic => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user