@indexyz/pi-provider-sub2api
A pi provider extension for Sub2API-compatible relays with model discovery and quota reporting
Package details
Install @indexyz/pi-provider-sub2api from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@indexyz/pi-provider-sub2api- Package
@indexyz/pi-provider-sub2api- Version
0.1.23- Published
- Sep 5, 2026
- Downloads
- 3,123/mo · 522/wk
- Author
- indexyz
- License
- MIT
- Types
- extension
- Size
- 136 KB
- Dependencies
- 1 dependency · 2 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
@indexyz/pi-provider-sub2api
A pi provider extension for Sub2API-compatible relays. It discovers models dynamically, registers every configured relay as a separate pi provider, and reports relay quota usage in pi.
Requirements
- pi 0.83.0 or newer
- Node.js 22.19 or newer
- A Sub2API-compatible relay exposing
GET /v1/modelsand at least one supported generation endpoint
Install
pi install npm:@indexyz/pi-provider-sub2api
To try the extension from this repository without installing it:
pi -e ./pi-provider-sub2api/index.ts
Configure
Create ~/.pi/agent/sub2api.json:
{
"my-relay": {
"baseURL": "https://relay.example.com",
"token": "replace-with-your-token",
"api": "anthropic-messages",
"serverTools": {
"anthropic": [
{
"type": "web_search_20260209",
"name": "web_search"
}
]
}
},
"another-relay": {
"baseURL": "https://another.example.com/v1",
"token": "replace-with-another-token",
"api": "openai-codex-responses",
"serverTools": {
"responses": [{ "type": "web_search" }]
}
},
"responses-relay": {
"baseURL": "https://responses.example.com/v1",
"token": "replace-with-token",
"api": "openai-responses"
},
"completions-relay": {
"baseURL": "https://completions.example.com/v1",
"token": "replace-with-token",
"api": "openai-completions"
}
}
Each top-level key becomes the provider name shown by pi. baseURL may include the /v1 suffix; the extension normalizes both forms. The optional api setting accepts anthropic-messages, openai-codex-responses, openai-responses, or openai-completions. When present, it is applied to every discovered model for that provider instead of inferring an API from model IDs.
The configuration directory follows PI_CODING_AGENT_DIR when that environment variable is set. Otherwise it defaults to ~/.pi/agent.
Restart pi after creating the file, or run /reload in an interactive session. Then use /model to select a discovered model under the configured provider.
Provider-hosted tools
Use the optional serverTools object to append provider-executed tools to every matching model request:
{
"openai-tools": {
"baseURL": "https://relay.example.com/v1",
"token": "replace-with-token",
"api": "openai-responses",
"serverTools": {
"responses": [
{ "type": "web_search" },
{
"type": "file_search",
"vector_store_ids": ["vs_replace_me"]
},
{
"type": "code_interpreter",
"container": { "type": "auto" }
},
{
"type": "shell",
"environment": { "type": "container_auto" }
},
{
"type": "mcp",
"server_label": "docs",
"server_url": "https://mcp.example.com/sse",
"require_approval": "never"
},
{
"type": "tool_search",
"execution": "server",
"description": "Load hosted tools only when needed"
}
]
}
},
"claude-tools": {
"baseURL": "https://relay.example.com/v1",
"token": "replace-with-token",
"api": "anthropic-messages",
"serverTools": {
"anthropic": [
{
"type": "web_search_20260209",
"name": "web_search",
"max_uses": 5
},
{
"type": "web_fetch_20260309",
"name": "web_fetch",
"max_uses": 3
},
{
"type": "code_execution_20260120",
"name": "code_execution"
},
{
"type": "tool_search_tool_regex_20251119",
"name": "tool_search_tool_regex"
}
]
}
}
}
serverTools.responses applies to openai-responses and openai-codex-responses. Supported provider-hosted families are:
- Web search:
web_search,web_search_2025_08_26,web_search_preview, andweb_search_preview_2025_03_11. - File search:
file_search;vector_store_idsmust contain at least one ID. - Hosted Python:
code_interpreter; setcontainerto a container ID or{ "type": "auto" }. - Hosted shell:
shell;environmentmust becontainer_autoorcontainer_reference. Container skills and network policies may be nested in that environment. - Remote MCP/connectors:
mcp; setserver_label, eitherserver_urlorconnector_id, andrequire_approvalto"never". Pi cannot answer an upstream MCP approval prompt. - Deferred hosted tool discovery:
tool_search;executionmust be"server".
serverTools.anthropic applies to anthropic-messages. Supported provider-hosted families are Anthropic's versioned web_search_*, web_fetch_*, code_execution_*, tool_search_tool_regex*, and tool_search_tool_bm25* definitions. Their required name is validated. The exact version must be supported by the selected model, relay, and upstream account.
Definitions are appended verbatim after Pi's normal function tools; exact duplicates are not appended. The extension accepts unknown future tool types so new provider-hosted definitions do not require an extension release. Upstream schema, model eligibility, account permissions, billing, and regional restrictions still apply.
This is intentionally not an enable-tool boolean: neither protocol defines a safe “enable every tool” request. File search needs vector-store IDs, MCP may need credentials, hosted containers have resource and network policy, and Anthropic tool versions are model-specific. Explicit definitions make those capabilities and costs reviewable.
Safety and compatibility limits:
openai-completionshas no provider-hosted tool injection.- Responses
function,custom,namespace,local_shell,apply_patch, and computer-use tools require Pi-side execution and are rejected here. Pi already supplies its own function tools. - Responses
image_generationis rejected because Pi's current Responses adapter cannot retain its image output. - Anthropic
bash_*,computer_*,memory_*, andtext_editor_*definitions require a client-side execution loop and are rejected. - Provider-specific call/result blocks and citation metadata are not persisted as Pi tool calls. The provider executes the configured tool inside the model request, and Pi retains the final assistant text. Generated files and other provider-only artifacts may therefore be unavailable.
- MCP
authorization, custom MCP headers, and other secrets are sent unchanged and stored as clear text insub2api.json; protect the file accordingly.
When api is fixed, only its matching serverTools group is accepted. An auto-routed relay may configure both groups; each discovered model receives only the group matching its selected API.
Routing behavior
When a provider does not set api, model IDs select an API as follows:
claude-*→anthropic-messages- OpenAI IDs such as
gpt-*,codex-*,chatgpt-*, ando3-*→openai-codex-responses - Other IDs, including
grok-*→openai-responses
Anthropic models are registered directly with pi's built-in Anthropic Messages implementation and a model-level base URL without /v1. Claude 4.6+ models receive compat.forceAdaptiveThinking.
Codex models use pi's Codex Responses adapter so requests keep the Codex shape (instructions, store: false, encrypted reasoning, forced SSE). Sub2API relays do not expose ChatGPT's /v1/codex/responses passthrough route, so at the request boundary the extension rewrites the request URL to the relay's standard /v1/responses endpoint, drops the generated chatgpt-account-id header, and replaces only the fake JWT bearer credential with the relay token.
Codex models also use OpenAI's Remote Compaction V2 protocol when pi runs /compact, threshold compaction, or overflow recovery. The extension serializes pi's active session into Responses input items, appends a compaction_trigger, and streams POST /v1/responses with x-codex-beta-features: remote_compaction_v2 and the real relay bearer token. It retains the newest replayable user messages locally within budgets of 64,000 estimated tokens and 16 MiB, collects exactly one encrypted compaction output item from response.output_item.done, waits for response.completed, and persists that native window in the compaction entry's details. Before later Codex Responses calls, it replaces pi's textual summary replay plus its pre-compaction kept window with the stored native window, followed by the live post-compaction tail. Repeated compactions compact the previous native window together with the new tail instead of nesting textual checkpoint markers.
Transient native compaction failures are retried five times before fallback, using one-, two-, four-, eight-, and sixteen-second exponential backoff. Retries cover transport and timeout failures, incomplete or explicitly failed upstream SSE streams, and HTTP 408, 425, 429, 500, 502, 503, and 504 responses; permanent HTTP and protocol failures return immediately. If the first native compact request still fails, pi's normal textual compaction remains the fallback. Once an opaque native checkpoint is active, a failed repeated compaction is cancelled rather than replacing the only replayable checkpoint with a summary of its local marker. Native checkpoints are bound to the provider, model, API, and normalized relay Responses URL; switching any of those leaves the opaque state untouched rather than forwarding it to a different endpoint. Existing version-1 checkpoints created through the retired standalone endpoint remain replayable and are upgraded when they are compacted again.
When a relayed Codex stream reports or throws an upstream error, including Upstream request failed and stream_read_error, the extension retries the complete model request indefinitely. Context-window overflow errors are returned immediately instead, allowing Pi to run its normal compaction recovery. Retry delays start at one second and double after each failure up to a maximum interval of 30 minutes. While an attempt or a longer backoff is still active, no-op stream activity is emitted every 30 seconds so Pi's stream watchdog does not mistake the extension's buffered retry cycle for a stalled provider. Cancelling the active turn aborts both the backoff wait and further retries. To prevent partial text or tool calls from a failed attempt leaking into the session, relayed Codex output is released to pi only after an attempt succeeds.
Run /toggle-fast to enable OpenAI Fast mode for the current session. While enabled, requests for OpenAI model IDs routed through Codex Responses, OpenAI Responses, or Chat Completions override the top-level service_tier with "priority", and the usage footer adds [FAST]. Run the command again to disable it. The relay remains responsible for model/account eligibility and Fast-mode policy; current Sub2API versions derive the upstream x-codex-routing-hint from the final model and service tier, so the extension does not forge that gateway-owned header.
Reasoning models use the upstream Codex manifest's supported_reasoning_levels when available, so pi exposes only the efforts declared for each model. Pi has one highest UI level, max; by default it sends the upstream max effort when advertised. Run /toggle-ultra to switch the current session into ultra mode: the command selects Pi's max level, remaps it to the upstream ultra effort for models that advertise it, and adds [ULTRA ENABLED] to the usage footer. Run it again to restore the normal max wire value. This lets models such as gpt-5.6 use their highest gpt-5.6-sol effort without adding a Pi-only level name. minimal still clamps to low because relayed Codex backends reject the minimal and none efforts. The Codex adapter omits the reasoning field when thinking is off; for plain OpenAI Responses models, off is not selectable, which makes that adapter omit the field as well. If the manifest is unavailable or omits the capability field, the previous low through xhigh compatibility mapping remains the fallback.
OpenAI Responses and Chat Completions models use pi's built-in implementations and call /v1/responses and /v1/chat/completions, respectively.
Quota reporting
At startup and during quota refreshes, the extension queries Sub2API's API-key billing endpoint, GET /v1/sub2api/billing. When it returns a valid v1 token-billing contract, the extension applies its effective_rate_multiplier to pi's built-in per-token prices for known models, including long-context pricing tiers. Models absent from pi's catalog keep zero estimated prices because Sub2API does not expose an absolute model price table through this endpoint.
For the active provider, the extension also probes a root /usage compatibility route and then Sub2API's official GET /v1/usage endpoint. A recognized response may contain key quotas, rate_limits, subscription daily/weekly/monthly usage, daily_usage, and usage.today/usage.total data; camelCase aliases are accepted for compatible relays.
Usage refreshes run in the background on session_start, model_select, and turn_end. The extension registers /toggle-ultra and /toggle-fast, and installs a custom footer that keeps the project path first, followed by pi's token/model statistics and other extension statuses, then adds usage as its own final row. Keeping usage outside the shared status row prevents it from being joined with MCP and similar extension statuses:
my-relay · loading…
my-relay · d 24% · w 11% · m 7%
my-relay · d $200.74 · 317.7m tok [ULTRA ENABLED] [FAST]
The dedicated usage row intentionally omits leading padding, status icons, the usage label, and billing multipliers to keep the completed state compact. Providers without a compatible usage endpoint continue to work normally; their usage row changes to my-relay · usage unavailable instead of disappearing silently. Pi exposes one custom-footer slot, so another extension that replaces the entire footer can override this layout; extensions that only call setStatus, including MCP status integrations, remain on their own row above usage.
The billing endpoint reports a token-price multiplier, not the purchase price of a subscription plan. The latter is not available through Sub2API's API-key-authenticated contract.
Additional behavior:
- Model discovery and billing requests use a 5-second timeout per attempt. Usage requests use a 30-second timeout because
/v1/usagemay aggregate a large history before responding. Transient network errors plus HTTP 408, 425, 429, 500, 502, 503, and 504 responses are retried up to twice with one- and two-second exponential backoff. - Models whose IDs start with
gpt-imageare excluded. - IDs containing
claude,codex,gpt-5, orgpt-6are exposed as reasoning models. GET /v1/modelsremains the authoritative model inventory. When an OpenAI model is missing token limits or reasoning-effort capabilities, the extension best-effort merges metadata for the same model ID from Sub2API'sGET /backend-api/codex/modelsmanifest; manifest-only models are never registered. Thegpt-5.6inventory alias usesgpt-5.6-solmetadata.- Remote model metadata accepts
context_window,contextWindow,context_length,max_context_tokens,limit.context, andlimits.contextfor context size. Output limits acceptmax_tokens,maxTokens,max_output_tokens,max_completion_tokens,limit.output, andlimits.output; the first valid positive integer is used, so an invalid earlier alias does not hide a valid later one. Supported thinking efforts are read fromsupported_reasoning_levelsorsupportedReasoningLevels, accepting both Codex object entries such as{ "effort": "ultra" }and string values. - Missing OpenAI model limits are filled from pi's catalog for the selected API (
openai-codexoropenai). Only fields still unavailable after remote and catalog lookup fall back to a 200,000-token context window and a model-family-specific output limit. - Each Codex remote compaction attempt has a three-minute timeout. Attempts reject redirects, cap the SSE response at 32 MiB, and retain at most 64,000 estimated message tokens or 16 MiB of replay messages so native checkpoints remain bounded.
- Network interception is request-scoped; process-wide transports are never patched.
Security
The configuration contains plaintext API tokens. Keep it outside repositories and restrict its permissions:
chmod 600 ~/.pi/agent/sub2api.json
Use HTTPS for remote relays. Plain HTTP is accepted for trusted local development endpoints only. Base URLs containing embedded credentials, query strings, or fragments are rejected. Authenticated discovery, Codex-manifest, billing, quota, and wrapped Codex requests reject redirects, and discovery/manifest/billing/quota JSON responses are capped at 1 MiB.
Troubleshooting
- Provider does not appear: inspect stderr for
[sub2api] failed to load ...; verify that the JSON is valid and every entry has non-emptybaseURLandtokenstrings. - No models appear: verify that
<baseURL>/v1/modelsis reachable withAuthorization: Bearer <token>. Discovery failures are logged as[sub2api:<provider>] failed to fetch models. - Requests fail: confirm the configured API is supported by the relay: Anthropic Messages uses
/v1/messages, Codex generation and Remote Compaction V2 both use/v1/responses, OpenAI Responses uses/v1/responses, and Chat Completions uses/v1/chat/completions. - Fast mode is rejected: verify that the selected OpenAI model/account supports
service_tier: "priority"and that the Sub2API OpenAI Fast policy allows it./toggle-fastdeliberately leaves relay eligibility and policy enforcement to Sub2API. - Native compaction falls back: verify that the relay supports Remote Compaction V2 on streamed
POST /v1/responses: the request must end with acompaction_trigger, and the SSE response must contain exactly one encryptedcompactionitem inresponse.output_item.donefollowed byresponse.completed. The extension also accepts thecompaction_summarytype emitted by compatible Codex relays. - Usage unavailable: verify that the relay's
/v1/usageendpoint returns JSON with subscription, quota, rate-limit, or usage data for the configured bearer token. The root/usageroute is probed only for compatibility with other relays. - Price multiplier missing: verify that
<baseURL>/v1/sub2api/billingreturns the v1 token-billing contract:object: "sub2api.key_billing",schema_version: 1,billing_scope: "token", non-negativegroup_rate_multiplier,resolved_rate_multiplier, andeffective_rate_multipliervalues, plus a booleanpeak_rate_enabled. Sub2API simple mode returns 404 and therefore uses pi's standard built-in prices. - Custom agent directory: ensure
sub2api.jsonis directly inside the directory named byPI_CODING_AGENT_DIR.
Development
From the repository root:
npm install
npm run format
npm run lint
npm run check
npm run pack:sub2api
Release checklist
Releases use npm trusted publishing through .github/workflows/publish.yml; no long-lived npm token is stored in GitHub. A release run checks every workspace and publishes each local package version that does not already exist on npm, so pi-provider-sub2api and pi-continue can ship together or independently from the same workflow.
Trusted publisher setup
Each npm package's GitHub Actions trusted publisher is configured with:
- Organization or user:
5aaee9 - Repository:
pi-agent-extensions - Workflow filename:
publish.yml - Allowed action:
npm publish
For each release:
- Update the version and
CHANGELOG.mdfor each package being released. - Run
npm run checkand inspect the correspondingnpm run pack:*commands. - Commit and push the release commit to
main. - Create and push the next repository
v*release tag. - Confirm the Publish Packages workflow completed, then create the matching GitHub release.
The workflow fails when every local workspace version is already published. Otherwise it skips existing versions and publishes only unpublished ones from a GitHub-hosted runner using npm's short-lived OIDC credentials.
Acknowledgements
The quota workflow is informed by the MIT-licensed dereknex/pi-sub2api-provider project and adapted to this package's sub2api.json configuration and native Pi provider routing. The native compaction hook design is informed by the MIT-licensed jordyvandomselaar/pi-openai-compaction extension and adapted to Sub2API relay authentication and routing.
License
MIT