FN-9008: add Grok 4.6 model catalog support

Register Grok 4.6 across Fusion's shared Grok provider catalog.

- Add Grok 4.6 metadata and preserve it through provider merges
- Cover registration and extension model-list behavior
- Update Grok model references and publish a minor changeset

Files changed:
 .changeset/fn-9008-grok-4-6.md                    |  7 +++
 docs/grok-cli-contract.md                         |  4 +-
 docs/settings-reference.md                        |  2 +-
 packages/core/src/__tests__/grok-provider.test.ts | 54 +++++++++++++++++------
 packages/core/src/ai/grok-provider.ts             | 21 +++++++++
 5 files changed, 72 insertions(+), 16 deletions(-)

Fusion-Task-Id: FN-9008

Fusion-Task-Lineage: 5ee7688e-a1eb-4b57-8f72-1f69616ff456

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-08-12 13:29:34 -07:00
parent f85f6257f6
commit 508ebc3812
5 changed files with 72 additions and 16 deletions

View File

@@ -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`.

View File

@@ -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 # Official automation shape (docs.x.ai): suppress update checks in CI/scripts
grok --no-auto-update agent stdio grok --no-auto-update agent stdio
# with optional model + session skills plugin: # with optional model + session skills plugin:
grok --no-auto-update agent --plugin-dir <session-plugin> -m grok-4.5 stdio grok --no-auto-update agent --plugin-dir <session-plugin> -m grok-4.6 stdio
``` ```
ACP session lifecycle (official contract): 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. 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/<model>` (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/<model>` (for example `grok/grok-4.6`) before appending thinking metadata, never `undefined/undefined`.
### Session lifecycle ### Session lifecycle

View File

@@ -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. 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). 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. 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.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 <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 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.

View File

@@ -7,7 +7,7 @@ import {
} from "../ai/grok-provider.js"; } from "../ai/grok-provider.js";
import { ZAI_PROVIDER_ID, ZAI_PROVIDER_REGISTRATION, registerBuiltInZaiProvider } from "../ai/zai-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", () => { describe("GROK_PROVIDER_REGISTRATION", () => {
it("uses the xAI OpenAI-compatible endpoint and API-key auth", () => { 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"); 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", () => { it("does not copy Z.ai's thinkingFormat compat field", () => {
for (const model of GROK_PROVIDER_REGISTRATION.models) { for (const model of GROK_PROVIDER_REGISTRATION.models) {
expect(model.compat).not.toHaveProperty("thinkingFormat"); expect(model.compat).not.toHaveProperty("thinkingFormat");
@@ -87,9 +100,21 @@ describe("registerBuiltInGrokProvider", () => {
describe("mergeBuiltInGrokProviderModels", () => { describe("mergeBuiltInGrokProviderModels", () => {
it("re-adds missing built-in models after a user grok extension replacement", () => { it("re-adds missing built-in models after a user grok extension replacement", () => {
const extensionModels = GROK_PROVIDER_REGISTRATION.models const extensionModels = [
.filter((model) => model.id !== "grok-4.5") ...GROK_PROVIDER_REGISTRATION.models
.map((model) => ({ ...model })); .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<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>(); const registeredProviders = new Map<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>();
const registry = { const registry = {
registeredProviders, registeredProviders,
@@ -109,22 +134,25 @@ describe("mergeBuiltInGrokProviderModels", () => {
const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id);
expect(mergedIds).toEqual(expect.arrayContaining(EXPECTED_GROK_MODELS)); 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"); 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<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>(); const registeredProviders = new Map<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>();
const registry = { const registerProvider = (providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) => {
registeredProviders, registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config });
registerProvider(providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) {
registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config });
},
}; };
const registry = { registeredProviders, registerProvider };
registerBuiltInGrokProvider(registry); registerBuiltInGrokProvider(registry);
const before = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id);
expect(() => mergeBuiltInGrokProviderModels(registry)).not.toThrow(); expect(() => mergeBuiltInGrokProviderModels(registry)).not.toThrow();
const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id); const after = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id);
expect(mergedIds).toEqual(EXPECTED_GROK_MODELS);
expect(before).toEqual(EXPECTED_GROK_MODELS);
expect(after).toEqual(before);
expect(after?.filter((id) => id === "grok-4.6")).toHaveLength(1);
}); });
}); });

View File

@@ -62,6 +62,27 @@ export const GROK_PROVIDER_REGISTRATION: GrokProviderRegistration = {
apiKey: "$GROK_API_KEY", apiKey: "$GROK_API_KEY",
api: "openai-completions", api: "openai-completions",
models: [ 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", id: "grok-4.5",
name: "Grok 4.5", name: "Grok 4.5",