diff --git a/src/plugins/contracts/registry.retry.test.ts b/src/plugins/contracts/registry.retry.test.ts index 044e4979484..6b4d6db25e7 100644 --- a/src/plugins/contracts/registry.retry.test.ts +++ b/src/plugins/contracts/registry.retry.test.ts @@ -144,4 +144,37 @@ describe("plugin contract registry scoped retries", () => { ).toEqual(["grok"]); expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(2); }); + + it("reuses the single registered provider contract for paired manifest alias ids", async () => { + const loadBundledCapabilityRuntimeRegistry = vi.fn().mockReturnValue( + createMockRuntimeRegistry({ + plugin: { + id: "byteplus", + status: "loaded", + providerIds: ["byteplus"], + webSearchProviderIds: [], + }, + providers: [ + { + pluginId: "byteplus", + provider: { + id: "byteplus", + label: "BytePlus", + docsPath: "/providers/byteplus", + auth: [], + } as ProviderPlugin, + }, + ], + }), + ); + + vi.doMock("../bundled-capability-runtime.js", () => ({ + loadBundledCapabilityRuntimeRegistry, + })); + + const { requireProviderContractProvider } = await import("./registry.js"); + + expect(requireProviderContractProvider("byteplus-plan").id).toBe("byteplus"); + expect(loadBundledCapabilityRuntimeRegistry).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index 7922b49972b..0e4c5a1c6f5 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -395,10 +395,19 @@ export const providerContractCompatPluginIds: string[] = createLazyArrayView( ); export function requireProviderContractProvider(providerId: string): ProviderPlugin { - const provider = loadProviderContractEntriesForPluginIds( - providerContractPluginIdsByProviderId.get(providerId) ?? [], - ).find((entry) => entry.provider.id === providerId)?.provider; + const pluginIds = providerContractPluginIdsByProviderId.get(providerId) ?? []; + const entries = loadProviderContractEntriesForPluginIds(pluginIds); + const provider = entries.find((entry) => entry.provider.id === providerId)?.provider; if (!provider) { + const pluginScopedProviders = [ + ...new Map(entries.map((entry) => [entry.provider.id, entry.provider])).values(), + ]; + // Paired catalogs may expose multiple runtime provider ids from one shared + // ProviderPlugin contract entry. Reuse that single contract surface for the + // manifest-owned alias ids instead of requiring duplicate registration. + if (pluginIds.length === 1 && pluginScopedProviders.length === 1) { + return pluginScopedProviders[0]; + } if (providerContractLoadError) { throw new Error( `provider contract entry missing for ${providerId}; bundled provider registry failed to load: ${providerContractLoadError.message}`,