feat: unify provider active logic and add env field support for kimi_code_io

This commit is contained in:
KimiSwitch Dev
2026-07-09 08:44:11 +08:00
parent 3b12844342
commit b01a5661b4
6 changed files with 44 additions and 25 deletions
+4 -3
View File
@@ -87,12 +87,13 @@ fn active_provider_and_model(config: &Config) -> Option<(String, String)> {
} }
fn build_active_config(config: &Config) -> Config { 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<String, Provider> = config let providers: IndexMap<String, Provider> = config
.providers .providers
.iter() .iter()
.filter(|(_, p)| is_kimi_native(p) || p.active) .filter(|(_, p)| p.active)
.map(|(k, p)| (k.clone(), p.clone())) .map(|(k, p)| (k.clone(), p.clone()))
.collect(); .collect();
+1
View File
@@ -258,6 +258,7 @@ fn set_setting_tx(tx: &rusqlite::Transaction, key: &str, value: &str) -> DbResul
fn provider_type_for_str(s: &str) -> ProviderType { fn provider_type_for_str(s: &str) -> ProviderType {
match s { match s {
"anthropic" => ProviderType::Anthropic, "anthropic" => ProviderType::Anthropic,
"openai" => ProviderType::Openai,
"openai_responses" => ProviderType::OpenaiResponses, "openai_responses" => ProviderType::OpenaiResponses,
"google-genai" => ProviderType::GoogleGenai, "google-genai" => ProviderType::GoogleGenai,
"vertexai" => ProviderType::Vertexai, "vertexai" => ProviderType::Vertexai,
+29 -11
View File
@@ -16,9 +16,10 @@ use crate::models::{Agent, Config, Model, Provider, ProviderType};
pub type KimiResult<T> = anyhow::Result<T>; pub type KimiResult<T> = anyhow::Result<T>;
pub fn kimi_code_config_dir() -> PathBuf { pub fn kimi_code_config_dir() -> PathBuf {
dirs::home_dir() std::env::var_os("KIMI_CODE_HOME")
.map(|h| h.join(".kimi-code")) .map(PathBuf::from)
.expect("failed to resolve home directory") .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 { 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); || 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 enabled = table.get("enabled").and_then(|v| v.as_bool()).unwrap_or(true);
let env: IndexMap<String, String> = 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 raw_other = {
let mut rest = table.clone(); let mut rest = table.clone();
rest.remove("type"); rest.remove("type");
@@ -109,6 +120,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config {
rest.remove("managed"); rest.remove("managed");
rest.remove("enabled"); rest.remove("enabled");
rest.remove("oauth"); rest.remove("oauth");
rest.remove("env");
toml_value_to_json(&TomlValue::Table(rest)) toml_value_to_json(&TomlValue::Table(rest))
}; };
@@ -119,7 +131,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config {
provider_type, provider_type,
base_url: base_url.filter(|s| !s.is_empty()), base_url: base_url.filter(|s| !s.is_empty()),
api_key: api_key.filter(|s| !s.is_empty()), api_key: api_key.filter(|s| !s.is_empty()),
env: IndexMap::new(), env,
note: None, note: None,
official_url: None, official_url: None,
managed, managed,
@@ -226,13 +238,11 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom
.unwrap_or(TomlValue::String("".to_string())), .unwrap_or(TomlValue::String("".to_string())),
); );
let is_kimi_native = |p: &Provider| p.managed;
let mut providers_table = Table::new(); let mut providers_table = Table::new();
for (name, provider) in &config.providers { for (name, provider) in &config.providers {
// Skip inactive custom providers when writing to Kimi Code config. // Only write the active provider to Kimi Code config so the agent
// Kimi native (managed) providers are always preserved. // follows Pi Switch's selection.
if !is_kimi_native(provider) && !provider.active { if !provider.active {
continue; continue;
} }
let mut pt = Table::new(); 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("api_key".to_string(), TomlValue::String(api_key));
} }
pt.insert("enabled".to_string(), TomlValue::Boolean(provider.enabled)); 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 { if provider.managed {
// Preserve existing oauth config if present, otherwise create a default entry. // Preserve existing oauth config if present, otherwise create a default entry.
let oauth = provider 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); 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())) { if let TomlValue::Table(mut extra) = json_to_toml(&provider.raw_other).unwrap_or(TomlValue::Table(Table::new())) {
extra.remove("oauth"); extra.remove("oauth");
extra.remove("env");
for (k, v) in extra { for (k, v) in extra {
pt.insert(k, v); 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 let active_provider_names: std::collections::HashSet<&str> = config
.providers .providers
.values() .values()
.filter(|p| is_kimi_native(p) || p.active) .filter(|p| p.active)
.map(|p| p.name.as_str()) .map(|p| p.name.as_str())
.collect(); .collect();
+4 -3
View File
@@ -17,9 +17,10 @@ pub type PiResult<T> = anyhow::Result<T>;
/// Returns the default Pi agent config directory for the current user. /// Returns the default Pi agent config directory for the current user.
pub fn pi_agent_dir() -> PathBuf { pub fn pi_agent_dir() -> PathBuf {
dirs::home_dir() std::env::var_os("PI_CODING_AGENT_DIR")
.map(|h| h.join(".pi").join("agent")) .map(PathBuf::from)
.expect("failed to resolve home directory") .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. /// Returns the path to Pi's `models.json` file.
+5 -6
View File
@@ -195,15 +195,14 @@ export default function App() {
const handleSwitchProvider = async (name: string) => { const handleSwitchProvider = async (name: string) => {
updateConfig((cfg) => { updateConfig((cfg) => {
const target = cfg.providers[name]; const target = cfg.providers[name];
if (!target || target.managed) return cfg; if (!target) return cfg;
const isKimiNative = (p: Provider) => p.managed === true; // Only the selected provider is active. All other providers (including
// Kimi native/managed and custom) are deactivated so the target agent
// Mark the selected provider as active and deactivate other custom providers. // follows Pi Switch's choice exactly.
// Kimi native providers are left untouched. No provider/model records are deleted.
const providers: Record<string, Provider> = {}; const providers: Record<string, Provider> = {};
for (const [key, p] of Object.entries(cfg.providers)) { 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. // Set default model to the first model of the selected provider.
+1 -2
View File
@@ -94,8 +94,7 @@ export function ProviderList({
const defaultModelName = defaultModel const defaultModelName = defaultModel
? models[defaultModel]?.display_name || defaultModel ? models[defaultModel]?.display_name || defaultModel
: null; : null;
const isKimiNative = provider.managed === true; const isActive = provider.active === true;
const isActive = !isKimiNative && provider.active === true;
return ( return (
<div <div