@indexyz/pi-provider-sub2api

A pi provider extension for Sub2API-compatible relays with model discovery and quota reporting

Packages

Package details

extension

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/models and 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, and web_search_preview_2025_03_11.
  • File search: file_search; vector_store_ids must contain at least one ID.
  • Hosted Python: code_interpreter; set container to a container ID or { "type": "auto" }.
  • Hosted shell: shell; environment must be container_auto or container_reference. Container skills and network policies may be nested in that environment.
  • Remote MCP/connectors: mcp; set server_label, either server_url or connector_id, and require_approval to "never". Pi cannot answer an upstream MCP approval prompt.
  • Deferred hosted tool discovery: tool_search; execution must 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-completions has 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_generation is rejected because Pi's current Responses adapter cannot retain its image output.
  • Anthropic bash_*, computer_*, memory_*, and text_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 in sub2api.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-*, and o3-*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/usage may 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-image are excluded.
  • IDs containing claude, codex, gpt-5, or gpt-6 are exposed as reasoning models.
  • GET /v1/models remains 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's GET /backend-api/codex/models manifest; manifest-only models are never registered. The gpt-5.6 inventory alias uses gpt-5.6-sol metadata.
  • Remote model metadata accepts context_window, contextWindow, context_length, max_context_tokens, limit.context, and limits.context for context size. Output limits accept max_tokens, maxTokens, max_output_tokens, max_completion_tokens, limit.output, and limits.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 from supported_reasoning_levels or supportedReasoningLevels, 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-codex or openai). 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-empty baseURL and token strings.
  • No models appear: verify that <baseURL>/v1/models is reachable with Authorization: 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-fast deliberately 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 a compaction_trigger, and the SSE response must contain exactly one encrypted compaction item in response.output_item.done followed by response.completed. The extension also accepts the compaction_summary type emitted by compatible Codex relays.
  • Usage unavailable: verify that the relay's /v1/usage endpoint returns JSON with subscription, quota, rate-limit, or usage data for the configured bearer token. The root /usage route is probed only for compatibility with other relays.
  • Price multiplier missing: verify that <baseURL>/v1/sub2api/billing returns the v1 token-billing contract: object: "sub2api.key_billing", schema_version: 1, billing_scope: "token", non-negative group_rate_multiplier, resolved_rate_multiplier, and effective_rate_multiplier values, plus a boolean peak_rate_enabled. Sub2API simple mode returns 404 and therefore uses pi's standard built-in prices.
  • Custom agent directory: ensure sub2api.json is directly inside the directory named by PI_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:

  1. Update the version and CHANGELOG.md for each package being released.
  2. Run npm run check and inspect the corresponding npm run pack:* commands.
  3. Commit and push the release commit to main.
  4. Create and push the next repository v* release tag.
  5. 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