FN-7711: add built-in Grok CLI provider to fix pi model registry lookup
Registers a built-in grok-cli provider so Grok CLI model executions no longer hard-fail with "not found in the pi model registry". - Add packages/core/src/grok-provider.ts: built-in grok-cli provider config (xAI OpenAI-compatible endpoint https://api.x.ai/v1, api openai-completions, apiKey $GROK_API_KEY), mirroring the existing Z.ai provider - Register the provider in packages/engine/src/pi.ts (registerExtensionProviders) and packages/engine/src/provider-registration.ts (seedDashboardProviders) - Wire the provider into CLI entrypoints: packages/cli/src/commands/daemon.ts, dashboard.ts, serve.ts - Export grok-provider from packages/core/src/index.ts and packages/core/src/index.gate.ts - Add unit tests: packages/core/src/__tests__/grok-provider.test.ts, and extend packages/engine/src/__tests__/pi-create-fn-agent.test.ts and provider-registration.test.ts - Document the new provider in docs/settings-reference.md - Add changeset .changeset/fn-7711-grok-cli-model-registry.md (patch, category: fix) Note: Grok CLI binary remains discovery/probe only; GrokRuntimeAdapter streaming is a stub (tracked follow-up). Files changed: .changeset/fn-7711-grok-cli-model-registry.md | 7 + docs/settings-reference.md | 2 + packages/cli/src/commands/daemon.ts | 4 + packages/cli/src/commands/dashboard.ts | 4 + packages/cli/src/commands/serve.ts | 4 + packages/core/src/__tests__/grok-provider.test.ts | 130 ++++++++++++ packages/core/src/grok-provider.ts | 224 +++++++++++++++++++++ packages/core/src/index.gate.ts | 7 + packages/core/src/index.ts | 7 + .../src/__tests__/pi-create-fn-agent.test.ts | 78 ++++++- .../src/__tests__/provider-registration.test.ts | 4 +- packages/engine/src/pi.ts | 4 + packages/engine/src/provider-registration.ts | 4 + 13 files changed, 476 insertions(+), 3 deletions(-) Fusion-Task-Id: FN-7711 Fusion-Task-Lineage: ae90b54f-206e-46fd-8365-b0a4488ceb84 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7711-grok-cli-model-registry.md
Normal file
7
.changeset/fn-7711-grok-cli-model-registry.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Grok CLI models now run instead of failing with "not found in the pi model registry".
|
||||||
|
category: fix
|
||||||
|
dev: Adds a built-in grok-cli provider (packages/core/src/grok-provider.ts) — xAI OpenAI-compatible endpoint https://api.x.ai/v1, api openai-completions, apiKey $GROK_API_KEY — registered into the execution registry (pi.ts registerExtensionProviders), seedDashboardProviders, and CLI serve/daemon/dashboard, mirroring the built-in Z.ai provider. Grok CLI binary remains discovery/probe only; GrokRuntimeAdapter streaming is still a stub (tracked follow-up).
|
||||||
@@ -966,6 +966,8 @@ Fusion resolves task models through workflow-backed lane values first, then glob
|
|||||||
|
|
||||||
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); a missing `GROK_API_KEY` surfaces only as a normal auth error at stream time, not a model-resolution failure.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
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 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.
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import {
|
|||||||
GlobalSettingsStore,
|
GlobalSettingsStore,
|
||||||
resolveGlobalDir,
|
resolveGlobalDir,
|
||||||
getEnabledPiExtensionPaths,
|
getEnabledPiExtensionPaths,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
mergeBuiltInZaiProviderModels,
|
mergeBuiltInZaiProviderModels,
|
||||||
reconcileClaudeCliPaths,
|
reconcileClaudeCliPaths,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
} from "@fusion/core";
|
} from "@fusion/core";
|
||||||
import type { AutomationRunResult, ScheduledTask } from "@fusion/core";
|
import type { AutomationRunResult, ScheduledTask } from "@fusion/core";
|
||||||
@@ -550,6 +552,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
|||||||
const authStorage = createFusionAuthStorage();
|
const authStorage = createFusionAuthStorage();
|
||||||
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
||||||
registerBuiltInZaiProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
registerBuiltInZaiProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
|
registerBuiltInGrokProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
||||||
|
|
||||||
// PackageManager may be used for skills adapter even if extension loading fails
|
// PackageManager may be used for skills adapter even if extension loading fails
|
||||||
@@ -665,6 +668,7 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
|||||||
|
|
||||||
extensionsResult.runtime.pendingProviderRegistrations = [];
|
extensionsResult.runtime.pendingProviderRegistrations = [];
|
||||||
mergeBuiltInZaiProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
mergeBuiltInZaiProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
|
mergeBuiltInGrokProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
modelRegistry.refresh();
|
modelRegistry.refresh();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import {
|
|||||||
isWorkspaceTask,
|
isWorkspaceTask,
|
||||||
resolveColumnFlags,
|
resolveColumnFlags,
|
||||||
BUILTIN_CODING_WORKFLOW_IR,
|
BUILTIN_CODING_WORKFLOW_IR,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
mergeBuiltInZaiProviderModels,
|
mergeBuiltInZaiProviderModels,
|
||||||
parseWorkflowIr,
|
parseWorkflowIr,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
type WorkflowIrColumn,
|
type WorkflowIrColumn,
|
||||||
type TraitFlags,
|
type TraitFlags,
|
||||||
@@ -1485,6 +1487,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
|||||||
const authStorage = createFusionAuthStorage();
|
const authStorage = createFusionAuthStorage();
|
||||||
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
||||||
registerBuiltInZaiProvider(modelRegistry, (message) => logSink.log(message, "extensions"));
|
registerBuiltInZaiProvider(modelRegistry, (message) => logSink.log(message, "extensions"));
|
||||||
|
registerBuiltInGrokProvider(modelRegistry, (message) => logSink.log(message, "extensions"));
|
||||||
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
||||||
|
|
||||||
// PackageManager may be used for skills adapter even if extension loading fails.
|
// PackageManager may be used for skills adapter even if extension loading fails.
|
||||||
@@ -1605,6 +1608,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
|||||||
|
|
||||||
extensionsResult.runtime.pendingProviderRegistrations = [];
|
extensionsResult.runtime.pendingProviderRegistrations = [];
|
||||||
mergeBuiltInZaiProviderModels(modelRegistry, (message) => logSink.log(message, "extensions"));
|
mergeBuiltInZaiProviderModels(modelRegistry, (message) => logSink.log(message, "extensions"));
|
||||||
|
mergeBuiltInGrokProviderModels(modelRegistry, (message) => logSink.log(message, "extensions"));
|
||||||
modelRegistry.refresh();
|
modelRegistry.refresh();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import {
|
|||||||
GlobalSettingsStore,
|
GlobalSettingsStore,
|
||||||
resolveGlobalDir,
|
resolveGlobalDir,
|
||||||
getEnabledPiExtensionPaths,
|
getEnabledPiExtensionPaths,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
mergeBuiltInZaiProviderModels,
|
mergeBuiltInZaiProviderModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
} from "@fusion/core";
|
} from "@fusion/core";
|
||||||
import type { AutomationRunResult, ScheduledTask } from "@fusion/core";
|
import type { AutomationRunResult, ScheduledTask } from "@fusion/core";
|
||||||
@@ -599,6 +601,7 @@ export async function runServe(
|
|||||||
const authStorage = createFusionAuthStorage();
|
const authStorage = createFusionAuthStorage();
|
||||||
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
||||||
registerBuiltInZaiProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
registerBuiltInZaiProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
|
registerBuiltInGrokProvider(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
||||||
|
|
||||||
// PackageManager may be used for skills adapter even if extension loading fails
|
// PackageManager may be used for skills adapter even if extension loading fails
|
||||||
@@ -715,6 +718,7 @@ export async function runServe(
|
|||||||
|
|
||||||
extensionsResult.runtime.pendingProviderRegistrations = [];
|
extensionsResult.runtime.pendingProviderRegistrations = [];
|
||||||
mergeBuiltInZaiProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
mergeBuiltInZaiProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
|
mergeBuiltInGrokProviderModels(modelRegistry, (message) => console.log(`[extensions] ${message}`));
|
||||||
modelRegistry.refresh();
|
modelRegistry.refresh();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
130
packages/core/src/__tests__/grok-provider.test.ts
Normal file
130
packages/core/src/__tests__/grok-provider.test.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
GROK_CLI_PROVIDER_ID,
|
||||||
|
GROK_PROVIDER_REGISTRATION,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
|
} from "../grok-provider.js";
|
||||||
|
import { ZAI_PROVIDER_ID, ZAI_PROVIDER_REGISTRATION, registerBuiltInZaiProvider } from "../zai-provider.js";
|
||||||
|
|
||||||
|
const EXPECTED_GROK_MODELS = ["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", () => {
|
||||||
|
expect(GROK_CLI_PROVIDER_ID).toBe("grok-cli");
|
||||||
|
expect(GROK_PROVIDER_REGISTRATION).toMatchObject({
|
||||||
|
name: "Grok",
|
||||||
|
baseUrl: "https://api.x.ai/v1",
|
||||||
|
apiKey: "$GROK_API_KEY",
|
||||||
|
api: "openai-completions",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("seeds the reported default model plus a conservative catalog", () => {
|
||||||
|
const modelIds = GROK_PROVIDER_REGISTRATION.models.map((model) => model.id);
|
||||||
|
expect(modelIds).toEqual(EXPECTED_GROK_MODELS);
|
||||||
|
expect(modelIds).toContain("grok-4.5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not copy Z.ai's thinkingFormat compat field", () => {
|
||||||
|
for (const model of GROK_PROVIDER_REGISTRATION.models) {
|
||||||
|
expect(model.compat).not.toHaveProperty("thinkingFormat");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("registerBuiltInGrokProvider", () => {
|
||||||
|
it("registers grok-cli with the expected baseUrl/api/models", () => {
|
||||||
|
const registeredProviders = new Map<string, unknown>();
|
||||||
|
const registry = {
|
||||||
|
registeredProviders,
|
||||||
|
registerProvider(providerName: string, config: unknown) {
|
||||||
|
registeredProviders.set(providerName, config);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
registerBuiltInGrokProvider(registry);
|
||||||
|
|
||||||
|
const registered = registeredProviders.get(GROK_CLI_PROVIDER_ID) as typeof GROK_PROVIDER_REGISTRATION;
|
||||||
|
expect(registered).toMatchObject({
|
||||||
|
name: "Grok",
|
||||||
|
baseUrl: "https://api.x.ai/v1",
|
||||||
|
apiKey: "$GROK_API_KEY",
|
||||||
|
api: "openai-completions",
|
||||||
|
});
|
||||||
|
expect(registered.models.map((model) => model.id)).toEqual(EXPECTED_GROK_MODELS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is additive — does not displace a pre-existing zai provider registration", () => {
|
||||||
|
const registeredProviders = new Map<string, unknown>();
|
||||||
|
const registry = {
|
||||||
|
registeredProviders,
|
||||||
|
registerProvider(providerName: string, config: unknown) {
|
||||||
|
registeredProviders.set(providerName, config);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
registerBuiltInZaiProvider(registry);
|
||||||
|
registerBuiltInGrokProvider(registry);
|
||||||
|
|
||||||
|
expect(registeredProviders.has(ZAI_PROVIDER_ID)).toBe(true);
|
||||||
|
expect(registeredProviders.has(GROK_CLI_PROVIDER_ID)).toBe(true);
|
||||||
|
expect((registeredProviders.get(ZAI_PROVIDER_ID) as typeof ZAI_PROVIDER_REGISTRATION).models.map((m) => m.id))
|
||||||
|
.toEqual(ZAI_PROVIDER_REGISTRATION.models.map((m) => m.id));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not throw when registerProvider throws", () => {
|
||||||
|
const registry = {
|
||||||
|
registerProvider() {
|
||||||
|
throw new Error("boom");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const warnings: string[] = [];
|
||||||
|
expect(() => registerBuiltInGrokProvider(registry, (message) => warnings.push(message))).not.toThrow();
|
||||||
|
expect(warnings[0]).toContain("Failed to register built-in grok-cli provider");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 registeredProviders = new Map<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>();
|
||||||
|
const registry = {
|
||||||
|
registeredProviders,
|
||||||
|
registerProvider(providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) {
|
||||||
|
registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
registerBuiltInGrokProvider(registry);
|
||||||
|
registry.registerProvider(GROK_CLI_PROVIDER_ID, {
|
||||||
|
...GROK_PROVIDER_REGISTRATION,
|
||||||
|
name: "User Grok extension",
|
||||||
|
models: extensionModels,
|
||||||
|
});
|
||||||
|
|
||||||
|
mergeBuiltInGrokProviderModels(registry);
|
||||||
|
|
||||||
|
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(registeredProviders.get(GROK_CLI_PROVIDER_ID)?.name).toBe("User Grok extension");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is a no-op when all built-in models are already present", () => {
|
||||||
|
const registeredProviders = new Map<string, Partial<typeof GROK_PROVIDER_REGISTRATION>>();
|
||||||
|
const registry = {
|
||||||
|
registeredProviders,
|
||||||
|
registerProvider(providerName: string, config: typeof GROK_PROVIDER_REGISTRATION) {
|
||||||
|
registeredProviders.set(providerName, { ...registeredProviders.get(providerName), ...config });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
registerBuiltInGrokProvider(registry);
|
||||||
|
expect(() => mergeBuiltInGrokProviderModels(registry)).not.toThrow();
|
||||||
|
const mergedIds = registeredProviders.get(GROK_CLI_PROVIDER_ID)?.models?.map((model) => model.id);
|
||||||
|
expect(mergedIds).toEqual(EXPECTED_GROK_MODELS);
|
||||||
|
});
|
||||||
|
});
|
||||||
224
packages/core/src/grok-provider.ts
Normal file
224
packages/core/src/grok-provider.ts
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* FNXC:ModelRegistry 2026-07-09-00:00:
|
||||||
|
* FN-7711 registers `grok-cli` as a built-in, OpenAI-compatible xAI provider so the
|
||||||
|
* execution model registry (packages/engine/src/pi.ts `createFnAgent`) can resolve
|
||||||
|
* `grok-cli/*` model selections. Before this, selecting a `grok-cli` model hard-failed
|
||||||
|
* at session creation with "not found in the pi model registry" because FN-7705/FN-7710
|
||||||
|
* only made Grok models appear in the `/api/models` picker — they were never registered
|
||||||
|
* into the execution registry that `resolveConfiguredModel()` reads from.
|
||||||
|
*
|
||||||
|
* The operator-installed `grok` CLI binary (landed by FN-7705) remains discovery/probe
|
||||||
|
* only — this module does not shell out to it. The Grok plugin's `GrokRuntimeAdapter` is
|
||||||
|
* a no-op streaming stub, so once this provider exists, execution streams against xAI's
|
||||||
|
* OpenAI-compatible endpoint (`https://api.x.ai/v1`, api type `openai-completions`) via
|
||||||
|
* the standard pi/openai-completions path, not the plugin runtime.
|
||||||
|
*
|
||||||
|
* This module mirrors `zai-provider.ts` (the canonical built-in-provider pattern):
|
||||||
|
* a static provider registration, `registerBuiltInGrokProvider` to seed it, and
|
||||||
|
* `mergeBuiltInGrokProviderModels` to re-add any built-in models an extension's
|
||||||
|
* provider registration may have dropped (pi's `registerProvider()` replaces the
|
||||||
|
* provider's model list wholesale rather than merging).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const GROK_CLI_PROVIDER_ID = "grok-cli";
|
||||||
|
|
||||||
|
type GrokModelInput = "text" | "image";
|
||||||
|
|
||||||
|
interface GrokModelRegistration {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
reasoning: boolean;
|
||||||
|
input: GrokModelInput[];
|
||||||
|
cost: {
|
||||||
|
input: number;
|
||||||
|
output: number;
|
||||||
|
cacheRead: number;
|
||||||
|
cacheWrite: number;
|
||||||
|
};
|
||||||
|
contextWindow: number;
|
||||||
|
maxTokens: number;
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrokProviderRegistration {
|
||||||
|
name: string;
|
||||||
|
baseUrl: string;
|
||||||
|
apiKey: string;
|
||||||
|
api: "openai-completions";
|
||||||
|
models: GrokModelRegistration[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// pi registerProvider() replaces the provider's model list, so keep every
|
||||||
|
// currently built-in Grok model here and append new models as xAI ships them.
|
||||||
|
export const GROK_PROVIDER_REGISTRATION: GrokProviderRegistration = {
|
||||||
|
name: "Grok",
|
||||||
|
baseUrl: "https://api.x.ai/v1",
|
||||||
|
apiKey: "$GROK_API_KEY",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "grok-4.5",
|
||||||
|
name: "Grok 4.5",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 256000,
|
||||||
|
maxTokens: 65536,
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "grok-4",
|
||||||
|
name: "Grok 4",
|
||||||
|
reasoning: true,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 256000,
|
||||||
|
maxTokens: 65536,
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "grok-code-fast-1",
|
||||||
|
name: "Grok Code Fast 1",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 32768,
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "grok-3",
|
||||||
|
name: "Grok 3",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 32768,
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "grok-3-mini",
|
||||||
|
name: "Grok 3 Mini",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 131072,
|
||||||
|
maxTokens: 32768,
|
||||||
|
compat: {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
type GrokModelLike = Partial<Omit<GrokModelRegistration, "name" | "api" | "baseUrl" | "compat">> & {
|
||||||
|
id: string;
|
||||||
|
name?: unknown;
|
||||||
|
provider?: string;
|
||||||
|
baseUrl?: unknown;
|
||||||
|
api?: unknown;
|
||||||
|
compat?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GrokModelRegistryLike {
|
||||||
|
registerProvider(providerName: string, config: GrokProviderRegistration): void;
|
||||||
|
getAll?: () => GrokModelLike[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegistryWithProviderState = GrokModelRegistryLike & {
|
||||||
|
registeredProviders?: Map<string, Partial<GrokProviderRegistration>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function toGrokModelRegistration(model: GrokModelLike): GrokModelRegistration & { baseUrl?: string; api?: string } {
|
||||||
|
return {
|
||||||
|
id: model.id,
|
||||||
|
name: String(model.name ?? model.id),
|
||||||
|
api: typeof model.api === "string" ? model.api : undefined,
|
||||||
|
baseUrl: typeof model.baseUrl === "string" ? model.baseUrl : undefined,
|
||||||
|
reasoning: model.reasoning === true,
|
||||||
|
input: Array.isArray(model.input) ? model.input as GrokModelInput[] : ["text"],
|
||||||
|
cost: model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: Number(model.contextWindow ?? 0),
|
||||||
|
maxTokens: Number(model.maxTokens ?? 0),
|
||||||
|
compat: typeof model.compat === "object" && model.compat !== null
|
||||||
|
? { ...(model.compat as GrokModelRegistration["compat"]) }
|
||||||
|
: GROK_PROVIDER_REGISTRATION.models.find((builtInModel) => builtInModel.id === model.id)?.compat ?? {
|
||||||
|
supportsDeveloperRole: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneGrokProviderRegistration(config: GrokProviderRegistration): GrokProviderRegistration {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
models: config.models.map((model) => toGrokModelRegistration(model)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FNXC:ModelRegistry 2026-07-09-00:00:
|
||||||
|
* Seeds the built-in `grok-cli` provider (mirrors `registerBuiltInZaiProvider`). Registration
|
||||||
|
* is unconditional and harmless without a `GROK_API_KEY` — a missing key only surfaces as a
|
||||||
|
* normal downstream auth error at stream time, never as the "not found in the pi model
|
||||||
|
* registry" hard-fail this task fixes. Always pass a cloned config because pi's
|
||||||
|
* registerProvider() stores and mutates the config object during later upserts.
|
||||||
|
*/
|
||||||
|
export function registerBuiltInGrokProvider(
|
||||||
|
modelRegistry: GrokModelRegistryLike,
|
||||||
|
logWarning: (message: string) => void = () => {},
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
modelRegistry.registerProvider(GROK_CLI_PROVIDER_ID, cloneGrokProviderRegistration(GROK_PROVIDER_REGISTRATION));
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
logWarning(`Failed to register built-in ${GROK_CLI_PROVIDER_ID} provider: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FNXC:ModelRegistry 2026-07-09-00:00:
|
||||||
|
* pi's registerProvider() treats a provider config with models as a full provider replacement,
|
||||||
|
* and user extensions load after Fusion's built-in provider registration. Re-merge missing
|
||||||
|
* built-in Grok models after extension registration so grok-4.5 remains visible wherever the
|
||||||
|
* user's existing Grok extension models are visible, without deleting extension-supplied models
|
||||||
|
* (mirrors `mergeBuiltInZaiProviderModels`).
|
||||||
|
*/
|
||||||
|
export function mergeBuiltInGrokProviderModels(
|
||||||
|
modelRegistry: GrokModelRegistryLike,
|
||||||
|
logWarning: (message: string) => void = () => {},
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
const registryWithState = modelRegistry as RegistryWithProviderState;
|
||||||
|
const registeredProvider = registryWithState.registeredProviders?.get(GROK_CLI_PROVIDER_ID);
|
||||||
|
if (!registeredProvider && !modelRegistry.getAll) return;
|
||||||
|
const registeredModels = registeredProvider?.models?.map((model) => toGrokModelRegistration(model)) ?? [];
|
||||||
|
const currentModels = registeredModels.length > 0
|
||||||
|
? registeredModels
|
||||||
|
: modelRegistry.getAll?.()
|
||||||
|
.filter((model) => model.provider === GROK_CLI_PROVIDER_ID)
|
||||||
|
.map((model) => toGrokModelRegistration(model)) ?? [];
|
||||||
|
const currentModelIds = new Set(currentModels.map((model) => model.id));
|
||||||
|
const missingBuiltInModels = GROK_PROVIDER_REGISTRATION.models.filter((model) => !currentModelIds.has(model.id));
|
||||||
|
|
||||||
|
if (missingBuiltInModels.length === 0) return;
|
||||||
|
|
||||||
|
modelRegistry.registerProvider(GROK_CLI_PROVIDER_ID, {
|
||||||
|
...cloneGrokProviderRegistration(GROK_PROVIDER_REGISTRATION),
|
||||||
|
...registeredProvider,
|
||||||
|
models: [...currentModels, ...missingBuiltInModels.map((model) => toGrokModelRegistration(model))],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
logWarning(`Failed to merge built-in ${GROK_CLI_PROVIDER_ID} models: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,13 @@ export {
|
|||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
} from "./zai-provider.js";
|
} from "./zai-provider.js";
|
||||||
export type { ZaiProviderRegistration } from "./zai-provider.js";
|
export type { ZaiProviderRegistration } from "./zai-provider.js";
|
||||||
|
export {
|
||||||
|
GROK_CLI_PROVIDER_ID,
|
||||||
|
GROK_PROVIDER_REGISTRATION,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
|
} from "./grok-provider.js";
|
||||||
|
export type { GrokProviderRegistration } from "./grok-provider.js";
|
||||||
export {
|
export {
|
||||||
resolveWorktrunkSettings,
|
resolveWorktrunkSettings,
|
||||||
requiresWorktrunkInstallVerification,
|
requiresWorktrunkInstallVerification,
|
||||||
|
|||||||
@@ -53,6 +53,13 @@ export {
|
|||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
} from "./zai-provider.js";
|
} from "./zai-provider.js";
|
||||||
export type { ZaiProviderRegistration } from "./zai-provider.js";
|
export type { ZaiProviderRegistration } from "./zai-provider.js";
|
||||||
|
export {
|
||||||
|
GROK_CLI_PROVIDER_ID,
|
||||||
|
GROK_PROVIDER_REGISTRATION,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
|
} from "./grok-provider.js";
|
||||||
|
export type { GrokProviderRegistration } from "./grok-provider.js";
|
||||||
export {
|
export {
|
||||||
resolveWorktrunkSettings,
|
resolveWorktrunkSettings,
|
||||||
requiresWorktrunkInstallVerification,
|
requiresWorktrunkInstallVerification,
|
||||||
|
|||||||
@@ -1537,7 +1537,12 @@ describe("createFnAgent", () => {
|
|||||||
expect(registerProviderMock).toHaveBeenNthCalledWith(1, "zai", expect.objectContaining({
|
expect(registerProviderMock).toHaveBeenNthCalledWith(1, "zai", expect.objectContaining({
|
||||||
models: expect.arrayContaining([expect.objectContaining({ id: "glm-5.2" })]),
|
models: expect.arrayContaining([expect.objectContaining({ id: "glm-5.2" })]),
|
||||||
}));
|
}));
|
||||||
expect(registerProviderMock).toHaveBeenNthCalledWith(2, "zai", expect.objectContaining({
|
// FN-7711: registerBuiltInGrokProvider seeds grok-cli immediately after the built-in zai
|
||||||
|
// registration, before the extension's pending provider registrations replay.
|
||||||
|
expect(registerProviderMock).toHaveBeenNthCalledWith(2, "grok-cli", expect.objectContaining({
|
||||||
|
models: expect.arrayContaining([expect.objectContaining({ id: "grok-4.5" })]),
|
||||||
|
}));
|
||||||
|
expect(registerProviderMock).toHaveBeenNthCalledWith(3, "zai", expect.objectContaining({
|
||||||
models: [{ id: "glm-5.1" }],
|
models: [{ id: "glm-5.1" }],
|
||||||
}));
|
}));
|
||||||
expect(refreshMock).toHaveBeenCalled();
|
expect(refreshMock).toHaveBeenCalled();
|
||||||
@@ -1786,6 +1791,77 @@ describe("createFnAgent", () => {
|
|||||||
expect(createAgentSessionMock).not.toHaveBeenCalled();
|
expect(createAgentSessionMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// FNXC:ModelRegistry 2026-07-09-00:00:
|
||||||
|
// FN-7711 symptom verification: selecting grok-cli/grok-4.5 used to hard-fail at session
|
||||||
|
// creation with "not found in the pi model registry" because the grok-cli provider was never
|
||||||
|
// registered into the execution registry (see registerExtensionProviders in ../pi.js). This
|
||||||
|
// mirrors the zai/glm-5.1 throw test above to reproduce the exact original failure, then proves
|
||||||
|
// it is gone once the registry resolves a grok-cli model (mirroring how registerBuiltInGrokProvider
|
||||||
|
// makes the provider resolvable), and that an unlisted grok-cli id also resolves via the
|
||||||
|
// provider-base-model on-the-fly fallback.
|
||||||
|
it("reproduces then proves gone the grok-cli/grok-4.5 'not found in the pi model registry' hard-fail", async () => {
|
||||||
|
// Without any grok-cli provider registration, find() returns nothing and getAll() has no
|
||||||
|
// grok-cli models — resolveConfiguredModel must throw the exact original error message.
|
||||||
|
findMock.mockImplementation((provider: string, modelId: string) => (
|
||||||
|
provider === "grok-cli" && modelId === "grok-4.5" ? undefined : { provider, id: modelId }
|
||||||
|
));
|
||||||
|
getAllMock.mockReturnValue([]);
|
||||||
|
|
||||||
|
const { createFnAgent } = await import("../pi.js");
|
||||||
|
|
||||||
|
await expect(createFnAgent({
|
||||||
|
cwd: "/tmp",
|
||||||
|
systemPrompt: "test",
|
||||||
|
tools: "readonly",
|
||||||
|
defaultProvider: "grok-cli",
|
||||||
|
defaultModelId: "grok-4.5",
|
||||||
|
})).rejects.toThrow("Configured model grok-cli/grok-4.5 (primary selection) was not found in the pi model registry");
|
||||||
|
expect(createAgentSessionMock).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Once the grok-cli provider is resolvable (as it is after registerBuiltInGrokProvider seeds
|
||||||
|
// it into the real execution registry via registerExtensionProviders), find() returns a model
|
||||||
|
// and the session is created successfully — the hard-fail is gone.
|
||||||
|
findMock.mockImplementation((provider: string, modelId: string) => ({ provider, id: modelId }));
|
||||||
|
|
||||||
|
await createFnAgent({
|
||||||
|
cwd: "/tmp",
|
||||||
|
systemPrompt: "test",
|
||||||
|
tools: "readonly",
|
||||||
|
defaultProvider: "grok-cli",
|
||||||
|
defaultModelId: "grok-4.5",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createAgentSessionMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(createAgentSessionMock.mock.calls[0]?.[0]).toMatchObject({
|
||||||
|
model: { provider: "grok-cli", id: "grok-4.5" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("resolves an unlisted grok-cli model id via the provider-base-model on-the-fly fallback", async () => {
|
||||||
|
// find() has no entry for the unlisted id, but getAll() reports the provider has at least one
|
||||||
|
// registered grok-cli model (as it does once registerBuiltInGrokProvider seeds the provider) —
|
||||||
|
// resolveConfiguredModel should synthesize a model from the base model rather than throwing.
|
||||||
|
findMock.mockImplementation((provider: string, modelId: string) => (
|
||||||
|
provider === "grok-cli" && modelId === "grok-4-fast" ? undefined : { provider, id: modelId }
|
||||||
|
));
|
||||||
|
getAllMock.mockReturnValue([{ provider: "grok-cli", id: "grok-4.5", name: "Grok 4.5" }]);
|
||||||
|
|
||||||
|
const { createFnAgent } = await import("../pi.js");
|
||||||
|
|
||||||
|
await createFnAgent({
|
||||||
|
cwd: "/tmp",
|
||||||
|
systemPrompt: "test",
|
||||||
|
tools: "readonly",
|
||||||
|
defaultProvider: "grok-cli",
|
||||||
|
defaultModelId: "grok-4-fast",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createAgentSessionMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(createAgentSessionMock.mock.calls[0]?.[0]).toMatchObject({
|
||||||
|
model: { provider: "grok-cli", id: "grok-4-fast" },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("creates a session when configured models resolve successfully", async () => {
|
it("creates a session when configured models resolve successfully", async () => {
|
||||||
const { createFnAgent } = await import("../pi.js");
|
const { createFnAgent } = await import("../pi.js");
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("seedDashboardProviders", () => {
|
|||||||
const { authStorage: wrapped } = await seedDashboardProviders({ store, authStorage, modelRegistry });
|
const { authStorage: wrapped } = await seedDashboardProviders({ store, authStorage, modelRegistry });
|
||||||
|
|
||||||
const providerIds = wrapped.getApiKeyProviders().map((p) => p.id);
|
const providerIds = wrapped.getApiKeyProviders().map((p) => p.id);
|
||||||
expect(providerIds).toEqual(expect.arrayContaining(["zai", "openrouter", "kimi-coding"]));
|
expect(providerIds).toEqual(expect.arrayContaining(["zai", "openrouter", "kimi-coding", "grok-cli"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers built-in API-key providers with an empty customProviders array", async () => {
|
it("registers built-in API-key providers with an empty customProviders array", async () => {
|
||||||
@@ -115,7 +115,7 @@ describe("seedDashboardProviders", () => {
|
|||||||
const { authStorage: wrapped } = await seedDashboardProviders({ store, authStorage, modelRegistry });
|
const { authStorage: wrapped } = await seedDashboardProviders({ store, authStorage, modelRegistry });
|
||||||
|
|
||||||
const providerIds = wrapped.getApiKeyProviders().map((p) => p.id);
|
const providerIds = wrapped.getApiKeyProviders().map((p) => p.id);
|
||||||
expect(providerIds).toEqual(expect.arrayContaining(["zai", "openrouter", "kimi-coding"]));
|
expect(providerIds).toEqual(expect.arrayContaining(["zai", "openrouter", "kimi-coding", "grok-cli"]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("registers one custom provider alongside built-ins", async () => {
|
it("registers one custom provider alongside built-ins", async () => {
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ import {
|
|||||||
getProjectRootFromWorktree,
|
getProjectRootFromWorktree,
|
||||||
reconcileClaudeCliPaths,
|
reconcileClaudeCliPaths,
|
||||||
reconcileDroidCliPaths,
|
reconcileDroidCliPaths,
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
mergeBuiltInZaiProviderModels,
|
mergeBuiltInZaiProviderModels,
|
||||||
mergeSupplementalAnthropicModels,
|
mergeSupplementalAnthropicModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
resolvePiExtensionProjectRoot,
|
resolvePiExtensionProjectRoot,
|
||||||
} from "@fusion/core";
|
} from "@fusion/core";
|
||||||
@@ -1463,6 +1465,7 @@ function resolveVendoredDroidCliEntry(): string | null {
|
|||||||
|
|
||||||
async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegistry): Promise<void> {
|
async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegistry): Promise<void> {
|
||||||
registerBuiltInZaiProvider(modelRegistry, (message) => extensionsLog.warn(message));
|
registerBuiltInZaiProvider(modelRegistry, (message) => extensionsLog.warn(message));
|
||||||
|
registerBuiltInGrokProvider(modelRegistry, (message) => extensionsLog.warn(message));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const agentDir = getPackageManagerAgentDir();
|
const agentDir = getPackageManagerAgentDir();
|
||||||
@@ -1525,6 +1528,7 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
|
|||||||
|
|
||||||
extensionsResult.runtime.pendingProviderRegistrations = [];
|
extensionsResult.runtime.pendingProviderRegistrations = [];
|
||||||
mergeBuiltInZaiProviderModels(modelRegistry, (message) => extensionsLog.warn(message));
|
mergeBuiltInZaiProviderModels(modelRegistry, (message) => extensionsLog.warn(message));
|
||||||
|
mergeBuiltInGrokProviderModels(modelRegistry, (message) => extensionsLog.warn(message));
|
||||||
modelRegistry.refresh();
|
modelRegistry.refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ DashboardAuthStorage callers MUST pass to `createServer(...)` (not the raw authS
|
|||||||
disposer to unsubscribe the settings listener on shutdown.
|
disposer to unsubscribe the settings listener on shutdown.
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
|
mergeBuiltInGrokProviderModels,
|
||||||
mergeBuiltInZaiProviderModels,
|
mergeBuiltInZaiProviderModels,
|
||||||
|
registerBuiltInGrokProvider,
|
||||||
registerBuiltInZaiProvider,
|
registerBuiltInZaiProvider,
|
||||||
type CustomProvider,
|
type CustomProvider,
|
||||||
type TaskStore,
|
type TaskStore,
|
||||||
@@ -62,9 +64,11 @@ export async function seedDashboardProviders(
|
|||||||
const log = options.log ?? (() => {});
|
const log = options.log ?? (() => {});
|
||||||
|
|
||||||
registerBuiltInZaiProvider(modelRegistry, (message) => log("extensions", message));
|
registerBuiltInZaiProvider(modelRegistry, (message) => log("extensions", message));
|
||||||
|
registerBuiltInGrokProvider(modelRegistry, (message) => log("extensions", message));
|
||||||
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
|
||||||
|
|
||||||
mergeBuiltInZaiProviderModels(modelRegistry, (message) => log("extensions", message));
|
mergeBuiltInZaiProviderModels(modelRegistry, (message) => log("extensions", message));
|
||||||
|
mergeBuiltInGrokProviderModels(modelRegistry, (message) => log("extensions", message));
|
||||||
modelRegistry.refresh();
|
modelRegistry.refresh();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user