Files
OmniRoute/tests/unit/modelsDevSync.test.mjs
T
oyi77 cc048e55bf feat: integrate models.dev as authoritative model database with UI controls
- Add models.dev sync engine (src/lib/modelsDevSync.ts) for pricing, capabilities, and model specs
- Fetch from https://models.dev/api.json (109 providers, 4,146+ models, MIT licensed)
- 4-layer pricing resolution: User Override > models.dev > LiteLLM > Hardcoded Default
- New model_capabilities DB table for synced capability data
- UI toggle in Settings > AI tab: enable/disable sync, configure interval (1h-7d), manual sync trigger
- Live stats dashboard showing provider/model/capability counts
- New API route /api/settings/models-dev for sync status and manual triggers
- Fix 39 missing i18n keys across all 30 languages (Memory & Skills tab fully translated)
- 25 unit + integration tests, 1,439 existing tests pass, lint clean, typecheck clean

Closes #979
2026-04-04 18:38:25 +07:00

450 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Unit tests for modelsDevSync.ts
*
* Tests: fetch, transform (pricing + capabilities), provider mapping,
* DB save/retrieve, and resolution order.
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import {
transformModelsDevToPricing,
transformModelsDevToCapabilities,
mapProviderId,
fetchModelsDev,
} from "../../src/lib/modelsDevSync.ts";
// ─── Mock data ───────────────────────────────────────────
const MOCK_MODELS_DEV_DATA = {
openai: {
id: "openai",
name: "OpenAI",
npm: "@ai-sdk/openai",
env: ["OPENAI_API_KEY"],
doc: "https://platform.openai.com/docs/models",
models: {
"gpt-4o": {
id: "gpt-4o",
name: "GPT-4o",
family: "gpt-4",
attachment: true,
reasoning: false,
tool_call: true,
structured_output: true,
temperature: true,
knowledge: "2024-10",
release_date: "2024-05-13",
last_updated: "2024-10-01",
open_weights: false,
cost: {
input: 2.5,
output: 10.0,
cache_read: 1.25,
cache_write: 2.5,
},
limit: {
context: 128000,
input: 128000,
output: 16384,
},
modalities: {
input: ["text", "image"],
output: ["text"],
},
},
"o3-mini": {
id: "o3-mini",
name: "o3-mini",
family: "o3",
attachment: false,
reasoning: true,
tool_call: true,
structured_output: true,
temperature: false,
release_date: "2025-01-31",
last_updated: "2025-01-31",
open_weights: false,
cost: {
input: 1.1,
output: 4.4,
reasoning: 4.4,
},
limit: {
context: 200000,
output: 100000,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
anthropic: {
id: "anthropic",
name: "Anthropic",
npm: "@ai-sdk/anthropic",
env: ["ANTHROPIC_API_KEY"],
doc: "https://docs.anthropic.com/en/docs/about-claud/models",
models: {
"claude-sonnet-4-20250514": {
id: "claude-sonnet-4-20250514",
name: "Claude Sonnet 4",
family: "claude-4",
attachment: true,
reasoning: false,
tool_call: true,
structured_output: true,
temperature: true,
release_date: "2025-05-14",
last_updated: "2025-05-14",
open_weights: false,
cost: {
input: 3.0,
output: 15.0,
cache_read: 0.3,
cache_write: 3.75,
},
limit: {
context: 200000,
output: 64000,
},
modalities: {
input: ["text", "image"],
output: ["text"],
},
},
},
},
deepseek: {
id: "deepseek",
name: "DeepSeek",
npm: "@ai-sdk/openai-compatible",
api: "https://api.deepseek.com/v1",
env: ["DEEPSEEK_API_KEY"],
doc: "https://api-docs.deepseek.com/",
models: {
"deepseek-chat": {
id: "deepseek-chat",
name: "DeepSeek V3",
family: "deepseek-v3",
attachment: false,
reasoning: false,
tool_call: true,
temperature: true,
release_date: "2024-12-26",
last_updated: "2025-01-20",
open_weights: true,
cost: {
input: 0.27,
output: 1.1,
cache_read: 0.07,
},
limit: {
context: 64000,
output: 8192,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
"unknown-provider": {
id: "unknown-provider",
name: "Unknown",
npm: "@ai-sdk/openai-compatible",
api: "https://api.unknown.com/v1",
env: ["UNKNOWN_API_KEY"],
models: {
"some-model": {
id: "some-model",
name: "Some Model",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
cost: {
input: 1.0,
output: 2.0,
},
limit: {
context: 32000,
output: 4096,
},
modalities: {
input: ["text"],
output: ["text"],
},
},
},
},
};
// ─── Tests ───────────────────────────────────────────────
describe("modelsDevSync — transformModelsDevToPricing", () => {
it("transforms pricing data correctly for openai provider", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
// openai maps to ["openai", "cx"]
assert.ok(pricing.openai, "openai provider should exist");
assert.ok(pricing.openai["gpt-4o"], "gpt-4o pricing should exist");
assert.equal(pricing.openai["gpt-4o"].input, 2.5);
assert.equal(pricing.openai["gpt-4o"].output, 10.0);
assert.equal(pricing.openai["gpt-4o"].cached, 1.25);
assert.equal(pricing.openai["gpt-4o"].cache_creation, 2.5);
// cx (Codex alias) should also have the same pricing
assert.ok(pricing.cx, "cx provider should exist");
assert.ok(pricing.cx["gpt-4o"], "gpt-4o pricing in cx should exist");
assert.equal(pricing.cx["gpt-4o"].input, 2.5);
});
it("maps reasoning cost correctly", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing.openai["o3-mini"], "o3-mini pricing should exist");
assert.equal(pricing.openai["o3-mini"].input, 1.1);
assert.equal(pricing.openai["o3-mini"].output, 4.4);
assert.equal(pricing.openai["o3-mini"].reasoning, 4.4);
// o3-mini has no cache_read/cache_write
assert.equal(pricing.openai["o3-mini"].cached, undefined);
assert.equal(pricing.openai["o3-mini"].cache_creation, undefined);
});
it("maps deepseek to both deepseek and if (Qoder) providers", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing.deepseek, "deepseek provider should exist");
assert.ok(pricing.if, "if (Qoder) provider should exist");
assert.equal(pricing.deepseek["deepseek-chat"].input, 0.27);
assert.equal(pricing.deepseek["deepseek-chat"].output, 1.1);
assert.equal(pricing.deepseek["deepseek-chat"].cached, 0.07);
// Should be same in if provider
assert.equal(pricing.if["deepseek-chat"].input, 0.27);
});
it("handles unknown providers by using the models.dev ID as-is", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
assert.ok(pricing["unknown-provider"], "unknown-provider should exist");
assert.equal(pricing["unknown-provider"]["some-model"].input, 1.0);
assert.equal(pricing["unknown-provider"]["some-model"].output, 2.0);
});
it("skips models without cost data", () => {
const dataWithNoCost = {
testprovider: {
id: "testprovider",
models: {
"no-cost-model": {
id: "no-cost-model",
name: "No Cost Model",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
// No cost field
limit: { context: 4096, output: 2048 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const pricing = transformModelsDevToPricing(dataWithNoCost);
assert.equal(pricing.testprovider, undefined, "provider without cost should not exist");
});
it("counts pricing entries correctly", () => {
const pricing = transformModelsDevToPricing(MOCK_MODELS_DEV_DATA);
const totalModels = Object.values(pricing).reduce(
(sum, models) => sum + Object.keys(models).length,
0
);
// openai: 2 models × 2 providers (openai, cx) = 4
// anthropic: 1 model × 2 providers (anthropic, cc) = 2
// deepseek: 1 model × 2 providers (deepseek, if) = 2
// unknown-provider: 1 model × 1 provider = 1
assert.equal(totalModels, 9);
});
});
describe("modelsDevSync — transformModelsDevToCapabilities", () => {
it("transforms capability data correctly", () => {
const caps = transformModelsDevToCapabilities(MOCK_MODELS_DEV_DATA);
assert.ok(caps.openai, "openai capabilities should exist");
assert.ok(caps.openai["gpt-4o"], "gpt-4o capabilities should exist");
const gpt4o = caps.openai["gpt-4o"];
assert.equal(gpt4o.tool_call, true);
assert.equal(gpt4o.reasoning, false);
assert.equal(gpt4o.attachment, true);
assert.equal(gpt4o.structured_output, true);
assert.equal(gpt4o.temperature, true);
assert.equal(gpt4o.limit_context, 128000);
assert.equal(gpt4o.limit_output, 16384);
assert.equal(gpt4o.status, null); // not set in mock
assert.equal(gpt4o.family, "gpt-4");
assert.equal(gpt4o.open_weights, false);
assert.equal(JSON.parse(gpt4o.modalities_input).length, 2);
assert.equal(JSON.parse(gpt4o.modalities_output).length, 1);
});
it("handles interleaved reasoning field", () => {
const dataWithInterleaved = {
testprovider: {
id: "testprovider",
models: {
"reasoning-model": {
id: "reasoning-model",
name: "Reasoning Model",
attachment: false,
reasoning: true,
tool_call: true,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: true,
interleaved: { field: "reasoning_content" },
limit: { context: 200000, output: 64000 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithInterleaved);
assert.equal(caps.testprovider["reasoning-model"].interleaved_field, "reasoning_content");
});
it("handles interleaved as boolean true", () => {
const dataWithBoolInterleaved = {
testprovider: {
id: "testprovider",
models: {
"bool-model": {
id: "bool-model",
name: "Bool Model",
attachment: false,
reasoning: true,
tool_call: true,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
interleaved: true,
limit: { context: 100000, output: 32000 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithBoolInterleaved);
assert.equal(caps.testprovider["bool-model"].interleaved_field, "reasoning_content");
});
it("handles null/missing optional fields", () => {
const dataWithMinimal = {
minimal: {
id: "minimal",
models: {
"minimal-model": {
id: "minimal-model",
name: "Minimal",
attachment: false,
reasoning: false,
tool_call: false,
release_date: "2025-01-01",
last_updated: "2025-01-01",
open_weights: false,
// No cost, no interleaved, no status, no family
limit: { context: 4096, output: 2048 },
modalities: { input: ["text"], output: ["text"] },
},
},
},
};
const caps = transformModelsDevToCapabilities(dataWithMinimal);
const m = caps.minimal["minimal-model"];
assert.equal(m.tool_call, false);
assert.equal(m.reasoning, false);
assert.equal(m.attachment, false);
assert.equal(m.structured_output, null);
assert.equal(m.temperature, null);
assert.equal(m.knowledge_cutoff, null);
assert.equal(m.status, null);
assert.equal(m.family, null);
assert.equal(m.interleaved_field, null);
assert.equal(m.limit_context, 4096);
});
});
describe("modelsDevSync — mapProviderId", () => {
it("maps openai to [openai, cx]", () => {
assert.deepEqual(mapProviderId("openai"), ["openai", "cx"]);
});
it("maps anthropic to [anthropic, cc]", () => {
assert.deepEqual(mapProviderId("anthropic"), ["anthropic", "cc"]);
});
it("maps google to [gemini, gemini-cli]", () => {
assert.deepEqual(mapProviderId("google"), ["gemini", "gemini-cli"]);
});
it("maps deepseek to [deepseek, if]", () => {
assert.deepEqual(mapProviderId("deepseek"), ["deepseek", "if"]);
});
it("falls back to original ID for unmapped providers", () => {
assert.deepEqual(mapProviderId("some-new-provider"), ["some-new-provider"]);
});
it("maps bedrock to [kiro, kr]", () => {
assert.deepEqual(mapProviderId("bedrock"), ["kiro", "kr"]);
});
});
describe("modelsDevSync — fetchModelsDev (live API)", () => {
it("fetches data from models.dev API", async () => {
const data = await fetchModelsDev();
assert.ok(typeof data === "object", "data should be an object");
const providerCount = Object.keys(data).length;
assert.ok(providerCount >= 100, `should have 100+ providers, got ${providerCount}`);
let modelCount = 0;
for (const provider of Object.values(data)) {
const p = provider;
if (p.models) {
modelCount += Object.keys(p.models).length;
}
}
assert.ok(modelCount >= 4000, `should have 4000+ models, got ${modelCount}`);
});
it("returns cached data on second call", async () => {
const data1 = await fetchModelsDev();
const data2 = await fetchModelsDev();
assert.strictEqual(data1, data2, "should return same cached reference");
});
it("has openai provider with gpt-4o model", async () => {
const data = await fetchModelsDev();
assert.ok(data.openai, "openai provider should exist");
assert.ok(data.openai.models["gpt-4o"], "gpt-4o model should exist");
});
it("has anthropic provider with claude models", async () => {
const data = await fetchModelsDev();
assert.ok(data.anthropic, "anthropic provider should exist");
const claudeModels = Object.keys(data.anthropic.models).filter((m) => m.includes("claude"));
assert.ok(claudeModels.length > 0, "should have claude models");
});
});