@blockrun/llm 3.21.0 → 3.22.1
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/README.md +70 -12
- package/dist/index.cjs +495 -99
- package/dist/index.d.cts +215 -17
- package/dist/index.d.ts +215 -17
- package/dist/index.js +491 -99
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
BatchPaymentUnresolvedError: () => BatchPaymentUnresolvedError,
|
|
41
41
|
BlockrunClient: () => BlockrunClient,
|
|
42
42
|
BlockrunError: () => BlockrunError,
|
|
43
|
+
DEFAULT_BATCH_IN_DOUBT: () => DEFAULT_BATCH_IN_DOUBT,
|
|
43
44
|
DEFAULT_BATCH_RATE_LIMIT: () => DEFAULT_BATCH_RATE_LIMIT,
|
|
44
45
|
EVM_NETWORKS: () => EVM_NETWORKS,
|
|
45
46
|
ImageClient: () => ImageClient,
|
|
@@ -103,6 +104,7 @@ __export(index_exports, {
|
|
|
103
104
|
loadWallet: () => loadWallet,
|
|
104
105
|
logCost: () => logCost,
|
|
105
106
|
parsePaymentRequired: () => parsePaymentRequired,
|
|
107
|
+
readSettlement: () => readSettlement,
|
|
106
108
|
retryDisposition: () => retryDisposition,
|
|
107
109
|
saveSolanaWallet: () => saveSolanaWallet,
|
|
108
110
|
saveToCache: () => saveToCache,
|
|
@@ -117,6 +119,8 @@ __export(index_exports, {
|
|
|
117
119
|
solanaKeyToBytes: () => solanaKeyToBytes,
|
|
118
120
|
solanaPublicKey: () => solanaPublicKey,
|
|
119
121
|
status: () => status,
|
|
122
|
+
toWireSearchParameters: () => toWireSearchParameters,
|
|
123
|
+
toWireTools: () => toWireTools,
|
|
120
124
|
validateMaxTokens: () => validateMaxTokens,
|
|
121
125
|
validateModel: () => validateModel,
|
|
122
126
|
validateTemperature: () => validateTemperature,
|
|
@@ -439,6 +443,130 @@ var ApiKeyAuth = class {
|
|
|
439
443
|
}
|
|
440
444
|
};
|
|
441
445
|
|
|
446
|
+
// src/search-wire.ts
|
|
447
|
+
function put(out, key, value) {
|
|
448
|
+
if (value !== void 0) out[key] = value;
|
|
449
|
+
}
|
|
450
|
+
function handles(list) {
|
|
451
|
+
return list?.map((h) => h.trim().replace(/^@+/, ""));
|
|
452
|
+
}
|
|
453
|
+
function sourceToWire(source) {
|
|
454
|
+
const raw = source;
|
|
455
|
+
const out = { ...raw };
|
|
456
|
+
for (const key of Object.keys(out)) if (/[A-Z]/.test(key)) delete out[key];
|
|
457
|
+
switch (source.type) {
|
|
458
|
+
case "x":
|
|
459
|
+
put(out, "included_x_handles", handles(source.includedXHandles) ?? out.included_x_handles);
|
|
460
|
+
put(out, "excluded_x_handles", handles(source.excludedXHandles) ?? out.excluded_x_handles);
|
|
461
|
+
put(out, "post_favorite_count", source.postFavoriteCount);
|
|
462
|
+
put(out, "post_view_count", source.postViewCount);
|
|
463
|
+
put(out, "enable_image_understanding", source.enableImageUnderstanding);
|
|
464
|
+
put(out, "enable_video_understanding", source.enableVideoUnderstanding);
|
|
465
|
+
break;
|
|
466
|
+
case "web":
|
|
467
|
+
put(out, "country", source.country);
|
|
468
|
+
put(out, "allowed_websites", source.allowedWebsites);
|
|
469
|
+
put(out, "excluded_websites", source.excludedWebsites);
|
|
470
|
+
put(out, "safe_search", source.safeSearch);
|
|
471
|
+
put(out, "enable_image_understanding", source.enableImageUnderstanding);
|
|
472
|
+
break;
|
|
473
|
+
case "news":
|
|
474
|
+
put(out, "country", source.country);
|
|
475
|
+
put(out, "allowed_websites", source.allowedWebsites);
|
|
476
|
+
put(out, "excluded_websites", source.excludedWebsites);
|
|
477
|
+
put(out, "safe_search", source.safeSearch);
|
|
478
|
+
break;
|
|
479
|
+
case "rss":
|
|
480
|
+
put(out, "links", source.links);
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
function toWireSearchParameters(sp) {
|
|
486
|
+
const raw = sp;
|
|
487
|
+
const out = {};
|
|
488
|
+
for (const [key, value] of Object.entries(raw)) if (!/[A-Z]/.test(key) && key !== "sources") out[key] = value;
|
|
489
|
+
put(out, "mode", sp.mode);
|
|
490
|
+
if (sp.sources) out.sources = sp.sources.map(sourceToWire);
|
|
491
|
+
put(out, "return_citations", sp.returnCitations);
|
|
492
|
+
put(out, "from_date", sp.fromDate);
|
|
493
|
+
put(out, "to_date", sp.toDate);
|
|
494
|
+
put(out, "max_search_results", sp.maxSearchResults);
|
|
495
|
+
put(out, "max_turns", sp.maxTurns);
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
function toWireTools(tools) {
|
|
499
|
+
return tools.map((tool) => {
|
|
500
|
+
if (tool.type === "x_search") {
|
|
501
|
+
const out = { type: "x_search" };
|
|
502
|
+
put(out, "allowed_x_handles", handles(tool.allowedXHandles));
|
|
503
|
+
put(out, "excluded_x_handles", handles(tool.excludedXHandles));
|
|
504
|
+
put(out, "from_date", tool.fromDate);
|
|
505
|
+
put(out, "to_date", tool.toDate);
|
|
506
|
+
put(out, "enable_image_understanding", tool.enableImageUnderstanding);
|
|
507
|
+
put(out, "enable_video_understanding", tool.enableVideoUnderstanding);
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
if (tool.type === "web_search") {
|
|
511
|
+
const out = { type: "web_search" };
|
|
512
|
+
put(out, "allowed_domains", tool.allowedDomains);
|
|
513
|
+
put(out, "excluded_domains", tool.excludedDomains);
|
|
514
|
+
put(out, "enable_image_understanding", tool.enableImageUnderstanding);
|
|
515
|
+
return out;
|
|
516
|
+
}
|
|
517
|
+
return tool;
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/receipt.ts
|
|
522
|
+
function atomicToUsd(amount) {
|
|
523
|
+
return typeof amount === "string" && /^\d+$/.test(amount) ? Number(amount) / 1e6 : void 0;
|
|
524
|
+
}
|
|
525
|
+
function receiptChargedUsd(receipt) {
|
|
526
|
+
return atomicToUsd(receipt?.extra?.chargedAmount) ?? atomicToUsd(receipt?.amount);
|
|
527
|
+
}
|
|
528
|
+
function decode(header) {
|
|
529
|
+
try {
|
|
530
|
+
const json = typeof Buffer !== "undefined" ? Buffer.from(header, "base64").toString("utf8") : atob(header);
|
|
531
|
+
const parsed = JSON.parse(json);
|
|
532
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
533
|
+
} catch {
|
|
534
|
+
return void 0;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
function readSettlement(response, quotedUsd, scheme) {
|
|
538
|
+
const header = response.headers.get("PAYMENT-RESPONSE") ?? response.headers.get("X-PAYMENT-RESPONSE");
|
|
539
|
+
const receipt = header ? decode(header) : void 0;
|
|
540
|
+
const extra = receipt?.extra;
|
|
541
|
+
const costUsd = receiptChargedUsd({
|
|
542
|
+
amount: receipt?.amount,
|
|
543
|
+
extra: extra && typeof extra === "object" ? extra : void 0
|
|
544
|
+
}) ?? quotedUsd;
|
|
545
|
+
const receiptTx = typeof receipt?.transaction === "string" && receipt.transaction ? receipt.transaction : void 0;
|
|
546
|
+
const transaction = receiptTx ?? (response.headers.get("X-Payment-Receipt")?.trim() || void 0);
|
|
547
|
+
const settlement = {
|
|
548
|
+
scheme,
|
|
549
|
+
quotedUsd,
|
|
550
|
+
...transaction ? { transaction } : {},
|
|
551
|
+
...typeof receipt?.network === "string" ? { network: receipt.network } : {},
|
|
552
|
+
...receipt?.success === false ? { success: false } : {}
|
|
553
|
+
};
|
|
554
|
+
return { costUsd, settlement };
|
|
555
|
+
}
|
|
556
|
+
function settlementSkipped(response) {
|
|
557
|
+
return response.headers.get("X-Settlement-Skipped") === "free-fallback";
|
|
558
|
+
}
|
|
559
|
+
function withCost(body, response, bookedUsd, scheme) {
|
|
560
|
+
if (settlementSkipped(response)) {
|
|
561
|
+
body.costUsd = 0;
|
|
562
|
+
body.settlement = { ...readSettlement(response, bookedUsd, scheme).settlement, skipped: "free-fallback" };
|
|
563
|
+
return body;
|
|
564
|
+
}
|
|
565
|
+
body.costUsd = bookedUsd;
|
|
566
|
+
body.settlement = readSettlement(response, bookedUsd, scheme).settlement;
|
|
567
|
+
return body;
|
|
568
|
+
}
|
|
569
|
+
|
|
442
570
|
// src/client.ts
|
|
443
571
|
var import_accounts2 = require("viem/accounts");
|
|
444
572
|
|
|
@@ -4225,6 +4353,11 @@ function errSummary(err) {
|
|
|
4225
4353
|
}
|
|
4226
4354
|
return String(err).slice(0, 100);
|
|
4227
4355
|
}
|
|
4356
|
+
function requestsSearch(options) {
|
|
4357
|
+
if (options.tools?.some((tool) => tool.type === "x_search" || tool.type === "web_search")) return true;
|
|
4358
|
+
if (options.searchParameters !== void 0) return options.searchParameters.mode !== "off";
|
|
4359
|
+
return options.search === true;
|
|
4360
|
+
}
|
|
4228
4361
|
function routingProfileForModel(model) {
|
|
4229
4362
|
return AUTO_ROUTING_PROFILES[model.toLowerCase()];
|
|
4230
4363
|
}
|
|
@@ -4252,7 +4385,7 @@ function routingText(messages) {
|
|
|
4252
4385
|
};
|
|
4253
4386
|
}
|
|
4254
4387
|
function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, options = {}) {
|
|
4255
|
-
const tools = options.tools ?? [];
|
|
4388
|
+
const tools = (options.tools ?? []).filter((tool) => tool.type === "function");
|
|
4256
4389
|
const requiresTools = options.toolChoice === "none" ? false : options.toolChoice === "required" || typeof options.toolChoice === "object" ? true : void 0;
|
|
4257
4390
|
const decision = route(prompt, systemPrompt, maxOutputTokens, {
|
|
4258
4391
|
config: DEFAULT_ROUTING_CONFIG,
|
|
@@ -4272,6 +4405,15 @@ function routeWithCatalog(prompt, systemPrompt, maxOutputTokens, modelPricing, o
|
|
|
4272
4405
|
const resolved = !id.startsWith("free/") ? id : modelPricing.has(`nvidia/${id.slice(5)}`) ? `nvidia/${id.slice(5)}` : null;
|
|
4273
4406
|
if (resolved && !callable.includes(resolved)) callable.push(resolved);
|
|
4274
4407
|
}
|
|
4408
|
+
if (requestsSearch(options)) {
|
|
4409
|
+
const searchable = callable.filter((id) => id.startsWith("xai/"));
|
|
4410
|
+
if (searchable.length === 0) {
|
|
4411
|
+
throw new Error(
|
|
4412
|
+
`Search (x_search / web_search tools or searchParameters) runs on xai/* models only, and the ${options.routingProfile ?? "auto"} route has no xai/* candidate for this request. Call an xai/* model directly (e.g. xai/grok-4.3) or drop the search options.`
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4415
|
+
callable.splice(0, callable.length, ...searchable);
|
|
4416
|
+
}
|
|
4275
4417
|
const estimatedInputTokens = Math.ceil(
|
|
4276
4418
|
Math.max(options.conversationChars ?? 0, `${systemPrompt ?? ""} ${prompt}`.length) / 4
|
|
4277
4419
|
);
|
|
@@ -4690,7 +4832,7 @@ function getCostSummary() {
|
|
|
4690
4832
|
}
|
|
4691
4833
|
|
|
4692
4834
|
// src/version.ts
|
|
4693
|
-
var SDK_VERSION = "3.
|
|
4835
|
+
var SDK_VERSION = "3.22.1";
|
|
4694
4836
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4695
4837
|
|
|
4696
4838
|
// src/client.ts
|
|
@@ -4836,7 +4978,9 @@ var LLMClient = class _LLMClient {
|
|
|
4836
4978
|
options?.maxOutputTokens ?? options?.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
4837
4979
|
{
|
|
4838
4980
|
routingProfile: options?.routingProfile,
|
|
4839
|
-
requiresStructuredOutput: options?.responseFormat !== void 0
|
|
4981
|
+
requiresStructuredOutput: options?.responseFormat !== void 0,
|
|
4982
|
+
search: options?.search,
|
|
4983
|
+
searchParameters: options?.searchParameters
|
|
4840
4984
|
}
|
|
4841
4985
|
);
|
|
4842
4986
|
}
|
|
@@ -4903,6 +5047,8 @@ var LLMClient = class _LLMClient {
|
|
|
4903
5047
|
requiresStructuredOutput: options.responseFormat !== void 0,
|
|
4904
5048
|
tools: options.tools,
|
|
4905
5049
|
toolChoice: options.toolChoice,
|
|
5050
|
+
search: options.search,
|
|
5051
|
+
searchParameters: options.searchParameters,
|
|
4906
5052
|
conversationChars,
|
|
4907
5053
|
hasVision
|
|
4908
5054
|
}
|
|
@@ -4917,6 +5063,7 @@ var LLMClient = class _LLMClient {
|
|
|
4917
5063
|
toolChoice: options.toolChoice,
|
|
4918
5064
|
responseFormat: options.responseFormat,
|
|
4919
5065
|
stop: options.stop,
|
|
5066
|
+
timeout: options.timeout,
|
|
4920
5067
|
// An explicit caller-supplied chain wins over the routed one.
|
|
4921
5068
|
fallbackModels: options.fallbackModels ?? decision.fallbacks
|
|
4922
5069
|
});
|
|
@@ -4993,11 +5140,11 @@ var LLMClient = class _LLMClient {
|
|
|
4993
5140
|
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
4994
5141
|
if (options?.topP !== void 0) body.top_p = options.topP;
|
|
4995
5142
|
if (options?.searchParameters !== void 0) {
|
|
4996
|
-
body.search_parameters = options.searchParameters;
|
|
5143
|
+
body.search_parameters = toWireSearchParameters(options.searchParameters);
|
|
4997
5144
|
} else if (options?.search === true) {
|
|
4998
5145
|
body.search_parameters = { mode: "on" };
|
|
4999
5146
|
}
|
|
5000
|
-
if (options?.tools !== void 0) body.tools = options.tools;
|
|
5147
|
+
if (options?.tools !== void 0) body.tools = toWireTools(options.tools);
|
|
5001
5148
|
if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
5002
5149
|
if (options?.responseFormat !== void 0) body.response_format = options.responseFormat;
|
|
5003
5150
|
if (options?.stop !== void 0) body.stop = options.stop;
|
|
@@ -5008,7 +5155,7 @@ var LLMClient = class _LLMClient {
|
|
|
5008
5155
|
for (let i = 0; i < chain.length; i++) {
|
|
5009
5156
|
const candidate = chain[i];
|
|
5010
5157
|
try {
|
|
5011
|
-
return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate));
|
|
5158
|
+
return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate), options?.timeout);
|
|
5012
5159
|
} catch (err) {
|
|
5013
5160
|
lastErr = err;
|
|
5014
5161
|
const next = chain[i + 1];
|
|
@@ -5079,19 +5226,19 @@ var LLMClient = class _LLMClient {
|
|
|
5079
5226
|
* is `"paid-or-in-doubt"`. With an API key the request itself is billed, so
|
|
5080
5227
|
* only the account API's explicit 4xx refusal is `"unpaid"`.
|
|
5081
5228
|
*/
|
|
5082
|
-
async requestWithPayment(endpoint, body) {
|
|
5229
|
+
async requestWithPayment(endpoint, body, timeoutMs) {
|
|
5083
5230
|
const url = `${this.apiUrl}${endpoint}`;
|
|
5084
5231
|
const unpaid = {
|
|
5085
5232
|
method: "POST",
|
|
5086
5233
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
5087
5234
|
body: JSON.stringify(body)
|
|
5088
5235
|
};
|
|
5089
|
-
const response = await this.sendUnpaid(url, unpaid);
|
|
5236
|
+
const response = await this.sendUnpaid(url, unpaid, timeoutMs);
|
|
5090
5237
|
if (response.status === 502 || response.status === 503) {
|
|
5091
5238
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
5092
|
-
const retryResp = await this.sendUnpaid(url, unpaid);
|
|
5239
|
+
const retryResp = await this.sendUnpaid(url, unpaid, timeoutMs);
|
|
5093
5240
|
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
5094
|
-
if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
|
|
5241
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp, timeoutMs);
|
|
5095
5242
|
if (!retryResp.ok) {
|
|
5096
5243
|
let errorBody;
|
|
5097
5244
|
try {
|
|
@@ -5108,7 +5255,7 @@ var LLMClient = class _LLMClient {
|
|
|
5108
5255
|
}
|
|
5109
5256
|
}
|
|
5110
5257
|
if (response.status === 402) {
|
|
5111
|
-
return this.handlePaymentAndRetry(url, body, response);
|
|
5258
|
+
return this.handlePaymentAndRetry(url, body, response, timeoutMs);
|
|
5112
5259
|
}
|
|
5113
5260
|
if (!response.ok) {
|
|
5114
5261
|
let errorBody;
|
|
@@ -5133,9 +5280,9 @@ var LLMClient = class _LLMClient {
|
|
|
5133
5280
|
* {@link RetryDisposition}: `"unpaid"` with a wallet; with an API key, see
|
|
5134
5281
|
* {@link accountErrorDisposition} (the request itself is billed).
|
|
5135
5282
|
*/
|
|
5136
|
-
async sendUnpaid(url, init) {
|
|
5283
|
+
async sendUnpaid(url, init, timeoutMs) {
|
|
5137
5284
|
try {
|
|
5138
|
-
return await this.fetchWithTimeout(url, init);
|
|
5285
|
+
return await this.fetchWithTimeout(url, init, timeoutMs);
|
|
5139
5286
|
} catch (err) {
|
|
5140
5287
|
throw withDisposition(err, this.apiAuth ? accountErrorDisposition(err) : "unpaid");
|
|
5141
5288
|
}
|
|
@@ -5143,13 +5290,22 @@ var LLMClient = class _LLMClient {
|
|
|
5143
5290
|
/**
|
|
5144
5291
|
* Parse a response to a request that carried no payment: the free tier,
|
|
5145
5292
|
* or, with an API key, the billed request itself.
|
|
5293
|
+
*
|
|
5294
|
+
* With a wallet nothing was paid, so `costUsd` is 0 (a free model). With an
|
|
5295
|
+
* API key the call is billed to the account and `costUsd` stays undefined.
|
|
5146
5296
|
*/
|
|
5147
5297
|
async parseUnpaidChatResponse(response) {
|
|
5298
|
+
let served;
|
|
5148
5299
|
try {
|
|
5149
|
-
|
|
5300
|
+
served = await this.parseChatResponse(response);
|
|
5150
5301
|
} catch (err) {
|
|
5151
5302
|
throw withDisposition(err, this.apiAuth ? "paid-or-in-doubt" : "unpaid");
|
|
5152
5303
|
}
|
|
5304
|
+
if (!this.apiAuth) {
|
|
5305
|
+
served.costUsd = 0;
|
|
5306
|
+
served.settlement = { scheme: "free", quotedUsd: 0 };
|
|
5307
|
+
}
|
|
5308
|
+
return served;
|
|
5153
5309
|
}
|
|
5154
5310
|
/**
|
|
5155
5311
|
* Handle 402 response: parse requirements, sign payment, retry.
|
|
@@ -5157,7 +5313,7 @@ var LLMClient = class _LLMClient {
|
|
|
5157
5313
|
* Anything thrown before the signed payment is sent is `"unpaid"`; anything
|
|
5158
5314
|
* after it, `"paid-or-in-doubt"` (see {@link RetryDisposition}).
|
|
5159
5315
|
*/
|
|
5160
|
-
async handlePaymentAndRetry(url, body, response) {
|
|
5316
|
+
async handlePaymentAndRetry(url, body, response, timeoutMs) {
|
|
5161
5317
|
let signed;
|
|
5162
5318
|
try {
|
|
5163
5319
|
signed = await this.signFrom402(url, response);
|
|
@@ -5165,7 +5321,7 @@ var LLMClient = class _LLMClient {
|
|
|
5165
5321
|
throw withDisposition(err, "unpaid");
|
|
5166
5322
|
}
|
|
5167
5323
|
try {
|
|
5168
|
-
return await this.sendPaid(url, body, signed.paymentPayload, signed.details);
|
|
5324
|
+
return await this.sendPaid(url, body, signed.paymentPayload, signed.details, timeoutMs);
|
|
5169
5325
|
} catch (err) {
|
|
5170
5326
|
throw withDisposition(err, "paid-or-in-doubt");
|
|
5171
5327
|
}
|
|
@@ -5210,7 +5366,7 @@ var LLMClient = class _LLMClient {
|
|
|
5210
5366
|
return { paymentPayload, details };
|
|
5211
5367
|
}
|
|
5212
5368
|
/** Send the signed payment and read the answer. The payment may settle from here on. */
|
|
5213
|
-
async sendPaid(url, body, paymentPayload, details) {
|
|
5369
|
+
async sendPaid(url, body, paymentPayload, details, timeoutMs) {
|
|
5214
5370
|
const retryResponse = await this.fetchWithTimeout(url, {
|
|
5215
5371
|
method: "POST",
|
|
5216
5372
|
headers: {
|
|
@@ -5219,7 +5375,7 @@ var LLMClient = class _LLMClient {
|
|
|
5219
5375
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
5220
5376
|
},
|
|
5221
5377
|
body: JSON.stringify(body)
|
|
5222
|
-
});
|
|
5378
|
+
}, timeoutMs);
|
|
5223
5379
|
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
5224
5380
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
5225
5381
|
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
@@ -5230,7 +5386,7 @@ var LLMClient = class _LLMClient {
|
|
|
5230
5386
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
5231
5387
|
},
|
|
5232
5388
|
body: JSON.stringify(body)
|
|
5233
|
-
});
|
|
5389
|
+
}, timeoutMs);
|
|
5234
5390
|
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
5235
5391
|
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
5236
5392
|
if (!retryResp2.ok) {
|
|
@@ -5242,11 +5398,7 @@ var LLMClient = class _LLMClient {
|
|
|
5242
5398
|
}
|
|
5243
5399
|
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
5244
5400
|
}
|
|
5245
|
-
|
|
5246
|
-
this.sessionCalls += 1;
|
|
5247
|
-
this.sessionTotalUsd += costUsd2;
|
|
5248
|
-
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
5249
|
-
return this.parseChatResponse(retryResp2);
|
|
5401
|
+
return this.bookPaid(url, body, details, retryResp2);
|
|
5250
5402
|
}
|
|
5251
5403
|
}
|
|
5252
5404
|
if (retryResponse.status === 402) {
|
|
@@ -5265,11 +5417,22 @@ var LLMClient = class _LLMClient {
|
|
|
5265
5417
|
sanitizeErrorResponse(errorBody)
|
|
5266
5418
|
);
|
|
5267
5419
|
}
|
|
5420
|
+
return this.bookPaid(url, body, details, retryResponse);
|
|
5421
|
+
}
|
|
5422
|
+
/**
|
|
5423
|
+
* Book a served, paid chat call and attach its cost. A response the gateway
|
|
5424
|
+
* served from a free fallback with settlement skipped
|
|
5425
|
+
* (`X-Settlement-Skipped: free-fallback`) moved nothing on chain, so it is
|
|
5426
|
+
* not added to `getSpending()` or the cost log, and reports `costUsd: 0`.
|
|
5427
|
+
*/
|
|
5428
|
+
async bookPaid(url, body, details, response) {
|
|
5268
5429
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5430
|
+
if (!settlementSkipped(response)) {
|
|
5431
|
+
this.sessionCalls += 1;
|
|
5432
|
+
this.sessionTotalUsd += costUsd;
|
|
5433
|
+
this.recordCost(url, costUsd, { body, network: details.network });
|
|
5434
|
+
}
|
|
5435
|
+
return withCost(await this.parseChatResponse(response), response, costUsd, "exact");
|
|
5273
5436
|
}
|
|
5274
5437
|
/**
|
|
5275
5438
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
@@ -5339,7 +5502,7 @@ var LLMClient = class _LLMClient {
|
|
|
5339
5502
|
};
|
|
5340
5503
|
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
5341
5504
|
if (options?.topP !== void 0) body.top_p = options.topP;
|
|
5342
|
-
if (options?.tools !== void 0) body.tools = options.tools;
|
|
5505
|
+
if (options?.tools !== void 0) body.tools = toWireTools(options.tools);
|
|
5343
5506
|
if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
5344
5507
|
if (options?.responseFormat !== void 0) body.response_format = options.responseFormat;
|
|
5345
5508
|
if (options?.stop !== void 0) body.stop = options.stop;
|
|
@@ -5737,11 +5900,11 @@ var LLMClient = class _LLMClient {
|
|
|
5737
5900
|
return retryResponse.json();
|
|
5738
5901
|
}
|
|
5739
5902
|
/**
|
|
5740
|
-
* Fetch with timeout
|
|
5903
|
+
* Fetch with timeout: `timeoutMs` (a per-call override), else the client's `timeout`.
|
|
5741
5904
|
*/
|
|
5742
|
-
async fetchWithTimeout(url, options) {
|
|
5905
|
+
async fetchWithTimeout(url, options, timeoutMs = this.timeout) {
|
|
5743
5906
|
const controller = new AbortController();
|
|
5744
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
5907
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
5745
5908
|
try {
|
|
5746
5909
|
const response = await (this.apiAuth ? this.apiAuth.fetch.bind(this.apiAuth) : fetch)(url, {
|
|
5747
5910
|
...options,
|
|
@@ -9726,6 +9889,7 @@ var import_bs582 = __toESM(require("bs58"), 1);
|
|
|
9726
9889
|
var BATCH_SCHEME = "batch-settlement";
|
|
9727
9890
|
var BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
|
|
9728
9891
|
var DEFAULT_BATCH_RATE_LIMIT = { maxAttempts: 3, maxWaitMs: 6e4 };
|
|
9892
|
+
var DEFAULT_BATCH_IN_DOUBT = { maxWaitMs: 3e5 };
|
|
9729
9893
|
var BatchPaymentUnresolvedError = class extends PaymentError {
|
|
9730
9894
|
/** The {@link SolanaBatchEvent.reason} of the `unresolved` event reported with it. */
|
|
9731
9895
|
reason;
|
|
@@ -10402,6 +10566,46 @@ function withoutClientSignedBatch(paymentRequired) {
|
|
|
10402
10566
|
const accepts = paymentRequired.accepts ?? [];
|
|
10403
10567
|
return accepts.some(clientSignedBatch) ? { ...paymentRequired, accepts: accepts.filter((accept) => !clientSignedBatch(accept)) } : paymentRequired;
|
|
10404
10568
|
}
|
|
10569
|
+
var PAYMENT_IDENTIFIER = "payment-identifier";
|
|
10570
|
+
function withPaymentIdentifier(paymentRequired) {
|
|
10571
|
+
const declared = paymentRequired.extensions?.[PAYMENT_IDENTIFIER];
|
|
10572
|
+
if (typeof declared?.info?.required !== "boolean") return paymentRequired;
|
|
10573
|
+
const id = `pay_${(0, import_crypto2.randomUUID)().replace(/-/g, "")}`;
|
|
10574
|
+
return {
|
|
10575
|
+
...paymentRequired,
|
|
10576
|
+
extensions: { ...paymentRequired.extensions, [PAYMENT_IDENTIFIER]: { ...declared, info: { ...declared.info, id } } }
|
|
10577
|
+
};
|
|
10578
|
+
}
|
|
10579
|
+
function peekReceipt(http, getHeader) {
|
|
10580
|
+
try {
|
|
10581
|
+
return http.getPaymentSettleResponse(getHeader);
|
|
10582
|
+
} catch {
|
|
10583
|
+
return void 0;
|
|
10584
|
+
}
|
|
10585
|
+
}
|
|
10586
|
+
function receiptSucceeded(http, answer) {
|
|
10587
|
+
return peekReceipt(http, (name) => answer.headers.get(name))?.success === true;
|
|
10588
|
+
}
|
|
10589
|
+
function hasPaymentIdentifier(payload) {
|
|
10590
|
+
const info = payload.extensions?.[PAYMENT_IDENTIFIER]?.info;
|
|
10591
|
+
return typeof info?.id === "string";
|
|
10592
|
+
}
|
|
10593
|
+
var IN_DOUBT_MIN_WAIT_MS = 1e3;
|
|
10594
|
+
var IN_DOUBT_MAX_WAIT_MS = 3e4;
|
|
10595
|
+
function replayMayResolve(doubt) {
|
|
10596
|
+
if (doubt.unanswered) return true;
|
|
10597
|
+
const answer = doubt.held;
|
|
10598
|
+
if (!answer) return false;
|
|
10599
|
+
return answer.status >= 500 || answer.status === 429 || answer.status === 409 && Boolean(answer.headers.get("retry-after")?.trim());
|
|
10600
|
+
}
|
|
10601
|
+
function inDoubtWait(doubt, replay) {
|
|
10602
|
+
const asked = doubt.held ? parseRetryAfter(doubt.held.headers.get("retry-after")) : void 0;
|
|
10603
|
+
return Math.min(IN_DOUBT_MAX_WAIT_MS, Math.max(IN_DOUBT_MIN_WAIT_MS, asked ?? backoffDelay(replay)));
|
|
10604
|
+
}
|
|
10605
|
+
function replayDoubt(outcome) {
|
|
10606
|
+
if (outcome.kind === "charged" || outcome.kind === "in_doubt") return outcome;
|
|
10607
|
+
return { kind: "in_doubt", reason: "replay_unresolved", replayable: false, settled: true, detail: "the replay was refused" };
|
|
10608
|
+
}
|
|
10405
10609
|
function trustedBatchAccepts(paymentRequired, operators) {
|
|
10406
10610
|
return (paymentRequired.accepts ?? []).filter((candidate) => {
|
|
10407
10611
|
const extra = candidate.extra;
|
|
@@ -10411,9 +10615,6 @@ function trustedBatchAccepts(paymentRequired, operators) {
|
|
|
10411
10615
|
function trustedBatchAccept(paymentRequired, operators) {
|
|
10412
10616
|
return trustedBatchAccepts(paymentRequired, operators)[0];
|
|
10413
10617
|
}
|
|
10414
|
-
function atomicToUsd(amount) {
|
|
10415
|
-
return typeof amount === "string" && /^\d+$/.test(amount) ? Number(amount) / 1e6 : void 0;
|
|
10416
|
-
}
|
|
10417
10618
|
function chargedUsd(getReceipt) {
|
|
10418
10619
|
let receipt;
|
|
10419
10620
|
try {
|
|
@@ -10421,7 +10622,7 @@ function chargedUsd(getReceipt) {
|
|
|
10421
10622
|
} catch {
|
|
10422
10623
|
return 0;
|
|
10423
10624
|
}
|
|
10424
|
-
return
|
|
10625
|
+
return receiptChargedUsd(receipt) ?? 0;
|
|
10425
10626
|
}
|
|
10426
10627
|
function rebuiltCharge(settled, payload) {
|
|
10427
10628
|
if (settled?.success !== true) return 0n;
|
|
@@ -10541,13 +10742,42 @@ function parseRetryAfter(value, now = Date.now()) {
|
|
|
10541
10742
|
}
|
|
10542
10743
|
return Number.isFinite(ms) && ms <= MAX_RETRY_AFTER_MS ? ms : void 0;
|
|
10543
10744
|
}
|
|
10544
|
-
var
|
|
10745
|
+
var NOT_BROADCAST_REFUSALS = /* @__PURE__ */ new Set([
|
|
10746
|
+
// 429
|
|
10545
10747
|
"batch_account_channel_capacity_exhausted",
|
|
10546
10748
|
"batch_channel_capacity_exhausted",
|
|
10547
|
-
"batch_deposit_rate_limited"
|
|
10749
|
+
"batch_deposit_rate_limited",
|
|
10750
|
+
"batch_deposit_ip_rate_limited",
|
|
10751
|
+
// 503
|
|
10752
|
+
"batch_signer_funding_headroom_exhausted",
|
|
10753
|
+
"batch_new_channels_disabled",
|
|
10754
|
+
"batch_public_new_channels_disabled",
|
|
10755
|
+
// 403
|
|
10756
|
+
"batch_account_admission_paused",
|
|
10757
|
+
"batch_channel_owner_mismatch",
|
|
10758
|
+
"batch_fee_payer_unavailable",
|
|
10759
|
+
"batch_fee_payer_not_dedicated",
|
|
10760
|
+
"batch_fee_payer_lane_not_authorized",
|
|
10761
|
+
"batch_transaction_fee_payer_mismatch",
|
|
10762
|
+
// 400
|
|
10763
|
+
"batch_initial_deposit_out_of_range",
|
|
10764
|
+
"batch_deposit_asset_not_supported",
|
|
10765
|
+
"batch_withdraw_delay_out_of_range"
|
|
10548
10766
|
]);
|
|
10549
10767
|
function provesNothingBroadcast(receipt) {
|
|
10550
|
-
return receipt?.success === false && !receipt.transaction && typeof receipt.errorReason === "string" &&
|
|
10768
|
+
return receipt?.success === false && !receipt.transaction && typeof receipt.errorReason === "string" && NOT_BROADCAST_REFUSALS.has(receipt.errorReason);
|
|
10769
|
+
}
|
|
10770
|
+
var CONFIG_REFUSALS = /* @__PURE__ */ new Set([
|
|
10771
|
+
"batch_initial_deposit_out_of_range",
|
|
10772
|
+
"batch_deposit_asset_not_supported",
|
|
10773
|
+
"batch_withdraw_delay_out_of_range"
|
|
10774
|
+
]);
|
|
10775
|
+
var REFUSAL_COOLDOWN_MS = 6e4;
|
|
10776
|
+
var CONFIG_REFUSAL_COOLDOWN_MS = 10 * 6e4;
|
|
10777
|
+
var REFUSAL_COOLDOWN_MAX_MS = 15 * 6e4;
|
|
10778
|
+
function refusalCooldownMs(errorReason, retryAfterMs2) {
|
|
10779
|
+
if (retryAfterMs2 !== void 0) return Math.min(retryAfterMs2, REFUSAL_COOLDOWN_MAX_MS);
|
|
10780
|
+
return errorReason !== void 0 && CONFIG_REFUSALS.has(errorReason) ? CONFIG_REFUSAL_COOLDOWN_MS : REFUSAL_COOLDOWN_MS;
|
|
10551
10781
|
}
|
|
10552
10782
|
function backoffDelay(attempt) {
|
|
10553
10783
|
const base = 1e3 * 2 ** Math.max(0, attempt - 1);
|
|
@@ -10680,7 +10910,7 @@ function intentTarget(intent) {
|
|
|
10680
10910
|
var SolanaBatchPayer = class {
|
|
10681
10911
|
constructor(init) {
|
|
10682
10912
|
this.init = init;
|
|
10683
|
-
const { operators, rateLimit } = init.options;
|
|
10913
|
+
const { operators, rateLimit, inDoubt } = init.options;
|
|
10684
10914
|
if (!Array.isArray(operators) || operators.length === 0 || operators.some((o) => typeof o !== "string" || !o)) {
|
|
10685
10915
|
throw new Error("batch.operators must list at least one base58 operator public key");
|
|
10686
10916
|
}
|
|
@@ -10692,11 +10922,16 @@ var SolanaBatchPayer = class {
|
|
|
10692
10922
|
if (!Number.isFinite(this.maxWaitMs) || this.maxWaitMs < 0) {
|
|
10693
10923
|
throw new Error("batch.rateLimit.maxWaitMs must be a finite number of ms >= 0");
|
|
10694
10924
|
}
|
|
10925
|
+
this.inDoubtMaxWaitMs = inDoubt?.maxWaitMs ?? DEFAULT_BATCH_IN_DOUBT.maxWaitMs;
|
|
10926
|
+
if (!Number.isFinite(this.inDoubtMaxWaitMs) || this.inDoubtMaxWaitMs < 0) {
|
|
10927
|
+
throw new Error("batch.inDoubt.maxWaitMs must be a finite number of ms >= 0");
|
|
10928
|
+
}
|
|
10695
10929
|
}
|
|
10696
10930
|
init;
|
|
10697
10931
|
state;
|
|
10698
10932
|
maxAttempts;
|
|
10699
10933
|
maxWaitMs;
|
|
10934
|
+
inDoubtMaxWaitMs;
|
|
10700
10935
|
counters = {
|
|
10701
10936
|
fallbacks: 0,
|
|
10702
10937
|
fallbacksByReason: {},
|
|
@@ -11065,14 +11300,19 @@ var SolanaBatchPayer = class {
|
|
|
11065
11300
|
* the call's result (`served`, nothing paid); one that fails otherwise
|
|
11066
11301
|
* is the call's error (`failed`, `"unpaid"`), never an exact payment
|
|
11067
11302
|
* against the stale challenge;
|
|
11068
|
-
* - in doubt: ends only with a definitive success receipt from
|
|
11069
|
-
*
|
|
11070
|
-
*
|
|
11071
|
-
*
|
|
11072
|
-
*
|
|
11303
|
+
* - in doubt: ends only with a definitive success receipt from a replay
|
|
11304
|
+
* of the identical payment ({@link resolveInDoubt}: the one replay a
|
|
11305
|
+
* receipt-less 429 is allowed, or the replays of a payment carrying a
|
|
11306
|
+
* payment identifier while the gateway says one can succeed, within
|
|
11307
|
+
* `inDoubt.maxWaitMs`), or a thrown {@link BatchPaymentUnresolvedError}.
|
|
11308
|
+
* No new authorization, no new deposit, no exact payment and no fallback
|
|
11309
|
+
* model is ever paid for a call in doubt.
|
|
11073
11310
|
*
|
|
11074
11311
|
* The wallet stays in cooldown after a 429, so its other calls wait
|
|
11075
|
-
* instead of sending opens of their own.
|
|
11312
|
+
* instead of sending opens of their own. After a deposit the facilitator
|
|
11313
|
+
* refused before broadcasting it (a 402 with a failed `batch_*` admission
|
|
11314
|
+
* receipt), its calls do not wait: they pay exact at once
|
|
11315
|
+
* (`admission_refused`) until the refusal cooldown ends.
|
|
11076
11316
|
*
|
|
11077
11317
|
* @param closesAtStart - {@link closeFence}, taken before the call's first
|
|
11078
11318
|
* (unpaid) request. If a close completed since, the call pays exact
|
|
@@ -11136,6 +11376,15 @@ var SolanaBatchPayer = class {
|
|
|
11136
11376
|
try {
|
|
11137
11377
|
for (; ; ) {
|
|
11138
11378
|
const now = Date.now();
|
|
11379
|
+
if ((wallet.refusedUntil ?? 0) > now) {
|
|
11380
|
+
return fallback({
|
|
11381
|
+
reason: "admission_refused",
|
|
11382
|
+
errorReason: wallet.refusedFor,
|
|
11383
|
+
retryAfterMs: (wallet.refusedUntil ?? 0) - now,
|
|
11384
|
+
attempt: budget.attempt,
|
|
11385
|
+
detail: `the facilitator refused this wallet's last batch deposit (${wallet.refusedFor ?? "unknown"}); batch is skipped until the refusal cooldown ends`
|
|
11386
|
+
});
|
|
11387
|
+
}
|
|
11139
11388
|
if (wallet.cooldownUntil > now && wallet.cooldownUntil !== waitedUntil) {
|
|
11140
11389
|
const wait = wallet.cooldownUntil - now;
|
|
11141
11390
|
if (budget.waited + wait > this.maxWaitMs) {
|
|
@@ -11225,12 +11474,23 @@ var SolanaBatchPayer = class {
|
|
|
11225
11474
|
if (built.kind !== "authorization") {
|
|
11226
11475
|
if (outcome.depositSafe) this.forgetIntent(wallet, built.key);
|
|
11227
11476
|
else this.distrust(wallet, built, "deposit_refused");
|
|
11477
|
+
if (outcome.refusedForMs !== void 0) {
|
|
11478
|
+
const until = Date.now() + outcome.refusedForMs;
|
|
11479
|
+
if (until > (wallet.refusedUntil ?? 0)) {
|
|
11480
|
+
wallet.refusedUntil = until;
|
|
11481
|
+
wallet.refusedFor = outcome.errorReason;
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11228
11484
|
}
|
|
11229
11485
|
sent = void 0;
|
|
11230
11486
|
release();
|
|
11231
11487
|
if (!outcome.rateLimited) {
|
|
11232
|
-
const { kind: _kind, rateLimited: _limited, depositSafe: _safe, ...event } = outcome;
|
|
11233
|
-
return fallback({
|
|
11488
|
+
const { kind: _kind, rateLimited: _limited, depositSafe: _safe, refusedForMs, ...event } = outcome;
|
|
11489
|
+
return fallback({
|
|
11490
|
+
...event,
|
|
11491
|
+
...refusedForMs !== void 0 && built.kind !== "authorization" ? { retryAfterMs: refusedForMs } : {},
|
|
11492
|
+
attempt: budget.attempt
|
|
11493
|
+
});
|
|
11234
11494
|
}
|
|
11235
11495
|
const wait = this.coolDown(wallet, outcome.rateLimited.retryAfterMs ?? backoffDelay(budget.attempt));
|
|
11236
11496
|
if (budget.attempt >= this.maxAttempts || budget.waited + wait > this.maxWaitMs) {
|
|
@@ -11276,7 +11536,7 @@ var SolanaBatchPayer = class {
|
|
|
11276
11536
|
if (built.kind !== "authorization" && !(built.key && wallet.resyncs.has(built.key))) this.forgetIntent(wallet, built.key);
|
|
11277
11537
|
sent = void 0;
|
|
11278
11538
|
release();
|
|
11279
|
-
await this.report({ type: "recovered", reason: "rate_limited", attempt: budget.attempt });
|
|
11539
|
+
await this.report({ type: "recovered", reason: outcome.status === 429 ? "rate_limited" : "in_doubt", attempt: budget.attempt });
|
|
11280
11540
|
return { kind: "paid", response: resolved.response, chargedUsd: resolved.chargedUsd };
|
|
11281
11541
|
}
|
|
11282
11542
|
return await this.raiseUnresolved(wallet, built, resolved, budget.attempt);
|
|
@@ -11325,21 +11585,24 @@ var SolanaBatchPayer = class {
|
|
|
11325
11585
|
/**
|
|
11326
11586
|
* Try to end a payment in doubt with the gateway's own word.
|
|
11327
11587
|
*
|
|
11328
|
-
* The one source of evidence this SDK accepts
|
|
11329
|
-
*
|
|
11330
|
-
*
|
|
11331
|
-
*
|
|
11332
|
-
*
|
|
11333
|
-
*
|
|
11588
|
+
* The one source of evidence this SDK accepts is a definitive success
|
|
11589
|
+
* receipt on a byte-identical replay of the payment. Any other answer to a
|
|
11590
|
+
* replay (a 402 or `duplicate_settlement`, which mean the original reached
|
|
11591
|
+
* the gateway; a 409; another 429; a 5xx; a timeout) leaves it in doubt.
|
|
11592
|
+
*
|
|
11593
|
+
* - A payment carrying a payment identifier is replayed for as long as
|
|
11594
|
+
* the gateway says a later replay can succeed ({@link replayInDoubt}).
|
|
11595
|
+
* - Any other payment gets a replay only after a 429 without a receipt:
|
|
11596
|
+
* one, after its backoff (the owner's bounded safety attempt), within
|
|
11597
|
+
* `rateLimit`. Every other in-doubt outcome is never replayed.
|
|
11334
11598
|
*
|
|
11335
11599
|
* Chain state is not evidence here: it can settle a deposit (see
|
|
11336
|
-
* `resync`), never whether the gateway charged.
|
|
11337
|
-
* receipt on every response, a request-status endpoint with a fence)
|
|
11338
|
-
* plugs in here, before the caller raises.
|
|
11600
|
+
* `resync`), never whether the gateway charged.
|
|
11339
11601
|
*
|
|
11340
11602
|
* @returns the charged replay, or the doubt to raise.
|
|
11341
11603
|
*/
|
|
11342
11604
|
async resolveInDoubt(wallet, sent, doubt, send, budget) {
|
|
11605
|
+
if (hasPaymentIdentifier(sent.payload)) return this.replayInDoubt(wallet, sent, doubt, send, budget.attempt);
|
|
11343
11606
|
if (!doubt.replayable) return doubt;
|
|
11344
11607
|
const wait = this.coolDown(wallet, doubt.retryAfterMs ?? backoffDelay(budget.attempt));
|
|
11345
11608
|
if (budget.attempt >= this.maxAttempts || budget.waited + wait > this.maxWaitMs) {
|
|
@@ -11359,9 +11622,94 @@ var SolanaBatchPayer = class {
|
|
|
11359
11622
|
budget.waited += wait;
|
|
11360
11623
|
await sleep2(wait);
|
|
11361
11624
|
budget.attempt += 1;
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11625
|
+
return replayDoubt(await this.sendOnce(wallet, sent, send, true, true));
|
|
11626
|
+
}
|
|
11627
|
+
/**
|
|
11628
|
+
* Replay a payment in doubt that carries a payment identifier until the
|
|
11629
|
+
* gateway's answer settles it.
|
|
11630
|
+
*
|
|
11631
|
+
* A gateway that declares `payment-identifier` stores each paid response
|
|
11632
|
+
* under its identifier and answers a byte-identical replay of a completed
|
|
11633
|
+
* request with it, the original receipt included; while the original is
|
|
11634
|
+
* still running, it answers 409 with a `Retry-After`. So the identical
|
|
11635
|
+
* payment (the same `PAYMENT-SIGNATURE` header and the same body) is
|
|
11636
|
+
* replayed for as long as the last answer says a later one can succeed
|
|
11637
|
+
* ({@link replayMayResolve}), after {@link inDoubtWait} each time, as long
|
|
11638
|
+
* as the wait fits in `inDoubt.maxWaitMs`, counted from the first doubt:
|
|
11639
|
+
* every wait (as long as it really took) and every replay's round trip. A
|
|
11640
|
+
* replay already sent is not cut short. These replays do not count
|
|
11641
|
+
* against `rateLimit`; a 429 among them still puts the wallet in cooldown.
|
|
11642
|
+
*
|
|
11643
|
+
* Every answer that does not settle it is held back from the scheme
|
|
11644
|
+
* ({@link InDoubt.held}) and handed over only once no replay follows, so
|
|
11645
|
+
* the scheme rolls the payment back once, or takes the receipt that answer
|
|
11646
|
+
* carries. That is the last answer, unless an earlier one carried a
|
|
11647
|
+
* success receipt and the later ones do not: a success receipt is the
|
|
11648
|
+
* gateway's word that it charged, and handing the scheme a receipt-less
|
|
11649
|
+
* refusal of a replay instead would roll a real charge back and desync the
|
|
11650
|
+
* channel's cumulative.
|
|
11651
|
+
*
|
|
11652
|
+
* @returns the charged replay, or the doubt to raise.
|
|
11653
|
+
*/
|
|
11654
|
+
async replayInDoubt(wallet, sent, first, send, attempt) {
|
|
11655
|
+
let doubt = first;
|
|
11656
|
+
let held = first.held;
|
|
11657
|
+
let heldSucceeded = held !== void 0 && receiptSucceeded(sent.http, held);
|
|
11658
|
+
let replays = 0;
|
|
11659
|
+
let spent = 0;
|
|
11660
|
+
const stop = async (note) => {
|
|
11661
|
+
if (!doubt.settled && held) {
|
|
11662
|
+
const answer = held;
|
|
11663
|
+
await this.settle(sent.http, sent.payload, (name) => answer.headers.get(name), answer.status);
|
|
11664
|
+
}
|
|
11665
|
+
const notes = [replays > 0 ? `${replays} replay${replays === 1 ? "" : "s"} did not resolve it` : "", note ?? ""];
|
|
11666
|
+
return {
|
|
11667
|
+
...doubt,
|
|
11668
|
+
settled: doubt.settled || held !== void 0,
|
|
11669
|
+
held: void 0,
|
|
11670
|
+
firstReason: first.reason,
|
|
11671
|
+
detail: [doubt.detail, ...notes].filter(Boolean).join("; ")
|
|
11672
|
+
};
|
|
11673
|
+
};
|
|
11674
|
+
for (; ; ) {
|
|
11675
|
+
if (!replayMayResolve(doubt)) {
|
|
11676
|
+
return stop(doubt.held?.status === 409 ? "the gateway sent no Retry-After, so no replay can resolve it" : void 0);
|
|
11677
|
+
}
|
|
11678
|
+
const wait = inDoubtWait(doubt, replays + 1);
|
|
11679
|
+
const status2 = doubt.held?.status;
|
|
11680
|
+
if (status2 === 429) this.coolDown(wallet, (doubt.held && parseRetryAfter(doubt.held.headers.get("retry-after"))) ?? wait);
|
|
11681
|
+
if (spent + wait > this.inDoubtMaxWaitMs) {
|
|
11682
|
+
return stop(`batch.inDoubt.maxWaitMs (${this.inDoubtMaxWaitMs} ms) leaves no room for ${replays > 0 ? "another replay" : "a replay"}`);
|
|
11683
|
+
}
|
|
11684
|
+
await this.report({
|
|
11685
|
+
type: "backoff",
|
|
11686
|
+
reason: status2 === 429 ? "rate_limited" : "in_doubt",
|
|
11687
|
+
status: status2,
|
|
11688
|
+
errorReason: doubt.errorReason,
|
|
11689
|
+
retryAfterMs: wait,
|
|
11690
|
+
attempt,
|
|
11691
|
+
detail: `replay ${replays + 1} of the payment in doubt`
|
|
11692
|
+
});
|
|
11693
|
+
const sleptFrom = Date.now();
|
|
11694
|
+
await sleep2(wait);
|
|
11695
|
+
spent += Math.max(wait, Date.now() - sleptFrom);
|
|
11696
|
+
replays += 1;
|
|
11697
|
+
const startedAt = Date.now();
|
|
11698
|
+
const replayed = replayDoubt(await this.sendOnce(wallet, sent, send, true, true));
|
|
11699
|
+
spent += Math.max(0, Date.now() - startedAt);
|
|
11700
|
+
if (replayed.kind === "charged") return replayed;
|
|
11701
|
+
doubt = replayed;
|
|
11702
|
+
if (doubt.held) {
|
|
11703
|
+
const succeeded = receiptSucceeded(sent.http, doubt.held);
|
|
11704
|
+
if (succeeded || !heldSucceeded) {
|
|
11705
|
+
held = doubt.held;
|
|
11706
|
+
heldSucceeded = succeeded;
|
|
11707
|
+
}
|
|
11708
|
+
} else if (doubt.settled) {
|
|
11709
|
+
held = void 0;
|
|
11710
|
+
heldSucceeded = false;
|
|
11711
|
+
}
|
|
11712
|
+
}
|
|
11365
11713
|
}
|
|
11366
11714
|
/**
|
|
11367
11715
|
* Raise a payment in doubt: release the scheme's hold on it (the channel
|
|
@@ -11372,10 +11720,11 @@ var SolanaBatchPayer = class {
|
|
|
11372
11720
|
async raiseUnresolved(wallet, sent, doubt, attempt) {
|
|
11373
11721
|
if (!doubt.settled) await this.settle(sent.http, sent.payload, () => null, 0);
|
|
11374
11722
|
if (sent.kind !== "authorization") {
|
|
11723
|
+
const reason = doubt.firstReason ?? doubt.reason;
|
|
11375
11724
|
this.distrust(
|
|
11376
11725
|
wallet,
|
|
11377
11726
|
sent,
|
|
11378
|
-
|
|
11727
|
+
reason === "no_response" ? "deposit_unanswered" : reason === "replay_unresolved" ? "deposit_rate_limited" : "deposit_failed"
|
|
11379
11728
|
);
|
|
11380
11729
|
}
|
|
11381
11730
|
await this.report({
|
|
@@ -11462,8 +11811,8 @@ var SolanaBatchPayer = class {
|
|
|
11462
11811
|
* here, and only for a first send.
|
|
11463
11812
|
*
|
|
11464
11813
|
* @param retry - count this send in `retries` (it follows this call's own 429).
|
|
11465
|
-
* @param replay - this is
|
|
11466
|
-
*
|
|
11814
|
+
* @param replay - this is a replay of a payment in doubt: only a 2xx with
|
|
11815
|
+
* a success receipt counts, everything else stays in doubt.
|
|
11467
11816
|
*/
|
|
11468
11817
|
async sendOnce(wallet, sent, send, retry, replay) {
|
|
11469
11818
|
if (retry) this.counters.retries += 1;
|
|
@@ -11477,6 +11826,7 @@ var SolanaBatchPayer = class {
|
|
|
11477
11826
|
reason: replay ? "replay_unresolved" : "no_response",
|
|
11478
11827
|
replayable: false,
|
|
11479
11828
|
settled: false,
|
|
11829
|
+
unanswered: true,
|
|
11480
11830
|
detail: `${replay ? "the replay" : "the payment"} got no answer (${err instanceof Error ? `${err.name}: ${err.message}` : String(err)})`,
|
|
11481
11831
|
cause: err
|
|
11482
11832
|
};
|
|
@@ -11501,6 +11851,7 @@ var SolanaBatchPayer = class {
|
|
|
11501
11851
|
const getHeader = (name) => response.headers.get(name);
|
|
11502
11852
|
const receipted = Boolean(getHeader("PAYMENT-RESPONSE") || getHeader("X-PAYMENT-RESPONSE"));
|
|
11503
11853
|
const status2 = response.status;
|
|
11854
|
+
const hold = hasPaymentIdentifier(payload) && (!response.ok || replay && peekReceipt(http, getHeader)?.success !== true);
|
|
11504
11855
|
if (status2 === 429 && !receipted) {
|
|
11505
11856
|
return {
|
|
11506
11857
|
kind: "in_doubt",
|
|
@@ -11510,12 +11861,27 @@ var SolanaBatchPayer = class {
|
|
|
11510
11861
|
status: status2,
|
|
11511
11862
|
retryAfterMs: parseRetryAfter(getHeader("retry-after")),
|
|
11512
11863
|
errorReason: await rateLimitReason(response, void 0),
|
|
11513
|
-
detail: replay ? "the replay was answered 429 without a receipt" : "a 429 without a receipt"
|
|
11864
|
+
detail: replay ? "the replay was answered 429 without a receipt" : "a 429 without a receipt",
|
|
11865
|
+
...hold ? { held: response } : {}
|
|
11514
11866
|
};
|
|
11515
11867
|
}
|
|
11868
|
+
const outcome = await this.classifyAnswer(wallet, sent, response, replay, hold);
|
|
11869
|
+
if (!hold) return outcome;
|
|
11870
|
+
if (outcome.kind === "in_doubt") return { ...outcome, settled: false, held: response };
|
|
11871
|
+
await this.settle(http, payload, getHeader, status2);
|
|
11872
|
+
return outcome;
|
|
11873
|
+
}
|
|
11874
|
+
/**
|
|
11875
|
+
* {@link classify}, past a receipt-less 429. With `hold`, the answer is not
|
|
11876
|
+
* handed to the scheme: its receipt is only read.
|
|
11877
|
+
*/
|
|
11878
|
+
async classifyAnswer(wallet, sent, response, replay, hold) {
|
|
11879
|
+
const { http, payload } = sent;
|
|
11880
|
+
const getHeader = (name) => response.headers.get(name);
|
|
11881
|
+
const status2 = response.status;
|
|
11516
11882
|
const channelId = payloadChannelId(payload);
|
|
11517
11883
|
const before = channelId ? confirmedState(wallet.book.find(channelId)?.record) : void 0;
|
|
11518
|
-
const reconciled = await this.settle(http, payload, getHeader, status2);
|
|
11884
|
+
const reconciled = hold ? { settleResponse: peekReceipt(http, getHeader), ok: false } : await this.settle(http, payload, getHeader, status2);
|
|
11519
11885
|
const receipt = reconciled.settleResponse;
|
|
11520
11886
|
const definitive = receipt?.success === true && reconciled.ok && recordAdvanced(before, channelId ? confirmedState(wallet.book.find(channelId)?.record) : void 0, payload, receipt);
|
|
11521
11887
|
if (!response.ok && !replay && sent.kind === "authorization" && cancelledReceipt(receipt)) {
|
|
@@ -11578,7 +11944,15 @@ var SolanaBatchPayer = class {
|
|
|
11578
11944
|
cause: await afterPaymentError(response)
|
|
11579
11945
|
};
|
|
11580
11946
|
}
|
|
11581
|
-
|
|
11947
|
+
const refused = provesNothingBroadcast(receipt);
|
|
11948
|
+
return {
|
|
11949
|
+
kind: "not_charged",
|
|
11950
|
+
reason: "payment_required",
|
|
11951
|
+
status: status2,
|
|
11952
|
+
errorReason: receipt?.errorReason,
|
|
11953
|
+
depositSafe: refused,
|
|
11954
|
+
...refused ? { refusedForMs: refusalCooldownMs(receipt?.errorReason, parseRetryAfter(getHeader("retry-after"))) } : {}
|
|
11955
|
+
};
|
|
11582
11956
|
}
|
|
11583
11957
|
const refusal = await batchRefusal(response);
|
|
11584
11958
|
if (refusal) {
|
|
@@ -11609,7 +11983,7 @@ var SolanaBatchPayer = class {
|
|
|
11609
11983
|
const http = (await this.build(wallet)).http;
|
|
11610
11984
|
try {
|
|
11611
11985
|
await this.prepareTopUp(wallet, paymentRequired);
|
|
11612
|
-
return { http, payload: await http.createPaymentPayload(withoutClientSignedBatch(paymentRequired)) };
|
|
11986
|
+
return { http, payload: await http.createPaymentPayload(withPaymentIdentifier(withoutClientSignedBatch(paymentRequired))) };
|
|
11613
11987
|
} catch (err) {
|
|
11614
11988
|
if (pass > 0 || !isResyncRequired(err) || wallet.resyncs.size === 0) throw err;
|
|
11615
11989
|
} finally {
|
|
@@ -11702,13 +12076,7 @@ var SolanaBatchPayer = class {
|
|
|
11702
12076
|
console.error(
|
|
11703
12077
|
`[@blockrun/llm] batch-settlement receipt not reconciled: ${JSON.stringify(err instanceof Error ? err.message : String(err))}`
|
|
11704
12078
|
);
|
|
11705
|
-
|
|
11706
|
-
try {
|
|
11707
|
-
settleResponse = http.getPaymentSettleResponse(getHeader);
|
|
11708
|
-
} catch {
|
|
11709
|
-
settleResponse = void 0;
|
|
11710
|
-
}
|
|
11711
|
-
return { settleResponse, ok: false };
|
|
12079
|
+
return { settleResponse: peekReceipt(http, getHeader), ok: false };
|
|
11712
12080
|
}
|
|
11713
12081
|
}
|
|
11714
12082
|
/**
|
|
@@ -12091,9 +12459,9 @@ var SolanaLLMClient = class {
|
|
|
12091
12459
|
};
|
|
12092
12460
|
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
12093
12461
|
if (options?.topP !== void 0) body.top_p = options.topP;
|
|
12094
|
-
if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
|
|
12462
|
+
if (options?.searchParameters !== void 0) body.search_parameters = toWireSearchParameters(options.searchParameters);
|
|
12095
12463
|
else if (options?.search === true) body.search_parameters = { mode: "on" };
|
|
12096
|
-
if (options?.tools !== void 0) body.tools = options.tools;
|
|
12464
|
+
if (options?.tools !== void 0) body.tools = toWireTools(options.tools);
|
|
12097
12465
|
if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
12098
12466
|
if (options?.responseFormat !== void 0) body.response_format = options.responseFormat;
|
|
12099
12467
|
if (options?.stop !== void 0) body.stop = options.stop;
|
|
@@ -12104,7 +12472,7 @@ var SolanaLLMClient = class {
|
|
|
12104
12472
|
for (let index = 0; index < chain.length; index += 1) {
|
|
12105
12473
|
const candidate = chain[index];
|
|
12106
12474
|
try {
|
|
12107
|
-
return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate));
|
|
12475
|
+
return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate), options?.timeout);
|
|
12108
12476
|
} catch (error) {
|
|
12109
12477
|
lastError = error;
|
|
12110
12478
|
if (!isTransientError(error) || index === chain.length - 1) throw error;
|
|
@@ -12154,6 +12522,8 @@ var SolanaLLMClient = class {
|
|
|
12154
12522
|
{
|
|
12155
12523
|
routingProfile: options?.routingProfile,
|
|
12156
12524
|
requiresStructuredOutput: options?.responseFormat !== void 0,
|
|
12525
|
+
search: options?.search,
|
|
12526
|
+
searchParameters: options?.searchParameters,
|
|
12157
12527
|
minimumPaymentUsd: this.apiAuth ? 0 : SOLANA_MINIMUM_PAYMENT_USD
|
|
12158
12528
|
}
|
|
12159
12529
|
);
|
|
@@ -12181,6 +12551,8 @@ var SolanaLLMClient = class {
|
|
|
12181
12551
|
requiresStructuredOutput: options.responseFormat !== void 0,
|
|
12182
12552
|
tools: options.tools,
|
|
12183
12553
|
toolChoice: options.toolChoice,
|
|
12554
|
+
search: options.search,
|
|
12555
|
+
searchParameters: options.searchParameters,
|
|
12184
12556
|
conversationChars,
|
|
12185
12557
|
hasVision,
|
|
12186
12558
|
minimumPaymentUsd: this.apiAuth ? 0 : SOLANA_MINIMUM_PAYMENT_USD
|
|
@@ -12196,6 +12568,7 @@ var SolanaLLMClient = class {
|
|
|
12196
12568
|
toolChoice: options.toolChoice,
|
|
12197
12569
|
responseFormat: options.responseFormat,
|
|
12198
12570
|
stop: options.stop,
|
|
12571
|
+
timeout: options.timeout,
|
|
12199
12572
|
// An explicit caller-supplied chain wins over the routed one.
|
|
12200
12573
|
fallbackModels: options.fallbackModels ?? decision.fallbacks
|
|
12201
12574
|
});
|
|
@@ -12333,7 +12706,7 @@ var SolanaLLMClient = class {
|
|
|
12333
12706
|
*
|
|
12334
12707
|
* @returns USDC balance as a float
|
|
12335
12708
|
*/
|
|
12336
|
-
async getBalance() {
|
|
12709
|
+
async getBalance(options) {
|
|
12337
12710
|
const usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
12338
12711
|
const address = await this.getWalletAddress();
|
|
12339
12712
|
try {
|
|
@@ -12348,6 +12721,9 @@ var SolanaLLMClient = class {
|
|
|
12348
12721
|
})
|
|
12349
12722
|
});
|
|
12350
12723
|
const data = await response.json();
|
|
12724
|
+
if (options?.strict && (!response.ok || data.error)) {
|
|
12725
|
+
throw new Error(`Solana RPC balance lookup failed: ${data.error?.message ?? `HTTP ${response.status}`}`);
|
|
12726
|
+
}
|
|
12351
12727
|
const accounts = data.result?.value || [];
|
|
12352
12728
|
if (!accounts.length) return 0;
|
|
12353
12729
|
let total = 0;
|
|
@@ -12355,7 +12731,8 @@ var SolanaLLMClient = class {
|
|
|
12355
12731
|
total += acct.account?.data?.parsed?.info?.tokenAmount?.uiAmount || 0;
|
|
12356
12732
|
}
|
|
12357
12733
|
return total;
|
|
12358
|
-
} catch {
|
|
12734
|
+
} catch (error) {
|
|
12735
|
+
if (options?.strict) throw error;
|
|
12359
12736
|
return 0;
|
|
12360
12737
|
}
|
|
12361
12738
|
}
|
|
@@ -12630,15 +13007,16 @@ var SolanaLLMClient = class {
|
|
|
12630
13007
|
* `"paid-or-in-doubt"`, and so is any error with an API key once the billed
|
|
12631
13008
|
* request was sent, except the account API's explicit 4xx refusal.
|
|
12632
13009
|
*/
|
|
12633
|
-
async requestWithPayment(endpoint, body) {
|
|
13010
|
+
async requestWithPayment(endpoint, body, timeoutMs) {
|
|
12634
13011
|
const url = `${this.apiUrl}${endpoint}`;
|
|
13012
|
+
const json = JSON.stringify(body);
|
|
12635
13013
|
const closesAtStart = await this.batchPayer?.closeFence();
|
|
12636
13014
|
for (let staleRetries = 0; ; ) {
|
|
12637
13015
|
const response = await this.sendUnpaid(url, {
|
|
12638
13016
|
method: "POST",
|
|
12639
13017
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
12640
|
-
body:
|
|
12641
|
-
});
|
|
13018
|
+
body: json
|
|
13019
|
+
}, timeoutMs);
|
|
12642
13020
|
if (response.status === 402) {
|
|
12643
13021
|
try {
|
|
12644
13022
|
let paymentRequired;
|
|
@@ -12654,16 +13032,16 @@ var SolanaLLMClient = class {
|
|
|
12654
13032
|
(paymentHeaders) => this.fetchWithTimeout(url, {
|
|
12655
13033
|
method: "POST",
|
|
12656
13034
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT, ...paymentHeaders },
|
|
12657
|
-
body:
|
|
12658
|
-
}),
|
|
13035
|
+
body: json
|
|
13036
|
+
}, timeoutMs),
|
|
12659
13037
|
// A fresh, unpaid challenge after a 429 wait: the request itself,
|
|
12660
13038
|
// sent again without a payment. Every error it throws is unpaid.
|
|
12661
13039
|
async () => {
|
|
12662
13040
|
const challenge = await this.sendUnpaid(url, {
|
|
12663
13041
|
method: "POST",
|
|
12664
13042
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
12665
|
-
body:
|
|
12666
|
-
});
|
|
13043
|
+
body: json
|
|
13044
|
+
}, timeoutMs);
|
|
12667
13045
|
if (challenge.status === 402) {
|
|
12668
13046
|
try {
|
|
12669
13047
|
return { kind: "challenge", paymentRequired: await this.readPaymentRequired(challenge) };
|
|
@@ -12678,13 +13056,14 @@ var SolanaLLMClient = class {
|
|
|
12678
13056
|
);
|
|
12679
13057
|
if (batch.kind === "paid") {
|
|
12680
13058
|
this.recordSettlement(batch.chargedUsd);
|
|
12681
|
-
|
|
13059
|
+
const paid = await this.readPaidJson(batch.response);
|
|
13060
|
+
return withCost(paid, batch.response, batch.chargedUsd, "batch");
|
|
12682
13061
|
}
|
|
12683
|
-
if (batch.kind === "served") return await this.readUnpaidJson(batch.response);
|
|
13062
|
+
if (batch.kind === "served") return this.withUnpaidCost(await this.readUnpaidJson(batch.response));
|
|
12684
13063
|
if (batch.kind === "failed") throw batch.error;
|
|
12685
13064
|
exactRequired = batch.paymentRequired ?? paymentRequired;
|
|
12686
13065
|
}
|
|
12687
|
-
return await this.handlePaymentAndRetry(url,
|
|
13066
|
+
return await this.handlePaymentAndRetry(url, json, exactRequired, staleRetries > 0, timeoutMs);
|
|
12688
13067
|
} catch (error) {
|
|
12689
13068
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
12690
13069
|
await waitForStaleRetry(staleRetries++);
|
|
@@ -12692,9 +13071,21 @@ var SolanaLLMClient = class {
|
|
|
12692
13071
|
}
|
|
12693
13072
|
}
|
|
12694
13073
|
if (!response.ok) throw await unpaidApiError(response);
|
|
12695
|
-
return await this.readUnpaidJson(response);
|
|
13074
|
+
return this.withUnpaidCost(await this.readUnpaidJson(response));
|
|
12696
13075
|
}
|
|
12697
13076
|
}
|
|
13077
|
+
/**
|
|
13078
|
+
* Cost of a chat response served without a payment: 0 with a wallet (a free
|
|
13079
|
+
* model). With an API key the call is billed to the account, so costUsd stays
|
|
13080
|
+
* undefined (see the dashboard).
|
|
13081
|
+
*/
|
|
13082
|
+
withUnpaidCost(served) {
|
|
13083
|
+
if (!this.apiAuth) {
|
|
13084
|
+
served.costUsd = 0;
|
|
13085
|
+
served.settlement = { scheme: "free", quotedUsd: 0 };
|
|
13086
|
+
}
|
|
13087
|
+
return served;
|
|
13088
|
+
}
|
|
12698
13089
|
/**
|
|
12699
13090
|
* Read the JSON body of a 2xx served without a payment: the free tier, or
|
|
12700
13091
|
* with an API key, where this very request was the billed one.
|
|
@@ -12711,9 +13102,9 @@ var SolanaLLMClient = class {
|
|
|
12711
13102
|
* {@link RetryDisposition}: `"unpaid"` with a wallet; with an API key, see
|
|
12712
13103
|
* {@link accountErrorDisposition} (the request itself is billed).
|
|
12713
13104
|
*/
|
|
12714
|
-
async sendUnpaid(url, init) {
|
|
13105
|
+
async sendUnpaid(url, init, timeoutMs) {
|
|
12715
13106
|
try {
|
|
12716
|
-
return await this.fetchWithTimeout(url, init);
|
|
13107
|
+
return await this.fetchWithTimeout(url, init, timeoutMs);
|
|
12717
13108
|
} catch (error) {
|
|
12718
13109
|
throw withDisposition(error, this.apiAuth ? accountErrorDisposition(error) : "unpaid");
|
|
12719
13110
|
}
|
|
@@ -12982,7 +13373,7 @@ var SolanaLLMClient = class {
|
|
|
12982
13373
|
if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
|
|
12983
13374
|
const fromAddress = await this.getWalletAddress();
|
|
12984
13375
|
const secretKey = await solanaKeyToBytes(this.privateKey);
|
|
12985
|
-
const extensions = paymentRequired.extensions;
|
|
13376
|
+
const extensions = withPaymentIdentifier(paymentRequired).extensions;
|
|
12986
13377
|
const paymentPayload = await createSolanaPaymentPayload(
|
|
12987
13378
|
secretKey,
|
|
12988
13379
|
fromAddress,
|
|
@@ -13005,7 +13396,7 @@ var SolanaLLMClient = class {
|
|
|
13005
13396
|
);
|
|
13006
13397
|
return { paymentPayload, costUsd: parseFloat(details.amount) / 1e6 };
|
|
13007
13398
|
}
|
|
13008
|
-
async handlePaymentAndRetry(url,
|
|
13399
|
+
async handlePaymentAndRetry(url, json, paymentRequired, forceFreshBlockhash = false, timeoutMs) {
|
|
13009
13400
|
let signed;
|
|
13010
13401
|
try {
|
|
13011
13402
|
signed = await this.signExactPayment(url, paymentRequired, forceFreshBlockhash, `${this.apiUrl}/v1/chat/completions`);
|
|
@@ -13022,14 +13413,15 @@ var SolanaLLMClient = class {
|
|
|
13022
13413
|
"User-Agent": USER_AGENT,
|
|
13023
13414
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
13024
13415
|
},
|
|
13025
|
-
body:
|
|
13026
|
-
});
|
|
13416
|
+
body: json
|
|
13417
|
+
}, timeoutMs);
|
|
13027
13418
|
} catch (error) {
|
|
13028
13419
|
throw withDisposition(error, "paid-or-in-doubt");
|
|
13029
13420
|
}
|
|
13030
13421
|
await this.assertPaid(retryResponse);
|
|
13031
|
-
this.recordSettlement(costUsd);
|
|
13032
|
-
|
|
13422
|
+
if (!settlementSkipped(retryResponse)) this.recordSettlement(costUsd);
|
|
13423
|
+
const paid = await this.readPaidJson(retryResponse);
|
|
13424
|
+
return withCost(paid, retryResponse, costUsd, "exact");
|
|
13033
13425
|
}
|
|
13034
13426
|
async requestWithPaymentRaw(endpoint, body, media = {}) {
|
|
13035
13427
|
const url = `${this.apiUrl}${endpoint}`;
|
|
@@ -13745,6 +14137,7 @@ var AnthropicClient = class {
|
|
|
13745
14137
|
BatchPaymentUnresolvedError,
|
|
13746
14138
|
BlockrunClient,
|
|
13747
14139
|
BlockrunError,
|
|
14140
|
+
DEFAULT_BATCH_IN_DOUBT,
|
|
13748
14141
|
DEFAULT_BATCH_RATE_LIMIT,
|
|
13749
14142
|
EVM_NETWORKS,
|
|
13750
14143
|
ImageClient,
|
|
@@ -13807,6 +14200,7 @@ var AnthropicClient = class {
|
|
|
13807
14200
|
loadWallet,
|
|
13808
14201
|
logCost,
|
|
13809
14202
|
parsePaymentRequired,
|
|
14203
|
+
readSettlement,
|
|
13810
14204
|
retryDisposition,
|
|
13811
14205
|
saveSolanaWallet,
|
|
13812
14206
|
saveToCache,
|
|
@@ -13821,6 +14215,8 @@ var AnthropicClient = class {
|
|
|
13821
14215
|
solanaKeyToBytes,
|
|
13822
14216
|
solanaPublicKey,
|
|
13823
14217
|
status,
|
|
14218
|
+
toWireSearchParameters,
|
|
14219
|
+
toWireTools,
|
|
13824
14220
|
validateMaxTokens,
|
|
13825
14221
|
validateModel,
|
|
13826
14222
|
validateTemperature,
|