Skip to main content

Module plugin

Module plugin 

Source
Expand description

Plugin artifacts — the files that must be installed into a harness before its traffic can be captured at all.

Most harnesses need nothing here: capture works by pointing the harness’s base-URL knob at a proxy, which crate::launch plans. A harness with no such knob needs code running inside it instead, and that code is an asset somebody has to write to disk. This module owns those assets, and — because how many copies of an asset end up in a harness’s auto-discovery directory is a correctness property, not a packaging detail — it owns the install too, through PluginArtifact::install.

§Why the assets live here

An in-harness extension is harness knowledge in the most literal sense — it is written against the harness’s own extension API. Keeping it in a consumer’s repository meant every consumer that wanted to capture that harness had to carry its own copy, and the copies would drift in exactly the way this crate exists to prevent. The asset moved here so tapesctl plugin install and a closed-source client install the same bytes.

§What still must not be vendored

The move is conditional on the asset being vendor-neutral, which was not free: the extension that seeded PI_GATEWAY_EXTENSION read a product’s environment variables, defaulted to that product’s daemon port, and told the user to run that product’s CLI. All three are gone. What a crate-owned asset may know is that a capture proxy exists and how to talk to one; where that proxy is, what it is called, and how a user manages it stay with whoever installs the asset.

Concretely, an asset here may not carry a vendor’s name, a vendor’s default endpoint, or a vendor’s environment-variable spelling — it reads GATEWAY_URL_ENV and nothing else. A consumer whose plugin genuinely cannot be de-branded keeps that plugin in its own repository; it does not get a variant of crate::harness::PluginDelivery here.

§…and what a consumer may still choose

De-branding is not the same as having nothing to say. A consumer’s status label and the command it tells a user to run are legitimately its own, and a consumer that had to fork a whole asset to express them would be back where this module started.

An asset resolves that at runtime, not by being rendered: the launching consumer sets those strings in the environment of the launch it owns, and the asset reads them (see pi for pi’s three). Rendering per consumer is the thing this module now refuses for a file-copy artifact, and for a structural reason — a rendered asset is one file per product, and a harness that auto-loads every file in a directory then loads two of them into one process, where they contend over the launch nonce and over the provider registrations and silently unattribute both products’ sessions. One artifact, one path, identical bytes is what makes that second reader impossible rather than merely coordinated.

codex_app is still rendered, and can be: its manifests are installed by the harness’s own plugin manager into a per-consumer plugin, not copied into a directory something globs.

§The environment contract

An installed artifact is inert until the launching consumer sets GATEWAY_URL_ENV. That is deliberate: an artifact installs globally into the harness’s own extension directory, so it loads for every session on the machine — including sessions nobody is capturing. Making the redirect conditional on the environment is what keeps an install from changing the behaviour of sessions the user did not launch under capture.

The names are shared across consumers, and that is safe for exactly the reason above: one installed artifact means one reader per harness, so there is nothing to collide with. Per-consumer variable names buy nothing once the second copy is gone, and cost a launcher that can set a variable its installed asset does not read.

§All seven variables

The contract is split across two crates, and this is the only place that lists it whole — a consumer wiring up a launch needs every row. The split is the crate boundary doing its job: the first four are protocol, true of any capture proxy and so owned by tapes_capture::gateway; the last three are presentation, which is a product’s own and so lives with the artifact that reads them.

variableconstset bymeaning
TAPES_GATEWAY_URLGATEWAY_URL_ENVlauncherWhere to send the harness’s LLM traffic. Unset means “not captured” — the artifact leaves the harness’s own endpoints alone, which is what keeps a global install from touching sessions nobody launched under capture.
TAPES_GATEWAY_SCHEMAtapes_capture::gateway::GATEWAY_SCHEMA_ENVlauncherWhich upstream schema the proxy fronts (anthropic, openai). A display and diagnostic hint; an artifact must not gate the redirect on it.
TAPES_GATEWAY_NONCEGATEWAY_NONCE_ENVlauncherThe per-launch secret. Read once at load and deleted from the process environment immediately, so the harness’s own subprocesses never receive it.
TAPES_GATEWAY_PROVIDER_ROUTEStapes_capture::gateway::GATEWAY_PROVIDER_ROUTES_ENVlauncherSet to 1 when the proxy serves each provider on its own route. Unset is the single-upstream shape, which is what a launcher predating this variable gets.
TAPES_GATEWAY_LABELpi::GATEWAY_LABEL_ENVlauncherThe product word shown in pi’s status entry.
TAPES_GATEWAY_LABEL_SUFFIXpi::GATEWAY_LABEL_SUFFIX_ENVlauncherAppended to the status label after the active schema.
TAPES_GATEWAY_REMEDYpi::GATEWAY_REMEDY_ENVlauncherThe sentence appended to a schema-mismatch warning — the diagnosis is the asset’s, the remedy is the launcher’s, because only it knows which command switches its proxy.

Every one of the seven is optional, and every one has a defined unset behaviour. That is not politeness: an artifact installs globally and loads for every session on the machine, so “nothing set” has to be a working configuration rather than an error.

One request the harness makes on its own behalf goes back the other way: tapes_capture::gateway::GATEWAY_NONCE_HEADER is the request header in which the artifact echoes the nonce, and tapes_capture::gateway::nonce_matches is what the proxy compares it with.

Modules§

codex_app
The Codex desktop app’s hook-plugin manifests, as templates.
pi
pi’s capture extension: one installed file, branded at runtime.

Structs§

PluginArtifact
One file a consumer installs into a harness.

Constants§

GATEWAY_NONCE_ENV
Environment variable carrying the per-launch capture nonce.
GATEWAY_NONCE_HEADER
Request header in which an installed plugin echoes the capture nonce back to the proxy that launched it.
GATEWAY_PROVIDER_ROUTES_ENV
Environment variable declaring that the capture proxy serves each captured provider on its own route, so an installed plugin must say which provider a request belongs to.
GATEWAY_PROVIDER_ROUTES_ON
The value a client sets in GATEWAY_PROVIDER_ROUTES_ENV to ask for per-provider routes.
GATEWAY_PROVIDER_ROUTE_PREFIX
Path prefix under which a labelled request names its provider.
GATEWAY_SCHEMA_ENV
Environment variable naming which upstream provider schema the capture proxy is currently fronting (e.g. anthropic, openai).
GATEWAY_URL_ENV
Environment variable naming the capture-proxy base URL an installed plugin should send the harness’s LLM traffic to.
OPENCODE_GATEWAY_EXTENSION
opencode’s capture plugin.
PI_GATEWAY_EXTENSION
pi’s capture extension.

Functions§

nonce_matches
Does a presented nonce match the one this capture generated?
provider_route
The path a request for provider is sent to when per-provider routes are on: GATEWAY_PROVIDER_ROUTE_PREFIX, the provider name, then whatever path the harness’s client would have used on its own.
split_provider_route
Split a labelled request path into the provider it names and the path the harness’s client actually asked for.