x402-proxy 0.10.5 → 0.10.7

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,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.7] - 2026-04-01
11
+
12
+ ### Added
13
+ - `claude` and `claude --model` shown in default `npx x402-proxy` commands output
14
+ - `claude --help` lists available models and usage examples
15
+
16
+ ## [0.10.6] - 2026-04-01
17
+
18
+ ### Fixed
19
+ - Client disconnect now cancels upstream requests via abort signal propagation (prevents resource leaks on dropped connections)
20
+ - Anthropic SSE streaming stops reading after `message_stop` event instead of waiting for connection close
21
+ - Non-LLM endpoint requests (tool discovery, resource listing) no longer write empty records to inference history
22
+ - Empty inference history records (no amount, model, tokens, or tx) filtered out when reading history
23
+ - `formatUsdcValue()` uses `Intl.NumberFormat` for full precision up to 12 decimals instead of truncating to fixed tiers
24
+ - Stream responses guarded against double `res.end()` calls
25
+
26
+ ### Changed
27
+ - Default model for `claude` command changed from `minimax/minimax-m2.7` to `stepfun/step-3.5-flash`
28
+
10
29
  ## [0.10.5] - 2026-04-01
11
30
 
12
31
  ### Fixed
@@ -374,7 +393,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
374
393
  - `appendHistory` / `readHistory` / `calcSpend` - JSONL transaction history
375
394
  - Re-exports from `@x402/fetch`, `@x402/svm`, `@x402/evm`
376
395
 
377
- [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.5...HEAD
396
+ [Unreleased]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.7...HEAD
397
+ [0.10.7]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.6...v0.10.7
398
+ [0.10.6]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.5...v0.10.6
378
399
  [0.10.5]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.4...v0.10.5
379
400
  [0.10.4]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.3...v0.10.4
380
401
  [0.10.3]: https://github.com/cascade-protocol/x402-proxy/compare/v0.10.2...v0.10.3
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";
@@ -328,10 +328,28 @@ function addressForNetwork(evmAddress, solanaAddress, network) {
328
328
  function dbg(msg) {
329
329
  if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
330
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
+ };
347
+ }
331
348
  function createInferenceProxyRouteHandler(opts) {
332
349
  const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
333
350
  const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
334
351
  return async (req, res) => {
352
+ const downstream = createDownstreamAbort(req, res);
335
353
  const url = new URL(req.url ?? "/", "http://localhost");
336
354
  const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
337
355
  if (!provider) {
@@ -395,7 +413,8 @@ function createInferenceProxyRouteHandler(opts) {
395
413
  const requestInit = {
396
414
  method,
397
415
  headers,
398
- body: ["GET", "HEAD"].includes(method) ? void 0 : body
416
+ body: ["GET", "HEAD"].includes(method) ? void 0 : body,
417
+ signal: downstream.signal
399
418
  };
400
419
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
401
420
  const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
@@ -409,11 +428,14 @@ function createInferenceProxyRouteHandler(opts) {
409
428
  } }));
410
429
  return true;
411
430
  }
431
+ const mppWalletAddress = getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress;
412
432
  return await handleMppRequest({
413
433
  res,
414
434
  upstreamUrl,
415
435
  requestInit,
416
- walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
436
+ abortSignal: downstream.signal,
437
+ isLlmEndpoint,
438
+ walletAddress: mppWalletAddress,
417
439
  historyPath,
418
440
  logger,
419
441
  allModels,
@@ -441,7 +463,7 @@ function createInferenceProxyRouteHandler(opts) {
441
463
  const amount = paymentAmount(payment);
442
464
  const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
443
465
  const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
444
- appendHistory(historyPath, {
466
+ if (isLlmEndpoint) appendHistory(historyPath, {
445
467
  t: Date.now(),
446
468
  ok: false,
447
469
  kind: "x402_inference",
@@ -493,7 +515,10 @@ function createInferenceProxyRouteHandler(opts) {
493
515
  res.writeHead(response.status, resHeaders);
494
516
  if (!response.body) {
495
517
  res.end();
496
- appendHistory(historyPath, {
518
+ if (shouldAppendInferenceHistory({
519
+ isLlmEndpoint,
520
+ amount
521
+ })) appendHistory(historyPath, {
497
522
  t: Date.now(),
498
523
  ok: true,
499
524
  kind: "x402_inference",
@@ -513,16 +538,28 @@ function createInferenceProxyRouteHandler(opts) {
513
538
  const decoder = new TextDecoder();
514
539
  try {
515
540
  while (true) {
541
+ if (downstream.signal.aborted) {
542
+ await reader.cancel().catch(() => {});
543
+ break;
544
+ }
516
545
  const { done, value } = await reader.read();
517
546
  if (done) break;
518
547
  res.write(value);
519
548
  sse?.push(decoder.decode(value, { stream: true }));
549
+ if (isMessagesApi && sse?.sawAnthropicMessageStop) {
550
+ await reader.cancel().catch(() => {});
551
+ break;
552
+ }
520
553
  }
521
554
  } finally {
522
555
  reader.releaseLock();
523
556
  }
524
- res.end();
525
- appendInferenceHistory({
557
+ if (!res.writableEnded) res.end();
558
+ if (shouldAppendInferenceHistory({
559
+ isLlmEndpoint,
560
+ amount,
561
+ usage: sse?.result
562
+ })) appendInferenceHistory({
526
563
  historyPath,
527
564
  allModels,
528
565
  walletAddress: paymentFrom,
@@ -539,7 +576,7 @@ function createInferenceProxyRouteHandler(opts) {
539
576
  const msg = String(err);
540
577
  logger.error(`x402: fetch threw: ${msg}`);
541
578
  getX402Proxy()?.shiftPayment();
542
- appendHistory(historyPath, {
579
+ if (isLlmEndpoint) appendHistory(historyPath, {
543
580
  t: Date.now(),
544
581
  ok: false,
545
582
  kind: "x402_inference",
@@ -554,6 +591,8 @@ function createInferenceProxyRouteHandler(opts) {
554
591
  else userMessage = `x402 request failed: ${msg}`;
555
592
  if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
556
593
  return true;
594
+ } finally {
595
+ downstream.cleanup();
557
596
  }
558
597
  };
559
598
  }
@@ -572,6 +611,10 @@ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat)
572
611
  code
573
612
  } }));
574
613
  }
614
+ function shouldAppendInferenceHistory(opts) {
615
+ if (!opts.isLlmEndpoint) return false;
616
+ return opts.amount != null || opts.usage != null;
617
+ }
575
618
  function createSseTracker() {
576
619
  let residual = "";
577
620
  let anthropicModel = "";
@@ -579,6 +622,7 @@ function createSseTracker() {
579
622
  let anthropicOutputTokens = 0;
580
623
  let anthropicCacheRead;
581
624
  let anthropicCacheWrite;
625
+ let anthropicMessageStop = false;
582
626
  let lastOpenAiData = "";
583
627
  let isAnthropic = false;
584
628
  function processJson(json) {
@@ -607,6 +651,11 @@ function createSseTracker() {
607
651
  if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
608
652
  return;
609
653
  }
654
+ if (type === "message_stop") {
655
+ isAnthropic = true;
656
+ anthropicMessageStop = true;
657
+ return;
658
+ }
610
659
  if (type === "message") {
611
660
  isAnthropic = true;
612
661
  anthropicModel = parsed.model ?? "";
@@ -630,6 +679,9 @@ function createSseTracker() {
630
679
  pushJson(text) {
631
680
  processJson(text);
632
681
  },
682
+ get sawAnthropicMessageStop() {
683
+ return anthropicMessageStop;
684
+ },
633
685
  get result() {
634
686
  if (isAnthropic) {
635
687
  if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
@@ -659,7 +711,7 @@ function createSseTracker() {
659
711
  };
660
712
  }
661
713
  async function handleMppRequest(opts) {
662
- 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;
663
715
  try {
664
716
  if (wantsStreaming) {
665
717
  res.writeHead(200, {
@@ -671,27 +723,49 @@ async function handleMppRequest(opts) {
671
723
  dbg(`mpp.sse() calling ${upstreamUrl}`);
672
724
  const stream = await mpp.sse(upstreamUrl, requestInit);
673
725
  dbg("mpp.sse() resolved, iterating stream");
674
- if (isMessagesApi) for await (const chunk of stream) {
675
- const text = String(chunk);
676
- let eventType = "unknown";
677
- try {
678
- eventType = JSON.parse(text).type ?? "unknown";
679
- } catch {}
680
- res.write(`event: ${eventType}\ndata: ${text}\n\n`);
681
- sse.pushJson(text);
682
- }
683
- else {
684
- for await (const chunk of stream) {
685
- const text = String(chunk);
686
- 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`);
687
739
  sse.pushJson(text);
740
+ if (sse.sawAnthropicMessageStop) {
741
+ semanticComplete = true;
742
+ break;
743
+ }
688
744
  }
689
- res.write("data: [DONE]\n\n");
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");
755
+ }
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(() => {});
690
761
  }
691
762
  dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
692
- res.end();
763
+ if (!res.writableEnded) res.end();
693
764
  mpp.shiftPayment();
694
- appendInferenceHistory({
765
+ if (shouldAppendInferenceHistory({
766
+ isLlmEndpoint,
767
+ usage: sse.result
768
+ })) appendInferenceHistory({
695
769
  historyPath,
696
770
  allModels,
697
771
  walletAddress,
@@ -708,7 +782,7 @@ async function handleMppRequest(opts) {
708
782
  if (response.status === 402) {
709
783
  const responseBody = await response.text();
710
784
  logger.error(`mpp: payment failed, raw response: ${responseBody}`);
711
- appendHistory(historyPath, {
785
+ if (isLlmEndpoint) appendHistory(historyPath, {
712
786
  t: Date.now(),
713
787
  ok: false,
714
788
  kind: "x402_inference",
@@ -728,14 +802,19 @@ async function handleMppRequest(opts) {
728
802
  const usageTracker = createSseTracker();
729
803
  usageTracker.pushJson(responseBody);
730
804
  const payment = mpp.shiftPayment();
731
- appendInferenceHistory({
805
+ const amount = parseMppAmount(payment?.amount);
806
+ if (shouldAppendInferenceHistory({
807
+ isLlmEndpoint,
808
+ amount,
809
+ usage: usageTracker.result
810
+ })) appendInferenceHistory({
732
811
  historyPath,
733
812
  allModels,
734
813
  walletAddress,
735
814
  paymentNetwork: payment?.network ?? "eip155:4217",
736
815
  paymentTo: void 0,
737
816
  tx: tx ?? payment?.receipt?.reference,
738
- amount: parseMppAmount(payment?.amount),
817
+ amount,
739
818
  thinkingMode,
740
819
  usage: usageTracker.result,
741
820
  durationMs: Date.now() - startMs
@@ -744,7 +823,7 @@ async function handleMppRequest(opts) {
744
823
  } catch (err) {
745
824
  dbg(`mpp error: ${String(err)}`);
746
825
  logger.error(`mpp: fetch threw: ${String(err)}`);
747
- appendHistory(historyPath, {
826
+ if (isLlmEndpoint) appendHistory(historyPath, {
748
827
  t: Date.now(),
749
828
  ok: false,
750
829
  kind: "x402_inference",
@@ -1003,25 +1082,43 @@ Examples:
1003
1082
  });
1004
1083
  //#endregion
1005
1084
  //#region src/commands/claude.ts
1006
- const DEFAULT_MODEL = "minimax/minimax-m2.7";
1085
+ const DEFAULT_MODEL = "stepfun/step-3.5-flash";
1086
+ const modelList = [
1087
+ "stepfun/step-3.5-flash",
1088
+ "minimax/minimax-m2.5",
1089
+ "minimax/minimax-m2.7",
1090
+ "z-ai/glm-5",
1091
+ "z-ai/glm-5-turbo",
1092
+ "moonshotai/kimi-k2.5"
1093
+ ].map((id) => ` ${id}`).join("\n");
1007
1094
  function normalizeClaudeArgs(args) {
1008
1095
  return args[0] === "--" ? args.slice(1) : args;
1009
1096
  }
1010
1097
  const claudeCommand = buildCommand({
1011
1098
  docs: {
1012
1099
  brief: "Run Claude Code through a paid local proxy",
1013
- fullDescription: `Start a local x402-proxy server and launch Claude Code with ANTHROPIC_BASE_URL pointed at it.
1100
+ fullDescription: `Starts a local x402-proxy server and launches Claude Code with
1101
+ ANTHROPIC_BASE_URL pointed at it. All inference requests go through
1102
+ the proxy, which handles payments automatically via MPP.
1014
1103
 
1015
- Examples:
1016
- $ x402-proxy claude
1017
- $ x402-proxy claude --model z-ai/glm-5
1018
- $ x402-proxy claude -- --print "explain this codebase"`
1104
+ Usage:
1105
+ $ x402-proxy claude Start with default model
1106
+ $ x402-proxy claude --model z-ai/glm-5 Use a specific model
1107
+ $ x402-proxy claude -- --print "explain this" Pass args to Claude Code
1108
+ $ x402-proxy claude -- -p "summarize *.ts" Print mode (non-interactive)
1109
+
1110
+ Available models (via surf.cascade.fyi):
1111
+ ${modelList}
1112
+
1113
+ The --model value is passed as ANTHROPIC_MODEL and ANTHROPIC_CUSTOM_MODEL_OPTION
1114
+ to Claude Code. Any model supported by the upstream endpoint will work, even if
1115
+ not listed above.`
1019
1116
  },
1020
1117
  parameters: {
1021
1118
  flags: {
1022
1119
  model: {
1023
1120
  kind: "parsed",
1024
- brief: "Model to use (default: minimax/minimax-m2.7)",
1121
+ brief: "Model to use (default: stepfun/step-3.5-flash)",
1025
1122
  parse: String,
1026
1123
  default: DEFAULT_MODEL
1027
1124
  },
@@ -1368,12 +1465,14 @@ Examples:
1368
1465
  };
1369
1466
  if (!url) {
1370
1467
  if (isConfigured()) {
1371
- const { displayStatus } = await import("../status-wknbavnl.js");
1468
+ const { displayStatus } = await import("../status-DZlJ4pS7.js");
1372
1469
  await displayStatus();
1373
1470
  console.log();
1374
1471
  console.log(pc.dim(" Commands:"));
1375
1472
  console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
1376
1473
  console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
1474
+ console.log(` ${pc.cyan("$ npx x402-proxy claude")} Run Claude Code via paid proxy`);
1475
+ console.log(` ${pc.cyan("$ npx x402-proxy claude --model <id>")} Use a specific model`);
1377
1476
  console.log(` ${pc.cyan("$ npx x402-proxy setup")} Reconfigure wallet`);
1378
1477
  console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
1379
1478
  console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Full payment history`);
@@ -1391,6 +1490,7 @@ Examples:
1391
1490
  console.log(` ${pc.cyan("$ npx x402-proxy setup")} Create a wallet`);
1392
1491
  console.log(` ${pc.cyan("$ npx x402-proxy <url>")} Fetch a paid API`);
1393
1492
  console.log(` ${pc.cyan("$ npx x402-proxy mcp <url>")} MCP proxy for AI agents`);
1493
+ console.log(` ${pc.cyan("$ npx x402-proxy claude")} Run Claude Code via paid proxy`);
1394
1494
  console.log(` ${pc.cyan("$ npx x402-proxy wallet")} Addresses and balances`);
1395
1495
  console.log(` ${pc.cyan("$ npx x402-proxy wallet history")} Payment history`);
1396
1496
  console.log(` ${pc.cyan("$ npx x402-proxy --help")} All options`);
@@ -1433,7 +1533,7 @@ Examples:
1433
1533
  verbose(`protocol: ${resolvedProtocol ?? "auto-detect"}, maxDeposit: ${maxDeposit}`);
1434
1534
  let preferredNetwork = config?.defaultNetwork;
1435
1535
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
1436
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1536
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1437
1537
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1438
1538
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
1439
1539
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1600,7 +1700,7 @@ Examples:
1600
1700
  const hasSolana = accepts.some((a) => a.network.startsWith("solana:"));
1601
1701
  const hasMpp = detected.mpp;
1602
1702
  const hasOther = accepts.some((a) => !a.network.startsWith("eip155:") && !a.network.startsWith("solana:"));
1603
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1703
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1604
1704
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1605
1705
  const evmUsdc = hasEvm && balances.evm ? Number(balances.evm.usdc) : 0;
1606
1706
  const solUsdc = hasSolana && balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1820,7 +1920,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1820
1920
  async function startX402Proxy() {
1821
1921
  let preferredNetwork = config?.defaultNetwork;
1822
1922
  if (!preferredNetwork && wallet.evmAddress && wallet.solanaAddress) {
1823
- const { fetchAllBalances } = await import("../wallet-C9UlV7qi.js");
1923
+ const { fetchAllBalances } = await import("../wallet-DBrVZJqe.js");
1824
1924
  const balances = await fetchAllBalances(wallet.evmAddress, wallet.solanaAddress);
1825
1925
  const evmUsdc = balances.evm ? Number(balances.evm.usdc) : 0;
1826
1926
  const solUsdc = balances.sol ? Number(balances.sol.usdc) : 0;
@@ -1840,7 +1940,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1840
1940
  }
1841
1941
  const remoteClient = new Client({
1842
1942
  name: "x402-proxy",
1843
- version: "0.10.5"
1943
+ version: "0.10.7"
1844
1944
  });
1845
1945
  const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
1846
1946
  autoPayment: true,
@@ -1878,7 +1978,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1878
1978
  }
1879
1979
  const localServer = new Server({
1880
1980
  name: "x402-proxy",
1881
- version: "0.10.5"
1981
+ version: "0.10.7"
1882
1982
  }, { capabilities: {
1883
1983
  tools: tools.length > 0 ? {} : void 0,
1884
1984
  resources: remoteResources.length > 0 ? {} : void 0
@@ -1973,7 +2073,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1973
2073
  }));
1974
2074
  const remoteClient = new Client({
1975
2075
  name: "x402-proxy",
1976
- version: "0.10.5"
2076
+ version: "0.10.7"
1977
2077
  });
1978
2078
  await connectTransport(remoteClient);
1979
2079
  const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
@@ -1988,7 +2088,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1988
2088
  }
1989
2089
  const localServer = new Server({
1990
2090
  name: "x402-proxy",
1991
- version: "0.10.5"
2091
+ version: "0.10.7"
1992
2092
  }, { capabilities: {
1993
2093
  tools: tools.length > 0 ? {} : void 0,
1994
2094
  resources: remoteResources.length > 0 ? {} : void 0
@@ -2387,7 +2487,7 @@ const app = buildApplication(buildRouteMap({
2387
2487
  docs: { brief: "curl for x402 paid APIs" }
2388
2488
  }), {
2389
2489
  name: "x402-proxy",
2390
- versionInfo: { currentVersion: "0.10.5" },
2490
+ versionInfo: { currentVersion: "0.10.7" },
2391
2491
  scanner: { caseStyle: "allow-kebab-for-camel" }
2392
2492
  });
2393
2493
  //#endregion
package/dist/index.js CHANGED
@@ -174,6 +174,11 @@ async function createMppProxyHandler(opts) {
174
174
  //#region src/history.ts
175
175
  const HISTORY_MAX_LINES = 1e3;
176
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
+ }
177
182
  function appendHistory(historyPath, record) {
178
183
  try {
179
184
  mkdirSync(dirname(historyPath), { recursive: true });
@@ -195,7 +200,8 @@ function readHistory(historyPath) {
195
200
  try {
196
201
  const parsed = JSON.parse(line);
197
202
  if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
198
- return [parsed];
203
+ const record = parsed;
204
+ return isMeaningfulInferenceRecord(record) ? [record] : [];
199
205
  } catch {
200
206
  return [];
201
207
  }
@@ -224,12 +230,12 @@ function calcSpend(records) {
224
230
  count
225
231
  };
226
232
  }
227
- /** Format a USDC value with adaptive precision (no token suffix). */
228
233
  function formatUsdcValue(amount) {
229
- if (amount >= .01) return amount.toFixed(2);
230
- if (amount >= .001) return amount.toFixed(3);
231
- if (amount >= 1e-4) return amount.toFixed(4);
232
- return amount.toFixed(6);
234
+ return new Intl.NumberFormat("en-US", {
235
+ useGrouping: false,
236
+ minimumFractionDigits: 0,
237
+ maximumFractionDigits: 12
238
+ }).format(amount);
233
239
  }
234
240
  function formatAmount(amount, token) {
235
241
  if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
@@ -258,6 +258,11 @@ var OptimizedSvmScheme = class {
258
258
  };
259
259
  }
260
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
+ }
261
266
  function appendHistory(historyPath, record) {
262
267
  try {
263
268
  mkdirSync(dirname(historyPath), { recursive: true });
@@ -279,7 +284,8 @@ function readHistory(historyPath) {
279
284
  try {
280
285
  const parsed = JSON.parse(line);
281
286
  if (typeof parsed.t !== "number" || typeof parsed.kind !== "string") return [];
282
- return [parsed];
287
+ const record = parsed;
288
+ return isMeaningfulInferenceRecord(record) ? [record] : [];
283
289
  } catch {
284
290
  return [];
285
291
  }
@@ -308,12 +314,12 @@ function calcSpend(records) {
308
314
  count
309
315
  };
310
316
  }
311
- /** Format a USDC value with adaptive precision (no token suffix). */
312
317
  function formatUsdcValue(amount) {
313
- if (amount >= .01) return amount.toFixed(2);
314
- if (amount >= .001) return amount.toFixed(3);
315
- if (amount >= 1e-4) return amount.toFixed(4);
316
- return amount.toFixed(6);
318
+ return new Intl.NumberFormat("en-US", {
319
+ useGrouping: false,
320
+ minimumFractionDigits: 0,
321
+ maximumFractionDigits: 12
322
+ }).format(amount);
317
323
  }
318
324
  function formatAmount(amount, token) {
319
325
  if (token === "USDC") return `${formatUsdcValue(amount)} USDC`;
@@ -1153,7 +1159,7 @@ function createWalletCommand(ctx) {
1153
1159
  if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
1154
1160
  try {
1155
1161
  const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
1156
- const lines = [`x402-proxy v0.10.5`];
1162
+ const lines = [`x402-proxy v0.10.7`];
1157
1163
  const defaultModel = ctx.allModels[0];
1158
1164
  if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
1159
1165
  lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
@@ -1372,10 +1378,28 @@ function resolveMppSessionBudget(value, fallback = "0.5") {
1372
1378
  function dbg(msg) {
1373
1379
  if (process.env.X402_PROXY_DEBUG === "1") process.stderr.write(`[x402-proxy] ${msg}\n`);
1374
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
+ };
1397
+ }
1375
1398
  function createInferenceProxyRouteHandler(opts) {
1376
1399
  const { providers, getX402Proxy, getMppHandler, getWalletAddress, getWalletAddressForNetwork, historyPath, allModels, logger } = opts;
1377
1400
  const sortedProviders = providers.slice().sort((left, right) => right.baseUrl.length - left.baseUrl.length);
1378
1401
  return async (req, res) => {
1402
+ const downstream = createDownstreamAbort(req, res);
1379
1403
  const url = new URL(req.url ?? "/", "http://localhost");
1380
1404
  const provider = sortedProviders.find((entry) => entry.baseUrl === "/" || url.pathname.startsWith(entry.baseUrl));
1381
1405
  if (!provider) {
@@ -1439,7 +1463,8 @@ function createInferenceProxyRouteHandler(opts) {
1439
1463
  const requestInit = {
1440
1464
  method,
1441
1465
  headers,
1442
- body: ["GET", "HEAD"].includes(method) ? void 0 : body
1466
+ body: ["GET", "HEAD"].includes(method) ? void 0 : body,
1467
+ signal: downstream.signal
1443
1468
  };
1444
1469
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
1445
1470
  const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
@@ -1453,11 +1478,14 @@ function createInferenceProxyRouteHandler(opts) {
1453
1478
  } }));
1454
1479
  return true;
1455
1480
  }
1481
+ const mppWalletAddress = getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress;
1456
1482
  return await handleMppRequest({
1457
1483
  res,
1458
1484
  upstreamUrl,
1459
1485
  requestInit,
1460
- walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
1486
+ abortSignal: downstream.signal,
1487
+ isLlmEndpoint,
1488
+ walletAddress: mppWalletAddress,
1461
1489
  historyPath,
1462
1490
  logger,
1463
1491
  allModels,
@@ -1485,7 +1513,7 @@ function createInferenceProxyRouteHandler(opts) {
1485
1513
  const amount = paymentAmount(payment);
1486
1514
  const paymentFrom = (payment?.network && getWalletAddressForNetwork?.(payment.network)) ?? walletAddress;
1487
1515
  const paymentNetwork = payment?.network ?? "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
1488
- appendHistory(historyPath, {
1516
+ if (isLlmEndpoint) appendHistory(historyPath, {
1489
1517
  t: Date.now(),
1490
1518
  ok: false,
1491
1519
  kind: "x402_inference",
@@ -1537,7 +1565,10 @@ function createInferenceProxyRouteHandler(opts) {
1537
1565
  res.writeHead(response.status, resHeaders);
1538
1566
  if (!response.body) {
1539
1567
  res.end();
1540
- appendHistory(historyPath, {
1568
+ if (shouldAppendInferenceHistory({
1569
+ isLlmEndpoint,
1570
+ amount
1571
+ })) appendHistory(historyPath, {
1541
1572
  t: Date.now(),
1542
1573
  ok: true,
1543
1574
  kind: "x402_inference",
@@ -1557,16 +1588,28 @@ function createInferenceProxyRouteHandler(opts) {
1557
1588
  const decoder = new TextDecoder();
1558
1589
  try {
1559
1590
  while (true) {
1591
+ if (downstream.signal.aborted) {
1592
+ await reader.cancel().catch(() => {});
1593
+ break;
1594
+ }
1560
1595
  const { done, value } = await reader.read();
1561
1596
  if (done) break;
1562
1597
  res.write(value);
1563
1598
  sse?.push(decoder.decode(value, { stream: true }));
1599
+ if (isMessagesApi && sse?.sawAnthropicMessageStop) {
1600
+ await reader.cancel().catch(() => {});
1601
+ break;
1602
+ }
1564
1603
  }
1565
1604
  } finally {
1566
1605
  reader.releaseLock();
1567
1606
  }
1568
- res.end();
1569
- appendInferenceHistory({
1607
+ if (!res.writableEnded) res.end();
1608
+ if (shouldAppendInferenceHistory({
1609
+ isLlmEndpoint,
1610
+ amount,
1611
+ usage: sse?.result
1612
+ })) appendInferenceHistory({
1570
1613
  historyPath,
1571
1614
  allModels,
1572
1615
  walletAddress: paymentFrom,
@@ -1583,7 +1626,7 @@ function createInferenceProxyRouteHandler(opts) {
1583
1626
  const msg = String(err);
1584
1627
  logger.error(`x402: fetch threw: ${msg}`);
1585
1628
  getX402Proxy()?.shiftPayment();
1586
- appendHistory(historyPath, {
1629
+ if (isLlmEndpoint) appendHistory(historyPath, {
1587
1630
  t: Date.now(),
1588
1631
  ok: false,
1589
1632
  kind: "x402_inference",
@@ -1598,6 +1641,8 @@ function createInferenceProxyRouteHandler(opts) {
1598
1641
  else userMessage = `x402 request failed: ${msg}`;
1599
1642
  if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
1600
1643
  return true;
1644
+ } finally {
1645
+ downstream.cleanup();
1601
1646
  }
1602
1647
  };
1603
1648
  }
@@ -1616,6 +1661,10 @@ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat)
1616
1661
  code
1617
1662
  } }));
1618
1663
  }
1664
+ function shouldAppendInferenceHistory(opts) {
1665
+ if (!opts.isLlmEndpoint) return false;
1666
+ return opts.amount != null || opts.usage != null;
1667
+ }
1619
1668
  function createSseTracker() {
1620
1669
  let residual = "";
1621
1670
  let anthropicModel = "";
@@ -1623,6 +1672,7 @@ function createSseTracker() {
1623
1672
  let anthropicOutputTokens = 0;
1624
1673
  let anthropicCacheRead;
1625
1674
  let anthropicCacheWrite;
1675
+ let anthropicMessageStop = false;
1626
1676
  let lastOpenAiData = "";
1627
1677
  let isAnthropic = false;
1628
1678
  function processJson(json) {
@@ -1651,6 +1701,11 @@ function createSseTracker() {
1651
1701
  if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
1652
1702
  return;
1653
1703
  }
1704
+ if (type === "message_stop") {
1705
+ isAnthropic = true;
1706
+ anthropicMessageStop = true;
1707
+ return;
1708
+ }
1654
1709
  if (type === "message") {
1655
1710
  isAnthropic = true;
1656
1711
  anthropicModel = parsed.model ?? "";
@@ -1674,6 +1729,9 @@ function createSseTracker() {
1674
1729
  pushJson(text) {
1675
1730
  processJson(text);
1676
1731
  },
1732
+ get sawAnthropicMessageStop() {
1733
+ return anthropicMessageStop;
1734
+ },
1677
1735
  get result() {
1678
1736
  if (isAnthropic) {
1679
1737
  if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
@@ -1703,7 +1761,7 @@ function createSseTracker() {
1703
1761
  };
1704
1762
  }
1705
1763
  async function handleMppRequest(opts) {
1706
- 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;
1707
1765
  try {
1708
1766
  if (wantsStreaming) {
1709
1767
  res.writeHead(200, {
@@ -1715,27 +1773,49 @@ async function handleMppRequest(opts) {
1715
1773
  dbg(`mpp.sse() calling ${upstreamUrl}`);
1716
1774
  const stream = await mpp.sse(upstreamUrl, requestInit);
1717
1775
  dbg("mpp.sse() resolved, iterating stream");
1718
- if (isMessagesApi) for await (const chunk of stream) {
1719
- const text = String(chunk);
1720
- let eventType = "unknown";
1721
- try {
1722
- eventType = JSON.parse(text).type ?? "unknown";
1723
- } catch {}
1724
- res.write(`event: ${eventType}\ndata: ${text}\n\n`);
1725
- sse.pushJson(text);
1726
- }
1727
- else {
1728
- for await (const chunk of stream) {
1729
- const text = String(chunk);
1730
- 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`);
1731
1789
  sse.pushJson(text);
1790
+ if (sse.sawAnthropicMessageStop) {
1791
+ semanticComplete = true;
1792
+ break;
1793
+ }
1794
+ }
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");
1732
1805
  }
1733
- res.write("data: [DONE]\n\n");
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(() => {});
1734
1811
  }
1735
1812
  dbg(`stream done, ${sse.result ? `${sse.result.model} ${sse.result.inputTokens}+${sse.result.outputTokens}t` : "no usage"}`);
1736
- res.end();
1813
+ if (!res.writableEnded) res.end();
1737
1814
  mpp.shiftPayment();
1738
- appendInferenceHistory({
1815
+ if (shouldAppendInferenceHistory({
1816
+ isLlmEndpoint,
1817
+ usage: sse.result
1818
+ })) appendInferenceHistory({
1739
1819
  historyPath,
1740
1820
  allModels,
1741
1821
  walletAddress,
@@ -1752,7 +1832,7 @@ async function handleMppRequest(opts) {
1752
1832
  if (response.status === 402) {
1753
1833
  const responseBody = await response.text();
1754
1834
  logger.error(`mpp: payment failed, raw response: ${responseBody}`);
1755
- appendHistory(historyPath, {
1835
+ if (isLlmEndpoint) appendHistory(historyPath, {
1756
1836
  t: Date.now(),
1757
1837
  ok: false,
1758
1838
  kind: "x402_inference",
@@ -1772,14 +1852,19 @@ async function handleMppRequest(opts) {
1772
1852
  const usageTracker = createSseTracker();
1773
1853
  usageTracker.pushJson(responseBody);
1774
1854
  const payment = mpp.shiftPayment();
1775
- appendInferenceHistory({
1855
+ const amount = parseMppAmount(payment?.amount);
1856
+ if (shouldAppendInferenceHistory({
1857
+ isLlmEndpoint,
1858
+ amount,
1859
+ usage: usageTracker.result
1860
+ })) appendInferenceHistory({
1776
1861
  historyPath,
1777
1862
  allModels,
1778
1863
  walletAddress,
1779
1864
  paymentNetwork: payment?.network ?? "eip155:4217",
1780
1865
  paymentTo: void 0,
1781
1866
  tx: tx ?? payment?.receipt?.reference,
1782
- amount: parseMppAmount(payment?.amount),
1867
+ amount,
1783
1868
  thinkingMode,
1784
1869
  usage: usageTracker.result,
1785
1870
  durationMs: Date.now() - startMs
@@ -1788,7 +1873,7 @@ async function handleMppRequest(opts) {
1788
1873
  } catch (err) {
1789
1874
  dbg(`mpp error: ${String(err)}`);
1790
1875
  logger.error(`mpp: fetch threw: ${String(err)}`);
1791
- appendHistory(historyPath, {
1876
+ if (isLlmEndpoint) appendHistory(historyPath, {
1792
1877
  t: Date.now(),
1793
1878
  ok: false,
1794
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.5",
3
+ "version": "0.10.7",
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 };