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.
- Fan-out is nearly free. All 16 questions go out in one request. Adding questions barely moves the response time, because each one is evaluated in parallel against the same state.
- The distribution is the product. The bars aren't decoration — they're the
full probability vector. A
scoreanswer of 2.77 means "77% Warm, 23% Neutral", and the panel shows exactly that. - Code owns the rendering. The model returns numbers. The bars are CSS. There is no prompt that says "output a chart".
- Speed changes the design. At a few hundred milliseconds this is a live instrument panel, not a form you submit. A keystroke re-asks all 16 questions.
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:
- What it is — asks a question, asks for action, makes a promise, states an opinion, checkable claim, names specifics
- How it lands — reads AI-written, passive-aggressive, expresses emotion, would start an argument, would get a reply, tone, conviction, urgency
- Who it's for — audience, formality
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.
- Text is capped at 4000 characters.
TYPESAFE_API_KEYis the only required environment variable.