@lightninglabs/pi-402 0.0.1 → 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/README.md CHANGED
@@ -95,6 +95,7 @@ Select a paid model and prompt:
95
95
 
96
96
  ```
97
97
  /model l402-baseten/kimi-k3 # 1M context, natively multimodal
98
+ /model l402-baseten/kimi-k3.1-pro # 1M context, cheaper than K3 upstream
98
99
  /model l402-baseten/glm-4.7 # 200K context
99
100
  /model l402-baseten/glm-5.2 # 200K context
100
101
  /model l402-baseten/deepseek-v4-flash # 1M context, the cheap one
@@ -183,6 +184,7 @@ The WASM bundle is not in git. To work on the embedded wallet, build it with
183
184
 
184
185
  ## Docs
185
186
 
187
+ - [`docs/architecture.md`](docs/architecture.md): a map of the extension: components, request and payment pipelines, storage, and concurrency.
186
188
  - [`docs/l402-walletdk-inference-payments.md`](docs/l402-walletdk-inference-payments.md): end-to-end design of the extension.
187
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.
188
190
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightninglabs/pi-402",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Pay for Pi inference with Lightning: an L402 + walletdk extension for the Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -47,6 +47,19 @@ export default function (pi: ExtensionAPI): void {
47
47
  pi.on("session_start", async () => {
48
48
  await wallet.startAndUnlock();
49
49
  });
50
+
51
+ // Release the wallet when Pi tears the extension down. Without this
52
+ // the WASM runtime and its SQLite worker thread keep Node's event
53
+ // loop alive, and a non-interactive `pi -p` never exits.
54
+ //
55
+ // Only a quit stops it. Pi also emits session_shutdown when it replaces
56
+ // the session (reload, new, resume, fork) and keeps running, and the
57
+ // next session needs the wallet the SQLite bridge belongs to.
58
+ pi.on("session_shutdown", async (event) => {
59
+ if (event.reason === "quit") {
60
+ await wallet.stop();
61
+ }
62
+ });
50
63
  }
51
64
 
52
65
  // Register the paid model provider. Its streamSimple owns the request path,
@@ -103,6 +116,17 @@ export default function (pi: ExtensionAPI): void {
103
116
  contextWindow: 1_048_576,
104
117
  maxTokens: 32_768,
105
118
  },
119
+ {
120
+ // Maps to moonshotai/Kimi-K3.1-Pro on the wire. 1M-token
121
+ // context, priced below Kimi K3 upstream.
122
+ id: "kimi-k3.1-pro",
123
+ name: "Kimi K3.1 Pro (L402)",
124
+ input: ["text"],
125
+ reasoning: true,
126
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
127
+ contextWindow: 1_000_000,
128
+ maxTokens: 32_768,
129
+ },
106
130
  ],
107
131
  streamSimple: makeStreamSimple({ payer, policy, tokenStore, credentialStore, receiptLog }),
108
132
  });
package/src/provider.ts CHANGED
@@ -57,6 +57,7 @@ const BASETEN_MODEL_SLUGS: Record<string, string> = {
57
57
  "glm-5": "zai-org/GLM-5",
58
58
  "glm-5.2": "zai-org/GLM-5.2",
59
59
  "kimi-k3": "moonshotai/Kimi-K3",
60
+ "kimi-k3.1-pro": "moonshotai/Kimi-K3.1-Pro",
60
61
  };
61
62
 
62
63
  // wireModelId resolves the model id sent to the proxy/upstream.
package/src/wallet.ts CHANGED
@@ -104,6 +104,9 @@ export class WalletService {
104
104
  // any more, so calls have to fail fast rather than hang against it.
105
105
  private runtimeExited = false;
106
106
 
107
+ // stopped records that stop() has run, so a second call does nothing.
108
+ private stopped = false;
109
+
107
110
  // startAndUnlock brings up the selected backend and unlocks the wallet,
108
111
  // creating it on the first run. It is bootstrapped once per session from
109
112
  // the session_start hook.
@@ -274,6 +277,14 @@ export class WalletService {
274
277
  }
275
278
  }
276
279
 
280
+ // Credit caps: small payments (under about 1,000 sat) go over the swap
281
+ // server's credit rail, and the wallet only authorizes that rail when the
282
+ // caller sets an explicit cap. With no cap the quote is refused ("below
283
+ // the 1000 sat minimum for pay swaps"), which is how a 20 sat session
284
+ // deposit fails. We pass the per-request ceiling for both caps, so the
285
+ // wallet never reserves or tops up more credit than a single request may
286
+ // spend.
287
+ //
277
288
  // payInvoice pays a BOLT11 invoice via a two-phase wavewalletdk send.
278
289
  // prepareSend quotes the payment (including the swap fee), send
279
290
  // dispatches it, and the preimage is read from the settled entry once
@@ -289,7 +300,11 @@ export class WalletService {
289
300
  const ceiling = maxSatPerRequest();
290
301
 
291
302
  if (this.gateway) {
292
- const quote = await this.rest("/v1/wallet/prepare-send", { invoice });
303
+ const quote = await this.rest("/v1/wallet/prepare-send", {
304
+ invoice,
305
+ max_credit_sat: ceiling,
306
+ max_credit_topup_sat: ceiling,
307
+ });
293
308
  const outflow = quotedOutflowSat(quote);
294
309
  assertWithinCeiling(outflow, ceiling);
295
310
 
@@ -312,7 +327,11 @@ export class WalletService {
312
327
  }
313
328
 
314
329
  const call = this.requireCall();
315
- const quote = await call("prepareSend", { Invoice: invoice });
330
+ const quote = await call("prepareSend", {
331
+ Invoice: invoice,
332
+ MaxCreditSat: ceiling,
333
+ MaxCreditTopupSat: ceiling,
334
+ });
316
335
  const outflow = quotedOutflowSat(quote);
317
336
  assertWithinCeiling(outflow, ceiling);
318
337
 
@@ -502,6 +521,57 @@ export class WalletService {
502
521
  return payload;
503
522
  }
504
523
 
524
+ // stop shuts the embedded wallet down so the process can exit. The Go
525
+ // runtime parks on a channel and the SQLite bridge owns a worker thread;
526
+ // a worker thread is a live handle, so until both are released Node never
527
+ // drains its event loop and a non-interactive `pi -p` hangs after the
528
+ // reply is printed.
529
+ //
530
+ // It is idempotent, a no-op when the embedded runtime never started (mock
531
+ // mode, sidecar mode, or a failed bootstrap), and never throws: it runs on
532
+ // Pi's shutdown path, where an error would only mask the exit. Each step
533
+ // is bounded by timeoutMs so a wedged wallet cannot hang the exit it is
534
+ // meant to enable.
535
+ async stop(timeoutMs = 5_000): Promise<void> {
536
+ const call = this.call;
537
+ if (!call || this.stopped) {
538
+ return;
539
+ }
540
+ this.stopped = true;
541
+
542
+ // Once stopping begins nothing may use the wallet again.
543
+ this.ready = false;
544
+
545
+ const bounded = async (what: string, work: () => Promise<unknown>) => {
546
+ let timer: NodeJS.Timeout | undefined;
547
+ try {
548
+ await Promise.race([
549
+ work(),
550
+ new Promise((_, reject) => {
551
+ timer = setTimeout(() => reject(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
552
+ }),
553
+ ]);
554
+ } catch (err) {
555
+ console.error(`[pi-402] embedded wallet ${what} failed:`, err);
556
+ } finally {
557
+ clearTimeout(timer);
558
+ }
559
+ };
560
+
561
+ // If the runtime already died on its own there is nothing to stop,
562
+ // but the worker thread may still be holding the process open.
563
+ if (!this.runtimeExited) {
564
+ await bounded("stop", () => call("stop"));
565
+ }
566
+
567
+ // The bridge's shutdown terminates the SQLite worker. It runs even
568
+ // when stop failed, since a leaked worker is what blocks the exit.
569
+ const bridge = (globalThis as any).sqliteBridge;
570
+ if (typeof bridge?.shutdown === "function") {
571
+ await bounded("sqlite shutdown", () => bridge.shutdown());
572
+ }
573
+ }
574
+
505
575
  // noteRuntimeExit records that the embedded Go runtime is gone. The
506
576
  // program is supposed to run for the lifetime of the process, so any exit
507
577
  // is the wallet dying, and the only useful things to do are say so once,
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/wasm/wavewalletdk.wasm
3
- size: 136699930 bytes
4
- sha256: eac3751112c824328c2d62224453e34b18ef94c5459b897e44718d881e8353ac
3
+ size: 133563673 bytes
4
+ sha256: 6d945797ce051806ed880b80a22a3a79e2b303d229cea41dc6d5071a30139f84