pi-402

Pi Agent 402 extension: pay-per-request access to OpenAI-compatible inference providers that support 402 payment protocols (L402, x402, MPP), settled over a Nostr Wallet Connect (NWC) Lightning wallet.

Packages

Package details

extension

Install pi-402 from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-402
Package
pi-402
Version
0.2.1
Published
Jun 20, 2026
Downloads
not available
Author
rolznz
License
unknown
Types
extension
Size
281.5 KB
Dependencies
0 dependencies · 0 peers
Pi manifest JSON
{
  "extensions": [
    "dist/index.js"
  ],
  "image": "https://github.com/rolznz/pi-402/raw/master/media/screenshot.png"
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-402

A pi extension for pay-per-request AI inference against OpenAI-compatible providers that support a 402 payment protocol (L402, x402, MPP), settled over a Nostr Wallet Connect (NWC) Lightning wallet — no account, no API key, no prepaid balance. Each request pays its own invoice.

You choose the provider in setup (default blockrun.ai). pi-402 probes it and picks the cheapest route automatically:

  • L402-native provider (issues its own Lightning invoices) → paid directly.
  • Anything else (x402/USDC, MPP, …) → routed through the l402.space gateway, which bridges the provider's rail to a Lightning/L402 invoice we can pay.

So you can pay any 402-capable OpenAI-compatible provider with Lightning — even one that doesn't support L402 itself — without paying the gateway's cut when you don't have to.

/402-setup   →  paste an NWC connection secret, pick a model
/model       →  switch between the upstream's models (native pi UI, provider "l402")
… chat …     →  every request pays its own Lightning invoice via your wallet

How it works

Pi's provider machinery makes a plain OpenAI-style HTTP call; it can't run a 402 402 → pay → retry loop on its own. So pi-402 starts a tiny loopback proxy and registers an openai-completions provider that points at it. The proxy rewrites each request to the gateway and pays:

pi ──/v1/chat/completions──▶ 127.0.0.1:<port>
        rewrite to: https://l402.space/<url-encoded  <upstream>/chat/completions >
                                     │ 402 Payment Required (macaroon + invoice)
                                     │ pay invoice via NWC ──▶ preimage
                                     ▼ retry  Authorization: L402 <macaroon>:<preimage>
                                l402.space ──(L402 / x402 / MPP)──▶ upstream provider
pi ◀──────── 200 (streamed) ─────────┘
  • GET /v1/models is fetched directly from the upstream (free) to populate /model; it never goes through the gateway or pays.
  • POST /v1/chat/completions is wrapped for the gateway and triggers the pay-and-retry; streaming (SSE) works.
  • The proxy is a pure pass-through for the request/response body — pi already sends OpenAI-compatible requests, so nothing is rewritten except the URL.
  • Payment is retried up to 3 times with a fresh invoice if a Lightning payment flakes (e.g. RouteNotFound); a failed payment never settles, so retrying is safe.

Why a loopback proxy and not pi's hooks?

There is no extension hook that can catch a 402 and resubmit with payment: before_provider_request fires before the call, and after_provider_response is observe-only ({status, headers}, no body, returns void). Pi dispatches via the official openai SDK using the model's baseUrl, and ProviderConfig exposes no custom-fetch/retry seam — so pointing baseUrl at a local paying proxy is the clean, version-robust way to insert payment. (A streamSimple custom provider handler could also do it in-process, but it requires reimplementing the OpenAI⇄pi translation against pi-ai internals; not used here.)

Install

The extension ships as a single self-contained bundle (dist/index.js) with all runtime deps inlined — no npm install needed by the end user. pi loads it via the pi.extensions manifest in package.json.

pi install npm:pi-402                 # once published to npm
pi install /path/to/pi-402            # from a local checkout (after `npm run build`)
pi install git:github.com/you/pi-402  # from git

Restart pi (or /reload). You'll get /402-setup, /402-provider, and /402-status.

The published npm tarball contains only dist/index.js, package.json, and this README — the bundle is fully self-contained, so it works on a fresh machine with nothing else installed.

Usage

  1. /402-setup — paste your NWC connection string (nostr+walletconnect://…), then pick a provider from the list (blockrun.ai, tx402.ai, or Custom). For Custom you must enter the full base URL — including https:// and the API path, e.g. https://host/v1. It validates the wallet, probes the provider to pick direct-vs-gateway routing, loads the model catalog, and lets you pick a default model.
  2. Chat normally. Every request pays its own invoice (you'll see L402: paying N sats …), and pi's footer cost reflects what you actually paid: the sats spent on each request are converted to USD (BTC/USD rate fetched once at session start) and written into that request's cost. Switch models anytime with /model (provider l402).
  3. /402-provider — switch to a different provider without redoing setup. Keeps your connected wallet; just re-pick from the list (or Custom URL), then it reloads the model catalog and re-probes direct-vs-gateway routing. Switch models afterward with /model.
  4. /402-status — show provider, routing (direct/gateway), wallet balance, sats spent this session, current model, proxy URL — plus the NWC connection's spending budget (remaining / total / period) when it has one.

Spending limits

pi-402 auto-pays every invoice and adds no caps of its own. Put the budget on the NWC connection instead: when you create the connection in your wallet (Alby Hub, etc.), set a per-period spending limit and a max-amount. That's enforced by the wallet, survives across apps, and can't be bypassed by a buggy endpoint.

Configuration

Written to ~/.pi/agent/pi-402.json (mode 0600):

{ "nwcUrl": "nostr+walletconnect://…", "upstream": "https://blockrun.ai/api/v1", "useGateway": true, "defaultModel": "openai/gpt-4o-mini" }
  • upstream — any OpenAI-compatible API root that exposes /models and /chat/completions. Defaults to blockrun.ai.
  • useGateway — set by /402-setup from a probe: false when the provider is L402-native (paid directly), true otherwise (routed via the hardcoded https://l402.space gateway, which bridges x402/MPP/L402 rails to Lightning).
  • nwcUrl — a spending secret; the file is owner-only and never logged. Note: the /402-setup prompt is not masked.

Provider compatibility

The proxy is a pure pass-through — it inserts payment at the HTTP layer and never rewrites request or response bodies. A provider must be genuinely OpenAI-compatible (accept the OpenAI-standard request pi sends, including content-part arrays and the tools parameter). Providers that reject spec-compliant requests are not supported; the fix belongs on the provider.

Development / testing

npm install      # dev deps (also runs the build via `prepare`)
npm run build    # bundle index.ts + deps -> dist/index.js (esbuild)
npm run typecheck

A test wallet lives in .env.test (gitignored). The harnesses make real Lightning payments through l402.space using the cheapest models:

npm run models   # list cheapest chat models (no payment)
npm run e2e      # proxy → l402.space → upstream: paid completion + streaming
npx tsx --env-file=.env.test scripts/blockrun.ts   # array vs string content through l402.space
npx tsx --env-file=.env.test scripts/smoke.ts      # drive the extension via a mock pi API

To test the bundled extension exactly as users get it (no node_modules):

npm run build
pi --print -e ./dist/index.js --provider l402 --model openai/gpt-4o-mini "say pong"

Verified end-to-end inside real pi (headless):

export PI_402_NWC_URL="$(grep -oP '(?<=PI_402_NWC_URL=).*' .env.test)"
pi --print --no-extensions -e ./index.ts --no-tools --no-session \
   --provider l402 --model "openai/gpt-4o-mini" "say pong"

Set PI_402_DEBUG=1 to log upstream status / error bodies to stderr. Override the upstream for a run with PI_402_UPSTREAM=....

Layout

index.ts          extension entry: /402-setup, /402-provider, /402-status, provider registration
scripts/build.mjs esbuild bundle → dist/index.js (self-contained, deps inlined)
src/config.ts     load/save config; GATEWAY constant + gatewayUrl() URL wrapping
src/proxy.ts      loopback proxy: rewrite to gateway, 402 → pay → retry, stream
src/l402.ts       parse the L402 challenge, build the Authorization credential
src/nwc.ts        NWC client: payInvoice, balance, validate
src/models.ts     fetch upstream /models and map to pi's provider-model shape
scripts/e2e.ts    end-to-end proxy test (→ l402.space → upstream)
scripts/blockrun.ts  array-vs-string content probe through l402.space
scripts/smoke.ts  drives the extension via a mocked ExtensionAPI