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 +10 -0
- package/dist/bin/cli.js +174 -140
- package/dist/openclaw/plugin.js +170 -136
- package/package.json +1 -1
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 =
|
|
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
|
|
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 &&
|
|
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
|
|
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 =
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
548
|
-
|
|
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
|
|
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]")
|
|
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
|
|
590
|
-
|
|
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
|
-
|
|
617
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
2362
|
+
versionInfo: { currentVersion: "0.10.3" },
|
|
2329
2363
|
scanner: { caseStyle: "allow-kebab-for-camel" }
|
|
2330
2364
|
});
|
|
2331
2365
|
//#endregion
|
package/dist/openclaw/plugin.js
CHANGED
|
@@ -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.
|
|
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 =
|
|
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
|
|
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 &&
|
|
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
|
|
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 =
|
|
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
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
-
|
|
1616
|
-
|
|
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
|
|
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]")
|
|
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
|
|
1658
|
-
|
|
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
|
-
|
|
1685
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
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