feat(ai): expose Jev on Vercel AI Gateway and OpenCode Zen

Vercel evaluation models are generated from the AI Gateway catalog and
routed through its TypeSafe-compatible /typesafe/v1/systemone endpoint.
OpenCode Zen's jev-1.13 and jev-1.13-free are routed through
/zen/v1/systemone.
This commit is contained in:
Armin Ronacher 2026-09-29 15:32:10 +02:00
parent 5257d0d5f3
commit 33e2033543
7 changed files with 123 additions and 16 deletions

View file

@ -151,6 +151,7 @@ interface NvidiaNimModelListItem {
interface AiGatewayModel {
id: string;
name?: string;
type?: string;
context_window?: number;
max_tokens?: number;
tags?: string[];
@ -222,6 +223,9 @@ const TOGETHER_TOGGLE_REASONING_LEVEL_MAP = {
const AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1";
const AI_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
// TypeSafe-compatible System One endpoint for evaluation models.
// https://vercel.com/docs/ai-gateway/sdks-and-apis/typesafe
const AI_GATEWAY_TYPESAFE_BASE_URL = "https://ai-gateway.vercel.sh/typesafe/v1";
const VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com";
const NVIDIA_BASE_URL = "https://integrate.api.nvidia.com/v1";
const NVIDIA_HEADERS = {
@ -1328,13 +1332,17 @@ async function fetchRadiusModels(): Promise<Model<"pi-messages">[]> {
}
}
async function fetchAiGatewayModels(): Promise<Model<any>[]> {
async function fetchAiGatewayModels(): Promise<{
chat: Model<any>[];
classifiers: ClassifierModel<"typesafe-system-one">[];
}> {
try {
console.log("Fetching models from Vercel AI Gateway API...");
const response = await fetch(`${AI_GATEWAY_MODELS_URL}/models`);
if (!response.ok) throw new Error(`Vercel AI Gateway API returned ${response.status}`);
const data = await response.json();
const models: Model<any>[] = [];
const classifiers: ClassifierModel<"typesafe-system-one">[] = [];
const toNumber = (value: string | number | undefined): number => {
if (typeof value === "number") {
@ -1346,6 +1354,27 @@ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
const items = Array.isArray(data.data) ? (data.data as AiGatewayModel[]) : [];
for (const model of items) {
// Evaluation models such as TypeSafe's Jev are served through the
// TypeSafe-compatible System One endpoint.
if (model.type === "evaluation") {
classifiers.push({
type: "classifier",
id: model.id,
name: model.name || model.id,
api: "typesafe-system-one",
provider: "vercel-ai-gateway",
baseUrl: AI_GATEWAY_TYPESAFE_BASE_URL,
input: ["text"],
cost: {
input: roundCost(toNumber(model.pricing?.input) * 1_000_000),
output: roundCost(toNumber(model.pricing?.output) * 1_000_000),
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: model.context_window || 4096,
});
continue;
}
const tags = Array.isArray(model.tags) ? model.tags : [];
// Only include models that support tools
if (!tags.includes("tool-use")) continue;
@ -1380,12 +1409,14 @@ async function fetchAiGatewayModels(): Promise<Model<any>[]> {
});
}
console.log(`Fetched ${models.length} tool-capable models from Vercel AI Gateway`);
return models;
console.log(
`Fetched ${models.length} tool-capable and ${classifiers.length} classifier models from Vercel AI Gateway`,
);
return { chat: models, classifiers };
} catch (error) {
console.error("Failed to fetch Vercel AI Gateway models:", error);
if (generatorOptions.strict) throw error;
return [];
return { chat: [], classifiers: [] };
}
}
@ -2665,6 +2696,34 @@ async function loadModelsDevClassifierModels(): Promise<ClassifierModel<"typesaf
// Workers AI has no unauthenticated catalog and models.dev does not list its
// System One models yet. Cloudflare publishes pricing only in the dashboard.
// https://developers.cloudflare.com/ai/models/typesafe/jev/
// OpenCode Zen serves Jev through its TypeSafe-compatible System One endpoint.
// Neither its /zen/v1/models listing nor models.dev carries metadata for it.
// https://opencode.ai/docs/zen
const OPENCODE_CLASSIFIER_MODELS: ClassifierModel<"typesafe-system-one">[] = [
{
type: "classifier",
id: "jev-1.13",
name: "Jev 1.13",
api: "typesafe-system-one",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
input: ["text"],
cost: { input: 0.042, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
},
{
type: "classifier",
id: "jev-1.13-free",
name: "Jev 1.13 Free",
api: "typesafe-system-one",
provider: "opencode",
baseUrl: "https://opencode.ai/zen/v1",
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
},
];
const CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS: ClassifierModel<"cloudflare-workers-ai-system-one">[] = [
{
type: "classifier",
@ -2688,11 +2747,11 @@ async function generateModels() {
const modelsDevModels = await loadModelsDevData();
const modelsDevClassifierModels = await loadModelsDevClassifierModels();
const openRouterCatalog = await fetchOpenRouterModels();
const aiGatewayModels = await fetchAiGatewayModels();
const aiGatewayCatalog = await fetchAiGatewayModels();
const radiusModels = await fetchRadiusModels();
// Combine chat models (models.dev has priority where sources overlap).
const allModels = [...modelsDevModels, ...openRouterCatalog.chat, ...aiGatewayModels, ...radiusModels].filter(
const allModels = [...modelsDevModels, ...openRouterCatalog.chat, ...aiGatewayCatalog.chat, ...radiusModels].filter(
(model) =>
!(model.provider === "xai" && XAI_BUILTIN_EXCLUDED_MODEL_IDS.has(model.id)) &&
!((model.provider === "opencode" || model.provider === "opencode-go") && model.id === "gpt-5.3-codex-spark"),
@ -3357,6 +3416,8 @@ async function generateModels() {
const classifierModels: ClassifierModel<ClassifierApi>[] = [
...modelsDevClassifierModels,
...openRouterCatalog.classifiers,
...aiGatewayCatalog.classifiers,
...OPENCODE_CLASSIFIER_MODELS,
...CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS,
];
for (const model of classifierModels) {