Files
anomalyco--models.dev/packages/core/test/sync.test.ts
T
2026-07-13 12:28:55 +08:00

1325 lines
40 KiB
TypeScript

import { expect, test } from "bun:test";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { formatToml, preserveReasoningOptions, syncProvider, type SyncProvider } from "../src/sync/index.js";
import {
anthropic,
buildAnthropicModel,
parseAnthropicPricing,
type AnthropicModel,
} from "../src/sync/providers/anthropic.js";
import { buildDeepInfraModel, type DeepInfraModel } from "../src/sync/providers/deepinfra.js";
import {
buildDigitalOceanModel,
digitalocean,
fetchDigitalOceanModels,
parseDigitalOceanModels,
resolveDigitalOceanBaseModel,
type DigitalOceanSourceModel,
} from "../src/sync/providers/digitalocean.js";
import {
buildEmpiriolabsModel,
empiriolabs,
resolveEmpiriolabsBaseModel,
type EmpiriolabsModel,
} from "../src/sync/providers/empiriolabs.js";
import {
buildOpenRouterModel,
openrouter,
resolveCanonicalBaseModel,
type OpenRouterModel,
} from "../src/sync/providers/openrouter.js";
import { buildLLMGatewayModel, type LLMGatewayModel } from "../src/sync/providers/llmgateway.js";
import { openai, parseOpenAIModels } from "../src/sync/providers/openai.js";
import { resolveVeniceBaseModel } from "../src/sync/providers/venice.js";
import { buildVercelModel, vercel } from "../src/sync/providers/vercel.js";
import { buildWandbModel, type WandbModel } from "../src/sync/providers/wandb.js";
import { buildXAIModel } from "../src/sync/providers/xai.js";
function anthropicModel(overrides: Partial<AnthropicModel> = {}): AnthropicModel {
return {
id: "claude-sonnet-5",
display_name: "Claude Sonnet 5",
created_at: "2026-06-30T00:00:00Z",
max_input_tokens: 1_000_000,
max_tokens: 128_000,
capabilities: {
image_input: { supported: true },
pdf_input: { supported: true },
structured_outputs: { supported: true },
thinking: {
supported: true,
types: { adaptive: { supported: true } },
},
effort: {
supported: true,
low: { supported: true },
medium: { supported: true },
high: { supported: true },
xhigh: { supported: true },
max: { supported: true },
},
},
...overrides,
};
}
const anthropicPricingMarkdown = `
## Model pricing
| Model | Base Input Tokens | 5m Cache Writes | 1h Cache Writes | Cache Hits & Refreshes | Output Tokens |
| --- | --- | --- | --- | --- | --- |
| Claude Opus 4.8 | $5 / MTok | $6.25 / MTok | $10 / MTok | $0.50 / MTok | $25 / MTok |
| Claude Opus 4.1 ([deprecated](/deprecated)) | $15 / MTok | $18.75 / MTok | $30 / MTok | $1.50 / MTok | $75 / MTok |
| Claude Sonnet 5 [through August 31, 2026](/pricing) | $2 / MTok | $2.50 / MTok | $4 / MTok | $0.20 / MTok | $10 / MTok |
| Claude Sonnet 5 starting September 1, 2026 | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
| Claude Sonnet 4.6 | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
| Claude Sonnet 4.5 | $3 / MTok | $3.75 / MTok | $6 / MTok | $0.30 / MTok | $15 / MTok |
## Cloud platform pricing
`;
test("parses current and future Anthropic pricing rows", () => {
const introductory = parseAnthropicPricing(anthropicPricingMarkdown, new Date("2026-07-04T00:00:00Z"));
expect(introductory.get("claude sonnet 5")).toMatchObject({
input: 2,
output: 10,
cacheRead: 0.2,
cacheWrite: 2.5,
});
expect(introductory.get("claude opus 4.1")?.deprecated).toBe(true);
const standard = parseAnthropicPricing(anthropicPricingMarkdown, new Date("2026-09-01T00:00:00Z"));
expect(standard.get("claude sonnet 5")).toMatchObject({ input: 3, output: 15 });
});
test("syncs Anthropic capabilities and exact effort levels", () => {
const model = buildAnthropicModel(anthropicModel(), {
name: "Claude Sonnet 5",
description: "Balanced Claude model for coding and agentic workflows",
release_date: "2026-06-30",
last_updated: "2026-06-30",
attachment: true,
reasoning: true,
reasoning_options: [{ type: "toggle" }, { type: "budget_tokens", min: 1_024 }],
tool_call: true,
open_weights: false,
cost: { input: 2, output: 10 },
limit: { context: 1_000_000, output: 128_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
});
expect(model).toMatchObject({
reasoning: true,
reasoning_options: [
{ type: "toggle" },
{ type: "effort", values: ["low", "medium", "high", "xhigh", "max"] },
],
structured_output: true,
limit: { context: 1_000_000, output: 128_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
});
});
test("adds manual budget control for new Anthropic models", () => {
const model = buildAnthropicModel(anthropicModel({
capabilities: {
thinking: {
supported: true,
types: { enabled: { supported: true } },
},
},
}), undefined, "anthropic/claude-sonnet-5");
expect(model.reasoning_options).toEqual([{ type: "budget_tokens" }]);
});
test("labels Anthropic aliases as latest", () => {
const model = buildAnthropicModel(anthropicModel({
id: "claude-sonnet-5",
canonical_id: "claude-sonnet-5-20260630",
}), undefined, "anthropic/claude-sonnet-5");
expect(model.name).toBe("Claude Sonnet 5 (latest)");
});
test("Anthropic sync preserves base model inheritance", () => {
const resolved = {
base_model: "anthropic/claude-opus-4-5",
name: "Claude Opus 4.5 (latest)",
description: "Flagship Claude model",
release_date: "2025-11-24",
last_updated: "2025-11-24",
attachment: true,
reasoning: true,
tool_call: true,
knowledge: "2025-05",
open_weights: false,
cost: { input: 5, output: 25 },
limit: { context: 200_000, output: 64_000 },
modalities: { input: ["text" as const, "image" as const], output: ["text" as const] },
};
const translated = anthropic.translateModel(anthropicModel({
id: "claude-opus-4-5",
canonical_id: "claude-opus-4-5-20251101",
display_name: "Claude Opus 4.5",
created_at: "2025-11-24T00:00:00Z",
max_input_tokens: 200_000,
max_tokens: 64_000,
}), {
existing: () => resolved,
authored: () => ({ base_model: "anthropic/claude-opus-4-5" }),
});
expect(translated?.model).toMatchObject({
base_model: "anthropic/claude-opus-4-5",
name: "Claude Opus 4.5 (latest)",
});
expect(translated?.model).not.toHaveProperty("knowledge");
expect(translated?.model).not.toHaveProperty("release_date");
});
test("filters customer-owned OpenAI models from availability tracking", () => {
expect(parseOpenAIModels({
object: "list",
data: [
{ id: "gpt-5.5", object: "model", created: 1, owned_by: "system" },
{ id: "ft:gpt-5.5:org:custom", object: "model", created: 2, owned_by: "org-example" },
{ id: "custom-model", object: "model", created: 3, owned_by: "org-example" },
],
}).map((model) => model.id)).toEqual(["gpt-5.5"]);
});
test("OpenAI availability sync preserves authored metadata", () => {
const authored = {
base_model: "openai/gpt-5.5",
cost: { input: 5, output: 30 },
};
expect(openai.translateModel(
{ id: "gpt-5.5", object: "model", created: 1, owned_by: "system" },
{ existing: () => authored as never, authored: () => authored },
)).toEqual({ id: "gpt-5.5", model: authored });
});
test("OpenAI availability sync retains models absent from a scoped response", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "sync-openai-"));
const modelsDir = path.join(dir, "providers", "openai", "models");
await Bun.write(path.join(modelsDir, "gpt-existing.toml"), [
'name = "Existing GPT"',
'release_date = "2026-01-01"',
'last_updated = "2026-01-01"',
"attachment = false",
"reasoning = false",
"tool_call = true",
"open_weights = false",
"",
"[cost]",
"input = 1",
"output = 2",
"",
"[limit]",
"context = 1_000",
"output = 100",
"",
"[modalities]",
'input = ["text"]',
'output = ["text"]',
"",
].join("\n"));
try {
const result = await syncProvider({
...openai,
modelsDir,
async fetchModels() {
return {
object: "list",
data: [{ id: "gpt-scoped", object: "model", created: 1, owned_by: "system" }],
};
},
});
expect(result.deleted).toBe(0);
expect(result.unchanged).toBe(1);
expect(await Bun.file(path.join(modelsDir, "gpt-existing.toml")).exists()).toBe(true);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
function digitalOceanModel(overrides: Partial<DigitalOceanSourceModel> = {}): DigitalOceanSourceModel {
return {
id: "anthropic-claude-4.6-sonnet",
name: "Claude Sonnet 4.6",
lifecycle_status: "active",
type: "chat",
thinking: true,
reasoning_efforts: ["low", "medium", "high"],
context_window: 1_000_000,
max_output_tokens: 8_192,
availability: ["serverless"],
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
settings: [{ name: "max_tokens", max: 64_000 }],
created_at: "2026-02-17T00:00:00Z",
pricing: {
input: 3,
output: 15,
cacheRead: 0.3,
},
...overrides,
};
}
test("syncs DigitalOcean catalog limits and extended pricing thresholds", () => {
const model = buildDigitalOceanModel(digitalOceanModel({
pricing: {
input: 3,
output: 15,
cacheRead: 0.3,
extended: {
context: 272_000,
input: 6,
output: 22.5,
cacheRead: 0.6,
cacheWrite: 7.5,
},
},
}), {
name: "Claude Sonnet 4.6",
description: "Curated DigitalOcean description",
family: "claude-sonnet",
release_date: "2026-02-17",
last_updated: "2026-03-13",
attachment: true,
reasoning: true,
reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }],
temperature: true,
tool_call: true,
open_weights: false,
cost: {
input: 2,
output: 10,
cache_read: 0.3,
cache_write: 3.75,
tiers: [{
tier: { type: "context", size: 200_000 },
input: 4,
output: 15,
cache_read: 0.6,
cache_write: 7.5,
}],
},
limit: { context: 200_000, output: 64_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
});
expect(model).toMatchObject({
description: "Curated DigitalOcean description",
last_updated: "2026-03-13",
cost: {
input: 3,
output: 15,
cache_read: 0.3,
cache_write: 3.75,
tiers: [{
tier: { type: "context", size: 272_000 },
input: 6,
output: 22.5,
cache_read: 0.6,
cache_write: 7.5,
}],
},
limit: { context: 1_000_000, output: 8_192 },
});
});
test("skips existing dedicated-only DigitalOcean models without token pricing", () => {
const existing = {
name: "Mistral 7B Instruct v0.3",
description: "Mistral model for multilingual chat and dedicated inference",
family: "mistral" as const,
release_date: "2024-05-22",
last_updated: "2024-05-22",
attachment: false,
reasoning: false,
temperature: true,
tool_call: true,
open_weights: true,
limit: { context: 32_768, output: 32_768 },
modalities: { input: ["text" as const], output: ["text" as const] },
};
const translated = digitalocean.translateModel(digitalOceanModel({
id: "mistral-7b-instruct-v0.3",
name: "Mistral 7B Instruct v0.3",
thinking: false,
context_window: 32_768,
modalities: { input: ["text"], output: ["text"] },
settings: [{ name: "max_tokens", max: 8_192 }],
pricing: undefined,
}), {
existing: () => existing,
authored: () => existing,
});
expect(translated).toBeUndefined();
});
test("syncs existing DigitalOcean image models with catalog output limits", () => {
const existing = {
name: "GPT Image 1.5",
description: "Image generation model",
family: "gpt-image" as const,
release_date: "2025-11-25",
last_updated: "2025-11-25",
attachment: true,
reasoning: false,
temperature: false,
tool_call: false,
open_weights: false,
cost: { input: 5, output: 10 },
limit: { context: 0, output: 0 },
modalities: { input: ["text" as const, "image" as const], output: ["image" as const] },
};
const translated = digitalocean.translateModel(digitalOceanModel({
id: "openai-gpt-image-1.5",
name: "GPT Image 1.5",
context_window: undefined,
max_output_tokens: 16_384,
modalities: { input: ["text", "image"], output: ["text", "image"] },
settings: [],
pricing: { input: 6, output: 12 },
}), {
existing: () => existing,
authored: () => existing,
});
expect(translated?.model).toMatchObject({
cost: { input: 6, output: 12 },
limit: { context: 0, output: 16_384 },
});
});
test("filters unmanaged DigitalOcean models and joins catalog data by ID", () => {
const models = parseDigitalOceanModels({
models: [
digitalOceanModel({ id: "kimi-k2.5", name: "Kimi K2", pricing: undefined }),
digitalOceanModel({
id: "bge-m3",
name: "BGE M3",
type: "embedding",
modalities: { input: ["text"], output: ["text"] },
pricing: undefined,
}),
],
catalog: [
{
model_id: "kimi-k2.5",
name: "Kimi K2.5",
context_window: "256000",
max_output_tokens: "32768",
availability: ["serverless", "dedicated"],
pricing: {
input_price_per_million: 0.000000375,
output_price_per_million: 0.000002025,
cache_read_input_price_per_million: 0.000000203,
},
pricing_detail: {
variants: [{
tier: "MODEL_PRICING_TIER_EXTENDED_272K",
mode: "MODEL_BILLING_MODE_INTERACTIVE",
prices: {
input_price_per_million: 0.00000075,
output_price_per_million: 0.000003,
},
}],
},
},
{
model_id: "bge-m3",
name: "BGE M3",
availability: ["serverless"],
},
],
});
expect(models).toHaveLength(1);
expect(models[0]).toMatchObject({
id: "kimi-k2.5",
context_window: "256000",
max_output_tokens: "32768",
pricing: {
input: 0.375,
output: 2.025,
cacheRead: 0.203,
extended: {
context: 272_000,
input: 0.75,
output: 3,
},
},
});
});
test("maps DigitalOcean 1M catalog pricing to its 200K threshold", () => {
const models = parseDigitalOceanModels({
models: [digitalOceanModel({ pricing: undefined })],
catalog: [{
model_id: "anthropic-claude-4.6-sonnet",
name: "Claude Sonnet 4.6",
context_window: "1000000",
max_output_tokens: "64000",
availability: ["serverless"],
modalities: { input: ["text", "image"], output: ["text"] },
pricing: {
input_price_per_million: 0.000003,
output_price_per_million: 0.000015,
},
pricing_detail: {
variants: [{
tier: "MODEL_PRICING_TIER_EXTENDED_1M",
mode: "MODEL_BILLING_MODE_INTERACTIVE",
prices: {
input_price_per_million: 0.000006,
output_price_per_million: 0.0000225,
},
}],
},
}],
});
expect(models[0]?.pricing?.extended).toEqual({
context: 200_000,
input: 6,
output: 22.5,
cacheRead: undefined,
cacheWrite: undefined,
});
});
test("syncs DigitalOcean reasoning capability, efforts, and lifecycle status", () => {
const model = buildDigitalOceanModel(digitalOceanModel({
lifecycle_status: "deprecated",
thinking: false,
reasoning_efforts: ["none", "low", "medium", "high", "max", "unsupported"],
}), {
name: "Claude Sonnet 4.6",
description: "Curated DigitalOcean description",
family: "claude-sonnet",
release_date: "2026-02-17",
last_updated: "2026-03-13",
attachment: true,
reasoning: false,
reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }],
temperature: true,
tool_call: true,
open_weights: false,
status: "beta",
cost: { input: 3, output: 15 },
limit: { context: 200_000, output: 64_000 },
modalities: { input: ["text", "image"], output: ["text"] },
});
expect(model).toMatchObject({
status: "deprecated",
reasoning: true,
reasoning_options: [{ type: "effort", values: ["none", "low", "medium", "high", "max"] }],
});
});
test("resolves DigitalOcean IDs to canonical model metadata", () => {
expect(resolveDigitalOceanBaseModel("openai-gpt-5.5")).toBe("openai/gpt-5.5");
expect(resolveDigitalOceanBaseModel("deepseek-v4-pro")).toBe("deepseek/deepseek-v4-pro");
});
test("new DigitalOcean base models inherit intrinsic capabilities", () => {
const model = buildDigitalOceanModel(
digitalOceanModel({
id: "openai-gpt-5.5",
name: "GPT-5.5",
thinking: undefined,
reasoning_efforts: undefined,
}),
undefined,
"openai/gpt-5.5",
);
expect(model).toMatchObject({ base_model: "openai/gpt-5.5" });
expect(model).not.toHaveProperty("open_weights");
expect(model).not.toHaveProperty("family");
expect(model).not.toHaveProperty("release_date");
expect(model).not.toHaveProperty("knowledge");
expect(model).not.toHaveProperty("reasoning");
expect(model).not.toHaveProperty("temperature");
});
test("xAI sync factors inherited base model fields", () => {
const model = buildXAIModel(
{
id: "grok-4.5",
created: Date.parse("2026-06-29T00:00:00Z") / 1000,
input_modalities: ["text", "image"],
output_modalities: ["text"],
prompt_text_token_price: 20_000,
cached_prompt_text_token_price: 5_000,
completion_text_token_price: 60_000,
max_prompt_length: 500_000,
},
{
base_model: "xai/grok-4.5",
name: "Grok 4.5",
description: "xAI's latest Grok for chat, coding, agentic tools, and lower hallucination risk",
family: "grok",
release_date: "2026-07-08",
last_updated: "2026-07-08",
attachment: true,
reasoning: true,
reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }],
temperature: true,
tool_call: true,
structured_output: true,
open_weights: false,
cost: {
input: 2,
output: 6,
cache_read: 0.5,
tiers: [{ tier: { size: 200_000 }, input: 4, output: 12, cache_read: 1 }],
},
limit: { context: 500_000, output: 500_000 },
modalities: { input: ["text", "image"], output: ["text"] },
},
);
expect(model).toMatchObject({
base_model: "xai/grok-4.5",
reasoning_options: [{ type: "effort", values: ["low", "medium", "high"] }],
cost: {
input: 2,
output: 6,
cache_read: 0.5,
tiers: [{ tier: { size: 200_000 }, input: 4, output: 12, cache_read: 1 }],
},
});
expect(model).not.toHaveProperty("name");
expect(model).not.toHaveProperty("family");
expect(model).not.toHaveProperty("release_date");
expect(model).not.toHaveProperty("last_updated");
expect(model).not.toHaveProperty("limit");
});
test("skips new DigitalOcean models with incomplete pricing or limits", () => {
const translated = digitalocean.translateModel(
digitalOceanModel({ pricing: undefined }),
{ existing: () => undefined, authored: () => undefined },
);
expect(translated).toBeUndefined();
});
test("fetches every page of the DigitalOcean catalog", async () => {
const requests: string[] = [];
const first = digitalOceanModel({ id: "first", pricing: undefined });
const second = digitalOceanModel({ id: "second", pricing: undefined });
const fetcher = ((input: string | URL | Request) => {
const url = String(input);
requests.push(url);
if (url.includes("/catalog/first-catalog-id")) {
return Promise.resolve(new Response(JSON.stringify({
data: {
id: "first-catalog-id",
model_id: "first",
name: "Stale First Detail",
context_window: "50",
max_output_tokens: "10",
availability: ["dedicated"],
modalities: { input: ["text", "image"], output: ["text"] },
pricing: { input_price_per_million: 0.000009, output_price_per_million: 0.000009 },
pricing_detail: { variants: [] },
},
})));
}
if (url.includes("/catalog/second-catalog-id")) {
return Promise.resolve(new Response(JSON.stringify({
data: { id: "second-catalog-id", model_id: "second", name: "Second", availability: ["serverless"] },
})));
}
if (url.includes("/catalog") && url.includes("page=2")) {
return Promise.resolve(new Response(JSON.stringify({
data: [{ id: "second-catalog-id", model_id: "second", name: "Second", availability: ["serverless"] }],
meta: { total: 2, page: 2, pages: 2 },
})));
}
if (url.includes("/catalog")) {
return Promise.resolve(new Response(JSON.stringify({
data: [{
id: "first-catalog-id",
model_id: "first",
name: "First",
context_window: "100",
max_output_tokens: "90",
availability: ["serverless"],
pricing: { input_price_per_million: 0.000001, output_price_per_million: 0.000002 },
}],
meta: { total: 2, page: 1, pages: 2 },
})));
}
if (url.includes("?page=2")) {
return Promise.resolve(new Response(JSON.stringify({ models: [second] })));
}
return Promise.resolve(new Response(JSON.stringify({
models: [first],
links: { pages: { next: "https://api.digitalocean.com/v2/gen-ai/models?page=2" } },
})));
}) as typeof fetch;
const result = await fetchDigitalOceanModels("test-key", fetcher);
expect(result.models.map((model) => model.id)).toEqual(["first", "second"]);
expect(result.catalog.map((model) => model.model_id)).toEqual(["first", "second"]);
expect(result.catalog[0]).toMatchObject({
name: "First",
context_window: "100",
max_output_tokens: "90",
availability: ["serverless"],
pricing: { input_price_per_million: 0.000001, output_price_per_million: 0.000002 },
modalities: { input: ["text", "image"], output: ["text"] },
pricing_detail: { variants: [] },
});
expect(requests).toHaveLength(6);
});
function deepInfraModel(model_name: string, tags: string[]): DeepInfraModel {
return {
model_name,
type: "text-generation",
tags,
pricing: {
cents_per_input_token: 0.00001,
cents_per_output_token: 0.00002,
},
max_tokens: 262_144,
};
}
test("formats interleaved as a root field before reasoning option tables", () => {
const content = formatToml({
id: "example/model",
name: "Example Model",
description: "Example model for sync formatting regression tests",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: true,
reasoning_options: [{ type: "toggle" }],
tool_call: true,
interleaved: true,
open_weights: false,
cost: { input: 1, output: 2 },
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
});
expect(Bun.TOML.parse(content)).toMatchObject({
interleaved: true,
reasoning_options: [{ type: "toggle" }],
});
});
test("formats empty reasoning options outside the interleaved table", () => {
const content = formatToml({
id: "example/model",
name: "Example Model",
description: "Example model for sync formatting regression tests",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: true,
reasoning_options: [],
tool_call: true,
interleaved: { field: "reasoning_content" },
open_weights: false,
cost: { input: 1, output: 2 },
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
});
expect(Bun.TOML.parse(content)).toMatchObject({
interleaved: { field: "reasoning_content" },
reasoning_options: [],
});
});
test("formats provider overrides and experimental modes", () => {
const content = formatToml({
id: "example/model",
name: "Example Model",
description: "Example model for sync formatting regression tests",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: false,
tool_call: true,
open_weights: false,
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
provider: { body: { custom_flag: true } },
experimental: {
modes: {
fast: {
cost: { input: 2, output: 4 },
provider: {
body: { speed: "fast" },
headers: { "anthropic-beta": "fast-mode-2026-02-01" },
},
},
},
},
});
expect(Bun.TOML.parse(content)).toMatchObject({
provider: { body: { custom_flag: true } },
experimental: {
modes: {
fast: {
cost: { input: 2, output: 4 },
provider: {
body: { speed: "fast" },
headers: { "anthropic-beta": "fast-mode-2026-02-01" },
},
},
},
},
});
});
test("DeepInfra preserves live modalities for new base models", () => {
const model = buildDeepInfraModel(
deepInfraModel("Qwen/Qwen3.5-9B", ["multimodal", "input-video"]),
undefined,
"alibaba/qwen3.5-9b",
);
expect(model).toMatchObject({
attachment: true,
modalities: { input: ["text", "image", "video"] },
});
});
test("DeepInfra excludes incorrectly tagged Gemma 4 audio input", () => {
const model = buildDeepInfraModel(
deepInfraModel("google/gemma-4-31B-it", ["multimodal", "input-audio", "input-video"]),
{ modalities: { input: ["text", "image", "audio", "video"] } },
"google/gemma-4-31b-it",
);
expect(model).toMatchObject({
modalities: { input: ["text", "image", "video"] },
});
});
test("DeepInfra preserves descriptions for standalone models", () => {
const model = buildDeepInfraModel(
deepInfraModel("example/model", []),
{
name: "Example Model",
description: "Authored standalone model description",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: false,
tool_call: false,
open_weights: true,
cost: { input: 1, output: 2 },
limit: { context: 262_144, output: 8_192 },
modalities: { input: ["text"], output: ["text"] },
},
);
expect(model).toMatchObject({
description: "Authored standalone model description",
});
});
test("W&B preserves curated model dates", () => {
const model: WandbModel = {
id: "example/model",
name: "Example Model",
description: "Example model used to verify W&B date preservation",
attachment: false,
reasoning: false,
tool_call: true,
release_date: "2024-07-01",
last_updated: "2024-07-01",
open_weights: true,
};
expect(buildWandbModel(model, {
release_date: "2024-07-23",
last_updated: "2024-07-23",
})).toMatchObject({
release_date: "2024-07-23",
last_updated: "2024-07-23",
});
});
test("formats reasoning efforts from lowest to highest", () => {
const content = formatToml({
id: "example/model",
name: "Example Model",
description: "Example model for sync formatting regression tests",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: true,
reasoning_options: [{
type: "effort",
values: ["max", "xhigh", "high", "medium", "low", "minimal", "none", "default"],
}],
tool_call: true,
open_weights: false,
cost: { input: 1, output: 2 },
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
});
expect(content).toContain(
'values = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "default"]',
);
});
test("defaults new reasoning models to empty reasoning options", () => {
expect(preserveReasoningOptions({ reasoning: true }, undefined)).toEqual({
reasoning: true,
reasoning_options: [],
});
});
test("syncs OpenRouter reasoning efforts from model metadata", () => {
const model = buildOpenRouterModel(openRouterModel({
reasoning: {
mandatory: false,
supported_efforts: ["max", "xhigh", "high", "medium", "low"],
},
}), undefined);
expect(model).toMatchObject({
base_model: "anthropic/claude-sonnet-5",
reasoning_options: [
{ type: "effort", values: ["max", "xhigh", "high", "medium", "low"] },
],
});
});
test("uses OpenRouter model context when top provider reports a shorter context", () => {
const model = buildOpenRouterModel(openRouterModel({
context_length: 1_048_576,
top_provider: {
context_length: 32_000,
max_completion_tokens: 8_192,
},
}), undefined);
expect(model).toMatchObject({
limit: {
context: 1_048_576,
output: 8_192,
},
});
});
test("factors OpenRouter Pro routes against canonical OpenAI metadata", () => {
const model = buildOpenRouterModel(openRouterModel({
id: "openai/gpt-5.6-sol-pro",
name: "OpenAI: GPT-5.6 Sol Pro",
knowledge_cutoff: "2026-02-16",
context_length: 1_050_000,
top_provider: {
context_length: 1_050_000,
max_completion_tokens: 128_000,
},
}), undefined);
expect([
resolveCanonicalBaseModel("openai/gpt-5.6-luna-pro"),
resolveCanonicalBaseModel("openai/gpt-5.6-sol-pro"),
resolveCanonicalBaseModel("openai/gpt-5.6-terra-pro"),
]).toEqual([
"openai/gpt-5.6-luna",
"openai/gpt-5.6-sol",
"openai/gpt-5.6-terra",
]);
expect(model).toMatchObject({
base_model: "openai/gpt-5.6-sol",
name: "GPT-5.6 Sol Pro",
});
expect("family" in model).toBe(false);
expect("release_date" in model).toBe(false);
});
test("resolves Venice Pro routes to canonical OpenAI metadata", () => {
expect([
resolveVeniceBaseModel("openai-gpt-56-luna-pro", "GPT-5.6 Luna Pro"),
resolveVeniceBaseModel("openai-gpt-56-sol-pro", "GPT-5.6 Sol Pro"),
resolveVeniceBaseModel("openai-gpt-56-terra-pro", "GPT-5.6 Terra Pro"),
]).toEqual([
"openai/gpt-5.6-luna",
"openai/gpt-5.6-sol",
"openai/gpt-5.6-terra",
]);
});
test("preserves authored OpenRouter reasoning options over model metadata", () => {
const model = buildOpenRouterModel(openRouterModel({
reasoning: {
mandatory: false,
supported_efforts: ["max", "xhigh", "high", "medium", "low"],
},
}), {
name: "Claude Sonnet 5",
description: "Balanced Claude model for coding and agentic workflows",
release_date: "2026-06-30",
last_updated: "2026-06-30",
attachment: true,
reasoning: true,
reasoning_options: [{ type: "toggle" }],
tool_call: true,
open_weights: false,
cost: { input: 2, output: 10 },
limit: { context: 1_000_000, output: 128_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
});
expect(model).toMatchObject({
reasoning_options: [{ type: "toggle" }],
});
});
test("upgrades empty OpenRouter reasoning options from model metadata", () => {
const model = buildOpenRouterModel(openRouterModel({
reasoning: {
mandatory: false,
supported_efforts: ["high", "medium", "low"],
},
}), {
name: "Claude Sonnet 5",
description: "Balanced Claude model for coding and agentic workflows",
release_date: "2026-06-30",
last_updated: "2026-06-30",
attachment: true,
reasoning: true,
reasoning_options: [],
tool_call: true,
open_weights: false,
cost: { input: 2, output: 10 },
limit: { context: 1_000_000, output: 128_000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
});
expect(model).toMatchObject({
reasoning_options: [
{ type: "effort", values: ["high", "medium", "low"] },
],
});
});
test("factors new LLM Gateway models against the canonical base metadata", () => {
const model = buildLLMGatewayModel(llmGatewayModel(), undefined);
expect(model).toEqual({
base_model: "anthropic/claude-fable-5",
cost: {
input: 10,
output: 50,
cache_read: 1,
cache_write: 12.5,
},
});
expect("name" in model).toBe(false);
expect("modalities" in model).toBe(false);
});
test("factors aliased LLM Gateway routes against canonical metadata", () => {
const model = buildLLMGatewayModel(llmGatewayModel({
id: "glm-5-2",
name: "GLM-5.2 (260617)",
family: "bytedance",
context_length: 1_024_000,
pricing: {
prompt: "1.4e-6",
completion: "4.4e-6",
input_cache_read: "0.26e-6",
},
}), undefined);
expect(model).toEqual({
base_model: "zhipuai/glm-5.2",
cost: {
input: 1.4,
output: 4.4,
cache_read: 0.26,
},
limit: {
context: 1_024_000,
},
});
});
test("parses Vercel pricing tiers with an implicit zero minimum", () => {
const [model] = vercel.parseModels({
data: [{
id: "openai/gpt-5.6-luna",
name: "GPT-5.6 Luna",
created: 1_780_963_200,
context_window: 1_050_000,
max_tokens: 128_000,
type: "language",
pricing: {
input: "0.000001",
output: "0.000006",
input_cache_read: "0.0000001",
input_cache_read_tiers: [
{ cost: "0.0000001", max: 272_000 },
{ cost: "0.0000002", min: 272_000 },
],
},
}],
});
expect(model).toBeDefined();
expect(buildVercelModel(model!, undefined)).toMatchObject({
cost: { input: 1, output: 6, cache_read: 0.1 },
});
});
test("skips LLM Gateway base_model factoring when no metadata entry exists", () => {
const model = buildLLMGatewayModel(
llmGatewayModel({ id: "claude-fable-does-not-exist" }),
undefined,
);
expect("base_model" in model).toBe(false);
expect(model).toMatchObject({ name: "Claude Fable 5" });
});
test("preserves the authored header comment block when rewriting a changed model", async () => {
const dir = await mkdtemp(path.join(tmpdir(), "sync-header-"));
const modelsDir = path.join(dir, "providers", "example", "models");
await Bun.write(path.join(modelsDir, "example-model.toml"), [
"# Documented quirk: this route needs a manual note.",
"# https://example.com/docs (accessed 2026-06-25)",
'name = "Example Model"',
'release_date = "2026-01-01"',
'last_updated = "2026-01-01"',
"attachment = false",
"reasoning = false",
"tool_call = true",
"open_weights = false",
"",
"[cost]",
"input = 1",
"output = 2",
"",
"[limit]",
"context = 1_000",
"output = 100",
"",
"[modalities]",
'input = ["text"]',
'output = ["text"]',
"",
].join("\n"));
const provider: SyncProvider<{ id: string }> = {
id: "example",
name: "Example",
modelsDir,
deleteMissing: false,
async fetchModels() {
return [{ id: "example-model" }];
},
parseModels(raw) {
return raw as { id: string }[];
},
translateModel(model) {
return {
id: model.id,
model: {
name: "Example Model",
description: "Example model used to verify sync formatting behavior",
release_date: "2026-01-01",
last_updated: "2026-01-01",
attachment: false,
reasoning: false,
tool_call: true,
open_weights: false,
cost: { input: 3, output: 9 },
limit: { context: 1_000, output: 100 },
modalities: { input: ["text"], output: ["text"] },
},
};
},
};
try {
const result = await syncProvider(provider);
expect(result.updated).toBe(1);
const written = await readFile(path.join(modelsDir, "example-model.toml"), "utf8");
expect(written).toStartWith(
"# Documented quirk: this route needs a manual note.\n# https://example.com/docs (accessed 2026-06-25)\n",
);
expect(written).toContain("input = 3");
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test("retains authored data when OpenRouter reports an unavailable stub", () => {
const authored = {
name: "Claude Fable Latest",
reasoning: true as const,
reasoning_options: [{ type: "effort" as const, values: ["low", "high"] as const }],
tool_call: true as const,
structured_output: true as const,
};
const translated = openrouter.translateModel(unavailableStub(), {
existing: () => undefined,
authored: () => authored as never,
});
expect(translated).toEqual({ id: "~anthropic/claude-fable-latest", model: authored as never });
});
test("skips an unavailable OpenRouter stub with no authored file", () => {
const translated = openrouter.translateModel(unavailableStub(), {
existing: () => undefined,
authored: () => undefined,
});
expect(translated).toBeUndefined();
});
test("parses nullable EmpirioLabs release dates", () => {
expect(empiriolabs.parseModels({
data: [{ id: "unknown-text-model", category: "text", model_released_at: null }],
})).toHaveLength(1);
});
test("syncs EmpirioLabs pricing tiers and reasoning controls", () => {
const model: EmpiriolabsModel = {
id: "minimax-m3",
display_name: "MiniMax M3",
category: "text",
context_length: 1_000_000,
max_output_tokens: null,
capabilities: { reasoning: true },
features: ["reasoning", "function_calling"],
structured_output: "json_object",
input_modalities: ["text", "image", "video"],
output_modalities: ["text"],
supported_parameters: [
{ name: "temperature" },
{ name: "max_completion_tokens", max: 524_288 },
{ name: "enable_thinking" },
{ name: "reasoning_effort", options: ["none", "low", "medium", "high", "max"] },
{ name: "thinking_budget", min: 1_024, max: 32_768 },
],
pricing: [
{ prompt: "0.000000225", completion: "0.0000009", input_cache_read: "0.000000045" },
{
prompt: "0.00000045",
completion: "0.0000018",
input_cache_read: "0.000000045",
min_context: 512_000,
},
],
};
expect(buildEmpiriolabsModel(model, { base_model: "minimax/MiniMax-M3" })).toMatchObject({
base_model: "minimax/MiniMax-M3",
structured_output: true,
reasoning_options: [
{ type: "toggle" },
{ type: "effort", values: ["none", "low", "medium", "high", "max"] },
{ type: "budget_tokens", min: 1_024, max: 32_768 },
],
cost: {
input: 0.225,
output: 0.9,
cache_read: 0.045,
tiers: [{
tier: { type: "context", size: 512_000 },
input: 0.45,
output: 1.8,
cache_read: 0.045,
}],
},
limit: { context: 1_000_000, output: 524_288 },
});
});
test("maps EmpirioLabs aliases to canonical model metadata", () => {
expect(resolveEmpiriolabsBaseModel("fugu-ultra")).toBe("sakana/fugu-ultra");
expect(resolveEmpiriolabsBaseModel("muse-spark-1-1")).toBe("meta/muse-spark-1.1");
expect(resolveEmpiriolabsBaseModel("step-3-5-flash")).toBe("stepfun/step-3.5-flash");
});
function unavailableStub(): OpenRouterModel {
return openRouterModel({
id: "~anthropic/claude-fable-latest",
name: "Anthropic: Claude Fable Latest",
supported_parameters: [],
pricing: { prompt: "-1", completion: "-1" },
reasoning: { mandatory: true },
top_provider: { context_length: null, max_completion_tokens: null },
});
}
function llmGatewayModel(overrides: Partial<LLMGatewayModel> = {}): LLMGatewayModel {
return {
id: "claude-fable-5",
name: "Claude Fable 5",
created: 1_780_963_200,
family: "anthropic",
architecture: {
input_modalities: ["text", "image"],
output_modalities: ["text"],
},
pricing: {
prompt: "10.0e-6",
completion: "50.0e-6",
input_cache_read: "1.0e-6",
input_cache_write: "12.5e-6",
internal_reasoning: "0",
},
context_length: 1_000_000,
supported_parameters: ["temperature", "max_tokens", "top_p", "effort", "reasoning"],
structured_outputs: true,
...overrides,
};
}
function openRouterModel(overrides: Partial<OpenRouterModel> = {}): OpenRouterModel {
return {
id: "anthropic/claude-sonnet-5",
name: "Anthropic: Claude Sonnet 5",
created: 1_782_777_600,
hugging_face_id: null,
knowledge_cutoff: "2026-01-31",
context_length: 1_000_000,
architecture: {
input_modalities: ["text", "image", "file"],
output_modalities: ["text"],
},
pricing: {
prompt: "0.000002",
completion: "0.00001",
input_cache_read: "0.0000002",
input_cache_write: "0.0000025",
},
top_provider: {
context_length: 1_000_000,
max_completion_tokens: 128_000,
},
supported_parameters: ["include_reasoning", "reasoning", "structured_outputs", "tools"],
...overrides,
};
}