feat(ai): add Grok CLI as a Desktop no-API-key provider
* feat(ai): add Grok CLI as a Desktop no-API-key provider Add grok-cli alongside Codex/Claude Code/Pi, with path/env settings, isolated MCP-scoped headless runs, model listing, and connection test. * feat(ai): expose Grok CLI reasoning effort like Codex Attach low/medium/high effort capabilities to Grok models for the assistant effort menu, wire resolve_model_effort for grok-cli, and use the monochrome Grok mark icon. * i18n: add Grok CLI error messages for remaining locales Fill grokCli* cliErrors strings in es, it, ja, ko, pt-BR, and zh-TW to match en/zh-CN and satisfy i18n autofill CI. * fix(ai): align Grok CLI headless with official Build flags Address review feedback on PR #5494: - Use streaming-json + CliAgentJsonlDialect::GrokStreamingJson - Switch permission to --always-approve and MCPTool(dbx__*) rules - Prefer --effort over --reasoning-effort; keep --prompt-file Verified with unit tests and a local headless e2e against grok 1.0. * style(ai): fix rustfmt for Grok CLI provider files * test(ai): fix Grok CLI entry in provider ordering spec * fix(ai): harden grok CLI error wording and config schema Add 'not signed in' to classify_grok_run_error and test_grok_connection auth detection — real @xai-official/grok 1.0.0 emits this exact wording on headless auth failure (verified against the shipped binary). Remove invalid config.toml fields: permission_mode='always-approve' (not a valid enum; auto-approve is driven by --always-approve flag) and startup_timeout_sec/tool_timeout_sec/enabled_tools (not mcp_servers schema fields per 'grok mcp add'). --------- Co-authored-by: t8y2 <1156263951@qq.com>
This commit is contained in:
1 parent
fff07ada83
commit
d08189ba7d
34 files changed
+1156
-14
No files matched your search
@@ -2635,7 +2635,7 @@ function normalizeMaxRetries(value: number | undefined): number {
|
||||
const aiDeleteConfirmOpen = ref(false);
|
||||
const aiDeleteConfigId = ref<string | null>(null);
|
||||
|
||||
const CLI_AI_PROVIDERS = new Set<AiProvider>(["claude-code-cli", "codex-cli", "opencode-cli", "pi-agent-cli", "cursor-cli"]);
|
||||
const CLI_AI_PROVIDERS = new Set<AiProvider>(["claude-code-cli", "codex-cli", "opencode-cli", "pi-agent-cli", "cursor-cli", "grok-cli"]);
|
||||
const OPENCODE_CONTROL_ENV = new Set(["OPENCODE_CONFIG", "OPENCODE_CONFIG_CONTENT", "OPENCODE_CONFIG_DIR", "OPENCODE_DB", "OPENCODE_PERMISSION", "OPENCODE_DISABLE_PROJECT_CONFIG"]);
|
||||
const CURSOR_CONTROL_ENV = new Set(["CURSOR_CONFIG_DIR", "CURSOR_DATA_DIR"]);
|
||||
const aiProviderOptions = computed(() => Object.values(AI_PROVIDER_PRESETS).filter((provider) => !isWeb || !CLI_AI_PROVIDERS.has(provider.provider)));
|
||||
@@ -2663,6 +2663,8 @@ const aiEditOpenCodeCliPath = ref("");
|
||||
const aiEditOpenCodeCliEnvRows = ref<AiEnvRow[]>([]);
|
||||
const aiEditCursorCliPath = ref("");
|
||||
const aiEditCursorCliEnvRows = ref<AiEnvRow[]>([]);
|
||||
const aiEditGrokCliPath = ref("");
|
||||
const aiEditGrokCliEnvRows = ref<AiEnvRow[]>([]);
|
||||
|
||||
const aiAnthropicMessagesMode = computed(() => aiEditApiStyle.value === "anthropic-messages");
|
||||
|
||||
@@ -2695,6 +2697,7 @@ const aiIsClaudeCodeCli = computed(() => aiEditProvider.value === "claude-code-c
|
||||
const aiIsPiAgentCli = computed(() => aiEditProvider.value === "pi-agent-cli");
|
||||
const aiIsOpenCodeCli = computed(() => aiEditProvider.value === "opencode-cli");
|
||||
const aiIsCursorCli = computed(() => aiEditProvider.value === "cursor-cli");
|
||||
const aiIsGrokCli = computed(() => aiEditProvider.value === "grok-cli");
|
||||
const aiIsCliProvider = computed(() => CLI_AI_PROVIDERS.has(aiEditProvider.value));
|
||||
const aiCliProviderLabel = computed(() => selectedAiProviderPreset.value.label);
|
||||
const aiCliCommandName = computed(() => {
|
||||
@@ -2702,6 +2705,7 @@ const aiCliCommandName = computed(() => {
|
||||
if (aiIsPiAgentCli.value) return "pi";
|
||||
if (aiIsOpenCodeCli.value) return "opencode";
|
||||
if (aiIsCursorCli.value) return "agent";
|
||||
if (aiIsGrokCli.value) return "grok";
|
||||
return "codex";
|
||||
});
|
||||
const aiCliLoginCommand = computed(() => {
|
||||
@@ -2709,6 +2713,7 @@ const aiCliLoginCommand = computed(() => {
|
||||
if (aiIsPiAgentCli.value) return "pi";
|
||||
if (aiIsOpenCodeCli.value) return "opencode auth login";
|
||||
if (aiIsCursorCli.value) return "agent login";
|
||||
if (aiIsGrokCli.value) return "grok login";
|
||||
return "codex login";
|
||||
});
|
||||
const aiEditCliPath = computed({
|
||||
@@ -2717,6 +2722,7 @@ const aiEditCliPath = computed({
|
||||
if (aiIsPiAgentCli.value) return aiEditPiAgentCliPath.value;
|
||||
if (aiIsOpenCodeCli.value) return aiEditOpenCodeCliPath.value;
|
||||
if (aiIsCursorCli.value) return aiEditCursorCliPath.value;
|
||||
if (aiIsGrokCli.value) return aiEditGrokCliPath.value;
|
||||
return aiEditCodexCliPath.value;
|
||||
},
|
||||
set: (value: string) => {
|
||||
@@ -2728,6 +2734,8 @@ const aiEditCliPath = computed({
|
||||
aiEditOpenCodeCliPath.value = value;
|
||||
} else if (aiIsCursorCli.value) {
|
||||
aiEditCursorCliPath.value = value;
|
||||
} else if (aiIsGrokCli.value) {
|
||||
aiEditGrokCliPath.value = value;
|
||||
} else {
|
||||
aiEditCodexCliPath.value = value;
|
||||
}
|
||||
@@ -2738,6 +2746,7 @@ const aiEditCliEnvRows = computed(() => {
|
||||
if (aiIsPiAgentCli.value) return aiEditPiAgentCliEnvRows.value;
|
||||
if (aiIsOpenCodeCli.value) return aiEditOpenCodeCliEnvRows.value;
|
||||
if (aiIsCursorCli.value) return aiEditCursorCliEnvRows.value;
|
||||
if (aiIsGrokCli.value) return aiEditGrokCliEnvRows.value;
|
||||
return aiEditCodexCliEnvRows.value;
|
||||
});
|
||||
watch(aiIsCliProvider, (isCliProvider) => {
|
||||
@@ -2834,6 +2843,8 @@ function removeCliEnvRow(id: string) {
|
||||
aiEditOpenCodeCliEnvRows.value = aiEditOpenCodeCliEnvRows.value.filter((row) => row.id !== id);
|
||||
} else if (aiIsCursorCli.value) {
|
||||
aiEditCursorCliEnvRows.value = aiEditCursorCliEnvRows.value.filter((row) => row.id !== id);
|
||||
} else if (aiIsGrokCli.value) {
|
||||
aiEditGrokCliEnvRows.value = aiEditGrokCliEnvRows.value.filter((row) => row.id !== id);
|
||||
} else {
|
||||
aiEditCodexCliEnvRows.value = aiEditCodexCliEnvRows.value.filter((row) => row.id !== id);
|
||||
}
|
||||
@@ -2866,6 +2877,8 @@ function currentAiEditConfig() {
|
||||
opencodeCliEnv: aiIsOpenCodeCli.value ? cliEnvFromRows(aiEditOpenCodeCliEnvRows.value) : {},
|
||||
cursorCliPath: aiEditCursorCliPath.value.trim() || undefined,
|
||||
cursorCliEnv: aiIsCursorCli.value ? cliEnvFromRows(aiEditCursorCliEnvRows.value) : {},
|
||||
grokCliPath: aiEditGrokCliPath.value.trim() || undefined,
|
||||
grokCliEnv: aiIsGrokCli.value ? cliEnvFromRows(aiEditGrokCliEnvRows.value) : {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2939,6 +2952,8 @@ function aiEnterEditMode(configId?: string) {
|
||||
aiEditOpenCodeCliEnvRows.value = aiEnvRowsFromConfig(config.opencodeCliEnv);
|
||||
aiEditCursorCliPath.value = config.cursorCliPath ?? "";
|
||||
aiEditCursorCliEnvRows.value = aiEnvRowsFromConfig(config.cursorCliEnv);
|
||||
aiEditGrokCliPath.value = config.grokCliPath ?? "";
|
||||
aiEditGrokCliEnvRows.value = aiEnvRowsFromConfig(config.grokCliEnv);
|
||||
}
|
||||
} else {
|
||||
aiEditConfigName.value = "";
|
||||
@@ -2964,6 +2979,8 @@ function aiEnterEditMode(configId?: string) {
|
||||
aiEditOpenCodeCliEnvRows.value = [];
|
||||
aiEditCursorCliPath.value = "";
|
||||
aiEditCursorCliEnvRows.value = [];
|
||||
aiEditGrokCliPath.value = "";
|
||||
aiEditGrokCliEnvRows.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ watch(
|
||||
},
|
||||
);
|
||||
|
||||
const usesWhiteDarkIcon = computed(() => props.provider === "claude" || props.provider === "anthropic-compatible" || props.provider === "ollama" || props.provider === "openai" || props.provider === "openai-compatible" || props.provider === "opencode-cli" || props.provider === "cursor-cli");
|
||||
const usesWhiteDarkIcon = computed(() => props.provider === "claude" || props.provider === "anthropic-compatible" || props.provider === "ollama" || props.provider === "openai" || props.provider === "openai-compatible" || props.provider === "opencode-cli" || props.provider === "cursor-cli" || props.provider === "grok-cli");
|
||||
const localIconUrl = computed(() => {
|
||||
if (props.provider === "openai-compatible") return webPath("/icons/ai/openai.svg");
|
||||
return props.iconSlug ? webPath(`/icons/ai/${props.iconSlug}.svg`) : "";
|
||||
|
||||
Reference in new issue
Block a user