pi-other-provider

Unofficial custom pi providers: OpenCode Zen/Go (protocol-routing fix) + Command Code + Factory Droid (session adapter)

Packages

Package details

extension

Install pi-other-provider from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-other-provider
Package
pi-other-provider
Version
0.3.1
Published
Aug 21, 2026
Downloads
270/mo · 30/wk
Author
bom0792
License
MIT
Types
extension
Size
279.6 KB
Dependencies
0 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-other-provider

pi-extension

⚠️ 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 /models endpoint through pi's own model-store cache (refreshModels hook) — new models appear without code updates
  • Usage-aware pricing — cache-hit (cacheRead / cacheWrite) rates included for cost display; Command Code requests use a stable threadId per conversation so upstream prefix caching can hit across turns (see docs/CACHING.md)
  • /providers command + 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 dependenciespi-ai / pi-coding-agent / pi-tui are optional peers

Installation

# Local development (from this directory)
npx pi install .

# Or from npm (once published)
npx pi install npm:pi-other-provider

Restart/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)

  1. Environment variable (recommended):

    export OPENCODE_API_KEY="your-key"
    
  2. Auth file — entries are keyed by provider id, so add one per custom provider (or reuse the built-in opencode-go key 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 /model and pi reports Could not refresh <provider> when the model selector tries to refresh its catalog.

Command Code

  1. Run /login, select Use a subscriptionCommand Code (browser flow), or
  2. Set COMMANDCODE_API_KEY, or
  3. 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.

  1. 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.json as { "apiKey": "fk-..." }.

  2. Computer id (recommended) — set FACTORY_DROID_COMPUTER_ID to an existing Factory computer for stability; otherwise the provider reuses an active e2b computer 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 packageoc-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 after showOnly.
  • 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:

/providers

It 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 hide list → reopen /model to apply.
  • Provider on/off → rewrites enabledrestart 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 carries api: "opencode-custom" (the OPENCODE_CUSTOM_API marker), matching the provider's api. pi's stream router only invokes an extension's streamSimple when model.api === provider.api, so the marker is what makes the gotcha hooks run — stream.ts then re-derives the real protocol from the catalog. Setting model.api to the real protocol instead would silently bypass every gotcha (a previous regression, now guarded by tests/test-stream.ts).

Gotcha hooks (onPayload)

Five transforms applied to every OpenCode request, ported from OmniRoute's OpencodeExecutor:

  1. stripClientMetadata — removes client_metadata (upstream 400 "Extra inputs not permitted")
  2. limitTools — caps tools at 128
  3. rewriteEffortTier — expands effort aliases (deepseek-v4-pro-low → base + reasoning_effort: low)
  4. stripBooleanReasoning — removes boolean reasoning fields some upstreams reject
  5. injectReasoningContent — echoes reasoning_content on 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_tokens is 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/skills sent as "" (not null), plus permissionMode: "standard"
  • Stable threadId per pi conversation (from options.sessionId) so upstream prompt caching can hit across turns

Caching

Two distinct cache concepts, both supported (details: docs/CACHING.md):

  1. Model catalog cache — pi's models-store.json persists the live model list; refreshed when you open /model, restored on offline startups.
  2. Usage cache hits — provider-side prompt caching while chatting:
    • OpenCode: pi-ai streamers send cache_control / prompt-cache keys automatically; pricing tables include cacheRead/cacheWrite rates.
    • Command Code: stable threadId per conversation enables cross-turn prefix caching; cacheRead/cacheWrite tokens are parsed from responses and costed at their discounted rates.

Development

npm test            # typecheck + full unit suite (mocked HTTP, no real calls)
npm run typecheck   # tsc --noEmit

See 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

Update / remove

npx pi update --extensions   # update installed packages
npx pi remove pi-other-provider

License

MIT