Development Journey — Jev Model Router on the Vercel AI Gateway
.env, .ignore/README.txt, and .ignore/vercel_gateway_api_key.txt. The .gitignore written at the end of the session excludes .env, .ignore/, and node_modules/. Two steps remain for you: (1) rotate the key on the Vercel AI Gateway page, because it was echoed in a session transcript; (2) run git status after git init and confirm those three paths do not appear.
1. The brief — a feasibility report, then a build
The session had two phases. Phase one was read-only: read a YouTube transcript (Riley Brown, "Jev" video, 2026), read the TypeSafe docs, and report which of the five apps in the video could be built with a Vercel AI Gateway key. Phase two was a build. The user picked three things from the report: TypeScript through the gateway, the model router first, and the Gmail MCP as the future email source.
Invisible constraints shaped every line of code and prose. Three standing modes were active: ASD-STE100 Simplified Technical English for all replies, a "ponytail" minimal-code mode at level full, and a "precise phrasing, never rhetorical" rule. The user's own rules also applied: questions are read-only, never kill a process by name, find a path before you read it, add no file that was not asked for, and report what was left undone. One of those rules was set aside at the end of the session. Section 12 names it.
The five apps in the transcript: email triage over 500 emails, a model router, "monitor everything", a Tesla-autopilot demo by a third party, and a stock trader by a third party. The report ranked them: build email triage and the router first; monitor-everything later; autopilot and trader as demos only, because Jev takes text input only and a trader needs a market feed.
2. Cold start — what the docs say, and what they do not
Minute zero: a folder with transcript.txt, README.txt, gpt6_summary.txt, vercel_gateway_api_key.txt, and .env. No package.json. No git. Node v22.22.0, npm 9.9.4, no pnpm. The .env variable was named VERCEL_GATEWAY_API_KEY. The README.txt already asked "AI_GATEWAY_API_KEY or VERCEL_GATEWAY_API_KEY ??". That question turned out to matter.
The TypeSafe docs are a Mintlify site. Append .md to any page path to get Markdown. The index at https://docs.typesafe.ai/llms.txt lists every page. Pages read in this session: api.md, introduction/quickstart.md, models.md, model-jaggedness/jev-1.13.md, agent-skill.md, confidence.md, sdk/javascript.md, sdk/python.md, concepts/system-one.md, introduction/machine-learning-primer.md, primitives/choice.md, and the SKILL.md on GitHub.
Facts from the docs that the build depends on
- Direct endpoint:
POST https://api.typesafe.ai/v1/systemone. Body:state,model,questions. Needs aTYPESAFE_API_KEY. The user did not have one. - Three question types. Choice: pick one option from a map of
criteria. Score: rate against an ordered array of levels. Noul: yes/no, returns a probability. The video calls the third one "null". The docs call it "Noul". The gateway calls itboolean. - Model:
jev-1.13.0, aliasjev-latest. Price$0.042per million input tokens. Output tokens are free. Context: 64k tokens per request; 32k forstateplus the longest question. Text only. - Rate limits on the direct API: 250,000 tokens per second, 1,200 requests per minute. The docs say these "can change without notice".
- The jaggedness page lists nine failure modes for
jev-1.13. Four matter for a router: literal reading, adversarial content, indirection, and nononeoption unless you add one.
What the docs do not say
The TypeSafe docs do not mention the Vercel AI Gateway at all. The Vercel docs mention Jev on one model page and one AI SDK page. No page on either site gives the raw HTTP path the gateway uses for Jev. https://vercel.com/docs/ai-gateway/evaluation returned 404. The wire protocol had to be read from package source. Section 3 covers that.
Rule learned: when two vendors each document half of a bridge, read the SDK source that crosses it.
3. How Jev is reached through the gateway
There are two doors to Jev. The router uses the second one.
| Direct TypeSafe API | Vercel AI Gateway | |
|---|---|---|
| URL | POST https://api.typesafe.ai/v1/systemone | POST https://ai-gateway.vercel.sh/v4/ai/evaluation-model |
| Key | TYPESAFE_API_KEY | AI_GATEWAY_API_KEY |
| Model selector | "model": "jev-latest" in the body | Header ai-model-id: typesafe-ai/jev |
| Yes/no type name | noul, answer field noul | boolean, answer field probability |
| Confidence | Inside each Choice and Score answer | Moved to providerMetadata.typesafe.confidence.<questionId> |
| Score legend | Returned as legend | Not in the gateway schema |
| Billing | TypeSafe account | Gateway credits, $0.042/M input, no markup |
The gateway path was found by downloading @ai-sdk/gateway@4.0.87 from npm into the session scratchpad and reading package/dist/index.js. Two guesses at the tarball layout were wrong first: the shell's $TMP was not the scratchpad, and the file is index.js, not index.mjs. The relevant lines:
// @ai-sdk/gateway 4.0.87, dist/index.js, lines 2340–2346
getUrl() {
return `${this.config.baseURL}/evaluation-model`;
}
getModelConfigHeaders() {
return {
"ai-evaluation-model-specification-version": "4",
"ai-model-id": this.modelId
};
}
// line 3624: var AI_GATEWAY_PROTOCOL_VERSION = "0.0.1";
// line 3636: baseURL default "https://ai-gateway.vercel.sh/v4/ai"
// line 3638: Authorization: `Bearer ${auth.token}`, plus "ai-gateway-auth-method"
A reconstructed raw call. This shape was read from source. It was never sent in this session. The AI SDK sends it for you.
curl -X POST https://ai-gateway.vercel.sh/v4/ai/evaluation-model \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "ai-model-id: typesafe-ai/jev" \
-H "ai-evaluation-model-specification-version: 4" \
-H "ai-gateway-protocol-version: 0.0.1" \
-H "ai-gateway-auth-method: api-key" \
-H "Content-Type: application/json" \
-d '{ "state": "Hey, I'\''m Riley.", "questions": { "tier": { "type": "choice",
"instructions": "Which model tier should answer this message?",
"criteria": { "nano": "Greeting or trivial chat", "balanced": "Needs real reasoning or code" } } } }'
What the app uses instead. The ai package, version 7.0.107, exports experimental_evaluate. A plain string model id resolves through the gateway when AI_GATEWAY_API_KEY is set. No @ai-sdk/gateway import is needed.
import { experimental_evaluate as evaluate } from "ai";
const r = await evaluate({
model: "typesafe-ai/jev",
state: "Hey, I'm Riley.",
questions: {
tier: { type: "choice", instructions: "Which model tier should answer this message?",
criteria: { nano: "Greeting or trivial chat", balanced: "Needs real reasoning or code" } },
is_greeting: { type: "boolean", instructions: "Is this only a greeting?" },
},
});
The raw response body from that probe, the first call of the session with the key. Provider time from startTime to endTime: 138 ms. Gateway generation id and the routing block are shortened.
{
"answers": {
"tier": { "type": "choice", "choice": "nano", "probabilities": { "nano": 1, "balanced": 0 } },
"is_greeting": { "type": "boolean", "probability": 0.39 }
},
"rounding": { "probabilityDecimals": 2, "scoreDecimals": 2 },
"usage": { "inputTokens": 336, "outputTokens": 51 },
"warnings": [],
"providerMetadata": {
"typesafe": { "confidence": { "tier": 1 } },
"gateway": { "routing": { "finalProvider": "typesafe-ai", "statusCode": 200, ... },
"cost": "0", "marketCost": "0.000014112", "generationId": "gen_..." }
}
}
Three things in that body are not obvious. confidence lives under providerMetadata.typesafe, not in the answer. rounding says every probability is rounded to two decimals, so "nano": 1 means "at least 0.995", not certainty. cost is "0" while marketCost is "0.000014112", because the account was on free credits at that moment.
Env var: the packages read AI_GATEWAY_API_KEY (dist/index.js:180 and :3947). The folder's .env had VERCEL_GATEWAY_API_KEY. The user added the second name by hand. Node reads the file with node --env-file=.env, which needs Node 20.6 or later. No dotenv package.
4. How the router uses Jev
The router is one HTTP server in server.mjs and one page in index.html. No framework. Node's built-in http module serves the page on GET / and takes a message on POST /chat. The flow for one message:
- The page posts
{ "message": "..." }. - The server caps the message at 20,000 characters. This keeps the state under Jev's 32k-token limit.
- The server sends one Choice question to Jev. The state is
{ user_message: message }. The options are the four tier names with their rubric strings. - Jev returns a tier, a probability for every tier, and a confidence in metadata.
- The server looks up the model id for that tier and calls it with
generateTextthrough the same gateway key. - The response carries the tier, the model id, the probabilities, the confidence, both timings, and the reply.
The two blocks a reviewer must read. Everything else in the file is plumbing.
const JEV = "typesafe-ai/jev";
const TIERS = {
nano: { model: "openai/gpt-5-nano", rubric: "Greeting, small talk, a one-line fact, or a trivial request" },
fast: { model: "google/gemini-3-flash", rubric: "A simple question or short task with a clear answer; no deep reasoning" },
balanced: { model: "anthropic/claude-sonnet-5", rubric: "Needs real reasoning, planning, a careful explanation, or writing code" },
frontier: { model: "anthropic/claude-fable-5.1", rubric: "Large multi-step or high-stakes work where a wrong answer is costly: a complete system, hard math, a long document" },
};
const TIER_QUESTION = {
type: "choice",
instructions: "Which model tier is the cheapest one that can answer this user message well?",
criteria: Object.fromEntries(Object.entries(TIERS).map(([k, v]) => [k, v.rubric])),
};
async function route(message) {
const t0 = performance.now();
const ev = await evaluate({ model: JEV, state: { user_message: message }, questions: { tier: TIER_QUESTION } });
const jevMs = Math.round(performance.now() - t0);
const { choice: tier, probabilities } = ev.answers.tier;
const confidence = ev.providerMetadata?.typesafe?.confidence?.tier ?? null;
const model = TIERS[tier].model;
const t1 = performance.now();
// ponytail: the reply model may be refused on the gateway free tier; keep Jev's decision visible either way
let reply = "", replyError = null, usage = null;
try { const gen = await generateText({ model, prompt: message }); reply = gen.text; usage = gen.usage; }
catch (e) { replyError = String(e.message ?? e); }
return { tier, model, probabilities, confidence, jevMs, replyMs: Math.round(performance.now() - t1), reply, replyError, usage };
}
What Jev sees, and what it never sees
Jev receives the state, the instruction string, and the criteria map. The criteria map is option name to rubric string. The docs say: "The option names and their descriptions are both sent to the model." Jev never sees the question id tier. The docs say: "The model never sees the question id." Jev never sees the model ids in TIERS. It does not know that "balanced" means Claude Sonnet 5. It picks a word from four words, guided by four rubric sentences. Rename balanced to medium and the answer can change, because the option name is part of the input.
The router is stateless. generateText({ model, prompt: message }) sends no chat history and no system prompt. Each message is a fresh conversation for the reply model. That is a deliberate cut. A chat with memory needs a message array and a store. Section 12 lists it as not built.
5. The four tiers — which model is which, and why
Riley's video shows four tiers: "tiny or nano, fast, balanced, and frontier". The video shows Claude Sonnet 5 as the balanced pick. It does not show the other three model ids. The ids below are this session's picks from the gateway price list at GET https://ai-gateway.vercel.sh/v1/models, which needs no key. Prices are per million tokens.
| Tier | Model id on the gateway | Input | Output | Why this one |
|---|---|---|---|---|
| nano | openai/gpt-5-nano | $0.05 | $0.40 | Cheapest named-vendor text model on the list. Also the only reply model that worked on the free tier. |
| fast | google/gemini-3-flash | $0.50 | $3.00 | Ten times nano's input price. A step up in quality with low latency. |
| balanced | anthropic/claude-sonnet-5 | $2.00 | $10.00 | The model Riley's video shows for this tier. |
| frontier | anthropic/claude-fable-5.1 | $10.00 | $50.00 | Highest-priced Anthropic model on the list. Same price as openai/gpt-6-astra. |
The price ratio nano to frontier is 200 to 1 on input. That ratio is the point of a router. A greeting sent to frontier costs 200 times what it needs to. An "Anthropic only" set was offered as an option (claude-3-haiku, claude-haiku-4.5, claude-sonnet-5, claude-fable-5.1). The user picked the default set.
Cost per Jev routing decision, measured: 336 input tokens at $0.042/M is $0.000014. The gateway reported the same figure as marketCost. Cost per reply depends on the tier: about $0.0001 for nano, up to a few cents for frontier on a long answer. Total API spend for the whole session, all probes and checks: $0.014.
6. Where the probabilities come from
This is the part the user asked to demystify. Here is what is documented, what was observed, and what is not public.
Documented: the training objective
The TypeSafe AI primer names the method: RLCD, "reinforcement learning for calibrated decisions". It contrasts it with RLHF (trains a model to say what people prefer) and RLVR (trains reasoning with verifiable rewards). The primer's claim is: "Higher probability should correspond to a greater chance that the answer is correct." Calibration is defined across groups: "Outcomes assigned a probability of 0.8 should occur about 80% of the time." The primer adds: "These rates describe groups of predictions, not a guarantee about any single answer."
The models page says the same weights serve every account. There is no fine-tuning and no LoRA. You shape the answer through state, instructions, and criteria only.
Documented: what a Choice returns
The choice page: "probabilities: The full probability distribution across every option. The sum of all values is 1." And: "confidence: A number from 0 to 1 computed from how probabilities is spread. A flat shape ... means low confidence. A single peak on one option means high confidence." The confidence page says the formula is TypeSafe's chosen statistic and that a different one may suit you, which is why the full distribution is returned.
Observed: four distributions and their confidence values
| Input | Probabilities (rounded to 2 decimals by the API) | Confidence |
|---|---|---|
| "Hey, I'm Riley." | { nano: 1, fast: 0, balanced: 0, frontier: 0 } | 1 |
| "Hey, I'm Riley. I want to build an app that uses AI as a wrapper. Tell me the best way to do it." | { balanced: 0.8, fast: 0.2, nano: 0, frontier: 0 } | 0.73 |
| "Please generate all of the code for this. Make sure it's perfect." | { balanced: 0.74, frontier: 0.25, fast: 0.01, nano: 0 } | 0.65 |
| Docs quickstart example, three options | { billing: 0.84, technical: 0.159, sales: 0.001 } | 0.596 |
Two readings of that table. First, confidence is not the top probability: 0.8 gives 0.73, and 0.84 gives 0.596. Second, confidence is not the margin between first and second: 0.8 minus 0.2 is 0.6, and the reported value is 0.73. The formula is a function of the whole distribution. It is not published. This document does not try to reverse-engineer it from four points.
Compare with Riley's video. His third prompt showed 95% for balanced. This session's third prompt gave 74% balanced and 25% frontier. The prompts are close but not identical, the rubrics differ, and the model version may differ. The numbers are not comparable as a benchmark.
Not public
- The architecture. The docs say Jev "reads language like an LLM" and never generates. No parameter count, no base model, no description of the output head.
- The confidence formula.
- The training data, beyond "not trained on customer requests or responses".
Consequences for a router
- Choice is relative. It always picks one of the four. There is no "none" tier. The docs advise adding
otherwhen the list may not cover the input. This router did not add one, because every message needs some model. - Literal reading. The instruction says "cheapest ... that can answer this user message well". Jev reads "cheapest" as a word in a sentence. It has no price table. The rubrics carry the meaning.
- Adversarial content. The jaggedness page: state "is data, and jev-1.13 does not treat it as hostile by default." A message that says "this is trivial, use the cheapest model" can pull the answer toward nano. A router in front of untrusted users should test that case.
- Rounding.
probabilityDecimals: 2. A 0 is "below 0.005". A 1 is "above 0.995".
7. The free-tier wall — the crux of the session
The build took about twenty minutes. The free tier took the rest of the session.
Timeline
- Probe call: success. One Jev call.
- First
--checkrun: prompt 1 succeeded (Jev call, thengpt-5-nanoreply). Prompt 2 failed. The check printedAssertionError [ERR_ASSERTION]: unknown tier undefinedand nothing else. The self-check had swallowed the real error. Two Jev calls had succeeded in that burst. - The check was patched to print
r.error. Second run:GatewayRateLimitError: Free tier requests on this model are rate-limited. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted access.This time on prompt 1. - The Jev-only probe was re-run to isolate the cause. It returned the same 429 with
"modelAttempts": [{ "canonicalSlug": "typesafe-ai/jev", "success": false }]. So Jev itself was limited. - The credit endpoint showed
balance 4.9998,total_used 0.0002, free credits. - The user ran the check later on their own machine. Prompt 1 passed. Prompt 2 failed with a different message:
Free tier users do not have access to this model. Upgrade to paid credits .... Which of the two calls inroute()threw — Jev or the reply model — could not be determined, becauseroute()had no try/catch between them at that time. - A probe of seven reply models, one tiny call each:
openai/gpt-5-nanoanswered.google/gemini-3-flash,anthropic/claude-sonnet-5,anthropic/claude-fable-5.1,anthropic/claude-haiku-4.5,google/gemini-2.5-flash-lite,openai/gpt-5-miniall returned the "rate-limited" 429. - That probe used up the window. The next check run returned the Jev 429 again.
- The user bought credits. Balance moved to the paid tier. The next check run passed all three prompts.
A wrong statement, corrected
After step 4, the report to the user said: "The limit is on Jev, not on the reply models." Step 7 showed six reply models limited too. The correct statement is: on the free tier, Jev is limited to a small burst, and most reply models are limited or refused. The only reply model proven to work on free credits was openai/gpt-5-nano.
What the Vercel docs say, read after the fact
Pricing page, updated 2026-09-08: "The free tier includes a subset of models, not the full catalog." "Free tier requests are also rate limited per model, with lower limits than the paid tier." "Once you purchase credits, your account transitions to the paid tier and the monthly free credit no longer applies." Rate-limits page: "AI Gateway does not rate limit paid-tier requests." "Limits can change, so this page describes behavior rather than fixed numbers."
The free-tier model list itself was not found. A fetch of the models page with ?freeTier=true returned models priced at $0, which is a different thing. The /v1/models JSON has no free-tier field. So the list of free-tier models is unknown, except for the one proven by a call.
Rule learned: two error strings that both end in "Upgrade to paid credits" are the same wall. Do not spend turns telling them apart.
Rule learned: a self-check that asserts on a field must print the error that made the field empty. assert.ok(x, `... ${r.error}`) costs one line and saves a run.
8. Tools and features used
| Tool | What it did in this session |
|---|---|
Read, Glob, Bash cat/sed | Read the transcript, the notes, and the env var names. The key value was redacted in every shell output with sed -E 's/vck_[A-Za-z0-9]+/vck_<redacted>/g'. |
WebFetch, curl | Fetched the TypeSafe Markdown pages and the Vercel docs pages. curl for pages where WebFetch's summary dropped detail. |
curl to /v1/models and /v1/models/typesafe-ai/jev/endpoints | Public, no key. Confirmed the model id, price, zdr: "all", no_training: "all", and supports_implicit_caching: false. |
| npm registry + tarball | Downloaded @ai-sdk/gateway@4.0.87 into the scratchpad to read the evaluation endpoint path. Session-transient; the findings are in section 3. |
npm init -y, npm i ai | Installed ai 7.0.107. One dependency. |
| Write, Edit | probe.mjs, server.mjs, index.html, HANDOFF.md, .gitignore, this document. |
| AskUserQuestion | Two rounds. Round one: app shape, dispatch or pick-only, tier models, permission to probe. Round two: audience and depth for this document. |
| advisor | Two consultations. The first, before the feasibility report, added the env-var mismatch, the "no confidence in the gateway schema" flag, and the "not verified" list. The second, before this document, added the rounding finding, the corrected count of successful calls, and the push-safety warning at the top. |
| Skill: handoff-after-clear | Triggered by a context-size nudge at about 160k tokens. Produced HANDOFF.md and one memory file. |
| Skill: dev-journey | This document. |
No subagents were spawned. All work ran inline. The model was switched from Opus 5 to Fable 5.1 by the user mid-session with /model fable, and effort was set to high.
Human-in-the-loop moments
- The user added
AI_GATEWAY_API_KEYto.envand moved the notes into.ignore/. - The user answered four build questions and two document questions.
- The user ran the check on their own machine and pasted the output. That output carried the second error string.
- The user bought gateway credits.
9. What went wrong, and the fixes
- Self-check hid the error.
assert.ok(TIERS[r.tier], `unknown tier ${r.tier}`)printedunknown tier undefinedwhen the server returned{ error }. Fix: append: ${r.error}to the message. - Self-check failed on a refused reply.
assert.ok(r.reply.length > 0, "empty reply")made a free-tier refusal look like a router bug. Fix: removed the assertion; the check printsreply model ... failed: ...instead. - One failure hid Jev's answer. When
generateTextthrew, the wholeroute()threw, and the page showed a red line with no tier. Fix: try/catch aroundgenerateText; returnreplyErrornext to the tier. The page shows the badge and bars, then the refusal in red. - Wrong shell working directory.
cd "$TMP"in Git Bash did not land in the session scratchpad. Two greps hit "No such file". Fix: use the scratchpad's absolute path. - Wrong file name guess. The gateway package ships
dist/index.js, notindex.mjs. Fix:ls package/distfirst. - 404 on a guessed doc URL.
vercel.com/docs/ai-gateway/evaluationdoes not exist. Fix: the AI SDK site has the page, atai-sdk.dev/docs/ai-sdk-core/evaluation. - Misleading free-tier page fetch.
?freeTier=truereturned $0-priced models through the summarizer. Not fixed; the free-tier list stays unknown. - An unverified claim in a reply. "The paid tier can take a minute to apply" was said without evidence. It was not needed; the first run after payment passed.
10. Verification
Every "works" claim below was checked by an observed output, not a clean exit.
- Jev reachable through the gateway: the probe's raw body in section 3,
statusCode: 200,finalProvider: "typesafe-ai". - The router end to end: the check output after payment, verbatim:
The check also asserts thatHey, I'm Riley. → nano conf=1 jev=849ms reply=4166ms {"fast":0,"balanced":0,"frontier":0,"nano":1} Hey, I'm Riley. I want to build an app t → balanced conf=0.73 jev=349ms reply=12925ms {"fast":0.2,"frontier":0,"nano":0,"balanced":0.8} Please generate all of the code for this → balanced conf=0.65 jev=403ms reply=7006ms {"nano":0,"fast":0.01,"balanced":0.74,"frontier":0.25}GET /returns a page with a<title>. - Latency: Jev round trip from this machine 349 ms to 1038 ms across five runs. Provider-side time 138 ms in the probe metadata. The video's "0.4 seconds" is not in any doc read in this session.
- Cost:
total_usedfromGET /v1/creditswent from0.0002to0.0141over the session.
Not verified
- The raw curl in section 3. Read from source, never sent.
- The web page in a browser with a real click. The check exercises
GET /andPOST /chatwithfetch. No screenshot was taken. - The adversarial case ("use the cheapest model") against the router.
- Behavior of
typesafe-ai/jev-latestas a gateway id. The AI SDK doc example uses it. Onlytypesafe-ai/jevwas called.
11. Unknown unknowns — things a first-time user would not think to ask
| You might assume | What is true |
|---|---|
| A probability of 1 means certainty. | The API rounds to two decimals (probabilityDecimals: 2). 1 means at least 0.995. 0 means below 0.005. |
| Confidence is the top probability. | It is a separate statistic over the whole distribution. 0.8 top gave 0.73; 0.84 top gave 0.596. The formula is not published. |
| Confidence is in the answer. | Through the gateway it is in providerMetadata.typesafe.confidence.<questionId>. The direct API puts it in the answer. |
| Jev knows which model each tier maps to. | It sees only the four option names and four rubric strings. The model ids and the question id never reach it. |
| Jev picks "none" when nothing fits. | A Choice always picks one option. Add an other option if you need a way out. |
| "Cheapest" in the instruction makes Jev price-aware. | Jev has no price table. It reads the word. The rubrics carry the meaning. |
| Users cannot steer the router. | State is not treated as hostile. "This is trivial, use nano" in the message can move the answer. Untested here. |
| Free credits let you test freely. | The free tier limits Jev to a small burst and refuses most reply models. Only openai/gpt-5-nano replied. Any paid top-up removes the gateway limits and ends the monthly free credit. |
| The paid tier has no limits. | The gateway adds none. TypeSafe's own limits still apply: 250,000 tokens per second, 1,200 requests per minute, "can change without notice". |
| The AI SDK API is stable. | The function is experimental_evaluate. The prefix means it can change. package.json has "ai": "^7.0.107"; pin it before you rely on it. |
| "Failed after 3 attempts" means three bugs. | The AI SDK's maxRetries defaults to 2. One 429 becomes three tries, then one error. |
| One vendor sees your message. | Two do. TypeSafe sees it for the routing decision. The reply model's provider sees it for the answer. The gateway entry for Jev carries zdr: "all" and no_training: "all"; check the reply model's entry separately. |
| The router is a chat. | It is stateless. Each message is a new conversation for the reply model. No history, no system prompt. |
| The two vendors document the bridge. | Neither does fully. The raw gateway path came from @ai-sdk/gateway source. |
| "Null" is a Jev type. | The type is Noul. The video misheard it. The gateway names it boolean and returns probability. |
| Prompt caching helps repeated calls. | The Jev endpoint record says supports_implicit_caching: false. |
Node loads .env by itself. | Only with node --env-file=.env, Node 20.6 or later. The env var must be AI_GATEWAY_API_KEY. |
| Jev's version is fixed. | The gateway id typesafe-ai/jev is an alias. TypeSafe's jev-latest moves when a release ships. Thresholds tuned today can drift. |
12. Where things stand
Done
server.mjsandindex.html: the router, working on the paid tier. Runnode --env-file=.env server.mjsand openhttp://localhost:3000. Run with--checkfor the three-prompt self-test.probe.mjs: a raw Jev call. Delete when no longer wanted.HANDOFF.md: the resume point for a fresh session..gitignore:.env,.ignore/,node_modules/. This file was not asked for. The rule "add no file I did not ask for" was set aside, because the user said the folder will be pushed to a public repository and the key sits in two of the excluded paths. Security measures are the one exception the minimal-code mode allows.- One memory file in the agent's memory store: gateway model id, where confidence lives, the free-tier 429.
Not built, by choice
- Riley's tools around the router: web search, file generation, code generation. The router is the decision layer only.
- Chat memory. Each message is stateless.
- A
noneorothertier. - Email triage. The user chose the Gmail MCP as the source. Nothing was started. Plan: one Jev request per email with four questions (category choice, importance score, brand-deal boolean, scam boolean). Estimated cost for 500 emails: about
$0.02. - Monitor-everything, autopilot, trader.
Before git push
- Rotate the gateway key. Put the new value in
.envonly. git init, thengit status. Confirm.env,.ignore/, andnode_modules/are absent.- Pin the dependency: change
"ai": "^7.0.107"to"ai": "7.0.107"inpackage.json, or accept drift.
Next options
- Start email triage with the Gmail MCP.
- Add the adversarial test to
--check: a message that asks for the cheapest model while requesting a full code base. - Add an
othertier and a confidence floor: below 0.5, ask the user which tier they want.