opencode-proxies
v1.0.0
Published
OpenCode v2 plugin for routing model HTTP requests through configurable forward proxies.
Maintainers
Readme
opencode-proxies
Route OpenCode v2 model HTTP requests through configurable forward proxies, including Cloudflare WARP's local HTTP proxy. Give each destination or provider its own proxy using plugin options.
Features
- Multiple ordered routes, each with a proxy URL and destination URL prefixes.
- Optional provider filters and subdomain matching.
- HTTP and HTTPS forward proxies, including proxy URL authentication; HTTPS destinations use CONNECT tunnels.
- Preserves methods, paths, query strings, request bodies, provider credentials, response status, and streaming responses.
- Redirects stay on the selected proxy; cross-origin redirects strip authorization and cookies.
- Unmatched requests retain OpenCode's normal transport. Proxy failures return an error without a direct fallback.
- No WARP-specific environment variables or hard-coded destinations.
Package 1.0.0 targets OpenCode v2, depends on @opencode/[email protected], and uses the stable plugin ID opencode-proxies. Use Node.js 24+ or a compatible Bun runtime (verification runs on both).
Install and configure
After publication, add this entry to ~/.config/opencode/opencode.jsonc or your project's opencode.json. Append it to your existing plugins array if you have other plugins.
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "[email protected]",
"options": {
"enabled": true,
"routes": [
{
"proxyUrl": "http://127.0.0.1:40000",
"urls": ["https://opencode.ai"],
"includeSubdomains": true,
"providerIds": ["opencode", "opencode-go"]
}
]
}
}
]
}This reproduces the routing of zen-warp.ts: Zen and Go requests to opencode.ai and its subdomains go through WARP's local HTTP proxy. WARP must already be running in proxy mode on port 40000; the plugin does not launch or configure it.
OpenCode installs published npm plugins automatically. Quit and restart OpenCode after installing or changing options. Restart its persistent background server too, when applicable.
Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| enabled | boolean | true | false disables routing without starting a relay or registering hooks. |
| routes | array | [] | Ordered routes. The first matching route wins. |
| routes[].proxyUrl | string | required | Absolute HTTP/HTTPS forward proxy URL, optionally with username/password. Paths, queries, and fragments are not supported. |
| routes[].urls | string[] | required | Non-empty list of absolute HTTP/HTTPS destination prefixes. Credentials, queries, and fragments are not supported in these matchers. |
| routes[].includeSubdomains | boolean | false | Also match subdomains of each destination hostname. |
| routes[].providerIds | string[] | any provider | Optional non-empty list of exact provider IDs. |
Options are read from ctx.options and captured during setup. Invalid options fail setup with a field-specific error that excludes supplied values. An explicitly disabled plugin ignores route configuration. No routes means no listener and no hooks.
TypeScript consumers can import ProxiesOptions and ProxyRoute from opencode-proxies.
URL matching
Matching compares parsed scheme, hostname, port, and path. A prefix of https://api.example.com/v1 matches /v1, /v1/, and /v1/chat?stream=true; it does not match /v10, HTTP, another port, or api.example.com.evil.test. A root URL matches every path on that origin. Trailing slashes on a configured prefix are ignored. Hostname case and default ports are normalized by the URL parser; paths remain case-sensitive and percent-encoded. Query strings on actual requests do not affect matching and are forwarded intact.
includeSubdomains: true matches the exact hostname and dot-separated subdomains, with the same scheme and port. URL prefixes are literal, not glob patterns. Provider and URL conditions must both match.
Multiple proxies and environment substitution
Put specific routes before broader routes:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "[email protected]",
"options": {
"routes": [
{
"proxyUrl": "{env:CORPORATE_PROXY_URL}",
"urls": ["https://api.example.com/v1/private"],
"providerIds": ["acme"]
},
{
"proxyUrl": "http://127.0.0.1:40000",
"urls": ["https://api.example.com/v1", "https://opencode.ai"]
}
]
}
}
]
}OpenCode resolves {env:...} before passing options to the plugin. Set variables in the environment of the OpenCode server. An unset URL variable resolves to an empty string and causes validation to fail. An authenticated proxy URL can look like http://username:[email protected]:8080; percent-encode reserved characters in credentials. Diagnostics do not print URLs or credentials.
How the v2 relay works
V2's public http.request hook can replace a standard Request, but cannot set a forward proxy or inject a fetch implementation. The plugin starts an in-process HTTP relay on 127.0.0.1 with an ephemeral port and a random access token, then rewrites matching requests to that relay. Undici's ProxyAgent forwards them through the configured proxy. This uses the public plugin API without patching global fetch or OpenCode internals.
The relay forwards raw response bytes and removes hop-by-hop headers. Model response streams are not buffered. OpenCode's own HTTP-hook adapter may buffer request bodies. Redirect locations are rewritten through the relay so automatic redirects cannot bypass the selected proxy, including when the destination no longer matches the original route. Cross-origin redirects permanently strip authorization and cookie headers for that redirect chain. OpenCode's HTTP client controls redirect limits and method changes.
Client cancellation aborts the upstream request. Unloading disposes registrations, aborts active requests, destroys proxy connection pools, and closes the relay. A proxy failure before response headers produces a redacted HTTP 502; a failure midstream terminates that stream. OpenCode retains control of retries and model timeouts.
Scope
- Covers native model HTTP requests issued by sessions: primary, title, compaction, and session-generation requests.
- Does not proxy tool traffic, MCP, package installation, authentication flows, or standalone
ctx.generate.text()calls outside the session HTTP hook. - OpenCode disables WebSocket optimization for providers with an HTTP hook. If every route has provider filters, this plugin scopes its registrations to those providers. Any unfiltered route requires a global HTTP hook.
- Proxy endpoints must support HTTP CONNECT. SOCKS proxy URLs and API base-URL rewriting are not supported.
- Requests that do not match retain any proxy behavior already configured by OpenCode or the environment.
Migrate from zen-warp.ts
- Upgrade OpenCode to v2.
- Add the package entry and WARP options shown above.
- Move
~/.config/opencode/plugin/zen-warp.tsoutside the auto-discoveredplugin/andplugins/directories, and remove an explicit old entry if present. - Replace
ZEN_WARP_PROXYwithproxyUrl(or"{env:ZEN_WARP_PROXY}"if keeping that variable). Replace itsoff/disabledvalues withenabled: false. - Quit and restart OpenCode and its background server. Confirm
opencode-proxiesand its source in the active plugin list, then exercise a Zen or Go request with WARP connected.
The old @opencode-ai/plugin config-hook implementation is not compatible with v2. Native v2 config uses plugins and { package, options } objects. At implementation time, the shared https://opencode.ai/config.json still described the v1 plugin field; the v2 documentation and released API define the format used here.
Development and verification
Requires Bun, Node.js 24+, npm, and OpenSSL for the temporary HTTPS test certificate.
bun install
bun run check:ci
bun run typecheck
bun run knip
bun run test:coverage
bun run build
bun run test:packageUnit tests cover option validation, matching, provider scoping, request kinds, and registration rollback/cleanup. test:package packs and installs the actual release into an isolated consumer. It resolves and loads the plugin through @opencode/plugin/host, then runs real HTTP and HTTPS CONNECT tests in both Node and Bun: route precedence, proxy authentication, forwarding, redirects, compression, SSE streaming, errors, cancellation, and unload. It also verifies the exact tarball contents and installed public declarations. All traffic stays on local test servers; HTTPS uses a generated trusted test certificate rather than disabling TLS verification.
For local OpenCode testing, run bun run build and set the plugin entry's package to /absolute/path/to/opencode-proxies/dist, the directory containing index.js. Configure routes, restart OpenCode and its background server, and confirm the plugin's ID/source before sending a model request.
Publishing
The npm tarball contains only dist/index.js, index.d.ts, package.json, README.md, and LICENSE. prepack rebuilds JavaScript before packing or publishing.
First release
Run the verification commands above, then:
npm login
npm publish --access publicSubsequent releases
Configure an npm trusted publisher in the package settings:
- Provider: GitHub Actions
- Organization/user: ysm-dev
- Repository: opencode-proxies
- Workflow filename: publish.yml
- Environment: leave blank
Bump package.json's version and update the pinned installation examples. Push release changes to main. The workflow verifies the package, publishes unpublished versions with npm provenance through OIDC, and creates a GitHub release. It also supports manual dispatch and skips versions already on npm. No NPM_TOKEN is needed. Complete the first manual publish and trusted-publisher setup before relying on automatic publication.
References and license
- OpenCode v2 migration
- Plugin migration
- Plugin API
- Package structure follows opencode-discord-noti.
MIT — see LICENSE.
