mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
f733def452
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.
249 lines
6.8 KiB
TypeScript
249 lines
6.8 KiB
TypeScript
import { invoke } from "@tauri-apps/api/core";
|
||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||
import type {
|
||
Provider,
|
||
UniversalProvider,
|
||
UniversalProvidersMap,
|
||
} from "@/types";
|
||
import type { AppId } from "./types";
|
||
|
||
export interface ProviderSortUpdate {
|
||
id: string;
|
||
sortIndex: number;
|
||
}
|
||
|
||
export interface ProviderSwitchEvent {
|
||
appType: AppId;
|
||
providerId: string;
|
||
}
|
||
|
||
export interface SwitchResult {
|
||
warnings: string[];
|
||
}
|
||
|
||
export interface OpenTerminalOptions {
|
||
cwd?: string;
|
||
}
|
||
|
||
export interface ClaudeDesktopStatus {
|
||
supported: boolean;
|
||
configured: boolean;
|
||
appliedId?: string | null;
|
||
profilePath?: string | null;
|
||
configLibraryPath?: string | null;
|
||
mode?: "direct" | "proxy" | null;
|
||
expectedBaseUrl?: string | null;
|
||
actualBaseUrl?: string | null;
|
||
proxyRunning: boolean;
|
||
staleRawModels: boolean;
|
||
missingRouteMappings: boolean;
|
||
gatewayTokenConfigured: boolean;
|
||
}
|
||
|
||
export interface ClaudeDesktopDefaultRoute {
|
||
routeId: string;
|
||
envKey: string;
|
||
supports1m: boolean;
|
||
}
|
||
|
||
export const providersApi = {
|
||
async getAll(appId: AppId): Promise<Record<string, Provider>> {
|
||
return await invoke("get_providers", { app: appId });
|
||
},
|
||
|
||
async getCurrent(appId: AppId): Promise<string> {
|
||
return await invoke("get_current_provider", { app: appId });
|
||
},
|
||
|
||
async add(
|
||
provider: Provider,
|
||
appId: AppId,
|
||
addToLive?: boolean,
|
||
): Promise<boolean> {
|
||
return await invoke("add_provider", { provider, app: appId, addToLive });
|
||
},
|
||
|
||
async update(
|
||
provider: Provider,
|
||
appId: AppId,
|
||
originalId?: string,
|
||
): Promise<boolean> {
|
||
return await invoke("update_provider", {
|
||
provider,
|
||
app: appId,
|
||
originalId,
|
||
});
|
||
},
|
||
|
||
async delete(id: string, appId: AppId): Promise<boolean> {
|
||
return await invoke("delete_provider", { id, app: appId });
|
||
},
|
||
|
||
/**
|
||
* Remove provider from live config only (for additive mode apps like OpenCode)
|
||
* Does NOT delete from database - provider remains in the list
|
||
*/
|
||
async removeFromLiveConfig(id: string, appId: AppId): Promise<boolean> {
|
||
return await invoke("remove_provider_from_live_config", { id, app: appId });
|
||
},
|
||
|
||
async switch(id: string, appId: AppId): Promise<SwitchResult> {
|
||
return await invoke("switch_provider", { id, app: appId });
|
||
},
|
||
|
||
async importDefault(appId: AppId): Promise<boolean> {
|
||
return await invoke("import_default_config", { app: appId });
|
||
},
|
||
|
||
async importClaudeDesktopFromClaude(): Promise<number> {
|
||
return await invoke("import_claude_desktop_providers_from_claude");
|
||
},
|
||
|
||
async ensureClaudeDesktopOfficialProvider(): Promise<boolean> {
|
||
return await invoke("ensure_claude_desktop_official_provider");
|
||
},
|
||
|
||
async ensureCodexOfficialProvider(): Promise<boolean> {
|
||
return await invoke("ensure_codex_official_provider");
|
||
},
|
||
|
||
async ensureGrokBuildOfficialProvider(): Promise<boolean> {
|
||
return await invoke("ensure_grokbuild_official_provider");
|
||
},
|
||
|
||
async getClaudeDesktopStatus(): Promise<ClaudeDesktopStatus> {
|
||
return await invoke("get_claude_desktop_status");
|
||
},
|
||
|
||
async getClaudeDesktopDefaultRoutes(): Promise<ClaudeDesktopDefaultRoute[]> {
|
||
return await invoke("get_claude_desktop_default_routes");
|
||
},
|
||
|
||
async updateTrayMenu(): Promise<boolean> {
|
||
return await invoke("update_tray_menu");
|
||
},
|
||
|
||
async updateSortOrder(
|
||
updates: ProviderSortUpdate[],
|
||
appId: AppId,
|
||
): Promise<boolean> {
|
||
return await invoke("update_providers_sort_order", { updates, app: appId });
|
||
},
|
||
|
||
async onSwitched(
|
||
handler: (event: ProviderSwitchEvent) => void,
|
||
): Promise<UnlistenFn> {
|
||
return await listen("provider-switched", (event) => {
|
||
const payload = event.payload as ProviderSwitchEvent;
|
||
handler(payload);
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 打开指定提供商的终端
|
||
* 任何提供商都可以打开终端,不受是否为当前激活提供商的限制
|
||
* 终端会使用该提供商特定的 API 配置,不影响全局设置
|
||
*/
|
||
async openTerminal(
|
||
providerId: string,
|
||
appId: AppId,
|
||
options?: OpenTerminalOptions,
|
||
): Promise<boolean> {
|
||
const { cwd } = options ?? {};
|
||
return await invoke("open_provider_terminal", {
|
||
providerId,
|
||
app: appId,
|
||
cwd,
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 从 OpenCode live 配置导入供应商到数据库
|
||
* OpenCode 特有功能:由于累加模式,用户可能已在 opencode.json 中配置供应商
|
||
*/
|
||
async importOpenCodeFromLive(): Promise<number> {
|
||
return await invoke("import_opencode_providers_from_live");
|
||
},
|
||
|
||
/**
|
||
* 获取 OpenCode live 配置中的供应商 ID 列表
|
||
* 用于前端判断供应商是否已添加到 opencode.json
|
||
*/
|
||
async getOpenCodeLiveProviderIds(): Promise<string[]> {
|
||
return await invoke("get_opencode_live_provider_ids");
|
||
},
|
||
|
||
/**
|
||
* 获取 OpenClaw live 配置中的供应商 ID 列表
|
||
* 用于前端判断供应商是否已添加到 openclaw.json
|
||
*/
|
||
async getOpenClawLiveProviderIds(): Promise<string[]> {
|
||
return await invoke("get_openclaw_live_provider_ids");
|
||
},
|
||
|
||
/**
|
||
* 获取 Hermes live 配置中的供应商 ID 列表
|
||
* 用于前端判断供应商是否已添加到 Hermes 配置
|
||
*/
|
||
async getHermesLiveProviderIds(): Promise<string[]> {
|
||
return await invoke("get_hermes_live_provider_ids");
|
||
},
|
||
|
||
/**
|
||
* 从 OpenClaw live 配置导入供应商到数据库
|
||
* OpenClaw 特有功能:由于累加模式,用户可能已在 openclaw.json 中配置供应商
|
||
*/
|
||
async importOpenClawFromLive(): Promise<number> {
|
||
return await invoke("import_openclaw_providers_from_live");
|
||
},
|
||
|
||
/**
|
||
* 从 Hermes live 配置导入供应商到数据库
|
||
* Hermes 特有功能:由于累加模式,用户可能已在 Hermes 配置中配置供应商
|
||
*/
|
||
async importHermesFromLive(): Promise<number> {
|
||
return await invoke("import_hermes_providers_from_live");
|
||
},
|
||
};
|
||
|
||
// ============================================================================
|
||
// 统一供应商(Universal Provider)API
|
||
// ============================================================================
|
||
|
||
export const universalProvidersApi = {
|
||
/**
|
||
* 获取所有统一供应商
|
||
*/
|
||
async getAll(): Promise<UniversalProvidersMap> {
|
||
return await invoke("get_universal_providers");
|
||
},
|
||
|
||
/**
|
||
* 获取单个统一供应商
|
||
*/
|
||
async get(id: string): Promise<UniversalProvider | null> {
|
||
return await invoke("get_universal_provider", { id });
|
||
},
|
||
|
||
/**
|
||
* 添加或更新统一供应商
|
||
*/
|
||
async upsert(provider: UniversalProvider): Promise<boolean> {
|
||
return await invoke("upsert_universal_provider", { provider });
|
||
},
|
||
|
||
/**
|
||
* 删除统一供应商
|
||
*/
|
||
async delete(id: string): Promise<boolean> {
|
||
return await invoke("delete_universal_provider", { id });
|
||
},
|
||
|
||
/**
|
||
* 手动同步统一供应商到各应用
|
||
*/
|
||
async sync(id: string): Promise<boolean> {
|
||
return await invoke("sync_universal_provider", { id });
|
||
},
|
||
};
|