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.

Packages

Package details

extension

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: /switch covers add / remove / switch relays
  • Authoritative model metadata: reads supports_reasoning / supports_vision / context_length / pricing from the relay's /models response — no guessing
  • Thinking levels that work: reasoning models automatically get reasoning_effort configuration, so pi's shift+tab cycle 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.json on 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 my completes my-relay). Typing filter: in any picker, just type to prefix-filter (e.g. deep leaves only deepseek models); backspace removes the filter; esc clears 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+r switch directly without a picker
  • Model changes auto-sync: switching models via pi's /model / ctrl+l updates the relay's lastModel (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:

  1. Marks reasoning models: prefers the supports_reasoning field 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.)

  2. 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_effort to the relay.

  3. 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, or anthropic (Anthropic native /v1/messages)
  • headers: optional, overrides default headers (e.g. some relays require a specific User-Agent)
  • models[].supportsReasoning: authoritative data from /models, auto-updated on refresh
  • models[].reasoning: optional manual override (wins over supportsReasoning, 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 api field)
  • settings.json's defaultModel becomes lastModel when 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 with 0600 permissions 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/defaultModel in settings.json the last model is restored — no No models available warning
  • Reserved keyword collision: if a relay id happens to be add / remove etc., 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 lastModel or the first model; if none, it prompts to retry /switch later

License

MIT