diff --git a/src/commands/models/list.list-command.forward-compat.test.ts b/src/commands/models/list.list-command.forward-compat.test.ts index ccf76bd6e2e..dd839e94cde 100644 --- a/src/commands/models/list.list-command.forward-compat.test.ts +++ b/src/commands/models/list.list-command.forward-compat.test.ts @@ -394,4 +394,52 @@ describe("modelsListCommand forward-compat", () => { ]); }); }); + + describe("provider filter canonicalization", () => { + it("matches alias-valued discovered providers against canonical provider filters", async () => { + mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] }); + mocks.loadModelRegistry.mockResolvedValueOnce({ + models: [ + { + provider: "z.ai", + id: "glm-4.5", + name: "GLM-4.5", + api: "openai-responses", + baseUrl: "https://api.z.ai/v1", + input: ["text"], + contextWindow: 128_000, + maxTokens: 16_384, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + }, + ], + availableKeys: new Set(["z.ai/glm-4.5"]), + registry: { + getAll: () => [ + { + provider: "z.ai", + id: "glm-4.5", + name: "GLM-4.5", + api: "openai-responses", + baseUrl: "https://api.z.ai/v1", + input: ["text"], + contextWindow: 128_000, + maxTokens: 16_384, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + }, + ], + }, + }); + + const runtime = createRuntime(); + + await modelsListCommand({ all: true, provider: "z-ai", json: true }, runtime as never); + + expect(mocks.printModelTable).toHaveBeenCalled(); + expect(lastPrintedRows<{ key: string }>()).toEqual([ + expect.objectContaining({ + key: "z.ai/glm-4.5", + }), + ]); + }); + }); }); diff --git a/src/commands/models/list.rows.ts b/src/commands/models/list.rows.ts index 7abf7861914..d1af21c8701 100644 --- a/src/commands/models/list.rows.ts +++ b/src/commands/models/list.rows.ts @@ -4,6 +4,7 @@ import type { AuthProfileStore } from "../../agents/auth-profiles.js"; import { loadModelCatalog } from "../../agents/model-catalog.js"; import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; import { resolveModelWithRegistry } from "../../agents/pi-embedded-runner/model.js"; +import { normalizeProviderId } from "../../agents/provider-id.js"; import type { OpenClawConfig } from "../../config/config.js"; import { loadModelRegistry, toModelRow } from "./list.registry.js"; import type { ConfiguredEntry, ModelRow } from "./list.types.js"; @@ -26,7 +27,7 @@ type RowBuilderContext = { }; function matchesRowFilter(filter: RowFilter, model: { provider: string; baseUrl?: string }) { - if (filter.provider && model.provider.toLowerCase() !== filter.provider) { + if (filter.provider && normalizeProviderId(model.provider) !== filter.provider) { return false; } if (filter.local && !isLocalBaseUrl(model.baseUrl ?? "")) { @@ -110,7 +111,7 @@ export async function appendCatalogSupplementRows(params: { for (const entry of catalog) { if ( params.context.filter.provider && - entry.provider.toLowerCase() !== params.context.filter.provider + normalizeProviderId(entry.provider) !== params.context.filter.provider ) { continue; } @@ -148,7 +149,7 @@ export function appendConfiguredRows(params: { for (const entry of params.entries) { if ( params.context.filter.provider && - entry.ref.provider.toLowerCase() !== params.context.filter.provider + normalizeProviderId(entry.ref.provider) !== params.context.filter.provider ) { continue; }