pi-custom-openai-providers
Maintain multiple OpenAI-compatible providers in pi with /custom-providers; switch between them with /model.
Package details
Install pi-custom-openai-providers from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-custom-openai-providers- Package
pi-custom-openai-providers- Version
0.2.1- Published
- Aug 7, 2026
- Downloads
- 441/mo · 441/wk
- Author
- zongchenghao
- License
- MIT
- Types
- extension
- Size
- 41.6 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-custom-openai-providers
Maintain multiple OpenAI-compatible providers in pi and switch between them with
/model.
Features
- One command for everything —
/custom-providerswithadd/list/edit/removesubcommands - Switch with
/model— each provider registers ascustom-<name>and appears in pi's model picker - Persistent storage in pi's official path —
~/.pi/agent/models.json(pi's ownmodels.jsonschema, written mode0600) - Full
models.mdschema —headers/compat/thinkingLevelMap/cost.tiers/modelOverrides/ input modes (text/image) / customapietc. all supported - Editor for the full spec —
add <name> --raw/edit <name> --rawopens$VISUAL/$EDITOR(falls back tovi) so you can edit the complete provider JSON including fields the 3-prompt flow doesn't expose - Auto-migration — the legacy
<cwd>/.pi/custom-providers.json(multi-provider) and<cwd>/.pi/custom-openai.json(single-provider) are detected on first load, copied into~/.pi/agent/models.json, then deleted - Env-var seed —
CUSTOM_OPENAI_*env vars create acustom-defaultprovider when no providers are configured anywhere (CI / scripting friendly) - Zero runtime dependencies — pure TypeScript loaded by pi's jiti; only
node:fs/node:path/node:os/node:child_process
Installation
⚠ Install in exactly one location. Installing both globally (
~/.pi/agent/extensions/) and project-locally (.pi/extensions/) — or installing viapi installwhile a symlink / dev copy already exists — causes pi to load the extension twice. pi then adds a:1suffix to the command's invocation name, so Tab autocomplete inserts/custom-providers:1instead of/custom-providers. The extension detects this at load time, logs a warning to the terminal, and skips the duplicate command registration, but the cleanest fix is to remove one installation.
One-shot install (recommended)
# From GitHub
pi install github:playmaker/pi-custom-openai-providers
# Or from npm
pi install npm:pi-custom-openai-providers
Manual install (project-local)
git clone https://github.com/playmaker/pi-custom-openai-providers
cd <your-project>
ln -s "$(pwd)/../pi-custom-openai-providers" .pi/extensions/pi-custom-openai-providers
Manual install (global)
git clone https://github.com/playmaker/pi-custom-openai-providers ~/.pi/agent/extensions/pi-custom-openai-providers
Quick try (no install)
pi -e /path/to/pi-custom-openai-providers/src/index.ts
Usage
Add a provider
/custom-providers add deepseek
Base URL: https://api.deepseek.com/v1
API key: sk-...
Model id: deepseek-chat
<name> is a short identifier without whitespace. It becomes part of the provider id custom-<name> shown in /model.
List configured providers
/custom-providers
Shows a persistent widget above the editor listing all configured providers with their endpoints and models. Stays visible until the next command or model change.
Edit a provider
/custom-providers edit deepseek
Base URL [https://api.deepseek.com/v1]:
API key (leave blank to keep current ending in xxxx):
Model id [deepseek-chat]:
Empty input keeps the current value. Pressing Esc on any prompt cancels the edit and emits Edit cancelled. (nothing is saved or re-registered). Pressing Enter through all three prompts with no changes emits No changes for "<name>". and skips the save + re-register. Any actual change emits Updated custom provider "<name>".
Add or edit via the editor (full schema)
The 3-prompt flow only edits baseUrl / apiKey / the first model's id. For everything else in pi's models.md schema (headers / compat / thinkingLevelMap / cost.tiers / modelOverrides / image input / custom api per model / multiple models / ...), pass --raw and the extension opens $VISUAL / $EDITOR (falls back to vi) with the full provider JSON:
/custom-providers add my-openrouter --raw
/custom-providers edit my-openrouter --raw
The editor temp file is created with mode 0600 and cleaned up on exit. The extension validates the JSON before writing: it must be parseable, have a non-empty baseUrl, and at least one models entry with a non-empty id. Each model gets api: "openai-completions" injected if missing.
Honored environment variables for the editor (in order):
$VISUAL$EDITORvi(fallback if neither is set)
If the editor is not found, you get Editor "..." not found. Set $VISUAL or $EDITOR to a valid editor path. instead of a silent failure. If the file's contents are unchanged after you exit, the edit is a no-op and the disk state is preserved.
Remove a provider
/custom-providers remove deepseek
Switch providers
After configuring, use /model and pick custom-<name>/<modelId>:
/model
> custom-openrouter / anthropic/claude-sonnet-4.5
Tab completion
The /custom-providers command surfaces its subcommand shape through pi's autocomplete:
- Typing
/custom-providersshows the dropdown with[list | add <name> | edit <name> | remove <name>] — <description>. - Typing
/custom-providers(with a trailing space) lists the four subcommands; keep typing to filter (e.g.a→add). - After
/custom-providers editor/custom-providers remove, existing provider names are offered for tab completion, filtered as you type.
Configuration
Storage
~/.pi/agent/models.json (mode 0600) — pi's official model.json schema. The
extension adds a key per provider under the top-level providers object, prefixed
with custom- to avoid clashing with built-ins (e.g. custom-my-qianwen).
Full ProviderEntry shape (every field from models.md is supported):
{
"providers": {
"custom-my-qianwen": {
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",
"apiKey": "$DASHSCOPE_KEY",
"headers": {
"x-portkey-api-key": "$PORTKEY_KEY"
},
"compat": {
"supportsDeveloperRole": false,
"supportsReasoningEffort": false
},
"models": [
{
"id": "qwen3-max",
"name": "Qwen3 Max",
"input": ["text"],
"reasoning": true,
"contextWindow": 262144,
"maxTokens": 8192,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
}
]
}
}
}
You can also edit ~/.pi/agent/models.json directly with any text editor — the
extension is just a convenience layer on top of pi's own config. Add the path to
your global gitignore (or HOME-level) if you don't want it in version control:
.pi/agent/models.json
Environment variable seed
If no providers are configured anywhere and these are set on startup, a
custom-default provider is created and persisted to ~/.pi/agent/models.json:
| Variable | Example |
|---|---|
CUSTOM_OPENAI_BASE_URL |
https://api.deepseek.com/v1 |
CUSTOM_OPENAI_API_KEY |
sk-... |
CUSTOM_OPENAI_MODEL |
deepseek-chat |
Legacy migration
The earlier single-provider .pi/custom-openai.json is auto-migrated as a default provider on first load; the old file is deleted.
Examples
DeepSeek
/custom-providers add deepseek
Base URL: https://api.deepseek.com/v1
API key: sk-...
Model id: deepseek-chat
OpenRouter (route to any upstream model)
/custom-providers add openrouter
Base URL: https://openrouter.ai/api/v1
API key: sk-or-...
Model id: anthropic/claude-sonnet-4.5
Local llama.cpp / Ollama OpenAI-compat mode
/custom-providers add local-llama
Base URL: http://localhost:8080/v1
API key: local
Model id: llama-3.1-70b
Multiple endpoints side-by-side
/custom-providers
Custom providers (3):
custom-deepseek https://api.deepseek.com/v1 model=deepseek-chat
custom-openrouter https://openrouter.ai/api/v1 model=anthropic/claude-sonnet-4.5
custom-local-llama http://localhost:8080/v1 model=llama-3.1-70b
/model # pick custom-openrouter / anthropic/claude-sonnet-4.5
Compatibility
- pi ≥ 0.83 — uses
registerProvider,registerCommand,setWidget,unregisterProvider - Node.js ≥ 20 — uses
node:fs,node:path - Any endpoint speaking the OpenAI Chat Completions protocol: DeepSeek, OpenRouter, OpenAI, Azure OpenAI gateways, llama.cpp server, vLLM, Ollama's OpenAI-compat mode, custom corporate gateways, etc.
If your endpoint speaks a different protocol, adjust api: "openai-completions" in src/index.ts to the appropriate value (see pi docs for the full list).
Project layout
pi-custom-openai-providers/
├── src/
│ └── index.ts # extension entry — exported default function(pi)
├── package.json # pi.extensions → "./src/index.ts"
├── README.md
├── README_zh.md
├── LICENSE
└── .gitignore
This follows the official pi extension convention (pi.extensions in package.json pointing at the entry file). No build step — pi loads src/index.ts directly via jiti.
Development
git clone https://github.com/playmaker/pi-custom-openai-providers
cd pi-custom-openai-providers
There is no build step and no runtime npm dependency. To test against your local pi:
# Symlink into pi's global extensions directory
ln -s "$(pwd)" ~/.pi/agent/extensions/pi-custom-openai-providers
# Or load directly via -e flag
pi -e "$(pwd)/src/index.ts"
Run pi and try /custom-providers add test to exercise the flow.
Contributing
Issues and pull requests welcome at https://github.com/playmaker/pi-custom-openai-providers.
License
MIT © 2026 Zongchenghao
Acknowledgements
- Built for pi — the AI coding agent
- Follows the convention of the official
custom-provider-anthropicexample extension