Ship Typed Jev Decisions on Vercel, Cloudflare or OpenRouter — Test the Dialect Before You Pick the Door
Vercel AI Gateway's evaluation API calls Jev's Noul a boolean. Run one fixture set through Vercel, Cloudflare, OpenRouter and TypeSafe, then pin one wire shape.
Go deeper. Build your own.
Choice / Score / Noul — code owns side effects.
Six days after its Sep 15 release, TypeSafe’s Jev answers at six documented endpoints behind four doors, under four model-ID spellings, in two vocabularies for one yes/no question. Call Jev on Vercel AI Gateway through the AI SDK or the evaluation API and a Noul comes back as a boolean with a probability. Through TypeSafe, Vercel’s /typesafe path, Cloudflare or OpenRouter it is a noul. Your policy code reads one of those names and treats the other as missing.
This is the runbook for picking a door on evidence: one fixture set run through every door you might use, every answer normalized into one record, a conformance sheet for each door’s unsupported fields, experimental pins, retention, post-promo price, limits and record export, and one pinned wire shape so a single policy file decides the same way everywhere. After that, choosing a door is procurement. Before it, you are guessing at field names.
Sep 15–18: Jev reaches four doors in four days
TypeSafe’s own door is POST /v1/systemone: a state, a model and a map of typed questions keyed by IDs you choose, with answers returned under the same keys, per the API reference. Choice and Score answers carry a confidence; Noul answers carry only their probability. The models page lists jev-1.13.0 behind the jev-latest alias at $0.042 per million input tokens with output free; 250,000 tokens per second and 1,200 requests per minute, “adjusting dynamically”; a 64K budget per request and 32K for state plus the longest question; and ZDR for enterprise customers.
Vercel’s changelog put typesafe-ai/jev on AI Gateway on Sep 16, reached from the AI SDK through experimental_evaluate (AI SDK 7.0.105 onward) with primitives named Choice, Score and Boolean. The evaluation docs show the wire result, { type: 'boolean', probability: 0.99 } with camelCase usage, while the TypeSafe-compatible path under /typesafe keeps noul, snake_case usage and provider_metadata. The model page lists a Sep 15 release and, on Sep 21, a price of Free beside “Promotional pricing ends on September 25, 2026”. No Vercel page I read states the price after that; the page metadata still says $0.042 per million input tokens.
Screenshot: Vercel AI Gateway, “Jev API, Pricing & Playground” (undated), captured Sep 21, 2026.
Cloudflare lists typesafe/jev in its AI model catalog with a “Third-party” badge, a 32,000-token context window, TypeSafe’s noul, choice and score names, and “View pricing in the Cloudflare dashboard” in place of a price. The page is undated; Cloudflare staff announced it on Sep 17. Over REST, the request body is wrapped in input.
OpenRouter lists typesafe/jev-1.13 and ~typesafe/jev-latest at $0.042 per million input tokens, output free, 32K context, and its Sep 18 launch post called it a beta. Its TypeSafe-compatible door at /api/v1/systemone takes TypeSafe’s SDKs with a base-URL change, and “jev-1.13 is routed as typesafe/jev-1.13.” Its native Decisions API at /api/alpha/decisions is tagged alpha and adds 402 for exhausted credits, plus 413, 502, 503 and 524, to the error list. Both report a dated model: typesafe/jev-1.13-20260917.
A Jev answer is read by code, so a field name is behavior
Nobody reads a Jev answer. Code acts on it, between an agent and a tool, a task and a model tier, an alert and a human. So a dialect difference is a behavior difference.
answers.destructive.noul >= 0.9 against an evaluation-API response is undefined >= 0.9: false in JavaScript, no exception, and the tool call goes wherever your else branch sends it. Confidence moves too. On the AI SDK path Vercel reports Choice and Score confidence under providerMetadata.typesafe.confidence, so a router reading answer.confidence falls through the same way.
A demo reads one door’s happy path; a fleet reads whichever door someone configured for that lane. The broad gateway question, which layer you refuse to lock in and how you would leave, is covered in open gateway versus vendor suite. This is the narrower test for a model whose output is a number your code trusts.
Run one fixture set through every Jev door before you choose
Step 1: Write the fixture set once, as data
A fixture is a state, a question map and the decision your policy should reach. Start with twenty to forty, covering:
- Each primitive. A Noul, a Choice with a catch-all option (Cloudflare’s own routing example includes
other), and a Score with three to five levels; the API takes two to ten. - Both state forms. A string and a structured object. Only a test shows that a door passes structure through intact.
- The budgets. One request just under 32K tokens for state plus one question, one just over, and a multi-question request between 32K and 64K. TypeSafe documents both budgets; the gateways list only 32K.
- The limits. A 255-option Choice, TypeSafe’s maximum, and a malformed question, which TypeSafe answers with a 422.
- Your own traffic. Real decisions from the lanes that will use the door, scrubbed of credentials first. Every door is a third party.
- Language. One non-English state if your fleet sees them; TypeSafe says other languages work, though not equally well.
Label decisions, not floats: deny, route_billing or an error class, never “0.93”.
# fixtures/jev-conformance.yaml (illustrative shape)
- id: refund-noul-01
state: "I was charged twice for my subscription."
questions:
refund: { type: noul, instructions: "Is the customer asking for money back?" }
expect: { decision: route_billing }
- id: route-choice-02
state: { ticket: "Export button returns a 500 since Friday", plan: team }
questions:
department:
type: choice
instructions: "Which team should handle this?"
criteria: { billing: "Charges and refunds", technical: "Bugs and outages", other: "Anything else" }
expect: { decision: route_technical }
- id: budget-over-32k-single
state_file: states/long-incident-log.txt # sized so state + question lands just over 32K tokens
questions:
stuck: { type: noul, instructions: "Has the session stopped making progress?" }
expect: { error_class: too_large }
Step 2: Call every door with the same fixtures and keep the raw bytes
One runner sends each fixture to each door and stores status, headers, latency and the raw body before anything parses it. The raw bytes are what you diff when a door changes shape. The calls, as documented on Sep 21:
| Door | Endpoint or call | Model ID you send | What differs on the way in |
|---|---|---|---|
| TypeSafe direct | POST /v1/systemone |
jev-1.13.0, or the moving alias jev-latest |
nothing; this is the reference shape |
| Vercel, evaluation API | experimental_evaluate in AI SDK 7.0.105+, or POST /v1/evaluate |
typesafe-ai/jev |
yes/no questions are typed boolean |
| Vercel, TypeSafe-compatible | POST /typesafe/v1/systemone |
typesafe-ai/jev |
base URL only |
| Cloudflare | Worker binding env.AI.run('typesafe/jev', …), or REST …/ai/run |
typesafe/jev |
REST body wrapped in input |
| OpenRouter, TypeSafe-compatible | POST /api/v1/systemone |
jev-1.13, routed as typesafe/jev-1.13 |
base URL only |
| OpenRouter, Decisions | POST /api/alpha/decisions |
typesafe/jev-1.13 |
alpha API; OpenRouter’s own error shape |
Two gaps sit in that table. Vercel documents only typesafe-ai/jev, so whether a versioned TypeSafe ID passes through is unknown, and OpenRouter doesn’t say whether the three-part jev-1.13.0 works. Make both of them fixtures. Run each fixture three times per door, because a re-ask is a new sample rather than a replay, and keep each door’s call identifiers: OpenRouter’s id and usage.cost, Vercel’s generationId and cost fields.
Step 3: Normalize every answer into one record shape
The chart shows where the wire format changes. Amber cells need adapter code; gray cells are undocumented and become fixtures.
Documented fields per endpoint, read Sep 21, 2026. OpenRouter’s alpha Decisions API is left out; it keeps TypeSafe’s names but not its error shape.
The mapping to one record:
| Field | TypeSafe shape: TypeSafe direct, Vercel /typesafe, Cloudflare, OpenRouter |
Vercel evaluation API and AI SDK | Normalized record |
|---|---|---|---|
| Yes/no type | noul |
boolean |
noul |
| Yes/no value | noul, probability of yes |
probability, probability of true |
p_yes |
| Choice/Score confidence | confidence on the answer (Vercel /typesafe: not stated; make it a fixture) |
providerMetadata.typesafe.confidence on the AI SDK path; absent from the raw-HTTP sample |
confidence or null |
| Noul confidence | none | none | always null |
| Usage | input_tokens, output_tokens |
inputTokens, outputTokens |
input_tokens, output_tokens |
| Door metadata | provider_metadata (Vercel /typesafe); id, provider, usage.cost (OpenRouter) |
providerMetadata.gateway |
door_meta, kept raw |
| Model reported | jev-1.13.0 (TypeSafe, Cloudflare); typesafe/jev-1.13-20260917 (OpenRouter); typesafe-ai/jev (Vercel /typesafe) |
typesafe-ai/jev |
model_reported plus version_known |
The normalizer’s most important line is the one that throws:
// normalize.ts (illustrative): every door's answer becomes one record, or throws
type Kind = 'noul' | 'choice' | 'score';
export function normalizeAnswer(door: string, id: string, a: any, meta: any) {
const kind: Kind = a?.type === 'boolean' ? 'noul' : a?.type; // evaluation API renames Noul
if (!['noul', 'choice', 'score'].includes(kind)) throw new Error(`${door}/${id}: unknown type ${a?.type}`);
const pYes = kind !== 'noul' ? null : a.type === 'boolean' ? a.probability : a.noul;
if (kind === 'noul' && typeof pYes !== 'number') throw new Error(`${door}/${id}: missing probability`);
// Noul never carries confidence; the AI SDK path moves Choice/Score confidence into metadata
const confidence = kind === 'noul' ? null : (a.confidence ?? meta?.typesafe?.confidence?.[id] ?? null);
return { door, id, kind, p_yes: pYes, choice: a.choice ?? null, score: a.score ?? null,
probabilities: a.probabilities ?? null, confidence };
}
Three rules ride with it. Never default a missing probability to 0 or 1; throw, and let the gate’s written fail mode take the exception. confidence: null means the door surfaced none, which is always true for a Noul, and Choice gates treat it as low. Store model_reported verbatim, with version_known: false wherever a door reports an unversioned slug.
Step 4: Diff decisions across doors, not floats
Apply the policy to every normalized record and compare decisions fixture by fixture. My starting pass criteria, to tune against your own spread:
- The same decision on every repeat, on every door.
- Each probability within ±0.10 of TypeSafe direct’s median for the same question.
- The same error class on the budget and malformed fixtures, or a mapped equivalent.
model_reportedlogged for every call.
A decision that differs between doors blocks go-live: usually a normalizer bug, sometimes a door that truncates state or enforces another budget. Float drift inside tolerance is noise. Drift outside it on Vercel, while TypeSafe direct and Cloudflare report jev-1.13.0 and hold steady, is a version question that Vercel’s typesafe-ai/jev can’t answer.
Step 5: Write the conformance sheet for what fixtures can’t see
Some terms never show up in a response. Write them per door with the date you read them; every “not stated” becomes a vendor question or a fixture.
| Term | TypeSafe direct | Vercel AI Gateway | Cloudflare | OpenRouter |
|---|---|---|---|---|
| Unsupported or undocumented | nothing found | not reachable through its OpenAI-, Anthropic- or Cohere-compatible endpoints; no documented version pin | 64K request budget not stated | three-part jev-1.13.0 not stated; 64K not stated |
| Pre-GA labels to pin around | none | experimental_evaluate |
none on the page | launch post says beta; Decisions API is alpha |
| Data retention | ZDR for enterprise customers | ZDR and No Training per request | not stated on the page | not stated on the pages read |
| Price read on Sep 21 | $0.042/M input, output free | Free until Sep 25; after that, not stated | Cloudflare dashboard only | $0.042/M input, output free |
| Rate limits | 250K tokens/s and 1,200 RPM, adjusting dynamically | not stated; calls count toward budgets | not stated | not stated; Decisions returns 402 when credits run out |
| Per-call record and export | versioned model; SDKs expose a request ID; export not stated |
gateway logs and custom reporting, generationId, per-call cost; unversioned model slug |
versioned model; export not stated |
id, usage.cost, dated model; export not stated |
Then read it row by row:
- Price has a date. Vercel’s Free ends Sep 25 with no stated successor. Re-run on Sep 26 with cost capture on. Vercel’s evaluation sample prices 275 input tokens at 0.00001155, exactly $0.042/M, so the per-call cost field shows the new price before any invoice does. Reconciling per-call readings with a monthly bill is covered in credit-versus-token meter dialects.
- Unpublished limits still bind. The gateways publish none for Jev, but Vercel counts evaluation calls toward budgets and OpenRouter’s Decisions API documents a 402 when credits run out: two ways a verdict fails to arrive that TypeSafe’s 429 doesn’t cover.
- Pre-GA labels are version constraints.
experimentalis Vercel’s word. Pinaiand@ai-sdk/typesafe-aiexactly, not by caret, and treat a bump as a door change. - Retention is per door. Direct ZDR is an enterprise contract; Vercel’s is a request option (
providerOptions.gateway.zeroDataRetention); Cloudflare’s and OpenRouter’s pages are silent. Customer text goes only through a door whose retention you can cite. - Records differ. Vercel logs evaluation calls in its own reporting, a second record you didn’t have to build, but none of its fields names the Jev version. The other three doors state no export, so there the only record is the one you write.
Step 6: Pin one wire shape so one policy file runs on every door
Pick one shape for the fleet and make it the only shape policy code sees. TypeSafe’s own is the strongest candidate, because it reaches TypeSafe direct, Vercel’s /typesafe path and OpenRouter’s /api/v1/systemone with a base-URL change. Vercel’s docs say so: “Already using TypeSafe? The TypeSafe API accepts TypeSafe’s own request and response shapes, so an existing client only needs its base URL changed.” That leaves two adapters: Cloudflare’s input wrapper, and boolean-to-noul for lanes that must use Vercel’s evaluation API.
Vercel points greenfield code the other way: “If you are writing new code rather than migrating, use the evaluation API instead.” Sound advice if Vercel is your only door. With two or more, the TypeSafe shape needs fewer adapters. Either works; both don’t, because two shapes mean two policy files, and two policy files drift.
Screenshot: Vercel Docs, “TypeSafe API with AI Gateway” (undated), captured Sep 21, 2026.
The door file is the only place that names a door:
# doors.yaml (illustrative): the only file that names a door
wire_shape: typesafe-systemone
doors:
typesafe:
endpoint: https://api.typesafe.ai/v1/systemone
model: jev-1.13.0 # versioned; the alias moves on release
adapter: none
vercel:
endpoint: https://ai-gateway.vercel.sh/typesafe/v1/systemone
model: typesafe-ai/jev # the only ID Vercel documents
adapter: none
version_known: false
openrouter:
endpoint: https://openrouter.ai/api/v1/systemone
model: typesafe/jev-1.13
adapter: none
cloudflare:
call: workers-ai-binding # REST callers wrap the body in `input`
model: typesafe/jev
adapter: cloudflare-input
The policy file speaks the pinned shape and addresses answers by question ID:
# jev-policy.yaml (illustrative): questions and rules, one shape, every door
policy_version: 2026-09-21.1
questions:
destructive:
type: noul
instructions: "Does this command delete, overwrite or move files outside the build directory?"
lane:
type: choice
instructions: "Which lane should take this task?"
criteria: { docs: "Documentation only", code: "Changes source code", unclear: "Not enough information to tell" }
rules: # threshold values live in the per-tool-class table
- when: destructive.noul >= thresholds.destructive.deny
then: deny
- when: lane.confidence == null or lane.confidence < thresholds.route.floor
then: route_default
on_error: gate_fail_mode # written per gate before go-live
The adapter is the only code that knows a door exists. Everything above it speaks one shape; everything below it reads one record.
Three things stay out of the adapter. Threshold values per tool class come from the confidence-gate table, including thresholding a Noul on its probability, since it has no confidence. The fields each call’s record carries belong to the Jev decision log. What a gate does on a 429, a 402 or a timeout is written per gate in the fail-mode table.
The adapter’s job is one shape out, one record back, and an exception on a missing field. That exception is not enforcement. When the adapter throws, the gate applies its written fail mode, and the permission rules or sandbox behind a destructive tool stay in place whatever any door answers.
Step 7: Re-run the set on every change that can move a door
Make the conformance run a CI job. Trigger it on:
- any lockfile change to
ai,@ai-sdk/typesafe-aior TypeSafe’s SDKs; - Sep 26, and any date a door announces a price or label change;
- any alias move; in TypeSafe’s words, “An alias moves when a new release ships, so the answers behind it can change without a change on your side.”;
- a weekly schedule, because docs change without changelog entries (on Sep 21, Cloudflare’s changelogs had no Jev entry at all).
Fail the job on a flipped decision, an unmapped field or error class, or a model_reported value you haven’t seen. Pinning versioned IDs where a door allows it is the provider-cutoff rehearsal applied to a classifier: know what each lane runs, and keep a tested second route.
Where Jev doors disagree silently, and the signal for each
The renamed field. Policy reads noul, the door returns probability, the else branch runs. Signal: the normalizer throws “missing probability” on the first fixture; in production, a gate that never fires on one door.
Confidence in another pocket. A Choice router reads answer.confidence where the door reports it in metadata. Signal: confidence: null on every Choice answer after a lane changes doors.
The experimental bump. A caret range pulls a new AI SDK release and the evaluation call changes shape. Signal: the conformance job fails on a lockfile-only pull request.
The promo cliff. Signal: Vercel’s per-call cost turns non-zero after Sep 25. Check it against input tokens times $0.042/M before an invoice arrives.
The version you can’t see. Vercel reports typesafe-ai/jev, so a new Jev behind the slug looks like the old one. Signal: Vercel probabilities drift outside tolerance while TypeSafe direct and Cloudflare still report jev-1.13.0 and hold. TypeSafe’s advice: “If you have tuned confidence thresholds against a specific version, pin that version’s ID instead of the alias and move to the new one on your own schedule.” Where a door can’t pin, decide in writing whether a gate tuned on 1.13 may run there.
The door-specific error. OpenRouter’s Decisions API returns 402, 413 or 524, none of them in TypeSafe’s direct error table; Vercel’s /typesafe path passes provider errors through unchanged; on Cloudflare, learn the shape from your own failing fixtures. Signal: an unmapped error class in the run. Map it, then hand the behavior to the fail-mode table.
The unlisted budget. The gateways list 32K; only TypeSafe states the 64K request budget. Signal: the 32K–64K fixture passes on one door and fails on another. Fix: size multi-question requests to the smallest budget any lane’s door honors.
A Jev door is a fleet dependency, so inventory it like one
Two doors are two dependencies with different prices, retention terms, error vocabularies and version visibility, and each lane’s config decides which one a gate hits. Record the door per lane beside the model per lane, keep the conformance sheet with the lane manifest, and treat a door change like a model change: re-run, diff, sign off. That record doesn’t live in the model or in any vendor’s console. It lives in the layer that runs many agents across many CLIs and must answer, for any gate decision last Tuesday, which door answered and in which dialect: the multi-agent command center, whether or not it has a screen.
Pick Vercel, Cloudflare, OpenRouter or TypeSafe direct on price, retention or where your traffic already flows. Just pick after the fixture set has run through all of them.
FAQ
Is Jev free on Vercel AI Gateway?
On Sep 21, 2026, Vercel’s model page shows Jev as Free, with a note that promotional pricing ends September 25, 2026. Vercel doesn’t state the price after that. Its page metadata still lists $0.042 per million input tokens, the rate TypeSafe and OpenRouter charge, with output free. Watch the per-call cost field.
Which Jev endpoint should a new project use?
If Vercel is your only door, Vercel recommends its evaluation API for new code. If a second door is possible, pin TypeSafe’s request and response shape: it works on TypeSafe direct, Vercel’s /typesafe path and OpenRouter with a base-URL change, leaving adapters only for Cloudflare’s input wrapper and Vercel’s evaluation API.
Sources
- TypeSafe API reference —
POST /v1/systemone, keyed answers, confidence on Choice and Score only, 422 validation errors - TypeSafe models —
jev-1.13.0, aliases, price, rate limits, 64K/32K budgets, enterprise ZDR, pinning advice - Vercel changelog: Jev on AI Gateway — Sep 16, 2026;
experimental_evaluate, AI SDK 7.0.105, Boolean naming, per-request ZDR, budgets and logs - Vercel AI Gateway: Jev model page — Free; promotional pricing ends Sep 25, 2026
- Vercel docs: Evaluation —
boolean/probability, camelCase usage, cost fields, unsupported endpoints - Vercel docs: TypeSafe API with AI Gateway —
/typesafekeepsnoul; errors passed through - Cloudflare: Jev (typesafe) —
typesafe/jev, Third-party, 32,000 tokens,inputwrapper - OpenRouter: typesafe/jev-1.13 — listing, price, 32K context
- OpenRouter: TypeSafe SDK guide —
/api/v1/systemone, model-ID routing, dated response model - OpenRouter: Decisions API reference — alpha endpoint; errors including 402, 413 and 524
