- TypeScript 87.8%
- JavaScript 12.2%
pi's anthropic-messages API uses the Anthropic SDK, which appends its own /v1/messages to baseUrl. aor's OpenAI-style root already includes /v1, so Anthropic models were being sent to /v1/v1/messages, which aor misclassifies as OpenAI and round-trips through the OpenAI->Anthropic translator. Strip the trailing /v1 for anthropic models; keep it for openai/google. |
||
|---|---|---|
| scripts | ||
| src | ||
| .gitignore | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
pi-aor
A pi extension for Actually Open Router (aor).
It exposes your aor instance as a single aor provider in pi. Every model is registered with the protocol aor reports for it (openai / anthropic / google) — pi dispatches each request on the model's api, so requests reach aor in the shape its serving upstream expects:
aor api |
pi API |
|---|---|
openai |
openai-completions |
anthropic |
anthropic-messages |
google |
google-generative-ai |
All models share one sk-aor-... key, so there is one provider, one /login, one credential.
Install
pi install /path/to/pi-aor
or point pi at the repo directly for a single run:
pi -e /path/to/pi-aor/src/index.ts
Configuration
The base URL lives in a user config file — no source edits:
// ~/.pi/agent/aor.json
{ "baseUrl": "https://aor.example.com/v1" }
baseUrl is your aor instance's OpenAI-style root (the part before /chat/completions). Change the file and /reload.
If aor.json is missing, the environment variable AOR_BASE_URL is used as a fallback. When neither is set the provider starts empty (no models), and the first /login aor prompts you for the URL.
First run (/login aor)
The standard pi login flow bootstraps everything in one step:
- Run
/login aor. If no base URL is configured yet, pi asks for it — e.g.https://aor.example.com/v1— validates it againstGET {url}/models, discovers the model catalog, writes~/.pi/agent/aor.json, and registers the models immediately. - pi then asks for your
sk-aor-...key and stores it inauth.json(the standard pi credential store;/logoutmanages it). - Models appear in
/modelas soon as the key is saved — no/reloadneeded.
After that, /login aor just asks for the key, like any other provider. To point at a different server later, edit aor.json and /reload.
How it works
On startup the extension's async factory resolves the base URL (aor.json → AOR_BASE_URL → nothing). If a URL is configured it fetches GET {baseUrl}/models (no key needed for the default public endpoint) and registers a single aor provider carrying every discovered model with its protocol-specific api. cost and limit metadata from the same response feed pi's $ spent and % context used displays.
The provider is always registered — even with an empty catalog and no base URL — so /login aor is available on a cold first run.
Notes / limitations
-
Discovery is at startup. If aor isn't reachable when pi starts, the catalog is empty (the provider still exists for
/login). Start aor, then/reload. -
reasoningand image input default to off. aor does not advertise these capabilities per model, so pi-aor registers every model withreasoning: falseandinput: ["text"]. Enable them per model withmodelOverridesin~/.pi/agent/models.json, for example:{ "providers": { "aor": { "modelOverrides": { "some-reasoning-model": { "reasoning": true }, "some-vision-model": { "input": ["text", "image"] } } } } }Overrides apply on top of the registered models and reload when you open
/model. -
OpenAI-shape requests are kept maximally compatible. Because aor forwards the request body verbatim to an unknown upstream,
openaimodels use conservativecompatsettings (max_tokens,systemrole, nostore, nostricttools, noreasoning_effort). If your upstream supports more, loosen viamodelOverrides. -
Mixed-protocol models. A model backed by providers of more than one protocol type is reported by aor with an array
apifield. Since one provider can hold each model id once, pi-aor picks one protocol for it deterministically —openaiwhen available, else the first known shape aor reports. Both shapes are valid for such a model; split it into separate catalog ids in aor's config if you want a specific one. -
No protocol translation. aor forwards the client's request shape verbatim to the matching upstream, so each model's
apialways matches what its upstreams actually speak. -
Cost/limit metadata comes from aor's primary (highest-priority) provider for each model. When limits or pricing vary greatly across providers for the same model, consider splitting into separate catalog models in aor's config so clients get deterministic metadata.
LICENSE
MIT