@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.js
CHANGED
|
@@ -423,13 +423,44 @@ function readSettlement(response, quotedUsd, scheme) {
|
|
|
423
423
|
};
|
|
424
424
|
return { costUsd, settlement };
|
|
425
425
|
}
|
|
426
|
+
function settlementSkipReason(response) {
|
|
427
|
+
const headers = response.headers;
|
|
428
|
+
if (typeof headers?.get !== "function") return void 0;
|
|
429
|
+
const reason = [headers.get("X-Settlement-Skipped"), headers.get("X-Payment-Waived")].map((value) => value?.trim()).find((value) => !!value && !NOT_A_SKIP.test(value));
|
|
430
|
+
if (reason === void 0) return void 0;
|
|
431
|
+
const evidence = settlementEvidence(response);
|
|
432
|
+
if (evidence !== void 0) {
|
|
433
|
+
console.error(
|
|
434
|
+
`[@blockrun/llm] settlement: the gateway named a skipped settlement (${JSON.stringify(reason)}) on a response that also carries ${evidence}; booked as charged`
|
|
435
|
+
);
|
|
436
|
+
return void 0;
|
|
437
|
+
}
|
|
438
|
+
return reason;
|
|
439
|
+
}
|
|
440
|
+
var NOT_A_SKIP = /^(false|0|no|none)$/i;
|
|
441
|
+
function settlementEvidence(response) {
|
|
442
|
+
const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
443
|
+
const receipt = header ? decode(header) : void 0;
|
|
444
|
+
if (receipt && receipt.success !== false) {
|
|
445
|
+
if (typeof receipt.transaction === "string" && receipt.transaction) return "a settlement receipt with a transaction";
|
|
446
|
+
const extra = receipt.extra;
|
|
447
|
+
const charged = receiptChargedUsd({
|
|
448
|
+
amount: receipt.amount,
|
|
449
|
+
extra: extra && typeof extra === "object" ? extra : void 0
|
|
450
|
+
});
|
|
451
|
+
if (charged !== void 0 && charged > 0) return "a settlement receipt with a charge";
|
|
452
|
+
}
|
|
453
|
+
if (response.headers.get("X-Payment-Receipt")?.trim()) return "an X-Payment-Receipt transaction";
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
426
456
|
function settlementSkipped(response) {
|
|
427
|
-
return response
|
|
457
|
+
return settlementSkipReason(response) !== void 0;
|
|
428
458
|
}
|
|
429
459
|
function withCost(body, response, bookedUsd, scheme) {
|
|
430
|
-
|
|
460
|
+
const skipped = settlementSkipReason(response);
|
|
461
|
+
if (skipped !== void 0) {
|
|
431
462
|
body.costUsd = 0;
|
|
432
|
-
body.settlement = { ...readSettlement(response, bookedUsd, scheme).settlement, skipped
|
|
463
|
+
body.settlement = { ...readSettlement(response, bookedUsd, scheme).settlement, skipped };
|
|
433
464
|
return body;
|
|
434
465
|
}
|
|
435
466
|
body.costUsd = bookedUsd;
|
|
@@ -4958,7 +4989,7 @@ function getCostSummary() {
|
|
|
4958
4989
|
}
|
|
4959
4990
|
|
|
4960
4991
|
// src/version.ts
|
|
4961
|
-
var SDK_VERSION = "3.24.
|
|
4992
|
+
var SDK_VERSION = "3.24.9";
|
|
4962
4993
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4963
4994
|
|
|
4964
4995
|
// src/client.ts
|
|
@@ -5554,9 +5585,9 @@ var LLMClient = class _LLMClient {
|
|
|
5554
5585
|
return this.bookPaid(url, body, details, retryResponse);
|
|
5555
5586
|
}
|
|
5556
5587
|
/**
|
|
5557
|
-
* Book a served, paid chat call and attach its cost. A response
|
|
5558
|
-
*
|
|
5559
|
-
*
|
|
5588
|
+
* Book a served, paid chat call and attach its cost. A response whose
|
|
5589
|
+
* settlement the gateway skipped ({@link settlementSkipped}: e.g.
|
|
5590
|
+
* `X-Settlement-Skipped: free-fallback`) moved nothing on chain, so it is
|
|
5560
5591
|
* not added to `getSpending()` or the cost log, and reports `costUsd: 0`.
|
|
5561
5592
|
*/
|
|
5562
5593
|
async bookPaid(url, body, details, response) {
|
|
@@ -5568,6 +5599,21 @@ var LLMClient = class _LLMClient {
|
|
|
5568
5599
|
}
|
|
5569
5600
|
return withCost(await this.parseChatResponse(response), response, costUsd, "exact");
|
|
5570
5601
|
}
|
|
5602
|
+
/**
|
|
5603
|
+
* Book a served, paid raw (non-chat) call: the signed amount goes to
|
|
5604
|
+
* `getSpending()` and the cost log, unless the gateway skipped settlement
|
|
5605
|
+
* ({@link settlementSkipped}: e.g. `X-Settlement-Skipped: empty-result` on an
|
|
5606
|
+
* empty prediction-market search, `unexecuted` on a JSON-RPC call that never
|
|
5607
|
+
* ran, or `X-Payment-Waived`), in which case nothing moved and nothing is
|
|
5608
|
+
* booked. `X-Payment-Settled: false` alone is still booked: it is owed.
|
|
5609
|
+
*/
|
|
5610
|
+
bookPaidRaw(url, details, response, body) {
|
|
5611
|
+
if (settlementSkipped(response)) return;
|
|
5612
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
5613
|
+
this.sessionCalls += 1;
|
|
5614
|
+
this.sessionTotalUsd += costUsd;
|
|
5615
|
+
this.recordCost(url, costUsd, body ? { body, network: details.network } : { network: details.network });
|
|
5616
|
+
}
|
|
5571
5617
|
/**
|
|
5572
5618
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
5573
5619
|
* Extracted to share logic between streaming and non-streaming flows.
|
|
@@ -5668,8 +5714,10 @@ var LLMClient = class _LLMClient {
|
|
|
5668
5714
|
throw withDisposition(err, "paid-or-in-doubt");
|
|
5669
5715
|
}
|
|
5670
5716
|
if (preAuthResp.ok) {
|
|
5671
|
-
|
|
5672
|
-
|
|
5717
|
+
if (!settlementSkipped(preAuthResp)) {
|
|
5718
|
+
this.sessionCalls += 1;
|
|
5719
|
+
this.sessionTotalUsd += signed.costUsd;
|
|
5720
|
+
}
|
|
5673
5721
|
return preAuthResp;
|
|
5674
5722
|
}
|
|
5675
5723
|
this.preAuthCache.delete(cacheKey2);
|
|
@@ -5734,8 +5782,10 @@ var LLMClient = class _LLMClient {
|
|
|
5734
5782
|
}
|
|
5735
5783
|
throw new APIError(`API error after payment: ${streamResp.status}`, streamResp.status, sanitizeErrorResponse(errorBody));
|
|
5736
5784
|
}
|
|
5737
|
-
|
|
5738
|
-
|
|
5785
|
+
if (!settlementSkipped(streamResp)) {
|
|
5786
|
+
this.sessionCalls += 1;
|
|
5787
|
+
this.sessionTotalUsd += costUsd;
|
|
5788
|
+
}
|
|
5739
5789
|
return streamResp;
|
|
5740
5790
|
}
|
|
5741
5791
|
/**
|
|
@@ -5859,10 +5909,7 @@ var LLMClient = class _LLMClient {
|
|
|
5859
5909
|
}
|
|
5860
5910
|
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
5861
5911
|
}
|
|
5862
|
-
|
|
5863
|
-
this.sessionCalls += 1;
|
|
5864
|
-
this.sessionTotalUsd += costUsd2;
|
|
5865
|
-
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
5912
|
+
this.bookPaidRaw(url, details, retryResp2, body);
|
|
5866
5913
|
return retryResp2.json();
|
|
5867
5914
|
}
|
|
5868
5915
|
}
|
|
@@ -5882,10 +5929,7 @@ var LLMClient = class _LLMClient {
|
|
|
5882
5929
|
sanitizeErrorResponse(errorBody)
|
|
5883
5930
|
);
|
|
5884
5931
|
}
|
|
5885
|
-
|
|
5886
|
-
this.sessionCalls += 1;
|
|
5887
|
-
this.sessionTotalUsd += costUsd;
|
|
5888
|
-
this.recordCost(url, costUsd, { body, network: details.network });
|
|
5932
|
+
this.bookPaidRaw(url, details, retryResponse, body);
|
|
5889
5933
|
return retryResponse.json();
|
|
5890
5934
|
}
|
|
5891
5935
|
/**
|
|
@@ -6005,10 +6049,7 @@ var LLMClient = class _LLMClient {
|
|
|
6005
6049
|
}
|
|
6006
6050
|
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
6007
6051
|
}
|
|
6008
|
-
|
|
6009
|
-
this.sessionCalls += 1;
|
|
6010
|
-
this.sessionTotalUsd += costUsd2;
|
|
6011
|
-
this.recordCost(url, costUsd2, { network: details.network });
|
|
6052
|
+
this.bookPaidRaw(url, details, retryResp2);
|
|
6012
6053
|
return retryResp2.json();
|
|
6013
6054
|
}
|
|
6014
6055
|
}
|
|
@@ -6028,10 +6069,7 @@ var LLMClient = class _LLMClient {
|
|
|
6028
6069
|
sanitizeErrorResponse(errorBody)
|
|
6029
6070
|
);
|
|
6030
6071
|
}
|
|
6031
|
-
|
|
6032
|
-
this.sessionCalls += 1;
|
|
6033
|
-
this.sessionTotalUsd += costUsd;
|
|
6034
|
-
this.recordCost(url, costUsd, { network: details.network });
|
|
6072
|
+
this.bookPaidRaw(url, details, retryResponse);
|
|
6035
6073
|
return retryResponse.json();
|
|
6036
6074
|
}
|
|
6037
6075
|
/**
|
|
@@ -7044,8 +7082,10 @@ var MusicClient = class {
|
|
|
7044
7082
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7045
7083
|
}
|
|
7046
7084
|
const data = await retryResponse.json();
|
|
7047
|
-
|
|
7048
|
-
|
|
7085
|
+
if (!settlementSkipped(retryResponse)) {
|
|
7086
|
+
this.sessionCalls++;
|
|
7087
|
+
this.sessionTotalUsd += 0.1575;
|
|
7088
|
+
}
|
|
7049
7089
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7050
7090
|
if (txHash) data.txHash = txHash;
|
|
7051
7091
|
return data;
|
|
@@ -7257,9 +7297,11 @@ var SpeechClient = class {
|
|
|
7257
7297
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7258
7298
|
}
|
|
7259
7299
|
const data = await retryResponse.json();
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7300
|
+
if (!settlementSkipped(retryResponse)) {
|
|
7301
|
+
this.sessionCalls++;
|
|
7302
|
+
const paidUsd = Number(details.amount) / 1e6;
|
|
7303
|
+
if (Number.isFinite(paidUsd)) this.sessionTotalUsd += paidUsd;
|
|
7304
|
+
}
|
|
7263
7305
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7264
7306
|
if (txHash) data.txHash = txHash;
|
|
7265
7307
|
return data;
|
|
@@ -7556,8 +7598,10 @@ var VideoClient = class {
|
|
|
7556
7598
|
}
|
|
7557
7599
|
if (lastStatus === "completed") {
|
|
7558
7600
|
const data = pollData;
|
|
7559
|
-
|
|
7560
|
-
|
|
7601
|
+
if (!settlementSkipped(pollResp)) {
|
|
7602
|
+
this.sessionCalls++;
|
|
7603
|
+
this.sessionTotalUsd += Number(details.amount) / 1e6;
|
|
7604
|
+
}
|
|
7561
7605
|
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
7562
7606
|
if (txHash) data.txHash = txHash;
|
|
7563
7607
|
return data;
|
|
@@ -7852,8 +7896,10 @@ var VoiceClient = class {
|
|
|
7852
7896
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
7853
7897
|
}
|
|
7854
7898
|
const data = await retryResponse.json();
|
|
7855
|
-
|
|
7856
|
-
|
|
7899
|
+
if (!settlementSkipped(retryResponse)) {
|
|
7900
|
+
this.sessionCalls++;
|
|
7901
|
+
this.sessionTotalUsd += CALL_PRICE_USD;
|
|
7902
|
+
}
|
|
7857
7903
|
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
7858
7904
|
if (txHash) data.txHash = txHash;
|
|
7859
7905
|
return data;
|
|
@@ -8073,9 +8119,10 @@ var PhoneClient = class {
|
|
|
8073
8119
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
8074
8120
|
}
|
|
8075
8121
|
const data = await this.unwrap(retry, true);
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8122
|
+
if (!settlementSkipped(retry)) {
|
|
8123
|
+
this.sessionCalls += 1;
|
|
8124
|
+
this.sessionTotalUsd += Number.parseFloat(details.amount) / 1e6;
|
|
8125
|
+
}
|
|
8079
8126
|
const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
|
|
8080
8127
|
if (txHash && data && typeof data === "object" && !("txHash" in data)) {
|
|
8081
8128
|
data.txHash = txHash;
|
|
@@ -8240,9 +8287,10 @@ var PortraitClient = class {
|
|
|
8240
8287
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
8241
8288
|
}
|
|
8242
8289
|
const data = await this.unwrap(retry, true);
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8290
|
+
if (!settlementSkipped(retry)) {
|
|
8291
|
+
this.sessionCalls += 1;
|
|
8292
|
+
this.sessionTotalUsd += Number.parseFloat(details.amount) / 1e6;
|
|
8293
|
+
}
|
|
8246
8294
|
if (data && typeof data === "object" && !("txHash" in data)) {
|
|
8247
8295
|
const settlement = data.settlement;
|
|
8248
8296
|
if (settlement?.tx_hash) {
|
|
@@ -9059,9 +9107,11 @@ var RpcClient = class {
|
|
|
9059
9107
|
}
|
|
9060
9108
|
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
9061
9109
|
}
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9110
|
+
if (!settlementSkipped(retryResponse)) {
|
|
9111
|
+
this.sessionCalls++;
|
|
9112
|
+
const paidUsd = Number(details.amount) / 1e6;
|
|
9113
|
+
if (Number.isFinite(paidUsd)) this.sessionTotalUsd += paidUsd;
|
|
9114
|
+
}
|
|
9065
9115
|
return { data: await retryResponse.json(), headers: retryResponse.headers };
|
|
9066
9116
|
}
|
|
9067
9117
|
async fetchWithTimeout(url, options) {
|
|
@@ -9208,7 +9258,7 @@ var BlockrunClient = class {
|
|
|
9208
9258
|
if (resp402.status !== 402) {
|
|
9209
9259
|
await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
|
|
9210
9260
|
}
|
|
9211
|
-
const paymentPayload = await this.signFrom402(resp402, submitUrl, {
|
|
9261
|
+
const { paymentPayload, costUsd } = await this.signFrom402(resp402, submitUrl, {
|
|
9212
9262
|
description: "BlockRun async job",
|
|
9213
9263
|
maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
|
|
9214
9264
|
});
|
|
@@ -9227,7 +9277,7 @@ var BlockrunClient = class {
|
|
|
9227
9277
|
await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
|
|
9228
9278
|
}
|
|
9229
9279
|
if (submitResp.status === 200) {
|
|
9230
|
-
this.recordSpending();
|
|
9280
|
+
this.recordSpending(submitResp, costUsd);
|
|
9231
9281
|
return submitResp.json();
|
|
9232
9282
|
}
|
|
9233
9283
|
const submitData = await submitResp.json();
|
|
@@ -9264,7 +9314,7 @@ var BlockrunClient = class {
|
|
|
9264
9314
|
);
|
|
9265
9315
|
}
|
|
9266
9316
|
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
9267
|
-
this.recordSpending();
|
|
9317
|
+
this.recordSpending(pollResp, costUsd);
|
|
9268
9318
|
return pollData;
|
|
9269
9319
|
}
|
|
9270
9320
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
@@ -9305,7 +9355,7 @@ var BlockrunClient = class {
|
|
|
9305
9355
|
if (resp402.status === 200) {
|
|
9306
9356
|
streamResp = resp402;
|
|
9307
9357
|
} else if (resp402.status === 402) {
|
|
9308
|
-
const paymentPayload = await this.signFrom402(resp402, url, {
|
|
9358
|
+
const { paymentPayload, costUsd } = await this.signFrom402(resp402, url, {
|
|
9309
9359
|
description: "BlockRun stream"
|
|
9310
9360
|
});
|
|
9311
9361
|
streamResp = await this.fetchWithTimeout(url, {
|
|
@@ -9324,7 +9374,7 @@ var BlockrunClient = class {
|
|
|
9324
9374
|
if (!streamResp.ok) {
|
|
9325
9375
|
await this.throwApiError(streamResp, `stream failed after payment (${url})`);
|
|
9326
9376
|
}
|
|
9327
|
-
this.recordSpending();
|
|
9377
|
+
this.recordSpending(streamResp, costUsd);
|
|
9328
9378
|
} else {
|
|
9329
9379
|
await this.throwApiError(resp402, `stream failed (${url})`);
|
|
9330
9380
|
return;
|
|
@@ -9380,7 +9430,7 @@ var BlockrunClient = class {
|
|
|
9380
9430
|
return response.json();
|
|
9381
9431
|
}
|
|
9382
9432
|
async handlePaymentAndRetry(url, method, body, response) {
|
|
9383
|
-
const paymentPayload = await this.signFrom402(response, url, {
|
|
9433
|
+
const { paymentPayload, costUsd } = await this.signFrom402(response, url, {
|
|
9384
9434
|
description: "BlockRun"
|
|
9385
9435
|
});
|
|
9386
9436
|
const retryInit = {
|
|
@@ -9403,13 +9453,14 @@ var BlockrunClient = class {
|
|
|
9403
9453
|
if (!retry.ok) {
|
|
9404
9454
|
await this.throwApiError(retry, `${method} ${url} failed after payment`);
|
|
9405
9455
|
}
|
|
9406
|
-
this.recordSpending();
|
|
9456
|
+
this.recordSpending(retry, costUsd);
|
|
9407
9457
|
return retry.json();
|
|
9408
9458
|
}
|
|
9409
9459
|
/**
|
|
9410
9460
|
* Read a 402 response's payment requirements (header or body), then sign and
|
|
9411
|
-
* return the base64 PAYMENT-SIGNATURE payload
|
|
9412
|
-
*
|
|
9461
|
+
* return the base64 PAYMENT-SIGNATURE payload with what it pays, in USD.
|
|
9462
|
+
* The cost stays with the call that signed it (booked by `recordSpending`):
|
|
9463
|
+
* concurrent calls on one client never share it.
|
|
9413
9464
|
*/
|
|
9414
9465
|
async signFrom402(response, url, opts) {
|
|
9415
9466
|
let paymentHeader = response.headers.get("payment-required");
|
|
@@ -9427,8 +9478,8 @@ var BlockrunClient = class {
|
|
|
9427
9478
|
}
|
|
9428
9479
|
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
9429
9480
|
const details = extractPaymentDetails(paymentRequired);
|
|
9430
|
-
|
|
9431
|
-
|
|
9481
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
9482
|
+
const paymentPayload = await createPaymentPayload(
|
|
9432
9483
|
this.privateKey,
|
|
9433
9484
|
this.account.address,
|
|
9434
9485
|
details.recipient,
|
|
@@ -9445,14 +9496,20 @@ var BlockrunClient = class {
|
|
|
9445
9496
|
asset: details.asset
|
|
9446
9497
|
}
|
|
9447
9498
|
);
|
|
9499
|
+
return { paymentPayload, costUsd };
|
|
9448
9500
|
}
|
|
9449
|
-
/**
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9501
|
+
/**
|
|
9502
|
+
* Book `costUsd`, what the call that `response` answered signed. A response
|
|
9503
|
+
* whose settlement the gateway skipped ({@link settlementSkipped}:
|
|
9504
|
+
* `X-Settlement-Skipped: <reason>` or `X-Payment-Waived`) moved nothing, so
|
|
9505
|
+
* nothing is booked. `X-Payment-Settled: false` alone is still booked: the
|
|
9506
|
+
* payment is owed.
|
|
9507
|
+
*/
|
|
9508
|
+
recordSpending(response, costUsd) {
|
|
9509
|
+
if (settlementSkipped(response)) return;
|
|
9510
|
+
if (costUsd > 0) {
|
|
9453
9511
|
this.sessionCalls += 1;
|
|
9454
|
-
this.sessionTotalUsd +=
|
|
9455
|
-
this.pendingCostUsd = 0;
|
|
9512
|
+
this.sessionTotalUsd += costUsd;
|
|
9456
9513
|
}
|
|
9457
9514
|
}
|
|
9458
9515
|
async throwApiError(resp, prefix) {
|
|
@@ -11671,6 +11728,8 @@ var SolanaBatchPayer = class {
|
|
|
11671
11728
|
wallet.refusedFor = outcome.errorReason;
|
|
11672
11729
|
}
|
|
11673
11730
|
}
|
|
11731
|
+
} else if (built.key && outcome.reason === "payment_required" && outcome.refusedForMs === void 0) {
|
|
11732
|
+
await this.recheckRefusedChannel(wallet, built.key, built.reclaimAnchor);
|
|
11674
11733
|
}
|
|
11675
11734
|
sent = void 0;
|
|
11676
11735
|
release();
|
|
@@ -11938,6 +11997,13 @@ var SolanaBatchPayer = class {
|
|
|
11938
11997
|
}
|
|
11939
11998
|
/** Build a new batch payment for a 402: the payload, its headers and its kind. */
|
|
11940
11999
|
async buildPayment(wallet, paymentRequired) {
|
|
12000
|
+
const acceptKey = channelKeyOf(trustedBatchAccept(paymentRequired, this.init.options.operators));
|
|
12001
|
+
let reclaimAnchor;
|
|
12002
|
+
try {
|
|
12003
|
+
const stored = acceptKey ? await wallet.book.base.get(acceptKey) : void 0;
|
|
12004
|
+
reclaimAnchor = stored ? savedReclaimAnchor(stored) : void 0;
|
|
12005
|
+
} catch {
|
|
12006
|
+
}
|
|
11941
12007
|
const { http, payload } = await this.createPayload(wallet, paymentRequired);
|
|
11942
12008
|
const scope = payScope.getStore();
|
|
11943
12009
|
if (scope?.afterError) {
|
|
@@ -11959,7 +12025,16 @@ var SolanaBatchPayer = class {
|
|
|
11959
12025
|
const record = known?.record;
|
|
11960
12026
|
kind = record && (record.pending === void 0 || record.hasConfirmedState) ? "top-up" : "open";
|
|
11961
12027
|
}
|
|
11962
|
-
return {
|
|
12028
|
+
return {
|
|
12029
|
+
http,
|
|
12030
|
+
payload,
|
|
12031
|
+
headers,
|
|
12032
|
+
kind,
|
|
12033
|
+
key: known?.key,
|
|
12034
|
+
...reclaimAnchor !== void 0 && known?.key === acceptKey ? { reclaimAnchor } : {},
|
|
12035
|
+
sentAt: Date.now(),
|
|
12036
|
+
sends: 0
|
|
12037
|
+
};
|
|
11963
12038
|
}
|
|
11964
12039
|
/**
|
|
11965
12040
|
* Write a deposit's intent to the wallet's journal before it is sent
|
|
@@ -12193,6 +12268,84 @@ var SolanaBatchPayer = class {
|
|
|
12193
12268
|
}
|
|
12194
12269
|
}
|
|
12195
12270
|
}
|
|
12271
|
+
/**
|
|
12272
|
+
* Queue a re-read of a stored channel whose account is gone at finalized
|
|
12273
|
+
* and at confirmed commitment (closed, its account reclaimed), anchored at
|
|
12274
|
+
* the SDK's own `getBlockHeight` at confirmed. The anchor is saved in the
|
|
12275
|
+
* record (`reclaimAnchorHeight`), so a short-lived process finishes the
|
|
12276
|
+
* wait instead of starting it over; a failed read leaves resync to take it,
|
|
12277
|
+
* and a failed save only costs that. resync pays exact until finality is
|
|
12278
|
+
* LANDING_MARGIN_BLOCKS past the anchor, then drops the record if the
|
|
12279
|
+
* finalized chain still has no account (a lagging read finds the channel
|
|
12280
|
+
* and rewrites the record, without the anchor).
|
|
12281
|
+
*/
|
|
12282
|
+
async queueReclaimed(wallet, key, record, channelId, savedAnchor) {
|
|
12283
|
+
let anchorHeight = savedReclaimAnchor(record) ?? savedAnchor;
|
|
12284
|
+
if (anchorHeight === void 0) {
|
|
12285
|
+
try {
|
|
12286
|
+
anchorHeight = rpcInteger(
|
|
12287
|
+
await rpcRequest(this.init.rpcUrl, "getBlockHeight", [{ commitment: "confirmed" }], this.init.rpcHeaders),
|
|
12288
|
+
"getBlockHeight"
|
|
12289
|
+
);
|
|
12290
|
+
} catch {
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12293
|
+
if (anchorHeight !== void 0 && savedReclaimAnchor(record) !== anchorHeight) {
|
|
12294
|
+
try {
|
|
12295
|
+
await wallet.book.set(key, { ...record, reclaimAnchorHeight: anchorHeight });
|
|
12296
|
+
} catch {
|
|
12297
|
+
}
|
|
12298
|
+
}
|
|
12299
|
+
addResyncTarget(wallet, {
|
|
12300
|
+
key,
|
|
12301
|
+
channelId,
|
|
12302
|
+
channelConfig: record.channelConfig,
|
|
12303
|
+
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12304
|
+
knownDeposit: parseAtomic(record.deposit),
|
|
12305
|
+
...anchorHeight !== void 0 ? { anchorHeight } : {},
|
|
12306
|
+
reason: "channel_unusable"
|
|
12307
|
+
});
|
|
12308
|
+
}
|
|
12309
|
+
/**
|
|
12310
|
+
* After the gateway refused an authorization with a plain 402 (no receipt,
|
|
12311
|
+
* not an admission refusal), check the channel it paid into. That is how
|
|
12312
|
+
* the gateway answers an authorization into a channel the facilitator
|
|
12313
|
+
* closed (its verify rejects it), and a channel with deposit to spare is
|
|
12314
|
+
* otherwise never read again: every call would sign another authorization
|
|
12315
|
+
* into it and pay exact. A channel the finalized chain shows closing,
|
|
12316
|
+
* closed or not this wallet's is re-read through resync (record dropped,
|
|
12317
|
+
* or kept and paid exact); one gone at every commitment is queued as
|
|
12318
|
+
* {@link queueReclaimed}; an open one of this wallet's, or one only at
|
|
12319
|
+
* confirmed (not finalized yet), is left as it is. A queued re-read is run
|
|
12320
|
+
* at once. Best effort: a failed read changes nothing, and the next
|
|
12321
|
+
* refusal checks again.
|
|
12322
|
+
*/
|
|
12323
|
+
async recheckRefusedChannel(wallet, key, savedAnchor) {
|
|
12324
|
+
try {
|
|
12325
|
+
const record = await wallet.book.get(key);
|
|
12326
|
+
if (!record || record.pending || typeof record.channelId !== "string" || wallet.resyncs.has(key)) return;
|
|
12327
|
+
const channelId = record.channelId;
|
|
12328
|
+
const channel = await readChannelAccount(this.init.rpcUrl, channelId, this.init.rpcHeaders);
|
|
12329
|
+
if (channel !== void 0) {
|
|
12330
|
+
if (channel.open && channel.payer === await this.init.address() && channel.authorizedSigner === record.channelConfig.payerAuthorizer && channel.mint === record.channelConfig.token) {
|
|
12331
|
+
return;
|
|
12332
|
+
}
|
|
12333
|
+
addResyncTarget(wallet, {
|
|
12334
|
+
key,
|
|
12335
|
+
channelId,
|
|
12336
|
+
channelConfig: record.channelConfig,
|
|
12337
|
+
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12338
|
+
knownDeposit: parseAtomic(record.deposit),
|
|
12339
|
+
reason: "channel_unusable"
|
|
12340
|
+
});
|
|
12341
|
+
} else {
|
|
12342
|
+
if (await readChannelAccount(this.init.rpcUrl, channelId, this.init.rpcHeaders, { commitment: "confirmed" }) !== void 0) return;
|
|
12343
|
+
await this.queueReclaimed(wallet, key, record, channelId, savedAnchor);
|
|
12344
|
+
}
|
|
12345
|
+
await this.resync(wallet);
|
|
12346
|
+
} catch {
|
|
12347
|
+
}
|
|
12348
|
+
}
|
|
12196
12349
|
/**
|
|
12197
12350
|
* When this payment will top up the wallet's channel, read how much of its
|
|
12198
12351
|
* deposit is already settled on-chain (at `finalized` commitment, so never
|
|
@@ -12246,31 +12399,7 @@ var SolanaBatchPayer = class {
|
|
|
12246
12399
|
}
|
|
12247
12400
|
if (channel === void 0 && unfinalized) return;
|
|
12248
12401
|
if (channel === void 0) {
|
|
12249
|
-
|
|
12250
|
-
if (anchorHeight === void 0) {
|
|
12251
|
-
try {
|
|
12252
|
-
anchorHeight = rpcInteger(
|
|
12253
|
-
await rpcRequest(this.init.rpcUrl, "getBlockHeight", [{ commitment: "confirmed" }], this.init.rpcHeaders),
|
|
12254
|
-
"getBlockHeight"
|
|
12255
|
-
);
|
|
12256
|
-
} catch {
|
|
12257
|
-
}
|
|
12258
|
-
if (anchorHeight !== void 0) {
|
|
12259
|
-
try {
|
|
12260
|
-
await wallet.book.set(key, { ...record, reclaimAnchorHeight: anchorHeight });
|
|
12261
|
-
} catch {
|
|
12262
|
-
}
|
|
12263
|
-
}
|
|
12264
|
-
}
|
|
12265
|
-
addResyncTarget(wallet, {
|
|
12266
|
-
key,
|
|
12267
|
-
channelId: record.channelId,
|
|
12268
|
-
channelConfig: record.channelConfig,
|
|
12269
|
-
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12270
|
-
knownDeposit: deposit,
|
|
12271
|
-
...anchorHeight !== void 0 ? { anchorHeight } : {},
|
|
12272
|
-
reason: "channel_unusable"
|
|
12273
|
-
});
|
|
12402
|
+
await this.queueReclaimed(wallet, key, record, record.channelId);
|
|
12274
12403
|
throw new ChannelResyncRequiredError(record.channelId);
|
|
12275
12404
|
}
|
|
12276
12405
|
if (!channel.open || channel.payer !== await this.init.address() || channel.authorizedSigner !== record.channelConfig.payerAuthorizer || channel.mint !== record.channelConfig.token) {
|
|
@@ -12907,7 +13036,7 @@ var SolanaLLMClient = class {
|
|
|
12907
13036
|
body: requestBody
|
|
12908
13037
|
});
|
|
12909
13038
|
await this.assertPaid(paid);
|
|
12910
|
-
this.recordSettlement(costUsd);
|
|
13039
|
+
this.recordSettlement(costUsd, paid);
|
|
12911
13040
|
return paid;
|
|
12912
13041
|
} catch (error) {
|
|
12913
13042
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
@@ -13294,7 +13423,7 @@ var SolanaLLMClient = class {
|
|
|
13294
13423
|
closesAtStart
|
|
13295
13424
|
);
|
|
13296
13425
|
if (batch.kind === "paid") {
|
|
13297
|
-
this.recordSettlement(batch.chargedUsd);
|
|
13426
|
+
this.recordSettlement(batch.chargedUsd, batch.response);
|
|
13298
13427
|
const paid = await this.readPaidJson(batch.response);
|
|
13299
13428
|
return withCost(paid, batch.response, batch.chargedUsd, "batch");
|
|
13300
13429
|
}
|
|
@@ -13411,7 +13540,7 @@ var SolanaLLMClient = class {
|
|
|
13411
13540
|
};
|
|
13412
13541
|
const deliver = (data, response) => {
|
|
13413
13542
|
if (kind === "video") {
|
|
13414
|
-
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd);
|
|
13543
|
+
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd, response);
|
|
13415
13544
|
const txHash = response.headers.get("x-payment-receipt");
|
|
13416
13545
|
if (txHash) data.txHash = txHash;
|
|
13417
13546
|
}
|
|
@@ -13658,7 +13787,7 @@ var SolanaLLMClient = class {
|
|
|
13658
13787
|
throw withDisposition(error, "paid-or-in-doubt");
|
|
13659
13788
|
}
|
|
13660
13789
|
await this.assertPaid(retryResponse);
|
|
13661
|
-
|
|
13790
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13662
13791
|
const paid = await this.readPaidJson(retryResponse);
|
|
13663
13792
|
return withCost(paid, retryResponse, costUsd, "exact");
|
|
13664
13793
|
}
|
|
@@ -13718,11 +13847,11 @@ var SolanaLLMClient = class {
|
|
|
13718
13847
|
await this.assertPaid(retryResponse);
|
|
13719
13848
|
if (retryResponse.status === 202) {
|
|
13720
13849
|
const kind = pollKindFor(url);
|
|
13721
|
-
if (kind !== "video") this.recordSettlement(costUsd);
|
|
13850
|
+
if (kind !== "video") this.recordSettlement(costUsd, retryResponse);
|
|
13722
13851
|
const submitted = await retryResponse.json();
|
|
13723
13852
|
return this.followSolanaJob(submitted, media.pollBudgetMs ?? this.timeout, kind);
|
|
13724
13853
|
}
|
|
13725
|
-
this.recordSettlement(costUsd);
|
|
13854
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13726
13855
|
return retryResponse.json();
|
|
13727
13856
|
}
|
|
13728
13857
|
async getWithPaymentRaw(endpoint, params) {
|
|
@@ -13766,7 +13895,7 @@ var SolanaLLMClient = class {
|
|
|
13766
13895
|
}
|
|
13767
13896
|
});
|
|
13768
13897
|
await this.assertPaid(retryResponse);
|
|
13769
|
-
this.recordSettlement(costUsd);
|
|
13898
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13770
13899
|
return retryResponse.json();
|
|
13771
13900
|
}
|
|
13772
13901
|
/**
|
|
@@ -13804,8 +13933,17 @@ var SolanaLLMClient = class {
|
|
|
13804
13933
|
);
|
|
13805
13934
|
}
|
|
13806
13935
|
}
|
|
13807
|
-
/**
|
|
13808
|
-
|
|
13936
|
+
/**
|
|
13937
|
+
* Count one settled x402 payment against the session total.
|
|
13938
|
+
*
|
|
13939
|
+
* Takes the paid response so no booking site can forget the check: a
|
|
13940
|
+
* response whose settlement the gateway skipped ({@link settlementSkipped}:
|
|
13941
|
+
* `X-Settlement-Skipped: free-fallback | empty-result | unexecuted | …`, or
|
|
13942
|
+
* `X-Payment-Waived`) moved nothing on chain and books nothing.
|
|
13943
|
+
* `X-Payment-Settled: false` alone is still booked: the payment is owed.
|
|
13944
|
+
*/
|
|
13945
|
+
recordSettlement(costUsd, response) {
|
|
13946
|
+
if (settlementSkipped(response)) return;
|
|
13809
13947
|
this.sessionCalls += 1;
|
|
13810
13948
|
this.sessionTotalUsd += costUsd;
|
|
13811
13949
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "3.24.
|
|
3
|
+
"version": "3.24.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TypeScript SDK for BlockRun - every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. No rate limits.",
|
|
6
6
|
"main": "dist/index.cjs",
|