From d1ef7d64e96127c7798f151eb49db15caf7aeb17 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Wed, 18 Mar 2026 01:30:05 -0700 Subject: [PATCH] Contracts: harden provider registry loading --- extensions/github-copilot/index.ts | 8 +--- .../contracts/provider.contract.test.ts | 11 ++++- src/plugins/contracts/registry.ts | 43 +++++++++++++------ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/extensions/github-copilot/index.ts b/extensions/github-copilot/index.ts index ee85f76fd61..39116636b76 100644 --- a/extensions/github-copilot/index.ts +++ b/extensions/github-copilot/index.ts @@ -1,15 +1,11 @@ +import { ensureAuthProfileStore, listProfilesForProvider } from "openclaw/plugin-sdk/agent-runtime"; import { definePluginEntry, type ProviderAuthContext, type ProviderResolveDynamicModelContext, type ProviderRuntimeModel, } from "openclaw/plugin-sdk/core"; -import { - coerceSecretRef, - ensureAuthProfileStore, - githubCopilotLoginCommand, - listProfilesForProvider, -} from "openclaw/plugin-sdk/provider-auth"; +import { coerceSecretRef, githubCopilotLoginCommand } from "openclaw/plugin-sdk/provider-auth"; import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-models"; import { DEFAULT_COPILOT_API_BASE_URL, resolveCopilotApiToken } from "./token.js"; import { fetchCopilotUsage } from "./usage.js"; diff --git a/src/plugins/contracts/provider.contract.test.ts b/src/plugins/contracts/provider.contract.test.ts index 9ff8f7458d3..db5ce6e3c03 100644 --- a/src/plugins/contracts/provider.contract.test.ts +++ b/src/plugins/contracts/provider.contract.test.ts @@ -1,7 +1,14 @@ -import { describe } from "vitest"; -import { providerContractRegistry } from "./registry.js"; +import { describe, expect, it } from "vitest"; +import { providerContractLoadError, providerContractRegistry } from "./registry.js"; import { installProviderPluginContractSuite } from "./suites.js"; +describe("provider contract registry load", () => { + it("loads bundled providers without import-time registry failure", () => { + expect(providerContractLoadError).toBeUndefined(); + expect(providerContractRegistry.length).toBeGreaterThan(0); + }); +}); + for (const entry of providerContractRegistry) { describe(`${entry.pluginId}:${entry.provider.id} provider contract`, () => { installProviderPluginContractSuite({ diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index e4b6cf1059a..142aa578b0f 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -99,19 +99,31 @@ export const providerContractRegistry: ProviderContractEntry[] = buildCapability select: () => [], }); -const loadedBundledProviderRegistry: ProviderContractEntry[] = resolvePluginProviders({ - bundledProviderAllowlistCompat: true, - bundledProviderVitestCompat: true, - cache: false, - activate: false, -}) - .filter((provider): provider is ProviderPlugin & { pluginId: string } => - Boolean(provider.pluginId), - ) - .map((provider) => ({ - pluginId: provider.pluginId, - provider, - })); +export let providerContractLoadError: Error | undefined; + +function loadBundledProviderRegistry(): ProviderContractEntry[] { + try { + providerContractLoadError = undefined; + return resolvePluginProviders({ + bundledProviderAllowlistCompat: true, + bundledProviderVitestCompat: true, + cache: false, + activate: false, + }) + .filter((provider): provider is ProviderPlugin & { pluginId: string } => + Boolean(provider.pluginId), + ) + .map((provider) => ({ + pluginId: provider.pluginId, + provider, + })); + } catch (error) { + providerContractLoadError = error instanceof Error ? error : new Error(String(error)); + return []; + } +} + +const loadedBundledProviderRegistry: ProviderContractEntry[] = loadBundledProviderRegistry(); providerContractRegistry.splice( 0, @@ -134,6 +146,11 @@ export const providerContractCompatPluginIds = providerContractPluginIds.map((pl export function requireProviderContractProvider(providerId: string): ProviderPlugin { const provider = uniqueProviderContractProviders.find((entry) => entry.id === providerId); if (!provider) { + if (providerContractLoadError) { + throw new Error( + `provider contract entry missing for ${providerId}; bundled provider registry failed to load: ${providerContractLoadError.message}`, + ); + } throw new Error(`provider contract entry missing for ${providerId}`); } return provider;