Pi provider extension that discovers models from CLIProxyAPI and registers them for use in pi. It supports catalog-driven OpenAI Fast mode and also ships a small TUI helper that shows elapsed runtime and a TPS summary after each agent turn.
Compared with router-for-me/pi-cliproxyapi-provider, this fork:
- uses Pi's native API-key login and stores credentials only in
auth.json; - routes Codex-backed models through CLIProxyAPI's Codex client endpoint and every other model through its standard Responses API, deciding by the catalog's
prefer_websocketsflag rather than by model name; - uses Pi's public stock
openai-codex-responsesandopenai-responsesstreams without resolving or rewriting Pi build files, adapting plain CPA authentication at the request boundary; - passes Pi's
transportsetting through unchanged, so Pi's defaultautoowns WebSocket-first transport and SSE fallback for every model; - drops empty user/assistant messages (for example extension fold markers) before the request, because CPA forwards them to backends such as Claude or Gemini that reject empty content;
- resets the reused Codex WebSocket after compaction so server-side context follows Pi's compacted messages;
- keeps the model catalog in Pi's native model store and refresh lifecycle, and improves catalog mapping with opt-in maximum context windows, grammar/freeform tools, and output-token metadata resolved from CPA,
models.dev, or a safe default.
Codex-backed models keep the Codex client protocol, which preserves the Codex-specific WebSocket, compaction, Fast, and tool behavior. Models CPA serves from other backends (Claude, Gemini, …) use the standard Responses API at {root}/v1 with a plain Bearer key; see Transport.
- Registers a native API-key provider that always appears in
/login. - Interactive setup collects
baseUrl+apiKeyvia/login CLIProxyAPIor/login cliproxyapi. - Fetches
{root}/v1/models?client_version=cpa, including explicit native search capability metadata. - Maps the CLIProxyAPI catalog into pi models, including Fast service-tier capability.
- Registers inference against
{root}/backend-api/with Pi's standardopenai-codex-responsesAPI metadata. - Provides
/fastto toggle OpenAI priority processing for supported models. - Stores the model catalog through Pi's native provider refresh lifecycle (Pi's
models-store.json) and provides/cliproxyapi-refreshto force a refresh. - In interactive TUI sessions, shows footer elapsed time during runs and a TPS / token usage toast when the agent settles.
- Leaves compaction triggering to Pi, including native checks between tool turns. After compaction, closes the reused Codex WebSocket for that session so CLIProxyAPI's server-side context resets with the compacted client messages. The extension does not inject synthetic context-overflow errors or block summary requests.
- Optionally registers CPA image models listed in
imageModelsfor Pi image generation. - Registers CPA decision models (TypeSafe System One, such as Jev, and OpenAI Decisions API models such as GPT-6 Luna) as Pi classifier models.
# from npm
pi install npm:@router-for-me/pi-cliproxyapi-provider
# from a local checkout
pi install /absolute/path/to/pi-cliproxyapi-provider
# or temporarily for one run
pi -e /absolute/path/to/pi-cliproxyapi-providerThis plugin uses Pi's native API-key authentication while prompting for both baseUrl and apiKey.
/login CLIProxyAPI
or:
/login cliproxyapi
These shortcuts jump straight into CLIProxyAPI's baseUrl + API key prompts.
/login
Then choose CLIProxyAPI from API-key providers and enter:
- base URL — preferred form is host:port, e.g.
http://127.0.0.1:8317 - API key
Final login validation calls {root}/v1/models?client_version=cpa:
- HTTP 200 → login succeeds (an empty model list is still OK)
- non-200 / network error → login fails and you are prompted to re-enter base URL + API key
On success:
- Pi stores the API key and base URL in
~/.pi/agent/auth.json, then refreshes this provider's catalog (0 models is allowed) - after Pi persists the native credential, duplicate
baseUrl/apiKeyfields are removed from~/.pi/agent/cliproxyapi.json
Re-run /login CLIProxyAPI or /login cliproxyapi anytime to reconfigure. /logout removes the native credential; non-secret settings in cliproxyapi.json remain.
You can still configure without /login.
~/.pi/agent/cliproxyapi.json:
{
"baseUrl": "http://127.0.0.1:8317",
"apiKey": "12345",
"fast": false,
"webSearch": false,
"pause": false,
"useMaxContextWindow": false
}Optional fields:
| Field | Default | Description |
|---|---|---|
baseUrl |
http://127.0.0.1:8317 |
CLIProxyAPI address |
apiKey |
(required unless set via /login or env) | Bearer token / CPA API key |
providerId |
cliproxyapi |
Provider id shown in /model |
providerName |
CLIProxyAPI |
Display name in /login and UI |
fast |
false |
Persisted Fast mode preference; only applies to catalog-supported models |
webSearch |
false |
Enable the cliproxyapi_search tool for models with explicit native search support |
pause |
false |
Persisted request-pause preference; provider requests wait until it is cleared |
useMaxContextWindow |
false |
Use catalog max_context_window instead of standard context_window when available |
imageModels |
[] |
CPA image model ids (e.g. ["gpt-image-2"]) registered as Pi image models; see Image generation |
| Variable | Overrides |
|---|---|
CLIPROXYAPI_BASE_URL |
baseUrl |
CLIPROXYAPI_API_KEY |
apiKey |
CLIPROXYAPI_PROVIDER_ID |
providerId |
CLIPROXYAPI_PROVIDER_NAME |
providerName |
CLIPROXYAPI_FAST |
fast (true / false, also accepts 1, 0, yes, no, on, off) |
CLIPROXYAPI_WEB_SEARCH |
webSearch (same boolean forms as CLIPROXYAPI_FAST) |
CLIPROXYAPI_PARENT_SESSION_ID |
Explicit immediate parent session ID supplied by a child launcher; not a root ID or file path |
CLIPROXYAPI_USE_MAX_CONTEXT_WINDOW |
useMaxContextWindow (same boolean forms as CLIPROXYAPI_FAST) |
Resolution order for connection settings:
- Environment variables
/logincredentials inauth.jsoncliproxyapi.json- Default baseUrl
http://127.0.0.1:8317
The Fast preference resolves separately as CLIPROXYAPI_FAST → cliproxyapi.json → false. Maximum context is opt-in via CLIPROXYAPI_USE_MAX_CONTEXT_WINDOW → cliproxyapi.json → false.
CLIProxyAPI requests follow Pi's own transport setting (~/.pi/agent/settings.json, see Pi's settings docs). The value is passed to Pi's stock Codex stream unchanged and applies to every CLIProxyAPI model; the catalog's prefer_websockets field is ignored. With Pi's default auto, Pi tries WebSocket first and owns SSE fallback, cooldown, and recovery. One-off native summary requests (cacheRetention: "none") always use SSE.
CLIPROXYAPI_TRANSPORT and the transport field in cliproxyapi.json are no longer read; a startup warning points to Pi's setting if either is still present.
Pi's stock transport behavior applies; see Pi's settings docs for how auto falls back to SSE and when it retries WebSocket. Use sse to disable WebSocket.
Models the CPA catalog marks prefer_websockets: false (every non-Codex backend) use Pi's openai-responses API against {root}/v1/responses and declare compat.supportsResponseCheckpoints: true. Pi then requests store: true and continues later turns with previous_response_id plus only the new input, which CLIProxyAPI expands from its own response cache (in-memory, 30 minutes) — so a continuation survives a new WebSocket or a Pi restart within that window. An expired or unknown id returns previous_response_not_found; Pi resends the full history once and cools checkpoints down. Codex-backed models keep Pi's connection-local continuation on the reused Codex WebSocket. Fast and the native search tool remain Codex-only.
Known limit: Pi captures a checkpoint only when the server's terminal function_call output is byte-identical to Pi's own encoding. CLIProxyAPI returns Claude's tool arguments with Claude's original JSON spacing, so turns ending in a tool call currently do not checkpoint and the next cross-socket request sends full history. A fix is pending in Pi (xz-dev/pi, patch/responses-websocket-transport); no extension change is needed once it lands.
Preferred form is host:port only:
| Input | Inference baseUrl | Models URL |
|---|---|---|
http://127.0.0.1:8317 |
http://127.0.0.1:8317/backend-api/ |
http://127.0.0.1:8317/v1/models?client_version=cpa |
http://127.0.0.1:8317/backend-api |
http://127.0.0.1:8317/backend-api/ |
same models URL |
http://127.0.0.1:8317/v1 |
http://127.0.0.1:8317/backend-api/ |
same models URL |
127.0.0.1:8317 |
http://127.0.0.1:8317/backend-api/ |
same models URL |
pi then sends inference traffic to {inference}/codex/responses. This fixed CLIProxyAPI client protocol is used for every discovered model. Pi can dispatch different models through different API implementations, but this extension intentionally leaves backend protocol translation to CLIProxyAPI instead of guessing from model IDs or origin metadata.
Model discovery and login validation continue to send the real CPA key as Authorization: Bearer <key> to /v1/models. Only inference is adapted for Pi's stock Codex parser:
Authorization: Bearer <synthetic-jwt>
X-Api-Key: <real-cpa-key>
chatgpt-account-id: cpa_<sha256-key-fingerprint>The synthetic JWT contains no raw API key. It supplies the account claim required by Pi and gives each CPA key a stable, isolated WebSocket cache identity. CLIProxyAPI authenticates the request through X-Api-Key. Any reverse proxy or gateway in front of CLIProxyAPI must preserve that header; stripping it makes inference authentication fail.
OpenAI Fast mode requests the priority service tier. It can reduce latency for supported models, but consumes more OpenAI/Codex credits or incurs priority-processing pricing.
Fast is off by default. Toggle the global preference with:
/fast
Each invocation switches Fast between on and off and writes the result to ~/.pi/agent/cliproxyapi.json. On the next startup, a persisted true value immediately enables Fast for catalog-supported models. Fast remains ineffective for unsupported models, so their requests are left unchanged. If CLIPROXYAPI_FAST is set, that environment variable still takes precedence on startup.
When Fast is effective, Pi's footer status line shows a yellow lowercase fast (set through the public ctx.ui.setStatus API; Pi's built-in footer is not modified). When Fast is off or the selected model is unsupported, no label is shown. Supported models do not produce a separate status notification. Running /fast with an unsupported model still updates the global preference; enabling it warns that the current model cannot use Fast.
Fast capability is catalog-driven: the plugin considers a CLIProxyAPI model Fast-capable when its service_tiers field is a non-empty array. The additional_speed_tiers field is ignored. For supported models, Fast passes Pi's native serviceTier: "priority" option to the stock Codex stream, which sends service_tier: "priority"; unsupported models are left unchanged. Fast is independent from pi's reasoning/thinking level.
Models keep their standard catalog prices. Pi's Codex stream applies its built-in priority-tier multiplier to each request's usage cost (2×, or 2.5× for gpt-5.5), based on the tier the response reports. Toggling /fast therefore takes effect on the next request without refreshing the catalog or switching the active model. The multiplier matches OpenAI Codex pricing; it is not tailored to non-OpenAI backends behind CPA.
Native search is off by default. It is a separate tool, not a rewrite of Pi's chat streaming runtime:
/cliproxyapi-refresh
/cliproxyapi-search on
/cliproxyapi-search status
/cliproxyapi-search off
Enabling registers cliproxyapi_search. It takes query and an optional exact CPA model ID, defaulting to the current model. A different search model can be selected without changing the chat model. Only visible models explicitly advertising cpa_capabilities.web_search: true are eligible; missing, false, or malformed capability claims never enable search. There is no provider-name inference or silent fallback to an ungrounded model answer.
The tool sends only the query, not conversation history, to {root}/v1/responses with the native web_search tool and stream: false. This keeps search results and citation URLs intact without modifying Pi's SSE/WebSocket parser. The result includes answer text, deduplicated HTTP(S) source URLs, and nested token usage. Results that report no completed search fail rather than masquerading as web results; incomplete answers are marked. Text is limited to 50KB/2000 lines, source details to 100 URLs, response bodies to 2 MiB, and each HTTP request to two minutes. Abort and /pause are honored. Normal chat transport is unchanged.
This is an additional model request and may incur native search fees. Token costs use the model catalog rates (with the same priority multiplier when Fast applies); separate per-search charges are not included. Catalog-supported /fast applies to the search request too. The tool reuses Pi's resolved model credentials and headers; it does not store another API key.
CLIPROXYAPI_WEB_SEARCH overrides webSearch at startup. The command changes the current session and persisted preference; an environment override still wins on the next startup. Turning the tool off prevents new calls, but does not cancel an HTTP request already in progress.
CPA serves image models through {root}/v1/images/generations and {root}/v1/images/edits, but its Codex catalog hides them without saying they are image models. List the ones you want in cliproxyapi.json:
{ "imageModels": ["gpt-image-2"] }They are registered as Pi image models under this provider and use the same credential. Like Pi's other image models, they do not appear in /model; codemode scripts reach them with models.getAvailableOfType("image") and models.generateImages(), and extensions with ctx.modelRegistry.generateImages(). A request with only text calls generations; a request with input images calls edits with them as data: URLs. Images come back as base64 (response_format: "b64_json"). Image costs are reported as zero. Changes to imageModels apply after /reload.
CPA builds that list model capabilities in {root}/v1/models (OpenRouter-style architecture.output_modalities; JohnsonRan/CLIProxyAPI patch/model-capabilities) mark System One models with decisions. Mark them in CPA with systemone: true. Each one is registered as a Pi classifier model under this provider with Pi's typesafe-system-one API, so models.getAvailableOfType("classifier") and models.classify() reach them with the same credential at {root}/v1/systemone. They do not appear in /model.
CPA models that Pi's built-in openai provider serves through the Decisions API (currently gpt-6-luna) are also registered as classifier models, with Pi's openai-decisions API at {root}/v1/decisions, whatever their listed modalities. They keep Pi's catalog metadata (input types, price, input limit) and also stay chat models in /model.
A System One classifier's contextWindow is CPA's context_length. Callers size their state by it, so set max-context-length on the CPA model to the real upstream input limit; without it the extension assumes 32,000 tokens. A CPA without capability fields registers no classifiers, and a failed list keeps the previously known ones.
For example, pi-llm-as-jev can use "classifierModel": "cliproxyapi/typesafe/jev" or "cliproxyapi/gpt-6-luna" without classifierBaseUrl or a separate TypeSafe/OpenAI key.
Inference preserves Pi's existing session/cache ID and adds X-Codex-Parent-Thread-Id when a real parent is known:
- For saved session ancestry (
/fork,/clone, ornewSession({ parentSession })), read only the referenced parent file's header ID. Missing/malformed parent files are ignored; filenames and conversation text are never used to guess IDs. - Child launchers can explicitly supply
CLIPROXYAPI_PARENT_SESSION_IDwith the immediate parent ID. It is bound on startup only when no saved parent resolves, then stored as a non-context custom session entry containing only the two IDs. Reload/resume restores it only for that exact session; it is not inherited into a new/resumed unrelated session. - SDK stream callers can pass
metadata: { parent_session_id: "..." }together with their ownsessionId. Explicit caller headers, including null suppression, take precedence.
PI_SUBAGENT_PARENT_SESSION is deliberately ignored: pi-subagents uses it for the root permission-routing session, not necessarily the direct parent. Fresh children without a saved parent or explicit launcher/request metadata remain unlinked. No pi-subagents internals are imported.
Native compaction (cacheRetention: "none") and calls using another session ID are not assigned the active chat's parent. Session shutdown clears captured ancestry. Native search requests use the current session ID and known parent for usage attribution.
Pause provider requests with:
/pause
Use /continue to clear the pause:
/continue
Both commands persist the pause boolean in ~/.pi/agent/cliproxyapi.json. Before every CLIProxyAPI request (other providers are not held), the extension rereads this setting. While paused and a CLIProxyAPI model is selected, the footer status line shows an orange paused. When it is true, the request waits asynchronously and checks again every 200 ms until /continue sets it to false. A pause issued during an active run lets that run finish before Elapsed stops; a run that starts while paused excludes its waiting time from Elapsed.
The catalog follows Pi's native provider lifecycle. Pi persists what the provider publishes in its own model store (~/.pi/agent/models-store.json, entry cliproxyapi); the extension keeps no separate cache file. Each stored model carries a cpa field with its catalog capabilities (Fast, native search, SSE preference), so they are restored offline too. Credentials are never stored there.
| Property | Value |
|---|---|
| Storage | Pi's model store, entry for the provider id |
| Startup fetch timeout | 5 seconds |
| Refresh timeout | 60 seconds |
| Scope | models are restored only when their inference URL matches the configured baseUrl |
- While the extension loads, it fetches
{root}/v1/models?client_version=cpaonce, bounded to 5 seconds. Pi resolves--modelright after startup and never goes online first in print or JSON mode, so this is what makespi -p --model cliproxyapi/...and subagent sessions work on a fresh install. - Pi's offline startup refresh publishes and persists that result. If the fetch failed, Pi's stored catalog for the configured
baseUrlis restored instead. - Interactive and RPC modes then run Pi's network refresh. Shortly after startup it reuses the startup fetch rather than requesting the catalog again.
A non-empty catalog replaces the stored list, so a removed model disappears immediately. An empty catalog does not erase a populated one; invalid JSON or a body with no model list fails the refresh and leaves the stored list active.
/cliproxyapi-refreshforces an immediate refresh through Pi's model registry (refresh({ providers: [id], force: true })). Use it after adding or removing models on the proxy./login CLIProxyAPI//login cliproxyapivalidates the credentials, then Pi refreshes this provider's catalog.
From CPA catalog entry → pi model:
| CPA field | Pi field |
|---|---|
slug |
id |
display_name |
name |
context_window |
contextWindow |
max_tokens / max_completion_tokens |
maxTokens (preferred) |
matching models.dev limit.output |
maxTokens fallback |
input_modalities |
input (text / image) |
supported_reasoning_levels[].effort |
thinkingLevelMap + reasoning |
apply_patch_tool_type: "freeform" |
enables Pi OpenAI grammar/freeform tools |
visibility: "hide" |
skipped |
cpa_capabilities.web_search: true |
native search eligibility (separate from Pi deferred tool search) |
Since CLIProxyAPI v8.0.9, the catalog sets apply_patch_tool_type to null unless the proxy sets client.codex.enable-apply-patch: true; without it, Pi uses plain function tools.
Unsupported pi thinking levels are set to null so they are hidden in the UI. Output limits resolve in this order: CPA max_tokens, CPA max_completion_tokens, matching models.dev limit.output, then 16384. CPA exposes this as model-catalog metadata on supported versions; it is a client budgeting value, not a guarantee that every backend enforces the same limit. When available, prices are matched against canonical model entries in models.dev; cost.tiers[].tier.size becomes pi's inputTokensAbove, including thresholds such as 272000. The legacy context_over_200k field is used only when no explicit tiers are present. Ambiguous reseller data is not selected arbitrarily, and prices fall back to zero.
The raw models.dev response is cached for 24 hours at ~/.pi/agent/tmp/models-dev-cache.json. A fresh cache avoids the network request; an expired cache is refreshed with a three-second timeout, and stale data is retained if refresh fails. If neither the network nor a previous cache is available, pricing safely falls back to zero. A small explicit alias table covers known CLIProxyAPI variants such as gemini-pro-agent → gemini-3.1-pro-preview; unknown variants are not guessed.
- Custom API id (through 1.4.13). Older versions stored assistant messages with the custom
cliproxyapi-codex-responsesAPI id; new messages use Pi's standardopenai-codex-responses. Existing sessions need no rewrite: Pi replays the older messages as foreign API metadata and normalizes tool-call ids, including parallel tool calls. - Separate model cache. Older versions kept their own cache in
~/.pi/agent/cliproxyapi-models.json. It is no longer read and can be deleted. - Transport setting.
CLIPROXYAPI_TRANSPORTandtransportincliproxyapi.jsonare replaced by Pi's owntransportsetting (see Transport).
If you previously maintained a static provider such as cpa-responses in ~/.pi/agent/models.json:
- Install this package and run
/login CLIProxyAPIor/login cliproxyapi(or setcliproxyapi.json). - Point
defaultProvider/enabledModelsatcliproxyapi/<model-id>(or setproviderIdtocpa-responsesfor a drop-in id). - Remove the hand-maintained models array once the dynamic list looks correct.
The package also registers extensions/tps.ts, which only activates for the primary interactive TUI session (ctx.hasUI && ctx.mode === "tui"):
- While the agent is running, the footer shows
Elapsed …(updates every second). - When the agent settles, the footer keeps the final elapsed time plus TPS (e.g.
Elapsed 12s · TPS 45.3 tok/s) and a notification reports TPS plus token usage (out/in/ cache r/w / total). - TPS divides output tokens by the time assistant responses spent streaming (from each response's first event to its end). Tool runs and gaps between turns count toward Elapsed but not TPS.
- Subagent and print-mode sessions do not own the timer, clear the parent footer, or emit TPS toasts.
Disable just this helper via pi config if you only want the CLIProxyAPI provider.
- CLIProxyAPI
closed network connectionresponses are normalized as transient network errors so pi's agent-level retry policy reconnects and restarts the interrupted assistant turn. Completed conversation and tool results remain available; token streaming does not resume from the exact interruption point. - Before setup / without credentials: provider still appears in
/login; no models are listed yet. - After successful
/login: native API-key credentials are stored only inauth.json, and Pi refreshes the catalog. - The built-in
/logoutcommand removes the matchingauth.jsoncredential; environment variables and non-secretcliproxyapi.jsonsettings are unchanged. - If a models request fails during startup (for example HTTP 401 or CPA unreachable), a warning is logged and Pi's stored catalog for the configured
baseUrlremains in use. Without a stored catalog, no models are listed until a refresh succeeds; reconfigure via/login CLIProxyAPIor fix config/env. - Login final step validates credentials by requesting models:
- HTTP 200 (including empty catalog) → credentials are persisted
- non-200 / network / invalid baseUrl → nothing is persisted; re-enter baseUrl + API key
- If CPA returns HTTP 200 with zero usable models: login still succeeds; re-run
/login CLIProxyAPIlater after models become available. - If the selected model does not provide a non-empty
service_tiersarray: the request is left unchanged;/faststill updates the global preference and warns when enabling it. - If a gateway strips
X-Api-Key: model discovery may still work, but inference fails because theAuthorizationheader intentionally contains only the synthetic parser JWT. - After
/compact, threshold compaction, or overflow recovery, the provider closes the reused Codex WebSocket for the current session. CLIProxyAPI binds server-side context to the connection, so a reused socket would keep reporting a near-fullcacheReadand retrigger compaction even though the client context is now small. SSE is unaffected because it bills from the request body.