import { describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { resolveProviderModels } from "@oh-my-pi/pi-catalog/model-manager"; import { PROVIDER_DESCRIPTORS } from "@oh-my-pi/pi-catalog/provider-models/descriptors"; import { opencodeGoModelManagerOptions, opencodeZenModelManagerOptions, } from "@oh-my-pi/pi-catalog/provider-models/openai-compat"; const LIVE_FREE_MODEL_IDS = [ "deepseek-v4-flash-free", "hy3-free", "mimo-v2.5-free", "nemotron-3-ultra-free", "north-mini-code-free", ] as const; const LIVE_PAID_MODEL_IDS = ["claude-opus-4-8", "gpt-5.5"] as const; function modelListResponse(ids: readonly string[]): Response { return Response.json({ object: "list", data: ids.map(id => ({ id, object: "model", owned_by: "opencode" })), }); } describe("OpenCode provider discovery", () => { test("treats the OpenCode model endpoints as authoritative catalogs", () => { for (const providerId of ["opencode-go", "opencode-zen"]) { const descriptor = PROVIDER_DESCRIPTORS.find(item => item.providerId === providerId); expect(descriptor?.dynamicModelsAuthoritative).toBe(true); } expect(opencodeGoModelManagerOptions().dynamicModelsAuthoritative).toBe(true); expect(opencodeZenModelManagerOptions().dynamicModelsAuthoritative).toBe(true); }); test("replaces stale bundled Zen models with each credential's live endpoint list", async () => { const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "pi-catalog-opencode-zen-")); try { let freeFetches = 0; const freeOptions = opencodeZenModelManagerOptions({ apiKey: "free-account-key", fetch: async () => { freeFetches++; return modelListResponse(LIVE_FREE_MODEL_IDS); }, }); const freeResult = await resolveProviderModels( { ...freeOptions, cacheDbPath: path.join(tempDir, "models.db") }, "online-if-uncached", ); let paidFetches = 0; const paidOptions = opencodeZenModelManagerOptions({ apiKey: "paid-account-key", fetch: async () => { paidFetches++; return modelListResponse(LIVE_PAID_MODEL_IDS); }, }); const paidResult = await resolveProviderModels( { ...paidOptions, cacheDbPath: path.join(tempDir, "models.db") }, "online-if-uncached", ); expect(freeOptions.cacheProviderId).not.toBe(paidOptions.cacheProviderId); expect(freeResult.stale).toBe(false); expect(freeResult.models.map(model => model.id).sort()).toEqual([...LIVE_FREE_MODEL_IDS].sort()); expect(paidResult.stale).toBe(false); expect(paidResult.models.map(model => model.id).sort()).toEqual([...LIVE_PAID_MODEL_IDS].sort()); expect([freeFetches, paidFetches]).toEqual([1, 1]); } finally { await fs.rm(tempDir, { recursive: true, force: true }); } }); });