diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index bcdd7b7..5751265 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -87,12 +87,13 @@ fn active_provider_and_model(config: &Config) -> Option<(String, String)> { } fn build_active_config(config: &Config) -> Config { - let is_kimi_native = |p: &Provider| p.managed; - + // Only the provider explicitly marked as active is written to the agent's + // native config. This ensures Kimi Code / Pi follow Pi Switch's choice + // instead of falling back to a managed/native provider. let providers: IndexMap = config .providers .iter() - .filter(|(_, p)| is_kimi_native(p) || p.active) + .filter(|(_, p)| p.active) .map(|(k, p)| (k.clone(), p.clone())) .collect(); diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index 6ee7a1e..9db11f4 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -258,6 +258,7 @@ fn set_setting_tx(tx: &rusqlite::Transaction, key: &str, value: &str) -> DbResul fn provider_type_for_str(s: &str) -> ProviderType { match s { "anthropic" => ProviderType::Anthropic, + "openai" => ProviderType::Openai, "openai_responses" => ProviderType::OpenaiResponses, "google-genai" => ProviderType::GoogleGenai, "vertexai" => ProviderType::Vertexai, diff --git a/src-tauri/src/kimi_code_io.rs b/src-tauri/src/kimi_code_io.rs index 3d3e230..3ccf60a 100644 --- a/src-tauri/src/kimi_code_io.rs +++ b/src-tauri/src/kimi_code_io.rs @@ -16,9 +16,10 @@ use crate::models::{Agent, Config, Model, Provider, ProviderType}; pub type KimiResult = anyhow::Result; pub fn kimi_code_config_dir() -> PathBuf { - dirs::home_dir() - .map(|h| h.join(".kimi-code")) - .expect("failed to resolve home directory") + std::env::var_os("KIMI_CODE_HOME") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".kimi-code"))) + .expect("failed to resolve Kimi Code config directory") } pub fn kimi_code_config_path() -> PathBuf { @@ -101,6 +102,16 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config { || table.get("managed").and_then(|v| v.as_bool()).unwrap_or(false); let enabled = table.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true); + let env: IndexMap = table + .get("env") + .and_then(|v| v.as_table()) + .map(|t| { + t.iter() + .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string()))) + .collect() + }) + .unwrap_or_default(); + let raw_other = { let mut rest = table.clone(); rest.remove("type"); @@ -109,6 +120,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config { rest.remove("managed"); rest.remove("enabled"); rest.remove("oauth"); + rest.remove("env"); toml_value_to_json(&TomlValue::Table(rest)) }; @@ -119,7 +131,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config { provider_type, base_url: base_url.filter(|s| !s.is_empty()), api_key: api_key.filter(|s| !s.is_empty()), - env: IndexMap::new(), + env, note: None, official_url: None, managed, @@ -226,13 +238,11 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom .unwrap_or(TomlValue::String("".to_string())), ); - let is_kimi_native = |p: &Provider| p.managed; - let mut providers_table = Table::new(); for (name, provider) in &config.providers { - // Skip inactive custom providers when writing to Kimi Code config. - // Kimi native (managed) providers are always preserved. - if !is_kimi_native(provider) && !provider.active { + // Only write the active provider to Kimi Code config so the agent + // follows Pi Switch's selection. + if !provider.active { continue; } let mut pt = Table::new(); @@ -244,6 +254,13 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom pt.insert("api_key".to_string(), TomlValue::String(api_key)); } pt.insert("enabled".to_string(), TomlValue::Boolean(provider.enabled)); + if !provider.env.is_empty() { + let mut env_table = Table::new(); + for (k, v) in &provider.env { + env_table.insert(k.clone(), TomlValue::String(v.clone())); + } + pt.insert("env".to_string(), TomlValue::Table(env_table)); + } if provider.managed { // Preserve existing oauth config if present, otherwise create a default entry. let oauth = provider @@ -258,9 +275,10 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom }); pt.insert("oauth".to_string(), oauth); } - // Merge remaining raw fields (oauth is handled explicitly above). + // Merge remaining raw fields (oauth and env are handled explicitly above). if let TomlValue::Table(mut extra) = json_to_toml(&provider.raw_other).unwrap_or(TomlValue::Table(Table::new())) { extra.remove("oauth"); + extra.remove("env"); for (k, v) in extra { pt.insert(k, v); } @@ -273,7 +291,7 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom let active_provider_names: std::collections::HashSet<&str> = config .providers .values() - .filter(|p| is_kimi_native(p) || p.active) + .filter(|p| p.active) .map(|p| p.name.as_str()) .collect(); diff --git a/src-tauri/src/pi_io.rs b/src-tauri/src/pi_io.rs index 7cda484..8a277fc 100644 --- a/src-tauri/src/pi_io.rs +++ b/src-tauri/src/pi_io.rs @@ -17,9 +17,10 @@ pub type PiResult = anyhow::Result; /// Returns the default Pi agent config directory for the current user. pub fn pi_agent_dir() -> PathBuf { - dirs::home_dir() - .map(|h| h.join(".pi").join("agent")) - .expect("failed to resolve home directory") + std::env::var_os("PI_CODING_AGENT_DIR") + .map(PathBuf::from) + .or_else(|| dirs::home_dir().map(|h| h.join(".pi").join("agent"))) + .expect("failed to resolve Pi agent config directory") } /// Returns the path to Pi's `models.json` file. diff --git a/src/App.tsx b/src/App.tsx index 6aec3a6..9348c35 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -195,15 +195,14 @@ export default function App() { const handleSwitchProvider = async (name: string) => { updateConfig((cfg) => { const target = cfg.providers[name]; - if (!target || target.managed) return cfg; + if (!target) return cfg; - const isKimiNative = (p: Provider) => p.managed === true; - - // Mark the selected provider as active and deactivate other custom providers. - // Kimi native providers are left untouched. No provider/model records are deleted. + // Only the selected provider is active. All other providers (including + // Kimi native/managed and custom) are deactivated so the target agent + // follows Pi Switch's choice exactly. const providers: Record = {}; for (const [key, p] of Object.entries(cfg.providers)) { - providers[key] = { ...p, active: isKimiNative(p) ? undefined : key === name }; + providers[key] = { ...p, active: key === name }; } // Set default model to the first model of the selected provider. diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index e288aa6..2c09aa6 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -94,8 +94,7 @@ export function ProviderList({ const defaultModelName = defaultModel ? models[defaultModel]?.display_name || defaultModel : null; - const isKimiNative = provider.managed === true; - const isActive = !isKimiNative && provider.active === true; + const isActive = provider.active === true; return (