Files
pi/packages/ai/test/image-model-data.test.ts
Armin Ronacher a7d17e39aa feat(ai): serve Jev classifier through OpenRouter and Cloudflare Workers AI
OpenRouter decision models use its TypeSafe-compatible /api/v1/systemone endpoint. Cloudflare Workers AI gets a cloudflare-workers-ai-system-one classifier API for the /ai/run envelope. Request and answer parsing is shared in system-one-shared.ts.
2026-09-24 12:34:58 +02:00

100 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { buildOpenRouterCatalog, type OpenRouterModelListItem } from "../scripts/openrouter-catalog.ts";
const imageOnly: OpenRouterModelListItem = {
id: "example/image-model",
name: "Example Image Model",
architecture: {
input_modalities: ["text", "image"],
output_modalities: ["image"],
},
pricing: {
prompt: "0.000001",
completion: "0.000002",
},
};
const chatWithImages: OpenRouterModelListItem = {
id: "example/multimodal",
name: "Example Multimodal",
supported_parameters: ["tools"],
architecture: {
modality: "text+image->text+image",
input_modalities: ["text", "image"],
output_modalities: ["text", "image"],
},
context_length: 32000,
};
const chatOnly: OpenRouterModelListItem = {
id: "example/chat",
name: "Example Chat",
supported_parameters: ["tools"],
architecture: { modality: "text->text", output_modalities: ["text"] },
};
const decisionModel: OpenRouterModelListItem = {
id: "typesafe/jev-1.13",
name: "TypeSafe: Jev 1.13",
supported_parameters: [],
architecture: { modality: "text->decisions", input_modalities: ["text"], output_modalities: ["decisions"] },
pricing: { prompt: "0.000000042", completion: "0" },
context_length: 32000,
top_provider: { context_length: 32000, max_completion_tokens: 28800 },
};
describe("OpenRouter catalog parsing", () => {
it("emits image-only models from the image listing as image models", () => {
const catalog = buildOpenRouterCatalog([], [imageOnly], []);
expect(catalog.chat).toEqual([]);
expect(catalog.images).toEqual([
expect.objectContaining({
type: "image",
id: "example/image-model",
api: "openrouter-images",
input: ["text", "image"],
output: ["image"],
cost: expect.objectContaining({ input: 1, output: 2 }),
}),
]);
});
it("emits separate chat and image entries for an id that supports both operations", () => {
const catalog = buildOpenRouterCatalog([chatWithImages, chatOnly], [chatWithImages, imageOnly], []);
expect(catalog.chat.map((model) => model.id)).toEqual(["example/multimodal", "example/chat"]);
expect(catalog.chat.map((model) => model.type)).toEqual(["chat", "chat"]);
expect(catalog.images.map((model) => model.id)).toEqual(["example/multimodal", "example/image-model"]);
expect(catalog.images.every((model) => model.type === "image")).toBe(true);
expect(catalog.images.map((model) => model.output)).toEqual([["text", "image"], ["image"]]);
});
it("ignores listed models that neither support tools nor emit images", () => {
const catalog = buildOpenRouterCatalog(
[{ ...chatOnly, supported_parameters: [] }],
[{ ...imageOnly, architecture: { output_modalities: ["text"] } }],
[{ ...decisionModel, architecture: { output_modalities: ["text"] } }],
);
expect(catalog.chat).toEqual([]);
expect(catalog.images).toEqual([]);
expect(catalog.classifiers).toEqual([]);
});
it("emits decision models as System One classifier models", () => {
const catalog = buildOpenRouterCatalog([], [], [decisionModel, decisionModel]);
expect(catalog.chat).toEqual([]);
expect(catalog.classifiers).toEqual([
{
type: "classifier",
id: "typesafe/jev-1.13",
name: "TypeSafe: Jev 1.13",
api: "typesafe-system-one",
provider: "openrouter",
baseUrl: "https://openrouter.ai/api/v1",
input: ["text"],
cost: { input: 0.042, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
},
]);
});
});