























@@ -105,6 +105,45 @@ function emptyPluginMetadataSnapshot() {
105105};
106106}
107107108+type ModelCatalogEntry = Awaited<
109+ReturnType<typeof import("./model-catalog.js").loadModelCatalog>
110+>[number];
111+112+function findCatalogEntry(
113+entries: readonly ModelCatalogEntry[],
114+provider: string,
115+id: string,
116+): ModelCatalogEntry | undefined {
117+return entries.find((entry) => entry.provider === provider && entry.id === id);
118+}
119+120+function requireCatalogEntry(
121+entries: readonly ModelCatalogEntry[],
122+provider: string,
123+id: string,
124+): ModelCatalogEntry {
125+const entry = findCatalogEntry(entries, provider, id);
126+if (!entry) {
127+throw new Error(`expected catalog entry ${provider}/${id}`);
128+}
129+return entry;
130+}
131+132+function expectNoCatalogEntry(entries: readonly ModelCatalogEntry[], provider: string, id: string) {
133+expect(findCatalogEntry(entries, provider, id)).toBeUndefined();
134+}
135+136+function requireMockCallParam(
137+mock: ReturnType<typeof vi.fn>,
138+label: string,
139+): Record<string, unknown> {
140+const first = mock.mock.calls[0]?.[0];
141+if (!first || typeof first !== "object" || Array.isArray(first)) {
142+throw new Error(`expected ${label}`);
143+}
144+return first as Record<string, unknown>;
145+}
146+108147describe("loadModelCatalog", () => {
109148beforeAll(async () => {
110149readFileMock = vi.fn();
@@ -278,9 +317,8 @@ describe("loadModelCatalog", () => {
278317readOnly: true,
279318});
280319281-expect(result).toContainEqual(
282-expect.objectContaining({ id: "gpt-test", name: "GPT Test", provider: "openai" }),
283-);
320+const entry = requireCatalogEntry(result, "openai", "gpt-test");
321+expect(entry.name).toBe("GPT Test");
284322expect(ensureOpenClawModelsJsonMock).not.toHaveBeenCalled();
285323expect(importPiSdk).not.toHaveBeenCalled();
286324expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
@@ -484,19 +522,9 @@ describe("loadModelCatalog", () => {
484522]);
485523486524const result = await loadModelCatalog({ config: {} as OpenClawConfig });
487-expect(result).not.toContainEqual(
488-expect.objectContaining({
489-provider: "openai-codex",
490-id: "gpt-5.3-codex-spark",
491-}),
492-);
493-expect(result).toContainEqual(
494-expect.objectContaining({
495-provider: "openai-codex",
496-id: "gpt-5.4",
497-name: "GPT-5.3 Codex",
498-}),
499-);
525+expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark");
526+const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4");
527+expect(entry.name).toBe("GPT-5.3 Codex");
500528});
501529502530it("filters stale gpt-5.3-codex-spark built-ins from the catalog", async () => {
@@ -528,24 +556,9 @@ describe("loadModelCatalog", () => {
528556]);
529557530558const result = await loadModelCatalog({ config: {} as OpenClawConfig });
531-expect(result).not.toContainEqual(
532-expect.objectContaining({
533-provider: "openai",
534-id: "gpt-5.3-codex-spark",
535-}),
536-);
537-expect(result).not.toContainEqual(
538-expect.objectContaining({
539-provider: "azure-openai-responses",
540-id: "gpt-5.3-codex-spark",
541-}),
542-);
543-expect(result).not.toContainEqual(
544-expect.objectContaining({
545-provider: "openai-codex",
546-id: "gpt-5.3-codex-spark",
547-}),
548-);
559+expectNoCatalogEntry(result, "openai", "gpt-5.3-codex-spark");
560+expectNoCatalogEntry(result, "azure-openai-responses", "gpt-5.3-codex-spark");
561+expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex-spark");
549562});
550563551564it("filters stale openai-codex 5.1/5.2/5.3 built-ins from the catalog", async () => {
@@ -585,18 +598,10 @@ describe("loadModelCatalog", () => {
585598]);
586599587600const result = await loadModelCatalog({ config: {} as OpenClawConfig });
588-expect(result).not.toContainEqual(
589-expect.objectContaining({ provider: "openai-codex", id: "gpt-5.1-codex-mini" }),
590-);
591-expect(result).not.toContainEqual(
592-expect.objectContaining({ provider: "openai-codex", id: "gpt-5.2-codex" }),
593-);
594-expect(result).not.toContainEqual(
595-expect.objectContaining({ provider: "openai-codex", id: "gpt-5.3-codex" }),
596-);
597-expect(result).toContainEqual(
598-expect.objectContaining({ provider: "openai-codex", id: "gpt-5.5" }),
599-);
601+expectNoCatalogEntry(result, "openai-codex", "gpt-5.1-codex-mini");
602+expectNoCatalogEntry(result, "openai-codex", "gpt-5.2-codex");
603+expectNoCatalogEntry(result, "openai-codex", "gpt-5.3-codex");
604+expect(requireCatalogEntry(result, "openai-codex", "gpt-5.5").name).toBe("GPT-5.5");
600605});
601606602607it("does not synthesize gpt-5.4 OpenAI forward-compat entries from template models", async () => {
@@ -648,13 +653,8 @@ describe("loadModelCatalog", () => {
648653expect(
649654result.some((entry) => entry.provider === "openai" && entry.id.startsWith("gpt-5.4")),
650655).toBe(false);
651-expect(result).toContainEqual(
652-expect.objectContaining({
653-provider: "openai-codex",
654-id: "gpt-5.4",
655-name: "GPT-5.3 Codex",
656-}),
657-);
656+const entry = requireCatalogEntry(result, "openai-codex", "gpt-5.4");
657+expect(entry.name).toBe("GPT-5.3 Codex");
658658expect(
659659result.some((entry) => entry.provider === "openai-codex" && entry.id === "gpt-5.4-mini"),
660660).toBe(false);
@@ -675,13 +675,8 @@ describe("loadModelCatalog", () => {
675675676676const result = await loadModelCatalog({ config: {} as OpenClawConfig });
677677678-expect(result).toContainEqual(
679-expect.objectContaining({
680-provider: "kilocode",
681-id: "google/gemini-3.1-pro-preview",
682-name: "Gemini 3 Pro Preview",
683-}),
684-);
678+const entry = requireCatalogEntry(result, "kilocode", "google/gemini-3.1-pro-preview");
679+expect(entry.name).toBe("Gemini 3 Pro Preview");
685680});
686681687682it("loads manifest catalog rows from the current metadata snapshot without provider runtime", () => {
@@ -743,11 +738,8 @@ describe("loadModelCatalog", () => {
743738fallbackToMetadataScan: false,
744739});
745740746-expect(currentPluginMetadataSnapshotMock).toHaveBeenCalledWith(
747-expect.objectContaining({
748-allowWorkspaceScopedSnapshot: true,
749-}),
750-);
741+const call = requireMockCallParam(currentPluginMetadataSnapshotMock, "current snapshot params");
742+expect(call.allowWorkspaceScopedSnapshot).toBe(true);
751743expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
752744});
753745@@ -760,12 +752,9 @@ describe("loadModelCatalog", () => {
760752fallbackToMetadataScan: false,
761753});
762754763-expect(currentPluginMetadataSnapshotMock).toHaveBeenCalledWith(
764-expect.objectContaining({
765- env,
766-allowWorkspaceScopedSnapshot: true,
767-}),
768-);
755+const call = requireMockCallParam(currentPluginMetadataSnapshotMock, "current snapshot params");
756+expect(call.env).toBe(env);
757+expect(call.allowWorkspaceScopedSnapshot).toBe(true);
769758expect(loadPluginMetadataSnapshotMock).not.toHaveBeenCalled();
770759});
771760@@ -789,9 +778,7 @@ describe("loadModelCatalog", () => {
789778790779const result = await loadModelCatalog({ config: {} as OpenClawConfig });
791780792-expect(result).toContainEqual(
793-expect.objectContaining({ provider: "ollama", id: "llama3.2", name: "Llama 3.2" }),
794-);
781+expect(requireCatalogEntry(result, "ollama", "llama3.2").name).toBe("Llama 3.2");
795782expect(
796783result.filter((entry) => entry.provider === "openai" && entry.id === "gpt-4.1"),
797784).toHaveLength(1);
@@ -823,16 +810,11 @@ describe("loadModelCatalog", () => {
823810} as OpenClawConfig,
824811});
825812826-expect(result).toContainEqual(
827-expect.objectContaining({
828-provider: "modelscope",
829-id: "Qwen/Qwen3.5-35B-A3B",
830-name: "Qwen3.5 35B",
831-input: ["text", "image"],
832-reasoning: true,
833-contextWindow: 128_000,
834-}),
835-);
813+const entry = requireCatalogEntry(result, "modelscope", "Qwen/Qwen3.5-35B-A3B");
814+expect(entry.name).toBe("Qwen3.5 35B");
815+expect(entry.input).toEqual(["text", "image"]);
816+expect(entry.reasoning).toBe(true);
817+expect(entry.contextWindow).toBe(128_000);
836818});
837819838820it("dedupes configured models against discovered provider aliases", async () => {
@@ -863,7 +845,10 @@ describe("loadModelCatalog", () => {
863845864846const matches = result.filter((entry) => findModelInCatalog([entry], "z-ai", "glm-5"));
865847expect(matches).toHaveLength(1);
866-expect(matches[0]).toMatchObject({ provider: "z.ai", id: "glm-5", name: "GLM-5" });
848+const match = matches[0];
849+expect(match?.provider).toBe("z.ai");
850+expect(match?.id).toBe("glm-5");
851+expect(match?.name).toBe("GLM-5");
867852});
868853869854it("does not add unrelated models when provider plugins return nothing", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。