import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { ProviderDriverKind, type ServerProviderModel } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import { HttpClient, HttpClientResponse } from "effect/unstable/http"; import * as ServerConfig from "../config.ts"; import * as ServerSettings from "../serverSettings.ts"; import { BUNDLED_MODEL_MANIFEST, classifyModels, isLegacyModel, make, type ModelManifestData, } from "./ModelManifest.ts"; const CODEX = ProviderDriverKind.make("codex"); const CLAUDE = ProviderDriverKind.make("claudeAgent"); const CURSOR = ProviderDriverKind.make("cursor"); describe("isLegacyModel (bundled manifest)", () => { it("keeps current Codex models out of legacy models", () => { assert.deepStrictEqual( [ "gpt-5.6-luna", "gpt-5.6-terra", "gpt-5.6-sol", "gpt-daybreak-blue-latest", "gpt-daybreak-red-latest", "gpt-5.4", ].map((model) => [model, isLegacyModel(BUNDLED_MODEL_MANIFEST, CODEX, model)]), [ ["gpt-5.6-luna", false], ["gpt-5.6-terra", false], ["gpt-5.6-sol", false], ["gpt-daybreak-blue-latest", false], ["gpt-daybreak-red-latest", false], ["gpt-5.4", true], ], ); }); it("keeps only the Claude 5 family out of legacy models", () => { assert.deepStrictEqual( ["claude-fable-5", "claude-opus-5", "claude-sonnet-5", "claude-opus-4-8"].map((model) => [ model, isLegacyModel(BUNDLED_MODEL_MANIFEST, CLAUDE, model), ]), [ ["claude-fable-5", false], ["claude-opus-5", false], ["claude-sonnet-5", false], ["claude-opus-4-8", true], ], ); }); it("leaves driver kinds without a manifest entry unflagged", () => { assert.isFalse(isLegacyModel(BUNDLED_MODEL_MANIFEST, CURSOR, "composer-1.5")); }); }); const model = (overrides: Partial): ServerProviderModel => ({ slug: "gpt-test", name: "GPT Test", isCustom: false, capabilities: null, ...overrides, }); describe("classifyModels", () => { it("flags non-current models, clears stale flags, and skips custom models", () => { const models = [ model({ slug: "gpt-5.6-sol" }), // Stale flag from a previous classification pass must be cleared. model({ slug: "gpt-5.6-luna", isLegacy: true }), model({ slug: "gpt-5.4" }), // Custom models are user-defined and never reclassified. model({ slug: "my-own-model", isCustom: true }), ]; assert.deepStrictEqual( classifyModels(models, BUNDLED_MODEL_MANIFEST, CODEX).map((entry) => [ entry.slug, entry.isLegacy ?? false, ]), [ ["gpt-5.6-sol", false], ["gpt-5.6-luna", false], ["gpt-5.4", true], ["my-own-model", false], ], ); }); }); const REMOTE_MANIFEST: ModelManifestData = { version: 1, currentModels: { codex: ["gpt-5.4"], claudeAgent: ["claude-fable-5"], }, }; const httpClientLayer = (handler: () => Response) => Layer.succeed( HttpClient.HttpClient, HttpClient.make((request) => Effect.succeed(HttpClientResponse.fromWeb(request, handler()))), ); const serviceLayers = (input: { readonly prefix: string; readonly response: () => Response; readonly settings?: Parameters[0]; }) => ServerConfig.layerTest(process.cwd(), { prefix: input.prefix }).pipe( Layer.provideMerge(NodeServices.layer), Layer.provideMerge(ServerSettings.layerTest(input.settings ?? {})), Layer.provideMerge(httpClientLayer(input.response)), ); describe("ModelManifest service", () => { it.live("prefers a fetched manifest over the bundle and caches it to disk", () => Effect.gen(function* () { const service = yield* make; const refreshed = yield* service.refresh; assert.deepStrictEqual(refreshed, REMOTE_MANIFEST); assert.isTrue(isLegacyModel(refreshed, CODEX, "gpt-5.6-sol")); assert.isFalse(isLegacyModel(refreshed, CODEX, "gpt-5.4")); // A fresh service instance sees the disk cache without another fetch: // its HTTP layer is still stubbed, but `current` never fetches at all. const rebooted = yield* make; assert.deepStrictEqual(yield* rebooted.current, REMOTE_MANIFEST); }).pipe( Effect.scoped, Effect.provide( serviceLayers({ prefix: "model-manifest-fetch-test", response: () => Response.json(REMOTE_MANIFEST), }), ), ), ); it.live("keeps the bundled manifest when the remote payload is malformed", () => Effect.gen(function* () { const service = yield* make; assert.deepStrictEqual(yield* service.refresh, BUNDLED_MODEL_MANIFEST); }).pipe( Effect.scoped, Effect.provide( serviceLayers({ prefix: "model-manifest-malformed-test", response: () => Response.json({ version: 999, nonsense: true }), }), ), ), ); it.live("does not fetch when provider update checks are disabled", () => Effect.gen(function* () { let fetchCount = 0; const service = yield* make.pipe( Effect.provide( httpClientLayer(() => { fetchCount += 1; return Response.json(REMOTE_MANIFEST); }), ), ); assert.deepStrictEqual(yield* service.refresh, BUNDLED_MODEL_MANIFEST); assert.strictEqual(fetchCount, 0); }).pipe( Effect.scoped, Effect.provide( serviceLayers({ prefix: "model-manifest-optout-test", response: () => Response.json(REMOTE_MANIFEST), settings: { enableProviderUpdateChecks: false }, }), ), ), ); });