x402-proxy 0.10.2 → 0.10.3

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,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.3] - 2026-04-01
11
+
12
+ ### Fixed
13
+ - Anthropic Messages API (`/v1/messages`) SSE streaming - proxy now emits proper `event: {type}` framing and omits the `data: [DONE]` sentinel that breaks the Anthropic SDK parser
14
+ - Anthropic error responses use correct format (`{type:"error",error:{type,message}}`) instead of OpenAI format for `/v1/messages` endpoints
15
+ - MPP streaming usage tracking - token counts were always 0/0 because raw JSON payloads lacked the `data:` prefix the tracker expected
16
+ - Anthropic usage accumulation from `message_start` (input tokens) and `message_delta` (output tokens) events instead of relying on a single final chunk
17
+ - Anthropic thinking mode (`thinking.budget_tokens`) now extracted from request body for history tracking
18
+ - MPP error messages and payment history now show the EVM wallet address instead of the Solana address
19
+
10
20
  ## [0.10.2] - 2026-04-01
11
21
 
12
22
  ### Fixed
package/dist/bin/cli.js CHANGED
@@ -343,6 +343,8 @@ function createInferenceProxyRouteHandler(opts) {
343
343
  if (typeof val === "string") headers[key] = val;
344
344
  }
345
345
  const isChatCompletion = pathSuffix.includes("/chat/completions");
346
+ const isMessagesApi = pathSuffix.includes("/messages");
347
+ const isLlmEndpoint = isChatCompletion || isMessagesApi;
346
348
  let thinkingMode;
347
349
  if (isChatCompletion && body) try {
348
350
  const parsed = JSON.parse(body);
@@ -352,6 +354,10 @@ function createInferenceProxyRouteHandler(opts) {
352
354
  body = JSON.stringify(parsed);
353
355
  }
354
356
  } catch {}
357
+ if (isMessagesApi && body) try {
358
+ const thinking = JSON.parse(body).thinking;
359
+ if (thinking?.type === "enabled" && thinking.budget_tokens) thinkingMode = `budget_${thinking.budget_tokens}`;
360
+ } catch {}
355
361
  const method = req.method ?? "GET";
356
362
  const startMs = Date.now();
357
363
  try {
@@ -361,7 +367,7 @@ function createInferenceProxyRouteHandler(opts) {
361
367
  body: ["GET", "HEAD"].includes(method) ? void 0 : body
362
368
  };
363
369
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
364
- const wantsStreaming = isChatCompletion && /"stream"\s*:\s*true/.test(body);
370
+ const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
365
371
  if (useMpp) {
366
372
  const evmKey = getEvmKey();
367
373
  if (!evmKey) {
@@ -377,12 +383,13 @@ function createInferenceProxyRouteHandler(opts) {
377
383
  res,
378
384
  upstreamUrl,
379
385
  requestInit,
380
- walletAddress,
386
+ walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
381
387
  historyPath,
382
388
  logger,
383
389
  allModels,
384
390
  thinkingMode,
385
391
  wantsStreaming,
392
+ isMessagesApi,
386
393
  startMs,
387
394
  evmKey,
388
395
  mppSessionBudget: provider.mppSessionBudget
@@ -421,15 +428,10 @@ function createInferenceProxyRouteHandler(opts) {
421
428
  if (responseBody.includes("simulation") || responseBody.includes("Simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC (SPL token) to pay for inference.`;
422
429
  else if (responseBody.includes("insufficient") || responseBody.includes("balance")) userMessage = `Insufficient funds in wallet ${walletAddress}. Top up with USDC on Solana mainnet.`;
423
430
  else userMessage = `x402 payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`;
424
- res.writeHead(402, { "Content-Type": "application/json" });
425
- res.end(JSON.stringify({ error: {
426
- message: userMessage,
427
- type: "x402_payment_error",
428
- code: "payment_failed"
429
- } }));
431
+ writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
430
432
  return true;
431
433
  }
432
- if (!response.ok && isChatCompletion) {
434
+ if (!response.ok && isLlmEndpoint) {
433
435
  const responseBody = await response.text();
434
436
  logger.error(`x402: upstream error ${response.status}: ${responseBody.substring(0, 300)}`);
435
437
  const payment = proxy.shiftPayment();
@@ -448,12 +450,7 @@ function createInferenceProxyRouteHandler(opts) {
448
450
  ms: Date.now() - startMs,
449
451
  error: `upstream_${response.status}`
450
452
  });
451
- res.writeHead(502, { "Content-Type": "application/json" });
452
- res.end(JSON.stringify({ error: {
453
- message: `LLM provider temporarily unavailable (HTTP ${response.status}). Try again shortly.`,
454
- type: "x402_upstream_error",
455
- code: "upstream_failed"
456
- } }));
453
+ writeErrorResponse(res, 502, `LLM provider temporarily unavailable (HTTP ${response.status}). Try again shortly.`, "x402_upstream_error", "upstream_failed", isMessagesApi);
457
454
  return true;
458
455
  }
459
456
  logger.info(`x402: response ${response.status}`);
@@ -482,7 +479,7 @@ function createInferenceProxyRouteHandler(opts) {
482
479
  return true;
483
480
  }
484
481
  const ct = response.headers.get("content-type") || "";
485
- const sse = isChatCompletion && ct.includes("text/event-stream") ? createSseTracker() : null;
482
+ const sse = isLlmEndpoint && ct.includes("text/event-stream") ? createSseTracker() : null;
486
483
  const reader = response.body.getReader();
487
484
  const decoder = new TextDecoder();
488
485
  try {
@@ -496,56 +493,17 @@ function createInferenceProxyRouteHandler(opts) {
496
493
  reader.releaseLock();
497
494
  }
498
495
  res.end();
499
- const durationMs = Date.now() - startMs;
500
- if (sse?.lastData) try {
501
- const parsed = JSON.parse(sse.lastData);
502
- const usage = parsed.usage;
503
- const model = parsed.model ?? "";
504
- appendHistory(historyPath, {
505
- t: Date.now(),
506
- ok: true,
507
- kind: "x402_inference",
508
- net: paymentNetwork,
509
- from: paymentFrom,
510
- to: payment?.payTo,
511
- tx: txSig,
512
- amount,
513
- token: "USDC",
514
- provider: allModels.find((m) => m.id === model || `${m.provider}/${m.id}` === model)?.provider,
515
- model,
516
- inputTokens: usage?.prompt_tokens ?? 0,
517
- outputTokens: usage?.completion_tokens ?? 0,
518
- reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
519
- cacheRead: usage?.prompt_tokens_details?.cached_tokens,
520
- cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
521
- thinking: thinkingMode,
522
- ms: durationMs
523
- });
524
- } catch {
525
- appendHistory(historyPath, {
526
- t: Date.now(),
527
- ok: true,
528
- kind: "x402_inference",
529
- net: paymentNetwork,
530
- from: paymentFrom,
531
- to: payment?.payTo,
532
- tx: txSig,
533
- amount,
534
- token: "USDC",
535
- ms: durationMs
536
- });
537
- }
538
- else appendHistory(historyPath, {
539
- t: Date.now(),
540
- ok: true,
541
- kind: "x402_inference",
542
- net: paymentNetwork,
543
- from: paymentFrom,
544
- to: payment?.payTo,
496
+ appendInferenceHistory({
497
+ historyPath,
498
+ allModels,
499
+ walletAddress: paymentFrom,
500
+ paymentNetwork,
501
+ paymentTo: payment?.payTo,
545
502
  tx: txSig,
546
503
  amount,
547
- token: "USDC",
548
- ms: durationMs
504
+ thinkingMode,
505
+ usage: sse?.result,
506
+ durationMs: Date.now() - startMs
549
507
  });
550
508
  return true;
551
509
  } catch (err) {
@@ -565,29 +523,109 @@ function createInferenceProxyRouteHandler(opts) {
565
523
  if (msg.includes("Simulation failed") || msg.includes("simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC and SOL to pay for inference.`;
566
524
  else if (msg.includes("Failed to create payment")) userMessage = `x402 payment creation failed: ${msg}. Wallet: ${walletAddress}`;
567
525
  else userMessage = `x402 request failed: ${msg}`;
568
- if (!res.headersSent) {
569
- res.writeHead(402, { "Content-Type": "application/json" });
570
- res.end(JSON.stringify({ error: {
571
- message: userMessage,
572
- type: "x402_payment_error",
573
- code: "payment_failed"
574
- } }));
575
- }
526
+ if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
576
527
  return true;
577
528
  }
578
529
  };
579
530
  }
531
+ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat) {
532
+ res.writeHead(status, { "Content-Type": "application/json" });
533
+ if (isAnthropicFormat) res.end(JSON.stringify({
534
+ type: "error",
535
+ error: {
536
+ type,
537
+ message
538
+ }
539
+ }));
540
+ else res.end(JSON.stringify({ error: {
541
+ message,
542
+ type,
543
+ code
544
+ } }));
545
+ }
580
546
  function createSseTracker() {
581
547
  let residual = "";
582
- let lastDataLine = "";
548
+ let anthropicModel = "";
549
+ let anthropicInputTokens = 0;
550
+ let anthropicOutputTokens = 0;
551
+ let anthropicCacheRead;
552
+ let anthropicCacheWrite;
553
+ let lastOpenAiData = "";
554
+ let isAnthropic = false;
555
+ function processJson(json) {
556
+ let parsed;
557
+ try {
558
+ parsed = JSON.parse(json);
559
+ } catch {
560
+ return;
561
+ }
562
+ const type = parsed.type;
563
+ if (type === "message_start") {
564
+ isAnthropic = true;
565
+ const msg = parsed.message;
566
+ anthropicModel = msg?.model ?? "";
567
+ const u = msg?.usage;
568
+ if (u) {
569
+ anthropicInputTokens = u.input_tokens ?? 0;
570
+ anthropicCacheWrite = u.cache_creation_input_tokens ?? void 0;
571
+ anthropicCacheRead = u.cache_read_input_tokens ?? void 0;
572
+ }
573
+ return;
574
+ }
575
+ if (type === "message_delta") {
576
+ isAnthropic = true;
577
+ const u = parsed.usage;
578
+ if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
579
+ return;
580
+ }
581
+ if (type === "message") {
582
+ isAnthropic = true;
583
+ anthropicModel = parsed.model ?? "";
584
+ const u = parsed.usage;
585
+ if (u) {
586
+ anthropicInputTokens = u.input_tokens ?? 0;
587
+ anthropicOutputTokens = u.output_tokens ?? 0;
588
+ anthropicCacheWrite = u.cache_creation_input_tokens ?? void 0;
589
+ anthropicCacheRead = u.cache_read_input_tokens ?? void 0;
590
+ }
591
+ return;
592
+ }
593
+ if (parsed.usage || parsed.model) lastOpenAiData = json;
594
+ }
583
595
  return {
584
596
  push(text) {
585
597
  const lines = (residual + text).split("\n");
586
598
  residual = lines.pop() ?? "";
587
- for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") lastDataLine = line.slice(6);
599
+ for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") processJson(line.slice(6));
600
+ },
601
+ pushJson(text) {
602
+ processJson(text);
588
603
  },
589
- get lastData() {
590
- return lastDataLine;
604
+ get result() {
605
+ if (isAnthropic) {
606
+ if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
607
+ return {
608
+ model: anthropicModel,
609
+ inputTokens: anthropicInputTokens,
610
+ outputTokens: anthropicOutputTokens,
611
+ cacheRead: anthropicCacheRead,
612
+ cacheWrite: anthropicCacheWrite
613
+ };
614
+ }
615
+ if (!lastOpenAiData) return void 0;
616
+ try {
617
+ const parsed = JSON.parse(lastOpenAiData);
618
+ return {
619
+ model: parsed.model ?? "",
620
+ inputTokens: parsed.usage?.prompt_tokens ?? 0,
621
+ outputTokens: parsed.usage?.completion_tokens ?? 0,
622
+ reasoningTokens: parsed.usage?.completion_tokens_details?.reasoning_tokens,
623
+ cacheRead: parsed.usage?.prompt_tokens_details?.cached_tokens,
624
+ cacheWrite: parsed.usage?.prompt_tokens_details?.cache_creation_input_tokens
625
+ };
626
+ } catch {
627
+ return;
628
+ }
591
629
  }
592
630
  };
593
631
  }
@@ -597,7 +635,7 @@ async function closeMppSession(handler) {
597
635
  } catch {}
598
636
  }
599
637
  async function handleMppRequest(opts) {
600
- const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, startMs, evmKey, mppSessionBudget } = opts;
638
+ const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, evmKey, mppSessionBudget } = opts;
601
639
  const mpp = await createMppProxyHandler({
602
640
  evmKey,
603
641
  maxDeposit: mppSessionBudget
@@ -611,10 +649,22 @@ async function handleMppRequest(opts) {
611
649
  });
612
650
  const sse = createSseTracker();
613
651
  const stream = await mpp.sse(upstreamUrl, requestInit);
614
- for await (const chunk of stream) {
652
+ if (isMessagesApi) for await (const chunk of stream) {
615
653
  const text = String(chunk);
616
- res.write(text);
617
- sse.push(text);
654
+ let eventType = "unknown";
655
+ try {
656
+ eventType = JSON.parse(text).type ?? "unknown";
657
+ } catch {}
658
+ res.write(`event: ${eventType}\ndata: ${text}\n\n`);
659
+ sse.pushJson(text);
660
+ }
661
+ else {
662
+ for await (const chunk of stream) {
663
+ const text = String(chunk);
664
+ res.write(`data: ${text}\n\n`);
665
+ sse.pushJson(text);
666
+ }
667
+ res.write("data: [DONE]\n\n");
618
668
  }
619
669
  res.end();
620
670
  mpp.shiftPayment();
@@ -628,7 +678,7 @@ async function handleMppRequest(opts) {
628
678
  tx: payment?.receipt?.reference ?? payment?.channelId,
629
679
  amount: parseMppAmount(payment?.amount),
630
680
  thinkingMode,
631
- lastDataLine: sse.lastData,
681
+ usage: sse.result,
632
682
  durationMs: Date.now() - startMs
633
683
  });
634
684
  return true;
@@ -647,12 +697,7 @@ async function handleMppRequest(opts) {
647
697
  ms: Date.now() - startMs,
648
698
  error: "payment_required"
649
699
  });
650
- res.writeHead(402, { "Content-Type": "application/json" });
651
- res.end(JSON.stringify({ error: {
652
- message: `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`,
653
- type: "mpp_payment_error",
654
- code: "payment_failed"
655
- } }));
700
+ writeErrorResponse(res, 402, `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`, "mpp_payment_error", "payment_failed", isMessagesApi);
656
701
  return true;
657
702
  }
658
703
  const resHeaders = {};
@@ -660,6 +705,8 @@ async function handleMppRequest(opts) {
660
705
  res.writeHead(response.status, resHeaders);
661
706
  const responseBody = await response.text();
662
707
  res.end(responseBody);
708
+ const usageTracker = createSseTracker();
709
+ usageTracker.pushJson(responseBody);
663
710
  const payment = mpp.shiftPayment();
664
711
  appendInferenceHistory({
665
712
  historyPath,
@@ -670,7 +717,7 @@ async function handleMppRequest(opts) {
670
717
  tx: tx ?? payment?.receipt?.reference,
671
718
  amount: parseMppAmount(payment?.amount),
672
719
  thinkingMode,
673
- lastDataLine: responseBody,
720
+ usage: usageTracker.result,
674
721
  durationMs: Date.now() - startMs
675
722
  });
676
723
  return true;
@@ -685,14 +732,7 @@ async function handleMppRequest(opts) {
685
732
  ms: Date.now() - startMs,
686
733
  error: String(err).substring(0, 200)
687
734
  });
688
- if (!res.headersSent) {
689
- res.writeHead(402, { "Content-Type": "application/json" });
690
- res.end(JSON.stringify({ error: {
691
- message: `MPP request failed: ${String(err)}`,
692
- type: "mpp_payment_error",
693
- code: "payment_failed"
694
- } }));
695
- }
735
+ if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
696
736
  return true;
697
737
  } finally {
698
738
  await closeMppSession(mpp);
@@ -701,45 +741,39 @@ async function handleMppRequest(opts) {
701
741
  }
702
742
  }
703
743
  function appendInferenceHistory(opts) {
704
- const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, lastDataLine, durationMs } = opts;
705
- try {
706
- const parsed = JSON.parse(lastDataLine);
707
- const usage = parsed.usage;
708
- const model = parsed.model ?? "";
709
- appendHistory(historyPath, {
710
- t: Date.now(),
711
- ok: true,
712
- kind: "x402_inference",
713
- net: paymentNetwork,
714
- from: walletAddress,
715
- to: paymentTo,
716
- tx,
717
- amount,
718
- token: "USDC",
719
- provider: allModels.find((entry) => entry.id === model || `${entry.provider}/${entry.id}` === model)?.provider,
720
- model,
721
- inputTokens: usage?.prompt_tokens ?? 0,
722
- outputTokens: usage?.completion_tokens ?? 0,
723
- reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
724
- cacheRead: usage?.prompt_tokens_details?.cached_tokens,
725
- cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
726
- thinking: thinkingMode,
727
- ms: durationMs
728
- });
729
- } catch {
730
- appendHistory(historyPath, {
731
- t: Date.now(),
732
- ok: true,
733
- kind: "x402_inference",
734
- net: paymentNetwork,
735
- from: walletAddress,
736
- to: paymentTo,
737
- tx,
738
- amount,
739
- token: "USDC",
740
- ms: durationMs
741
- });
742
- }
744
+ const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, usage, durationMs } = opts;
745
+ if (usage) appendHistory(historyPath, {
746
+ t: Date.now(),
747
+ ok: true,
748
+ kind: "x402_inference",
749
+ net: paymentNetwork,
750
+ from: walletAddress,
751
+ to: paymentTo,
752
+ tx,
753
+ amount,
754
+ token: "USDC",
755
+ provider: allModels.find((entry) => entry.id === usage.model || `${entry.provider}/${entry.id}` === usage.model)?.provider,
756
+ model: usage.model,
757
+ inputTokens: usage.inputTokens,
758
+ outputTokens: usage.outputTokens,
759
+ reasoningTokens: usage.reasoningTokens,
760
+ cacheRead: usage.cacheRead,
761
+ cacheWrite: usage.cacheWrite,
762
+ thinking: thinkingMode,
763
+ ms: durationMs
764
+ });
765
+ else appendHistory(historyPath, {
766
+ t: Date.now(),
767
+ ok: true,
768
+ kind: "x402_inference",
769
+ net: paymentNetwork,
770
+ from: walletAddress,
771
+ to: paymentTo,
772
+ tx,
773
+ amount,
774
+ token: "USDC",
775
+ ms: durationMs
776
+ });
743
777
  }
744
778
  //#endregion
745
779
  //#region src/commands/serve.ts
@@ -1778,7 +1812,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1778
1812
  }
1779
1813
  const remoteClient = new Client({
1780
1814
  name: "x402-proxy",
1781
- version: "0.10.2"
1815
+ version: "0.10.3"
1782
1816
  });
1783
1817
  const x402Mcp = new x402MCPClient(remoteClient, x402PaymentClient, {
1784
1818
  autoPayment: true,
@@ -1816,7 +1850,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1816
1850
  }
1817
1851
  const localServer = new Server({
1818
1852
  name: "x402-proxy",
1819
- version: "0.10.2"
1853
+ version: "0.10.3"
1820
1854
  }, { capabilities: {
1821
1855
  tools: tools.length > 0 ? {} : void 0,
1822
1856
  resources: remoteResources.length > 0 ? {} : void 0
@@ -1911,7 +1945,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1911
1945
  }));
1912
1946
  const remoteClient = new Client({
1913
1947
  name: "x402-proxy",
1914
- version: "0.10.2"
1948
+ version: "0.10.3"
1915
1949
  });
1916
1950
  await connectTransport(remoteClient);
1917
1951
  const mppClient = McpClient.wrap(remoteClient, { methods: wrappedMethods });
@@ -1926,7 +1960,7 @@ Wallet is auto-generated on first run. No env vars needed.`
1926
1960
  }
1927
1961
  const localServer = new Server({
1928
1962
  name: "x402-proxy",
1929
- version: "0.10.2"
1963
+ version: "0.10.3"
1930
1964
  }, { capabilities: {
1931
1965
  tools: tools.length > 0 ? {} : void 0,
1932
1966
  resources: remoteResources.length > 0 ? {} : void 0
@@ -2325,7 +2359,7 @@ const app = buildApplication(buildRouteMap({
2325
2359
  docs: { brief: "curl for x402 paid APIs" }
2326
2360
  }), {
2327
2361
  name: "x402-proxy",
2328
- versionInfo: { currentVersion: "0.10.2" },
2362
+ versionInfo: { currentVersion: "0.10.3" },
2329
2363
  scanner: { caseStyle: "allow-kebab-for-camel" }
2330
2364
  });
2331
2365
  //#endregion
@@ -1150,7 +1150,7 @@ function createWalletCommand(ctx) {
1150
1150
  if (parts[0]?.toLowerCase() === "send") return { text: "Use `/x_send <amount|all> <address>` for transfers." };
1151
1151
  try {
1152
1152
  const snap = await getWalletSnapshot(ctx.rpcUrl, solanaWallet, evmWallet, ctx.historyPath);
1153
- const lines = [`x402-proxy v0.10.2`];
1153
+ const lines = [`x402-proxy v0.10.3`];
1154
1154
  const defaultModel = ctx.allModels[0];
1155
1155
  if (defaultModel) lines.push("", `**Model** - ${defaultModel.name} (${defaultModel.provider})`);
1156
1156
  lines.push("", `**Protocol** - ${ctx.getDefaultRequestProtocol()}`);
@@ -1411,6 +1411,8 @@ function createInferenceProxyRouteHandler(opts) {
1411
1411
  if (typeof val === "string") headers[key] = val;
1412
1412
  }
1413
1413
  const isChatCompletion = pathSuffix.includes("/chat/completions");
1414
+ const isMessagesApi = pathSuffix.includes("/messages");
1415
+ const isLlmEndpoint = isChatCompletion || isMessagesApi;
1414
1416
  let thinkingMode;
1415
1417
  if (isChatCompletion && body) try {
1416
1418
  const parsed = JSON.parse(body);
@@ -1420,6 +1422,10 @@ function createInferenceProxyRouteHandler(opts) {
1420
1422
  body = JSON.stringify(parsed);
1421
1423
  }
1422
1424
  } catch {}
1425
+ if (isMessagesApi && body) try {
1426
+ const thinking = JSON.parse(body).thinking;
1427
+ if (thinking?.type === "enabled" && thinking.budget_tokens) thinkingMode = `budget_${thinking.budget_tokens}`;
1428
+ } catch {}
1423
1429
  const method = req.method ?? "GET";
1424
1430
  const startMs = Date.now();
1425
1431
  try {
@@ -1429,7 +1435,7 @@ function createInferenceProxyRouteHandler(opts) {
1429
1435
  body: ["GET", "HEAD"].includes(method) ? void 0 : body
1430
1436
  };
1431
1437
  const useMpp = provider.protocol === "mpp" || provider.protocol === "auto";
1432
- const wantsStreaming = isChatCompletion && /"stream"\s*:\s*true/.test(body);
1438
+ const wantsStreaming = isLlmEndpoint && /"stream"\s*:\s*true/.test(body);
1433
1439
  if (useMpp) {
1434
1440
  const evmKey = getEvmKey();
1435
1441
  if (!evmKey) {
@@ -1445,12 +1451,13 @@ function createInferenceProxyRouteHandler(opts) {
1445
1451
  res,
1446
1452
  upstreamUrl,
1447
1453
  requestInit,
1448
- walletAddress,
1454
+ walletAddress: getWalletAddressForNetwork?.("eip155:4217") ?? walletAddress,
1449
1455
  historyPath,
1450
1456
  logger,
1451
1457
  allModels,
1452
1458
  thinkingMode,
1453
1459
  wantsStreaming,
1460
+ isMessagesApi,
1454
1461
  startMs,
1455
1462
  evmKey,
1456
1463
  mppSessionBudget: provider.mppSessionBudget
@@ -1489,15 +1496,10 @@ function createInferenceProxyRouteHandler(opts) {
1489
1496
  if (responseBody.includes("simulation") || responseBody.includes("Simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC (SPL token) to pay for inference.`;
1490
1497
  else if (responseBody.includes("insufficient") || responseBody.includes("balance")) userMessage = `Insufficient funds in wallet ${walletAddress}. Top up with USDC on Solana mainnet.`;
1491
1498
  else userMessage = `x402 payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`;
1492
- res.writeHead(402, { "Content-Type": "application/json" });
1493
- res.end(JSON.stringify({ error: {
1494
- message: userMessage,
1495
- type: "x402_payment_error",
1496
- code: "payment_failed"
1497
- } }));
1499
+ writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
1498
1500
  return true;
1499
1501
  }
1500
- if (!response.ok && isChatCompletion) {
1502
+ if (!response.ok && isLlmEndpoint) {
1501
1503
  const responseBody = await response.text();
1502
1504
  logger.error(`x402: upstream error ${response.status}: ${responseBody.substring(0, 300)}`);
1503
1505
  const payment = proxy.shiftPayment();
@@ -1516,12 +1518,7 @@ function createInferenceProxyRouteHandler(opts) {
1516
1518
  ms: Date.now() - startMs,
1517
1519
  error: `upstream_${response.status}`
1518
1520
  });
1519
- res.writeHead(502, { "Content-Type": "application/json" });
1520
- res.end(JSON.stringify({ error: {
1521
- message: `LLM provider temporarily unavailable (HTTP ${response.status}). Try again shortly.`,
1522
- type: "x402_upstream_error",
1523
- code: "upstream_failed"
1524
- } }));
1521
+ writeErrorResponse(res, 502, `LLM provider temporarily unavailable (HTTP ${response.status}). Try again shortly.`, "x402_upstream_error", "upstream_failed", isMessagesApi);
1525
1522
  return true;
1526
1523
  }
1527
1524
  logger.info(`x402: response ${response.status}`);
@@ -1550,7 +1547,7 @@ function createInferenceProxyRouteHandler(opts) {
1550
1547
  return true;
1551
1548
  }
1552
1549
  const ct = response.headers.get("content-type") || "";
1553
- const sse = isChatCompletion && ct.includes("text/event-stream") ? createSseTracker() : null;
1550
+ const sse = isLlmEndpoint && ct.includes("text/event-stream") ? createSseTracker() : null;
1554
1551
  const reader = response.body.getReader();
1555
1552
  const decoder = new TextDecoder();
1556
1553
  try {
@@ -1564,56 +1561,17 @@ function createInferenceProxyRouteHandler(opts) {
1564
1561
  reader.releaseLock();
1565
1562
  }
1566
1563
  res.end();
1567
- const durationMs = Date.now() - startMs;
1568
- if (sse?.lastData) try {
1569
- const parsed = JSON.parse(sse.lastData);
1570
- const usage = parsed.usage;
1571
- const model = parsed.model ?? "";
1572
- appendHistory(historyPath, {
1573
- t: Date.now(),
1574
- ok: true,
1575
- kind: "x402_inference",
1576
- net: paymentNetwork,
1577
- from: paymentFrom,
1578
- to: payment?.payTo,
1579
- tx: txSig,
1580
- amount,
1581
- token: "USDC",
1582
- provider: allModels.find((m) => m.id === model || `${m.provider}/${m.id}` === model)?.provider,
1583
- model,
1584
- inputTokens: usage?.prompt_tokens ?? 0,
1585
- outputTokens: usage?.completion_tokens ?? 0,
1586
- reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
1587
- cacheRead: usage?.prompt_tokens_details?.cached_tokens,
1588
- cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
1589
- thinking: thinkingMode,
1590
- ms: durationMs
1591
- });
1592
- } catch {
1593
- appendHistory(historyPath, {
1594
- t: Date.now(),
1595
- ok: true,
1596
- kind: "x402_inference",
1597
- net: paymentNetwork,
1598
- from: paymentFrom,
1599
- to: payment?.payTo,
1600
- tx: txSig,
1601
- amount,
1602
- token: "USDC",
1603
- ms: durationMs
1604
- });
1605
- }
1606
- else appendHistory(historyPath, {
1607
- t: Date.now(),
1608
- ok: true,
1609
- kind: "x402_inference",
1610
- net: paymentNetwork,
1611
- from: paymentFrom,
1612
- to: payment?.payTo,
1564
+ appendInferenceHistory({
1565
+ historyPath,
1566
+ allModels,
1567
+ walletAddress: paymentFrom,
1568
+ paymentNetwork,
1569
+ paymentTo: payment?.payTo,
1613
1570
  tx: txSig,
1614
1571
  amount,
1615
- token: "USDC",
1616
- ms: durationMs
1572
+ thinkingMode,
1573
+ usage: sse?.result,
1574
+ durationMs: Date.now() - startMs
1617
1575
  });
1618
1576
  return true;
1619
1577
  } catch (err) {
@@ -1633,29 +1591,109 @@ function createInferenceProxyRouteHandler(opts) {
1633
1591
  if (msg.includes("Simulation failed") || msg.includes("simulation")) userMessage = `Insufficient USDC or SOL in wallet ${walletAddress}. Fund it with USDC and SOL to pay for inference.`;
1634
1592
  else if (msg.includes("Failed to create payment")) userMessage = `x402 payment creation failed: ${msg}. Wallet: ${walletAddress}`;
1635
1593
  else userMessage = `x402 request failed: ${msg}`;
1636
- if (!res.headersSent) {
1637
- res.writeHead(402, { "Content-Type": "application/json" });
1638
- res.end(JSON.stringify({ error: {
1639
- message: userMessage,
1640
- type: "x402_payment_error",
1641
- code: "payment_failed"
1642
- } }));
1643
- }
1594
+ if (!res.headersSent) writeErrorResponse(res, 402, userMessage, "x402_payment_error", "payment_failed", isMessagesApi);
1644
1595
  return true;
1645
1596
  }
1646
1597
  };
1647
1598
  }
1599
+ function writeErrorResponse(res, status, message, type, code, isAnthropicFormat) {
1600
+ res.writeHead(status, { "Content-Type": "application/json" });
1601
+ if (isAnthropicFormat) res.end(JSON.stringify({
1602
+ type: "error",
1603
+ error: {
1604
+ type,
1605
+ message
1606
+ }
1607
+ }));
1608
+ else res.end(JSON.stringify({ error: {
1609
+ message,
1610
+ type,
1611
+ code
1612
+ } }));
1613
+ }
1648
1614
  function createSseTracker() {
1649
1615
  let residual = "";
1650
- let lastDataLine = "";
1616
+ let anthropicModel = "";
1617
+ let anthropicInputTokens = 0;
1618
+ let anthropicOutputTokens = 0;
1619
+ let anthropicCacheRead;
1620
+ let anthropicCacheWrite;
1621
+ let lastOpenAiData = "";
1622
+ let isAnthropic = false;
1623
+ function processJson(json) {
1624
+ let parsed;
1625
+ try {
1626
+ parsed = JSON.parse(json);
1627
+ } catch {
1628
+ return;
1629
+ }
1630
+ const type = parsed.type;
1631
+ if (type === "message_start") {
1632
+ isAnthropic = true;
1633
+ const msg = parsed.message;
1634
+ anthropicModel = msg?.model ?? "";
1635
+ const u = msg?.usage;
1636
+ if (u) {
1637
+ anthropicInputTokens = u.input_tokens ?? 0;
1638
+ anthropicCacheWrite = u.cache_creation_input_tokens ?? void 0;
1639
+ anthropicCacheRead = u.cache_read_input_tokens ?? void 0;
1640
+ }
1641
+ return;
1642
+ }
1643
+ if (type === "message_delta") {
1644
+ isAnthropic = true;
1645
+ const u = parsed.usage;
1646
+ if (u?.output_tokens != null) anthropicOutputTokens = u.output_tokens;
1647
+ return;
1648
+ }
1649
+ if (type === "message") {
1650
+ isAnthropic = true;
1651
+ anthropicModel = parsed.model ?? "";
1652
+ const u = parsed.usage;
1653
+ if (u) {
1654
+ anthropicInputTokens = u.input_tokens ?? 0;
1655
+ anthropicOutputTokens = u.output_tokens ?? 0;
1656
+ anthropicCacheWrite = u.cache_creation_input_tokens ?? void 0;
1657
+ anthropicCacheRead = u.cache_read_input_tokens ?? void 0;
1658
+ }
1659
+ return;
1660
+ }
1661
+ if (parsed.usage || parsed.model) lastOpenAiData = json;
1662
+ }
1651
1663
  return {
1652
1664
  push(text) {
1653
1665
  const lines = (residual + text).split("\n");
1654
1666
  residual = lines.pop() ?? "";
1655
- for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") lastDataLine = line.slice(6);
1667
+ for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") processJson(line.slice(6));
1668
+ },
1669
+ pushJson(text) {
1670
+ processJson(text);
1656
1671
  },
1657
- get lastData() {
1658
- return lastDataLine;
1672
+ get result() {
1673
+ if (isAnthropic) {
1674
+ if (!anthropicModel && !anthropicInputTokens && !anthropicOutputTokens) return void 0;
1675
+ return {
1676
+ model: anthropicModel,
1677
+ inputTokens: anthropicInputTokens,
1678
+ outputTokens: anthropicOutputTokens,
1679
+ cacheRead: anthropicCacheRead,
1680
+ cacheWrite: anthropicCacheWrite
1681
+ };
1682
+ }
1683
+ if (!lastOpenAiData) return void 0;
1684
+ try {
1685
+ const parsed = JSON.parse(lastOpenAiData);
1686
+ return {
1687
+ model: parsed.model ?? "",
1688
+ inputTokens: parsed.usage?.prompt_tokens ?? 0,
1689
+ outputTokens: parsed.usage?.completion_tokens ?? 0,
1690
+ reasoningTokens: parsed.usage?.completion_tokens_details?.reasoning_tokens,
1691
+ cacheRead: parsed.usage?.prompt_tokens_details?.cached_tokens,
1692
+ cacheWrite: parsed.usage?.prompt_tokens_details?.cache_creation_input_tokens
1693
+ };
1694
+ } catch {
1695
+ return;
1696
+ }
1659
1697
  }
1660
1698
  };
1661
1699
  }
@@ -1665,7 +1703,7 @@ async function closeMppSession(handler) {
1665
1703
  } catch {}
1666
1704
  }
1667
1705
  async function handleMppRequest(opts) {
1668
- const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, startMs, evmKey, mppSessionBudget } = opts;
1706
+ const { req, res, upstreamUrl, requestInit, walletAddress, historyPath, logger, allModels, thinkingMode, wantsStreaming, isMessagesApi, startMs, evmKey, mppSessionBudget } = opts;
1669
1707
  const mpp = await createMppProxyHandler({
1670
1708
  evmKey,
1671
1709
  maxDeposit: mppSessionBudget
@@ -1679,10 +1717,22 @@ async function handleMppRequest(opts) {
1679
1717
  });
1680
1718
  const sse = createSseTracker();
1681
1719
  const stream = await mpp.sse(upstreamUrl, requestInit);
1682
- for await (const chunk of stream) {
1720
+ if (isMessagesApi) for await (const chunk of stream) {
1683
1721
  const text = String(chunk);
1684
- res.write(text);
1685
- sse.push(text);
1722
+ let eventType = "unknown";
1723
+ try {
1724
+ eventType = JSON.parse(text).type ?? "unknown";
1725
+ } catch {}
1726
+ res.write(`event: ${eventType}\ndata: ${text}\n\n`);
1727
+ sse.pushJson(text);
1728
+ }
1729
+ else {
1730
+ for await (const chunk of stream) {
1731
+ const text = String(chunk);
1732
+ res.write(`data: ${text}\n\n`);
1733
+ sse.pushJson(text);
1734
+ }
1735
+ res.write("data: [DONE]\n\n");
1686
1736
  }
1687
1737
  res.end();
1688
1738
  mpp.shiftPayment();
@@ -1696,7 +1746,7 @@ async function handleMppRequest(opts) {
1696
1746
  tx: payment?.receipt?.reference ?? payment?.channelId,
1697
1747
  amount: parseMppAmount(payment?.amount),
1698
1748
  thinkingMode,
1699
- lastDataLine: sse.lastData,
1749
+ usage: sse.result,
1700
1750
  durationMs: Date.now() - startMs
1701
1751
  });
1702
1752
  return true;
@@ -1715,12 +1765,7 @@ async function handleMppRequest(opts) {
1715
1765
  ms: Date.now() - startMs,
1716
1766
  error: "payment_required"
1717
1767
  });
1718
- res.writeHead(402, { "Content-Type": "application/json" });
1719
- res.end(JSON.stringify({ error: {
1720
- message: `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`,
1721
- type: "mpp_payment_error",
1722
- code: "payment_failed"
1723
- } }));
1768
+ writeErrorResponse(res, 402, `MPP payment failed: ${responseBody.substring(0, 200) || "unknown error"}. Wallet: ${walletAddress}`, "mpp_payment_error", "payment_failed", isMessagesApi);
1724
1769
  return true;
1725
1770
  }
1726
1771
  const resHeaders = {};
@@ -1728,6 +1773,8 @@ async function handleMppRequest(opts) {
1728
1773
  res.writeHead(response.status, resHeaders);
1729
1774
  const responseBody = await response.text();
1730
1775
  res.end(responseBody);
1776
+ const usageTracker = createSseTracker();
1777
+ usageTracker.pushJson(responseBody);
1731
1778
  const payment = mpp.shiftPayment();
1732
1779
  appendInferenceHistory({
1733
1780
  historyPath,
@@ -1738,7 +1785,7 @@ async function handleMppRequest(opts) {
1738
1785
  tx: tx ?? payment?.receipt?.reference,
1739
1786
  amount: parseMppAmount(payment?.amount),
1740
1787
  thinkingMode,
1741
- lastDataLine: responseBody,
1788
+ usage: usageTracker.result,
1742
1789
  durationMs: Date.now() - startMs
1743
1790
  });
1744
1791
  return true;
@@ -1753,14 +1800,7 @@ async function handleMppRequest(opts) {
1753
1800
  ms: Date.now() - startMs,
1754
1801
  error: String(err).substring(0, 200)
1755
1802
  });
1756
- if (!res.headersSent) {
1757
- res.writeHead(402, { "Content-Type": "application/json" });
1758
- res.end(JSON.stringify({ error: {
1759
- message: `MPP request failed: ${String(err)}`,
1760
- type: "mpp_payment_error",
1761
- code: "payment_failed"
1762
- } }));
1763
- }
1803
+ if (!res.headersSent) writeErrorResponse(res, 402, `MPP request failed: ${String(err)}`, "mpp_payment_error", "payment_failed", isMessagesApi);
1764
1804
  return true;
1765
1805
  } finally {
1766
1806
  await closeMppSession(mpp);
@@ -1769,45 +1809,39 @@ async function handleMppRequest(opts) {
1769
1809
  }
1770
1810
  }
1771
1811
  function appendInferenceHistory(opts) {
1772
- const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, lastDataLine, durationMs } = opts;
1773
- try {
1774
- const parsed = JSON.parse(lastDataLine);
1775
- const usage = parsed.usage;
1776
- const model = parsed.model ?? "";
1777
- appendHistory(historyPath, {
1778
- t: Date.now(),
1779
- ok: true,
1780
- kind: "x402_inference",
1781
- net: paymentNetwork,
1782
- from: walletAddress,
1783
- to: paymentTo,
1784
- tx,
1785
- amount,
1786
- token: "USDC",
1787
- provider: allModels.find((entry) => entry.id === model || `${entry.provider}/${entry.id}` === model)?.provider,
1788
- model,
1789
- inputTokens: usage?.prompt_tokens ?? 0,
1790
- outputTokens: usage?.completion_tokens ?? 0,
1791
- reasoningTokens: usage?.completion_tokens_details?.reasoning_tokens,
1792
- cacheRead: usage?.prompt_tokens_details?.cached_tokens,
1793
- cacheWrite: usage?.prompt_tokens_details?.cache_creation_input_tokens,
1794
- thinking: thinkingMode,
1795
- ms: durationMs
1796
- });
1797
- } catch {
1798
- appendHistory(historyPath, {
1799
- t: Date.now(),
1800
- ok: true,
1801
- kind: "x402_inference",
1802
- net: paymentNetwork,
1803
- from: walletAddress,
1804
- to: paymentTo,
1805
- tx,
1806
- amount,
1807
- token: "USDC",
1808
- ms: durationMs
1809
- });
1810
- }
1812
+ const { historyPath, allModels, walletAddress, paymentNetwork, paymentTo, tx, amount, thinkingMode, usage, durationMs } = opts;
1813
+ if (usage) appendHistory(historyPath, {
1814
+ t: Date.now(),
1815
+ ok: true,
1816
+ kind: "x402_inference",
1817
+ net: paymentNetwork,
1818
+ from: walletAddress,
1819
+ to: paymentTo,
1820
+ tx,
1821
+ amount,
1822
+ token: "USDC",
1823
+ provider: allModels.find((entry) => entry.id === usage.model || `${entry.provider}/${entry.id}` === usage.model)?.provider,
1824
+ model: usage.model,
1825
+ inputTokens: usage.inputTokens,
1826
+ outputTokens: usage.outputTokens,
1827
+ reasoningTokens: usage.reasoningTokens,
1828
+ cacheRead: usage.cacheRead,
1829
+ cacheWrite: usage.cacheWrite,
1830
+ thinking: thinkingMode,
1831
+ ms: durationMs
1832
+ });
1833
+ else appendHistory(historyPath, {
1834
+ t: Date.now(),
1835
+ ok: true,
1836
+ kind: "x402_inference",
1837
+ net: paymentNetwork,
1838
+ from: walletAddress,
1839
+ to: paymentTo,
1840
+ tx,
1841
+ amount,
1842
+ token: "USDC",
1843
+ ms: durationMs
1844
+ });
1811
1845
  }
1812
1846
  //#endregion
1813
1847
  //#region src/openclaw/plugin.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "x402-proxy",
3
- "version": "0.10.2",
3
+ "version": "0.10.3",
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,