pi
pi's coding agent — its loop, its tools and its models — loaded from a CDN and run over the workbench's files.
piAgent runs pi's coding agent in the page. pi is a terminal coding harness;
this extension takes the two packages underneath it, loads them from a CDN, and points them at the
workbench's own filesystem.
Nothing of pi is bundled and nothing is a dependency. The extension itself is about 11kb gzipped — the rest arrives over the network: the loop the first time somebody asks pi something, and the model catalogue the first time anything draws a model picker.
import { Workbench, FileSystem } from "codelet/workbench";
import { chat } from "codelet/extensions/chat";
import { piAgent } from "codelet/extensions/pi-agent";
import { terminal } from "codelet/extensions/terminal";
import { bash } from "codelet/extensions/bash";
new Workbench({
parent: document.getElementById("app")!,
fs: new FileSystem({ "/index.ts": "export const hello = 'world';\n" }),
extensions: [chat, piAgent(), terminal(), bash()],
});Note
piAgent answers a conversation but never draws one — mount chat beside it
for the pane. Anything else that answers one can be mounted beside it: a workbench carrying a
second participant puts both in the same conversation, and the reader picks per question.
#What it adds
- A
pichat participant. Its handler builds a piAgentper question, hands it the workspace, and streams what pi says into the answer. - pi's four tools —
read,edit,writeandbash— running over the same tree the explorer draws. A file pi writes is a file you watch appear, and each call is a row in the answer: what pi asked for, what came back, and a way to open it. - Whatever else the workbench registered, as tools of pi's own: a language server's, a
.vsix's, your own.workspace-toolsis the one to mount beside this — its seven over the files, and three for a command that isn't meant to end (bash_background,bash_output,bash_kill), since pi'sbashwaits for the line it runs and a dev server needs a shell of its own. Those three used to live here; they belong to the workbench, so a page answering with something else has them too. - pi's model catalogue, as a
pivendor in the model picker: 39 providers, a readable slice of their 1220 models, each answered by pi's own provider layer.
#What answers
By default pi's loop asks whatever model you picked in the workbench's own model picker — pi's own
catalogue, or anything a .vsix or a provider of your own registered. pi brings the loop, the
prompts and the tools; it doesn't insist on its own models.
pi's own catalogue is the second half. It lists 1220 models across 39 providers, which is not a picker anybody can read, so the extension publishes a slice of it and never a hardcoded list: one model for each of pi's providers, plus every model of any provider you've kept a key for, plus whatever you've picked before. Around forty rows to start with, growing into the provider you actually use.
Listing them loads pi's catalogue, so a workbench carrying this extension fetches 2.3mb from the
CDN the first time anything draws a model picker. catalogue: false is the way out if that
matters: no vendor, no fetch until pi's loop is wanted.
If you haven't picked a model, the first question you put to pi opens the workbench's own model
chooser — the same field the model button above the box opens, with Manage pi models… at the
head of it. Pick one of the published rows, or go through that first row to reach any of pi's
1220: which provider, then which of its models, every Claude, every GPT, whatever it publishes.
What you pick becomes your model for that question and every one after it, and the key field opens
for the provider it belongs to. Escape and the turn says so; nothing is asked for.
You are asked once. The choice is written down where the picker writes every choice down, so a reloaded page starts with the model you were using. Pick a model yourself before asking anything and the question never comes up at all, and neither does it once a key is held.
pi: Add a Model… is those same two questions plus the key, for when you want a second model without waiting to be asked.
pi: Manage Models is the same list from the other end — the models you added, plus any provider
you hold a key for, with Add a model… at the top. Per row: set or change the credentials, forget
the key, or take the model back out of the picker. It is also a row at the head of the workbench's
own model chooser (Manage pi models…), so you can get to it without knowing the command's name —
which is the point, since what pi publishes before you have chosen anything is one model per
provider and everything else is behind this.
The key question is pi's own, not a single "paste the key" box: pi asks whatever the provider it belongs to actually needs, so Anthropic asks for a secret while Amazon Bedrock offers a bearer token, an AWS profile or the ambient credential chain, and Google Vertex asks for a project and a location too. Where pi has something to say about a provider's credentials it arrives as a notification, with a button for any link it gave.
And it says where to get one. pi publishes no such address for any of its providers, so codelet keeps the list: the field asking for a key names that provider's console in the line under the box, and escaping it without typing anything leaves a notification with the same address as a button to open. A provider whose console codelet does not know says nothing — nothing is guessed from an API address.
Keys are kept in the workbench's secret storage — one secret per provider, and nothing is written
to disk unless the host opted into localSecrets(), which encrypts it there.
Where it did and you put a passkey on it, a reloaded page asks for your device rather than for the key again: a sealed store stays sealed until something asks it to open, so the first question that needs a key is the gesture that unlocks it. Only what that fails to turn up is asked for out loud.
// The loop and the tools, with no catalogue of pi's own: readers answer over
// whatever models are already registered.
piAgent({ catalogue: false });#The shell
pi's bash tool runs in whichever terminal profile the workbench has — bash, or anything else
that contributes one. Where there are several it is the last one
mounted, the shell a host added last being taken as the more deliberate choice. Name a specific one
with shell:
piAgent({ shell: "bash.shell" });The shell opens a tab of its own in the terminal panel, and the panel comes up on it the first time
without taking the cursor out of the message box. Mount no terminal at all and bash reports that
there's no shell here; the other three tools go on working. A page that also mounts
workspace-tools names the same profile there, that entry running the
commands pi's bash can't wait for.
#Confirmations
Tools run without asking. chat.tools.autoApprove defaults to on: agreeing to every write and
every command one dialog at a time is a conversation nobody finishes, and
nothing a tool does here is the last word — a written file lands in your tree with the unsaved dot
still on it, and bash runs in a shell you can open a tab on.
Turn it off — in the settings tab, or with Chat: Toggle Tool Confirmations — and write, edit
and bash ask before they change anything. It is VS Code's own key, so the answer follows you to
whatever else registers tools here.
Where the question appears depends on which tool asked it. A registered tool — anything from
workspace-tools, a language server or a .vsix — asks inline in
the conversation, over the answer that wanted it. pi's own four run inside pi's loop rather than
through the workbench's tool registry, and there is no inline question to be had for one of those:
they ask with a dialog over the shell instead.
#Options
| option | what it is |
|---|---|
instructions | What the model is told it is, replacing the default |
shell | Which contributed terminal profile bash runs in, by id or title |
cdn | Where pi is fetched from. https://esm.sh by default |
version | Which pi release. Pinned by default so a pi release never changes your page |
catalogue | false drops pi's own models and the two commands beside them |
keys | API keys the host already holds, by pi's provider id |
ask | false makes a missing key a failure rather than a field |