@blockrun/llm 3.24.7 → 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 +250 -95
- package/dist/index.d.cts +67 -12
- package/dist/index.d.ts +67 -12
- package/dist/index.js +250 -95
- 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) {
|
|
@@ -10614,7 +10671,7 @@ var ChannelLayoutError = class extends Error {
|
|
|
10614
10671
|
};
|
|
10615
10672
|
async function readChannelAccount(rpcUrl, channelId, rpcHeaders, options = {}) {
|
|
10616
10673
|
await checkChannelLayout();
|
|
10617
|
-
const config = { encoding: "base64", commitment: "finalized" };
|
|
10674
|
+
const config = { encoding: "base64", commitment: options.commitment ?? "finalized" };
|
|
10618
10675
|
if (options.minContextSlot !== void 0) config.minContextSlot = options.minContextSlot;
|
|
10619
10676
|
const response = await fetch(rpcUrl, {
|
|
10620
10677
|
method: "POST",
|
|
@@ -10678,7 +10735,10 @@ function checkChannelLayout() {
|
|
|
10678
10735
|
return layoutChecked;
|
|
10679
10736
|
}
|
|
10680
10737
|
var LANDING_MARGIN_BLOCKS = 300;
|
|
10681
|
-
|
|
10738
|
+
function savedReclaimAnchor(record) {
|
|
10739
|
+
const saved = record.reclaimAnchorHeight;
|
|
10740
|
+
return typeof saved === "number" && Number.isSafeInteger(saved) && saved > 0 ? saved : void 0;
|
|
10741
|
+
}
|
|
10682
10742
|
function maxOf(a, b) {
|
|
10683
10743
|
if (a === void 0) return b;
|
|
10684
10744
|
if (b === void 0) return a;
|
|
@@ -11668,6 +11728,8 @@ var SolanaBatchPayer = class {
|
|
|
11668
11728
|
wallet.refusedFor = outcome.errorReason;
|
|
11669
11729
|
}
|
|
11670
11730
|
}
|
|
11731
|
+
} else if (built.key && outcome.reason === "payment_required" && outcome.refusedForMs === void 0) {
|
|
11732
|
+
await this.recheckRefusedChannel(wallet, built.key, built.reclaimAnchor);
|
|
11671
11733
|
}
|
|
11672
11734
|
sent = void 0;
|
|
11673
11735
|
release();
|
|
@@ -11935,6 +11997,13 @@ var SolanaBatchPayer = class {
|
|
|
11935
11997
|
}
|
|
11936
11998
|
/** Build a new batch payment for a 402: the payload, its headers and its kind. */
|
|
11937
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
|
+
}
|
|
11938
12007
|
const { http, payload } = await this.createPayload(wallet, paymentRequired);
|
|
11939
12008
|
const scope = payScope.getStore();
|
|
11940
12009
|
if (scope?.afterError) {
|
|
@@ -11956,7 +12025,16 @@ var SolanaBatchPayer = class {
|
|
|
11956
12025
|
const record = known?.record;
|
|
11957
12026
|
kind = record && (record.pending === void 0 || record.hasConfirmedState) ? "top-up" : "open";
|
|
11958
12027
|
}
|
|
11959
|
-
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
|
+
};
|
|
11960
12038
|
}
|
|
11961
12039
|
/**
|
|
11962
12040
|
* Write a deposit's intent to the wallet's journal before it is sent
|
|
@@ -12190,6 +12268,84 @@ var SolanaBatchPayer = class {
|
|
|
12190
12268
|
}
|
|
12191
12269
|
}
|
|
12192
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
|
+
}
|
|
12193
12349
|
/**
|
|
12194
12350
|
* When this payment will top up the wallet's channel, read how much of its
|
|
12195
12351
|
* deposit is already settled on-chain (at `finalized` commitment, so never
|
|
@@ -12207,7 +12363,11 @@ var SolanaBatchPayer = class {
|
|
|
12207
12363
|
* pays exact (`channel_unreadable`), record kept. If
|
|
12208
12364
|
* the finalized channel is closing or closed, or is not this wallet's for
|
|
12209
12365
|
* the record's operator and mint, no top-up is sized from the record: it
|
|
12210
|
-
* is re-read at once (`channel_unusable`), see {@link createPayload}.
|
|
12366
|
+
* is re-read at once (`channel_unusable`), see {@link createPayload}. So is
|
|
12367
|
+
* a channel with no account at finalized or at confirmed commitment (closed
|
|
12368
|
+
* and reclaimed); resync drops its record only once finality is past its
|
|
12369
|
+
* own anchor. One that shows only at confirmed is not finalized yet: the
|
|
12370
|
+
* lifetime cap applies.
|
|
12211
12371
|
*/
|
|
12212
12372
|
async prepareTopUp(wallet, paymentRequired) {
|
|
12213
12373
|
wallet.topUp = void 0;
|
|
@@ -12219,8 +12379,12 @@ var SolanaBatchPayer = class {
|
|
|
12219
12379
|
const deposit = parseAtomic(record.deposit);
|
|
12220
12380
|
if (parseAtomic(record.chargedCumulativeAmount) + parseAtomic(accept.amount) <= deposit) return;
|
|
12221
12381
|
let channel;
|
|
12382
|
+
let unfinalized = false;
|
|
12222
12383
|
try {
|
|
12223
12384
|
channel = await readChannelAccount(this.init.rpcUrl, record.channelId, this.init.rpcHeaders);
|
|
12385
|
+
if (channel === void 0) {
|
|
12386
|
+
unfinalized = await readChannelAccount(this.init.rpcUrl, record.channelId, this.init.rpcHeaders, { commitment: "confirmed" }) !== void 0;
|
|
12387
|
+
}
|
|
12224
12388
|
} catch (err) {
|
|
12225
12389
|
if (err instanceof ChannelLayoutError) {
|
|
12226
12390
|
throw new ResyncError("channel_unreadable", `channel ${record.channelId}: ${err.message}; no top-up is sized from it`);
|
|
@@ -12233,27 +12397,9 @@ var SolanaBatchPayer = class {
|
|
|
12233
12397
|
`could not read channel ${record.channelId} before topping it up: ${err instanceof Error ? err.message : String(err)}`
|
|
12234
12398
|
);
|
|
12235
12399
|
}
|
|
12400
|
+
if (channel === void 0 && unfinalized) return;
|
|
12236
12401
|
if (channel === void 0) {
|
|
12237
|
-
|
|
12238
|
-
const delaySlots = Math.ceil((record.channelConfig.withdrawDelay ?? 86400) / SECONDS_PER_SLOT);
|
|
12239
|
-
let finalizedSlot;
|
|
12240
|
-
try {
|
|
12241
|
-
const epoch = await rpcRequest(this.init.rpcUrl, "getEpochInfo", [{ commitment: "finalized" }], this.init.rpcHeaders);
|
|
12242
|
-
finalizedSlot = rpcInteger(epoch.absoluteSlot, "getEpochInfo absoluteSlot");
|
|
12243
|
-
} catch {
|
|
12244
|
-
return;
|
|
12245
|
-
}
|
|
12246
|
-
if (!Number.isSafeInteger(openSlot) || openSlot <= 0 || finalizedSlot <= openSlot + delaySlots) return;
|
|
12247
|
-
addResyncTarget(wallet, {
|
|
12248
|
-
key,
|
|
12249
|
-
channelId: record.channelId,
|
|
12250
|
-
channelConfig: record.channelConfig,
|
|
12251
|
-
cumulative: parseAtomic(record.chargedCumulativeAmount),
|
|
12252
|
-
knownDeposit: deposit,
|
|
12253
|
-
// No deposit is in doubt: nothing can still land in a reclaimed account.
|
|
12254
|
-
anchorHeight: 0,
|
|
12255
|
-
reason: "channel_unusable"
|
|
12256
|
-
});
|
|
12402
|
+
await this.queueReclaimed(wallet, key, record, record.channelId);
|
|
12257
12403
|
throw new ChannelResyncRequiredError(record.channelId);
|
|
12258
12404
|
}
|
|
12259
12405
|
if (!channel.open || channel.payer !== await this.init.address() || channel.authorizedSigner !== record.channelConfig.payerAuthorizer || channel.mint !== record.channelConfig.token) {
|
|
@@ -12890,7 +13036,7 @@ var SolanaLLMClient = class {
|
|
|
12890
13036
|
body: requestBody
|
|
12891
13037
|
});
|
|
12892
13038
|
await this.assertPaid(paid);
|
|
12893
|
-
this.recordSettlement(costUsd);
|
|
13039
|
+
this.recordSettlement(costUsd, paid);
|
|
12894
13040
|
return paid;
|
|
12895
13041
|
} catch (error) {
|
|
12896
13042
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
@@ -13277,7 +13423,7 @@ var SolanaLLMClient = class {
|
|
|
13277
13423
|
closesAtStart
|
|
13278
13424
|
);
|
|
13279
13425
|
if (batch.kind === "paid") {
|
|
13280
|
-
this.recordSettlement(batch.chargedUsd);
|
|
13426
|
+
this.recordSettlement(batch.chargedUsd, batch.response);
|
|
13281
13427
|
const paid = await this.readPaidJson(batch.response);
|
|
13282
13428
|
return withCost(paid, batch.response, batch.chargedUsd, "batch");
|
|
13283
13429
|
}
|
|
@@ -13394,7 +13540,7 @@ var SolanaLLMClient = class {
|
|
|
13394
13540
|
};
|
|
13395
13541
|
const deliver = (data, response) => {
|
|
13396
13542
|
if (kind === "video") {
|
|
13397
|
-
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd);
|
|
13543
|
+
if (videoCostUsd !== void 0) this.recordSettlement(videoCostUsd, response);
|
|
13398
13544
|
const txHash = response.headers.get("x-payment-receipt");
|
|
13399
13545
|
if (txHash) data.txHash = txHash;
|
|
13400
13546
|
}
|
|
@@ -13641,7 +13787,7 @@ var SolanaLLMClient = class {
|
|
|
13641
13787
|
throw withDisposition(error, "paid-or-in-doubt");
|
|
13642
13788
|
}
|
|
13643
13789
|
await this.assertPaid(retryResponse);
|
|
13644
|
-
|
|
13790
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13645
13791
|
const paid = await this.readPaidJson(retryResponse);
|
|
13646
13792
|
return withCost(paid, retryResponse, costUsd, "exact");
|
|
13647
13793
|
}
|
|
@@ -13701,11 +13847,11 @@ var SolanaLLMClient = class {
|
|
|
13701
13847
|
await this.assertPaid(retryResponse);
|
|
13702
13848
|
if (retryResponse.status === 202) {
|
|
13703
13849
|
const kind = pollKindFor(url);
|
|
13704
|
-
if (kind !== "video") this.recordSettlement(costUsd);
|
|
13850
|
+
if (kind !== "video") this.recordSettlement(costUsd, retryResponse);
|
|
13705
13851
|
const submitted = await retryResponse.json();
|
|
13706
13852
|
return this.followSolanaJob(submitted, media.pollBudgetMs ?? this.timeout, kind);
|
|
13707
13853
|
}
|
|
13708
|
-
this.recordSettlement(costUsd);
|
|
13854
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13709
13855
|
return retryResponse.json();
|
|
13710
13856
|
}
|
|
13711
13857
|
async getWithPaymentRaw(endpoint, params) {
|
|
@@ -13749,7 +13895,7 @@ var SolanaLLMClient = class {
|
|
|
13749
13895
|
}
|
|
13750
13896
|
});
|
|
13751
13897
|
await this.assertPaid(retryResponse);
|
|
13752
|
-
this.recordSettlement(costUsd);
|
|
13898
|
+
this.recordSettlement(costUsd, retryResponse);
|
|
13753
13899
|
return retryResponse.json();
|
|
13754
13900
|
}
|
|
13755
13901
|
/**
|
|
@@ -13787,8 +13933,17 @@ var SolanaLLMClient = class {
|
|
|
13787
13933
|
);
|
|
13788
13934
|
}
|
|
13789
13935
|
}
|
|
13790
|
-
/**
|
|
13791
|
-
|
|
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;
|
|
13792
13947
|
this.sessionCalls += 1;
|
|
13793
13948
|
this.sessionTotalUsd += costUsd;
|
|
13794
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",
|