mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-02 08:44:38 +08:00
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.
100 lines
3.4 KiB
TypeScript
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,
|
|
},
|
|
]);
|
|
});
|
|
});
|