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 +23 -1
- package/dist/bin/cli.js +130 -47
- package/dist/index.js +17 -9
- package/dist/openclaw/plugin.js +126 -40
- package/dist/status-DZlJ4pS7.js +3 -0
- package/dist/{status-Dc1dB0ZG.js → status-DihAcUSC.js} +1 -1
- package/dist/{wallet-LUl2l8WM.js → wallet-B0S-rma9.js} +12 -6
- package/dist/wallet-DBrVZJqe.js +3 -0
- package/package.json +1 -1
- package/dist/status-wknbavnl.js +0 -3
- package/dist/wallet-C9UlV7qi.js +0 -3
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.
|
|
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-
|
|
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-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
res.write(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 = "
|
|
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:
|
|
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:
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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`;
|
package/dist/openclaw/plugin.js
CHANGED
|
@@ -87,11 +87,13 @@ async function createMppProxyHandler(opts) {
|
|
|
87
87
|
});
|
|
88
88
|
const payerAddress = account.address;
|
|
89
89
|
function injectPayerHeader(init) {
|
|
90
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
res.write(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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",
|
|
@@ -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-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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`;
|
package/package.json
CHANGED
package/dist/status-wknbavnl.js
DELETED
package/dist/wallet-C9UlV7qi.js
DELETED