Skip to content

Add Browser REPL env API and models namespace - #428

Open
rgarcia wants to merge 3 commits into
mainfrom
hypeship/repl-env-and-models
Open

rgarcia wants to merge 3 commits into
mainfrom
hypeship/repl-env-and-models

Conversation

@rgarcia

@rgarcia rgarcia commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds two Browser REPL features:

  • PUT/GET/DELETE /repl/env: set environment variables for the REPL process, such as model provider API keys, without putting them in REPL code.
  • A frozen models namespace in the default REPL scope: the model catalog plus classifier models (TypeSafe's Jev and others), adapted from the models object in pi's codemode.

/repl/env

  • PUT replaces the full set of variables. GET returns names only; values are never returned. DELETE clears everything.
  • Applied live: a running REPL gets the change in process.env through a new daemon message and keeps its state and custom tools.
    • If the REPL can't receive the change, it is terminated and the response sets repl_terminated: true.
  • Survives restarts: every REPL process started later (after a reset, timeout, or crash) starts with the stored variables.
    • Removing a variable restores the API process's own value, if it has one.
  • In memory only: values aren't recorded in telemetry. api_call events carry operation, status, and duration only.
  • Cleared on fork: values are dropped when a forked instance applies its fork identity, so forks don't inherit the source instance's keys. The running REPL drops them before any later execution or custom WebMCP code runs. The fork hook moved below api.New so it can reach the service.
  • Validation:
    • Names must match ^[A-Za-z_][A-Za-z0-9_]*$ and be at most 256 characters.
    • Reserved: names starting with BROWSER_REPL_, plus CDP_ENDPOINT, KERNEL_API_ENDPOINT, PORT, and NODE_OPTIONS.
    • At most 100 variables; each value at most 32 KiB with no NUL bytes; 1 MiB body.
    • Unknown top-level fields are rejected through StrictBrowserReplBodyMiddleware.

models

models.getModelsOfType, models.getAvailableOfType, models.getModelOfType, and models.classify match pi's codemode models globals:

  • same signatures and argument checks;
  • classify resolves the model by provider/id only, so a script-supplied baseUrl or headers never receives credentials;
  • headers are stripped from catalog entries;
  • at most four classifications run at once.

It is backed by @earendil-works/pi-ai@0.99.2. builtinModels() loads lazily on the first models.* call, so REPL startup is unchanged. The package is marked external in the esbuild bundle and resolved from the REPL's node_modules.

  • Credentials come from process.env at call time, which is what /repl/env sets.
  • Calls are tied to the active execution's abort signal, like webmcp.
  • Not carried over from pi: nested-call rows and session cost accounting. Each result still includes usage.

Attribution: runtime/models.ts is adapted from packages/coding-agent/src/extensions/codemode/execute.ts at pi v0.99.2. runtime/models.test.ts ports pi's codemode models tests. Both files carry pi's MIT copyright and license notice. The repl.help descriptions for models.* follow pi's declarations, and the docs link to pi.

Image size

@earendil-works/pi-ai brings its provider SDKs as regular dependencies. The REPL's node_modules grows from 89 MB to 201 MB uncompressed in the headless image.

Testing

  • make test-unit (go vet + go test -race, excluding e2e): passes.
    • New REPL-backed tests cover:
      • validation;
      • applying env before the REPL starts;
      • live application that keeps REPL state;
      • persistence across reset;
      • restoring the inherited value;
      • clearing on fork, including a REPL that runs code before the background cleanup;
      • termination when the REPL is unreachable;
      • the models namespace (frozen, catalog, availability following env, errors, help).
    • In 1 of about 6 local full-package runs, one test in cmd/api/api failed. I didn't capture which one. The new tests passed 15 consecutive -race runs.
  • make test-runtime (typecheck, repl-help-check, node --test): 37 passing, including the ported pi tests.
  • Built the headless image locally and ran TestBrowserReplAPI/Headless against it. All subtests pass, including the new env reaches models case. The image runs Node 22.23.2.
  • In the same image, set OPENROUTER_API_KEY through PUT /repl/env, then ran a real models.classify call against openrouter/typesafe/jev-1.13: stopReason: "stop" with the expected answers.
  • I didn't build the headful image locally. CI's test-server-e2e, which runs the REPL e2e suite on both images, passes.
  • On the last commit, test-server-e2e first failed in lib/cdpmonitor TestProxyErrorE2E (use of closed network connection, before the server e2e step). This PR doesn't touch that package. The rerun passed.

@socket-security

socket-security Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​earendil-works/​pi-ai@​0.99.210010010098100

View full report

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b19aca3. Configure here.

Comment thread server/cmd/api/api/browser_repl_env.go

@hiroTamada hiroTamada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed — the env lifecycle and credential re-resolution look sound. a few things worth fixing:

bugs

  • server/runtime/models.ts:65–71 — the limiter frees a slot before a queued caller resumes. a new call can take that slot, then the queued call starts too, exceeding the four-call cap. consider reserving the slot before waking the waiter and testing that interleaving.
  • server/cmd/api/api/browser_repl_env.go:234,242 — PUT/DELETE read response names after releasing admission. a concurrent update can make a successful response list the other request’s variables. consider returning a snapshot captured while admission is held.

contract

  • server/openapi.yaml:7603 — maxLength counts characters, but Go enforces 32 KiB in bytes (server/cmd/api/api/browser_repl_env.go:48). multibyte values can pass schema validation and still get a 400; consider aligning or clarifying the constraint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants