Files
fusion/packages/engine/src/provider-registration.ts
gsxdsm 5a60643c0a FN-8142: migrate auth storage and model runtime to pi SDK
Migrate Fusion's credential and model integrations to pi SDK 0.80.8+.

- Replace legacy AuthStorage initialization with a locked Fusion credential store and ModelRuntime-backed registry.
- Wire asynchronous model initialization and refresh through CLI, desktop, dashboard, executor, and provider paths.
- Update provider, routing, and registry tests for the new SDK contracts.

Files changed:
 packages/cli/src/commands/__tests__/daemon.test.ts |   2 +-
 .../cli/src/commands/__tests__/dashboard.test.ts   |   9 +-
 .../cli/src/commands/__tests__/onboard.test.ts     |   1 +
 packages/cli/src/commands/__tests__/serve.test.ts  |   2 +-
 packages/cli/src/commands/daemon.ts                |  19 +-
 packages/cli/src/commands/dashboard.ts             |  20 +-
 packages/cli/src/commands/onboard.ts               |   6 +-
 packages/cli/src/commands/serve.ts                 |  19 +-
 packages/cli/src/commands/startup-model-sync.ts    |   4 +-
 packages/core/src/__tests__/openai-models.test.ts  |  17 +-
 ...-model-routes-openai-codex-supplemental.test.ts |  17 +-
 ...register-model-routes-zai-real-registry.test.ts |  15 +-
 packages/dashboard/src/routes.ts                   |  12 +-
 .../dashboard/src/routes/register-model-routes.ts  |   2 +-
 packages/desktop/src/local-runtime.ts              |   2 +-
 packages/desktop/src/local-server.ts               |   2 +-
 .../custom-providers-openai-completions.test.ts    |  16 +-
 .../custom-providers-openai-responses.test.ts      |  16 +-
 .../engine/src/__tests__/executor-test-helpers.ts  |   2 +-
 .../src/__tests__/pi-create-fn-agent.test.ts       |   8 +-
 .../engine/src/__tests__/pi-layers-wiring.test.ts  |   2 +-
 packages/engine/src/__tests__/pi.test.ts           |  47 ++---
 .../src/__tests__/provider-registration.test.ts    |  17 +-
 packages/engine/src/auth-storage.ts                | 218 ++++++++++++++++++---
 packages/engine/src/custom-provider-registry.ts    |  14 +-
 packages/engine/src/executor.ts                    |  15 +-
 packages/engine/src/pi.ts                          |  50 +++--
 packages/engine/src/provider-auth.ts               |  58 +++---
 packages/engine/src/provider-registration.ts       |  16 +-
 29 files changed, 421 insertions(+), 207 deletions(-)

Fusion-Task-Id: FN-8142

Fusion-Task-Lineage: 8ae79064-7820-4976-9645-9431b5a3129e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-16 18:13:37 -07:00

114 lines
4.9 KiB
TypeScript

/*
FNXC:ProviderRegistration 2026-07-07-00:00:
FN-7622: the Electron desktop app's in-process dashboard server (packages/desktop/src/local-runtime.ts,
local-server.ts) constructed a raw authStorage/modelRegistry and skipped the built-in provider seeding,
the API-key-provider auth wrapping, and custom-provider registration that the CLI `serve`/`dashboard`/
`daemon` commands perform — so desktop's /api/providers and /api/models returned a truncated catalog
(missing built-in API-key providers like zai/openrouter/kimi-coding and any user customProviders[])
compared to the identical config rendered by the web build. This module is the SINGLE shared helper
both the CLI paths and the desktop paths call so that sequence can never drift apart again: it
mirrors the CLI's exact order — registerBuiltInZaiProvider -> wrapAuthStorageWithApiKeyProviders ->
mergeBuiltInZaiProviderModels -> modelRegistry.refresh() -> registerCustomProviders(customProviders)
-> subscribe settings:updated -> reregisterCustomProviders — and returns the wrapped
DashboardAuthStorage callers MUST pass to `createServer(...)` (not the raw authStorage) plus a
disposer to unsubscribe the settings listener on shutdown.
*/
import {
mergeBuiltInGrokProviderModels,
mergeBuiltInZaiProviderModels,
registerBuiltInGrokProvider,
registerBuiltInZaiProvider,
type CustomProvider,
type TaskStore,
} from "@fusion/core";
import type { ModelRegistry } from "@earendil-works/pi-coding-agent";
import type { FusionAuthStorage } from "./auth-storage.js";
import {
wrapAuthStorageWithApiKeyProviders,
type DashboardAuthStorage,
} from "./provider-auth.js";
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
export interface SeedDashboardProvidersStore {
getGlobalSettingsStore(): {
getSettings(): Promise<{ customProviders?: CustomProvider[] }>;
};
on: TaskStore["on"];
off: TaskStore["off"];
}
export interface SeedDashboardProvidersOptions {
/** The task store used to read `globalSettings.customProviders` and subscribe to `settings:updated`. */
store: SeedDashboardProvidersStore;
authStorage: FusionAuthStorage;
modelRegistry: ModelRegistry;
/** Optional structured logger; defaults to a no-op so callers can opt into console/trace logging. */
log?: (scope: string, message: string) => void;
}
export interface SeedDashboardProvidersResult {
/** The wrapped auth storage — pass this (NOT the raw authStorage) to `createServer(...)`. */
authStorage: DashboardAuthStorage;
/** Unsubscribes the `settings:updated` -> reregisterCustomProviders listener. Call on shutdown. */
dispose: () => void;
}
/**
* Performs the full provider-registration sequence the CLI `serve`/`dashboard`/`daemon` commands run
* at startup, so any host (CLI or desktop in-process server) that calls this gets an identical
* provider/model catalog. See the FNXC:ProviderRegistration header above for the FN-7622 motivation.
*/
export async function seedDashboardProviders(
options: SeedDashboardProvidersOptions,
): Promise<SeedDashboardProvidersResult> {
const { store, authStorage, modelRegistry } = options;
const log = options.log ?? (() => {});
registerBuiltInZaiProvider(modelRegistry, (message) => log("extensions", message));
registerBuiltInGrokProvider(modelRegistry, (message) => log("extensions", message));
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
mergeBuiltInZaiProviderModels(modelRegistry, (message) => log("extensions", message));
mergeBuiltInGrokProviderModels(modelRegistry, (message) => log("extensions", message));
await modelRegistry.refresh();
try {
const globalSettings = await store.getGlobalSettingsStore().getSettings();
await registerCustomProviders(
modelRegistry,
globalSettings.customProviders,
(message) => log("custom-providers", message),
);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
log("custom-providers", `Failed to load custom providers from global settings: ${message}`);
}
const onSettingsUpdated = (data: { settings: { customProviders?: CustomProvider[] }; previous: { customProviders?: CustomProvider[] } }) => {
const currentProviders = data.settings.customProviders;
const previousProviders = data.previous.customProviders;
if (JSON.stringify(currentProviders ?? []) === JSON.stringify(previousProviders ?? [])) {
return;
}
void reregisterCustomProviders(
modelRegistry,
previousProviders,
currentProviders,
(message) => log("custom-providers", message),
).catch((error) => {
const message = error instanceof Error ? error.message : String(error);
log("custom-providers", `Failed to refresh custom providers: ${message}`);
});
};
store.on("settings:updated", onSettingsUpdated);
return {
authStorage: dashboardAuthStorage,
dispose: () => {
store.off("settings:updated", onSettingsUpdated);
},
};
}