16 typed questions about whatever you're typing, re-asked on every keystroke.
TypeSafe's Jev is a "System One" model. You send it a piece of state and a map of typed questions, and it returns typed answers:
| Question | Returns |
|---|---|
choice | one option from a list you define, plus a probability for every option |
score | a position along an ordered rubric you define, plus the distribution |
noul | the probability that a statement is true |
No text generation, no parsing, no recovering a value from prose. Every answer is constrained to the options you supplied, and comes back with a probability distribution your code can threshold, sort, or route on.
The interesting property is that it's fast — fast enough to sit inside a UI loop. So this demo asks all 16 questions in a single API call and re-asks them on every keystroke.
score answer of 2.77 means "77% Warm, 23%
Neutral", and the panel shows exactly that.Rendering mermaid diagram...
The client debounces 120ms, cancels the pending request when you type again
(AbortController), and keeps showing the previous answers while the next batch
is in flight — so the panel never flickers to empty.
Three groups, all defined in questions.ts:
Adding one is a single entry in that file. The browser renders whatever the API reports, so there's nothing else to update.
See AGENTS.md for the file map.
Jev bills $0.042 per 1M input tokens and nothing at all for output, so a call costs only what you send it. A 16-question call on a short sentence bills ~900 input tokens — about $0.00004, or 25,000 calls to the dollar.
That cheapness is the risk. At a 120ms debounce a single open tab can ask a few
hundred times a minute, so a public link is a spend you can't see. POST /api/judge is capped twice over, and the two caps do different jobs:
| Cap | Lives in | Stops |
|---|---|---|
| 600/min per IP, 6000/min globally | rate-limit.ts, in memory | a burst from one tab or one scraper |
| $2.00 lifetime | budget.ts, in SQLite | the demo ever costing more than $2, however it's hit |
The budget is the real cap. It's one row in the val's SQLite database, so it
survives isolates, cold starts and deploys — an in-memory counter wouldn't be a
cap at all. Each call reserves a token estimate before it goes out (so a burst of
concurrent requests can't all read the same total and slip through) and then
corrects the ledger with the usage.input_tokens that came back. A call we
watched fail hands its reservation back.
When the budget is spent, /api/judge answers 429 with the ledger in the body,
and the client stops asking on its own — the panel freezes on the last answers
and says what the $2 bought, rather than filling with errors.
Set TYPESAFE_BUDGET_USD to change the cap. Current spend is in the app's footer,
in the response bodies, and in SELECT * FROM budget.
TYPESAFE_API_KEY is the only required environment variable. Without it the
page says so up front — /api/questions reports whether a key exists, so the
panel shows what's missing and asks nothing, instead of failing on the first
keystroke.