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:
chenow9andt8y2 authored and GitHub committed 2026-08-10 10:54:27 +08:00
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`) : "";