Skip to content
| Marketplace
Sign in
Visual Studio Code>AI>PerchNew to Visual Studio Code? Get it now.
Perch

Perch

seanahn

|
3 installs
| (0) | Free
Claude Code and Codex agents as tabs, each with its own look, with dictation, usage, and backend switching. A shell, not an agent.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

perch

Claude Code and Codex as tabs in VS Code, in the editor area or the sidebar. Any number of sessions, with usage and backend switching built in.

Why: the vendors' own agents, so their caching

perch runs the Claude Code CLI and the Codex CLI themselves, one process per tab, through the vendors' own SDKs. It is a shell around them, not an agent of its own. That is the whole design, and the reason is tokens.

The principle

An agent's token use is a function of prompt caching, and prompt caching is a function of a byte-identical prompt prefix.

An agent sends the whole conversation to the model at every step of a turn: system prompt, tool definitions, every message and tool result so far, then the new step. What keeps that affordable is prompt caching: the part of a call's prefix that matches a recent call is billed at a tenth of the input rate (writing the cache costs a quarter more). On a 100k-token conversation a cached step bills the equivalent of about 10k input tokens; a cold one about 125k. A cache hit needs the prefix byte-identical from step to step, and only the program that lays out the prompt can guarantee that. Claude Code and Codex do. A harness that replaces them with its own loop and its own prompt gets its own caching, its own compaction, and API billing instead of your subscription.

The cache itself is the provider's. On the vendors' own APIs it is the reference. On Amazon Bedrock, which hosts the models rather than proxying the vendors, both vendors' caching works as their own does (measured for Codex on Bedrock: every turn after the first read its whole prefix from the cache), with a lag on the newest options (the hour-long Claude cache only on some models, no 5m/1h breakdown). Behind a proxy such as an LLM gateway, whether the markers survive depends on the route; perch shows what came back, and design.md says what was measured.

PERCH TAB   (the vendor's agent lays out the prompt; perch never touches it)

  turn 1   [system · tools · conversation so far] [new message]  → cache written
  turn 2   [same bytes, unchanged ................] [new message]  → cache hit, 0.1×
  turn 3   [same bytes, unchanged ................] [new message]  → cache hit, 0.1×

A WELL-BUILT HARNESS   (Cursor, measured on the same model and provider)

  turn 1   [system · tools · conversation so far] [new message]  → cache written
  turn 2   [same bytes, unchanged ................] [new message]  → cache hit, 0.1×   (~96 % read back)
  turn 3   [same bytes, unchanged ................] [new message]  → cache hit, 0.1×

A HARNESS THAT RE-RENDERS CONTEXT NEAR THE FRONT   (Continue, as its source lays it out)

  turn 1   [system · editor state A · @context · history] [message]  → cache written
  turn 2   [system · editor state B · @context' · history] [message] → prefix changed: cache miss, 1×

So the cache is not what separates a shell from a harness: a harness that keeps its prefix stable caches as well as the terminal does. What perch guarantees is narrower: it adds nothing that could bust the vendor's cache, and it keeps the vendor's billing. The rest of the difference is the billing path (a subscription applies only to the vendor's own agent; a harness is API billing by construction) and the agent itself (prompt, tools, compaction, permissions, tool-call style).

Where the difference comes from

  1. Prompt assembly. A harness runs its own agent loop over the vendor's model and writes the editor's state into the prompt (the open file, the selection, @-mentioned files, the workspace tree). Where that lands decides the cache: near the front, where it changes between turns, it invalidates everything after it (Continue's context providers, as its source lays them out); behind a stable prefix, it costs nothing (Cursor, measured). perch never builds a request. The prompt an agent sends from a perch tab is the one it would send from a terminal, so a tab's cache hits are the terminal's cache hits, and a tab costs what a terminal session costs. IDE context in perch is appended to the message, at the end, where it changes nothing that came before.

  2. Compaction and sub-agents. A harness that keeps one growing chat buffer re-sends every raw tool output on every turn until the window is full. The vendors' agents compact on their own judgement, keep CLAUDE.md, memory, skills, hooks, and MCP servers in play, and (Claude Code especially) run searches and other side work in sub-agents whose transcripts never enter the main conversation. A perch tab inherits all of that unchanged, because it is that agent.

  3. Sessions across reloads. perch keeps no transcript of its own. A tab remembers the agent's session id and, after a window reload or a backend switch, resumes that session from the agent's own record (~/.claude/projects/, ~/.codex/sessions/), so the prompt prefix is the same bytes as before. Within the cache's life (an hour on a subscription, five minutes on the API) the earlier turns are still cached; beyond it, the next turn re-warms the same prefix once, rather than a reconstructed one. The sessions list, names, transcripts, and usage gauges are all read from those files and ask nothing of a model.

Side by side

perch Cursor Continue
Agent the vendor's own: Claude Code, Codex, through their SDKs Cursor's own loop and middleware, in a forked editor Continue's own harness, in an extension
Prompt laid out by the vendor's agent; perch adds nothing Cursor's own, closed source; its prefix stays stable in practice rebuilt each turn from @-context providers
Cache behaviour the terminal's: a stable prefix, hits turn after turn the same: ~96 % of each turn read back on turns 2+, measured (Claude Code 95 %) dynamic context in the prefix breaks the match (from its source; not measured)
Cost basis your existing subscriptions and logins; API / Bedrock when you switch Cursor's plans and request credits, or an API key your own API key
Compaction, memory, MCP the vendor's: CLAUDE.md, hooks, skills, MCP, sub-agents Cursor's own indexing and context filters Continue's client-side providers
Cost of a long session that of a terminal session about 10 % more than Claude Code on the same model and provider (twice the tool calls, at cached rates), on Cursor's plans rather than your subscription not measured; its prefix re-rendering says more than a terminal session

The Cursor figures are one controlled measurement (Claude Code and Cursor on Opus 5.5 through Bedrock, the same prompts, one price basis, 2026-10-06); an earlier version of this page said "several times a terminal session" on no source, and a public 5.5× figure turns out to be one run on mixed models with no cache analysis. What perch can show is that a tab's cache reads and costs are a terminal's, turn by turn, in its own footer. The full reasoning, with what has and has not been verified, is in design.md, sections 2 and 3.

Tab kind Agent How it runs Auth
Claude Claude Code @anthropic-ai/claude-agent-sdk, streaming input, canUseTool for permission prompts your existing claude login
Codex Codex @openai/codex-sdk, one codex exec per turn on one thread your existing codex login

Installed from the marketplace, perch runs the claude and codex programs that come with the Claude Code and ChatGPT extensions, so those two must be installed as well.

Where tabs live

perch.newTabs decides where a new tab opens.

editor (the default): each session is a native editor tab, like a file. The tab row is the first line: there is no container icon row and no view title above it, because those belong to VS Code's sidebars and an extension cannot remove them. Tabs carry the vendor's icon, can be dragged, split, and reordered, and open beside your editor. The + in the editor title bar opens a new one. A working tab's title ends in …, and a tab waiting on a permission prompt while hidden starts with ●. Closing the editor tab closes the session.

sidebar: sessions are tabs inside the Perch view, under the Perch icon in the activity bar.

Both can be used at once. Perch: Move Tab to the Editor Area and Perch: Move Tab to the Sidebar move the active tab; the session, its agent, and its transcript are untouched. There are Move All versions of both. Tabs saved before this setting existed follow it on the next reload.

An editor group that holds only Perch tabs is locked, the way a terminal's is, so a file opened from the Explorer goes to the group beside it and not in among the sessions. perch.lockGroup turns that off.

After a window reload VS Code brings the editor tabs back where they were, and each resumes its session.

Tabs

Press + to open a Claude or a Codex tab: in the editor title bar, or in the sidebar view's tab bar. Each tab is its own session with its own agent process, context, mode, draft, and transcript. Open as many as you like of either kind. When a gateway file exists (see Logins), a Claude tab can be put on an LLM gateway: a tab whose Claude Code reaches an Anthropic-compatible endpoint of your own, such as a company's LLM proxy, while the other tabs stay on your login. Paste an image into the message box to attach it: up to eight to a message, each scaled down to at most 1568 px on its long side, the most the API keeps as is. A tab is titled from its first message, or by you (see below). While a session works, the foot of its transcript says what it is doing and for how long. A dot on a tab means it is working; a red dot means it is waiting on a permission prompt. Middle-click or × closes a tab and stops its agent.

Tab icons are the vendors' own, read at runtime from the Claude Code and ChatGPT extensions if they are installed. perch ships no logos. It uses each extension's glyph, tinted as the vendor tints it on its own tabs: Claude's spark in orange, the ChatGPT blossom in the theme's text colour. Without a glyph it uses the marketplace image, and without the extension a letter, C or X.

Answers are drawn as Markdown: headings, lists, tables, code, links. A link to a file opens it in the editor, at its line if it names one. Each code block, and each tool call's command, shows a copy button when the pointer is over it. The transcript keeps to its end as more arrives, until you scroll away to read; scroll back to the end and it follows again. A long tool result is clipped and lets the wheel pass, so scrolling the transcript never catches on one; click it to open it and scroll inside, click again to close it.

Agents start lazily: an open tab costs nothing until you send a message.

Tabs survive a window reload. perch saves each tab's session id and the agent resumes its own saved conversation on the next message. The earlier transcript is read back from the agent's own record of the session: the last thousand entries of it, with images as a count and Codex's sealed reasoning left out.

Names and past sessions

A tab is named from its first message until you name it yourself: run Perch: Rename Active Tab…, or double-click a tab's name in the sidebar.

Perch: Sessions… (the clock icon in the title bar) lists the past sessions of the workspace folder, Claude's and Codex's together, newest first. Type to search. Choose one to open a tab on it, or go to the tab already open on it. The pencil on a row renames that session, open or not. The bin deletes it, after asking: the agent's own record of the session is removed from this machine (Claude Code's by the Agent SDK, Codex's by codex delete), and a tab open on it is closed first. A tab you have not named shows the title its agent's record has, so a tab and its row in the list say the same name.

perch keeps no list or names of its own. It reads each agent's records, and writes a name where that agent keeps names: for Claude, in the session's file under ~/.claude/projects/, through the Agent SDK, which is where Claude Code's own /rename writes; for Codex, as a line added to ~/.codex/session_index.jsonl. So a session named in Claude Code has that name here, and the reverse. A tab named before its first message has no record yet; its name is written when its first turn ends.

Tabs do not share history. Hand off (Perch: Hand Off Last Answer to Another Tab…, in the … menu of the title bar and in the command palette) pastes the active tab's last answer into another tab's input box, or into a new tab. That is the only bridge, on purpose: shared context would defeat each agent's caching.

Install

From the marketplace: install Perch; Perch Audio, the microphone companion, and the Claude Code and ChatGPT extensions come with it. Log in to each vendor when Perch asks, on the first message to a tab of that kind (see Logins below).

From a checkout:

make install     # npm install, syntax check, symlink into the extension dirs

Reload the window, then press Ctrl+Alt+P (Cmd+Alt+P on a Mac): Perch: Open brings back the tab you last used, or, with none open, the sessions list, which starts a new tab of either kind at the top. The same is behind the Perch icon in the activity bar and Perch: New Tab… in the command palette.

Logins

Both agents run on the logins already on the machine with the workspace: Claude Code's (~/.claude) and Codex's (~/.codex/auth.json). Nothing is copied from elsewhere, so a fresh remote has neither.

  • Claude, on a subscription: the first message to a Claude tab on a machine with no login is held back, the text put back in the box, and Log In offered: Claude Code's own sign-in page (or claude /login in a terminal when that extension is absent). Perch notices when the login lands and says so; send the message again. The footer's "log in" does the same at any time.
  • Claude, on API / Bedrock: the API switch in the footer moves Claude to those credentials. They are Claude Code's, not Perch's: AWS credentials or a profile on the workspace machine (~/.aws, or AWS_* variables), or ANTHROPIC_API_KEY; the region and model go in the env block of ~/.claude/settings.json (AWS_REGION, ANTHROPIC_MODEL, and the small/fast model if you use one). With none of that in place the first message is held back and the offer opens that file, or switches back to the subscription.
  • LLM gateway: a Claude tab can be put on a gateway, an Anthropic-compatible endpoint reached with ANTHROPIC_BASE_URL and a token, as a company's LLM proxy is. The variables live in a file of yours, ~/.config/gateway-claude/env by default (perch.claude.gatewayEnv), written as a shell reads it: export ANTHROPIC_BASE_URL=…, export ANTHROPIC_AUTH_TOKEN=… (or ANTHROPIC_API_KEY), and any model names (ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, …). The same file can drive a terminal launcher. A tab on the gateway runs its Claude Code process with those variables added to its environment, and that is all: nothing is written to ~/.claude/settings.json, the token is never in a file of Perch's, and the session, its record, your CLAUDE.md, memory, and MCP servers are the same as any other tab's. Put a tab on it from the footer's backend menu under any Claude tab, where the entry reads LLM gateway (or with Perch: New Claude Tab on the Gateway); once chosen, new Claude tabs start there until a tab is put back on the subscription or API / Bedrock. The template also sets ENABLE_PROMPT_CACHING_1H=1: on a token Claude Code would keep the prompt cache for five minutes, after which a turn re-sends the whole conversation at the cache-write rate; the hour costs more per write and less by the first pause over five minutes. Remove the line if the gateway rejects the request. The first message to a tab whose file is missing or incomplete is held back, and the offer opens the file (made from a template, readable by you alone) or takes the tab off the gateway; the two choices stay as buttons under the note. The window's own backend does not matter: with Claude set to API / Bedrock in ~/.claude/settings.json, a tab on the gateway still goes to the gateway, because Perch gives its process the gateway's variables (all but the token) as settings of its own, which Claude Code applies over the user's. A tab on the gateway runs its process through a small relay of Perch's on the loopback interface, which forwards each request to the gateway unchanged and reads the header in which a LiteLLM gateway names the model that answered (x-litellm-model-name); a routed name such as nexus-auto may send each request anywhere. The line under each answer then says what answered the turn's requests, via gpt-6-luna ×3, grok-4.6, and what the gateway charged for them, $0.0021 this turn, its own figure (x-litellm-response-cost, summed) in place of Claude Code's estimate at Anthropic list prices, which does not apply to a gateway's names; the tooltip has the session's total by the same reckoning. A streamed response carries no price from the gateway, so a turn is priced at list rates for the model that answered each request (LiteLLM's public price table, fetched weekly) and shown with ≈; the tooltip says so, and gives the token's running total as the gateway counts it. The model button's tooltip names the last model. The agent is still Claude Code; the model is whatever the gateway chose.
  • Codex: the first message to a Codex tab on a machine with no login is held back, the text put back in the box, and the login offered two ways. Log In runs codex login on the workspace machine and opens ChatGPT's sign-in page in your browser; the page returns to a local port, which VS Code forwards to the workspace machine when it is remote, so this works for anyone with a browser. Device Code runs codex login --device-auth in a terminal instead: a link and a one-time code, for a machine with no forwarding, which ChatGPT has to allow first (Settings → Security and login → App security → "Enable device code sign-in"). Either way Perch notices when the login lands and says so; send the message again. The same offer appears if Codex later answers 401, a login that has expired. On the gateway the model menu is the file's names, not Claude Code's catalog: the default is ANTHROPIC_MODEL, and opus, sonnet and haiku are what ANTHROPIC_DEFAULT_*_MODEL map them to, which is what the gateway receives. A tab moved onto the gateway with another model chosen starts from the file's default. The choice sticks: once a tab is put on the gateway, new Claude tabs start there, until a tab is put back on the subscription or API / Bedrock.

Permissions

Claude: the SDK calls back on every tool use and perch shows Allow / Always / Deny. The request reads as what it is: for an edit, the file and the change as a diff, removed lines then added; for a new file, the file and what goes in it; for a command, the command and what it is for. The default mode for new tabs is a setting (perch.claude.permissionMode) and each tab can switch live.

Codex: the SDK runs codex exec, which is non-interactive. Approvals resolve by policy (perch.codex.approvalPolicy) and the sandbox (perch.codex.sandboxMode). There is no per-command prompt.

Codex's sandbox on Linux is bubblewrap, which needs unprivileged user namespaces; a container that forbids them (a JupyterHub pod, say) fails every command with bwrap: No permissions to create a new namespace, and Codex has no other sandbox there. When that happens Perch asks whether to run Codex without a sandbox, for that tab or for every tab on that machine from then on; the choice is remembered by hostname, and such tabs say so when they open.

The composer

Each kind of tab takes its own vendor's look, so a Claude tab feels like Claude Code and a Codex tab like Codex.

Claude tab, left to right:

Tool What it does
+ Pick files and mention them by path, as @src/a.js.
/ Slash commands, read from Claude Code. Typing / opens the same list, filtered as you type; Enter completes.
ring How full the context window is. Amber at 80%, red at 95%.
clock Minutes until the prompt cache goes cold. A message sent before then is billed at the cached rate. One hour on a subscription, five minutes on API or Bedrock, unless you set promptCacheTtl.
pill Model and effort. Opens a menu for both.
bolt Permission mode: Ask, Edits, Plan, Auto, Bypass.
square Send. While Claude is working it becomes Stop.

Codex tab: +, then the sandbox behind a shield (Read only, Workspace, Full access, the last in amber), the model with its effort, IDE context, and a round send button. Codex runs on this machine through the SDK; Codex Cloud tasks are not offered (the SDK has no way to run a thread there), so there is no "Work locally" chooser as in the Codex extension.

When a Codex turn starts sub-agents, each shows as a line in the transcript when it starts, reports, takes a follow-up task, and finishes (⤷ desktop_audio_transport started), and the line at the foot says how many are working (sub-agents: 2 working, 1 done, each by name in its tooltip). What they report is encrypted, so only that they reported can be shown. A question Codex asks without waiting shows as a card that says so: the turn carries on, and your answers go to it as your next message, queued behind the turn if it is still working. The SDK streams neither, so perch reads them from the thread's own record in ~/.codex/sessions/ as the turn runs.

IDE context, on both kinds of tab, is on by default for new tabs (perch.ideContext) and remembered per tab. The toggle is green when on and grey when off. When on, each message carries the active file and, if there is one, the selection with its line numbers, as the vendors' own panels do. The transcript shows your message with a small tag, the file's name and the lines if any, not the attachment itself. The context is read when you write the message, so a queued message keeps the file that was open then. A Perch tab in the editor area takes the focus while you type, so the file attached is the one last focused, as long as it is still open on screen; the tag on your message says which.

Earlier messages. The arrow keys walk the tab's earlier messages, as a shell does: Up from the first line of the box goes back, Down from the last line comes forward, and past the newest the draft you were writing returns.

Queueing. Enter always sends. While the agent is working, the message is queued behind the current turn and marked so. Claude Code queues it itself; Codex takes one turn at a time, so perch holds the queue and feeds it as each turn ends. Stop ends the turn and drops what is queued.

Not included: Codex's microphone. A webview cannot open the microphone; Codex does it with a companion extension that records natively. perch could do the same, but dictation also needs a speech-to-text service, which is a choice for you to make.

Model and effort

The model and effort menu opens from the pill (Claude) or the model name (Codex).

The lists come from the agents themselves, so they match your account:

Models from Efforts from Default labels
Claude the Claude Code CLI, asked at startup without sending a message each model's supported levels default · Opus 5.5 names the model the default resolves to
Codex ~/.codex/models_cache.json each model's supported levels default · GPT-5.6-Sol and default · ultra come from ~/.codex/config.toml

The effort list follows the selected model. A model with no effort control says so in the menu, and an effort the new model does not accept resets to default. Perch: Refresh Model Lists re-reads both.

Claude: model, effort, and mode all change live, from the next message. Hover the model selector to see the model actually running.

Codex: model, effort, and sandbox change from the next message too. Codex runs each turn as a process of its own, so perch takes the thread up again with the new choice. Codex itself notes in the transcript when a thread recorded with one model is carried on with another. The sandbox menu also shows the approval policy.

Defaults for new tabs: perch.claude.model, perch.claude.effort, perch.claude.permissionMode, perch.codex.model, perch.codex.reasoningEffort, perch.codex.sandboxMode.

Usage, in the footer

The footer follows the active tab.

Under a Codex tab: a backend menu (ChatGPT, API or Bedrock), your ChatGPT plan, and the plan's usage: percent remaining and time to reset for the five-hour window and the week. The menu moves Codex between your ChatGPT login, an OpenAI API key and Amazon Bedrock. Choosing the key the first time asks for it and keeps it in VS Code's secret storage, never in a file of perch's; Codex gets it in its environment, per turn, so open tabs move on their next message with nothing restarted. Bedrock uses Codex's own Bedrock provider with the AWS credentials on the machine (a profile, AWS_* variables, or AWS_BEARER_TOKEN_BEDROCK); the region and profile come from perch.codex.bedrock.region and .profile, else from the environment or the env block of ~/.claude/settings.json, where a machine set up for Claude on Bedrock has them. By default Codex goes through the Bedrock runtime's OpenAI-compatible endpoint in your region, with models named us.openai.<model> and the same bedrock:* permissions Claude uses; perch.codex.bedrock.endpoint: mantle uses Bedrock Mantle instead (us-east-1 by default, openai.<model>, its own IAM action). The ChatGPT connector apps are off on Bedrock, where they cannot work; left on, their schemas cost about 25 K tokens a request. The model menu stays Codex's; each name is sent in Bedrock's form. On the key or on Bedrock there is no plan and no limit to show, only per-token billing on that account. Perch: Set OpenAI API Key for Codex… and Perch: Forget the OpenAI API Key for Codex manage the key. Codex records these in its session files after every turn, so perch reads them from ~/.codex/sessions/ and makes no request. The figure is as of the last Codex turn on this machine, from any Codex client, and the tooltip says when that was. A window whose reset time has passed is shown as full again. perch re-reads after each Codex turn it runs.

Under a Claude tab:

  • a backend button, sub, API, or gw. Click it for a menu of three: subscription (login) and API / Bedrock are the window's choice, for every Claude tab, and switching writes env.CLAUDE_CODE_USE_BEDROCK in ~/.claude/settings.json, leaving the rest of the file alone; LLM gateway is this tab's alone (see Logins). Open tabs come along with the window's switch, tabs on the gateway excepted: each one's process ends (after its current turn, if one is running) and the next message resumes the same conversation on the new backend, as after a window reload. Moving one tab onto or off the gateway does the same for that tab. The prompt cache starts over, being the backend's. Under a tab on the gateway the button reads gw, with a warning while its file is missing or incomplete, and the gauge shows nothing: the gateway's usage is not reported here, and the line under each answer shows its tokens and which models answered.
  • a usage gauge. On a subscription: percent remaining and time to reset for the 5-hour session, the week, and any model-scoped weekly limit, amber or red when one runs low. On API / Bedrock: the model in use and today's tokens and estimated cost, from the local transcripts. Click to refresh. Hover for detail.

This is AI Meter merged into perch. The gauge lives in the footer of each tab; perch puts nothing in the status bar. AI Meter can stay installed for a gauge that is visible whatever editor is open.

Privacy is unchanged from AI Meter. Subscription mode reads the OAuth token Claude Code stores and sends it only to api.anthropic.com/api/oauth/usage. Cost mode makes no network requests. Costs are estimates at Anthropic list prices; Bedrock or partner billing may differ.

Switching models pins: a Bedrock model id saved in the settings file is set aside when you switch to subscription, which cannot use it, and put back when you switch to API. A settings file that is not valid JSON is never overwritten; perch tells you instead.

The usage endpoint rate-limits. When it does, perch keeps the last reading, marks it stale in the tooltip, and leaves the endpoint alone for at least a minute, doubling up to half an hour. Clicking refresh during that pause makes no request.

Settings: perch.meter.mode, pollMinutes, display, showModelWeekly, warnBelow, errorBelow.

Dictation

The microphone in the composer dictates into the message box. Click to start, click again to finish, Escape to discard.

Speech-to-text is Whisper, running locally. Nothing you say leaves your machines. The first use asks to set it up: about 4 GB, into a private environment under ~/.local/share/perch/voice. It needs a python3; one without venv/pip support (Debian and Ubuntu before python3-venv is installed) is fine, pip is fetched from bootstrap.pypa.io into the private environment.

Recording is done by a second extension, Perch Audio, which must be installed on the computer you sit at, because that is where the microphone is:

You work Install Perch Audio on Whisper runs on
Directly on this machine this machine (make install-audio) this machine
From a computer with an NVIDIA GPU, over Remote-SSH that computer (it comes with Perch) that computer, in Perch Audio; only the text goes to the remote
From a laptop with no GPU, over Remote-SSH the laptop the remote machine; the audio goes there

perch.voice.runOn overrides the choice (local or remote). Perch Audio carries the same engine as Perch, so a GPU under your desk is used even when the workspace is a CPU-only container far away.

If no microphone is connected, the button says so. Perch Audio: Choose Microphone picks among several.

Bluetooth ear buds and headsets work too. In their music profile (A2DP) they have no microphone at all, so Perch Audio lists them from the sound server's card list, switches them to the headset profile for the recording (mSBC, 16 kHz, when the device offers it) and back to music when it ends. Music through them pauses for the length of the dictation. A plugged-in microphone is preferred when there is one; pick the headset by name to dictate into it regardless.

The model follows the machine: large-v3-turbo on an NVIDIA GPU, small on a CPU (perch.voice.model names one instead). On a CPU the turbo model's encoder alone takes seconds; small returns a sentence in about one, at a little less accuracy. Setting perch.voice.language (en, ko, …) halves the wait again, because detecting the language is a second pass over the audio.

Verified end to end on 2026-09-30, dictating messages into a tab, on this machine (GPU) and over Remote-SSH to a CPU-only container.

A web address in an error or a note is a link. The vendor's mark at the left of the footer opens the vendor's site, claude.ai or chatgpt.com; the plan badge under a Codex tab (Plus, Pro) opens your ChatGPT usage page, where limits and credits are managed.

Cost

Both agents run on your subscriptions through their normal logins. This is for personal use on your own machine. Shipping it to other people would require API-key billing under both vendors' terms.

Packaging and publishing

make package builds perch-<version>.vsix, about 8 MB. It carries the two SDKs but not the claude and codex programs they run: those are hundreds of megabytes, and built for one platform. A packaged Perch runs the programs inside the Claude Code and ChatGPT extensions, so those two extensions must be installed where Perch is: they are in Perch's extensionPack, so the marketplace installs them with it, on the workspace side, and either can be removed if only one vendor is wanted. perch.claude.executable and perch.codex.executable name a program of your own instead. In this checkout, installed from npm, the SDKs bring their own programs and those are used.

Install the file on another machine with code --install-extension perch-<version>.vsix.

To publish to the marketplace, as AI Meter is: create the publisher named in package.json, run make login once (or keep the token in ~/.ssh/azure-dev.pat, or put VSCE_PAT=<token> in .env), then make publish. make publish-patch and make publish-minor bump the version, commit, and tag first.

Perch Audio, the microphone companion, is an extension of its own, because it has to run on the computer you sit at while Perch runs with the workspace. Perch names it in its extensionPack, so installing Perch from the marketplace installs Perch Audio too, each on the machine it belongs on. make publish publishes Perch Audio first if its version is new. Installed from a .vsix file, Perch does not fetch it: build it with make package-audio and install that file as well.

Layout

  • src/extension.js — the view, sessions, tab state, persistence, commands
  • src/claudeAgent.js — Claude Agent SDK session, no VS Code dependency
  • src/gateway.js — the gateway file: reading it, what makes it usable, the environment a tab gets; no VS Code dependency
  • src/relay.js — the loopback relay a gateway tab's process goes through, which reads which model answered; no VS Code dependency
  • src/codexAgent.js — Codex SDK thread, no VS Code dependency
  • src/models.js — model catalogs read from the agents, no VS Code dependency
  • src/meter.js — Claude usage, cost, and the backend switch, no VS Code dependency
  • src/binaries.js — where the agents' programs are: the SDK's own, or the vendor extension's
  • src/codexMeter.js — ChatGPT plan usage, read from Codex's session files
  • src/markdown.js — an answer's Markdown, drawn as DOM nodes; written into the page as source
  • src/sessionStore.js — past sessions and their names, read from and written to the agents' own records
  • src/meterHost.js — polling, the backend switch, login
  • src/voice.js, src/voiceHost.js — the speech-to-text engine, and dictation
  • voice/server.py — Whisper behind JSON lines
  • audio/ — Perch Audio, the recording companion, an extension of its own
  • src/webview.js — the page: tab bar, per-tab panes, compose box
  • test/meter.test.js — usage, cost, and the switch, against throwaway home directories
  • test/codexMeter.test.js — Codex usage, against throwaway session directories
  • test/markdown.test.js — what each construct becomes, and that nothing written becomes markup
  • test/sessionStore.test.js — listing and naming, against throwaway directories and a fake Agent SDK
  • test/models.test.js — catalog parsing, no model calls
  • test/voice.test.js — the engine, against a stand-in server
  • audio/test/recorder.test.js — the recorder, with a library that plays back frames
  • test/host.test.js — host logic against a stubbed VS Code API, no model calls
  • test/page.test.js — the real page script in a DOM, no model calls
  • test/harness.mjs — one live turn through each agent, uses your logins

make test-offline runs everything except the live harness. make test runs all of it.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft