x402-proxy 0.10.4 → 0.10.6

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/CHANGELOG.md CHANGED
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.6] - 2026-04-01
11
+
12
+ ### Fixed
13
+ - Client disconnect now cancels upstream requests via abort signal propagation (prevents resource leaks on dropped connections)
14
+ - Anthropic SSE streaming stops reading after `message_stop` event instead of waiting for connection close
15
+ - Non-LLM endpoint requests (tool discovery, resource listing) no longer write empty records to inference history
16
+ - Empty inference history records (no amount, model, tokens, or tx) filtered out when reading history
17
+ - `formatUsdcValue()` uses `Intl.NumberFormat` for full precision up to 12 decimals instead of truncating to fixed tiers
18
+ - Stream responses guarded against double `res.end()` calls
19
+
20
+ ### Changed
21
+ - Default model for `claude` command changed from `minimax/minimax-m2.7` to `stepfun/step-3.5-flash`
22
+
23
+ ## [0.10.5] - 2026-04-01
24
+
25
+ ### Fixed
26
+ - `--debug` flag now works with inference proxy - route.ts debug check was captured at import time (before `cli.ts` sets the env var), so `dbg()` never fired; now evaluates lazily per call
27
+ - `--debug` on `claude` command now shows proxy logs (sets `quiet: false`)
28
+ - MPP `X-Payer-Address` header injection preserves headers as plain objects instead of converting to `Headers` instance, avoiding mppx headers-spreading bug that silently drops `Content-Type` on SSE requests
29
+
10
30
  ## [0.10.4] - 2026-04-01
11
31
 
12
32
  ### Fixed
@@ -367,7 +387,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
367
387
  - `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
368
388
  - Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
369
389
 
370
- [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.4...HEAD
390
+ [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.6...HEAD
391
+ [0.10.6]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.5...v0.10.6
392
+ [0.10.5]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.4...v0.10.5
371
393
  [0.10.4]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.3...v0.10.4
372
394
  [0.10.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.2...v0.10.3
373
395
  [0.10.2]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.1...v0.10.2
package/dist/bin/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { _ as error, b as success, c as resolveWallet, d as displayNetwork, f as formatAmount, g as dim, h as readHistory, l as appendHistory, m as formatUsdcValue, n as fetchAllBalances, o as walletInfoCommand, p as formatTxLine, s as buildX402Client, u as calcSpend, v as info, x as warn, y as isTTY } from "../wallet-LUl2l8WM.js";
2
+ import { _ as error, b as success, c as resolveWallet, d as displayNetwork, f as formatAmount, g as dim, h as readHistory, l as appendHistory, m as formatUsdcValue, n as fetchAllBalances, o as walletInfoCommand, p as formatTxLine, s as buildX402Client, u as calcSpend, v as info, x as warn, y as isTTY } from "../wallet-B0S-rma9.js";
3
3
  import { a as getHistoryPath, c as isConfigured, d as saveConfig, i as getConfigDirShort, l as loadConfig, u as loadWalletFile } from "../config-D9wIR3xc.js";
4
4
  import { n as setupCommand, t as runSetup } from "../setup-CNyMLnM-.js";
5
- import { n as statusCommand } from "../status-Dc1dB0ZG.js";
5
+ import { n as statusCommand } from "../status-DihAcUSC.js";
6
6
  import { dirname, join, normalize, resolve } from "node:path";
7
7
  import { buildApplication, buildCommand, buildRouteMap, run } from "@stricli/core";
8
8
  import { spawn } from "node:child_process";
@@ -216,11 +216,13 @@ async function createMppProxyHandler(opts) {
216
216
  });
217
217
  const payerAddress = account.address;
218
218
  function injectPayerHeader(init) {
219
- const headers = new Headers(init?.headers);
220
- headers.set("X-Payer-Address", payerAddress);
219
+ const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
221
220
  return {
222
221
  ...init,
223
- headers
222
+ headers: {
223
+ ...existing,
224
+ "X-Payer-Address": payerAddress
225
+ }
224
226
  };
225
227
  }
226
228
  let session;
@@ -323,14 +325,31 @@ function addressForNetwork(evmAddress, solanaAddress, network) {
323
325
  }
324
326
  //#endregion
325
327
  //#region src/openclaw/route.ts
326
- const debug = process.env.X402_PROXY_DEBUG === "1";
327
328
  function dbg(msg) {
328
- if (debug) process.stderr.write(`[x402-proxy] ${msg}\n`);
329
+ if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
330
+ }
331
+ function createDownstreamAbort(req, res) {
332
+ const controller = new AbortController();
333
+ const abort = () => {
334
+ if (!controller.signal.aborted) controller.abort();
335
+ };
336
+ const onReqAborted = () => abort();
337
+ const onResClose = () => abort();
338
+ req.once("aborted", onReqAborted);
339
+ res.once("close", onResClose);
340
+ return {
341
+ signal: controller.signal,
342
+ cleanup() {
343
+ req.off("aborted", onReqAborted);
344
+ res.off("close", onResClose);
345
+ }
346
+ };
329
347
  }
330
348
  function createInferenceProxyRouteHandler(opts) {
331
349
  const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
332
350
  const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
333
351
  return async (req, res) => {
352
+ const downstream = createDownstreamAbort(req, res);
334
353
  const url = new URL(req.url ?? "/", "http://localhost");
335
354
  const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
336
355
  if (!provider) {
@@ -394,7 +413,8 @@ function createInferenceProxyRouteHandler(opts) {
394
413
  const requestInit = {
395
414
  method,
396
415
  headers,
397
- body: ["GET", "HEAD"].includes(method) ? void 0 : body
416
+ body: ["GET", "HEAD"].includes(method) ? void 0 : body,
417
+ signal: downstream.signal
398
418
  };
399
419
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
400
420
  const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
@@ -408,11 +428,14 @@ function createInferenceProxyRouteHandler(opts) {
408
428
  } }));
409
429
  return true;
410
430
  }
431
+ const mppWalletAddress = getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress;
411
432
  return await handleMppRequest({
412
433
  res,
413
434
  upstreamUrl,
414
435
  requestInit,
415
- walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
436
+ abortSignal: downstream.signal,
437
+ isLlmEndpoint,
438
+ walletAddress: mppWalletAddress,
416
439
  historyPath,
417
440
  logger,
418
441
  allModels,
@@ -440,7 +463,7 @@ function createInferenceProxyRouteHandler(opts) {
440
463
  const amount = paymentAmount(payment);
441
464
  const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
442
465
  const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
443
- appendHistory(historyPath, {
466
+ if (isLlmEndpoint) appendHistory(historyPath, {
444
467
  t: Date.now(),
445
468
  ok: false,
446
469
  kind: "x402_inference",
@@ -492,7 +515,10 @@ function createInferenceProxyRouteHandler(opts) {
492
515
  res.writeHead(response.status, resHeaders);
493
516
  if (!response.body) {
494
517
  res.end();
495
- appendHistory(historyPath, {
518
+ if (shouldAppendInferenceHistory({
519
+ isLlmEndpoint,
520
+ amount
521
+ })) appendHistory(historyPath, {
496
522
  t: Date.now(),
497
523
  ok: true,
498
524
  kind: "x402_inference",
@@ -512,16 +538,28 @@ function createInferenceProxyRouteHandler(opts) {
512
538
  const decoder = new TextDecoder();
513
539
  try {
514
540
  while (true) {
541
+ if (downstream.signal.aborted) {
542
+ await reader.cancel().catch(() => {});
543
+ break;
544
+ }
515
545
  const { done, value } = await reader.read();
516
546
  if (done) break;
517
547
  res.write(value);
518
548
  sse?.push(decoder.decode(value, { stream: true }));
549
+ if (isMessagesApi && sse?.sawAnthropicMessageStop) {
550
+ await reader.cancel().catch(() => {});
551
+ break;
552
+ }
519
553
  }
520
554
  } finally {
521
555
  reader.releaseLock();
522
556
  }
523
- res.end();
524
- appendInferenceHistory({
557
+ if (!res.writableEnded) res.end();
558
+ if (shouldAppendInferenceHistory({
559
+ isLlmEndpoint,
560
+ amount,
561
+ usage: sse?.result
562
+ })) appendInferenceHistory({
525
563
  historyPath,
526
564
  allModels,
527
565
  walletAddress: paymentFrom,
@@ -538,7 +576,7 @@ function createInferenceProxyRouteHandler(opts) {
538
576
  const msg = String(err);
539
577
  logger.error(`x402: fetch threw: ${msg}`);
540
578
  getX402Proxy()?.shiftPayment();
541
- appendHistory(historyPath, {
579
+ if (isLlmEndpoint) appendHistory(historyPath, {
542
580
  t: Date.now(),
543
581
  ok: false,
544
582
  kind: "x402_inference",
@@ -553,6 +591,8 @@ function createInferenceProxyRouteHandler(opts) {
553
591
  else userMessage = `x402 request failed: ${msg}`;
554
592
  if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
555
593
  return true;
594
+ } finally {
595
+ downstream.cleanup();
556
596
  }
557
597
  };
558
598
  }
@@ -571,6 +611,10 @@ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat)
571
611
  code
572
612
  } }));
573
613
  }
614
+ function shouldAppendInferenceHistory(opts) {
615
+ if (!opts.isLlmEndpoint) return false;
616
+ return opts.amount != null || opts.usage != null;
617
+ }
574
618
  function createSseTracker() {
575
619
  let residual = "";
576
620
  let anthropicModel = "";
@@ -578,6 +622,7 @@ function createSseTracker() {
578
622
  let anthropicOutputTokens = 0;
579
623
  let anthropicCacheRead;
580
624
  let anthropicCacheWrite;
625
+ let anthropicMessageStop = false;
581
626
  let lastOpenAiData = "";
582
627
  let isAnthropic = false;
583
628
  function processJson(json) {
@@ -606,6 +651,11 @@ function createSseTracker() {
606
651
  if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
607
652
  return;
608
653
  }
654
+ if (type === "message_stop") {
655
+ isAnthropic = true;
656
+ anthropicMessageStop = true;
657
+ return;
658
+ }
609
659
  if (type === "message") {
610
660
  isAnthropic = true;
611
661
  anthropicModel = parsed.model ?? "";
@@ -629,6 +679,9 @@ function createSseTracker() {
629
679
  pushJson(text) {
630
680
  processJson(text);
631
681
  },
682
+ get sawAnthropicMessageStop() {
683
+ return anthropicMessageStop;
684
+ },
632
685
  get result() {
633
686
  if (isAnthropic) {
634
687
  if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
@@ -658,7 +711,7 @@ function createSseTracker() {
658
711
  };
659
712
  }
660
713
  async function handleMppRequest(opts) {
661
- const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
714
+ const { res, upstreamUrl, requestInit, abortSignal, isLlmEndpoint, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
662
715
  try {
663
716
  if (wantsStreaming) {
664
717
  res.writeHead(200, {
@@ -670,27 +723,49 @@ async function handleMppRequest(opts) {
670
723
  dbg(`mpp.sse() calling ${upstreamUrl}`);
671
724
  const stream = await mpp.sse(upstreamUrl, requestInit);
672
725
  dbg("mpp.sse() resolved, iterating stream");
673
- if (isMessagesApi) for await (const chunk of stream) {
674
- const text = String(chunk);
675
- let eventType = "unknown";
676
- try {
677
- eventType = JSON.parse(text).type ?? "unknown";
678
- } catch {}
679
- res.write(`event: ${eventType}\ndata: ${text}\n\n`);
680
- sse.pushJson(text);
681
- }
682
- else {
683
- for await (const chunk of stream) {
684
- const text = String(chunk);
685
- res.write(`data: ${text}\n\n`);
726
+ const iterator = stream[Symbol.asyncIterator]();
727
+ let semanticComplete = false;
728
+ try {
729
+ if (isMessagesApi) while (true) {
730
+ if (abortSignal.aborted) break;
731
+ const { done, value } = await iterator.next();
732
+ if (done) break;
733
+ const text = String(value);
734
+ let eventType = "unknown";
735
+ try {
736
+ eventType = JSON.parse(text).type ?? "unknown";
737
+ } catch {}
738
+ res.write(`event: ${eventType}\ndata: ${text}\n\n`);
686
739
  sse.pushJson(text);
740
+ if (sse.sawAnthropicMessageStop) {
741
+ semanticComplete = true;
742
+ break;
743
+ }
744
+ }
745
+ else {
746
+ while (true) {
747
+ if (abortSignal.aborted) break;
748
+ const { done, value } = await iterator.next();
749
+ if (done) break;
750
+ const text = String(value);
751
+ res.write(`data: ${text}\n\n`);
752
+ sse.pushJson(text);
753
+ }
754
+ if (!abortSignal.aborted) res.write("data: [DONE]\n\n");
687
755
  }
688
- res.write("data: [DONE]\n\n");
756
+ } catch (err) {
757
+ const msg = err instanceof Error ? err.message : String(err);
758
+ if (!(abortSignal.aborted || semanticComplete) || !msg.includes("terminated")) throw err;
759
+ } finally {
760
+ await iterator.return?.().catch(() => {});
689
761
  }
690
762
  dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
691
- res.end();
763
+ if (!res.writableEnded) res.end();
692
764
  mpp.shiftPayment();
693
- appendInferenceHistory({
765
+ if (shouldAppendInferenceHistory({
766
+ isLlmEndpoint,
767
+ usage: sse.result
768
+ })) appendInferenceHistory({
694
769
  historyPath,
695
770
  allModels,
696
771
  walletAddress,
@@ -707,7 +782,7 @@ async function handleMppRequest(opts) {
707
782
  if (response.status === 402) {
708
783
  const responseBody = await response.text();
709
784
  logger.error(`mpp: payment failed, raw response: ${responseBody}`);
710
- appendHistory(historyPath, {
785
+ if (isLlmEndpoint) appendHistory(historyPath, {
711
786
  t: Date.now(),
712
787
  ok: false,
713
788
  kind: "x402_inference",
@@ -727,14 +802,19 @@ async function handleMppRequest(opts) {
727
802
  const usageTracker = createSseTracker();
728
803
  usageTracker.pushJson(responseBody);
729
804
  const payment = mpp.shiftPayment();
730
- appendInferenceHistory({
805
+ const amount = parseMppAmount(payment?.amount);
806
+ if (shouldAppendInferenceHistory({
807
+ isLlmEndpoint,
808
+ amount,
809
+ usage: usageTracker.result
810
+ })) appendInferenceHistory({
731
811
  historyPath,
732
812
  allModels,
733
813
  walletAddress,
734
814
  paymentNetwork: payment?.network ?? "eip155:4217",
735
815
  paymentTo: void 0,
736
816
  tx: tx ?? payment?.receipt?.reference,
737
- amount: parseMppAmount(payment?.amount),
817
+ amount,
738
818
  thinkingMode,
739
819
  usage: usageTracker.result,
740
820
  durationMs: Date.now() - startMs
@@ -743,7 +823,7 @@ async function handleMppRequest(opts) {
743
823
  } catch (err) {
744
824
  dbg(`mpp error: ${String(err)}`);
745
825
  logger.error(`mpp: fetch threw: ${String(err)}`);
746
- appendHistory(historyPath, {
826
+ if (isLlmEndpoint) appendHistory(historyPath, {
747
827
  t: Date.now(),
748
828
  ok: false,
749
829
  kind: "x402_inference",
@@ -849,6 +929,7 @@ function createRequestHandler(routeHandler) {
849
929
  };
850
930
  }
851
931
  async function startServeServer(options = {}) {
932
+ if (options.debug) options.quiet = false;
852
933
  const config = loadConfig();
853
934
  const wallet = await resolveWalletForServe(options);
854
935
  const resolvedProtocol = resolveProtocol(options.protocol ?? config?.preferredProtocol);
@@ -1001,7 +1082,7 @@ Examples:
1001
1082
  });
1002
1083
  //#endregion
1003
1084
  //#region src/commands/claude.ts
1004
- const DEFAULT_MODEL = "minimax/minimax-m2.7";
1085
+ const DEFAULT_MODEL = "stepfun/step-3.5-flash";
1005
1086
  function normalizeClaudeArgs(args) {
1006
1087
  return args[0] === "--" ? args.slice(1) : args;
1007
1088
  }
@@ -1019,7 +1100,7 @@ Examples:
1019
1100
  flags: {
1020
1101
  model: {
1021
1102
  kind: "parsed",
1022
- brief: "Model to use (default: minimax/minimax-m2.7)",
1103
+ brief: "Model to use (default: stepfun/step-3.5-flash)",
1023
1104
  parse: String,
1024
1105
  default: DEFAULT_MODEL
1025
1106
  },
@@ -1069,6 +1150,7 @@ Examples:
1069
1150
  }
1070
1151
  },
1071
1152
  async func(flags, ...rawClaudeArgs) {
1153
+ const debug = process.env.X402_PROXY_DEBUG === "1";
1072
1154
  const started = await startServeServer({
1073
1155
  upstreamUrl: flags.upstream ?? "https://surf.cascade.fyi/api/v1/inference",
1074
1156
  port: Number(flags.port),
@@ -1076,7 +1158,8 @@ Examples:
1076
1158
  network: flags.network,
1077
1159
  evmKey: flags.evmKey,
1078
1160
  solanaKey: flags.solanaKey,
1079
- quiet: true
1161
+ quiet: !debug,
1162
+ debug
1080
1163
  });
1081
1164
  const child = spawn("claude", normalizeClaudeArgs(rawClaudeArgs), {
1082
1165
  stdio: "inherit",
@@ -1364,7 +1447,7 @@ Examples:
1364
1447
  };
1365
1448
  if (!url) {
1366
1449
  if (isConfigured()) {
1367
- const { displayStatus } = await import("../status-wknbavnl.js");
1450
+ const { displayStatus } = await import("../status-DZlJ4pS7.js");
1368
1451
  await displayStatus();
1369
1452
  console.log();
1370
1453
  console.log(pc.dim(" Commands:"));
@@ -1429,7 +1512,7 @@ Examples:
1429
1512
  verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
1430
1513
  let preferredNetwork = config?.defaultNetwork;
1431
1514
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
1432
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1515
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1433
1516
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1434
1517
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
1435
1518
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1596,7 +1679,7 @@ Examples:
1596
1679
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
1597
1680
  const hasMpp = detected.mpp;
1598
1681
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
1599
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1682
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1600
1683
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1601
1684
  const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
1602
1685
  const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1816,7 +1899,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1816
1899
  async function startX402Proxy() {
1817
1900
  let preferredNetwork = config?.defaultNetwork;
1818
1901
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
1819
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1902
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1820
1903
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1821
1904
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
1822
1905
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1836,7 +1919,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1836
1919
  }
1837
1920
  const remoteClient = new Client({
1838
1921
  name: "x402-proxy",
1839
- version: "0.10.4"
1922
+ version: "0.10.6"
1840
1923
  });
1841
1924
  const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
1842
1925
  autoPayment: true,
@@ -1874,7 +1957,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1874
1957
  }
1875
1958
  const localServer = new Server({
1876
1959
  name: "x402-proxy",
1877
- version: "0.10.4"
1960
+ version: "0.10.6"
1878
1961
  }, { capabilities: {
1879
1962
  tools: tools.length > 0 ? {} : void 0,
1880
1963
  resources: remoteResources.length > 0 ? {} : void 0
@@ -1969,7 +2052,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1969
2052
  }));
1970
2053
  const remoteClient = new Client({
1971
2054
  name: "x402-proxy",
1972
- version: "0.10.4"
2055
+ version: "0.10.6"
1973
2056
  });
1974
2057
  await connectTransport(remoteClient);
1975
2058
  const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
@@ -1984,7 +2067,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1984
2067
  }
1985
2068
  const localServer = new Server({
1986
2069
  name: "x402-proxy",
1987
- version: "0.10.4"
2070
+ version: "0.10.6"
1988
2071
  }, { capabilities: {
1989
2072
  tools: tools.length > 0 ? {} : void 0,
1990
2073
  resources: remoteResources.length > 0 ? {} : void 0
@@ -2383,7 +2466,7 @@ const app = buildApplication(buildRouteMap({
2383
2466
  docs: { brief: "curl for x402 paid APIs" }
2384
2467
  }), {
2385
2468
  name: "x402-proxy",
2386
- versionInfo: { currentVersion: "0.10.4" },
2469
+ versionInfo: { currentVersion: "0.10.6" },
2387
2470
  scanner: { caseStyle: "allow-kebab-for-camel" }
2388
2471
  });
2389
2472
  //#endregion
package/dist/index.js CHANGED
@@ -82,11 +82,13 @@ async function createMppProxyHandler(opts) {
82
82
  });
83
83
  const payerAddress = account.address;
84
84
  function injectPayerHeader(init) {
85
- const headers = new Headers(init?.headers);
86
- headers.set("X-Payer-Address", payerAddress);
85
+ const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
87
86
  return {
88
87
  ...init,
89
- headers
88
+ headers: {
89
+ ...existing,
90
+ "X-Payer-Address": payerAddress
91
+ }
90
92
  };
91
93
  }
92
94
  let session;
@@ -172,6 +174,11 @@ async function createMppProxyHandler(opts) {
172
174
  //#region src/history.ts
173
175
  const HISTORY_MAX_LINES = 1e3;
174
176
  const HISTORY_KEEP_LINES = 500;
177
+ function isMeaningfulInferenceRecord(record) {
178
+ if (record.kind !== "x402_inference") return true;
179
+ if (!record.ok) return true;
180
+ return record.amount != null || record.model != null || record.inputTokens != null || record.outputTokens != null || record.tx != null;
181
+ }
175
182
  function appendHistory(historyPath, record) {
176
183
  try {
177
184
  mkdirSync(dirname(historyPath), { recursive: true });
@@ -193,7 +200,8 @@ function readHistory(historyPath) {
193
200
  try {
194
201
  const parsed = JSON.parse(line);
195
202
  if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
196
- return [parsed];
203
+ const record = parsed;
204
+ return isMeaningfulInferenceRecord(record) ? [record] : [];
197
205
  } catch {
198
206
  return [];
199
207
  }
@@ -222,12 +230,12 @@ function calcSpend(records) {
222
230
  count
223
231
  };
224
232
  }
225
- /** Format a USDC value with adaptive precision (no token suffix). */
226
233
  function formatUsdcValue(amount) {
227
- if (amount >= .01) return amount.toFixed(2);
228
- if (amount >= .001) return amount.toFixed(3);
229
- if (amount >= 1e-4) return amount.toFixed(4);
230
- return amount.toFixed(6);
234
+ return new Intl.NumberFormat("en-US", {
235
+ useGrouping: false,
236
+ minimumFractionDigits: 0,
237
+ maximumFractionDigits: 12
238
+ }).format(amount);
231
239
  }
232
240
  function formatAmount(amount, token) {
233
241
  if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
@@ -87,11 +87,13 @@ async function createMppProxyHandler(opts) {
87
87
  });
88
88
  const payerAddress = account.address;
89
89
  function injectPayerHeader(init) {
90
- const headers = new Headers(init?.headers);
91
- headers.set("X-Payer-Address", payerAddress);
90
+ const existing = init?.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init?.headers ?? {};
92
91
  return {
93
92
  ...init,
94
- headers
93
+ headers: {
94
+ ...existing,
95
+ "X-Payer-Address": payerAddress
96
+ }
95
97
  };
96
98
  }
97
99
  let session;
@@ -256,6 +258,11 @@ var OptimizedSvmScheme = class {
256
258
  };
257
259
  }
258
260
  };
261
+ function isMeaningfulInferenceRecord(record) {
262
+ if (record.kind !== "x402_inference") return true;
263
+ if (!record.ok) return true;
264
+ return record.amount != null || record.model != null || record.inputTokens != null || record.outputTokens != null || record.tx != null;
265
+ }
259
266
  function appendHistory(historyPath, record) {
260
267
  try {
261
268
  mkdirSync(dirname(historyPath), { recursive: true });
@@ -277,7 +284,8 @@ function readHistory(historyPath) {
277
284
  try {
278
285
  const parsed = JSON.parse(line);
279
286
  if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
280
- return [parsed];
287
+ const record = parsed;
288
+ return isMeaningfulInferenceRecord(record) ? [record] : [];
281
289
  } catch {
282
290
  return [];
283
291
  }
@@ -306,12 +314,12 @@ function calcSpend(records) {
306
314
  count
307
315
  };
308
316
  }
309
- /** Format a USDC value with adaptive precision (no token suffix). */
310
317
  function formatUsdcValue(amount) {
311
- if (amount >= .01) return amount.toFixed(2);
312
- if (amount >= .001) return amount.toFixed(3);
313
- if (amount >= 1e-4) return amount.toFixed(4);
314
- return amount.toFixed(6);
318
+ return new Intl.NumberFormat("en-US", {
319
+ useGrouping: false,
320
+ minimumFractionDigits: 0,
321
+ maximumFractionDigits: 12
322
+ }).format(amount);
315
323
  }
316
324
  function formatAmount(amount, token) {
317
325
  if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
@@ -1151,7 +1159,7 @@ function createWalletCommand(ctx) {
1151
1159
  if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
1152
1160
  try {
1153
1161
  const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
1154
- const lines = [`x402-proxy v0.10.4`];
1162
+ const lines = [`x402-proxy v0.10.6`];
1155
1163
  const defaultModel = ctx.allModels[0];
1156
1164
  if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
1157
1165
  lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
@@ -1367,14 +1375,31 @@ function resolveMppSessionBudget(value, fallback = "0.5") {
1367
1375
  }
1368
1376
  //#endregion
1369
1377
  //#region src/openclaw/route.ts
1370
- const debug = process.env.X402_PROXY_DEBUG === "1";
1371
1378
  function dbg(msg) {
1372
- if (debug) process.stderr.write(`[x402-proxy] ${msg}\n`);
1379
+ if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
1380
+ }
1381
+ function createDownstreamAbort(req, res) {
1382
+ const controller = new AbortController();
1383
+ const abort = () => {
1384
+ if (!controller.signal.aborted) controller.abort();
1385
+ };
1386
+ const onReqAborted = () => abort();
1387
+ const onResClose = () => abort();
1388
+ req.once("aborted", onReqAborted);
1389
+ res.once("close", onResClose);
1390
+ return {
1391
+ signal: controller.signal,
1392
+ cleanup() {
1393
+ req.off("aborted", onReqAborted);
1394
+ res.off("close", onResClose);
1395
+ }
1396
+ };
1373
1397
  }
1374
1398
  function createInferenceProxyRouteHandler(opts) {
1375
1399
  const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
1376
1400
  const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
1377
1401
  return async (req, res) => {
1402
+ const downstream = createDownstreamAbort(req, res);
1378
1403
  const url = new URL(req.url ?? "/", "http://localhost");
1379
1404
  const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
1380
1405
  if (!provider) {
@@ -1438,7 +1463,8 @@ function createInferenceProxyRouteHandler(opts) {
1438
1463
  const requestInit = {
1439
1464
  method,
1440
1465
  headers,
1441
- body: ["GET", "HEAD"].includes(method) ? void 0 : body
1466
+ body: ["GET", "HEAD"].includes(method) ? void 0 : body,
1467
+ signal: downstream.signal
1442
1468
  };
1443
1469
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
1444
1470
  const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
@@ -1452,11 +1478,14 @@ function createInferenceProxyRouteHandler(opts) {
1452
1478
  } }));
1453
1479
  return true;
1454
1480
  }
1481
+ const mppWalletAddress = getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress;
1455
1482
  return await handleMppRequest({
1456
1483
  res,
1457
1484
  upstreamUrl,
1458
1485
  requestInit,
1459
- walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
1486
+ abortSignal: downstream.signal,
1487
+ isLlmEndpoint,
1488
+ walletAddress: mppWalletAddress,
1460
1489
  historyPath,
1461
1490
  logger,
1462
1491
  allModels,
@@ -1484,7 +1513,7 @@ function createInferenceProxyRouteHandler(opts) {
1484
1513
  const amount = paymentAmount(payment);
1485
1514
  const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
1486
1515
  const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
1487
- appendHistory(historyPath, {
1516
+ if (isLlmEndpoint) appendHistory(historyPath, {
1488
1517
  t: Date.now(),
1489
1518
  ok: false,
1490
1519
  kind: "x402_inference",
@@ -1536,7 +1565,10 @@ function createInferenceProxyRouteHandler(opts) {
1536
1565
  res.writeHead(response.status, resHeaders);
1537
1566
  if (!response.body) {
1538
1567
  res.end();
1539
- appendHistory(historyPath, {
1568
+ if (shouldAppendInferenceHistory({
1569
+ isLlmEndpoint,
1570
+ amount
1571
+ })) appendHistory(historyPath, {
1540
1572
  t: Date.now(),
1541
1573
  ok: true,
1542
1574
  kind: "x402_inference",
@@ -1556,16 +1588,28 @@ function createInferenceProxyRouteHandler(opts) {
1556
1588
  const decoder = new TextDecoder();
1557
1589
  try {
1558
1590
  while (true) {
1591
+ if (downstream.signal.aborted) {
1592
+ await reader.cancel().catch(() => {});
1593
+ break;
1594
+ }
1559
1595
  const { done, value } = await reader.read();
1560
1596
  if (done) break;
1561
1597
  res.write(value);
1562
1598
  sse?.push(decoder.decode(value, { stream: true }));
1599
+ if (isMessagesApi && sse?.sawAnthropicMessageStop) {
1600
+ await reader.cancel().catch(() => {});
1601
+ break;
1602
+ }
1563
1603
  }
1564
1604
  } finally {
1565
1605
  reader.releaseLock();
1566
1606
  }
1567
- res.end();
1568
- appendInferenceHistory({
1607
+ if (!res.writableEnded) res.end();
1608
+ if (shouldAppendInferenceHistory({
1609
+ isLlmEndpoint,
1610
+ amount,
1611
+ usage: sse?.result
1612
+ })) appendInferenceHistory({
1569
1613
  historyPath,
1570
1614
  allModels,
1571
1615
  walletAddress: paymentFrom,
@@ -1582,7 +1626,7 @@ function createInferenceProxyRouteHandler(opts) {
1582
1626
  const msg = String(err);
1583
1627
  logger.error(`x402: fetch threw: ${msg}`);
1584
1628
  getX402Proxy()?.shiftPayment();
1585
- appendHistory(historyPath, {
1629
+ if (isLlmEndpoint) appendHistory(historyPath, {
1586
1630
  t: Date.now(),
1587
1631
  ok: false,
1588
1632
  kind: "x402_inference",
@@ -1597,6 +1641,8 @@ function createInferenceProxyRouteHandler(opts) {
1597
1641
  else userMessage = `x402 request failed: ${msg}`;
1598
1642
  if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
1599
1643
  return true;
1644
+ } finally {
1645
+ downstream.cleanup();
1600
1646
  }
1601
1647
  };
1602
1648
  }
@@ -1615,6 +1661,10 @@ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat)
1615
1661
  code
1616
1662
  } }));
1617
1663
  }
1664
+ function shouldAppendInferenceHistory(opts) {
1665
+ if (!opts.isLlmEndpoint) return false;
1666
+ return opts.amount != null || opts.usage != null;
1667
+ }
1618
1668
  function createSseTracker() {
1619
1669
  let residual = "";
1620
1670
  let anthropicModel = "";
@@ -1622,6 +1672,7 @@ function createSseTracker() {
1622
1672
  let anthropicOutputTokens = 0;
1623
1673
  let anthropicCacheRead;
1624
1674
  let anthropicCacheWrite;
1675
+ let anthropicMessageStop = false;
1625
1676
  let lastOpenAiData = "";
1626
1677
  let isAnthropic = false;
1627
1678
  function processJson(json) {
@@ -1650,6 +1701,11 @@ function createSseTracker() {
1650
1701
  if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
1651
1702
  return;
1652
1703
  }
1704
+ if (type === "message_stop") {
1705
+ isAnthropic = true;
1706
+ anthropicMessageStop = true;
1707
+ return;
1708
+ }
1653
1709
  if (type === "message") {
1654
1710
  isAnthropic = true;
1655
1711
  anthropicModel = parsed.model ?? "";
@@ -1673,6 +1729,9 @@ function createSseTracker() {
1673
1729
  pushJson(text) {
1674
1730
  processJson(text);
1675
1731
  },
1732
+ get sawAnthropicMessageStop() {
1733
+ return anthropicMessageStop;
1734
+ },
1676
1735
  get result() {
1677
1736
  if (isAnthropic) {
1678
1737
  if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
@@ -1702,7 +1761,7 @@ function createSseTracker() {
1702
1761
  };
1703
1762
  }
1704
1763
  async function handleMppRequest(opts) {
1705
- const { res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
1764
+ const { res, upstreamUrl, requestInit, abortSignal, isLlmEndpoint, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, mpp } = opts;
1706
1765
  try {
1707
1766
  if (wantsStreaming) {
1708
1767
  res.writeHead(200, {
@@ -1714,27 +1773,49 @@ async function handleMppRequest(opts) {
1714
1773
  dbg(`mpp.sse() calling ${upstreamUrl}`);
1715
1774
  const stream = await mpp.sse(upstreamUrl, requestInit);
1716
1775
  dbg("mpp.sse() resolved, iterating stream");
1717
- if (isMessagesApi) for await (const chunk of stream) {
1718
- const text = String(chunk);
1719
- let eventType = "unknown";
1720
- try {
1721
- eventType = JSON.parse(text).type ?? "unknown";
1722
- } catch {}
1723
- res.write(`event: ${eventType}\ndata: ${text}\n\n`);
1724
- sse.pushJson(text);
1725
- }
1726
- else {
1727
- for await (const chunk of stream) {
1728
- const text = String(chunk);
1729
- res.write(`data: ${text}\n\n`);
1776
+ const iterator = stream[Symbol.asyncIterator]();
1777
+ let semanticComplete = false;
1778
+ try {
1779
+ if (isMessagesApi) while (true) {
1780
+ if (abortSignal.aborted) break;
1781
+ const { done, value } = await iterator.next();
1782
+ if (done) break;
1783
+ const text = String(value);
1784
+ let eventType = "unknown";
1785
+ try {
1786
+ eventType = JSON.parse(text).type ?? "unknown";
1787
+ } catch {}
1788
+ res.write(`event: ${eventType}\ndata: ${text}\n\n`);
1730
1789
  sse.pushJson(text);
1790
+ if (sse.sawAnthropicMessageStop) {
1791
+ semanticComplete = true;
1792
+ break;
1793
+ }
1731
1794
  }
1732
- res.write("data: [DONE]\n\n");
1795
+ else {
1796
+ while (true) {
1797
+ if (abortSignal.aborted) break;
1798
+ const { done, value } = await iterator.next();
1799
+ if (done) break;
1800
+ const text = String(value);
1801
+ res.write(`data: ${text}\n\n`);
1802
+ sse.pushJson(text);
1803
+ }
1804
+ if (!abortSignal.aborted) res.write("data: [DONE]\n\n");
1805
+ }
1806
+ } catch (err) {
1807
+ const msg = err instanceof Error ? err.message : String(err);
1808
+ if (!(abortSignal.aborted || semanticComplete) || !msg.includes("terminated")) throw err;
1809
+ } finally {
1810
+ await iterator.return?.().catch(() => {});
1733
1811
  }
1734
1812
  dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
1735
- res.end();
1813
+ if (!res.writableEnded) res.end();
1736
1814
  mpp.shiftPayment();
1737
- appendInferenceHistory({
1815
+ if (shouldAppendInferenceHistory({
1816
+ isLlmEndpoint,
1817
+ usage: sse.result
1818
+ })) appendInferenceHistory({
1738
1819
  historyPath,
1739
1820
  allModels,
1740
1821
  walletAddress,
@@ -1751,7 +1832,7 @@ async function handleMppRequest(opts) {
1751
1832
  if (response.status === 402) {
1752
1833
  const responseBody = await response.text();
1753
1834
  logger.error(`mpp: payment failed, raw response: ${responseBody}`);
1754
- appendHistory(historyPath, {
1835
+ if (isLlmEndpoint) appendHistory(historyPath, {
1755
1836
  t: Date.now(),
1756
1837
  ok: false,
1757
1838
  kind: "x402_inference",
@@ -1771,14 +1852,19 @@ async function handleMppRequest(opts) {
1771
1852
  const usageTracker = createSseTracker();
1772
1853
  usageTracker.pushJson(responseBody);
1773
1854
  const payment = mpp.shiftPayment();
1774
- appendInferenceHistory({
1855
+ const amount = parseMppAmount(payment?.amount);
1856
+ if (shouldAppendInferenceHistory({
1857
+ isLlmEndpoint,
1858
+ amount,
1859
+ usage: usageTracker.result
1860
+ })) appendInferenceHistory({
1775
1861
  historyPath,
1776
1862
  allModels,
1777
1863
  walletAddress,
1778
1864
  paymentNetwork: payment?.network ?? "eip155:4217",
1779
1865
  paymentTo: void 0,
1780
1866
  tx: tx ?? payment?.receipt?.reference,
1781
- amount: parseMppAmount(payment?.amount),
1867
+ amount,
1782
1868
  thinkingMode,
1783
1869
  usage: usageTracker.result,
1784
1870
  durationMs: Date.now() - startMs
@@ -1787,7 +1873,7 @@ async function handleMppRequest(opts) {
1787
1873
  } catch (err) {
1788
1874
  dbg(`mpp error: ${String(err)}`);
1789
1875
  logger.error(`mpp: fetch threw: ${String(err)}`);
1790
- appendHistory(historyPath, {
1876
+ if (isLlmEndpoint) appendHistory(historyPath, {
1791
1877
  t: Date.now(),
1792
1878
  ok: false,
1793
1879
  kind: "x402_inference",
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { t as displayStatus } from "./status-DihAcUSC.js";
3
+ export { displayStatus };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { c as resolveWallet, f as formatAmount, g as dim, h as readHistory, m as formatUsdcValue, n as fetchAllBalances, p as formatTxLine, t as balanceLine, u as calcSpend } from "./wallet-LUl2l8WM.js";
2
+ import { c as resolveWallet, f as formatAmount, g as dim, h as readHistory, m as formatUsdcValue, n as fetchAllBalances, p as formatTxLine, t as balanceLine, u as calcSpend } from "./wallet-B0S-rma9.js";
3
3
  import { a as getHistoryPath, i as getConfigDirShort, l as loadConfig } from "./config-D9wIR3xc.js";
4
4
  import { buildCommand } from "@stricli/core";
5
5
  import pc from "picocolors";
@@ -34,6 +34,11 @@ function dim(msg) {
34
34
  function success(msg) {
35
35
  process.stderr.write(`${isTTY() ? pc.green(`✓ ${msg}`) : `✓ ${msg}`}\n`);
36
36
  }
37
+ function isMeaningfulInferenceRecord(record) {
38
+ if (record.kind !== "x402_inference") return true;
39
+ if (!record.ok) return true;
40
+ return record.amount != null || record.model != null || record.inputTokens != null || record.outputTokens != null || record.tx != null;
41
+ }
37
42
  function appendHistory(historyPath, record) {
38
43
  try {
39
44
  mkdirSync(dirname(historyPath), { recursive: true });
@@ -55,7 +60,8 @@ function readHistory(historyPath) {
55
60
  try {
56
61
  const parsed = JSON.parse(line);
57
62
  if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
58
- return [parsed];
63
+ const record = parsed;
64
+ return isMeaningfulInferenceRecord(record) ? [record] : [];
59
65
  } catch {
60
66
  return [];
61
67
  }
@@ -84,12 +90,12 @@ function calcSpend(records) {
84
90
  count
85
91
  };
86
92
  }
87
- /** Format a USDC value with adaptive precision (no token suffix). */
88
93
  function formatUsdcValue(amount) {
89
- if (amount >= .01) return amount.toFixed(2);
90
- if (amount >= .001) return amount.toFixed(3);
91
- if (amount >= 1e-4) return amount.toFixed(4);
92
- return amount.toFixed(6);
94
+ return new Intl.NumberFormat("en-US", {
95
+ useGrouping: false,
96
+ minimumFractionDigits: 0,
97
+ maximumFractionDigits: 12
98
+ }).format(amount);
93
99
  }
94
100
  function formatAmount(amount, token) {
95
101
  if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { n as fetchAllBalances } from "./wallet-B0S-rma9.js";
3
+ export { fetchAllBalances };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.10.4",
3
+ "version": "0.10.6",
4
4
  "description": "curl for x402 paid APIs. Auto-pays any endpoint on Base, Solana, and Tempo. Also works as an OpenClaw plugin.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { t as displayStatus } from "./status-Dc1dB0ZG.js";
3
- export { displayStatus };
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { n as fetchAllBalances } from "./wallet-LUl2l8WM.js";
3
- export { fetchAllBalances };