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:
Thefool
2026-07-17 15:50:50 +08:00
committed by GitHub
parent f6e37ed994
commit 1c0ee0c58a
112 changed files with 4530 additions and 246 deletions
+8
View File
@@ -6,6 +6,14 @@ use cc_switch_lib::AppType;
fn parse_known_apps_case_insensitive_and_trim() {
assert!(matches!(AppType::from_str("claude"), Ok(AppType::Claude)));
assert!(matches!(AppType::from_str("codex"), Ok(AppType::Codex)));
assert!(matches!(
AppType::from_str("grokbuild"),
Ok(AppType::GrokBuild)
));
assert!(matches!(
AppType::from_str("Grok-Build"),
Ok(AppType::GrokBuild)
));
assert!(matches!(
AppType::from_str(" ClAuDe \n"),
Ok(AppType::Claude)
+2
View File
@@ -748,6 +748,7 @@ command = "echo"
claude: false,
codex: false, // 初始未启用
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -877,6 +878,7 @@ fn import_from_claude_merges_into_config() {
claude: false, // 初始未启用
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
+12
View File
@@ -227,6 +227,7 @@ command = "echo"
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -371,6 +372,7 @@ fn set_mcp_enabled_for_codex_writes_live_config() {
claude: false,
codex: false, // 初始未启用
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -436,6 +438,7 @@ fn enabling_codex_mcp_skips_when_codex_dir_missing() {
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -481,6 +484,7 @@ fn upsert_mcp_server_disabling_app_removes_from_claude_live_config() {
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -515,6 +519,7 @@ fn upsert_mcp_server_disabling_app_removes_from_claude_live_config() {
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -648,6 +653,7 @@ fn enabling_gemini_mcp_skips_when_gemini_dir_missing() {
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -703,6 +709,7 @@ fn enabling_claude_mcp_skips_when_claude_config_absent() {
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -758,6 +765,7 @@ fn explicit_default_claude_dir_keeps_default_split_mcp_path() {
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -814,6 +822,7 @@ fn custom_claude_dir_writes_mcp_inside_config_dir() {
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -893,6 +902,7 @@ fn custom_claude_dir_sync_does_not_copy_default_profile() {
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -1032,6 +1042,7 @@ fn sync_all_enabled_removes_known_disabled_but_preserves_unknown_live_entries()
claude: false,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -1054,6 +1065,7 @@ fn sync_all_enabled_removes_known_disabled_but_preserves_unknown_live_entries()
claude: true,
codex: false,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
+76
View File
@@ -19,6 +19,81 @@ fn settings_path(home: &Path) -> PathBuf {
home.join(".cc-switch").join("settings.json")
}
fn grokbuild_config(name: &str, endpoint: &str, api_key: &str) -> String {
format!(
r#"[models]
default = "grok-4.5"
[model."grok-4.5"]
model = "grok-4.5"
base_url = "{endpoint}"
name = "{name}"
api_key = "{api_key}"
api_backend = "responses"
context_window = 500000
"#
)
}
#[test]
fn grokbuild_import_and_switch_write_live_config() {
let _guard = test_mutex().lock().expect("acquire test mutex");
reset_test_fs();
let home = ensure_test_home();
let live_path = home.join(".grok").join("config.toml");
std::fs::create_dir_all(live_path.parent().expect("grok config dir"))
.expect("create grok config dir");
let imported_config = grokbuild_config("Imported", "https://old.example/v1", "old-key");
std::fs::write(&live_path, &imported_config).expect("seed Grok Build config");
let state = create_test_state().expect("create test state");
import_default_config_test_hook(&state, AppType::GrokBuild)
.expect("import Grok Build default provider");
let imported = state
.db
.get_provider_by_id("default", AppType::GrokBuild.as_str())
.expect("query imported provider")
.expect("imported provider exists");
assert_eq!(
imported
.settings_config
.get("config")
.and_then(|value| value.as_str()),
Some(imported_config.as_str())
);
let next_config = grokbuild_config("Relay", "https://new.example/v1", "new-key");
state
.db
.save_provider(
AppType::GrokBuild.as_str(),
&Provider::with_id(
"relay".to_string(),
"Relay".to_string(),
json!({ "config": next_config }),
None,
),
)
.expect("save second Grok Build provider");
switch_provider_test_hook(&state, AppType::GrokBuild, "relay")
.expect("switch Grok Build provider");
assert_eq!(
std::fs::read_to_string(&live_path).expect("read switched Grok Build config"),
next_config
);
assert_eq!(
state
.db
.get_current_provider(AppType::GrokBuild.as_str())
.expect("read Grok Build current provider")
.as_deref(),
Some("relay")
);
}
#[test]
fn codex_startup_import_fresh_install_imports_once_and_syncs_current_setting() {
let _guard = test_mutex().lock().expect("acquire test mutex");
@@ -300,6 +375,7 @@ command = "say"
claude: false,
codex: true, // 启用 Codex
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
+5
View File
@@ -164,6 +164,7 @@ command = "say"
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -934,6 +935,7 @@ fn reapply_codex_official_live_resyncs_mcp_servers() {
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -1032,6 +1034,7 @@ fn reapply_codex_official_live_projects_mcp_despite_broken_claude_json() {
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -1124,6 +1127,7 @@ fn switch_codex_projects_mcp_despite_broken_claude_json() {
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
@@ -1187,6 +1191,7 @@ fn sync_all_enabled_reports_broken_app_but_projects_the_rest() {
claude: false,
codex: true,
gemini: false,
grokbuild: false,
opencode: false,
hermes: false,
},
+1
View File
@@ -31,6 +31,7 @@ pub fn reset_test_fs() {
".codex",
".cc-switch",
".gemini",
".grok",
".config",
".openclaw",
"profiles",