@blockrun/llm 3.24.8 → 3.24.9
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/dist/index.cjs +235 -97
- package/dist/index.d.cts +62 -11
- package/dist/index.d.ts +62 -11
- package/dist/index.js +235 -97
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -554,13 +554,44 @@ function readSettlement(response, quotedUsd, scheme) {
|
|
|
554
554
|
};
|
|
555
555
|
return { costUsd, settlement };
|
|
556
556
|
}
|
|
557
|
+
function settlementSkipReason(response) {
|
|
558
|
+
const headers = response.headers;
|
|
559
|
+
if (typeof headers?.get !== "function") return void 0;
|
|
560
|
+
const reason = [headers.get("X-Settlement-Skipped"), headers.get("X-Payment-Waived")].map((value) => value?.trim()).find((value) => !!value && !NOT_A_SKIP.test(value));
|
|
561
|
+
if (reason === void 0) return void 0;
|
|
562
|
+
const evidence = settlementEvidence(response);
|
|
563
|
+
if (evidence !== void 0) {
|
|
564
|
+
console.error(
|
|
565
|
+
`[@blockrun/llm] settlement: the gateway named a skipped settlement (${JSON.stringify(reason)}) on a response that also carries ${evidence}; booked as charged`
|
|
566
|
+
);
|
|
567
|
+
return void 0;
|
|
568
|
+
}
|
|
569
|
+
return reason;
|
|
570
|
+
}
|
|
571
|
+
var NOT_A_SKIP = /^(false|0|no|none)$/i;
|
|
572
|
+
function settlementEvidence(response) {
|
|
573
|
+
const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
574
|
+
const receipt = header ? decode(header) : void 0;
|
|
575
|
+
if (receipt && receipt.success !== false) {
|
|
576
|
+
if (typeof receipt.transaction === "string" && receipt.transaction) return "a settlement receipt with a transaction";
|
|
577
|
+
const extra = receipt.extra;
|
|
578
|
+
const charged = receiptChargedUsd({
|
|
579
|
+
amount: receipt.amount,
|
|
580
|
+
extra: extra && typeof extra === "object" ? extra : void 0
|
|
581
|
+
});
|
|
582
|
+
if (charged !== void 0 && charged > 0) return "a settlement receipt with a charge";
|
|
583
|
+
}
|
|
584
|
+
if (response.headers.get("X-Payment-Receipt")?.trim()) return "an X-Payment-Receipt transaction";
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
557
587
|
function settlementSkipped(response) {
|
|
558
|
-
return response
|
|
588
|
+
return settlementSkipReason(response) !== void 0;
|
|
559
589
|
}
|
|
560
590
|
function withCost(body, response, bookedUsd, scheme) {
|
|
561
|
-
|
|
591
|
+
const skipped = settlementSkipReason(response);
|
|
592
|
+
if (skipped !== void 0) {
|
|
562
593
|
body.costUsd = 0;
|
|
563
|
-
body.settlement = { ...readSettlement(response, bookedUsd, scheme).settlement, skipped
|
|
594
|
+
body.settlement = { ...readSettlement(response, bookedUsd, scheme).settlement, skipped };
|
|
564
595
|
return body;
|
|
565
596
|
}
|
|
566
597
|
body.costUsd = bookedUsd;
|
|
@@ -5089,7 +5120,7 @@ function getCostSummary() {
|
|
|
5089
5120
|
}
|
|
5090
5121
|
|
|
5091
5122
|
// src/version.ts
|
|
5092
|
-
var SDK_VERSION = "3.24.
|
|
5123
|
+
var SDK_VERSION = "3.24.9";
|
|
5093
5124
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
5094
5125
|
|
|
5095
5126
|
// src/client.ts
|
|
@@ -5685,9 +5716,9 @@ var LLMClient = class _LLMClient {
|
|
|
5685
5716
|
return this.bookPaid(url, body, details, retryResponse);
|
|
5686
5717
|
}
|
|
5687
5718
|
/**
|
|
5688
|
-
* Book a served, paid chat call and attach its cost. A response
|
|
5689
|
-
*
|
|
5690
|
-
*
|
|
5719
|
+
* Book a served, paid chat call and attach its cost. A response whose
|
|
5720
|
+
* settlement the gateway skipped ({@link settlementSkipped}: e.g.
|
|
5721
|
+
* `X-Settlement-Skipped: free-fallback`) moved nothing on chain, so it is
|
|
5691
5722
|
* not added to `getSpending()` or the cost log, and reports `costUsd: 0`.
|
|
5692
5723
|
*/
|
|
5693
5724
|
async bookPaid(url, body, details, response) {
|
|
@@ -5699,6 +5730,21 @@ var LLMClient = class _LLMClient {
|
|
|
5699
5730
|
}
|
|
5700
5731
|
return withCost(await this.parseChatResponse(response), response, costUsd, "exact");
|
|
5701
5732
|
}
|
|
5733
|
+
/**
|
|
5734
|
+
* Book a served, paid raw (non-chat) call: the signed amount goes to
|
|
5735
|
+
* `getSpending()` and the cost log, unless the gateway skipped settlement
|
|
5736
|
+
* ({@link settlementSkipped}: e.g. `X-Settlement-Skipped: empty-result` on an
|
|
5737
|
+
* empty prediction-market search, `unexecuted` on a JSON-RPC call that never
|
|
5738
|
+
* ran, or `X-Payment-Waived`), in which case nothing moved and nothing is
|
|
5739
|
+
* booked. `X-Payment-Settled: false` alone is still booked: it is owed.
|
|
5740
|
+
*/
|
|
5741
|
+
bookPaidRaw(url, details, response, body) {
|
|
5742
|
+
if (settlementSkipped(response)) return;
|
|
5743
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
5744
|
+
this.sessionCalls += 1;
|
|
5745
|
+
this.sessionTotalUsd += costUsd;
|
|
5746
|
+
this.recordCost(url, costUsd, body ? { body, network: details.network } : { network: details.network });
|
|
5747
|
+
}
|
|
5702
5748
|
/**
|
|
5703
5749
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
5704
5750
|
* Extracted to share logic between streaming and non-streaming flows.
|
|
@@ -5799,8 +5845,10 @@ var LLMClient = class _LLMClient {
|
|
|
5799
5845
|
throw withDisposition(err, "paid-or-in-doubt");
|
|
5800
5846
|
}
|
|
5801
5847
|
if (preAuthResp.ok) {
|
|
5802
|
-
|
|
5803
|
-
|
|
5848
|
+
if (!settlementSkipped(preAuthResp)) {
|
|
5849
|
+
this.sessionCalls += 1;
|
|
5850
|
+
this.sessionTotalUsd += signed.costUsd;
|
|
5851
|
+
}
|
|
5804
5852
|
return preAuthResp;
|
|
5805
5853
|
}
|
|
5806
5854
|
this.preAuthCache.delete(cacheKey2);
|
|
@@ -5865,8 +5913,10 @@ var LLMClient = class _LLMClient {
|
|
|
5865
5913
|
}
|
|
5866
5914
|
throw new APIError(`API error after payment: ${streamResp.status}`, streamResp.status, sanitizeErrorResponse(errorBody));
|
|
5867
5915
|
}
|
|
5868
|
-
|
|
5869
|
-
|
|
5916
|
+
if (!settlementSkipped(streamResp)) {
|
|
5917
|
+
this.sessionCalls += 1;
|
|
5918
|
+
this.sessionTotalUsd += costUsd;
|
|
5919
|
+
}
|
|
5870
5920
|
return streamResp;
|
|
5871
5921
|
}
|
|
5872
5922
|
/**
|
|
@@ -5990,10 +6040,7 @@ var LLMClient = class _LLMClient {
|
|
|
5990
6040
|
}
|
|
5991
6041
|
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
5992
6042
|
}
|
|
5993
|
-
|
|
5994
|
-
this.sessionCalls += 1;
|
|
5995
|
-
this.sessionTotalUsd += costUsd2;
|
|
5996
|
-
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
6043
|
+
this.bookPaidRaw(url, details, retryResp2, body);
|
|
5997
6044
|
return retryResp2.json();
|
|
5998
6045
|
}
|
|
5999
6046
|
}
|
|
@@ -6013,10 +6060,7 @@ var LLMClient = class _LLMClient {
|
|
|
6013
6060
|
sanitizeErrorResponse(errorBody)
|
|
6014
6061
|
);
|
|
6015
6062
|
}
|
|
6016
|
-
|
|
6017
|
-
this.sessionCalls += 1;
|
|
6018
|
-
this.sessionTotalUsd += costUsd;
|
|
6019
|
-
this.recordCost(url, costUsd, { body, network: details.network });
|
|
6063
|
+
this.bookPaidRaw(url, details, retryResponse, body);
|
|
6020
6064
|
return retryResponse.json();
|
|
6021
6065
|
}
|
|
6022
6066
|
/**
|
|
@@ -6136,10 +6180,7 @@ var LLMClient = class _LLMClient {
|
|
|
6136
6180
|
}
|
|
6137
6181
|
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
6138
6182
|
}
|
|
6139
|
-
|
|
6140
|
-
this.sessionCalls += 1;
|
|
6141
|
-
this.sessionTotalUsd += costUsd2;
|
|
6142
|
-
this.recordCost(url, costUsd2, { network: details.network });
|
|
6183
|
+
this.bookPaidRaw(url, details, retryResp2);
|
|
6143
6184
|
return retryResp2.json();
|
|
6144
6185
|
}
|
|
6145
6186
|
}
|
|
@@ -6159,10 +6200,7 @@ var LLMClient = class _LLMClient {
|
|
|
6159
6200
|
sanitizeErrorResponse(errorBody)
|
|
6160
6201
|
);
|
|
6161
6202
|
}
|
|
6162
|
-
|
|
6163
|
-
this.sessionCalls += 1;
|
|
6164
|
-
this.sessionTotalUsd += costUsd;
|
|
6165
|
-
this.recordCost(url, costUsd, { network: details.network });
|
|
6203
|
+
this.bookPaidRaw(url, details, retryResponse);
|
|
6166
6204
|
return retryResponse.json();
|
|
6167
6205
|
}
|
|
6168
6206
|
/**
|
|
@@ -7175,8 +7213,10 @@ var MusicClient = class {
|
|
|
7175
7213
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7176
7214
|
}
|
|
7177
7215
|
const data = await retryResponse.json();
|
|
7178
|
-
|
|
7179
|
-
|
|
7216
|
+
if (!settlementSkipped(retryResponse)) {
|
|
7217
|
+
this.sessionCalls++;
|
|
7218
|
+
this.sessionTotalUsd += 0.1575;
|
|
7219
|
+
}
|
|
7180
7220
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7181
7221
|
if (txHash) data.txHash = txHash;
|
|
7182
7222
|
return data;
|
|
@@ -7388,9 +7428,11 @@ var SpeechClient = class {
|
|
|
7388
7428
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7389
7429
|
}
|
|
7390
7430
|
const data = await retryResponse.json();
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7431
|
+
if (!settlementSkipped(retryResponse)) {
|
|
7432
|
+
this.sessionCalls++;
|
|
7433
|
+
const paidUsd = Number(details.amount) / 1e6;
|
|
7434
|
+
if (Number.isFinite(paidUsd)) this.sessionTotalUsd += paidUsd;
|
|
7435
|
+
}
|
|
7394
7436
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7395
7437
|
if (txHash) data.txHash = txHash;
|
|
7396
7438
|
return data;
|
|
@@ -7687,8 +7729,10 @@ var VideoClient = class {
|
|
|
7687
7729
|
}
|
|
7688
7730
|
if (lastStatus === "completed") {
|
|
7689
7731
|
const data = pollData;
|
|
7690
|
-
|
|
7691
|
-
|
|
7732
|
+
if (!settlementSkipped(pollResp)) {
|
|
7733
|
+
this.sessionCalls++;
|
|
7734
|
+
this.sessionTotalUsd += Number(details.amount) / 1e6;
|
|
7735
|
+
}
|
|
7692
7736
|
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
7693
7737
|
if (txHash) data.txHash = txHash;
|
|
7694
7738
|
return data;
|
|
@@ -7983,8 +8027,10 @@ var VoiceClient = class {
|
|
|
7983
8027
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7984
8028
|
}
|
|
7985
8029
|
const data = await retryResponse.json();
|
|
7986
|
-
|
|
7987
|
-
|
|
8030
|
+
if (!settlementSkipped(retryResponse)) {
|
|
8031
|
+
this.sessionCalls++;
|
|
8032
|
+
this.sessionTotalUsd += CALL_PRICE_USD;
|
|
8033
|
+
}
|
|
7988
8034
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7989
8035
|
if (txHash) data.txHash = txHash;
|
|
7990
8036
|
return data;
|
|
@@ -8204,9 +8250,10 @@ var PhoneClient = class {
|
|
|
8204
8250
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
8205
8251
|
}
|
|
8206
8252
|
const data = await this.unwrap(retry, true);
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8253
|
+
if (!settlementSkipped(retry)) {
|
|
8254
|
+
this.sessionCalls += 1;
|
|
8255
|
+
this.sessionTotalUsd += Number.parseFloat(details.amount) / 1e6;
|
|
8256
|
+
}
|
|
8210
8257
|
const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
|
|
8211
8258
|
if (txHash && data && typeof data === "object" && !("txHash" in data)) {
|
|
8212
8259
|
data.txHash = txHash;
|
|
@@ -8371,9 +8418,10 @@ var PortraitClient = class {
|
|
|
8371
8418
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
8372
8419
|
}
|
|
8373
8420
|
const data = await this.unwrap(retry, true);
|
|
8374
|
-
|
|
8375
|
-
|
|
8376
|
-
|
|
8421
|
+
if (!settlementSkipped(retry)) {
|
|
8422
|
+
this.sessionCalls += 1;
|
|
8423
|
+
this.sessionTotalUsd += Number.parseFloat(details.amount) / 1e6;
|
|
8424
|
+
}
|
|
8377
8425
|
if (data && typeof data === "object" && !("txHash" in data)) {
|
|
8378
8426
|
const settlement = data.settlement;
|
|
8379
8427
|
if (settlement?.tx_hash) {
|
|
@@ -9190,9 +9238,11 @@ var RpcClient = class {
|
|
|
9190
9238
|
}
|
|
9191
9239
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
9192
9240
|
}
|
|
9193
|
-
|
|
9194
|
-
|
|
9195
|
-
|
|
9241
|
+
if (!settlementSkipped(retryResponse)) {
|
|
9242
|
+
this.sessionCalls++;
|
|
9243
|
+
const paidUsd = Number(details.amount) / 1e6;
|
|
9244
|
+
if (Number.isFinite(paidUsd)) this.sessionTotalUsd += paidUsd;
|
|
9245
|
+
}
|
|
9196
9246
|
return { data: await retryResponse.json(), headers: retryResponse.headers };
|
|
9197
9247
|
}
|
|
9198
9248
|
async fetchWithTimeout(url, options) {
|
|
@@ -9339,7 +9389,7 @@ var BlockrunClient = class {
|
|
|
9339
9389
|
if (resp402.status !== 402) {
|
|
9340
9390
|
await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
|
|
9341
9391
|
}
|
|
9342
|
-
const paymentPayload = await this.signFrom402(resp402, submitUrl, {
|
|
9392
|
+
const { paymentPayload, costUsd } = await this.signFrom402(resp402, submitUrl, {
|
|
9343
9393
|
description: "BlockRun async job",
|
|
9344
9394
|
maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
|
|
9345
9395
|
});
|
|
@@ -9358,7 +9408,7 @@ var BlockrunClient = class {
|
|
|
9358
9408
|
await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
|
|
9359
9409
|
}
|
|
9360
9410
|
if (submitResp.status === 200) {
|
|
9361
|
-
this.recordSpending();
|
|
9411
|
+
this.recordSpending(submitResp, costUsd);
|
|
9362
9412
|
return submitResp.json();
|
|
9363
9413
|
}
|
|
9364
9414
|
const submitData = await submitResp.json();
|
|
@@ -9395,7 +9445,7 @@ var BlockrunClient = class {
|
|
|
9395
9445
|
);
|
|
9396
9446
|
}
|
|
9397
9447
|
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
9398
|
-
this.recordSpending();
|
|
9448
|
+
this.recordSpending(pollResp, costUsd);
|
|
9399
9449
|
return pollData;
|
|
9400
9450
|
}
|
|
9401
9451
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
@@ -9436,7 +9486,7 @@ var BlockrunClient = class {
|
|
|
9436
9486
|
if (resp402.status === 200) {
|
|
9437
9487
|
streamResp = resp402;
|
|
9438
9488
|
} else if (resp402.status === 402) {
|
|
9439
|
-
const paymentPayload = await this.signFrom402(resp402, url, {
|
|
9489
|
+
const { paymentPayload, costUsd } = await this.signFrom402(resp402, url, {
|
|
9440
9490
|
description: "BlockRun stream"
|
|
9441
9491
|
});
|
|
9442
9492
|
streamResp = await this.fetchWithTimeout(url, {
|
|
@@ -9455,7 +9505,7 @@ var BlockrunClient = class {
|
|
|
9455
9505
|
if (!streamResp.ok) {
|
|
9456
9506
|
await this.throwApiError(streamResp, `stream failed after payment (${url})`);
|
|
9457
9507
|
}
|
|
9458
|
-
this.recordSpending();
|
|
9508
|
+
this.recordSpending(streamResp, costUsd);
|
|
9459
9509
|
} else {
|
|
9460
9510
|
await this.throwApiError(resp402, `stream failed (${url})`);
|
|
9461
9511
|
return;
|
|
@@ -9511,7 +9561,7 @@ var BlockrunClient = class {
|
|
|
9511
9561
|
return response.json();
|
|
9512
9562
|
}
|
|
9513
9563
|
async handlePaymentAndRetry(url, method, body, response) {
|
|
9514
|
-
const paymentPayload = await this.signFrom402(response, url, {
|
|
9564
|
+
const { paymentPayload, costUsd } = await this.signFrom402(response, url, {
|
|
9515
9565
|
description: "BlockRun"
|
|
9516
9566
|
});
|
|
9517
9567
|
const retryInit = {
|
|
@@ -9534,13 +9584,14 @@ var BlockrunClient = class {
|
|
|
9534
9584
|
if (!retry.ok) {
|
|
9535
9585
|
await this.throwApiError(retry, `${method} ${url} failed after payment`);
|
|
9536
9586
|
}
|
|
9537
|
-
this.recordSpending();
|
|
9587
|
+
this.recordSpending(retry, costUsd);
|
|
9538
9588
|
return retry.json();
|
|
9539
9589
|
}
|
|
9540
9590
|
/**
|
|
9541
9591
|
* Read a 402 response's payment requirements (header or body), then sign and
|
|
9542
|
-
* return the base64 PAYMENT-SIGNATURE payload
|
|
9543
|
-
*
|
|
9592
|
+
* return the base64 PAYMENT-SIGNATURE payload with what it pays, in USD.
|
|
9593
|
+
* The cost stays with the call that signed it (booked by `recordSpending`):
|
|
9594
|
+
* concurrent calls on one client never share it.
|
|
9544
9595
|
*/
|
|
9545
9596
|
async signFrom402(response, url, opts) {
|
|
9546
9597
|
let paymentHeader = response.headers.get("payment-required");
|
|
@@ -9558,8 +9609,8 @@ var BlockrunClient = class {
|
|
|
9558
9609
|
}
|
|
9559
9610
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
9560
9611
|
const details = extractPaymentDetails(paymentRequired);
|
|
9561
|
-
|
|
9562
|
-
|
|
9612
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
9613
|
+
const paymentPayload = await createPaymentPayload(
|
|
9563
9614
|
this.privateKey,
|
|
9564
9615
|
this.account.address,
|
|
9565
9616
|
details.recipient,
|
|
@@ -9576,14 +9627,20 @@ var BlockrunClient = class {
|
|
|
9576
9627
|
asset: details.asset
|
|
9577
9628
|
}
|
|
9578
9629
|
);
|
|
9630
|
+
return { paymentPayload, costUsd };
|
|
9579
9631
|
}
|
|
9580
|
-
/**
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9632
|
+
/**
|
|
9633
|
+
* Book `costUsd`, what the call that `response` answered signed. A response
|
|
9634
|
+
* whose settlement the gateway skipped ({@link settlementSkipped}:
|
|
9635
|
+
* `X-Settlement-Skipped: <reason>` or `X-Payment-Waived`) moved nothing, so
|
|
9636
|
+
* nothing is booked. `X-Payment-Settled: false` alone is still booked: the
|
|
9637
|
+
* payment is owed.
|
|
9638
|
+
*/
|
|
9639
|
+
recordSpending(response, costUsd) {
|
|
9640
|
+
if (settlementSkipped(response)) return;
|
|
9641
|
+
if (costUsd > 0) {
|
|
9584
9642
|
this.sessionCalls += 1;
|
|
9585
|
-
this.sessionTotalUsd +=
|
|
9586
|
-
this.pendingCostUsd = 0;
|
|
9643
|
+
this.sessionTotalUsd += costUsd;
|
|
9587
9644
|
}
|
|
9588
9645
|
}
|
|
9589
9646
|
async throwApiError(resp, prefix) {
|
|
@@ -11802,6 +11859,8 @@ var SolanaBatchPayer = class {
|
|
|
11802
11859
|
wallet.refusedFor = outcome.errorReason;
|
|
11803
11860
|
}
|
|
11804
11861
|
}
|
|
11862
|
+
} else if (built.key && outcome.reason === "payment_required" && outcome.refusedForMs === void 0) {
|
|
11863
|
+
await this.recheckRefusedChannel(wallet, built.key, built.reclaimAnchor);
|
|
11805
11864
|
}
|
|
11806
11865
|
sent = void 0;
|
|
11807
11866
|
release();
|
|
@@ -12069,6 +12128,13 @@ var SolanaBatchPayer = class {
|
|
|
12069
12128
|
}
|
|
12070
12129
|
/** Build a new batch payment for a 402: the payload, its headers and its kind. */
|
|
12071
12130
|
async buildPayment(wallet, paymentRequired) {
|
|
12131
|
+
const acceptKey = channelKeyOf(trustedBatchAccept(paymentRequired, this.init.options.operators));
|
|
12132
|
+
let reclaimAnchor;
|
|
12133
|
+
try {
|
|
12134
|
+
const stored = acceptKey ? await wallet.book.base.get(acceptKey) : void 0;
|
|
12135
|
+
reclaimAnchor = stored ? savedReclaimAnchor(stored) : void 0;
|
|
12136
|
+
} catch {
|
|
12137
|
+
}
|
|
12072
12138
|
const { http, payload } = await this.createPayload(wallet, paymentRequired);
|
|
12073
12139
|
const scope = payScope.getStore();
|
|
12074
12140
|
if (scope?.afterError) {
|
|
@@ -12090,7 +12156,16 @@ var SolanaBatchPayer = class {
|
|
|
12090
12156
|
const record = known?.record;
|
|
12091
12157
|
kind = record && (record.pending === void 0 || record.hasConfirmedState) ? "top-up" : "open";
|
|
12092
12158
|
}
|
|
12093
|
-
return {
|
|
12159
|
+
return {
|
|
12160
|
+
http,
|
|
12161
|
+
payload,
|
|
12162
|
+
headers,
|
|
12163
|
+
kind,
|
|
12164
|
+
key: known?.key,
|
|
12165
|
+
...reclaimAnchor !== void 0 && known?.key === acceptKey ? { reclaimAnchor } : {},
|
|
12166
|
+
sentAt: Date.now(),
|
|
12167
|
+
sends: 0
|
|
12168
|
+
};
|
|
12094
12169
|
}
|
|
12095
12170
|
/**
|
|
12096
12171
|
* Write a deposit's intent to the wallet's journal before it is sent
|
|
@@ -12324,6 +12399,84 @@ var SolanaBatchPayer = class {
|
|
|
12324
12399
|
}
|
|
12325
12400
|
}
|
|
12326
12401
|
}
|
|
12402
|
+
/**
|
|
12403
|
+
* Queue a re-read of a stored channel whose account is gone at finalized
|
|
12404
|
+
* and at confirmed commitment (closed, its account reclaimed), anchored at
|
|
12405
|
+
* the SDK's own `getBlockHeight` at confirmed. The anchor is saved in the
|
|
12406
|
+
* record (`reclaimAnchorHeight`), so a short-lived process finishes the
|
|
12407
|
+
* wait instead of starting it over; a failed read leaves resync to take it,
|
|
12408
|
+
* and a failed save only costs that. resync pays exact until finality is
|
|
12409
|
+
* LANDING_MARGIN_BLOCKS past the anchor, then drops the record if the
|
|
12410
|
+
* finalized chain still has no account (a lagging read finds the channel
|
|
12411
|
+
* and rewrites the record, without the anchor).
|
|
12412
|
+
*/
|
|
12413
|
+
async queueReclaimed(wallet, key, record, channelId, savedAnchor) {
|
|
12414
|
+
let anchorHeight = savedReclaimAnchor(record) ?? savedAnchor;
|
|
12415
|
+
if (anchorHeight === void 0) {
|
|
12416
|
+
try {
|
|
12417
|
+
anchorHeight = rpcInteger(
|
|
12418
|
+
await rpcRequest(this.init.rpcUrl, "getBlockHeight", [{ commitment: "confirmed" }], this.init.rpcHeaders),
|
|
12419
|
+
"getBlockHeight"
|
|
12420
|
+
);
|
|
12421
|
+
} catch {
|
|
12422
|
+
}
|
|
12423
|
+
}
|
|
12424
|
+
if (anchorHeight !== void 0 && savedReclaimAnchor(record) !== anchorHeight) {
|
|
12425
|
+
try {
|
|
12426
|
+
await wallet.book.set(key, { ...record, reclaimAnchorHeight: anchorHeight });
|
|
12427
|
+
} catch {
|
|
12428
|
+
}
|
|
12429
|
+
}
|
|
12430
|
+
addResyncTarget(wallet, {
|
|
12431
|
+
key,
|
|
12432
|
+
channelId,
|
|
12433
|
+
channelConfig: record.channelConfig,
|
|
12434
|
+
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12435
|
+
knownDeposit: parseAtomic(record.deposit),
|
|
12436
|
+
...anchorHeight !== void 0 ? { anchorHeight } : {},
|
|
12437
|
+
reason: "channel_unusable"
|
|
12438
|
+
});
|
|
12439
|
+
}
|
|
12440
|
+
/**
|
|
12441
|
+
* After the gateway refused an authorization with a plain 402 (no receipt,
|
|
12442
|
+
* not an admission refusal), check the channel it paid into. That is how
|
|
12443
|
+
* the gateway answers an authorization into a channel the facilitator
|
|
12444
|
+
* closed (its verify rejects it), and a channel with deposit to spare is
|
|
12445
|
+
* otherwise never read again: every call would sign another authorization
|
|
12446
|
+
* into it and pay exact. A channel the finalized chain shows closing,
|
|
12447
|
+
* closed or not this wallet's is re-read through resync (record dropped,
|
|
12448
|
+
* or kept and paid exact); one gone at every commitment is queued as
|
|
12449
|
+
* {@link queueReclaimed}; an open one of this wallet's, or one only at
|
|
12450
|
+
* confirmed (not finalized yet), is left as it is. A queued re-read is run
|
|
12451
|
+
* at once. Best effort: a failed read changes nothing, and the next
|
|
12452
|
+
* refusal checks again.
|
|
12453
|
+
*/
|
|
12454
|
+
async recheckRefusedChannel(wallet, key, savedAnchor) {
|
|
12455
|
+
try {
|
|
12456
|
+
const record = await wallet.book.get(key);
|
|
12457
|
+
if (!record || record.pending || typeof record.channelId !== "string" || wallet.resyncs.has(key)) return;
|
|
12458
|
+
const channelId = record.channelId;
|
|
12459
|
+
const channel = await readChannelAccount(this.init.rpcUrl, channelId, this.init.rpcHeaders);
|
|
12460
|
+
if (channel !== void 0) {
|
|
12461
|
+
if (channel.open && channel.payer === await this.init.address() && channel.authorizedSigner === record.channelConfig.payerAuthorizer && channel.mint === record.channelConfig.token) {
|
|
12462
|
+
return;
|
|
12463
|
+
}
|
|
12464
|
+
addResyncTarget(wallet, {
|
|
12465
|
+
key,
|
|
12466
|
+
channelId,
|
|
12467
|
+
channelConfig: record.channelConfig,
|
|
12468
|
+
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12469
|
+
knownDeposit: parseAtomic(record.deposit),
|
|
12470
|
+
reason: "channel_unusable"
|
|
12471
|
+
});
|
|
12472
|
+
} else {
|
|
12473
|
+
if (await readChannelAccount(this.init.rpcUrl, channelId, this.init.rpcHeaders, { commitment: "confirmed" }) !== void 0) return;
|
|
12474
|
+
await this.queueReclaimed(wallet, key, record, channelId, savedAnchor);
|
|
12475
|
+
}
|
|
12476
|
+
await this.resync(wallet);
|
|
12477
|
+
} catch {
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12327
12480
|
/**
|
|
12328
12481
|
* When this payment will top up the wallet's channel, read how much of its
|
|
12329
12482
|
* deposit is already settled on-chain (at `finalized` commitment, so never
|
|
@@ -12377,31 +12530,7 @@ var SolanaBatchPayer = class {
|
|
|
12377
12530
|
}
|
|
12378
12531
|
if (channel === void 0 && unfinalized) return;
|
|
12379
12532
|
if (channel === void 0) {
|
|
12380
|
-
|
|
12381
|
-
if (anchorHeight === void 0) {
|
|
12382
|
-
try {
|
|
12383
|
-
anchorHeight = rpcInteger(
|
|
12384
|
-
await rpcRequest(this.init.rpcUrl, "getBlockHeight", [{ commitment: "confirmed" }], this.init.rpcHeaders),
|
|
12385
|
-
"getBlockHeight"
|
|
12386
|
-
);
|
|
12387
|
-
} catch {
|
|
12388
|
-
}
|
|
12389
|
-
if (anchorHeight !== void 0) {
|
|
12390
|
-
try {
|
|
12391
|
-
await wallet.book.set(key, { ...record, reclaimAnchorHeight: anchorHeight });
|
|
12392
|
-
} catch {
|
|
12393
|
-
}
|
|
12394
|
-
}
|
|
12395
|
-
}
|
|
12396
|
-
addResyncTarget(wallet, {
|
|
12397
|
-
key,
|
|
12398
|
-
channelId: record.channelId,
|
|
12399
|
-
channelConfig: record.channelConfig,
|
|
12400
|
-
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12401
|
-
knownDeposit: deposit,
|
|
12402
|
-
...anchorHeight !== void 0 ? { anchorHeight } : {},
|
|
12403
|
-
reason: "channel_unusable"
|
|
12404
|
-
});
|
|
12533
|
+
await this.queueReclaimed(wallet, key, record, record.channelId);
|
|
12405
12534
|
throw new ChannelResyncRequiredError(record.channelId);
|
|
12406
12535
|
}
|
|
12407
12536
|
if (!channel.open || channel.payer !== await this.init.address() || channel.authorizedSigner !== record.channelConfig.payerAuthorizer || channel.mint !== record.channelConfig.token) {
|
|
@@ -13038,7 +13167,7 @@ var SolanaLLMClient = class {
|
|
|
13038
13167
|
body: requestBody
|
|
13039
13168
|
});
|
|
13040
13169
|
await this.assertPaid(paid);
|
|
13041
|
-
this.recordSettlement(costUsd);
|
|
13170
|
+
this.recordSettlement(costUsd, paid);
|
|
13042
13171
|
return paid;
|
|
13043
13172
|
} catch (error) {
|
|
13044
13173
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
@@ -13425,7 +13554,7 @@ var SolanaLLMClient = class {
|
|
|
13425
13554
|
closesAtStart
|
|
13426
13555
|
);
|
|
13427
13556
|
if (batch.kind === "paid") {
|
|
13428
|
-
this.recordSettlement(batch.chargedUsd);
|
|
13557
|
+
this.recordSettlement(batch.chargedUsd, batch.response);
|
|
13429
13558
|
const paid = await this.readPaidJson(batch.response);
|
|
13430
13559
|
return withCost(paid, batch.response, batch.chargedUsd, "batch");
|
|
13431
13560
|
}
|
|
@@ -13542,7 +13671,7 @@ var SolanaLLMClient = class {
|
|
|
13542
13671
|
};
|
|
13543
13672
|
const deliver = (data, response) => {
|
|
13544
13673
|
if (kind === "video") {
|
|
13545
|
-
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd);
|
|
13674
|
+
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd, response);
|
|
13546
13675
|
const txHash = response.headers.get("x-payment-receipt");
|
|
13547
13676
|
if (txHash) data.txHash = txHash;
|
|
13548
13677
|
}
|
|
@@ -13789,7 +13918,7 @@ var SolanaLLMClient = class {
|
|
|
13789
13918
|
throw withDisposition(error, "paid-or-in-doubt");
|
|
13790
13919
|
}
|
|
13791
13920
|
await this.assertPaid(retryResponse);
|
|
13792
|
-
|
|
13921
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13793
13922
|
const paid = await this.readPaidJson(retryResponse);
|
|
13794
13923
|
return withCost(paid, retryResponse, costUsd, "exact");
|
|
13795
13924
|
}
|
|
@@ -13849,11 +13978,11 @@ var SolanaLLMClient = class {
|
|
|
13849
13978
|
await this.assertPaid(retryResponse);
|
|
13850
13979
|
if (retryResponse.status === 202) {
|
|
13851
13980
|
const kind = pollKindFor(url);
|
|
13852
|
-
if (kind !== "video") this.recordSettlement(costUsd);
|
|
13981
|
+
if (kind !== "video") this.recordSettlement(costUsd, retryResponse);
|
|
13853
13982
|
const submitted = await retryResponse.json();
|
|
13854
13983
|
return this.followSolanaJob(submitted, media.pollBudgetMs ?? this.timeout, kind);
|
|
13855
13984
|
}
|
|
13856
|
-
this.recordSettlement(costUsd);
|
|
13985
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13857
13986
|
return retryResponse.json();
|
|
13858
13987
|
}
|
|
13859
13988
|
async getWithPaymentRaw(endpoint, params) {
|
|
@@ -13897,7 +14026,7 @@ var SolanaLLMClient = class {
|
|
|
13897
14026
|
}
|
|
13898
14027
|
});
|
|
13899
14028
|
await this.assertPaid(retryResponse);
|
|
13900
|
-
this.recordSettlement(costUsd);
|
|
14029
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13901
14030
|
return retryResponse.json();
|
|
13902
14031
|
}
|
|
13903
14032
|
/**
|
|
@@ -13935,8 +14064,17 @@ var SolanaLLMClient = class {
|
|
|
13935
14064
|
);
|
|
13936
14065
|
}
|
|
13937
14066
|
}
|
|
13938
|
-
/**
|
|
13939
|
-
|
|
14067
|
+
/**
|
|
14068
|
+
* Count one settled x402 payment against the session total.
|
|
14069
|
+
*
|
|
14070
|
+
* Takes the paid response so no booking site can forget the check: a
|
|
14071
|
+
* response whose settlement the gateway skipped ({@link settlementSkipped}:
|
|
14072
|
+
* `X-Settlement-Skipped: free-fallback | empty-result | unexecuted | …`, or
|
|
14073
|
+
* `X-Payment-Waived`) moved nothing on chain and books nothing.
|
|
14074
|
+
* `X-Payment-Settled: false` alone is still booked: the payment is owed.
|
|
14075
|
+
*/
|
|
14076
|
+
recordSettlement(costUsd, response) {
|
|
14077
|
+
if (settlementSkipped(response)) return;
|
|
13940
14078
|
this.sessionCalls += 1;
|
|
13941
14079
|
this.sessionTotalUsd += costUsd;
|
|
13942
14080
|
}
|