1import { assert, describe, it } from "@effect/vitest";
2import type { ServerProviderModel } from "@t3tools/contracts";
3
4import * as ModelCatalog from "./ModelCatalog.ts";
5
6/**
7 * Covers catalog semantics with synthetic models. Real model data lives in the
8 * server's model-manifest.json and is not asserted here.
9 */
10const model = (overrides: Partial<ServerProviderModel>): ServerProviderModel => ({
11 slug: "model-test",
12 name: "Model Test",
13 isCustom: false,
14 capabilities: null,
15 ...overrides,
16});
17
18const catalogModel = (
19 slug: string,
20 status: ModelCatalog.ProviderCatalogModel["status"],
21): ModelCatalog.ProviderCatalogModel => ({
22 slug,
23 name: slug,
24 status,
25 capabilities: null,
26 adapter: undefined,
27 profileAdapter: undefined,
28});
29
30/** Strips a vendor prefix, the way a driver maps qualified slugs to catalog families. */
31const prefixFamily: ModelCatalog.CatalogMatching = {
32 family: (slug) => slug.replace(/^vendor\./, ""),
33};
34
35describe("classifyCatalogModels", () => {
36 it("flags only known legacy models, clears stale flags, and skips custom models", () => {
37 const catalog: ModelCatalog.ProviderCatalog = {
38 models: [catalogModel("old-model", "legacy")],
39 defaultChatModel: undefined,
40 };
41 const models = [
42 model({ slug: "current-a" }),
43 // Stale flag from a previous classification pass must be cleared.
44 model({ slug: "current-b", isLegacy: true }),
45 model({ slug: "old-model" }),
46 // Custom models are user-defined and never reclassified.
47 model({ slug: "my-own-model", isCustom: true, isLegacy: true }),
48 ];
49 assert.deepStrictEqual(
50 ModelCatalog.classifyCatalogModels(models, catalog).map((entry) => [
51 entry.slug,
52 entry.isLegacy ?? false,
53 ]),
54 [
55 ["current-a", false],
56 ["current-b", false],
57 ["old-model", true],
58 ["my-own-model", true],
59 ],
60 );
61 });
62
63 it("matches qualified slugs through the driver's family without changing their ids", () => {
64 const catalog: ModelCatalog.ProviderCatalog = {
65 models: [catalogModel("model-old", "legacy")],
66 defaultChatModel: undefined,
67 };
68 assert.deepStrictEqual(
69 ModelCatalog.classifyCatalogModels(
70 [model({ slug: "vendor.model-test", isLegacy: true }), model({ slug: "vendor.model-old" })],
71 catalog,
72 prefixFamily,
73 ).map((entry) => [entry.slug, entry.isLegacy ?? false]),
74 [
75 ["vendor.model-test", false],
76 ["vendor.model-old", true],
77 ],
78 );
79 });
80
81 it("keeps discovered models current when the driver has no catalog", () => {
82 assert.deepStrictEqual(
83 ModelCatalog.classifyCatalogModels([model({ slug: "new", isLegacy: true })], undefined),
84 [model({ slug: "new" })],
85 );
86 });
87});
88
89describe("applyCatalogDefault", () => {
90 it("moves the default flag and its aliases to the catalog's chat default", () => {
91 const catalog: ModelCatalog.ProviderCatalog = {
92 models: [catalogModel("model-new", "current")],
93 defaultChatModel: "model-new",
94 };
95 const models = [
96 model({ slug: "model-old", isDefault: true, aliases: ["provider-default"] }),
97 model({ slug: "model-new" }),
98 ];
99 assert.deepStrictEqual(ModelCatalog.applyCatalogDefault(models, catalog), [
100 model({ slug: "model-old" }),
101 model({ slug: "model-new", isDefault: true, aliases: ["provider-default"] }),
102 ]);
103 // The account does not offer the catalog default: keep the runtime's choice.
104 assert.deepStrictEqual(
105 ModelCatalog.applyCatalogDefault(models.slice(0, 1), catalog),
106 models.slice(0, 1),
107 );
108 });
109
110 it("resolves the catalog default to a qualified discovered model", () => {
111 const catalog: ModelCatalog.ProviderCatalog = {
112 models: [],
113 defaultChatModel: "model-test",
114 };
115 const models = [
116 model({ slug: "vendor.model-old", isDefault: true }),
117 model({ slug: "vendor.model-test" }),
118 ];
119 assert.strictEqual(
120 ModelCatalog.applyCatalogDefault(models, catalog, prefixFamily).find(
121 (entry) => entry.isDefault,
122 )?.slug,
123 "vendor.model-test",
124 );
125 });
126});