pi-other-provider
v0.3.1
Published
Unofficial custom pi providers: OpenCode Zen/Go (protocol-routing fix) + Command Code + Factory Droid (session adapter)
Downloads
222
Maintainers
Readme
pi-other-provider
⚠️ UNOFFICIAL. This is a community package. It is not affiliated with, endorsed by, or maintained by OpenCode, Command Code, Factory, or the pi core team.
Custom model providers for pi that fix
protocol-routing bugs in the built-in opencode / opencode-go providers,
vendor the Command Code provider, and add a session-based Factory Droid
adapter — all in one package.
Why this exists
pi-ai ships built-in opencode and opencode-go providers, but several models
are routed to the wrong wire protocol, causing 400 errors:
| Model | Tier | Built-in routes to | Correct route |
| ------------ | ---- | ------------------------------- | ------------- |
| Qwen3.x | Go | /chat/completions (rejected!) | /messages |
| MiniMax M2.7 | Go | /chat/completions (rejected!) | /messages |
| MiniMax M3 | Go | /chat/completions (rejected!) | /messages |
This package registers new provider IDs (oc-zen, oc-go) — it does not
clobber the built-ins — with a data-driven routing table that pins each model to
the protocol its upstream actually accepts. It also injects 5 onPayload hooks
that patch known upstream incompatibilities, and vendors the full Command Code
provider so both model sources work from one package.
Features
oc-zen/oc-go— corrected OpenCode Zen + Go (see docs/OPENCODE-BACKEND.md)commandcode— vendored Command Code provider, hardened with upstream knowledge from OmniRoute (see docs/COMMANDCODE-BACKEND.md)factory-droid— session-based agent adapter for Factory Droid (the API is NOT OpenAI-compatible; see docs/FACTORYDROID-BACKEND.md)- Live model discovery — catalogs refresh from the upstream
/modelsendpoint through pi's own model-store cache (refreshModelshook) — new models appear without code updates - Usage-aware pricing — cache-hit (
cacheRead/cacheWrite) rates included for cost display; Command Code requests use a stablethreadIdper conversation so upstream prefix caching can hit across turns (see docs/CACHING.md) /providerscommand + on/off visibility — an interactive TUI (/providers) to turn the four providers and their individual models on/off in/model; see Model visibility below- Zero runtime dependencies —
pi-ai/pi-coding-agent/pi-tuiare optional peers
Installation
# Local development (from this directory)
npx pi install .
# Or from npm (once published)
npx pi install npm:pi-other-providerRestart/reload pi, then run /model to pick a model. Opening /model also
triggers the live catalog refresh (see Caching).
Providers
| Provider | Backend | Base URL | Models |
| --------------- | ------------- | ------------------------------- | -------------------- |
| oc-zen | OpenCode Zen | https://opencode.ai/zen/v1 | 43 static · ~64 live |
| oc-go | OpenCode Go | https://opencode.ai/zen/go/v1 | 44 static · ~48 live |
| commandcode | Command Code | https://api.commandcode.ai | live fetch (52+) |
| factory-droid | Factory Droid | https://api.factory.ai | 18 static |
Static counts are the curated baselines; the live counts are what the upstream
/models endpoints return at refresh time (they grow over time).
The factory-droid provider is different from the others: Factory Droid is
an agentic coding assistant, not a raw LLM endpoint. When pi sends a
message, the Droid runs its own full agent loop (its own tools) on a
Factory computer and returns the final text. pi's tools are ignored; each pi
turn is one Factory agent turn. See
docs/FACTORYDROID-BACKEND.md before using it.
Authentication
OpenCode (Zen + Go — one key for both)
Environment variable (recommended):
export OPENCODE_API_KEY="your-key"Auth file — entries are keyed by provider id, so add one per custom provider (or reuse the built-in
opencode-gokey by copying it):{ "oc-zen": { "type": "api_key", "key": "user_..." }, "oc-go": { "type": "api_key", "key": "user_..." } }If the key is missing (no env var, no auth entry), the provider will not appear in
/modeland pi reportsCould not refresh <provider>when the model selector tries to refresh its catalog.
Command Code
- Run
/login, select Use a subscription → Command Code (browser flow), or - Set
COMMANDCODE_API_KEY, or - Place the key in one of:
~/.commandcode/auth.json—{ "apiKey": "user_..." }~/.pi/agent/auth.json—{ "commandcode": { "type": "api_key", "key": "user_..." } }
Factory Droid
Factory Droid requires a Factory computer (a session cannot run without one). It is auto-created from a cloud template when none exists; see docs/FACTORYDROID-BACKEND.md for the full lifecycle.
API key — create one at https://app.factory.ai/settings/api-keys (keys start with
fk-), then either:export FACTORY_API_KEY="fk-your-api-key"or place it in
~/.factory/settings.jsonas{ "apiKey": "fk-..." }.Computer id (recommended) — set
FACTORY_DROID_COMPUTER_IDto an existing Factory computer for stability; otherwise the provider reuses an activee2bcomputer or auto-creates one.
Environment variables
| Variable | Provider | Purpose |
| ----------------------------------- | ------------- | ---------------------------------------------------- |
| OPENCODE_API_KEY | oc-zen/oc-go | OpenCode key (shared by both tiers) |
| COMMANDCODE_API_KEY | commandcode | Command Code key |
| COMMANDCODE_API_BASE | commandcode | Override API base (tests/mocks) |
| COMMANDCODE_MODELS_URL | commandcode | Override model catalog URL |
| COMMANDCODE_MODELS_CACHE | commandcode | Override model cache file path |
| COMMANDCODE_AUTH_TIMEOUT_MS | commandcode | Browser-login callback timeout (default 15s) |
| FACTORY_API_KEY | factory-droid | Factory API key (fk-…, required) |
| FACTORY_DROID_COMPUTER_ID | factory-droid | Reuse a specific Factory computer (recommended) |
| FACTORY_DROID_MACHINE_TEMPLATE_ID | factory-droid | Template to auto-create a computer from |
| FACTORY_DROID_BASE | factory-droid | Override API base (default https://api.factory.ai) |
| PI_OTHER_PROVIDER_CONFIG | all | Override the visibility config file path |
| PI_OTHER_PROVIDER_DISABLE | all | Comma list of provider ids to disable (* = all) |
Model visibility (on/off in /model)
You control which of the four providers registered by this package —
oc-zen, oc-go, commandcode, factory-droid — show up in pi's /model
selector (and in /login). This only affects these providers; every other pi
provider is left alone.
There are two layers. Both are optional — by default everything is visible.
1. Provider on/off
Disable a whole provider so it is not registered at all (it then disappears
from both /model and /login):
// ~/.pi/agent/pi-other-provider.json
{
"oc-go": { "enabled": false },
}Or, faster, with an env var (no file needed):
# Disable oc-go and commandcode:
export PI_OTHER_PROVIDER_DISABLE=oc-go,commandcode
# Disable all three of this package's providers:
export PI_OTHER_PROVIDER_DISABLE='*'2. Model-level filtering
Keep only some models, or hide a few, using glob patterns (* matches any
run of characters):
// ~/.pi/agent/pi-other-provider.json
{
"oc-zen": { "showOnly": ["claude*", "deepseek*"] },
"oc-go": { "hide": ["mimo-*"] },
"commandcode": { "showOnly": ["claude*", "qwen*"] },
}showOnly— if set and non-empty, only models whose id matches at least one pattern are shown.hide— models matching any pattern are hidden. Applied aftershowOnly.- Matching is case-sensitive and applies to the model id only.
*= any characters; e.g.claude*,*-pro,deepseek-*-low.
How it works
pi's own filterModels provider hook is only forwarded from pi-ai base
providers, never from extensions, so this package controls visibility at the
source: the model list it passes to registerProvider and the lists its
refreshModels hook returns (which pi persists to its model-store cache).
The live catalog is also filtered before it is persisted, so a refreshed
/model list already respects the rules.
To pick up a config change you can either restart pi or open /model again
(the refresh hook re-reads the config file each time).
/providers slash command (interactive TUI)
Instead of editing the JSON file by hand, run the built-in slash command inside pi:
/providersIt opens an interactive panel:
- Main menu — pick a provider to toggle its models, or "Enable/disable providers", or "Done".
- Per-provider model panel — a multi-toggle list of every model
(
on= visible in/model,off= hidden). Toggle with the keyboard; each change is saved immediately. Jump straight in with/providers oc-zen. - Provider on/off panel — enable/disable whole providers
(
/providers onoff).
Effects:
- Model on/off → rewrites the
hidelist → reopen/modelto apply. - Provider on/off → rewrites
enabled→ restart pi to apply (providers are registered at load time).
The rich SettingsList UI is used in TUI mode; in RPC mode (or if pi-tui is
unavailable) it automatically falls back to ctx.ui.select dialogs, so the
command always works. The command only affects these three providers.
How the routing fix works
Each OpenCode model is pinned to one of four wire protocols:
| Protocol | Endpoint path | pi-ai streamer |
| ---------------------- | ------------------- | ------------------------------- |
| openai-responses | /responses | streamSimpleOpenAIResponses |
| anthropic-messages | /messages | streamSimpleAnthropic |
| openai-completions | /chat/completions | streamSimpleOpenAICompletions |
| google-generative-ai | /models/{id} | streamSimpleGoogle |
The custom streamSimple (src/backends/opencode/stream.ts) re-derives the
protocol from the catalog, overrides the model's api + baseUrl, injects the
gotcha hooks, and delegates to the matching pi-ai built-in streamer — so
streaming, abort, usage parsing and prompt-cache hints (cache_control)
keep working exactly like the built-in providers.
How pi actually reaches this
streamSimple: every registered model carriesapi: "opencode-custom"(theOPENCODE_CUSTOM_APImarker), matching the provider'sapi. pi's stream router only invokes an extension'sstreamSimplewhenmodel.api === provider.api, so the marker is what makes the gotcha hooks run —stream.tsthen re-derives the real protocol from the catalog. Settingmodel.apito the real protocol instead would silently bypass every gotcha (a previous regression, now guarded bytests/test-stream.ts).
Gotcha hooks (onPayload)
Five transforms applied to every OpenCode request, ported from OmniRoute's
OpencodeExecutor:
stripClientMetadata— removesclient_metadata(upstream 400 "Extra inputs not permitted")limitTools— capstoolsat 128rewriteEffortTier— expands effort aliases (deepseek-v4-pro-low→ base +reasoning_effort: low)stripBooleanReasoning— removes booleanreasoningfields some upstreams rejectinjectReasoningContent— echoesreasoning_contenton assistant messages for thinking models (DeepSeek, Kimi, MiniMax, MiMo, big-pickle) — OpenAI-completions transport only
Command Code hardening
The vendored backend was aligned with OmniRoute's commandCode executor:
x-cli-environment: "external",x-taste-learning: "false"(privacy opt-out)max_tokensis not fabricated — only forwarded when pi sets it, clamped to the 200k endpoint ceiling (avoids the 400 "Too big" rejection)- Passthrough of
reasoning_effort/reasoning/thinking/effort/output_config/extra_body(payload-rule overrides are not dropped) memory/taste/skillssent as""(notnull), pluspermissionMode: "standard"- Stable
threadIdper pi conversation (fromoptions.sessionId) so upstream prompt caching can hit across turns
Caching
Two distinct cache concepts, both supported (details: docs/CACHING.md):
- Model catalog cache — pi's
models-store.jsonpersists the live model list; refreshed when you open/model, restored on offline startups. - Usage cache hits — provider-side prompt caching while chatting:
- OpenCode: pi-ai streamers send
cache_control/ prompt-cache keys automatically; pricing tables includecacheRead/cacheWriterates. - Command Code: stable
threadIdper conversation enables cross-turn prefix caching;cacheRead/cacheWritetokens are parsed from responses and costed at their discounted rates.
- OpenCode: pi-ai streamers send
Development
npm test # typecheck + full unit suite (mocked HTTP, no real calls)
npm run typecheck # tsc --noEmitSee docs/DEVELOPMENT.md for conventions and gotchas
(e.g. never import @earendil-works/pi-ai/<subpath> — pi loads extensions via
tsx and ESM-only subpath exports mis-resolve).
Documentation
- docs/ARCHITECTURE.md — module layout & data flow
- docs/OPENCODE-BACKEND.md — oc-zen / oc-go details
- docs/COMMANDCODE-BACKEND.md — commandcode details
- docs/FACTORYDROID-BACKEND.md — factory-droid session adapter
- docs/CACHING.md — catalog cache & usage cache hits
- docs/COMPARISON.md — vs pi-ai built-ins & OmniRoute
- docs/DEVELOPMENT.md — contributing / release
- docs/COMMANDCODE-ANALYSIS.md — deep analysis of
the upstream
pi-commandcode-providerthis package vendors
Update / remove
npx pi update --extensions # update installed packages
npx pi remove pi-other-providerLicense
MIT
