evOo-local/packages/adapters/src/pi-models.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

141 lines
5.9 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { catalogModelLabel, listPiCatalog, scriptedCatalogEntry } from "./pi-models.js";
describe("Pi model catalog", () => {
it("keeps the custom catalog independent of server model IDs", () => {
const custom = listPiCatalog().filter((entry) => entry.provider === "openai-compatible");
expect(custom).toHaveLength(1);
expect(custom[0]).toMatchObject({ id: "custom", placeholder: true, reasoning: false });
});
afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
});
it("lists real Pi providers instead of a two-option dropdown", () => {
const catalog = listPiCatalog();
const providers = new Set(catalog.map((entry) => entry.provider));
expect(catalog.length).toBeGreaterThan(20);
expect(providers.has("openrouter")).toBe(true);
expect(providers.size).toBeGreaterThan(5);
expect(
catalog.some(
(entry) => entry.auth === "oauth" || entry.auth === "both" || entry.subscription,
),
).toBe(true);
const chatgpt = catalog.find((entry) => entry.provider === "openai-codex");
expect(chatgpt?.signIn).toBe("device-code");
expect(chatgpt?.billing).toMatch(/ChatGPT Plus or Pro/);
const copilot = catalog.find((entry) => entry.provider === "github-copilot");
expect(copilot?.signIn).toBe("device-code");
const grok = catalog.find((entry) => entry.provider === "xai");
expect(grok?.signIn).toBe("device-code");
const claude = catalog.find((entry) => entry.provider === "anthropic");
expect(claude).toMatchObject({
signIn: "auth-url",
authHint: "Claude Pro/Max / key",
oauthLabel: "Sign in with Claude Pro/Max",
billing: "",
});
expect(scriptedCatalogEntry.provider).toBe("scripted");
});
it("lists current xAI and OpenCode Go models from the Pi catalog", () => {
const catalog = listPiCatalog();
const ids = (provider: string) =>
catalog.filter((entry) => entry.provider === provider).map((entry) => entry.id);
expect(ids("xai")).toContain("grok-4.6");
expect(ids("opencode-go")).toContain("glm-5.3");
const grok46 = catalog.find((entry) => entry.provider === "xai" && entry.id === "grok-4.6");
expect(grok46).toMatchObject({
reasoning: true,
thinkingLevels: ["low", "medium", "high", "xhigh"],
});
const openAiCompatible = catalog.find((entry) => entry.provider === "openai-compatible");
expect(openAiCompatible).toMatchObject({ id: "custom", placeholder: true });
});
it("adds a configured OpenRouter model that is newer than the static catalog", async () => {
vi.stubEnv("PI_DEFAULT_PROVIDER", " openrouter ");
vi.stubEnv("PI_DEFAULT_MODEL", " rakazo-test/unknown-future-model ");
vi.resetModules();
const { listPiCatalog: listConfiguredCatalog } = await import("./pi-models.js");
expect(listConfiguredCatalog()[0]).toMatchObject({
provider: "openrouter",
id: "rakazo-test/unknown-future-model",
label: "rakazo-test/unknown-future-model",
});
});
it("normalizes a PI_DEFAULT_MODEL id that ends in -latest", async () => {
vi.stubEnv("PI_DEFAULT_PROVIDER", "openrouter");
vi.stubEnv("PI_DEFAULT_MODEL", "foo-latest");
vi.resetModules();
const { listPiCatalog: listConfiguredCatalog } = await import("./pi-models.js");
expect(listConfiguredCatalog()[0]).toMatchObject({
provider: "openrouter",
id: "foo-latest",
label: "foo (auto-updates)",
});
});
it("does not advertise a synthetic model for providers the runtime cannot synthesize", async () => {
vi.stubEnv("PI_DEFAULT_PROVIDER", "anthropic");
vi.stubEnv("PI_DEFAULT_MODEL", "future/unknown-model");
vi.resetModules();
const { listPiCatalog: listConfiguredCatalog } = await import("./pi-models.js");
expect(
listConfiguredCatalog().some(
(entry) => entry.provider === "anthropic" && entry.id === "future/unknown-model",
),
).toBe(false);
});
it('never labels an older model "latest" and keeps aliases distinct from snapshots', () => {
const catalog = listPiCatalog();
const label = (id: string) =>
catalog.find((entry) => entry.provider === "anthropic" && entry.id === id)?.label;
expect(label("claude-opus-5")).toBeDefined();
expect(label("claude-opus-4-5")).toBe("Claude Opus 4.5 (auto-updates)");
expect(label("claude-haiku-4-5")).toBe("Claude Haiku 4.5 (auto-updates)");
expect(label("claude-haiku-4-5-20251001")).toBe("Claude Haiku 4.5");
expect(catalog.some((entry) => /\blatest\b/i.test(entry.label))).toBe(false);
});
});
describe("catalogModelLabel", () => {
const providerModelIds = [
"claude-opus-4-5",
"claude-opus-4-5-20251101",
"mistral-medium",
"mistral-medium-2508",
"mistral-small",
"mistral-small-260401",
"foo",
"foo-preview",
];
it.each([
// Alias: the id ends in `latest`, or a dated sibling proves the undated id floats.
["claude-opus-4-5", "Claude Opus 4.5 (latest)", "Claude Opus 4.5 (auto-updates)"],
["mistral-medium", "Mistral Medium Latest", "Mistral Medium (auto-updates)"],
["mistral-small", "Mistral Small Latest", "Mistral Small (auto-updates)"],
["gemini-flash-latest", "Gemini Flash Latest", "Gemini Flash (auto-updates)"],
["foo-latest", "foo-latest", "foo (auto-updates)"],
["foo/latest", "foo/latest", "foo (auto-updates)"],
// Pinned: `-preview` is its own model and a dated id is already a snapshot, so promise nothing.
["foo", "Foo Latest", "Foo"],
["claude-opus-4-5-20251101", "Claude Opus 4.5 (latest)", "Claude Opus 4.5"],
// Untouched: no marker, no name, or nothing left once the marker goes.
["claude-opus-5", "Claude Opus 5", "Claude Opus 5"],
["some-model", undefined, "some-model"],
["latest", "latest", "latest"],
])("labels %s / %s as %s", (id, name, expected) => {
expect(catalogModelLabel(id, name, providerModelIds)).toBe(expected);
});
});