diff --git a/.changeset/fn-9008-grok-4-6.md b/.changeset/fn-9008-grok-4-6.md new file mode 100644 index 0000000000..4bddf16026 --- /dev/null +++ b/.changeset/fn-9008-grok-4-6.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add Grok 4.6 to the built-in Grok model catalog across every model picker. +category: feature +dev: Registers Grok 4.6 in `GROK_PROVIDER_REGISTRATION` in `packages/core/src/ai/grok-provider.ts`, which fans out through `seedDashboardProviders` and `pi.ts`. diff --git a/docs/grok-cli-contract.md b/docs/grok-cli-contract.md index b78451e7b9..c1b4f1befc 100644 --- a/docs/grok-cli-contract.md +++ b/docs/grok-cli-contract.md @@ -35,7 +35,7 @@ Fusion's `GrokRuntimeAdapter` drives Grok as an ACP (Agent Client Protocol) agen # Official automation shape (docs.x.ai): suppress update checks in CI/scripts grok --no-auto-update agent stdio # with optional model + session skills plugin: -grok --no-auto-update agent --plugin-dir -m grok-4.5 stdio +grok --no-auto-update agent --plugin-dir -m grok-4.6 stdio ``` ACP session lifecycle (official contract): @@ -77,7 +77,7 @@ mcpServers through createResolvedAgentSession. Grok ACP must not drop them. When custom Fusion tools were requested but the bridge cannot start, the session emits `FUSION_TOOL_BRIDGE_FAILED: mcp-schema-server-missing` or `FUSION_TOOL_BRIDGE_FAILED: bridge-start-failed`. It deliberately omits the broken MCP server entry, and the engine records `fusionToolBridgeFailed`, its fixed reason code, and a requested-tool count on the ids-only `session:runtime-resolved` run-audit event. Paths, schemas, error prose, and credentials are not persisted there. -Grok ACP sessions store a string model plus `lastModelDescription`; lane markers normalize this to `grok/` (for example `grok/grok-4.5`) before appending thinking metadata, never `undefined/undefined`. +Grok ACP sessions store a string model plus `lastModelDescription`; lane markers normalize this to `grok/` (for example `grok/grok-4.6`) before appending thinking metadata, never `undefined/undefined`. ### Session lifecycle diff --git a/docs/settings-reference.md b/docs/settings-reference.md index 8e9dcc599c..2b0f62f75b 100644 --- a/docs/settings-reference.md +++ b/docs/settings-reference.md @@ -1140,7 +1140,7 @@ 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/` 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 `; explicit runtime hints and key-visible direct-endpoint routing take precedence. +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/` selections (e.g. `grok-cli/grok-4.6`) 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 `; 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. diff --git a/packages/core/src/__tests__/grok-provider.test.ts b/packages/core/src/__tests__/grok-provider.test.ts index 68557667d4..afc06d850b 100644 --- a/packages/core/src/__tests__/grok-provider.test.ts +++ b/packages/core/src/__tests__/grok-provider.test.ts @@ -7,7 +7,7 @@ import { } from "../ai/grok-provider.js"; import { ZAI_PROVIDER_ID, ZAI_PROVIDER_REGISTRATION, registerBuiltInZaiProvider } from "../ai/zai-provider.js"; -const EXPECTED_GROK_MODELS = ["grok-4.5", "grok-4", "grok-code-fast-1", "grok-3", "grok-3-mini"]; +const EXPECTED_GROK_MODELS = ["grok-4.6", "grok-4.5", "grok-4", "grok-code-fast-1", "grok-3", "grok-3-mini"]; describe("GROK_PROVIDER_REGISTRATION", () => { it("uses the xAI OpenAI-compatible endpoint and API-key auth", () => { @@ -26,6 +26,19 @@ describe("GROK_PROVIDER_REGISTRATION", () => { expect(modelIds).toContain("grok-4.5"); }); + it("registers Grok 4.6 with its compatible model shape", () => { + const grok46 = GROK_PROVIDER_REGISTRATION.models.find((model) => model.id === "grok-4.6"); + + expect(grok46).toMatchObject({ + id: "grok-4.6", + name: "Grok 4.6", + reasoning: true, + }); + expect(grok46?.contextWindow).toBeGreaterThan(0); + expect(grok46?.maxTokens).toBeGreaterThan(0); + expect(grok46?.compat).not.toHaveProperty("thinkingFormat"); + }); + it("does not copy Z.ai's thinkingFormat compat field", () => { for (const model of GROK_PROVIDER_REGISTRATION.models) { expect(model.compat).not.toHaveProperty("thinkingFormat"); @@ -87,9 +100,21 @@ describe("registerBuiltInGrokProvider", () => { describe("mergeBuiltInGrokProviderModels", () => { it("re-adds missing built-in models after a user grok extension replacement", () => { - const extensionModels = GROK_PROVIDER_REGISTRATION.models - .filter((model) => model.id !== "grok-4.5") - .map((model) => ({ ...model })); + const extensionModels = [ + ...GROK_PROVIDER_REGISTRATION.models + .filter((model) => model.id !== "grok-4.6") + .map((model) => ({ ...model })), + { + id: "extension-grok-preview", + name: "Extension Grok Preview", + reasoning: false, + input: ["text"] as const, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 1024, + maxTokens: 512, + compat: { supportsDeveloperRole: false }, + }, + ]; const registeredProviders = new Map>(); const registry = { registeredProviders, @@ -109,22 +134,25 @@ describe("mergeBuiltInGrokProviderModels", () => { const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); expect(mergedIds).toEqual(expect.arrayContaining(EXPECTED_GROK_MODELS)); - expect(mergedIds).toContain("grok-4.5"); + expect(mergedIds).toContain("grok-4.6"); + expect(mergedIds).toContain("extension-grok-preview"); expect(registeredProviders.get(GROK_CLI_PROVIDER_ID)?.name).toBe("User Grok extension"); }); - it("is a no-op when all built-in models are already present", () => { + it("does not duplicate, reorder, or drop models when Grok 4.6 is already present", () => { const registeredProviders = new Map>(); - const registry = { - registeredProviders, - registerProvider(providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) { - registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config }); - }, + const registerProvider = (providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) => { + registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config }); }; + const registry = { registeredProviders, registerProvider }; registerBuiltInGrokProvider(registry); + const before = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); expect(() => mergeBuiltInGrokProviderModels(registry)).not.toThrow(); - const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); - expect(mergedIds).toEqual(EXPECTED_GROK_MODELS); + const after = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); + + expect(before).toEqual(EXPECTED_GROK_MODELS); + expect(after).toEqual(before); + expect(after?.filter((id) => id === "grok-4.6")).toHaveLength(1); }); }); diff --git a/packages/core/src/ai/grok-provider.ts b/packages/core/src/ai/grok-provider.ts index c434d1368a..31f56375a9 100644 --- a/packages/core/src/ai/grok-provider.ts +++ b/packages/core/src/ai/grok-provider.ts @@ -62,6 +62,27 @@ export const GROK_PROVIDER_REGISTRATION: GrokProviderRegistration = { apiKey: "$GROK_API_KEY", api: "openai-completions", models: [ + /* + FNXC:ModelRegistry 2026-08-12-20:17: + FN-9008 requires Grok 4.6 to be selectable in every model area. Keep it in this shared + catalog because pi's registerProvider() replaces provider model lists wholesale, while every + Fusion host seeds and re-merges this export. xAI's model docs verify grok-4.6, its 500k-token + context window, and configurable reasoning; max output and per-model image input remain + upstream-pending-verification, so retain the conservative text-only and 65536-token defaults. + Source: https://docs.x.ai/developers/models + */ + { + id: "grok-4.6", + name: "Grok 4.6", + reasoning: true, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 500000, + maxTokens: 65536, + compat: { + supportsDeveloperRole: false, + }, + }, { id: "grok-4.5", name: "Grok 4.5",