feat(ai,coding-agent): image input for System One classifiers

Jev (TypeSafe, Workers AI), Clef (Workers AI), and OpenRouter decision
models now accept ClassifierContext.images. Each service takes its own
wire form: TypeSafe assets with the opt-in header, Clef images data URLs,
or OpenRouter image_url parts in the state. A new SystemOneCompat
(imageInput, sendImageOptInHeader) selects the form; catalog entries set
it explicitly and models.json provider compat now applies to classifiers.

Adds Clef Omni and Jev 1.14 on Workers AI and fixes Clef Flash and
Workers AI Jev catalog pricing.
This commit is contained in:
Armin Ronacher 2026-10-21 23:53:14 +02:00
parent 02726086d1
commit 7f225f25d8
19 changed files with 687 additions and 36 deletions

View file

@ -2631,11 +2631,15 @@ async function loadModelsDevClassifierModels(): Promise<ClassifierModel<"typesaf
api: "typesafe-system-one",
provider: "typesafe",
baseUrl: "https://api.typesafe.ai/v1/",
input: metadata.modalities?.input?.includes("image") ? ["text", "image"] : ["text"],
// models.dev lists text input only, but the API accepts image assets as an experimental
// feature behind an opt-in header, which the typesafe-system-one API sends.
// https://github.com/typesafe-ai/typesafe-sdk-js (v0.7.0 changelog)
input: ["text", "image"],
// The canonical models.dev entry has no direct-provider pricing. System One reports token usage,
// so classify() results carry token counts but price them at zero.
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: metadata.limit?.context || 64000,
compat: { imageInput: "assets", sendImageOptInHeader: true },
},
];
} catch (error) {
@ -2645,9 +2649,6 @@ 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 /inference/v1/models listing nor models.dev carries metadata for it.
// https://opencode.ai/docs/zen
@ -2676,11 +2677,14 @@ const OPENCODE_CLASSIFIER_MODELS: ClassifierModel<"typesafe-system-one">[] = [
},
];
// Workers AI has no unauthenticated catalog, so its System One models are listed here from
// Cloudflare's model pages.
const CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS: ClassifierModel<"cloudflare-workers-ai-system-one">[] = [
// Cloudflare-hosted Clef decision models. They accept images, but classifier
// contexts carry text or JSON state only, so the catalog advertises text.
// Pricing: https://developers.cloudflare.com/workers-ai/models/clef/
// and https://developers.cloudflare.com/workers-ai/models/clef-flash/
// Cloudflare-hosted Clef decision models. They take up to four PNG, JPEG, or WebP images as data URLs.
// Clef Omni also takes audio and video, which classifier contexts do not carry.
// Pricing and context: https://developers.cloudflare.com/workers-ai/models/clef/,
// https://developers.cloudflare.com/workers-ai/models/clef-flash/,
// and https://developers.cloudflare.com/workers-ai/models/clef-omni/
{
type: "classifier",
id: "@cf/cloudflare/clef",
@ -2688,9 +2692,10 @@ const CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS: ClassifierModel<"cloudflare-worke
api: "cloudflare-workers-ai-system-one",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_REST_BASE_URL,
input: ["text"],
input: ["text", "image"],
cost: { input: 0.24, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 65536,
compat: { imageInput: "images" },
},
{
type: "classifier",
@ -2699,10 +2704,26 @@ const CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS: ClassifierModel<"cloudflare-worke
api: "cloudflare-workers-ai-system-one",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_REST_BASE_URL,
input: ["text"],
cost: { input: 0.09, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 65536,
input: ["text", "image"],
cost: { input: 0.038, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 24576,
compat: { imageInput: "images" },
},
{
type: "classifier",
id: "@cf/cloudflare/clef-omni",
name: "Clef Omni",
api: "cloudflare-workers-ai-system-one",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_REST_BASE_URL,
input: ["text", "image"],
cost: { input: 0.15, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 64000,
compat: { imageInput: "images" },
},
// TypeSafe's Jev models take PNG and JPEG images as System One assets.
// https://developers.cloudflare.com/ai/models/typesafe/jev/ (jev-latest)
// and https://developers.cloudflare.com/ai/models/typesafe/jev-1.14.0/
{
type: "classifier",
id: "typesafe/jev",
@ -2710,9 +2731,22 @@ const CLOUDFLARE_WORKERS_AI_CLASSIFIER_MODELS: ClassifierModel<"cloudflare-worke
api: "cloudflare-workers-ai-system-one",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_REST_BASE_URL,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
input: ["text", "image"],
cost: { input: 0.042, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
compat: { imageInput: "assets" },
},
{
type: "classifier",
id: "typesafe/jev-1.14.0",
name: "Jev 1.14",
api: "cloudflare-workers-ai-system-one",
provider: "cloudflare-workers-ai",
baseUrl: CLOUDFLARE_WORKERS_AI_REST_BASE_URL,
input: ["text", "image"],
cost: { input: 0.042, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 32000,
compat: { imageInput: "assets" },
},
];

View file

@ -98,6 +98,8 @@ export function buildOpenRouterCatalog(
input: input.length > 0 ? input : ["text"],
cost: openRouterCost(model.pricing),
contextWindow: model.top_provider?.context_length || model.context_length || 4096,
// OpenRouter answers TypeSafe `assets` with 200 but drops the images.
compat: { imageInput: "state-image-urls", sendImageOptInHeader: false },
});
}