# pi-grok-build

xAI Grok Build for [pi](https://github.com/earendil-works/pi): OAuth login
(Grok subscription) plus Grok chat models over the OpenAI Responses API.
**OAuth only — API keys are deliberately unsupported in this project.**

One dependency-free core with two adapters:

| Entry point | File | Use it for |
|---|---|---|
| `@rise-so/pi-grok-build/extension` | `src/extension.ts` | pi coding-agent extension: `/login` offers "xAI (Grok subscription)", `pi --list-models` shows the models |
| `@rise-so/pi-grok-build` | `src/provider.ts` | a pi-ai `Provider<"openai-responses">` for programs composing their own `Models` registry (pi-ai / pi-agent core) |
| `@rise-so/pi-grok-build/core` | `src/xai-grok-build.ts` | the raw OAuth flow (`loginXai`, `refreshXai`) and model metadata, no pi-ai runtime imports |

## Models

| Model | Context | Pricing (per Mtok in/out/cache-read) | Notes |
|---|---|---|---|
| `grok-4.5` | 500k | $2.00 / $6.00 / $0.50 | reasoning model; documented efforts **low / medium / high** (default high, can't be disabled); pricing live-verified; encoded long-context tier doubles past 200k |
| `grok-composer-2.5-fast` | 200k | $3.00 / $15.00 / $0.50 | listed by the CLI proxy; prompt-sensitive; context/output metadata from third-party registries |

`grok-build-0.1` is deliberately not registered. It can emit reasoning tokens
but rejects explicit reasoning effort, which makes it a poor fit for Pi's
reasoning-level controls.

Composer works under Pi's normal system prompt and output budget, but live
probes found that tiny output caps can spend the budget on thinking without a
visible answer. Raw calls without a strong system prompt can also drift into
unrelated task/tool-looking text. It remains `reasoning: false` because xAI
rejects explicit reasoning effort for it; Pi can still parse thinking blocks
when the model emits them.

Reasoning models always request `include: ["reasoning.encrypted_content"]`
without forcing an effort. Pi uses `store: false` and replays full history from
session files, so opaque reasoning data remains available after reload.

## Use as a pi extension

For development, load the extension directly from a checkout:

```sh
pi -e /path/to/pi-grok-build
```

To test the same physical-copy shape used by a stable home-directory install,
pack the current checkout and replace `~/.pi/agent/extensions/pi-grok-build`:

```sh
make install-extension
```

The target refuses to run while the npm-managed package is installed or still
configured, because loading both copies would register the provider and its
request hook twice. Override the Pi home for isolated tests with
`PI_AGENT_DIR=/path/to/test-agent make install-extension`.

For a permanent install, copy the project into pi's extensions directory —
a copy gives pi a stable snapshot that won't shift underneath it as the
checkout changes:

```sh
cp -R /path/to/pi-grok-build ~/.pi/agent/extensions/pi-grok-build
```

Then pick a model:

```sh
pi --model xai-grok-build/grok-4.5
pi --model xai-grok-build/grok-4.5 -p "one-shot prompt"
pi --model xai-grok-build/grok-4.5:high -p "harder task"
pi --model xai-grok-build/grok-composer-2.5-fast -p "one-shot prompt"
```

No `npm install` is needed for pi imports — pi's loader aliases
`@earendil-works/pi-*` to its own bundled copies. The extension registers the
provider id `xai-grok-build`; log in with `/login` (OAuth is the only
credential — the provider intentionally has no API-key path).

### Install as a pi package

This repo is a pi package (`package.json` declares the extension under the
`pi` key), so `pi install` can manage it — pi records the source in
`~/.pi/agent/settings.json` and loads the extension on every run:

```sh
pi install /path/to/pi-grok-build                # from a local checkout
pi install git:github.com/rise-so/pi-grok-build  # from a git remote (@tag to pin)

pi list                                      # confirm it's installed
pi remove /path/to/pi-grok-build             # uninstall
```

Add `-l` to `pi install` to record it in the project's `.pi/settings.json`
instead of your user settings.

## Use as a library provider

```ts
import { createModels } from "@earendil-works/pi-ai";
import { xaiGrokBuildResponsesProvider } from "@rise-so/pi-grok-build";

const models = createModels({ credentials: myCredentialStore }); // holds the OAuth credential
models.setProvider(
  xaiGrokBuildResponsesProvider(),
);

const model = models.getModel("xai-grok-build", "grok-4.5");
if (!model) throw new Error("model not registered");
const reply = await models.completeSimple(model, {
  messages: [{ role: "user", content: "hello", timestamp: Date.now() }],
});
```

The host owns credential storage: pass a `CredentialStore` holding the OAuth
credential (obtained via the provider's `auth.oauth.login`), and pi-ai handles
refresh through the provider's own `refresh`. `live/support.ts` has a working
file-backed example. Model constants (`GROK_MODELS`, `GROK_45_MODEL`, …)
are exported from the package root.

`@earendil-works/pi-ai` is a peer dependency (pinned to `>=0.80.0 <0.81.0`).
The package exports built output (`dist/`, `.js` + `.d.ts`), so it installs
cleanly from npm, a tarball, or a git dependency — Node refuses to type-strip
raw `.ts` under `node_modules`, so the build step is what makes copy-based
installs work. `pnpm install` builds `dist/` automatically (`prepare` script),
as does pnpm when consuming this repo as a git dependency.

## Provider id: `xai-grok-build`, never `xai`

Credentials live in pi's shared `~/.pi/agent/auth.json`, keyed by provider id.
Registering OAuth under `"xai"` would capture pi's built-in xAI provider:
every built-in Grok model would be billed to the subscription token,
`registerProvider` would wipe pi's built-in xAI models, and `/login` would
offer an API-key row that silently overwrites the OAuth credential. A distinct
id makes all of that structurally impossible.

## Load-bearing constraint (extension path)

`src/extension.ts` — and everything it imports at runtime — must never import
a `@earendil-works/pi-ai` subpath (`api/*`, `providers/*`) and must never
import `src/provider.ts`. pi's extension loader aliases the bare package
prefix to its compat bundle, so subpath specifiers resolve to nonexistent
paths: silent at typecheck, fatal at extension load. `extension.test.ts`
guards this with an import grep; keep it green.

## Caveats

- The OAuth device flow borrows the Grok CLI's public client id because xAI has
  no public client registration. xAI can break it at any time. Login polling is
  cancellable, honors the device-code expiry, and is capped at 30 minutes.
- OAuth is the only auth path, by policy. There is no `$XAI_API_KEY` handling,
  no API-key login row, and no reading of other programs' credential files.
- The Grok CLI proxy's live `GET /v1/models` lists both registered models.
  `maxTokens` output caps are registry metadata only; Composer's context/output
  figures and pricing come from third-party registries, not from xAI
  documentation.

## Development

```sh
pnpm install          # also builds dist/ via the prepare script
pnpm check            # oxlint + tsc --noEmit + unit tests (offline, always safe)
pnpm fmt              # oxfmt over the repo
pnpm build            # emit dist/ (.js + .d.ts) for library consumers
pnpm test:live        # live acceptance against the Grok CLI proxy (skips without a credential)
pnpm smoke:models     # print the live model catalog for your credential
pnpm smoke:extension  # end-to-end through a real `pi` binary
pnpm smoke:session    # two-process fixed-session-id reload smoke through pi
```

Type-checking and declaration/JavaScript emit use the TypeScript 7 native
compiler directly. This project does not consume the compiler API.

### Releases

`pnpm release` increments the patch version, runs the full release gate,
commits `Release X.Y.Z`, and creates `vX.Y.Z` locally. By default it prints the
push and npm publish commands instead of running them:

```sh
pnpm release                 # increment patch; commit + tag locally
pnpm release 0.3.0           # explicit exact X.Y.Z version
pnpm release 0.3.0 --push    # also atomically push main and the tag
pnpm release 0.3.0 --push --publish  # also publish publicly with npm tag latest
```

Explicit versions reject a `v` prefix, prerelease/build suffixes, leading
zeroes, downgrades, existing tags, and versions already present on npm. Passing
the current version resumes a prepared release only when its tag points at
`HEAD`, which is useful after completing npm web/OTP authorization.

The pi extension path (`pi -e`, `pi install`, the extensions directory) needs
no build: pi's loader executes `src/extension.ts` directly, per the `pi` key
in `package.json`. Only library consumers go through `dist/`.

Unit tests (`src/*.test.ts`) run offline and capture the final serialized wire
payload where needed. Live tests (`live/*.test.ts`) hit the Grok CLI proxy with the stored
Pi OAuth credential and are never part of `pnpm check`; they include focused
prompt-cache and low-effort reasoning probes. Acceptance criteria and the full
test layering are documented in `ACCEPTANCE.md`.
