No description
  • TypeScript 87.8%
  • JavaScript 12.2%
Find a file
Trevin Jones 8fd7db30c5 Fix Anthropic-protocol models hitting a doubled /v1 path
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.
2026-08-19 21:58:58 -06:00
scripts Initial commit 2026-07-31 11:07:10 -06:00
src Fix Anthropic-protocol models hitting a doubled /v1 path 2026-08-19 21:58:58 -06:00
.gitignore Initial commit 2026-07-31 11:07:10 -06:00
LICENSE Initial commit 2026-07-31 11:07:10 -06:00
package-lock.json Initial commit 2026-07-31 11:07:10 -06:00
package.json Initial commit 2026-07-31 11:07:10 -06:00
README.md Initial commit 2026-07-31 11:07:10 -06:00
tsconfig.json Initial commit 2026-07-31 11:07:10 -06:00

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:

  1. Run /login aor. If no base URL is configured yet, pi asks for it — e.g. https://aor.example.com/v1 — validates it against GET {url}/models, discovers the model catalog, writes ~/.pi/agent/aor.json, and registers the models immediately.
  2. pi then asks for your sk-aor-... key and stores it in auth.json (the standard pi credential store; /logout manages it).
  3. Models appear in /model as soon as the key is saved — no /reload needed.

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.jsonAOR_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.

  • reasoning and image input default to off. aor does not advertise these capabilities per model, so pi-aor registers every model with reasoning: false and input: ["text"]. Enable them per model with modelOverrides in ~/.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, openai models use conservative compat settings (max_tokens, system role, no store, no strict tools, no reasoning_effort). If your upstream supports more, loosen via modelOverrides.

  • Mixed-protocol models. A model backed by providers of more than one protocol type is reported by aor with an array api field. Since one provider can hold each model id once, pi-aor picks one protocol for it deterministically — openai when 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 api always 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

CONTRIBUTING

See devtrev/CONTRIBUTING