TypeSafe Typewriter

16 typed questions about whatever you're typing, re-asked on every keystroke.

Try it

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:

QuestionReturns
choiceone option from a list you define, plus a probability for every option
scorea position along an ordered rubric you define, plus the distribution
noulthe 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.

What it demonstrates

  • 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 score answer 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.

How it works

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.

The questions

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.

Files

See AGENTS.md for the file map.

Cost and caps

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:

CapLives inStops
600/min per IP, 6000/min globallyrate-limit.ts, in memorya burst from one tab or one scraper
$2.00 lifetimebudget.ts, in SQLitethe 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.

Notes

  • Text is capped at 4000 characters.
  • 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.