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.
| variable | const | set by | meaning |
|---|---|---|---|
TAPES_GATEWAY_URL | GATEWAY_URL_ENV | launcher | Where 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_SCHEMA | tapes_capture::gateway::GATEWAY_SCHEMA_ENV | launcher | Which upstream schema the proxy fronts (anthropic, openai). A display and diagnostic hint; an artifact must not gate the redirect on it. |
TAPES_GATEWAY_NONCE | GATEWAY_NONCE_ENV | launcher | The 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_ROUTES | tapes_capture::gateway::GATEWAY_PROVIDER_ROUTES_ENV | launcher | Set 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_LABEL | pi::GATEWAY_LABEL_ENV | launcher | The product word shown in pi’s status entry. |
TAPES_GATEWAY_LABEL_SUFFIX | pi::GATEWAY_LABEL_SUFFIX_ENV | launcher | Appended to the status label after the active schema. |
TAPES_GATEWAY_REMEDY | pi::GATEWAY_REMEDY_ENV | launcher | The 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§
- Plugin
Artifact - 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_ENVto 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
provideris 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.