TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import { describe, it, expect } from "vitest";2import { customModelKey, parseCustomModelKey, isCustomModelKey, parseModelListing, mergeCustomModels, normalizeBaseUrl, normalizeModelsPath, createCustomEndpointAdapter } from "@/lib/ai/providers/custom";3import { parseModelKey, isProviderId, PROVIDER_IDS, KEYED_PROVIDER_IDS } from "@/lib/ai/core/types";4import { getAdapter, PROVIDER_META } from "@/lib/ai/providers";5import { PROVIDERS, PROVIDER_ORDER } from "@/lib/client/providers";67describe("custom model keys", () => {8 it("round-trips ids that contain colons (Ollama tags)", () => {9 const key = customModelKey("cep_abc123", "llama3.1:8b-instruct-q4_K_M");10 expect(key).toBe("custom/cep_abc123:llama3.1:8b-instruct-q4_K_M");11 expect(parseCustomModelKey(key)).toEqual({ endpointId: "cep_abc123", modelId: "llama3.1:8b-instruct-q4_K_M" });12 expect(isCustomModelKey(key)).toBe(true);13 });14 it("keeps parseModelKey compatible: provider is `custom`, id keeps the endpoint prefix", () => {15 const parsed = parseModelKey("custom/cep_x:mistral-nemo");16 expect(parsed).toEqual({ provider: "custom", id: "cep_x:mistral-nemo" });17 expect(isProviderId("custom")).toBe(true);18 expect(PROVIDER_IDS).toContain("custom");19 expect(KEYED_PROVIDER_IDS).not.toContain("custom");20 });21 it("rejects malformed keys", () => {22 expect(parseCustomModelKey("openai/gpt-5")).toBeNull();23 expect(parseCustomModelKey("custom/noendpoint")).toBeNull();24 expect(parseCustomModelKey("custom/cep_x:")).toBeNull();25 expect(parseCustomModelKey("custom/:model")).toBeNull();26 });27});2829describe("exhaustive provider maps include `custom`", () => {30 it("has metadata, colors and a placeholder adapter", () => {31 expect(PROVIDER_META.custom.color).toBe("var(--p-custom)");32 expect(PROVIDERS.custom.colorVar).toBe("var(--p-custom)");33 expect(PROVIDER_ORDER).not.toContain("custom"); // keyed providers only in the key list34 expect(getAdapter("custom").id).toBe("custom");35 });36 it("placeholder adapter fails loudly instead of calling anything", async () => {37 const a = getAdapter("custom");38 const v = await a.validateApiKey("x");39 expect(v.ok).toBe(false);40 const events = [];41 for await (const ev of a.streamChat({ provider: "custom", model: "m", apiKey: "k", messages: [] })) events.push(ev);42 expect(events[0]).toMatchObject({ type: "error" });43 });44});4546describe("discovery parsing + merge", () => {47 it("understands OpenAI, bare-array and Ollama listings", () => {48 expect(parseModelListing({ data: [{ id: "a", owned_by: "me" }, { id: "b" }] }).map((m) => m.id)).toEqual(["a", "b"]);49 expect(parseModelListing(["x", { id: "y" }]).map((m) => m.id)).toEqual(["x", "y"]);50 expect(parseModelListing({ models: [{ name: "llama3:8b", size: 1 }, { model: "phi3" }] }).map((m) => m.id)).toEqual(["llama3:8b", "phi3"]);51 expect(parseModelListing({ nope: true })).toEqual([]);52 expect(parseModelListing(null)).toEqual([]);53 });54 it("applies manual declarations as capability overrides and appends missing ids", () => {55 const cfg = { id: "cep_1", name: "My Ollama" };56 const models = mergeCustomModels(cfg, [{ id: "llama3.2-vision:11b", owned_by: "library" }, { id: "qwen2.5:7b" }], [{ id: "llama3.2-vision:11b", vision: true, tools: true, contextTokens: 128_000 }, { id: "manual-only", displayName: "Manual", reasoning: true }]);57 expect(models.map((m) => m.key)).toEqual(["custom/cep_1:llama3.2-vision:11b", "custom/cep_1:qwen2.5:7b", "custom/cep_1:manual-only"]);58 const vision = models[0];59 expect(vision.provider).toBe("custom");60 expect(vision.capabilities.vision).toBe(true);61 expect(vision.capabilities.tools).toBe(true);62 expect(vision.limits?.contextTokens).toBe(128_000);63 expect(vision.metadata?.source).toBe("discovery");64 expect(vision.pricing).toBeNull(); // never invent a price65 expect(models[1].capabilities.vision).toBe(false);66 expect(models[1].capabilities.tools).toBe(false);67 expect(models[2].displayName).toBe("Manual");68 expect(models[2].capabilities.reasoning).toBe(true);69 expect(models[2].metadata?.source).toBe("manual");70 expect(models[2].family).toBe("My Ollama");71 });72 it("normalizes base URLs and model paths", () => {73 expect(normalizeBaseUrl(" http://localhost:11434/v1/// ")).toBe("http://localhost:11434/v1");74 expect(normalizeModelsPath("models")).toBe("/models");75 expect(normalizeModelsPath(" ")).toBe("");76 expect(normalizeModelsPath(null)).toBe("");77 });78 it("builds a per-endpoint adapter with id `custom` and the endpoint's name", () => {79 const a = createCustomEndpointAdapter({ id: "cep_2", name: "LM Studio", baseUrl: "http://127.0.0.1:1234/v1/", headers: { "X-Test": "1" } });80 expect(a.id).toBe("custom");81 expect(a.name).toBe("LM Studio");82 const err = a.normalizeError(new Error("boom"));83 expect(err.provider).toBe("custom");84 });85});86