mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 08:44:41 +08:00
feat(grokbuild): add first-class Grok Build support (#5453)
* feat(grokbuild): add backend integration * feat(grokbuild): add provider and management UI * test(grokbuild): cover configuration and integrations * fix(grokbuild): address backend review feedback * fix(grokbuild): complete UI review feedback * feat(grokbuild): add CLI lifecycle management * fix(grokbuild): align provider icon fallback * test(grokbuild): cover provider icon persistence
This commit is contained in:
@@ -13,7 +13,7 @@ impl Database {
|
||||
pub fn get_all_mcp_servers(&self) -> Result<IndexMap<String, McpServer>, AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
|
||||
"SELECT id, name, server_config, description, homepage, docs, tags, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes
|
||||
FROM mcp_servers
|
||||
ORDER BY name ASC, id ASC"
|
||||
).map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -30,8 +30,9 @@ impl Database {
|
||||
let enabled_claude: bool = row.get(7)?;
|
||||
let enabled_codex: bool = row.get(8)?;
|
||||
let enabled_gemini: bool = row.get(9)?;
|
||||
let enabled_opencode: bool = row.get(10)?;
|
||||
let enabled_hermes: bool = row.get(11)?;
|
||||
let enabled_grokbuild: bool = row.get(10)?;
|
||||
let enabled_opencode: bool = row.get(11)?;
|
||||
let enabled_hermes: bool = row.get(12)?;
|
||||
|
||||
let server = serde_json::from_str(&server_config_str).unwrap_or_default();
|
||||
let tags = serde_json::from_str(&tags_str).unwrap_or_default();
|
||||
@@ -46,6 +47,7 @@ impl Database {
|
||||
claude: enabled_claude,
|
||||
codex: enabled_codex,
|
||||
gemini: enabled_gemini,
|
||||
grokbuild: enabled_grokbuild,
|
||||
opencode: enabled_opencode,
|
||||
hermes: enabled_hermes,
|
||||
},
|
||||
@@ -72,8 +74,8 @@ impl Database {
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO mcp_servers (
|
||||
id, name, server_config, description, homepage, docs, tags,
|
||||
enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
|
||||
enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes
|
||||
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
||||
params![
|
||||
server.id,
|
||||
server.name,
|
||||
@@ -88,6 +90,7 @@ impl Database {
|
||||
server.apps.claude,
|
||||
server.apps.codex,
|
||||
server.apps.gemini,
|
||||
server.apps.grokbuild,
|
||||
server.apps.opencode,
|
||||
server.apps.hermes,
|
||||
],
|
||||
|
||||
@@ -328,6 +328,7 @@ impl Database {
|
||||
"claude" => (6, 90, 180, 8, 3, 90, 0.7, 15),
|
||||
"codex" => (3, 60, 120, 4, 2, 60, 0.6, 10),
|
||||
"gemini" => (5, 60, 120, 4, 2, 60, 0.6, 10),
|
||||
"grokbuild" => (3, 60, 120, 4, 2, 60, 0.6, 10),
|
||||
_ => (3, 60, 120, 4, 2, 60, 0.6, 10), // 默认值
|
||||
};
|
||||
|
||||
@@ -398,6 +399,18 @@ impl Database {
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// grokbuild: Responses protocol, same timeout defaults as Codex.
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO proxy_config (
|
||||
app_type, max_retries,
|
||||
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
|
||||
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
|
||||
circuit_error_rate_threshold, circuit_min_requests
|
||||
) VALUES ('grokbuild', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ impl Database {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
|
||||
enabled_hermes, installed_at, content_hash, updated_at
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild,
|
||||
enabled_opencode, enabled_hermes, installed_at, content_hash, updated_at
|
||||
FROM skills ORDER BY name ASC",
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -43,12 +43,13 @@ impl Database {
|
||||
claude: row.get(8)?,
|
||||
codex: row.get(9)?,
|
||||
gemini: row.get(10)?,
|
||||
opencode: row.get(11)?,
|
||||
hermes: row.get(12)?,
|
||||
grokbuild: row.get(11)?,
|
||||
opencode: row.get(12)?,
|
||||
hermes: row.get(13)?,
|
||||
},
|
||||
installed_at: row.get(13)?,
|
||||
content_hash: row.get(14)?,
|
||||
updated_at: row.get::<_, i64>(15).unwrap_or(0),
|
||||
installed_at: row.get(14)?,
|
||||
content_hash: row.get(15)?,
|
||||
updated_at: row.get::<_, i64>(16).unwrap_or(0),
|
||||
})
|
||||
})
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -67,8 +68,8 @@ impl Database {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode,
|
||||
enabled_hermes, installed_at, content_hash, updated_at
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild,
|
||||
enabled_opencode, enabled_hermes, installed_at, content_hash, updated_at
|
||||
FROM skills WHERE id = ?1",
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
@@ -87,12 +88,13 @@ impl Database {
|
||||
claude: row.get(8)?,
|
||||
codex: row.get(9)?,
|
||||
gemini: row.get(10)?,
|
||||
opencode: row.get(11)?,
|
||||
hermes: row.get(12)?,
|
||||
grokbuild: row.get(11)?,
|
||||
opencode: row.get(12)?,
|
||||
hermes: row.get(13)?,
|
||||
},
|
||||
installed_at: row.get(13)?,
|
||||
content_hash: row.get(14)?,
|
||||
updated_at: row.get::<_, i64>(15).unwrap_or(0),
|
||||
installed_at: row.get(14)?,
|
||||
content_hash: row.get(15)?,
|
||||
updated_at: row.get::<_, i64>(16).unwrap_or(0),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -109,9 +111,9 @@ impl Database {
|
||||
conn.execute(
|
||||
"INSERT OR REPLACE INTO skills
|
||||
(id, name, description, directory, repo_owner, repo_name, repo_branch,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_opencode, enabled_hermes,
|
||||
readme_url, enabled_claude, enabled_codex, enabled_gemini, enabled_grokbuild, enabled_opencode, enabled_hermes,
|
||||
installed_at, content_hash, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16)",
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)",
|
||||
params![
|
||||
skill.id,
|
||||
skill.name,
|
||||
@@ -124,6 +126,7 @@ impl Database {
|
||||
skill.apps.claude,
|
||||
skill.apps.codex,
|
||||
skill.apps.gemini,
|
||||
skill.apps.grokbuild,
|
||||
skill.apps.opencode,
|
||||
skill.apps.hermes,
|
||||
skill.installed_at,
|
||||
@@ -157,8 +160,8 @@ impl Database {
|
||||
let conn = lock_conn!(self.conn);
|
||||
let affected = conn
|
||||
.execute(
|
||||
"UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_opencode = ?4, enabled_hermes = ?5 WHERE id = ?6",
|
||||
params![apps.claude, apps.codex, apps.gemini, apps.opencode, apps.hermes, id],
|
||||
"UPDATE skills SET enabled_claude = ?1, enabled_codex = ?2, enabled_gemini = ?3, enabled_grokbuild = ?4, enabled_opencode = ?5, enabled_hermes = ?6 WHERE id = ?7",
|
||||
params![apps.claude, apps.codex, apps.gemini, apps.grokbuild, apps.opencode, apps.hermes, id],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
Ok(affected > 0)
|
||||
|
||||
@@ -52,7 +52,7 @@ use std::sync::Mutex;
|
||||
|
||||
/// 当前 Schema 版本号
|
||||
/// 每次修改表结构时递增,并在 schema.rs 中添加相应的迁移逻辑
|
||||
pub(crate) const SCHEMA_VERSION: i32 = 13;
|
||||
pub(crate) const SCHEMA_VERSION: i32 = 15;
|
||||
|
||||
/// 安全地序列化 JSON,避免 unwrap panic
|
||||
pub(crate) fn to_json_string<T: Serialize>(value: &T) -> Result<String, AppError> {
|
||||
|
||||
@@ -65,7 +65,8 @@ impl Database {
|
||||
id TEXT PRIMARY KEY, name TEXT NOT NULL, server_config TEXT NOT NULL,
|
||||
description TEXT, homepage TEXT, docs TEXT, tags TEXT NOT NULL DEFAULT '[]',
|
||||
enabled_claude BOOLEAN NOT NULL DEFAULT 0, enabled_codex BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_gemini BOOLEAN NOT NULL DEFAULT 0, enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_gemini BOOLEAN NOT NULL DEFAULT 0, enabled_grokbuild BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_hermes BOOLEAN NOT NULL DEFAULT 0
|
||||
)",
|
||||
[],
|
||||
@@ -93,6 +94,7 @@ impl Database {
|
||||
enabled_claude BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_codex BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_gemini BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_grokbuild BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_opencode BOOLEAN NOT NULL DEFAULT 0,
|
||||
enabled_hermes BOOLEAN NOT NULL DEFAULT 0,
|
||||
installed_at INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -122,7 +124,7 @@ impl Database {
|
||||
|
||||
// 8. Proxy Config 表(三行结构,app_type 主键)
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS proxy_config (
|
||||
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini')),
|
||||
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
|
||||
proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
listen_port INTEGER NOT NULL DEFAULT 15721, enable_logging INTEGER NOT NULL DEFAULT 1,
|
||||
enabled INTEGER NOT NULL DEFAULT 0, auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -169,6 +171,15 @@ impl Database {
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO proxy_config (app_type, max_retries,
|
||||
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
|
||||
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
|
||||
circuit_error_rate_threshold, circuit_min_requests)
|
||||
VALUES ('grokbuild', 3, 60, 120, 600, 4, 2, 60, 0.6, 10)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
}
|
||||
|
||||
// 9. Provider Health 表
|
||||
@@ -485,6 +496,16 @@ impl Database {
|
||||
Self::migrate_v12_to_v13(conn)?;
|
||||
Self::set_user_version(conn, 13)?;
|
||||
}
|
||||
13 => {
|
||||
log::info!("迁移数据库从 v13 到 v14(添加 Grok Build 代理配置)");
|
||||
Self::migrate_v13_to_v14(conn)?;
|
||||
Self::set_user_version(conn, 14)?;
|
||||
}
|
||||
14 => {
|
||||
log::info!("迁移数据库从 v14 到 v15(Skills/MCP 添加 Grok Build 支持)");
|
||||
Self::migrate_v14_to_v15(conn)?;
|
||||
Self::set_user_version(conn, 15)?;
|
||||
}
|
||||
_ => {
|
||||
return Err(AppError::Database(format!(
|
||||
"未知的数据库版本 {version},无法迁移到 {SCHEMA_VERSION}"
|
||||
@@ -798,12 +819,25 @@ impl Database {
|
||||
old_cb.3,
|
||||
old_cb.4,
|
||||
),
|
||||
(
|
||||
"grokbuild",
|
||||
false,
|
||||
false,
|
||||
3,
|
||||
old_config.4,
|
||||
old_config.5,
|
||||
old_cb.0,
|
||||
old_cb.1,
|
||||
old_cb.2,
|
||||
old_cb.3,
|
||||
old_cb.4,
|
||||
),
|
||||
];
|
||||
|
||||
// 创建新表
|
||||
conn.execute("DROP TABLE IF EXISTS proxy_config_new", [])?;
|
||||
conn.execute("CREATE TABLE proxy_config_new (
|
||||
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini')),
|
||||
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
|
||||
proxy_enabled INTEGER NOT NULL DEFAULT 0, listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
listen_port INTEGER NOT NULL DEFAULT 15721, enable_logging INTEGER NOT NULL DEFAULT 1,
|
||||
enabled INTEGER NOT NULL DEFAULT 0, auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -814,6 +848,7 @@ impl Database {
|
||||
circuit_min_requests INTEGER NOT NULL DEFAULT 10,
|
||||
default_cost_multiplier TEXT NOT NULL DEFAULT '1',
|
||||
pricing_model_source TEXT NOT NULL DEFAULT 'response',
|
||||
live_takeover_active INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)", [])?;
|
||||
|
||||
@@ -1354,6 +1389,127 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// v13 -> v14: allow Grok Build to own an independent proxy configuration row.
|
||||
fn migrate_v13_to_v14(conn: &Connection) -> Result<(), AppError> {
|
||||
if !Self::table_exists(conn, "proxy_config")? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
conn.execute("DROP TABLE IF EXISTS proxy_config_v14", [])
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
conn.execute(
|
||||
"CREATE TABLE proxy_config_v14 (
|
||||
app_type TEXT PRIMARY KEY CHECK (app_type IN ('claude','codex','gemini','grokbuild')),
|
||||
proxy_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
listen_address TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
listen_port INTEGER NOT NULL DEFAULT 15721,
|
||||
enable_logging INTEGER NOT NULL DEFAULT 1,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
auto_failover_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
max_retries INTEGER NOT NULL DEFAULT 3,
|
||||
streaming_first_byte_timeout INTEGER NOT NULL DEFAULT 60,
|
||||
streaming_idle_timeout INTEGER NOT NULL DEFAULT 120,
|
||||
non_streaming_timeout INTEGER NOT NULL DEFAULT 600,
|
||||
circuit_failure_threshold INTEGER NOT NULL DEFAULT 4,
|
||||
circuit_success_threshold INTEGER NOT NULL DEFAULT 2,
|
||||
circuit_timeout_seconds INTEGER NOT NULL DEFAULT 60,
|
||||
circuit_error_rate_threshold REAL NOT NULL DEFAULT 0.6,
|
||||
circuit_min_requests INTEGER NOT NULL DEFAULT 10,
|
||||
default_cost_multiplier TEXT NOT NULL DEFAULT '1',
|
||||
pricing_model_source TEXT NOT NULL DEFAULT 'response',
|
||||
live_takeover_active INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
let copied_columns = [
|
||||
("app_type", "'claude'"),
|
||||
("proxy_enabled", "0"),
|
||||
("listen_address", "'127.0.0.1'"),
|
||||
("listen_port", "15721"),
|
||||
("enable_logging", "1"),
|
||||
("enabled", "0"),
|
||||
("auto_failover_enabled", "0"),
|
||||
("max_retries", "3"),
|
||||
("streaming_first_byte_timeout", "60"),
|
||||
("streaming_idle_timeout", "120"),
|
||||
("non_streaming_timeout", "600"),
|
||||
("circuit_failure_threshold", "4"),
|
||||
("circuit_success_threshold", "2"),
|
||||
("circuit_timeout_seconds", "60"),
|
||||
("circuit_error_rate_threshold", "0.6"),
|
||||
("circuit_min_requests", "10"),
|
||||
("default_cost_multiplier", "'1'"),
|
||||
("pricing_model_source", "'response'"),
|
||||
("live_takeover_active", "0"),
|
||||
("created_at", "datetime('now')"),
|
||||
("updated_at", "datetime('now')"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(column, fallback)| {
|
||||
Self::has_column(conn, "proxy_config", column).map(|exists| {
|
||||
if exists {
|
||||
format!("\"{column}\"")
|
||||
} else {
|
||||
fallback.into()
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, AppError>>()?
|
||||
.join(", ");
|
||||
|
||||
let copy_sql = format!(
|
||||
"INSERT INTO proxy_config_v14 (
|
||||
app_type, proxy_enabled, listen_address, listen_port, enable_logging,
|
||||
enabled, auto_failover_enabled, max_retries,
|
||||
streaming_first_byte_timeout, streaming_idle_timeout, non_streaming_timeout,
|
||||
circuit_failure_threshold, circuit_success_threshold, circuit_timeout_seconds,
|
||||
circuit_error_rate_threshold, circuit_min_requests,
|
||||
default_cost_multiplier, pricing_model_source, live_takeover_active,
|
||||
created_at, updated_at
|
||||
)
|
||||
SELECT {copied_columns} FROM proxy_config"
|
||||
);
|
||||
conn.execute(©_sql, [])
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
conn.execute("DROP TABLE proxy_config", [])
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
conn.execute("ALTER TABLE proxy_config_v14 RENAME TO proxy_config", [])
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO proxy_config (app_type) VALUES ('grokbuild')",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// v14 -> v15: persist Grok Build enablement for unified Skills and MCP.
|
||||
fn migrate_v14_to_v15(conn: &Connection) -> Result<(), AppError> {
|
||||
if Self::table_exists(conn, "mcp_servers")? {
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"mcp_servers",
|
||||
"enabled_grokbuild",
|
||||
"BOOLEAN NOT NULL DEFAULT 0",
|
||||
)?;
|
||||
}
|
||||
if Self::table_exists(conn, "skills")? {
|
||||
Self::add_column_if_missing(
|
||||
conn,
|
||||
"skills",
|
||||
"enabled_grokbuild",
|
||||
"BOOLEAN NOT NULL DEFAULT 0",
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 插入默认模型定价数据
|
||||
/// 格式: (model_id, display_name, input, output, cache_read, cache_creation)
|
||||
/// 注意: model_id 使用短横线格式(如 claude-haiku-4-5),与 API 返回的模型名称标准化后一致
|
||||
@@ -2766,7 +2922,7 @@ mod tests {
|
||||
|
||||
Database::apply_schema_migrations_on_conn(&conn)?;
|
||||
|
||||
assert_eq!(Database::get_user_version(&conn)?, 13);
|
||||
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
|
||||
assert!(Database::has_column(
|
||||
&conn,
|
||||
"proxy_request_logs",
|
||||
@@ -2787,4 +2943,82 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_v13_to_v14_adds_grokbuild_proxy_row_and_preserves_values() -> Result<(), AppError> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
Database::create_tables_on_conn(&conn)?;
|
||||
conn.execute("DELETE FROM proxy_config WHERE app_type = 'grokbuild'", [])?;
|
||||
conn.execute(
|
||||
"UPDATE proxy_config SET enabled = 1, max_retries = 9 WHERE app_type = 'codex'",
|
||||
[],
|
||||
)?;
|
||||
Database::set_user_version(&conn, 13)?;
|
||||
|
||||
Database::apply_schema_migrations_on_conn(&conn)?;
|
||||
|
||||
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
|
||||
let grok_rows: i64 = conn.query_row(
|
||||
"SELECT COUNT(*) FROM proxy_config WHERE app_type = 'grokbuild'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
assert_eq!(grok_rows, 1);
|
||||
let codex_values: (i64, i64) = conn.query_row(
|
||||
"SELECT enabled, max_retries FROM proxy_config WHERE app_type = 'codex'",
|
||||
[],
|
||||
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||
)?;
|
||||
assert_eq!(codex_values, (1, 9));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_v14_to_v15_adds_grokbuild_skill_and_mcp_flags() -> Result<(), AppError> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
conn.execute_batch(
|
||||
"CREATE TABLE mcp_servers (
|
||||
id TEXT PRIMARY KEY,
|
||||
enabled_codex BOOLEAN NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE skills (
|
||||
id TEXT PRIMARY KEY,
|
||||
enabled_codex BOOLEAN NOT NULL DEFAULT 0
|
||||
);",
|
||||
)?;
|
||||
conn.execute(
|
||||
"INSERT INTO mcp_servers (id, enabled_codex) VALUES ('mcp-1', 1)",
|
||||
[],
|
||||
)?;
|
||||
conn.execute(
|
||||
"INSERT INTO skills (id, enabled_codex) VALUES ('skill-1', 1)",
|
||||
[],
|
||||
)?;
|
||||
Database::set_user_version(&conn, 14)?;
|
||||
|
||||
Database::apply_schema_migrations_on_conn(&conn)?;
|
||||
|
||||
assert_eq!(Database::get_user_version(&conn)?, SCHEMA_VERSION);
|
||||
assert!(Database::has_column(
|
||||
&conn,
|
||||
"mcp_servers",
|
||||
"enabled_grokbuild"
|
||||
)?);
|
||||
assert!(Database::has_column(&conn, "skills", "enabled_grokbuild")?);
|
||||
let mcp_values: (i64, i64) = conn.query_row(
|
||||
"SELECT enabled_codex, enabled_grokbuild FROM mcp_servers WHERE id = 'mcp-1'",
|
||||
[],
|
||||
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||
)?;
|
||||
let skill_values: (i64, i64) = conn.query_row(
|
||||
"SELECT enabled_codex, enabled_grokbuild FROM skills WHERE id = 'skill-1'",
|
||||
[],
|
||||
|row| Ok((row.get(0)?, row.get(1)?)),
|
||||
)?;
|
||||
assert_eq!(mcp_values, (1, 0));
|
||||
assert_eq!(skill_values, (1, 0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,7 +517,7 @@ fn schema_create_tables_repairs_legacy_proxy_config_singleton_to_per_app() {
|
||||
let count: i32 = conn
|
||||
.query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0))
|
||||
.expect("count rows");
|
||||
assert_eq!(count, 3, "per-app proxy_config should have 3 rows");
|
||||
assert_eq!(count, 4, "per-app proxy_config should have 4 rows");
|
||||
|
||||
// 新结构下应能按 app_type 查询
|
||||
let _: i32 = conn
|
||||
@@ -657,7 +657,7 @@ fn migration_from_v3_8_schema_v1_to_current_schema_v3() {
|
||||
let proxy_rows: i64 = conn
|
||||
.query_row("SELECT COUNT(*) FROM proxy_config", [], |r| r.get(0))
|
||||
.expect("count proxy_config rows");
|
||||
assert_eq!(proxy_rows, 3);
|
||||
assert_eq!(proxy_rows, 4);
|
||||
|
||||
// model_pricing 应具备默认数据(迁移时会 seed)
|
||||
let pricing_rows: i64 = conn
|
||||
|
||||
Reference in New Issue
Block a user