mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-30 02:14:43 +08:00
feat(grokbuild): add Grok Official provider with official-state import
Add a "Grok Official" preset and seed (grokbuild-official) whose empty config represents the official login state: no custom [model.*] tables are written, so Grok CLI falls back to its built-in xAI OAuth login and cc-switch never touches those credentials. Backend: - Seed entry in OFFICIAL_SEEDS plus ensure_grokbuild_official_provider command for on-demand repair (the one-shot master seeding flag is already set for existing databases). - Split validation into syntax-only (empty allowed) for live reads, writes and official snapshots, keeping the full custom-model shape check for non-official provider writes and imports. Backup/restore can now round-trip an official-state live file. - Manual import (command layer only) recognizes an official-state live config and imports it as the official entry set as current, matching the Codex official-login import outcome. Startup auto-import keeps rejecting official-state live so a deleted official entry is never resurrected on launch: startup import only captures real user data as "default" and never manufactures official entries. - Manual import also ensures the official entry before importing (claude-desktop precedent) and after a successful custom import, so first-time users end up with default + official like other apps. - Proxy takeover guards skip or reject official-state live configs in all three takeover paths, consistent with the official-provider takeover ban. Frontend: - Grok Official preset entry in the GrokBuild form: official category hides connection fields and passes the raw config through untouched. - Filter managed-OAuth presets out of the GrokBuild preset list; they were never wired for this app and produced keyless broken configs. Tests cover seed presence, official round-trip, ensure-after-deletion, and the four import scenarios including startup non-resurrection.
This commit is contained in:
@@ -1459,6 +1459,16 @@ pub fn import_default_config(state: &AppState, app_type: AppType) -> Result<bool
|
||||
AppType::Codex => crate::codex_config::read_codex_live_settings()?,
|
||||
AppType::GrokBuild => {
|
||||
let mut settings = crate::grok_config::read_grok_live_settings()?;
|
||||
let config = settings
|
||||
.get("config")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
// 官方登录态(无自定义模型表)在这里必须报错:本函数也被启动
|
||||
// 自动导入调用,而全项目惯例是"启动自动导入只产出 default,
|
||||
// 从不产出官方条目"——否则删掉的官方条目每次重启都会复活。
|
||||
// 官方态的成功导入(补官方条目并激活)只挂在手动导入的命令层
|
||||
// (`import_default_config_internal`)。
|
||||
crate::grok_config::validate_config_toml(config)?;
|
||||
crate::grok_config::strip_grok_mcp_servers_from_settings(&mut settings)?;
|
||||
settings
|
||||
}
|
||||
@@ -1560,6 +1570,20 @@ pub fn import_default_config(state: &AppState, app_type: AppType) -> Result<bool
|
||||
.set_current_provider(app_type.as_str(), &provider.id)?;
|
||||
crate::settings::set_current_provider(&app_type, Some(provider.id.as_str()))?;
|
||||
|
||||
// 初次导入已有配置时随手补出官方入口,对齐其它应用"首启动 = 导入 default
|
||||
// + 播种官方条目"的观感。grokbuild 种子晚于 `official_providers_seeded`
|
||||
// flag 引入,存量库的主播种不会再跑,只能挂在导入动作上补。
|
||||
// 只在导入成功时执行;live 完全不可导入(文件缺失/语法错误/残缺配置)
|
||||
// 不会到达这里。失败只 warn。
|
||||
if matches!(app_type, AppType::GrokBuild) {
|
||||
if let Err(e) = state.db.ensure_official_seed_by_id(
|
||||
crate::database::GROKBUILD_OFFICIAL_PROVIDER_ID,
|
||||
AppType::GrokBuild,
|
||||
) {
|
||||
log::warn!("Failed to ensure grokbuild-official seed after import: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(true) // 真正导入了
|
||||
}
|
||||
|
||||
|
||||
@@ -3501,7 +3501,13 @@ impl ProviderService {
|
||||
"Grok Build configuration is missing the config field",
|
||||
)
|
||||
})?;
|
||||
crate::grok_config::validate_config_toml(config)?;
|
||||
if provider.category.as_deref() == Some("official") {
|
||||
// 官方条目走 Grok CLI 自带 OAuth:空 config 合法,
|
||||
// 回填快照只要求 TOML 语法合法。
|
||||
crate::grok_config::validate_config_toml_syntax(config)?;
|
||||
} else {
|
||||
crate::grok_config::validate_config_toml(config)?;
|
||||
}
|
||||
}
|
||||
AppType::OpenCode => {
|
||||
// OpenCode uses a different config structure: { npm, options, models }
|
||||
|
||||
@@ -1502,6 +1502,19 @@ impl ProxyService {
|
||||
Ok((proxy_url, proxy_codex_base_url))
|
||||
}
|
||||
|
||||
/// Grok Build live 是否具备可接管的自定义模型表。
|
||||
///
|
||||
/// 官方态 live(Grok CLI 自带 OAuth 登录、无 `[model.*]` 表)没有注入
|
||||
/// 占位符的落点,且官方供应商本就禁止经代理接管(封号风险),调用方
|
||||
/// 应跳过接管或直接报错。
|
||||
fn grok_live_config_supports_takeover(config: &Value) -> bool {
|
||||
config
|
||||
.get("config")
|
||||
.and_then(Value::as_str)
|
||||
.and_then(crate::grok_config::extract_model_config)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
fn apply_grok_takeover_fields(config: &mut Value, proxy_base_url: &str) -> Result<(), String> {
|
||||
let config_toml = config
|
||||
.get("config")
|
||||
@@ -1573,9 +1586,13 @@ impl ProxyService {
|
||||
|
||||
// Grok Build: keep its own provider namespace while reusing Responses forwarding.
|
||||
if let Ok(mut live_config) = self.read_grok_live() {
|
||||
Self::apply_grok_takeover_fields(&mut live_config, &proxy_grok_base_url)?;
|
||||
self.write_grok_live(&live_config)?;
|
||||
log::info!("Grok Build Live 配置已接管,代理地址: {proxy_grok_base_url}");
|
||||
if Self::grok_live_config_supports_takeover(&live_config) {
|
||||
Self::apply_grok_takeover_fields(&mut live_config, &proxy_grok_base_url)?;
|
||||
self.write_grok_live(&live_config)?;
|
||||
log::info!("Grok Build Live 配置已接管,代理地址: {proxy_grok_base_url}");
|
||||
} else {
|
||||
log::info!("Grok Build Live 处于官方登录态(无自定义模型表),跳过代理接管");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1630,6 +1647,14 @@ impl ProxyService {
|
||||
}
|
||||
AppType::GrokBuild => {
|
||||
let mut live_config = self.read_grok_live()?;
|
||||
if !Self::grok_live_config_supports_takeover(&live_config) {
|
||||
return Err(
|
||||
"Grok Build 当前为官方登录态(无自定义模型表),官方供应商不支持代理接管 \
|
||||
(Grok Build is using the official login without a custom model table; \
|
||||
official providers cannot be taken over by the proxy)"
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
Self::apply_grok_takeover_fields(&mut live_config, &proxy_grok_base_url)?;
|
||||
self.write_grok_live(&live_config)?;
|
||||
log::info!("Grok Build Live 配置已接管,代理地址: {proxy_grok_base_url}");
|
||||
@@ -1701,8 +1726,14 @@ impl ProxyService {
|
||||
}
|
||||
AppType::GrokBuild => {
|
||||
if let Ok(mut live_config) = self.read_grok_live() {
|
||||
Self::apply_grok_takeover_fields(&mut live_config, &proxy_grok_base_url)?;
|
||||
let _ = self.write_grok_live(&live_config);
|
||||
if Self::grok_live_config_supports_takeover(&live_config) {
|
||||
Self::apply_grok_takeover_fields(&mut live_config, &proxy_grok_base_url)?;
|
||||
let _ = self.write_grok_live(&live_config);
|
||||
} else {
|
||||
log::info!(
|
||||
"Grok Build Live 处于官方登录态(无自定义模型表),跳过代理接管"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
Reference in New Issue
Block a user