# pi-fast-all

One fast-mode toggle for [pi](https://pi.dev) that works across providers.

Providers all sell "pay more, go faster," but each spells it differently. `pi-fast-all`
keeps the differences in a rule table instead of in code, so one `/fast` covers all of
them — and adding a provider is a config edit, not a release.

| provider | mechanism |
| --- | --- |
| `anthropic` | body `speed: "fast"` + `anthropic-beta: fast-mode-2026-02-01` |
| `openai` | body `service_tier: "priority"` |
| `openai-codex` | body `service_tier: "priority"` |
| anything else | write a rule |

Fast tiers bill at roughly **2x** the standard token rate (2.5x on `gpt-5.5`), carry
their own rate limits, and — on Anthropic — do not share prompt-cache prefixes with
standard-speed requests. So this ships **off by default** and is a session toggle
rather than a persistent setting.

## Install

```bash
pi install npm:pi-fast-all
pi -e npm:pi-fast-all      # or try it for one run only
```

Then `/reload` (or restart pi).

## Usage

```
/fast          # toggle for this session
/fast on
/fast off
/fast once     # fast for the next turn only, then back to normal
/fast auto     # fall back to the config default
/fast status   # state, model, active mechanism, and what you have spent
/fast rules    # list every mechanism, flagging models that do not resolve
```

`/fast once` is the one to reach for. The usual case is "this next question is
hard, spend the money" — leaving the toggle on afterwards is how a 2x bill
happens by accident. It stays armed across the whole turn, tool calls included,
and disarms when pi settles.

Start a session with it already on:

```bash
pi --fast
```

While fast mode is on *and* the current model matches a rule, a `fast` marker appears
in the footer. `/fast status` explains itself when it isn't active:

```
Fast is on (session override) but inactive for anthropic/claude-sonnet-4-6:
anthropic has fast mode, but not for claude-sonnet-4-6.
```

## Rules

A rule declares how one provider does fast:

```jsonc
{
  "provider": "anthropic",              // required
  "api": "anthropic-messages",          // optional narrowing
  "models": ["claude-opus-5"],          // ids, "provider/id", or ["*"] for all
  "body": { "speed": "fast" },          // merged into the request body
  "headers": { "x-example": "1" },      // set on the model before requests
  "betaHeader": "fast-mode-2026-02-01", // appended to anthropic-beta, deduped
  "costMultiplier": 2,                  // what fast multiplies the bill by
  "costCountedByHost": false,           // true if pi already prices this tier
  "note": "~2x token cost"              // shown by /fast status
}
```

A rule applies only when provider, api, and model all match. If any `body` key is
already present on the payload, the extension backs off entirely — another extension
owns that knob.

## Config

Optional file at `<agent-dir>/extensions/pi-fast-all.json`
(stock pi: `~/.pi/agent/extensions/pi-fast-all.json`):

```json
{
  "enabled": false,
  "indicator": "widget",
  "indicatorText": "⚡fast",
  "indicatorColor": "#ffa657",
  "warnAboveUsd": 5,
  "extraRules": []
}
```

- `enabled` — state at session start. `/fast` overrides it for the session and is never written back.
- `indicator` — `widget` (above the editor), `status` (footer status line), `both`, or `off`. `widget` is the default because a host may replace pi's footer — arc did, and its replacement dropped every extension status until [that was fixed](#how-it-works).
- `indicatorText` / `indicatorColor` — the marker. A `#rrggbb` colour is emitted as a truecolor escape; anything else (`warning`, `accent`, …) goes through the theme.
- `warnAboveUsd` — recolour the marker once the fast premium passes this many dollars. `0` disables.
- `rules` — **replaces** the built-in rules.
- `extraRules` — **appends** to whichever rule set is in effect. Use this to add a provider without losing the defaults.

See [`pi-fast-all.example.json`](pi-fast-all.example.json).

## How it works

| Step | Mechanism |
| --- | --- |
| body patch | `before_provider_request` — pi lets extensions replace the serialized provider payload right before it's sent, and provider SDKs pass unknown body fields through. |
| headers | writing `ctx.model.headers` on `session_start` / `model_select`. |

`before_provider_headers` looks like the obvious hook for headers, but it only sees the
auth/options headers — not the `anthropic-beta` value the Anthropic client builds
internally — so setting `anthropic-beta` there would silently drop the Claude Code
betas on OAuth accounts. Writing `model.headers` is the layer that wins, and because it
overrides rather than appends, the extension re-adds `claude-code-20250219` and
`oauth-2025-04-20` when `ctx.modelRegistry.isUsingOAuth(model)` is true. Switching to a
model without a rule clears the header again.

### What it costs you

`/fast status` tracks only the requests this extension actually patched:

```
Fast is on (session override) and active for anthropic/claude-opus-5: speed="fast"
— ~2x token cost. 4 fast requests: $2.10 billed, ~$1.05 of that the fast premium.
pi's own cost display is short by ~$1.05 — it does not price Anthropic's speed tier.
```

That last sentence is not a rounding note. pi-ai reads `service_tier` back off OpenAI
responses and applies the multiplier, so OpenAI numbers are right. It has never heard
of Anthropic's `speed`, so it prices fast Anthropic turns at the **standard** rate and
pi's session cost is short by roughly the premium. This extension corrects for that
using each rule's `costMultiplier` / `costCountedByHost`. Your invoice remains the
source of truth.

Past `warnAboveUsd` (default `$5` of premium) the marker turns red.

## Development

```bash
npm install
npm run typecheck
npm test              # unit tests, no network
npm run test:wire     # end-to-end against a stub server (needs a pi on PATH)
```

`npm test` drives the extension through a fake of the pi API: rule matching, header
juggling, the OAuth beta case, one-shot mode, and spend accounting, all offline.

`npm run test:wire` is the one that catches integration lies. It points the real
`openai` provider at a local stub with `pi.registerProvider("openai", { baseUrl })`,
runs a real pi against it, and asserts `service_tier: "priority"` arrives in the HTTP
body — exercising the shipped default rule with no API key and no spend. Use
`PI_BIN=arc npm run test:wire` for a differently branded pi.

## Prior art

This exists because each of these solves one provider, and none of them generalize:

- [`@diegopetrucci/pi-claude-fast`](https://pi.dev/packages/@diegopetrucci/pi-claude-fast) — Anthropic `speed`, allowlist stops at Opus 4.8.
- [`pi-fast`](https://www.npmjs.com/package/pi-fast) — OpenAI Codex `priority`, hardcoded model set.
- [`@pi-plugins/fast-mode`](https://pi.dev/packages/@pi-plugins/fast-mode) — OpenAI `priority`, configurable model list.
- [`pi-fast-mode`](https://www.npmjs.com/package/pi-fast-mode) — `service_tier` per configured provider/model pair.

## License

MIT
