@lightninglabs/pi-402 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +270 -0
- package/LICENSE +21 -0
- package/README.md +192 -2
- package/package.json +47 -5
- package/src/CLAUDE.md +57 -0
- package/src/commands.ts +104 -0
- package/src/index.ts +138 -0
- package/src/payer.ts +34 -0
- package/src/policy.ts +209 -0
- package/src/provider.ts +568 -0
- package/src/store.ts +387 -0
- package/src/wallet.ts +1179 -0
- package/wasm/package.json +1 -0
- package/wasm/sqlite-bridge.js +240 -0
- package/wasm/sqlite-node-vfs.js +894 -0
- package/wasm/sqlite-worker.js +850 -0
- package/wasm/sqlite3.js +14318 -0
- package/wasm/sqlite3.wasm +0 -0
- package/wasm/wasm_exec.js +575 -0
- package/wasm/wavewalletdk.wasm +4 -0
package/INSTALL.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Installing pi-402
|
|
2
|
+
|
|
3
|
+
`pi-402` is a standard [Pi](https://pi.dev) extension. Pi can load an extension
|
|
4
|
+
from a managed package, from an auto-discovered directory, or from a per-run
|
|
5
|
+
flag, and pi-402 works with all three. Pi itself is not patched or forked. We
|
|
6
|
+
have verified the install paths below against Pi 1.1.0.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
You need Pi (`pi --version`) and Node.js 22.19 or newer. That is all for the
|
|
11
|
+
install. To make a payment you also need two things that pi-402 does not
|
|
12
|
+
provide:
|
|
13
|
+
|
|
14
|
+
- An L402 proxy that fronts a model, reachable at the URL you put in
|
|
15
|
+
`L402_PROXY_URL`. There is no hosted endpoint, so you run one yourself or use
|
|
16
|
+
one run by a provider. See [Bring your own seller](#bring-your-own-seller).
|
|
17
|
+
- A funded wallet. The wallet is built in and creates itself on first use, and
|
|
18
|
+
you fund it with `/fund` once Pi is running. See [Wallet](#wallet).
|
|
19
|
+
|
|
20
|
+
## Option 1: pi install from npm (recommended)
|
|
21
|
+
|
|
22
|
+
Pi's package manager downloads the package, records it in your settings, and
|
|
23
|
+
loads it on every run. The package carries the embedded wallet's WASM bundle
|
|
24
|
+
(about 137 MB unpacked, 22 MB to download), so nothing else needs building.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Global (user scope, ~/.pi/agent): available in every project.
|
|
28
|
+
pi install npm:@lightninglabs/pi-402
|
|
29
|
+
|
|
30
|
+
# Project scope only (recorded in ./.pi):
|
|
31
|
+
pi install npm:@lightninglabs/pi-402 -l
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The package declares its entry point in `package.json`, so Pi finds the
|
|
35
|
+
extension without any further wiring:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
"pi": { "extensions": ["./src/index.ts"] }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
To update or remove it later:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pi update --extensions
|
|
45
|
+
pi remove npm:@lightninglabs/pi-402 # alias: pi uninstall
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Installing from git instead
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pi install git:github.com/lightninglabs/pi-402
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
A git checkout does not contain the wallet's WASM bundle. It is about 137 MB, so
|
|
55
|
+
it ships in the npm package and not in the repository. After a git install, stage
|
|
56
|
+
a bundle built from [Wavelength](https://github.com/lightninglabs/wavelength)
|
|
57
|
+
(`make wasm-wallet` writes it to `bin/wasm`) into the package, or point
|
|
58
|
+
`PI402_WASM_DIR` at it:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
scripts/stage-wasm.sh /path/to/wavelength/bin/wasm # copies into ./wasm
|
|
62
|
+
# or, without copying anything:
|
|
63
|
+
export PI402_WASM_DIR=/path/to/wavelength/bin/wasm
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Option 2: auto-discovered directory
|
|
67
|
+
|
|
68
|
+
Pi scans two directories on startup and loads any extension it finds there:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Global: every project.
|
|
72
|
+
git clone https://github.com/lightninglabs/pi-402.git ~/.pi/agent/extensions/pi-402
|
|
73
|
+
|
|
74
|
+
# Project-local: only when Pi runs in this project.
|
|
75
|
+
git clone https://github.com/lightninglabs/pi-402.git ./.pi/extensions/pi-402
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Install its dependencies once with `npm install` inside the clone, and stage the
|
|
79
|
+
WASM bundle as described above.
|
|
80
|
+
|
|
81
|
+
## Option 3: per-run flag
|
|
82
|
+
|
|
83
|
+
Point Pi at a checkout for a single session, which is the usual way to develop
|
|
84
|
+
the extension:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pi -e /path/to/pi-402 # -e is short for --extension
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Where Pi looks for extensions
|
|
91
|
+
|
|
92
|
+
| Location | Scope | How it gets there |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `./.pi/extensions/` | Project | Option 2 (project) |
|
|
95
|
+
| `~/.pi/agent/extensions/` | Global | Option 2 (global) |
|
|
96
|
+
| Recorded packages in settings | Project or global | `pi install` (Option 1) |
|
|
97
|
+
| `--extension <path>` / `-e <path>` | This run only | Option 3 |
|
|
98
|
+
|
|
99
|
+
The agent directory is overridable via `PI_CODING_AGENT_DIR`.
|
|
100
|
+
|
|
101
|
+
## Bring your own seller
|
|
102
|
+
|
|
103
|
+
pi-402 is the buyer. The seller is an [aperture](https://github.com/lightninglabs/aperture)
|
|
104
|
+
proxy that fronts an OpenAI-compatible model endpoint, answers an unpaid request
|
|
105
|
+
with a `402` challenge carrying a Lightning invoice, and meters the paid bundle
|
|
106
|
+
as the model serves tokens. Aperture's
|
|
107
|
+
[`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
|
|
108
|
+
describes how that works, and
|
|
109
|
+
[lightmeter](https://github.com/lightninglabs/lightmeter) is the metering
|
|
110
|
+
controller that prices bundles. If you want to run the seller on your own
|
|
111
|
+
machine to try pi-402, `dev/regtest/README.md` and `dev/signet-demo.md` walk
|
|
112
|
+
through a complete stack.
|
|
113
|
+
|
|
114
|
+
Whoever runs the seller, you point pi-402 at it with one variable:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
export L402_PROXY_URL="https://your-aperture.example/v1"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The URL is the base that the OpenAI client appends `/chat/completions` to, so it
|
|
121
|
+
includes the API prefix (`/v1` for aperture). If it is unset, pi-402 uses
|
|
122
|
+
`http://localhost:8402`. The proxy holds the model credential. Pi never sees an
|
|
123
|
+
API key.
|
|
124
|
+
|
|
125
|
+
## Wallet
|
|
126
|
+
|
|
127
|
+
The wallet is Wavelength's, running inside the Pi process as WASM. There is no
|
|
128
|
+
daemon to start. On the first session it creates itself under
|
|
129
|
+
`~/.pi402/wallet` (override with `PI402_WALLET_DIR`) and writes its seed words
|
|
130
|
+
to `seed.txt` there, readable only by you. Back that file up. It is the only
|
|
131
|
+
copy, and the wallet cannot be recovered without it.
|
|
132
|
+
|
|
133
|
+
A running native `waved` can be used instead of the embedded wallet. Set
|
|
134
|
+
`PI402_WALLETD_GATEWAY` to its HTTP/JSON gateway (for example
|
|
135
|
+
`http://localhost:10031`) and pi-402 drives that daemon and loads no WASM.
|
|
136
|
+
|
|
137
|
+
### Choosing a network
|
|
138
|
+
|
|
139
|
+
`PI402_NETWORK` selects the network and defaults to `regtest`, which is for the
|
|
140
|
+
local dev stack. For anything real, set it explicitly. On `signet` and the
|
|
141
|
+
other test networks the embedded daemon finds the hosted Ark operator, swap
|
|
142
|
+
server and chain endpoints by itself. Only `regtest` needs them given, and it
|
|
143
|
+
defaults to the local compose stack's addresses; `PI402_ARK_SERVER`,
|
|
144
|
+
`PI402_SWAP_SERVER` and `PI402_ESPLORA_URL` override them on any network.
|
|
145
|
+
|
|
146
|
+
On non-mainnet networks the wallet uses a throwaway default password and
|
|
147
|
+
generates a seed without asking. Mainnet is stricter, because the funds are real:
|
|
148
|
+
|
|
149
|
+
- `PI402_WALLET_PASSWORD` must be set to a secret of your own. The extension
|
|
150
|
+
refuses to start a mainnet wallet with the built-in default.
|
|
151
|
+
- A fresh mainnet wallet will not generate a seed that would be written to disk
|
|
152
|
+
in the clear. Either restore a seed you already hold by setting
|
|
153
|
+
`PI402_WALLET_MNEMONIC` to its words, or set `PI402_ALLOW_PLAINTEXT_SEED=1` to
|
|
154
|
+
accept a plaintext `seed.txt` beside the wallet database.
|
|
155
|
+
- If `seed.txt` is present but the wallet database is gone, creation is refused
|
|
156
|
+
so the old words are never mistaken for the new wallet's backup. Restore from
|
|
157
|
+
the words with `PI402_WALLET_MNEMONIC`, or move the file away to start fresh.
|
|
158
|
+
|
|
159
|
+
### Funding
|
|
160
|
+
|
|
161
|
+
Inside Pi:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
/fund 50000 a Lightning invoice; pay it from the Wavelength app
|
|
165
|
+
/deposit an on-chain boarding address
|
|
166
|
+
/balance confirmed and pending balance
|
|
167
|
+
/wallet wallet status, and the balance in the status line
|
|
168
|
+
/receipts every payment made: count, all-time sats, and today's sats
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`/fund` is the quick path, since an invoice settles through a swap in seconds
|
|
172
|
+
while an on-chain deposit needs a confirmation and an Ark round. Whatever you
|
|
173
|
+
fund is the most the agent can ever spend.
|
|
174
|
+
|
|
175
|
+
## Configure and use
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
export L402_PROXY_URL="https://your-aperture.example/v1"
|
|
179
|
+
export PI402_NETWORK=signet
|
|
180
|
+
export PI402_MAX_SAT_PER_REQUEST=20000 # see the note below
|
|
181
|
+
export PI402_DAILY_CAP_SAT=100000
|
|
182
|
+
|
|
183
|
+
pi
|
|
184
|
+
# then, inside Pi:
|
|
185
|
+
/model l402-baseten/kimi-k3
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The per-request ceiling defaults to 1000 sats and is checked against the quoted
|
|
189
|
+
total outflow, meaning the invoice plus the swap fee. A seller that sells a
|
|
190
|
+
large token bundle in one invoice can quote more than that, and then the budget
|
|
191
|
+
policy declines the payment. Set the ceiling above the bundle price you expect.
|
|
192
|
+
|
|
193
|
+
| Env var | Default | Purpose |
|
|
194
|
+
|---------|---------|---------|
|
|
195
|
+
| `L402_PROXY_URL` | `http://localhost:8402` | Base URL of the L402 proxy, including the API prefix |
|
|
196
|
+
| `PI402_MAX_SAT_PER_REQUEST` | `1000` | Per-request spend ceiling, checked on the quoted total outflow |
|
|
197
|
+
| `PI402_DAILY_CAP_SAT` | `100000` | Daily spend ceiling, seeded from the receipt ledger on restart |
|
|
198
|
+
| `PI402_PAYMENT_SCHEME` | `auto` | `auto` takes a Payment offer when the seller makes one and falls back to L402; `l402` pins L402 |
|
|
199
|
+
| `PI402_MPP_INTENTS` | `charge` | Payment intents to accept. `session` is refused, since the swap-backed wallet cannot mint the refund invoice it needs |
|
|
200
|
+
| `PI402_NETWORK` | `regtest` | Network the embedded wallet runs on (`signet`, `mainnet`, and so on) |
|
|
201
|
+
| `PI402_WALLET_PASSWORD` | dev default | Wallet password; required on mainnet |
|
|
202
|
+
| `PI402_WALLET_MNEMONIC` | unset | Seed words to restore on first run instead of generating a wallet |
|
|
203
|
+
| `PI402_ALLOW_PLAINTEXT_SEED` | unset | Set to `1` to let a new mainnet wallet write a plaintext `seed.txt` |
|
|
204
|
+
| `PI402_WALLET_DIR` | `$PI402_DATA_DIR/wallet` | Where the embedded wallet keeps its databases and `seed.txt` |
|
|
205
|
+
| `PI402_DATA_DIR` | `~/.pi402` | Token cache, credential cache, receipt ledger and wallet |
|
|
206
|
+
| `PI402_WASM_DIR` | `<package>/wasm` | Directory holding the WASM bundle, overriding the bundled copy |
|
|
207
|
+
| `PI402_WALLETD_GATEWAY` | unset | Use a native `waved` at this gateway URL instead of the embedded wallet |
|
|
208
|
+
| `PI402_ARK_SERVER` | regtest: `127.0.0.1:7070` | Ark operator address; other networks resolve it by name |
|
|
209
|
+
| `PI402_SWAP_SERVER` | regtest: `127.0.0.1:8082` | Swap server address; other networks resolve it by name |
|
|
210
|
+
| `PI402_ESPLORA_URL` | regtest: `http://127.0.0.1:3002` | Esplora API; other networks resolve it by name |
|
|
211
|
+
| `PI402_SERVER_INSECURE` | `1` on regtest, else `0` | Plaintext transport to the operator and swap server |
|
|
212
|
+
| `PI402_DEBUG_LEVEL` | `error` | Embedded daemon log level (`debug`, `trace`, or `info,ROND=debug`) |
|
|
213
|
+
| `PI402_MOCK_PAYER` | unset | Set to `1` to skip Lightning and "pay" with a mock, against a mock proxy |
|
|
214
|
+
|
|
215
|
+
The embedded daemon logs through the Pi process, so it logs errors only by
|
|
216
|
+
default to keep its output off the TUI. Raise `PI402_DEBUG_LEVEL` when a swap
|
|
217
|
+
sticks.
|
|
218
|
+
|
|
219
|
+
## Trying it without a seller
|
|
220
|
+
|
|
221
|
+
`PI402_MOCK_PAYER=1` runs the buyer loop with no wallet and no Lightning, against
|
|
222
|
+
any mock L402 proxy such as `dev/mock-l402-proxy.ts`:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
export PI402_MOCK_PAYER=1
|
|
226
|
+
export L402_PROXY_URL="http://localhost:8402"
|
|
227
|
+
pi -e /path/to/pi-402
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Developing against a local aperture-ts checkout
|
|
231
|
+
|
|
232
|
+
The buyer loop comes from `@lightninglabs/aperture-client` and
|
|
233
|
+
`@lightninglabs/aperture-core`, which `npm install` fetches from npm. To work
|
|
234
|
+
against an unreleased [aperture-ts](https://github.com/lightninglabs/aperture-ts)
|
|
235
|
+
instead, build it and link the two packages into your checkout:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
cd aperture-ts && pnpm install && pnpm -r build
|
|
239
|
+
cd ../pi-402 && npm install --legacy-peer-deps
|
|
240
|
+
mkdir -p node_modules/@lightninglabs
|
|
241
|
+
ln -sfn ../../../aperture-ts/packages/core node_modules/@lightninglabs/aperture-core
|
|
242
|
+
ln -sfn ../../../aperture-ts/packages/client node_modules/@lightninglabs/aperture-client
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The Pi packages (`@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent`) are
|
|
246
|
+
peers. Pi provides them when it loads the extension, but `npm run check` needs
|
|
247
|
+
them installed in the checkout.
|
|
248
|
+
|
|
249
|
+
## Troubleshooting
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
pi -ne # start with no extensions, to isolate a load failure
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
If the extension fails to load, Pi prints `Failed to load extension "<path>"`
|
|
256
|
+
and the paid models never appear. The usual cause:
|
|
257
|
+
|
|
258
|
+
- `Cannot find module '@lightninglabs/aperture-client'`: dependencies are not
|
|
259
|
+
installed. Reinstall from npm, or run `npm install` in a checkout.
|
|
260
|
+
|
|
261
|
+
If the extension loads but the wallet cannot start, Pi keeps running and prints
|
|
262
|
+
`Extension "<path>" error: ...` at session start. The usual causes:
|
|
263
|
+
|
|
264
|
+
- `wasm bundle at ... is missing ...`: the bundle is incomplete or absent. Stage
|
|
265
|
+
one with `scripts/stage-wasm.sh`, or set `PI402_WASM_DIR`.
|
|
266
|
+
- `wasm bundle at ... would load as ES modules`: the bundle sits inside a
|
|
267
|
+
`"type": "module"` package without a `package.json` of its own. Re-run
|
|
268
|
+
`scripts/stage-wasm.sh`, or add a `package.json` containing
|
|
269
|
+
`{"type":"commonjs"}` to the bundle directory.
|
|
270
|
+
- A payment declined with a budget error: raise `PI402_MAX_SAT_PER_REQUEST`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lightning Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,193 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://pi.dev">
|
|
3
|
+
<img alt="pi logo" src="https://pi.dev/logo-auto.svg" width="128">
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
<p align="center">
|
|
7
|
+
<strong>pi-402</strong>: pay for inference with Lightning
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
|
-
|
|
10
|
+
# pi-402
|
|
11
|
+
|
|
12
|
+
`pi-402` is a [Pi](https://pi.dev) extension that pays for LLM inference over
|
|
13
|
+
Lightning. It loads an embedded
|
|
14
|
+
[Wavelength](https://github.com/lightninglabs/wavelength) wallet, registers a
|
|
15
|
+
paid model provider, and settles each inference call against an
|
|
16
|
+
[aperture](https://github.com/lightninglabs/aperture) proxy, so you can run Pi
|
|
17
|
+
against a metered model with sats instead of an API key.
|
|
18
|
+
|
|
19
|
+
No changes to Pi itself: this is a self-contained extension you drop into your
|
|
20
|
+
own Pi install.
|
|
21
|
+
|
|
22
|
+
Inference is priced by the token rather than by the request. One payment buys a
|
|
23
|
+
bundle of tokens, and the proxy debits what the model actually reported serving,
|
|
24
|
+
weighted by direction, until the bundle runs out and a fresh challenge arrives.
|
|
25
|
+
See [`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
|
|
26
|
+
in aperture for how that works end to end.
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Pi ──▶ pi-402 provider ──▶ OpenAI client (fetch = payingFetch) ──▶ aperture ──▶ Baseten
|
|
32
|
+
│ │ 402 → pay → retry │
|
|
33
|
+
└── Wavelength wallet ◀───┘ pays the BOLT11 invoice └── holds the model key
|
|
34
|
+
(Ark↔Lightning swap)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The extension registers a provider whose `streamSimple` owns the request path,
|
|
38
|
+
wrapping the model call in a paying `fetch`. When the proxy answers `402`, the
|
|
39
|
+
buyer loop runs your budget policy, pays the invoice through the embedded
|
|
40
|
+
wallet, caches the credential, and retries. Streaming included, and the agent
|
|
41
|
+
loop above it never learns that a payment happened.
|
|
42
|
+
|
|
43
|
+
The proxy offers two doors on the same `402` and the buyer takes whichever it
|
|
44
|
+
speaks: **L402** (a macaroon bound to the invoice's payment hash, which you can
|
|
45
|
+
attenuate offline and hand to a sub-agent) and the **Payment HTTP
|
|
46
|
+
Authentication Scheme** (`draft-httpauth-payment-00`, the IETF draft co-authored
|
|
47
|
+
by Stripe and Tempo). `PI402_PAYMENT_SCHEME` picks; the default takes a Payment
|
|
48
|
+
offer when the seller makes one and falls back to L402.
|
|
49
|
+
|
|
50
|
+
No API key ever reaches the buyer. Aperture holds the model credential and
|
|
51
|
+
injects it server-side, so the buyer holds sats and the seller holds the key.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pi install npm:@lightninglabs/pi-402
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Pi's package manager downloads the package and loads it on every run. The
|
|
60
|
+
package carries the embedded wallet's WASM bundle, so there is nothing to build
|
|
61
|
+
and no daemon to start. You need Node.js 22.19 or newer, and we have verified it
|
|
62
|
+
against Pi 1.1.0. [INSTALL.md](INSTALL.md) covers the alternatives (a git
|
|
63
|
+
install, an auto-discovered directory, and `pi -e` for development) and
|
|
64
|
+
troubleshooting.
|
|
65
|
+
|
|
66
|
+
pi-402 is the buyer half. It pays a seller, and there is no hosted seller to
|
|
67
|
+
point it at yet, so you bring your own.
|
|
68
|
+
|
|
69
|
+
## Bring your own seller
|
|
70
|
+
|
|
71
|
+
The seller is an [aperture](https://github.com/lightninglabs/aperture) proxy in
|
|
72
|
+
front of an OpenAI-compatible model endpoint, with metering turned on so a
|
|
73
|
+
payment buys a bundle of tokens that is drawn down as the model serves them.
|
|
74
|
+
Aperture's
|
|
75
|
+
[`docs/metering.md`](https://github.com/lightninglabs/aperture/blob/master/docs/metering.md)
|
|
76
|
+
explains the mechanism, and [lightmeter](https://github.com/lightninglabs/lightmeter)
|
|
77
|
+
is the controller that prices the bundles. Run your own, or use one a provider
|
|
78
|
+
runs, and point pi-402 at it:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export L402_PROXY_URL="https://your-aperture.example/v1" # include the /v1
|
|
82
|
+
export PI402_NETWORK=signet # defaults to regtest
|
|
83
|
+
export PI402_MAX_SAT_PER_REQUEST=20000 # above the bundle price
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The per-request ceiling defaults to 1000 sats and is checked against the quoted
|
|
87
|
+
total outflow, so raise it above whatever your seller's bundle costs or the
|
|
88
|
+
budget policy will decline the payment. `dev/regtest/README.md` and
|
|
89
|
+
`dev/signet-demo.md` bring up a complete seller and buyer on one machine, which
|
|
90
|
+
is the way to try pi-402 end to end.
|
|
91
|
+
|
|
92
|
+
## Use
|
|
93
|
+
|
|
94
|
+
Select a paid model and prompt:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
/model l402-baseten/kimi-k3 # 1M context, natively multimodal
|
|
98
|
+
/model l402-baseten/kimi-k3.1-pro # 1M context, cheaper than K3 upstream
|
|
99
|
+
/model l402-baseten/glm-4.7 # 200K context
|
|
100
|
+
/model l402-baseten/glm-5.2 # 200K context
|
|
101
|
+
/model l402-baseten/deepseek-v4-flash # 1M context, the cheap one
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
These are the models the extension registers. What a given seller actually serves
|
|
105
|
+
is up to the seller.
|
|
106
|
+
|
|
107
|
+
Wallet commands:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
/wallet show wallet status and balance
|
|
111
|
+
/fund <sats> show a Lightning invoice to fund the wallet (settles in seconds)
|
|
112
|
+
/deposit show an on-chain boarding address to fund the wallet
|
|
113
|
+
/balance show confirmed balance
|
|
114
|
+
/receipts show the payment ledger: count, all-time and today's sats
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`/fund` is the path to use. It mints a BOLT11 invoice that you pay from the
|
|
118
|
+
[Wavelength](https://wavelength.money) app on your phone, and the sats land in
|
|
119
|
+
seconds through an inbound swap. The wallet is an Ark wallet, so there are no
|
|
120
|
+
channels to open, no inbound liquidity to source, and nothing to keep online.
|
|
121
|
+
|
|
122
|
+
Whatever you fund is the ceiling on what the agent can ever spend, which is a
|
|
123
|
+
more comfortable thing to hand an autonomous process than a card.
|
|
124
|
+
|
|
125
|
+
## The wallet
|
|
126
|
+
|
|
127
|
+
The wallet runs inside the Pi process as WASM, shipped in the package. On the
|
|
128
|
+
first session it creates itself under `~/.pi402/wallet` and writes its seed
|
|
129
|
+
words to `seed.txt` there. Back that file up: it is the only copy.
|
|
130
|
+
|
|
131
|
+
On mainnet we are stricter, because the funds are real. `PI402_WALLET_PASSWORD`
|
|
132
|
+
must be set to a secret of your own, and a new wallet will not write a plaintext
|
|
133
|
+
seed unless you either restore words you already hold with
|
|
134
|
+
`PI402_WALLET_MNEMONIC` or accept the plaintext file with
|
|
135
|
+
`PI402_ALLOW_PLAINTEXT_SEED=1`.
|
|
136
|
+
|
|
137
|
+
Two overrides exist. `PI402_WASM_DIR` points at a different WASM bundle than the
|
|
138
|
+
bundled one, for example a fresh build from Wavelength. `PI402_WALLETD_GATEWAY`
|
|
139
|
+
points at a native `waved` that is already running, and pi-402 then drives that
|
|
140
|
+
daemon and loads no WASM.
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
| Env var | Default | Description |
|
|
145
|
+
|---------|---------|-------------|
|
|
146
|
+
| `L402_PROXY_URL` | `http://localhost:8402` | Base URL of the L402 proxy fronting the model, including the API prefix (for aperture, `/v1`) |
|
|
147
|
+
| `PI402_MAX_SAT_PER_REQUEST` | `1000` | Per-request spend ceiling, checked on the quoted total outflow |
|
|
148
|
+
| `PI402_DAILY_CAP_SAT` | `100000` | Daily spend ceiling, seeded from the receipt ledger on restart |
|
|
149
|
+
| `PI402_PAYMENT_SCHEME` | `auto` | `auto` takes a Payment offer when the seller makes one, else L402; `l402` forces the L402 door |
|
|
150
|
+
| `PI402_MPP_INTENTS` | `charge` | Comma-separated Payment intents to accept. Sessions need a return-invoice minter, which the swap-backed wallet cannot supply yet, so asking for `session` here throws. |
|
|
151
|
+
| `PI402_NETWORK` | `regtest` | Network the wallet runs on, for example `signet` or `mainnet` |
|
|
152
|
+
| `PI402_WALLET_PASSWORD` | dev default | Wallet password; mainnet refuses the default |
|
|
153
|
+
| `PI402_WALLET_MNEMONIC` | unset | Seed words to restore on first run instead of generating a wallet |
|
|
154
|
+
| `PI402_ALLOW_PLAINTEXT_SEED` | unset | `1` lets a new mainnet wallet write a plaintext `seed.txt` |
|
|
155
|
+
| `PI402_WALLET_DIR` | `$PI402_DATA_DIR/wallet` | Wallet databases and `seed.txt` |
|
|
156
|
+
| `PI402_DATA_DIR` | `~/.pi402` | Token cache, credential cache, receipt ledger, and the wallet |
|
|
157
|
+
| `PI402_WASM_DIR` | `<package>/wasm` | WASM bundle directory, overriding the bundled copy |
|
|
158
|
+
| `PI402_WALLETD_GATEWAY` | unset | Use a native `waved` at this HTTP/JSON gateway (e.g. `http://localhost:10031`) instead of the embedded wallet |
|
|
159
|
+
| `PI402_ARK_SERVER` | regtest only | Ark operator address; other networks resolve it by name |
|
|
160
|
+
| `PI402_SWAP_SERVER` | regtest only | Swap server address; other networks resolve it by name |
|
|
161
|
+
| `PI402_ESPLORA_URL` | regtest only | Esplora API; other networks resolve it by name |
|
|
162
|
+
| `PI402_SERVER_INSECURE` | `1` on regtest, else `0` | Plaintext transport to the operator and swap server |
|
|
163
|
+
| `PI402_DEBUG_LEVEL` | `error` | Embedded daemon log level. Quiet by default so it stays off the TUI; raise it (`debug`, or `info,ROND=debug`) when a swap sticks |
|
|
164
|
+
| `PI402_MOCK_PAYER` | unset | `1` uses the mock payer (no Lightning; dev only) |
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
This repo is also the dev environment for the extension.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install --legacy-peer-deps
|
|
172
|
+
npm run check # biome + tsc
|
|
173
|
+
npm test
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The buyer loop reuses [`@lightninglabs/aperture-client`](https://github.com/lightninglabs/aperture-ts)
|
|
177
|
+
(`createPayingFetch`) and [`@lightninglabs/aperture-core`](https://github.com/lightninglabs/aperture-ts)
|
|
178
|
+
(`InvoicePayer`, budget policies), both from npm. INSTALL.md has a note on linking
|
|
179
|
+
a local aperture-ts checkout instead.
|
|
180
|
+
|
|
181
|
+
The WASM bundle is not in git. To work on the embedded wallet, build it with
|
|
182
|
+
`make wasm-wallet` in Wavelength and run `scripts/stage-wasm.sh <path-to-bin/wasm>`.
|
|
183
|
+
`npm publish` refuses to run unless that staged bundle is complete.
|
|
184
|
+
|
|
185
|
+
## Docs
|
|
186
|
+
|
|
187
|
+
- [`docs/architecture.md`](docs/architecture.md): a map of the extension: components, request and payment pipelines, storage, and concurrency.
|
|
188
|
+
- [`docs/l402-walletdk-inference-payments.md`](docs/l402-walletdk-inference-payments.md): end-to-end design of the extension.
|
|
189
|
+
- [`docs/l402-protocol-extensions.md`](docs/l402-protocol-extensions.md): furthering L402 for metered / streaming inference: keep the macaroon + preimage atomic core, add the session economic model (verifiable receipts, HTLC-native refunds, a BOLT12 payment plane), informed by the Machine Payment Protocol.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@lightninglabs/pi-402",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Pay for Pi inference with Lightning: an L402 + walletdk extension for the Pi coding agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lightninglabs/pi-402.git"
|
|
10
|
+
},
|
|
11
|
+
"pi": {
|
|
12
|
+
"extensions": [
|
|
13
|
+
"./src/index.ts"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"wasm",
|
|
19
|
+
"README.md",
|
|
20
|
+
"INSTALL.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"stage-wasm": "scripts/stage-wasm.sh",
|
|
25
|
+
"prepublishOnly": "scripts/stage-wasm.sh --verify",
|
|
26
|
+
"check": "biome check --write --error-on-warnings . && tsc --noEmit",
|
|
27
|
+
"test": "node --test 'test/**/*.test.ts'",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"clean": "echo 'nothing to clean'"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@lightninglabs/aperture-client": "~0.0.1",
|
|
33
|
+
"@lightninglabs/aperture-core": "~0.0.1",
|
|
34
|
+
"openai": "6.26.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@earendil-works/pi-ai": "*",
|
|
38
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@biomejs/biome": "2.3.5",
|
|
42
|
+
"@types/node": "22.19.19",
|
|
43
|
+
"typescript": "5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.19.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/CLAUDE.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# pi-402/src
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The pi-402 extension itself: the code Pi loads to register a paid, L402-settled
|
|
6
|
+
model provider backed by an embedded Wavelength wallet.
|
|
7
|
+
|
|
8
|
+
## Module Structure
|
|
9
|
+
|
|
10
|
+
- `index.ts` — extension entry (`export default function (pi)`): registers the
|
|
11
|
+
provider, bootstraps the wallet on `session_start`, and wires the wallet
|
|
12
|
+
commands.
|
|
13
|
+
- `provider.ts` — `makeStreamSimple`: the provider's `streamSimple`. Builds an
|
|
14
|
+
OpenAI client whose `fetch` is `createPayingFetch(...)` and translates the
|
|
15
|
+
OpenAI SSE stream into Pi's `AssistantMessageEventStream`, text, reasoning
|
|
16
|
+
and tool calls alike (`BlockWriter`). This is the seam that injects payment
|
|
17
|
+
without touching Pi core.
|
|
18
|
+
- `payer.ts` — `InvoicePayer` implementations: `MockInvoicePayer` (dev) and
|
|
19
|
+
`WalletInvoicePayer` (delegates to `WalletService`).
|
|
20
|
+
- `wallet.ts` — `WalletService`: the embedded Wavelength WASM wrapper
|
|
21
|
+
(`prepareSend` → `sendPrepared` → await preimage; balance; boarding address).
|
|
22
|
+
- `policy.ts` — `buildBudgetPolicy`: per-request and daily spend ceilings; the
|
|
23
|
+
daily counter seeds from the receipt ledger on first assertion.
|
|
24
|
+
- `store.ts` — durable state under `PI402_DATA_DIR` (default `~/.pi402`):
|
|
25
|
+
`FileTokenStore` (paid L402 tokens, atomic rewrite, 0600) and
|
|
26
|
+
`FileReceiptLog` (append-only NDJSON spend ledger).
|
|
27
|
+
- `commands.ts` — `/wallet`, `/fund`, `/deposit`, `/balance`, `/receipts`. Two
|
|
28
|
+
funding paths: `/fund <sats>` returns a BOLT11 invoice (wallet `receive`,
|
|
29
|
+
settled through an inbound swap in seconds), `/deposit` returns an on-chain
|
|
30
|
+
boarding address (a confirmation plus an Ark round before it is spendable).
|
|
31
|
+
|
|
32
|
+
## Invariants
|
|
33
|
+
|
|
34
|
+
- `streamSimple` owns the whole request path; payment is injected here, nowhere
|
|
35
|
+
in Pi core. The OpenAI client sets `maxRetries: 0` so the paying fetch owns
|
|
36
|
+
retry semantics and the SDK never re-enters it to double-pay.
|
|
37
|
+
- The token cache is per provider instance and file-backed outside mock mode,
|
|
38
|
+
so paid tokens are reused until expiry, across process restarts, and retries
|
|
39
|
+
never re-pay. Every settled payment appends a self-verifying receipt
|
|
40
|
+
(`sha256(preimage) == payment_hash`) to the ledger; mock-mode spends never
|
|
41
|
+
touch either file.
|
|
42
|
+
- The budget policy runs before any wallet action, and the wallet re-asserts the
|
|
43
|
+
per-request ceiling on the *quoted total outflow* (invoice principal plus swap
|
|
44
|
+
fee) after `prepareSend`, before dispatching `send`/`sendPrepared`.
|
|
45
|
+
|
|
46
|
+
## Status / TODO
|
|
47
|
+
|
|
48
|
+
- `provider.ts` streams text, reasoning and tool calls. Declaring Pi's tools on
|
|
49
|
+
the request is not optional: Pi's system prompt tells the model it has tools
|
|
50
|
+
either way, so a request that omits them leaves a model like Kimi K3 emitting
|
|
51
|
+
its own native tool-call syntax as ordinary assistant text. Images on the
|
|
52
|
+
input side are still unmapped.
|
|
53
|
+
- `wallet.ts` implements both backends: the sidecar HTTP/JSON gateway
|
|
54
|
+
(`PI402_WALLETD_GATEWAY`) and the embedded WASM bridge. Both run
|
|
55
|
+
`prepareSend` → open settle stream → `send`/`sendPrepared` → read preimage,
|
|
56
|
+
which arrives on the settled entry, so the real payer path is live.
|
|
57
|
+
- Run with `PI402_MOCK_PAYER=1` to exercise the loop without spending sats.
|