pi-airpx
Log in to the airpx LLM proxy (https://airpx.cc) with an sk-proxy API key and auto-import its model catalog (prices, context windows, reasoning, vision) into the pi coding agent.
Package details
Install pi-airpx from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-airpx- Package
pi-airpx- Version
0.7.1- Published
- Aug 16, 2026
- Downloads
- 494/mo · 46/wk
- Author
- axel.baum.listo
- License
- MIT
- Types
- extension
- Size
- 64.3 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./extensions/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-airpx
Log in to the airpx LLM proxy with an sk-proxy-… key and auto-import its
model catalog (prices, context windows, reasoning/vision flags) into the
pi coding agent — instead of
hand-maintaining hundreds of entries in models.json.
- Proxy: https://airpx.cc — OpenAI-compatible endpoint at
https://airpx.cc/v1 - Package: https://www.npmjs.com/package/pi-airpx
- Source: https://github.com/axelbaumlisto/pi-airpx
The airpx proxy is an OpenAI-/Anthropic-compatible gateway that routes one
sk-proxy-… key to many upstream models (Claude, GPT, Gemini, Kimi, DeepSeek,
GLM, Qwen, Grok, …) with pooled keys, fallback routing, and unified pricing.
This plugin makes those models appear natively in pi's /model picker.
By default the plugin loads the proxy's main list: the anonymous
GET https://airpx.cc/v1/models response (proxy_public_models, the
super-current canonical models), with pricing and any global discount already
applied. It skips alias rows and provider-group pseudo rows, and it does not
load free/service models beyond what the main list already exposes.
Model ids and names are the proxy's, verbatim — the plugin keeps no rename or
relabel tables, so pi's picker shows exactly what the airpx landing page lists.
If a model is missing from pi, it is missing from proxy_public_models; publish
it on the server rather than patching the client.
The list is fetched anonymously (no
Authorizationheader) on purpose: a key's role is derived from its owner's user role, so a normal user key may return the full catalog instead. The curated main list is only obtainable anonymously.
Install / run
# ad-hoc
pi -e ./extensions/index.ts
# then inside pi:
/login airpx # paste your sk-proxy-... key
Or via env var (no /login needed):
AIRPX_API_KEY=sk-proxy-... pi -e ./extensions/index.ts
As an installed package — add to ~/.pi/agent/settings.json → packages:
{ "packages": ["npm:pi-airpx"] }
pi installs it from npm automatically. Then run /login airpx once and paste
your sk-proxy-… key (get one at https://airpx.cc).
Get a key
Sign in at https://airpx.cc, create an sk-proxy-… API key, and use it with
/login airpx (preferred — stored in auth.json with 0600) or, for headless
setups, AIRPX_API_KEY. The same key works with any OpenAI-compatible client
pointed at https://airpx.cc/v1.
Config
Every environment variable is optional — with a key saved by /login airpx the
plugin needs none of them. The plugin registers no CLI flags of its own; use the
standard pi --model airpx/<id>.
| Env | Default | Meaning |
|---|---|---|
AIRPX_API_KEY |
— | sk-proxy-... key (alternative to /login airpx) |
AIRPX_BASE_URL |
https://airpx.cc/v1 |
proxy base URL (use http://127.0.0.1:18100/v1 for local dev) |
AIRPX_ALL_MODELS |
— | set to 1 to fetch the FULL keyed catalog (still skips alias/pseudo rows) |
Credential precedence: auth.json wins over the environment. The stored
airpx entry is read first and AIRPX_API_KEY is only a fallback, so exporting
the variable while a credential is saved changes nothing — log out or edit
~/.pi/agent/auth.json to switch keys.
Store the key in auth.json (created 0600 by /login) rather than exporting
it from your shell: ANTHROPIC_API_KEY / OPENAI_API_KEY pointed at the proxy
are a common shortcut that backfires — pi then registers its own built-in
anthropic and openai providers from those names, and the same proxy shows up
a second time in /model under a different name and with pi's model metadata
instead of the catalog's.
How it works
- async factory resolves a key (
auth.jsonairpxentry →AIRPX_API_KEY), fetches/v1/models(anonymously by default), maps →pi.registerProvider("airpx", { models }). /login airpxprompts for the key, validates it with an explicit keyed probe (a bad/mistyped key is rejected, not silently saved), then fetches the default catalog and re-registers the provider live (no/reload).- The on-disk cache (
~/.pi/agent/airpx-catalog.json) is a last-good snapshot: a successful fetch replaces it with exactly the fresh response (so removed/re-priced models don't linger, andAIRPX_ALL_MODELSnever permanently pollutes the default list); the cache is used only as a fallback when a fetch fails, so pi still boots offline. - First-run hint: with no key configured the models load but pi keeps them
unavailable in
/modeluntil auth is set (documented pi behavior), so the plugin prints a one-line hint pointing you to/login airpx.
Field mapping (proxy → pi)
| proxy | pi |
|---|---|
id |
id |
display_name (else id) |
name |
reasoning |
reasoning |
supports_vision |
input: ["text","image"] else ["text"] |
context_window (0 → 128000) |
contextWindow |
max_output_tokens (0 → 16384) |
maxTokens |
effective_pricing|pricing .input |
cost.input (0 if absent) |
….output |
cost.output (0 if absent) |
….cache_read |
cost.cacheRead (0 if absent) |
….cache_write |
cost.cacheWrite (0 if absent) |
A small hand-maintained _OVERRIDES table plus apiFromCatalog() add per-model
tweaks. GPT-5.x rows default to api: "openai-responses" so Pi renders native
Responses reasoning modules; explicit proxy fields recommended_api/api_format
win once airpx exposes them. kimi-for-coding is renamed to Kimi K2.6 Coding.
Notes:
- Prices are runtime-only — they come from the live
/v1/modelsresponse and are never committed. Test fixtures deliberately use undiscountedpricing(not liveeffective_pricing) so they don't rot. - Only canonical ids are loaded — alias rows (those carrying
alias_of) are skipped, as are provider-group pseudo rows (e.g.anthropic, detected by a known provider name or by having neitherpricingnorsupported_parameters, not bycontext_window === 0).
Tests
npm test
# = node --experimental-strip-types --test
Pure, offline unit tests of the mapper (src/map.ts) against a frozen fixture
(test/fixtures/v1_models.sample.json) — no network.
Architecture
For the design rationale (module boundary, SOLID/DRY/KISS walkthrough, data flow, failure model, extension points) see docs/ARCHITECTURE.md.
TODO / open
- Map
thinkingLevelMapfromsupported_parameters(xhigh/max) per model. - Replace the transitional GPT-5.x fallback in
apiFromCatalog()once the proxy exposesrecommended_api/api_formatper model. - Decide
openai-completionsvsanthropic-messagesper model family (currently all viaopenai-completions; the proxy normalizes).