feat: v0.2.0 - add system tray, clean up unused role field
- Replace text '+' icon with centered SVG plus icon for the add-provider button - Add system tray: minimize-to-tray and quit-from-tray menu (minimize hides window, close exits app) - Remove unused model role field (no functional effect, only stored/displayed) - Fix multiple providers showing as active in Kimi Code mode: only the default_model's provider is marked active - Copy /reload to clipboard after provider switch as a convenience hint - Bump version to 0.2.0
No files matched your search
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kimiswitch"
|
||||
version = "0.1.0"
|
||||
description = "Pi Switch - model config manager"
|
||||
version = "0.2.0"
|
||||
description = "Kimi Switch - model config manager"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -9,7 +9,7 @@ edition = "2021"
|
||||
tauri-build = { version = "2.0.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.0.0", features = [] }
|
||||
tauri = { version = "2.0.0", features = ["tray-icon"] }
|
||||
tauri-plugin-opener = "2.0.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 15 KiB |
@@ -18,7 +18,7 @@ pub fn debug_log(message: String) {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn load_agent_config_command(agent: Agent) -> Result<Config, String> {
|
||||
// Load from Pi Switch's own SQLite database first.
|
||||
// Load from Kimi Switch's own SQLite database first.
|
||||
match db::load_config(&agent) {
|
||||
Ok(config) if !config.providers.is_empty() => Ok(config),
|
||||
_ => {
|
||||
@@ -51,7 +51,7 @@ pub fn load_agent_config_command(agent: Agent) -> Result<Config, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn save_agent_config_command(agent: Agent, config: Config) -> Result<(), String> {
|
||||
// Save the full Pi Switch configuration to local SQLite.
|
||||
// Save the full Kimi Switch configuration to local SQLite.
|
||||
db::save_config(&agent, &config).map_err(fmt_anyhow)
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ fn active_provider_and_model(config: &Config) -> Option<(String, String)> {
|
||||
|
||||
fn build_active_config(config: &Config) -> Config {
|
||||
// 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
|
||||
// native config. This ensures Kimi Code / Pi follow Kimi Switch's choice
|
||||
// instead of falling back to a managed/native provider.
|
||||
let providers: IndexMap<String, Provider> = config
|
||||
.providers
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Pi Switch local SQLite storage.
|
||||
//! Kimi Switch local SQLite storage.
|
||||
//!
|
||||
//! This module stores the full Pi Switch configuration (all providers and models
|
||||
//! This module stores the full Kimi Switch configuration (all providers and models
|
||||
//! for both Kimi Code and Pi agents) in a local SQLite database. It is separate
|
||||
//! from the agent-specific config files that are written when the user activates
|
||||
//! a provider.
|
||||
@@ -16,17 +16,43 @@ use crate::models::{Agent, Config, Model, Provider, ProviderType};
|
||||
|
||||
pub type DbResult<T> = anyhow::Result<T>;
|
||||
|
||||
pub fn pi_switch_data_dir() -> PathBuf {
|
||||
pub fn kimi_switch_data_dir() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.map(|h| h.join(".pi-switch"))
|
||||
.map(|h| h.join(".kimi-switch"))
|
||||
.expect("failed to resolve home directory")
|
||||
}
|
||||
|
||||
pub fn db_path() -> PathBuf {
|
||||
pi_switch_data_dir().join("pi-switch.db")
|
||||
kimi_switch_data_dir().join("kimi-switch.db")
|
||||
}
|
||||
|
||||
/// One-time migration: if the legacy `~/.pi-switch/` data directory exists and
|
||||
/// `~/.kimi-switch/` does not, move it so existing users keep their saved
|
||||
/// configuration. Safe to call on every startup — it is a no-op once the new
|
||||
/// directory exists.
|
||||
fn migrate_legacy_data_dir() {
|
||||
let new_dir = kimi_switch_data_dir();
|
||||
if new_dir.exists() {
|
||||
return;
|
||||
}
|
||||
let old_dir = match dirs::home_dir() {
|
||||
Some(h) => h.join(".pi-switch"),
|
||||
None => return,
|
||||
};
|
||||
if !old_dir.exists() {
|
||||
return;
|
||||
}
|
||||
// Best-effort move; failures are silently ignored so the app can still start.
|
||||
if let Some(new_parent) = new_dir.parent() {
|
||||
if let Err(_) = std::fs::create_dir_all(new_parent) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let _ = std::fs::rename(&old_dir, &new_dir);
|
||||
}
|
||||
|
||||
pub fn init_db() -> DbResult<Connection> {
|
||||
migrate_legacy_data_dir();
|
||||
let path = db_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
@@ -64,7 +90,6 @@ pub fn init_db() -> DbResult<Connection> {
|
||||
model TEXT NOT NULL,
|
||||
max_context_size INTEGER NOT NULL,
|
||||
display_name TEXT,
|
||||
role TEXT,
|
||||
supports_1m INTEGER NOT NULL DEFAULT 0,
|
||||
capabilities TEXT,
|
||||
raw_other TEXT,
|
||||
@@ -128,20 +153,19 @@ pub fn load_config(agent: &Agent) -> DbResult<Config> {
|
||||
let mut models = IndexMap::new();
|
||||
{
|
||||
let mut stmt = tx.prepare(
|
||||
"SELECT alias, provider_name, model, max_context_size, display_name, role, supports_1m, capabilities, raw_other
|
||||
"SELECT alias, provider_name, model, max_context_size, display_name, supports_1m, capabilities, raw_other
|
||||
FROM models WHERE agent = ?1 ORDER BY id",
|
||||
)?;
|
||||
let model_rows = stmt.query_map(params![agent.as_str()], |row| {
|
||||
let caps_json: Option<String> = row.get(7)?;
|
||||
let raw_json: Option<String> = row.get(8)?;
|
||||
let caps_json: Option<String> = row.get(6)?;
|
||||
let raw_json: Option<String> = row.get(7)?;
|
||||
Ok(Model {
|
||||
alias: row.get(0)?,
|
||||
provider: row.get(1)?,
|
||||
model: row.get(2)?,
|
||||
max_context_size: row.get::<_, i64>(3)? as u64,
|
||||
display_name: row.get(4)?,
|
||||
role: row.get(5)?,
|
||||
supports_1m: row.get::<_, i32>(6)? != 0,
|
||||
supports_1m: row.get::<_, i32>(5)? != 0,
|
||||
capabilities: caps_json
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.unwrap_or_default(),
|
||||
@@ -202,8 +226,8 @@ pub fn save_config(agent: &Agent, config: &Config) -> DbResult<()> {
|
||||
{
|
||||
let mut insert_model = tx.prepare(
|
||||
"INSERT INTO models
|
||||
(agent, alias, provider_name, model, max_context_size, display_name, role, supports_1m, capabilities, raw_other)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||
(agent, alias, provider_name, model, max_context_size, display_name, supports_1m, capabilities, raw_other)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||
)?;
|
||||
|
||||
for model in config.models.values() {
|
||||
@@ -214,7 +238,6 @@ pub fn save_config(agent: &Agent, config: &Config) -> DbResult<()> {
|
||||
model.model,
|
||||
model.max_context_size as i64,
|
||||
model.display_name,
|
||||
model.role,
|
||||
model.supports_1m as i32,
|
||||
serde_json::to_string(&model.capabilities).ok(),
|
||||
serde_json::to_string(&model.raw_other).ok(),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Kimi Code stores its configuration in `~/.kimi-code/config.toml`.
|
||||
//! This module reads/writes that file and converts between Kimi's TOML
|
||||
//! format and Pi Switch's internal `Config`/`Provider`/`Model` types.
|
||||
//! format and Kimi Switch's internal `Config`/`Provider`/`Model` types.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -64,7 +64,7 @@ pub fn save_kimi_code_config(value: &TomlValue) -> KimiResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Import a Kimi Code TOML config into Pi Switch's internal `Config`.
|
||||
/// Import a Kimi Code TOML config into Kimi Switch's internal `Config`.
|
||||
pub fn kimi_code_to_config(value: &TomlValue) -> Config {
|
||||
let root = value.as_table().cloned().unwrap_or_default();
|
||||
|
||||
@@ -76,6 +76,72 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config {
|
||||
let mut providers = IndexMap::new();
|
||||
let mut models = IndexMap::new();
|
||||
|
||||
// Read models first so we can determine which provider the default_model
|
||||
// belongs to. Only that provider should be marked active in the UI.
|
||||
if let Some(models_table) = root.get("models").and_then(|v| v.as_table()) {
|
||||
for (alias, mv) in models_table {
|
||||
let table = mv.as_table().cloned().unwrap_or_default();
|
||||
let provider = table
|
||||
.get("provider")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
let model_id = table
|
||||
.get("model")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
let max_context_size = table
|
||||
.get("max_context_size")
|
||||
.and_then(|v| v.as_integer())
|
||||
.map(|n| n as u64)
|
||||
.unwrap_or(128_000);
|
||||
let display_name = table
|
||||
.get("display_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let capabilities: Vec<String> = table
|
||||
.get("capabilities")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let supports_1m = max_context_size >= 1_000_000;
|
||||
|
||||
let raw_other = {
|
||||
let mut rest = table.clone();
|
||||
rest.remove("provider");
|
||||
rest.remove("model");
|
||||
rest.remove("max_context_size");
|
||||
rest.remove("display_name");
|
||||
rest.remove("capabilities");
|
||||
toml_value_to_json(&TomlValue::Table(rest))
|
||||
};
|
||||
|
||||
models.insert(
|
||||
alias.clone(),
|
||||
Model {
|
||||
alias: alias.clone(),
|
||||
provider,
|
||||
model: model_id,
|
||||
max_context_size,
|
||||
display_name,
|
||||
supports_1m,
|
||||
capabilities,
|
||||
raw_other,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let active_provider_name = default_model
|
||||
.as_ref()
|
||||
.and_then(|alias| models.get(alias))
|
||||
.map(|m| m.provider.as_str());
|
||||
|
||||
if let Some(providers_table) = root.get("providers").and_then(|v| v.as_table()) {
|
||||
for (name, pv) in providers_table {
|
||||
let table = pv.as_table().cloned().unwrap_or_default();
|
||||
@@ -125,68 +191,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config {
|
||||
official_url: None,
|
||||
managed,
|
||||
enabled,
|
||||
active: true,
|
||||
raw_other,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(models_table) = root.get("models").and_then(|v| v.as_table()) {
|
||||
for (alias, mv) in models_table {
|
||||
let table = mv.as_table().cloned().unwrap_or_default();
|
||||
let provider = table
|
||||
.get("provider")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
let model_id = table
|
||||
.get("model")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
let max_context_size = table
|
||||
.get("max_context_size")
|
||||
.and_then(|v| v.as_integer())
|
||||
.map(|n| n as u64)
|
||||
.unwrap_or(128_000);
|
||||
let display_name = table
|
||||
.get("display_name")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let capabilities: Vec<String> = table
|
||||
.get("capabilities")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|arr| {
|
||||
arr.iter()
|
||||
.filter_map(|v| v.as_str().map(|s| s.to_string()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let supports_1m = capabilities.iter().any(|c| c == "thinking")
|
||||
|| max_context_size >= 1_000_000;
|
||||
|
||||
let raw_other = {
|
||||
let mut rest = table.clone();
|
||||
rest.remove("provider");
|
||||
rest.remove("model");
|
||||
rest.remove("max_context_size");
|
||||
rest.remove("display_name");
|
||||
rest.remove("capabilities");
|
||||
toml_value_to_json(&TomlValue::Table(rest))
|
||||
};
|
||||
|
||||
models.insert(
|
||||
alias.clone(),
|
||||
Model {
|
||||
alias: alias.clone(),
|
||||
provider,
|
||||
model: model_id,
|
||||
max_context_size,
|
||||
display_name,
|
||||
role: None,
|
||||
supports_1m,
|
||||
capabilities,
|
||||
active: active_provider_name == Some(name.as_str()),
|
||||
raw_other,
|
||||
},
|
||||
);
|
||||
@@ -209,7 +214,7 @@ pub fn kimi_code_to_config(value: &TomlValue) -> Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Export Pi Switch's internal `Config` to a Kimi Code TOML config value.
|
||||
/// Export Kimi Switch's internal `Config` to a Kimi Code TOML config value.
|
||||
///
|
||||
/// If `existing` is provided, unknown top-level sections (e.g. `services`) are
|
||||
/// preserved; otherwise a fresh TOML table is used.
|
||||
@@ -230,7 +235,7 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom
|
||||
let mut providers_table = Table::new();
|
||||
for (name, provider) in &config.providers {
|
||||
// Only write the active provider to Kimi Code config so the agent
|
||||
// follows Pi Switch's selection.
|
||||
// follows Kimi Switch's selection.
|
||||
if !provider.active {
|
||||
continue;
|
||||
}
|
||||
@@ -299,10 +304,7 @@ pub fn config_to_kimi_code(config: &Config, existing: Option<&TomlValue>) -> Tom
|
||||
if let Some(display_name) = model.display_name.clone().filter(|s| !s.is_empty()) {
|
||||
mt.insert("display_name".to_string(), TomlValue::String(display_name));
|
||||
}
|
||||
let mut capabilities = model.capabilities.clone();
|
||||
if model.supports_1m && !capabilities.iter().any(|c| c == "thinking") {
|
||||
capabilities.push("thinking".to_string());
|
||||
}
|
||||
let capabilities = model.capabilities.clone();
|
||||
if !capabilities.is_empty() {
|
||||
mt.insert(
|
||||
"capabilities".to_string(),
|
||||
@@ -461,7 +463,8 @@ api_key = ""
|
||||
assert_eq!(model.model, "kimi-for-coding");
|
||||
assert_eq!(model.max_context_size, 262144);
|
||||
assert!(model.capabilities.contains(&"thinking".to_string()));
|
||||
assert!(model.supports_1m);
|
||||
// supports_1m reflects context size only, not thinking capability.
|
||||
assert!(!model.supports_1m);
|
||||
|
||||
// Unknown top-level sections are preserved.
|
||||
let services = config.raw_other.get("services");
|
||||
@@ -496,7 +499,6 @@ api_key = ""
|
||||
model: "glm-5.2".to_string(),
|
||||
max_context_size: 900_000,
|
||||
display_name: None,
|
||||
role: None,
|
||||
supports_1m: true,
|
||||
capabilities: vec!["thinking".to_string()],
|
||||
raw_other: Value::Null,
|
||||
@@ -527,4 +529,62 @@ api_key = ""
|
||||
let caps = model.get("capabilities").unwrap().as_array().unwrap();
|
||||
assert!(caps.iter().any(|v| v.as_str() == Some("thinking")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kimi_code_export_does_not_inject_thinking_from_supports_1m() {
|
||||
let mut providers = IndexMap::new();
|
||||
providers.insert(
|
||||
"test-provider".to_string(),
|
||||
Provider {
|
||||
name: "test-provider".to_string(),
|
||||
provider_type: ProviderType::Anthropic,
|
||||
base_url: Some("https://example.com".to_string()),
|
||||
api_key: Some("sk-test".to_string()),
|
||||
env: IndexMap::new(),
|
||||
note: None,
|
||||
official_url: None,
|
||||
managed: false,
|
||||
enabled: true,
|
||||
active: true,
|
||||
raw_other: Value::Null,
|
||||
},
|
||||
);
|
||||
let mut models = IndexMap::new();
|
||||
models.insert(
|
||||
"big-model".to_string(),
|
||||
Model {
|
||||
alias: "big-model".to_string(),
|
||||
provider: "test-provider".to_string(),
|
||||
model: "big-model".to_string(),
|
||||
max_context_size: 2_000_000,
|
||||
display_name: None,
|
||||
supports_1m: true,
|
||||
capabilities: vec![],
|
||||
raw_other: Value::Null,
|
||||
},
|
||||
);
|
||||
let config = Config {
|
||||
default_model: Some("big-model".to_string()),
|
||||
providers,
|
||||
models,
|
||||
raw_other: Value::Null,
|
||||
};
|
||||
|
||||
let exported = config_to_kimi_code(&config, None);
|
||||
let root = exported.as_table().unwrap();
|
||||
let models_table = root.get("models").unwrap().as_table().unwrap();
|
||||
let model = models_table.get("big-model").unwrap().as_table().unwrap();
|
||||
// supports_1m=true but capabilities empty → must NOT inject "thinking".
|
||||
let caps = model.get("capabilities");
|
||||
match caps {
|
||||
None => {}
|
||||
Some(v) => {
|
||||
let arr = v.as_array().unwrap();
|
||||
assert!(
|
||||
!arr.iter().any(|c| c.as_str() == Some("thinking")),
|
||||
"thinking should not be injected from supports_1m"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,82 @@ pub mod kimi_code_io;
|
||||
pub mod models;
|
||||
pub mod pi_io;
|
||||
|
||||
use tauri::menu::{Menu, MenuItem, PredefinedMenuItem};
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder};
|
||||
use tauri::Manager;
|
||||
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
println!("[Tauri] Setup started");
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
println!("[Tauri] Window label: {}", window.label());
|
||||
|
||||
// Tray menu: show / separator / quit
|
||||
let show_i = MenuItem::with_id(app, "show", "显示", true, None::<&str>)?;
|
||||
let quit_i = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?;
|
||||
let menu = Menu::with_items(
|
||||
app,
|
||||
&[&show_i, &PredefinedMenuItem::separator(app)?, &quit_i],
|
||||
)?;
|
||||
|
||||
// Tray icon: reuse the window icon
|
||||
let icon = app.default_window_icon().unwrap().clone();
|
||||
TrayIconBuilder::new()
|
||||
.icon(icon)
|
||||
.menu(&menu)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"show" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let tauri::tray::TrayIconEvent::Click {
|
||||
button,
|
||||
button_state,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
if button == MouseButton::Left && button_state == MouseButtonState::Up {
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
if window.is_visible().unwrap_or(true) {
|
||||
let _ = window.hide();
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.unminimize();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
// Minimize to tray: restore then hide so the taskbar button disappears
|
||||
let window_clone = window.clone();
|
||||
window.on_window_event(move |event| match event {
|
||||
tauri::WindowEvent::Resized(_) => {
|
||||
if window_clone.is_minimized().unwrap_or(false) {
|
||||
let _ = window_clone.unminimize();
|
||||
let _ = window_clone.hide();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::load_agent_config_command,
|
||||
commands::save_agent_config_command,
|
||||
|
||||
@@ -129,8 +129,6 @@ pub struct Model {
|
||||
pub model: String,
|
||||
pub max_context_size: u64,
|
||||
pub display_name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub role: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub supports_1m: bool,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
@@ -147,7 +145,6 @@ impl std::fmt::Debug for Model {
|
||||
.field("model", &self.model)
|
||||
.field("max_context_size", &self.max_context_size)
|
||||
.field("display_name", &self.display_name)
|
||||
.field("role", &self.role)
|
||||
.field("supports_1m", &self.supports_1m)
|
||||
.field("capabilities", &self.capabilities)
|
||||
.field("raw_other", &"<json>")
|
||||
@@ -162,7 +159,6 @@ impl PartialEq for Model {
|
||||
&& self.model == other.model
|
||||
&& self.max_context_size == other.max_context_size
|
||||
&& self.display_name == other.display_name
|
||||
&& self.role == other.role
|
||||
&& self.supports_1m == other.supports_1m
|
||||
&& self.capabilities == other.capabilities
|
||||
&& self.raw_other == other.raw_other
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//!
|
||||
//! Pi stores custom providers and models in `~/.pi/agent/models.json`.
|
||||
//! This module reads and writes that file and converts between Pi's JSON
|
||||
//! format and Pi Switch's internal `Config`/`Provider`/`Model` types.
|
||||
//! format and Kimi Switch's internal `Config`/`Provider`/`Model` types.
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -150,7 +150,7 @@ pub fn pi_settings_path() -> PathBuf {
|
||||
pi_agent_dir().join("settings.json")
|
||||
}
|
||||
|
||||
/// Pi's `settings.json` file (only the fields Pi Switch manipulates).
|
||||
/// Pi's `settings.json` file (only the fields Kimi Switch manipulates).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct PiSettingsFile {
|
||||
#[serde(rename = "defaultProvider", default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -197,7 +197,7 @@ pub fn save_pi_settings(file: &PiSettingsFile) -> PiResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert a Pi Switch provider type to the Pi `api` field value.
|
||||
/// Convert a Kimi Switch provider type to the Pi `api` field value.
|
||||
pub fn pi_api_for_provider(provider_type: &ProviderType) -> &'static str {
|
||||
match provider_type {
|
||||
ProviderType::Openai => "openai-completions",
|
||||
@@ -210,7 +210,7 @@ pub fn pi_api_for_provider(provider_type: &ProviderType) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a Pi `api` field value back to a Pi Switch provider type.
|
||||
/// Convert a Pi `api` field value back to a Kimi Switch provider type.
|
||||
pub fn provider_type_for_pi_api(api: &str) -> ProviderType {
|
||||
match api {
|
||||
"openai-responses" => ProviderType::OpenaiResponses,
|
||||
@@ -222,7 +222,7 @@ pub fn provider_type_for_pi_api(api: &str) -> ProviderType {
|
||||
}
|
||||
|
||||
/// Merge provider-level fields that have dedicated `PiProvider` fields into the
|
||||
/// `raw_other` blob so they survive the round-trip through Pi Switch's internal
|
||||
/// `raw_other` blob so they survive the round-trip through Kimi Switch's internal
|
||||
/// `Config`.
|
||||
fn merge_provider_known_into_raw(pi_provider: &PiProvider, raw: &mut Value) {
|
||||
let has_known = pi_provider.headers.is_some()
|
||||
@@ -249,7 +249,7 @@ fn merge_provider_known_into_raw(pi_provider: &PiProvider, raw: &mut Value) {
|
||||
}
|
||||
|
||||
/// Merge model-level fields that have dedicated `PiModel` fields into the
|
||||
/// `raw_other` blob so they survive the round-trip through Pi Switch's internal
|
||||
/// `raw_other` blob so they survive the round-trip through Kimi Switch's internal
|
||||
/// `Config`.
|
||||
fn merge_model_known_into_raw(pi_model: &PiModel, raw: &mut Value) {
|
||||
let has_known = pi_model.cost.is_some()
|
||||
@@ -310,7 +310,7 @@ fn extract_model_fields(raw: &Value) -> (Option<PiCost>, Option<Value>, Option<u
|
||||
(cost, compat, max_tokens, extra)
|
||||
}
|
||||
|
||||
/// Import a Pi `models.json` into a Pi Switch `Config`.
|
||||
/// Import a Pi `models.json` into a Kimi Switch `Config`.
|
||||
pub fn pi_file_to_config(file: &PiModelsFile) -> Config {
|
||||
let mut providers = IndexMap::new();
|
||||
let mut models = IndexMap::new();
|
||||
@@ -364,7 +364,6 @@ pub fn pi_file_to_config(file: &PiModelsFile) -> Config {
|
||||
model: pi_model.id.clone(),
|
||||
max_context_size: pi_model.context_window,
|
||||
display_name,
|
||||
role: None,
|
||||
supports_1m: pi_model.reasoning || pi_model.context_window >= 1_000_000,
|
||||
capabilities: vec![],
|
||||
raw_other: model_raw,
|
||||
@@ -383,7 +382,7 @@ pub fn pi_file_to_config(file: &PiModelsFile) -> Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Export a Pi Switch `Config` to a Pi `models.json`.
|
||||
/// Export a Kimi Switch `Config` to a Pi `models.json`.
|
||||
pub fn config_to_pi_file(config: &Config) -> PiModelsFile {
|
||||
let mut providers = IndexMap::new();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"productName": "Pi Switch",
|
||||
"version": "0.1.0",
|
||||
"identifier": "com.piswitch.app",
|
||||
"productName": "Kimi Switch",
|
||||
"version": "0.2.0",
|
||||
"identifier": "com.kimiswitch.app",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
@@ -11,7 +11,7 @@
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Pi Switch",
|
||||
"title": "Kimi Switch",
|
||||
"width": 1200,
|
||||
"height": 800,
|
||||
"minWidth": 1000,
|
||||
|
||||