FN-7753: route grok-cli execution through the grok CLI when no Fusion-visible GROK_API_KEY resolves
Route grok-cli model selections through the grok CLI runtime when no Fusion-visible GROK_API_KEY is available. - Add read-only isGrokApiKeyFusionVisible() in packages/core/src/grok-provider.ts, refactored to share user-settings-file reading with hydrateGrokApiKeyFromUserSettings without mutating process.env or logging key material. - In packages/engine/src/agent-session-helpers.ts, auto-derive the existing "grok" runtimeHint when defaultProvider is grok-cli, no key is Fusion-visible, and the grok plugin runtime is registered; explicit runtime hints and mock/test-mode routing remain unchanged, and the provider-qualified model prefix is stripped before handoff. - Normalize provider-qualified model ids (grok-cli/<id>, grok/<id>) in the grok-runtime plugin's runtime-adapter and CLI stream spawn so the concrete model reaches `grok --model`, with the historical grok/default fallback preserved for the no-model path. - Update docs (grok-cli-contract.md, settings-reference.md, plugin README) and add/extend tests covering the new fallback behavior, model normalization, and CLI streaming. - Add changeset fn-7753-grok-cli-no-key-fallback.md (patch, fix). Files changed: .changeset/fn-7753-grok-cli-no-key-fallback.md | 7 ++ docs/grok-cli-contract.md | 83 ++++++++++------ docs/settings-reference.md | 6 +- .../__tests__/grok-provider-user-settings.test.ts | 46 +++++++++ packages/core/src/grok-provider.ts | 39 +++++++- packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + .../src/__tests__/grok-runtime-routing.test.ts | 107 +++++++++++++++++++-- packages/engine/src/agent-session-helpers.ts | 52 +++++++++- plugins/fusion-plugin-grok-runtime/README.md | 46 +++++---- .../src/__tests__/cli-stream.test.ts | 70 ++++++++++++++ .../src/__tests__/runtime-adapter.test.ts | 28 ++++++ .../fusion-plugin-grok-runtime/src/cli-stream.ts | 6 ++ .../src/runtime-adapter.ts | 24 ++++- 14 files changed, 443 insertions(+), 73 deletions(-) Fusion-Task-Id: FN-7753 Fusion-Task-Lineage: 30ef7265-1ba9-47fd-8c4e-87b02f6a1d78 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -134,7 +134,7 @@ Notes:
|
||||
`GrokCliProviderCard.tsx`) is out of scope for this task and is not
|
||||
modified here.
|
||||
|
||||
## Wiring (resolved — FN-7725)
|
||||
## Wiring (resolved — FN-7725, extended by FN-7753)
|
||||
|
||||
<!--
|
||||
FNXC:GrokCli 2026-07-09-00:00:
|
||||
@@ -148,14 +148,33 @@ default and unchanged.
|
||||
|
||||
**Decision: option (a) — formalize, document, and test the existing agent
|
||||
Runtime-mode picker path. Do NOT add a new settings toggle (option (b)).**
|
||||
FN-7753 later closed the deferred no-key model-selection fallback without adding
|
||||
that rejected UI toggle: the session seam derives the same runtime hint
|
||||
automatically only when the direct endpoint cannot work because no Fusion-visible
|
||||
GROK_API_KEY resolves.
|
||||
|
||||
**Trigger:** an agent's `runtimeConfig.runtimeHint === "grok"`, set today via
|
||||
the dashboard's agent **Runtime Source → Runtime** picker
|
||||
**Explicit trigger:** an agent's `runtimeConfig.runtimeHint === "grok"`, set today
|
||||
via the dashboard's agent **Runtime Source → Runtime** picker
|
||||
(`NewAgentDialog.tsx` / `AgentDetailView.tsx`), which is populated from
|
||||
`GET /api/plugins/runtimes` (already generic — surfaces every registered
|
||||
plugin runtime, including the bundled Grok Runtime plugin's `runtimeId:
|
||||
"grok"`, with no Grok-specific code required).
|
||||
|
||||
**Automatic no-key fallback (FN-7753):** when `createResolvedAgentSession()` sees
|
||||
all of the following, it derives the same effective `runtimeHint: "grok"` before
|
||||
calling `resolveRuntime()`:
|
||||
|
||||
1. no explicit runtime hint was supplied (explicit hints, including `"pi"`,
|
||||
always win);
|
||||
2. the resolved execution provider is `grok-cli`;
|
||||
3. Fusion cannot see a non-empty `GROK_API_KEY` either in the environment or in
|
||||
`~/.grok/user-settings.json`'s `apiKey` field; and
|
||||
4. the bundled Grok Runtime plugin has registered runtime id `"grok"`.
|
||||
|
||||
If a Fusion-visible key exists, the direct xAI OpenAI-compatible endpoint remains
|
||||
the default. If the Grok runtime is not registered, Fusion leaves the session on
|
||||
the existing PI/direct path rather than inventing a separate routing mode.
|
||||
|
||||
**Exact seam:** `packages/engine/src/agent-session-helpers.ts`'s
|
||||
`extractRuntimeHint(runtimeConfig)` reads that hint from the assigned agent's
|
||||
`runtimeConfig` and threads it, as `runtimeHint`, into
|
||||
@@ -188,24 +207,21 @@ additive change," formalizing + testing + documenting the already-working
|
||||
path is lower risk and closes the actual gap (an *exercised* path, not just
|
||||
an implemented adapter) without adding new user-facing config surface.
|
||||
|
||||
**Known limitation (by design, unchanged by this task):** Runtime-mode is
|
||||
model-agnostic — `NewAgentDialog.tsx`/`AgentDetailView.tsx` clear the `model`
|
||||
field when Runtime mode is selected (`model: runtimeMode === "runtime" ? ""
|
||||
: ...`), so `GrokRuntimeAdapter.createSession()` never receives a
|
||||
`defaultModelId` from this path and always falls back to `"grok/default"`. A
|
||||
specific `grok-cli/*` model choice is therefore not preserved when routing
|
||||
via Runtime-mode. Preserving model selection through the CLI runtime would
|
||||
require option (b) (or an equivalent); it is filed as a follow-up task only
|
||||
if genuinely warranted (see Follow-ups below), not implemented here.
|
||||
**Model plumbing (FN-7753):** for the automatic no-key fallback, the selected
|
||||
`grok-cli/*` model id is preserved through `AgentRuntimeOptions.defaultModelId`,
|
||||
normalized by stripping a leading `grok-cli/` (or `grok/`) prefix, and passed to
|
||||
the CLI as `grok --model <id>` alongside `--prompt` and `--format json`.
|
||||
Runtime-mode remains model-agnostic when chosen explicitly from the dashboard;
|
||||
that no-model path still uses the adapter's historical `"grok/default"` session
|
||||
fallback and omits `--model`.
|
||||
|
||||
**Why the direct xAI endpoint stays default:** nothing in this task changes
|
||||
what a `grok-cli/*` **model** selection does — it continues to route through
|
||||
the direct xAI OpenAI-compatible endpoint (FN-7711/FN-7714,
|
||||
`packages/core/src/grok-provider.ts`, `packages/engine/src/pi.ts`), which
|
||||
this task does not touch. The CLI-routed path is reached *only* by the
|
||||
separate, explicit agent Runtime-mode choice — an opt-in, additive,
|
||||
fully-reversible path (nothing sets the hint unless an operator explicitly
|
||||
picks Runtime mode for that agent).
|
||||
**Why the direct xAI endpoint stays default:** a `grok-cli/*` **model** selection
|
||||
continues to route through the direct xAI OpenAI-compatible endpoint
|
||||
(FN-7711/FN-7714, `packages/core/src/grok-provider.ts`, `packages/engine/src/pi.ts`)
|
||||
whenever Fusion can see a key. FN-7753 changes only the failing no-visible-key
|
||||
case, where the direct path would otherwise hard-fail even though the installed
|
||||
CLI may be authenticated by a source Fusion cannot inspect (project `.env`,
|
||||
`grok -k`, OAuth/login token store, sandbox secrets, etc.).
|
||||
|
||||
## Decision
|
||||
|
||||
@@ -228,17 +244,18 @@ Rationale:
|
||||
tool-call/break-early bridging as a documented follow-up (the Droid
|
||||
adapter's much larger `provider.ts` is the effort ceiling, not the target
|
||||
shape).
|
||||
- It is fully reversible: the adapter is only reachable via
|
||||
`runtimeHint === "grok"`, which nothing sets today, so landing it carries
|
||||
no behavioral change to any exercised path.
|
||||
- It is fully reversible: the adapter is reachable via either an explicit
|
||||
`runtimeHint === "grok"` or FN-7753's narrow no-visible-key `grok-cli`
|
||||
fallback. The direct endpoint remains the key-visible default.
|
||||
|
||||
## What stays unchanged
|
||||
|
||||
- The **direct xAI OpenAI-compatible streaming path** (base URL
|
||||
`https://api.x.ai/v1`, api type `openai-completions`, `GROK_API_KEY`
|
||||
sourced per FN-7711/FN-7714) remains the default, exercised Grok execution
|
||||
path. This task does not touch `packages/core/src/grok-provider.ts` or
|
||||
`packages/engine/src/pi.ts`.
|
||||
sourced per FN-7711/FN-7714) remains the default when a Fusion-visible key
|
||||
exists. FN-7753 adds a read-only key-visibility check in
|
||||
`packages/core/src/grok-provider.ts` and derives CLI routing only when no
|
||||
such key is visible and the Grok runtime is registered.
|
||||
- FN-7716's probe/auth-readiness surface (`probe.ts`,
|
||||
`register-auth-routes.ts`, `GrokCliProviderCard.tsx`) is untouched by this
|
||||
task.
|
||||
@@ -265,8 +282,10 @@ See the task's `fn_task_create` calls (linked from FN-7722) for:
|
||||
`close`/`error`, unchanged from FN-7722. See
|
||||
`plugins/fusion-plugin-grok-runtime/README.md`'s "Tool execution
|
||||
bridging (FN-7724)" section.
|
||||
3. (Filed by FN-7725, if warranted) Preserving a specific `grok-cli/*` model
|
||||
selection when routing through the CLI runtime (Runtime-mode is currently
|
||||
model-agnostic — see "Known limitation" in "Wiring" above). This is the
|
||||
deferred option (b) shape; only file it if a genuine operator need
|
||||
surfaces, per the task's Decision guidance.
|
||||
3. ~~Preserving a specific `grok-cli/*` model selection when routing through
|
||||
the CLI runtime~~ — **closed by FN-7753** for the automatic no-visible-key
|
||||
fallback: `createResolvedAgentSession()` derives runtime hint `"grok"` only
|
||||
when no explicit hint is set, provider is `grok-cli`, no Fusion-visible
|
||||
`GROK_API_KEY`/user-settings `apiKey` resolves, and runtime id `"grok"` is
|
||||
registered; the selected model is passed to the CLI via `--model <id>`.
|
||||
Explicit Runtime-mode remains model-agnostic by design.
|
||||
|
||||
@@ -104,7 +104,7 @@ Fusion automatically falls back to ntfy's JSON publish format when a notificatio
|
||||
| `modelOnboardingComplete` | `boolean` | `undefined` | Whether AI onboarding has been completed or dismissed. |
|
||||
| `useCursorCli` | `boolean` | `undefined` | Enables the `cursor-cli` provider in model pickers after Cursor CLI status validation. Toggle from Settings → Authentication. |
|
||||
| `cursorCliBinaryPath` | `string` | `undefined` | Optional global, machine-local Cursor CLI executable override used by Settings → Authentication, status/enable validation, probes, and model discovery. Leave unset/blank to auto-detect `cursor-agent` then `cursor` on PATH. Use this when PATH points at the wrong Cursor install or Windows exposes a specific `.cmd`/`.bat` shim; invalid non-empty saves are rejected with bounded diagnostics. |
|
||||
| `useGrokCli` | `boolean` | `undefined` | Enables the `grok-cli` provider in model pickers after Grok CLI status validation. Toggle from Settings → Authentication. Grok is API-key auth (`GROK_API_KEY` env var or `~/.grok/user-settings.json` `apiKey`) — there is no OAuth/session login flow. |
|
||||
| `useGrokCli` | `boolean` | `undefined` | Enables the `grok-cli` provider in model pickers after Grok CLI status validation. Toggle from Settings → Authentication. Grok's direct xAI endpoint uses API-key auth (`GROK_API_KEY` env var or `~/.grok/user-settings.json` `apiKey`); when a `grok-cli/*` execution model has no Fusion-visible key, Fusion falls back to the `grok` CLI runtime if registered so the CLI can use its own auth store. |
|
||||
| `grokCliBinaryPath` | `string` | `undefined` | Optional global, machine-local Grok CLI executable override used by Settings → Authentication, status/enable validation, probes, and model discovery. Leave unset/blank to auto-detect `grok` on PATH. Invalid non-empty saves are rejected with bounded diagnostics. |
|
||||
| `executionGlobalProvider` | `string` | `undefined` | Global baseline provider for task execution. Project `executionProvider` overrides this. |
|
||||
| `executionGlobalModelId` | `string` | `undefined` | Global baseline model ID for task execution. |
|
||||
@@ -969,13 +969,13 @@ When the planning lane has neither `planningFallback*` nor a global `fallback*`
|
||||
|
||||
Z.ai's built-in provider uses the existing `zai` auth entry / `ZAI_API_KEY` environment variable and includes `zai/glm-5.2` as a selectable model in the same dropdowns and workflow lane controls as the other built-in GLM models. If a pi extension also registers the `zai` provider, Fusion preserves the extension's models and re-adds any missing built-in Z.ai models so built-in GLM choices remain available.
|
||||
|
||||
Grok (`grok-cli`) is likewise seeded as a built-in provider — xAI's OpenAI-compatible endpoint (`https://api.x.ai/v1`, api type `openai-completions`), API key `GROK_API_KEY` — into every model registry Fusion seeds (task execution, dashboard `/api/models`, and CLI `serve`/`daemon`/`dashboard`), mirroring the Z.ai pattern above. This makes `grok-cli/<model>` selections (e.g. `grok-cli/grok-4.5`) resolvable for execution even before the `grok` CLI binary is discovered or the picker surfaces additional Grok models (see the CLI-discovery paragraph below); a missing `GROK_API_KEY` surfaces only as a normal auth error at stream time, not a model-resolution failure. If `GROK_API_KEY` is not set in the environment, provider registration falls back to `~/.grok/user-settings.json`'s `apiKey` field (the same file the `grok` CLI itself writes on login) and hydrates `process.env.GROK_API_KEY` from it, so an operator who authenticated via the `grok` CLI but never exported the env var still resolves a key; an already-set env var always wins, and a missing/malformed/empty settings file is fail-soft (no error, no env mutation).
|
||||
Grok (`grok-cli`) is likewise seeded as a built-in provider — xAI's OpenAI-compatible endpoint (`https://api.x.ai/v1`, api type `openai-completions`), API key `GROK_API_KEY` — into every model registry Fusion seeds (task execution, dashboard `/api/models`, and CLI `serve`/`daemon`/`dashboard`), mirroring the Z.ai pattern above. This makes `grok-cli/<model>` selections (e.g. `grok-cli/grok-4.5`) resolvable for execution even before the `grok` CLI binary is discovered or the picker surfaces additional Grok models (see the CLI-discovery paragraph below). If `GROK_API_KEY` is not set in the environment, provider registration falls back to `~/.grok/user-settings.json`'s `apiKey` field (the same file the `grok` CLI itself writes on login) and hydrates `process.env.GROK_API_KEY` from it, so an operator who authenticated via the `grok` CLI but never exported the env var still resolves a key; an already-set env var always wins, and a missing/malformed/empty settings file is fail-soft (no error, no env mutation). When no Fusion-visible key resolves and the Grok Runtime plugin has registered runtime id `grok`, `createResolvedAgentSession()` derives that runtime automatically for `grok-cli` execution and passes the selected model to the CLI via `--model <id>`; explicit runtime hints and key-visible direct-endpoint routing take precedence.
|
||||
|
||||
When the Hermes Runtime plugin (`fusion-plugin-hermes-runtime`) is installed and the local `hermes` CLI has configured profiles (`hermes profile list`), those profiles are surfaced additively in `/api/models` under the `hermes` provider — one row per profile, id/name derived from the profile name and its configured model. This surfacing is read-only (Fusion does not create or edit Hermes profiles) and is fetched through a short-TTL, single-flight cache so the model picker never spawns the `hermes` CLI on every request; a missing/failed `hermes` binary simply yields zero Hermes rows without affecting other providers.
|
||||
|
||||
When the Cursor Runtime plugin (`fusion-plugin-cursor-runtime`) is installed and the `useCursorCli` toggle is enabled (Settings → Authentication), Cursor CLI-discovered models (`cursor-agent models --json`, with text/`model list` fallbacks) are surfaced additively in `/api/models` under the `cursor-cli` provider — id/name derived from the discovered model id/label. This surfacing is fetched through a short-TTL, single-flight cache so the model picker never spawns `cursor-agent` on every request; a missing/failed/unavailable Cursor CLI binary simply yields zero `cursor-cli` rows without affecting other providers. Disabling `useCursorCli` hides all `cursor-cli` rows.
|
||||
|
||||
When the Grok Runtime plugin (`fusion-plugin-grok-runtime`) is installed and the `useGrokCli` toggle is enabled (Settings → Authentication), Grok CLI-discovered models (`grok models`) are surfaced additively in `/api/models` under the `grok-cli` provider — id/name derived from the discovered model id/label. This surfacing is fetched through a short-TTL, single-flight cache so the model picker never spawns `grok` on every request; a missing/failed/unavailable Grok CLI binary simply yields zero `grok-cli` rows without affecting other providers. Disabling `useGrokCli` hides all `grok-cli` rows. Unlike Cursor (OAuth/session auth), Grok is API-key auth: the Settings card's status text guides operators to `GROK_API_KEY` or `~/.grok/user-settings.json` when the binary is available but no key is configured.
|
||||
When the Grok Runtime plugin (`fusion-plugin-grok-runtime`) is installed and the `useGrokCli` toggle is enabled (Settings → Authentication), Grok CLI-discovered models (`grok models`) are surfaced additively in `/api/models` under the `grok-cli` provider — id/name derived from the discovered model id/label. This surfacing is fetched through a short-TTL, single-flight cache so the model picker never spawns `grok` on every request; a missing/failed/unavailable Grok CLI binary simply yields zero `grok-cli` rows without affecting other providers. Disabling `useGrokCli` hides all `grok-cli` rows. Unlike Cursor (OAuth/session auth), Grok direct-endpoint auth is API-key based, but CLI-routed execution lets the `grok` binary use any auth source it supports; the Settings card still surfaces Fusion-visible key detection only as an informational hint.
|
||||
|
||||
The three GPT-5.6 codenamed OpenAI Codex variants (`gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`) are additively surfaced under the `openai-codex` provider (FN-7745/FN-7754, mirroring the Anthropic/Z.ai supplemental-merge pattern above) so they appear both in dashboard `/api/models` and the engine/pi `createFnAgent` registry-seeding surface whenever `openai-codex` is configured — deduped against any pinned pi-ai catalog row that already carries one of the ids.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user