pi-relay-switch
Manage multiple OpenAI/Anthropic-compatible API relays in pi: switch relays, pull model lists, manage API keys, and wire thinking levels (shift+tab) through to the relay.
Package details
Install pi-relay-switch from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-relay-switch- Package
pi-relay-switch- Version
0.1.1- Published
- Aug 26, 2026
- Downloads
- 201/mo · 168/wk
- Author
- n0rt6
- License
- MIT
- Types
- extension
- Size
- 62 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-relay-switch
Manage multiple API relays (中转站 / third-party OpenAI-compatible gateways) for pi: switch relays, pull model lists, manage API keys, and wire pi's thinking levels (shift+tab) through to the relay so they actually take effect.
中文文档:README.zh-CN.md
Features
- One command, everything:
/switchcovers add / remove / switch relays - Authoritative model metadata: reads
supports_reasoning/supports_vision/context_length/ pricing from the relay's/modelsresponse — no guessing - Thinking levels that work: reasoning models automatically get
reasoning_effortconfiguration, so pi'sshift+tabcycle really takes effect on the relay - Visual feedback: a persistent relay list widget (with status, model count, current model) sits above the editor at startup
- Legacy config migration: auto-migrates providers from
models.jsonon first run — no lost history - Multi-protocol: OpenAI-compatible (
/v1), OpenAI Responses (/v1/responses), and Anthropic native (/v1/messages)
Install
From npm (recommended)
pi install npm:pi-relay-switch
From git
pi install git:github.com/<you>/pi-relay-switch@v0.1.0
Manual (development)
Copy the directory into pi's extension folder:
~/.pi/agent/extensions/pi-relay-switch/
├── index.ts # Command entry / UI
├── config.ts # Config read/write / migration
├── detect.ts # Connectivity detection
└── models.ts # Model metadata extraction / config inference
Restart pi (or run /reload) after installing.
Quick Start
/switch # Operation menu: change / refresh / add / remove
/switch add # Add a relay interactively (type → Base URL → API Key)
/switch remove <id> # Remove a relay directly
/switch refresh [id] # Re-pull a relay's model list (picker if multiple, no id)
/switch my-relay # Switch to that relay (model list appears afterwards)
Command Reference
/switch — add / remove / switch
| Input | Behavior |
|---|---|
/switch |
Opens the operation menu: Switch relay / Refresh models / Add relay / Remove relay |
/switch <id> |
Switches to that relay (e.g. /switch my-relay), then shows the model list to confirm/change the model (🧠 reasoning marker + context window) |
/switch refresh [id] |
Re-pulls the relay's /models list and refreshes the cached metadata; shows the new model list (enter to switch, no double prompt). No id → picker if multiple |
/switch add |
Interactive add (type / Base URL / API Key / display name); detects connectivity and optionally switches immediately |
/switch remove <id> |
Removes a relay (with confirmation; removing the current one clears the default). No id → picker |
Interface type (first step when adding):
| Type | Protocol | Request header | Use case |
|---|---|---|---|
OpenAI-compatible (/v1) |
Chat Completions | Authorization: Bearer |
Most relays/aggregators (default) |
OpenAI Responses (/v1/responses) |
Responses API | Authorization: Bearer |
Relays that speak Responses, when you need native capabilities |
Anthropic native (/v1/messages) |
Anthropic Messages API | x-api-key + anthropic-version |
Relays that natively forward Claude's raw API |
A wrong type is caught during detection (401 wrong auth header / 404 missing endpoint) — just remove and re-add.
Argument completion: the first argument completes both subcommands and relay ids (e.g. typing
/switch mycompletesmy-relay). Typing filter: in any picker, just type to prefix-filter (e.g.deepleaves only deepseek models);backspaceremoves the filter;escclears the filter first, then closes.
Shortcut
| Key | Behavior |
|---|---|
ctrl+shift+r |
Quick relay switch (opens picker; single relay switches directly) |
Conveniences
- With only one relay,
/switch/ctrl+shift+rswitch directly without a picker - Model changes auto-sync: switching models via pi's
/model/ctrl+lupdates the relay'slastModel(the current relay follows the model's provider); the widget refreshes in real time
Flow
Switch: pick relay → (auto-detect + fetch models when cache empty) → register provider → model list (pre-selected to last used; enter confirms / esc keeps default) → writes relays.json + settings.json → notifies current thinking level.
Refresh: fetch /models → update cached metadata → model list → selection switches relay.
Remove current relay: auto-unregisters the provider and clears defaultProvider/defaultModel in settings.json.
Thinking Levels
pi's built-in shift+tab cycles: off → minimal → low → medium → high → xhigh → max.
What this plugin does:
Marks reasoning models: prefers the
supports_reasoningfield from/models(authoritative); falls back to model-id heuristics when the relay omits it (deepseek / kimi-k2 / glm-4.5+ / qwen3 / minimax-m2 / gemini-2.5+ / claude-4+ / o1-o4 / reasoner·thinking, etc.)Wires it through: reasoning models (OpenAI-compatible type) automatically get:
compat: { supportsReasoningEffort: true }, thinkingLevelMap: { minimal: "minimal", low: "low", medium: "medium", high: "high", xhigh: "high", // clamped to high to avoid relay rejection max: "high", }— without this layer, pi's OpenAI-compatible adapter will not send
reasoning_effortto the relay.Visual: the model picker shows capability markers — 🧠 = reasoning levels supported (
shift+tab), context window on the right (e.g.1M ctx); the switch notification includes the current level (e.g.thinking: high).
Manual override
If a model is misjudged (relay rejects reasoning_effort with a 400), set a manual override reasoning on that model entry in relays.json:
{ "id": "some-model", "reasoning": false }
Manual overrides take precedence over /models authoritative data and are preserved on refresh.
Field roles:
reasoning= your manual override;supportsReasoning= authoritative data pulled from/models(updated on refresh).
Model Metadata
On add/switch, metadata is extracted from the relay's /models response and cached per model:
| Field | Source | Purpose |
|---|---|---|
supportsReasoning |
supports_reasoning |
Whether thinking levels work (shift+tab); reasoning manual override wins over it |
vision |
supports_vision |
Whether image input is supported |
contextWindow |
context_length |
pi's context window stats |
maxTokens |
max_completion_tokens |
Max output tokens |
cost |
*_price_per_million |
pi's cost stats (per million tokens) |
Missing fields fall back to conservative defaults (reasoning by id heuristic, price 0, context 128k).
Config File relays.json
Located at ~/.pi/agent/relays.json:
{
"version": 1,
"currentRelay": "my-relay",
"relays": [
{
"id": "my-relay",
"name": "my-relay",
"type": "openai",
"baseUrl": "https://api.example.com/v1",
"apiKey": "sk_xxx",
"headers": { "User-Agent": "MyClient/1.0" },
"models": [
{
"id": "gpt-4o-mini",
"supportsReasoning": true,
"contextWindow": 1000000,
"cost": { "input": 1, "output": 2, "cacheRead": 0.2, "cacheWrite": 0 }
}
],
"status": "ok",
"lastModel": "gpt-4o-mini",
"latencyMs": 142,
"lastChecked": 1786700250424
}
]
}
type:openai(OpenAI-compatible/v1),openai-responses, oranthropic(Anthropic native/v1/messages)headers: optional, overrides default headers (e.g. some relays require a specificUser-Agent)models[].supportsReasoning: authoritative data from/models, auto-updated on refreshmodels[].reasoning: optional manual override (wins oversupportsReasoning, preserved on refresh)lastModel: last model used on that relay, restored on switch
Migration from models.json
On first run (when relays.json doesn't exist or is empty) with providers in models.json:
- Each provider becomes a relay (type inferred from the
apifield) settings.json'sdefaultModelbecomeslastModelwhen it matches- After migration,
models.json's providers are cleared to avoid double registration
Security Notes
⚠️ API keys are stored in plaintext at
~/.pi/agent/relays.json(created with0600permissions on new files). Never commit this file — it is gitignored in this repo by design.
⚠️ pi extensions run with full system access and can execute arbitrary code. Only install packages from sources you trust, and review the source before installing third-party extensions.
FAQ
- Changes require a restart: extensions are loaded at runtime
- Auto-restore on startup: the plugin registers the current relay during the factory phase (before the session starts), so with
defaultProvider/defaultModelinsettings.jsonthe last model is restored — noNo models availablewarning - Reserved keyword collision: if a relay id happens to be
add/removeetc., the management subcommand wins (ids are derived from domains, so this practically never happens) - Detection timeout: 8s by default; timeout is treated as unreachable (cancellable with
esc) - Invalid API key: 401/403 marks the relay as "API Key invalid" without interrupting others
- Empty model list: on switch, falls back to
lastModelor the first model; if none, it prompts to retry/switchlater