Nola is a TypeScript superset: .tsi files where ask is a keyword and your types are the contract. Extract typed values, or let the model fill a call’s arguments — your code still runs as code. Everything lowers to plain TypeScript before tsc, your bundler, or Node ever sees it; a provider fills in the T at run time.
JSX made markup a language feature. Nola does the same for inference. A .tsi file reads like TypeScript — but tsc never sees it: Nola owns the parse, lowers every ask to plain TS, and a provider fills in the type at run time.
You already know this grammar. async made concurrency a language feature instead of a callback library; infer does the same for inference.
Same file, one letter apart — the i in .tsi is inference.
inferinfer function declares an LLM-backed function the way async declares a concurrent one. Dot-prefixed params (.bio) are context the model sees; plain params stay ordinary values. Import it from plain TS like anything else.
askask resolves an intent the way await resolves a promise — and ask with chooses who resolves it. OpenAI, Anthropic, Google, any OpenAI-compatible endpoint, plus withRetry / fallback / roundRobin, a deterministic mockProvider and record/replay ledgers for tests.
Intent<T>Calling an infer function returns an Intent<T> — thenable like a Promise<T>, but nothing reaches a provider until you await it. Until then it is a value you can shape: .withRetry(2), .withProvider(…), .withParams({ temperature }), .withTimeout(30_000).
..`prompt`<T>Ask for a string, a union, an interface. The JSON Schema is derived from the type at compile time, and the runtime validates the reply, retrying once with a correction when it drifts.
fn`hint`(…)Let the model fill a function’s arguments, then run it. Extractors in argument slots resolve in one provider call; async callees are awaited for you.
nola runLowered TS carries a source map back to .tsi, so the tsserver plugin, the LSP and the VS Code extension report hover, completion and diagnostics at .tsi positions. nola build / run / check, node --import nola-lang/register, and plugins for Vite, webpack, Rollup, Rolldown, esbuild, Rspack and Next (server-side; client bundles are rejected at compile time) — plus a shipped agent skill so your coding agent writes valid .tsi.
An SDK call spreads one decision across a schema object, a prompt string, and a type that has to be kept in sync — then makes you re-assemble all of it for the next ask. A .tsi file makes each one a single typed expression that the compiler, the editor, and the runtime all understand.
Same task, same model, on every tab. Samples follow each SDK’s current documentation, checked August 2026. The snippets are trimmed to the decision; the repo has each stack as a complete installable project.
Full runnable projects for every tab →Three phases for every .tsi file — and only the first one is yours. You write TypeScript with a couple of new constructs; the toolchain and the runtime do the rest. Nothing else to learn — no schema DSL, no graph builder, no prompt library.
Everything you know about TypeScript still applies. Add infer functions, dot-prefixed context params (.message) the model can see, and ask ..`prompt`<T> wherever you need a value from the LLM. That’s the whole job.
Before tsc, the bundler, Node, or the editor sees it, the toolchain rewrites .tsi to ordinary TypeScript with a source map — the JSX model. JSON Schemas for every <T> are derived from your types at compile time. Nothing for you to run or configure.
Calling an infer function returns a lazy Intent<T>. Awaiting it composes the context, asks the configured provider, validates the reply against the schema (retrying if it drifts), and hands back a real T — plus a receipt for every ask.
Because .tsi lowers to plain TypeScript with a source map, editor support is a thin client over one language server. VS Code ships today; Zed and JetBrains are on the way. And the agent writing alongside you reads it too.
nola.nola-vscodeThe full editor story today: language server, tsserver plugin and debugger, all aware of .tsi positions.
zedExtension in progress — same language server, packaged for Zed.
webstorm · ideaPlugin in progress — WebStorm and IntelliJ IDEA first.
The Nola skill ships inside node_modules/nola-lang, so your agent learns the real .tsi grammar at the version you installed — not whatever it guessed.
Run it bare and it detects the agents your project already uses; nola init offers the same step.
Any editor: nola check reports errors at .tsi positions from the terminal, and the bundled tsserver plugin types .tsi imports from plain TS.
“Just plain TypeScript, as far as I can tell.”
“I bundled it. Didn’t notice a thing.”
“Thenable. I awaited it. No further questions.”
“Breakpoints bound on the first try.”
Node ≥ 22. The starter runs offline from a committed replay ledger — no API key needed until you switch the provider. Already have a project? npm create nola@latest -- --add wires Nola into it — config plus dependencies.
Scaffold a project in one command — it runs offline out of the box, so you can read, edit and re-run .tsi before you ever paste an API key.