Pi Coding Agent extension for 9router: register 9router as a Pi provider, discover models dynamically, enrich model metadata, and manage configuration from Pi with /9router.
Repository: https://github.com/ricatix/pi-9router npm package: https://www.npmjs.com/package/pi-9router
9router exposes an OpenAI-compatible API in front of many upstream models. This extension makes Pi see 9router as a native provider:
Pi Coding Agent ── OpenAI-compatible API ── 9router ── upstream models
You get one provider in Pi, backed by whatever models/routes your 9router instance exposes.
- Pi provider registration — registers
9routerwith Pi throughregisterProvider. - Dynamic model discovery — fetches available models from
GET /v1/models. - OpenAI-compatible chat — uses Pi's
openai-completionsprovider mode. - Model metadata enrichment — supplements discovered models with
models.devmetadata when available. - Local model cache — starts from cache when possible, then refreshes in background.
- Graceful startup — Pi remains usable even when 9router is not configured yet.
- Dual env var support — accepts both
NINE_ROUTER_*and9ROUTER_*names. - Interactive setup — configure from Pi using
/9router setup. - Slash command management — status, refresh, model listing, reasoning toggle, config clear.
- Secret-safe output — API keys are not printed in status/error output.
Web search/fetch tools are intentionally not registered yet. The extension only exposes ninerouter_status until a stable 9router web-route contract is available.
- Pi Coding Agent
- Bun for local development/build
- A 9router API key and reachable 9router base URL
Default base URL:
https://9router.com/v1
pi install npm:pi-9routerIf your Pi version uses ~/.pi/agent/settings.json package entries, add:
{
"packages": ["npm:pi-9router"]
}pi install git:github.com/ricatix/pi-9routerOr in ~/.pi/agent/settings.json:
{
"packages": ["git:github.com/ricatix/pi-9router"]
}git clone https://github.com/ricatix/pi-9router.git
cd pi-9router
bun install
bun run buildThen add local path to Pi settings:
{
"packages": ["/absolute/path/to/pi-9router"]
}Example:
{
"packages": ["/Users/ricoaditya/Project/open-source-project/pi-9router"]
}Restart Pi after changing packages.
Note: Pi's npm/git package loader discovers extensions through the
pi.extensionsmanifest inpackage.json. This package declarespi.extensions: ["./dist/index.js"], so no extra wiring is required — Pi will import the built entry-point on startup. Local-path installs also work, but rely on a different fallback path in the host.
You can configure 9router by environment variables or by /9router setup.
Recommended variable names:
export NINE_ROUTER_API_KEY="your-api-key"
export NINE_ROUTER_BASE_URL="https://9router.com/v1"Also supported:
export 9ROUTER_API_KEY="your-api-key"
export 9ROUTER_BASE_URL="https://9router.com/v1"NINE_ROUTER_* is safer in shells because variable names beginning with digits can be awkward or invalid in some environments.
Persistent config:
~/.pi/agent/9router-config.json
Model cache:
~/.pi/agent/9router-models-cache.json
Files containing secrets are written with restricted permissions when possible.
NINE_ROUTER_BASE_URL can be provided with or without /v1:
https://9router.com
https://9router.com/v1
Both normalize to:
https://9router.com/v1
Protocol is required. Use https://... or http://....
Start Pi after installing/configuring the extension:
piRun status:
/9router status
Refresh discovered models:
/9router refresh
List cached models:
/9router models
Open interactive setup:
/9router setup
After discovery succeeds, select a 9router model from Pi's model picker. Models are registered under provider 9router.
| Command | Description |
|---|---|
/9router |
Opens interactive setup when no sub-command is provided. |
/9router setup |
Prompts for base URL and API key in Pi TUI. |
/9router status |
Shows base URL, whether API key is set, cache count, and visible registry count when available. |
/9router refresh |
Fetches latest models from 9router, enriches metadata, updates cache, and re-registers provider. |
/9router reload |
Alias for refresh. |
/9router models |
Prints cached model IDs. |
/9router reasoning on |
Stores reasoning toggle as enabled. |
/9router reasoning off |
Stores reasoning toggle as disabled. |
/9router clear |
Clears local config and model cache. |
/9router debug |
Alias for status. |
/9router config |
Alias for setup. |
Note: reasoning config is persisted for future compatibility. Current provider model registration keeps reasoning disabled unless future 9router/Pi contracts expose a stable thinking mapping.
This extension registers one Pi tool:
| Tool | Description |
|---|---|
ninerouter_status |
Returns base URL, API-key presence, and cache count. |
Web search/fetch tools are not exposed in this release.
- Read env vars and saved config.
- Normalize base URL.
- Register cached provider immediately if model cache exists.
- Refresh models in background when cache exists.
- On first run with no cache, wait for discovery so Pi can list models.
- If discovery fails but cache exists, keep cached models registered.
- If no API key exists, Pi still loads and
/9router setupremains available.
Pi's package loader only auto-loads an extension entry-point if the package
declares it through the pi.extensions field in package.json. This package
ships with:
{
"pi": {
"extensions": ["./dist/index.js"]
}
}If /9router is missing after pi install npm:pi-9router:
- Verify the installed
package.jsonhas thepi.extensionsfield:cat ~/.pi/agent/npm/node_modules/pi-9router/package.json | grep -A2 '"pi"'
- If absent, your installed version is older than
0.1.2. Reinstall:pi install npm:pi-9router
- Restart Pi (or run
/reload) so the new manifest is picked up. - Confirm the package is loaded:
pi list
npm:pi-9routershould appear under User packages.
Local-path installs (local:/path/to/pi-9router) hide this issue because the
host falls back to importing dist/index.js even without the manifest — but
the manifest is still required for npm: and git: sources.
Set API key using env vars or run:
/9router setup
Run:
/9router status
/9router refresh
Check:
NINE_ROUTER_API_KEYis set.NINE_ROUTER_BASE_URLpoints to a reachable 9router endpoint.GET <baseUrl>/modelsreturns model data.
For default hosted 9router:
curl -H "Authorization: Bearer $NINE_ROUTER_API_KEY" https://9router.com/v1/modelsCommon causes:
- Invalid API key.
- Base URL missing protocol.
- Network/TLS issue.
- 9router instance returned no models.
The extension redacts long token-like strings and your literal API key from errors.
/9router setup needs Pi TUI context. In non-interactive/headless mode, use env vars or edit:
~/.pi/agent/9router-config.json
Use NINE_ROUTER_API_KEY instead of 9ROUTER_API_KEY.
git clone https://github.com/ricatix/pi-9router.git
cd pi-9router
bun install
bun run typecheck
bun test
bun run buildpi-9router/
├── src/
│ ├── index.ts # Pi extension entrypoint
│ ├── auth.ts # env auth and base URL normalization
│ ├── discovery.ts # 9router /models discovery
│ ├── enrichment.ts # models.dev metadata enrichment
│ ├── validation.ts # credential validation helper
│ ├── models.ts # Pi/OpenCode model mapping helpers
│ ├── state.ts # config/cache persistence
│ ├── commands.ts # /9router command router
│ ├── web.ts # reserved web helper module
│ ├── errors.ts # custom errors
│ ├── util.ts # hashing/redaction utilities
│ └── types.ts # local shared types
├── tests/
│ └── core.test.ts
├── package.json
├── tsconfig.json
└── README.md
Run before pushing or publishing:
bun run typecheck
bun test
bun run buildBefore publishing:
- Verify package metadata in
package.json. - Run validation commands.
- Confirm
dist/is fresh. - Confirm README install commands point to the intended npm package.
- Confirm no secrets in config, cache, shell history, or test fixtures.
- Pick the next version (semver):
MAJOR.MINOR.PATCHfor stable,MAJOR.MINOR.PATCH-rc.Nfor pre-release. - Push the tag — the workflow handles the rest.
Stable release:
git tag v0.2.0
git push origin v0.2.0
# workflow runs typecheck + test + build + npm publish --provenancePre-release (lands under the next dist-tag, leaves latest untouched):
git tag v0.2.0-rc.1
git push origin v0.2.0-rc.1Manual one-off publish (requires npm login + 2FA OTP, no provenance):
bun install
bun run typecheck
bun test
bun run build
npm publish --otp=<code>Install published package:
pi install npm:pi-9router
# or pin a pre-release
pi install npm:pi-9router@nextThe .github/workflows/publish.yml workflow runs on every v*.*.* tag push and:
- Installs Bun + Node 24 + latest
npm(npm 11.5.1+ is required for OIDC). - Runs
bun install --frozen-lockfile,bun run typecheck,bun test,bun run build. - Syncs
package.json#versionto the pushed tag. - Publishes to npm with
--provenance. Pre-release tags are published under thenextdist-tag.
Trusted publishing via OIDC requires npm 11.5.1+ to perform the token exchange with npmjs.com. Node 22 ships with npm 10.x, which silently fails the OIDC handshake and surfaces a misleading E404 'package@version' is not in this registry error. Node 24 LTS ships with npm 11.x and works out of the box.
When you set registry-url on actions/setup-node@v4, the action writes a //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN} line to .npmrc. With no NODE_AUTH_TOKEN secret configured (the intended state for OIDC), the placeholder expands to an empty string. npm then treats that as "auth is configured" and short-circuits the OIDC token exchange. The workflow sets the registry via npm config set instead.
After the first manual publish, configure the trusted publisher at:
https://www.npmjs.com/package/pi-9router/access
with:
| Field | Value |
|---|---|
| Repository owner / org | ricatix |
| Repository name | pi-9router |
| Workflow filename | publish.yml |
| Environment name | (leave empty) |
After that, every push of a v*.*.* tag is published automatically with no NPM_TOKEN secret.
- API keys are sent only to the configured 9router base URL through
Authorization: Bearer .... - Status output shows only
setorunset, never key values. - Config/cache files are written under
~/.pi/agent/. - Config files are restricted to owner read/write where the platform allows it.
- Model cache contains model metadata, not credentials.
- Do not commit local
~/.pi/agent/9router-config.jsonor API keys.
Report security issues privately through GitHub once repository security advisories are enabled:
https://github.com/ricatix/pi-9router/security
- Stable web search/fetch tools after 9router web-route contract is finalized.
- Full reasoning/thinking mapping when Pi and 9router expose a stable compatible shape.
- More integration tests against a live Pi extension runtime.