From d6c7b468ea0ddc346a8e9bf73a8783e36627bde1 Mon Sep 17 00:00:00 2001 From: Shakker Date: Wed, 22 Apr 2026 03:08:13 +0100 Subject: [PATCH] fix: honor provider hook aliases in catalog filters --- .../models/list.provider-catalog.test.ts | 25 ++++++++++++++++- src/commands/models/list.provider-catalog.ts | 28 +++++++++++++++++-- src/plugins/contracts/registry.ts | 25 +++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/commands/models/list.provider-catalog.test.ts b/src/commands/models/list.provider-catalog.test.ts index 8987927d006..adc025cb5ab 100644 --- a/src/commands/models/list.provider-catalog.test.ts +++ b/src/commands/models/list.provider-catalog.test.ts @@ -1,5 +1,8 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { loadProviderCatalogModelsForList } from "./list.provider-catalog.js"; +import { + loadProviderCatalogModelsForList, + resolveProviderCatalogPluginIdsForFilter, +} from "./list.provider-catalog.js"; const baseParams = { cfg: { @@ -47,4 +50,24 @@ describe("loadProviderCatalogModelsForList", () => { expect.arrayContaining(["moonshot/kimi-k2.6"]), ); }); + + it("recognizes bundled provider hook aliases before the unknown-provider short-circuit", async () => { + await expect( + resolveProviderCatalogPluginIdsForFilter({ + cfg: baseParams.cfg, + env: baseParams.env, + providerFilter: "azure-openai-responses", + }), + ).resolves.toEqual(["openai"]); + }); + + it("keeps unknown provider filters eligible for early empty results", async () => { + await expect( + resolveProviderCatalogPluginIdsForFilter({ + cfg: baseParams.cfg, + env: baseParams.env, + providerFilter: "unknown-provider-for-catalog-test", + }), + ).resolves.toBeUndefined(); + }); }); diff --git a/src/commands/models/list.provider-catalog.ts b/src/commands/models/list.provider-catalog.ts index 387d3f79832..7bb6ab4608a 100644 --- a/src/commands/models/list.provider-catalog.ts +++ b/src/commands/models/list.provider-catalog.ts @@ -16,6 +16,28 @@ const DISCOVERY_ORDERS = ["simple", "profile", "paired", "late"] as const; const SELF_HOSTED_DISCOVERY_PROVIDER_IDS = new Set(["lmstudio", "ollama", "sglang", "vllm"]); const log = createSubsystemLogger("models/list-provider-catalog"); +export async function resolveProviderCatalogPluginIdsForFilter(params: { + cfg: OpenClawConfig; + env?: NodeJS.ProcessEnv; + providerFilter: string; +}): Promise { + const providerFilter = normalizeProviderId(params.providerFilter); + if (!providerFilter) { + return undefined; + } + const manifestPluginIds = resolveOwningPluginIdsForProvider({ + provider: providerFilter, + config: params.cfg, + env: params.env, + }); + if (manifestPluginIds) { + return manifestPluginIds; + } + const { resolveProviderContractPluginIdsForProviderAlias } = + await import("../../plugins/contracts/registry.js"); + return resolveProviderContractPluginIdsForProviderAlias(providerFilter); +} + function modelFromProviderCatalog(params: { provider: string; providerConfig: ModelProviderConfig; @@ -47,10 +69,10 @@ export async function loadProviderCatalogModelsForList(params: { const env = params.env ?? process.env; const providerFilter = params.providerFilter ? normalizeProviderId(params.providerFilter) : ""; const onlyPluginIds = providerFilter - ? resolveOwningPluginIdsForProvider({ - provider: providerFilter, - config: params.cfg, + ? await resolveProviderCatalogPluginIdsForFilter({ + cfg: params.cfg, env, + providerFilter, }) : undefined; if (providerFilter && !onlyPluginIds) { diff --git a/src/plugins/contracts/registry.ts b/src/plugins/contracts/registry.ts index 6fa20c31f3e..872f3a2b151 100644 --- a/src/plugins/contracts/registry.ts +++ b/src/plugins/contracts/registry.ts @@ -1,3 +1,4 @@ +import { normalizeProviderId } from "../../agents/provider-id.js"; import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js"; import { loadBundledCapabilityRuntimeRegistry } from "../bundled-capability-runtime.js"; import { @@ -723,6 +724,30 @@ export function resolveProviderContractPluginIdsForProvider( return pluginIds.length > 0 ? pluginIds : undefined; } +export function resolveProviderContractPluginIdsForProviderAlias( + providerId: string, +): string[] | undefined { + const normalizedProvider = normalizeProviderId(providerId); + if (!normalizedProvider) { + return undefined; + } + const pluginIds = uniqueStrings( + loadProviderContractEntriesForPluginIds(resolveBundledProviderContractPluginIds()) + .filter((entry) => { + const providerIds = [ + entry.provider.id, + ...(entry.provider.aliases ?? []), + ...(entry.provider.hookAliases ?? []), + ]; + return providerIds.some( + (candidate) => normalizeProviderId(candidate) === normalizedProvider, + ); + }) + .map((entry) => entry.pluginId), + ).toSorted((left, right) => left.localeCompare(right)); + return pluginIds.length > 0 ? pluginIds : undefined; +} + export function resolveProviderContractProvidersForPluginIds( pluginIds: readonly string[], ): ProviderPlugin[] {