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>
114 lines
4.9 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|