@blockrun/llm 3.18.0 → 3.19.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 +60 -1
- package/dist/index.cjs +478 -90
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +480 -93
- package/package.json +19 -4
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
ARC_CHAIN_ID: () => ARC_CHAIN_ID,
|
|
36
36
|
AnthropicClient: () => AnthropicClient,
|
|
37
37
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
38
|
+
BLOCKRUN_SOL_OPERATOR: () => BLOCKRUN_SOL_OPERATOR,
|
|
38
39
|
BlockrunClient: () => BlockrunClient,
|
|
39
40
|
BlockrunError: () => BlockrunError,
|
|
40
41
|
EVM_NETWORKS: () => EVM_NETWORKS,
|
|
@@ -284,7 +285,7 @@ var TRANSIENT_RETRIES = 2;
|
|
|
284
285
|
var TRANSIENT_BACKOFF_MS = 1e3;
|
|
285
286
|
function retryDelay(ms, signal) {
|
|
286
287
|
signal?.throwIfAborted();
|
|
287
|
-
return new Promise((
|
|
288
|
+
return new Promise((resolve2, reject) => {
|
|
288
289
|
const onAbort = () => {
|
|
289
290
|
clearTimeout(timer);
|
|
290
291
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -292,7 +293,7 @@ function retryDelay(ms, signal) {
|
|
|
292
293
|
};
|
|
293
294
|
const timer = setTimeout(() => {
|
|
294
295
|
signal?.removeEventListener("abort", onAbort);
|
|
295
|
-
|
|
296
|
+
resolve2();
|
|
296
297
|
}, ms);
|
|
297
298
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
298
299
|
});
|
|
@@ -328,8 +329,8 @@ var ApiKeyAuth = class {
|
|
|
328
329
|
this.#key = key;
|
|
329
330
|
}
|
|
330
331
|
/** Resolve gateway poll paths without forwarding credentials to another origin. */
|
|
331
|
-
resolveUrl(
|
|
332
|
-
const url = new URL(
|
|
332
|
+
resolveUrl(path7) {
|
|
333
|
+
const url = new URL(path7, this.apiUrl + "/");
|
|
333
334
|
if (url.origin !== new URL(this.apiUrl).origin || url.username || url.password) {
|
|
334
335
|
throw new Error("Refusing to send a BlockRun API key to a different origin.");
|
|
335
336
|
}
|
|
@@ -395,7 +396,7 @@ var ApiKeyAuth = class {
|
|
|
395
396
|
if (["failed", "cancelled", "canceled"].includes(String(data.status))) {
|
|
396
397
|
throw new APIError("Account API job failed or was cancelled", 502, sanitizeErrorResponse(data));
|
|
397
398
|
}
|
|
398
|
-
await new Promise((
|
|
399
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(interval, Math.max(0, deadline - Date.now()))));
|
|
399
400
|
const remaining = deadline - Date.now();
|
|
400
401
|
if (remaining <= 0) break;
|
|
401
402
|
try {
|
|
@@ -4660,7 +4661,7 @@ function getCostSummary() {
|
|
|
4660
4661
|
}
|
|
4661
4662
|
|
|
4662
4663
|
// src/version.ts
|
|
4663
|
-
var SDK_VERSION = "3.
|
|
4664
|
+
var SDK_VERSION = "3.19.1";
|
|
4664
4665
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4665
4666
|
|
|
4666
4667
|
// src/client.ts
|
|
@@ -5769,8 +5770,8 @@ var LLMClient = class _LLMClient {
|
|
|
5769
5770
|
* @example
|
|
5770
5771
|
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
5771
5772
|
*/
|
|
5772
|
-
async exa(
|
|
5773
|
-
return this.requestWithPaymentRaw(`/v1/exa/${
|
|
5773
|
+
async exa(path7, body) {
|
|
5774
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path7}`, body);
|
|
5774
5775
|
}
|
|
5775
5776
|
/**
|
|
5776
5777
|
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
|
|
@@ -5918,8 +5919,8 @@ var LLMClient = class _LLMClient {
|
|
|
5918
5919
|
* const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
|
|
5919
5920
|
* const results = await client.pm("polymarket/search", { q: "bitcoin" });
|
|
5920
5921
|
*/
|
|
5921
|
-
async pm(
|
|
5922
|
-
return this.getWithPaymentRaw(`/v1/pm/${
|
|
5922
|
+
async pm(path7, params) {
|
|
5923
|
+
return this.getWithPaymentRaw(`/v1/pm/${path7}`, params);
|
|
5923
5924
|
}
|
|
5924
5925
|
/**
|
|
5925
5926
|
* Structured query for Predexon prediction market data (POST endpoints).
|
|
@@ -5935,8 +5936,8 @@ var LLMClient = class _LLMClient {
|
|
|
5935
5936
|
* addresses: ["0xabc...", "0xdef..."],
|
|
5936
5937
|
* });
|
|
5937
5938
|
*/
|
|
5938
|
-
async pmQuery(
|
|
5939
|
-
return this.requestWithPaymentRaw(`/v1/pm/${
|
|
5939
|
+
async pmQuery(path7, query) {
|
|
5940
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path7}`, query);
|
|
5940
5941
|
}
|
|
5941
5942
|
// ── PM convenience helpers (Predexon v2) ─────────────────────────────────
|
|
5942
5943
|
// Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
|
|
@@ -6024,8 +6025,8 @@ var LLMClient = class _LLMClient {
|
|
|
6024
6025
|
* "prices/coingecko:bitcoin,base:0x..."
|
|
6025
6026
|
* @param params - Query parameters passed through to DefiLlama
|
|
6026
6027
|
*/
|
|
6027
|
-
async defi(
|
|
6028
|
-
return this.getWithPaymentRaw(`/v1/defillama/${
|
|
6028
|
+
async defi(path7, params) {
|
|
6029
|
+
return this.getWithPaymentRaw(`/v1/defillama/${path7}`, params);
|
|
6029
6030
|
}
|
|
6030
6031
|
/** All DeFi protocols with TVL. */
|
|
6031
6032
|
async defiProtocols() {
|
|
@@ -6061,9 +6062,9 @@ var LLMClient = class _LLMClient {
|
|
|
6061
6062
|
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
|
|
6062
6063
|
* @param body - JSON body — pass to switch to POST (gasless/submit only)
|
|
6063
6064
|
*/
|
|
6064
|
-
async dex(
|
|
6065
|
-
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${
|
|
6066
|
-
return this.getWithPaymentRaw(`/v1/zerox/${
|
|
6065
|
+
async dex(path7, params, body) {
|
|
6066
|
+
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${path7}`, body);
|
|
6067
|
+
return this.getWithPaymentRaw(`/v1/zerox/${path7}`, params);
|
|
6067
6068
|
}
|
|
6068
6069
|
/** Indicative Permit2 swap price — no commitment (free). */
|
|
6069
6070
|
async dexPrice(params) {
|
|
@@ -6107,8 +6108,8 @@ var LLMClient = class _LLMClient {
|
|
|
6107
6108
|
* "sandbox/status", "sandbox/terminate"
|
|
6108
6109
|
* @param body - JSON body for the endpoint
|
|
6109
6110
|
*/
|
|
6110
|
-
async modal(
|
|
6111
|
-
return this.requestWithPaymentRaw(`/v1/modal/${
|
|
6111
|
+
async modal(path7, body) {
|
|
6112
|
+
return this.requestWithPaymentRaw(`/v1/modal/${path7}`, body ?? {});
|
|
6112
6113
|
}
|
|
6113
6114
|
/** Create a sandboxed compute environment (CPU / GPU tiers). */
|
|
6114
6115
|
async modalSandboxCreate(body) {
|
|
@@ -6416,7 +6417,7 @@ var ImageClient = class {
|
|
|
6416
6417
|
const pollUrl = pollPath.startsWith("/api/") ? `${this.apiUrl.replace(/\/api$/, "")}${pollPath}` : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
6417
6418
|
const startedAt = Date.now();
|
|
6418
6419
|
while (Date.now() - startedAt < POLL_MAX_DURATION_MS) {
|
|
6419
|
-
await new Promise((
|
|
6420
|
+
await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL_MS));
|
|
6420
6421
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
6421
6422
|
method: "GET",
|
|
6422
6423
|
headers: {
|
|
@@ -7614,8 +7615,8 @@ var PhoneClient = class {
|
|
|
7614
7615
|
return data;
|
|
7615
7616
|
}
|
|
7616
7617
|
// ─── Internals ─────────────────────────────────────────────────────────
|
|
7617
|
-
async request(
|
|
7618
|
-
const url = `${this.apiUrl}/v1/phone/${
|
|
7618
|
+
async request(path7, body) {
|
|
7619
|
+
const url = `${this.apiUrl}/v1/phone/${path7}`;
|
|
7619
7620
|
const response = await this.fetchWithTimeout(url, {
|
|
7620
7621
|
method: "POST",
|
|
7621
7622
|
headers: { "Content-Type": "application/json" },
|
|
@@ -8056,10 +8057,10 @@ var PriceClient = class {
|
|
|
8056
8057
|
}
|
|
8057
8058
|
async price(category, symbol, options) {
|
|
8058
8059
|
if (!symbol) throw new Error("symbol is required");
|
|
8059
|
-
const
|
|
8060
|
+
const path7 = categoryPath(category, options?.market, "price", symbol);
|
|
8060
8061
|
const query = {};
|
|
8061
8062
|
if (options?.session) query.session = options.session;
|
|
8062
|
-
const data = await this.getWithPayment(
|
|
8063
|
+
const data = await this.getWithPayment(path7, query);
|
|
8063
8064
|
return {
|
|
8064
8065
|
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
8065
8066
|
price: data.price,
|
|
@@ -8078,14 +8079,14 @@ var PriceClient = class {
|
|
|
8078
8079
|
if (!options.from || options.from <= 0) {
|
|
8079
8080
|
throw new Error("history requires options.from (unix seconds)");
|
|
8080
8081
|
}
|
|
8081
|
-
const
|
|
8082
|
+
const path7 = categoryPath(category, options.market, "history", symbol);
|
|
8082
8083
|
const query = {
|
|
8083
8084
|
resolution: options.resolution ?? "D",
|
|
8084
8085
|
from: String(options.from)
|
|
8085
8086
|
};
|
|
8086
8087
|
if (options.to) query.to = String(options.to);
|
|
8087
8088
|
if (options.session) query.session = options.session;
|
|
8088
|
-
const data = await this.getWithPayment(
|
|
8089
|
+
const data = await this.getWithPayment(path7, query);
|
|
8089
8090
|
return {
|
|
8090
8091
|
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
8091
8092
|
resolution: data.resolution ?? (options.resolution ?? "D"),
|
|
@@ -8097,12 +8098,12 @@ var PriceClient = class {
|
|
|
8097
8098
|
};
|
|
8098
8099
|
}
|
|
8099
8100
|
async listSymbols(category, options) {
|
|
8100
|
-
const
|
|
8101
|
+
const path7 = categoryPath(category, options?.market, "list");
|
|
8101
8102
|
const query = {
|
|
8102
8103
|
limit: String(options?.limit && options.limit > 0 ? options.limit : 100)
|
|
8103
8104
|
};
|
|
8104
8105
|
if (options?.query) query.q = options.query;
|
|
8105
|
-
const data = await this.getWithPayment(
|
|
8106
|
+
const data = await this.getWithPayment(path7, query);
|
|
8106
8107
|
if (Array.isArray(data)) {
|
|
8107
8108
|
return { symbols: data, count: data.length };
|
|
8108
8109
|
}
|
|
@@ -8275,19 +8276,19 @@ var SurfClient = class {
|
|
|
8275
8276
|
* `/v1/surf` is tolerated and stripped). Query params are URL-encoded;
|
|
8276
8277
|
* arrays become repeated keys (`?a=1&a=2`).
|
|
8277
8278
|
*/
|
|
8278
|
-
async get(
|
|
8279
|
-
const url = this.buildUrl(
|
|
8279
|
+
async get(path7, params) {
|
|
8280
|
+
const url = this.buildUrl(path7, params);
|
|
8280
8281
|
return this.requestWithPayment(url, "GET");
|
|
8281
8282
|
}
|
|
8282
8283
|
/**
|
|
8283
8284
|
* POST a Surf endpoint with a JSON body. Same path normalization as `get`.
|
|
8284
8285
|
*/
|
|
8285
|
-
async post(
|
|
8286
|
-
const url = this.buildUrl(
|
|
8286
|
+
async post(path7, body) {
|
|
8287
|
+
const url = this.buildUrl(path7);
|
|
8287
8288
|
return this.requestWithPayment(url, "POST", body);
|
|
8288
8289
|
}
|
|
8289
|
-
buildUrl(
|
|
8290
|
-
let normalized =
|
|
8290
|
+
buildUrl(path7, params) {
|
|
8291
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
8291
8292
|
if (!normalized.startsWith(SURF_PREFIX)) {
|
|
8292
8293
|
normalized = `${SURF_PREFIX}${normalized}`;
|
|
8293
8294
|
}
|
|
@@ -8745,15 +8746,15 @@ var BlockrunClient = class {
|
|
|
8745
8746
|
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
|
|
8746
8747
|
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
|
|
8747
8748
|
*/
|
|
8748
|
-
async get(
|
|
8749
|
-
const url = this.buildUrl(
|
|
8749
|
+
async get(path7, params) {
|
|
8750
|
+
const url = this.buildUrl(path7, params);
|
|
8750
8751
|
return this.requestWithPayment(url, "GET");
|
|
8751
8752
|
}
|
|
8752
8753
|
/**
|
|
8753
8754
|
* POST a BlockRun endpoint with a JSON body.
|
|
8754
8755
|
*/
|
|
8755
|
-
async post(
|
|
8756
|
-
const url = this.buildUrl(
|
|
8756
|
+
async post(path7, body) {
|
|
8757
|
+
const url = this.buildUrl(path7);
|
|
8757
8758
|
return this.requestWithPayment(url, "POST", body);
|
|
8758
8759
|
}
|
|
8759
8760
|
/**
|
|
@@ -8777,8 +8778,8 @@ var BlockrunClient = class {
|
|
|
8777
8778
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8778
8779
|
* video, music, voice) return 202 with a poll_url.
|
|
8779
8780
|
*/
|
|
8780
|
-
async poll(
|
|
8781
|
-
const submitUrl = this.buildUrl(
|
|
8781
|
+
async poll(path7, body, options) {
|
|
8782
|
+
const submitUrl = this.buildUrl(path7);
|
|
8782
8783
|
const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
|
|
8783
8784
|
const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
8784
8785
|
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
@@ -8878,8 +8879,8 @@ var BlockrunClient = class {
|
|
|
8878
8879
|
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
8879
8880
|
* }
|
|
8880
8881
|
*/
|
|
8881
|
-
async *stream(
|
|
8882
|
-
const url = this.buildUrl(
|
|
8882
|
+
async *stream(path7, body) {
|
|
8883
|
+
const url = this.buildUrl(path7);
|
|
8883
8884
|
const requestBody = JSON.stringify(body ?? {});
|
|
8884
8885
|
const resp402 = await this.fetchWithTimeout(url, {
|
|
8885
8886
|
method: "POST",
|
|
@@ -8922,8 +8923,8 @@ var BlockrunClient = class {
|
|
|
8922
8923
|
// --------------------------------------------------------------------
|
|
8923
8924
|
// Internal: shared infrastructure
|
|
8924
8925
|
// --------------------------------------------------------------------
|
|
8925
|
-
buildUrl(
|
|
8926
|
-
let normalized =
|
|
8926
|
+
buildUrl(path7, params) {
|
|
8927
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
8927
8928
|
if (normalized.startsWith("/api/")) {
|
|
8928
8929
|
normalized = normalized.slice(4);
|
|
8929
8930
|
}
|
|
@@ -9569,6 +9570,333 @@ async function getOrCreateSolanaWallet() {
|
|
|
9569
9570
|
return { address, privateKey, isNew: true };
|
|
9570
9571
|
}
|
|
9571
9572
|
|
|
9573
|
+
// src/solana-batch.ts
|
|
9574
|
+
var fs6 = __toESM(require("fs"), 1);
|
|
9575
|
+
var path5 = __toESM(require("path"), 1);
|
|
9576
|
+
var BATCH_SCHEME = "batch-settlement";
|
|
9577
|
+
var BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
|
|
9578
|
+
var INSTALL_HINT2 = "npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit";
|
|
9579
|
+
async function load(pkg, importer) {
|
|
9580
|
+
try {
|
|
9581
|
+
return await importer();
|
|
9582
|
+
} catch (err) {
|
|
9583
|
+
throw new Error(
|
|
9584
|
+
`@blockrun/llm: Solana batch-settlement requires the optional peer dependency "${pkg}", which is not installed.
|
|
9585
|
+
|
|
9586
|
+
${INSTALL_HINT2}
|
|
9587
|
+
|
|
9588
|
+
Original error: ${err instanceof Error ? err.message : String(err)}`,
|
|
9589
|
+
{ cause: err }
|
|
9590
|
+
);
|
|
9591
|
+
}
|
|
9592
|
+
}
|
|
9593
|
+
var FileChannelStorage = class {
|
|
9594
|
+
constructor(file) {
|
|
9595
|
+
this.file = file;
|
|
9596
|
+
}
|
|
9597
|
+
file;
|
|
9598
|
+
read() {
|
|
9599
|
+
try {
|
|
9600
|
+
const parsed = JSON.parse(fs6.readFileSync(this.file, "utf8"));
|
|
9601
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
9602
|
+
} catch (err) {
|
|
9603
|
+
if (err.code === "ENOENT") return {};
|
|
9604
|
+
throw err;
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
write(all) {
|
|
9608
|
+
fs6.mkdirSync(path5.dirname(this.file), { recursive: true, mode: 448 });
|
|
9609
|
+
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
9610
|
+
fs6.writeFileSync(tmp, JSON.stringify(all, null, 2), { mode: 384 });
|
|
9611
|
+
fs6.renameSync(tmp, this.file);
|
|
9612
|
+
}
|
|
9613
|
+
async get(key) {
|
|
9614
|
+
return this.read()[key];
|
|
9615
|
+
}
|
|
9616
|
+
async set(key, record) {
|
|
9617
|
+
const all = this.read();
|
|
9618
|
+
all[key] = record;
|
|
9619
|
+
this.write(all);
|
|
9620
|
+
}
|
|
9621
|
+
async delete(key) {
|
|
9622
|
+
const all = this.read();
|
|
9623
|
+
if (!(key in all)) return;
|
|
9624
|
+
delete all[key];
|
|
9625
|
+
this.write(all);
|
|
9626
|
+
}
|
|
9627
|
+
/** Forget every channel in this file. */
|
|
9628
|
+
clear() {
|
|
9629
|
+
try {
|
|
9630
|
+
fs6.unlinkSync(this.file);
|
|
9631
|
+
} catch (err) {
|
|
9632
|
+
if (err.code !== "ENOENT") throw err;
|
|
9633
|
+
}
|
|
9634
|
+
}
|
|
9635
|
+
};
|
|
9636
|
+
function processAlive(pid) {
|
|
9637
|
+
try {
|
|
9638
|
+
process.kill(pid, 0);
|
|
9639
|
+
return true;
|
|
9640
|
+
} catch (err) {
|
|
9641
|
+
return err.code === "EPERM";
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
var heldLocks = /* @__PURE__ */ new Set();
|
|
9645
|
+
var exitHookInstalled = false;
|
|
9646
|
+
function lockChannelFile(file) {
|
|
9647
|
+
if (heldLocks.has(file)) return void 0;
|
|
9648
|
+
const lock = `${file}.lock`;
|
|
9649
|
+
fs6.mkdirSync(path5.dirname(lock), { recursive: true, mode: 448 });
|
|
9650
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
9651
|
+
try {
|
|
9652
|
+
fs6.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
9653
|
+
heldLocks.add(file);
|
|
9654
|
+
if (!exitHookInstalled) {
|
|
9655
|
+
exitHookInstalled = true;
|
|
9656
|
+
process.once("exit", () => {
|
|
9657
|
+
for (const held of heldLocks) {
|
|
9658
|
+
try {
|
|
9659
|
+
fs6.unlinkSync(`${held}.lock`);
|
|
9660
|
+
} catch {
|
|
9661
|
+
}
|
|
9662
|
+
}
|
|
9663
|
+
});
|
|
9664
|
+
}
|
|
9665
|
+
return void 0;
|
|
9666
|
+
} catch (err) {
|
|
9667
|
+
if (err.code !== "EEXIST") throw err;
|
|
9668
|
+
}
|
|
9669
|
+
const owner = Number(fs6.readFileSync(lock, "utf8").trim());
|
|
9670
|
+
if (Number.isInteger(owner) && owner > 0 && owner !== process.pid && processAlive(owner)) return owner;
|
|
9671
|
+
try {
|
|
9672
|
+
fs6.unlinkSync(lock);
|
|
9673
|
+
} catch {
|
|
9674
|
+
}
|
|
9675
|
+
}
|
|
9676
|
+
return -1;
|
|
9677
|
+
}
|
|
9678
|
+
function pendingDeposit(pending) {
|
|
9679
|
+
const entries = Array.isArray(pending) ? pending : [pending];
|
|
9680
|
+
return entries.some(
|
|
9681
|
+
(entry) => entry?.payment?.payload?.type === "deposit"
|
|
9682
|
+
);
|
|
9683
|
+
}
|
|
9684
|
+
function dropOrphanedPending(storage) {
|
|
9685
|
+
return {
|
|
9686
|
+
get: async (key) => {
|
|
9687
|
+
const record = await storage.get(key);
|
|
9688
|
+
if (!record?.pending || record.channelConfig?.voucherSigner !== "server") return record;
|
|
9689
|
+
if (!record.hasConfirmedState || pendingDeposit(record.pending)) {
|
|
9690
|
+
await storage.delete(key);
|
|
9691
|
+
return void 0;
|
|
9692
|
+
}
|
|
9693
|
+
const { pending: _pending, hasConfirmedState: _confirmed, ...confirmed } = record;
|
|
9694
|
+
await storage.set(key, confirmed);
|
|
9695
|
+
return confirmed;
|
|
9696
|
+
},
|
|
9697
|
+
set: (key, record) => storage.set(key, record),
|
|
9698
|
+
delete: (key) => storage.delete(key)
|
|
9699
|
+
};
|
|
9700
|
+
}
|
|
9701
|
+
function offersBatch(paymentRequired) {
|
|
9702
|
+
return (paymentRequired.accepts ?? []).some((accept) => accept.scheme === BATCH_SCHEME);
|
|
9703
|
+
}
|
|
9704
|
+
function atomicToUsd(amount) {
|
|
9705
|
+
return amount !== void 0 && /^\d+$/.test(amount) ? Number(amount) / 1e6 : void 0;
|
|
9706
|
+
}
|
|
9707
|
+
async function isBatchRefusal(response) {
|
|
9708
|
+
if (response.status === 402) return "payment_required";
|
|
9709
|
+
if (![400, 403, 409, 503].includes(response.status)) return void 0;
|
|
9710
|
+
let body;
|
|
9711
|
+
try {
|
|
9712
|
+
body = await response.clone().json();
|
|
9713
|
+
} catch {
|
|
9714
|
+
return void 0;
|
|
9715
|
+
}
|
|
9716
|
+
const { error, code } = body ?? {};
|
|
9717
|
+
if (code === "PAYMENT_VERIFICATION_UNAVAILABLE") return code;
|
|
9718
|
+
return typeof error === "string" && error.startsWith("batch_") ? error : void 0;
|
|
9719
|
+
}
|
|
9720
|
+
var wallets = /* @__PURE__ */ new Map();
|
|
9721
|
+
var SolanaBatchPayer = class {
|
|
9722
|
+
constructor(init) {
|
|
9723
|
+
this.init = init;
|
|
9724
|
+
const { operators } = init.options;
|
|
9725
|
+
if (!Array.isArray(operators) || operators.length === 0 || operators.some((o) => typeof o !== "string" || !o)) {
|
|
9726
|
+
throw new Error("batch.operators must list at least one base58 operator public key");
|
|
9727
|
+
}
|
|
9728
|
+
}
|
|
9729
|
+
init;
|
|
9730
|
+
state;
|
|
9731
|
+
async storeFile() {
|
|
9732
|
+
const { channelStore } = this.init.options;
|
|
9733
|
+
if (channelStore === false) return void 0;
|
|
9734
|
+
return path5.resolve(channelStore ?? path5.join(paths().dir, "solana-batch", `${await this.init.address()}.json`));
|
|
9735
|
+
}
|
|
9736
|
+
/**
|
|
9737
|
+
* This wallet's shared batch state, or why this client cannot use batch:
|
|
9738
|
+
* another client in this process holds the wallet with a different trust
|
|
9739
|
+
* configuration, or another live process owns its channel file.
|
|
9740
|
+
*/
|
|
9741
|
+
wallet() {
|
|
9742
|
+
this.state ??= (async () => {
|
|
9743
|
+
const address = await this.init.address();
|
|
9744
|
+
const file = await this.storeFile();
|
|
9745
|
+
const { operators, maxDeposit } = this.init.options;
|
|
9746
|
+
const config = JSON.stringify([[...operators].sort(), String(maxDeposit ?? ""), file ?? null, this.init.rpcUrl]);
|
|
9747
|
+
const existing = wallets.get(address);
|
|
9748
|
+
if (existing) {
|
|
9749
|
+
return existing.config === config ? existing : "another client in this process uses this wallet with different batch options";
|
|
9750
|
+
}
|
|
9751
|
+
if (file) {
|
|
9752
|
+
const owner = lockChannelFile(file);
|
|
9753
|
+
if (owner !== void 0) return `channel store ${file} is in use by process ${owner}`;
|
|
9754
|
+
}
|
|
9755
|
+
const created = { config, busy: false, warned: /* @__PURE__ */ new Set() };
|
|
9756
|
+
wallets.set(address, created);
|
|
9757
|
+
return created;
|
|
9758
|
+
})();
|
|
9759
|
+
return this.state;
|
|
9760
|
+
}
|
|
9761
|
+
build(wallet) {
|
|
9762
|
+
wallet.client ??= (async () => {
|
|
9763
|
+
const [core, svm, kit] = await Promise.all([
|
|
9764
|
+
load("@x402/core", () => import("@x402/core/client")),
|
|
9765
|
+
load("@x402/svm", () => import("@x402/svm/batch-settlement/client")),
|
|
9766
|
+
load("@solana/kit", () => import("@solana/kit"))
|
|
9767
|
+
]);
|
|
9768
|
+
const signer = await kit.createKeyPairSignerFromBytes(await this.init.secretKey());
|
|
9769
|
+
const file = await this.storeFile();
|
|
9770
|
+
const storage = file ? dropOrphanedPending(new FileChannelStorage(file)) : void 0;
|
|
9771
|
+
const scheme = new svm.BatchSvmScheme(signer, {
|
|
9772
|
+
rpcUrl: this.init.rpcUrl,
|
|
9773
|
+
serverSignedChannelsPolicy: {
|
|
9774
|
+
allowedOperators: this.init.options.operators,
|
|
9775
|
+
maxDeposit: this.init.options.maxDeposit ?? svm.DEFAULT_SERVER_SIGNED_MAX_DEPOSIT
|
|
9776
|
+
},
|
|
9777
|
+
...storage ? { channelStorage: storage } : {}
|
|
9778
|
+
});
|
|
9779
|
+
const client = new core.x402Client().register("solana:*", scheme).registerPolicy(scheme.paymentPolicy);
|
|
9780
|
+
client.setSpendControls({ maxAmountPerPayment: false });
|
|
9781
|
+
return {
|
|
9782
|
+
http: new core.x402HTTPClient(client),
|
|
9783
|
+
refund: (url) => scheme.refund(url)
|
|
9784
|
+
};
|
|
9785
|
+
})();
|
|
9786
|
+
wallet.client.catch(() => {
|
|
9787
|
+
wallet.client = void 0;
|
|
9788
|
+
});
|
|
9789
|
+
return wallet.client;
|
|
9790
|
+
}
|
|
9791
|
+
/**
|
|
9792
|
+
* Drop everything this wallet knows about its channel, on disk and in
|
|
9793
|
+
* memory. The next call rebuilds the scheme, which reads the channel's real
|
|
9794
|
+
* state (deposit, open or closed) from the chain.
|
|
9795
|
+
*/
|
|
9796
|
+
async forget(wallet) {
|
|
9797
|
+
wallet.client = void 0;
|
|
9798
|
+
const file = await this.storeFile();
|
|
9799
|
+
if (file) new FileChannelStorage(file).clear();
|
|
9800
|
+
}
|
|
9801
|
+
warn(wallet, reason) {
|
|
9802
|
+
if (wallet?.warned.has(reason)) return;
|
|
9803
|
+
wallet?.warned.add(reason);
|
|
9804
|
+
console.error(`[@blockrun/llm] batch-settlement unavailable (${reason}); paying with exact`);
|
|
9805
|
+
}
|
|
9806
|
+
/**
|
|
9807
|
+
* Pay one 402 with batch-settlement, or say why the caller should use exact.
|
|
9808
|
+
*
|
|
9809
|
+
* `send` replays the original request with the payment headers. The
|
|
9810
|
+
* returned response is 2xx; any other answer either becomes a fallback (the
|
|
9811
|
+
* gateway charged nothing) or is returned to the caller's own error path.
|
|
9812
|
+
*/
|
|
9813
|
+
async pay(paymentRequired, send) {
|
|
9814
|
+
if (!offersBatch(paymentRequired)) return { kind: "fallback", reason: "not_offered" };
|
|
9815
|
+
const wallet = await this.wallet();
|
|
9816
|
+
if (typeof wallet === "string") {
|
|
9817
|
+
this.warn(void 0, wallet);
|
|
9818
|
+
return { kind: "fallback", reason: wallet };
|
|
9819
|
+
}
|
|
9820
|
+
if (wallet.busy) return { kind: "fallback", reason: "channel_busy" };
|
|
9821
|
+
wallet.busy = true;
|
|
9822
|
+
try {
|
|
9823
|
+
let http;
|
|
9824
|
+
let payload;
|
|
9825
|
+
try {
|
|
9826
|
+
http = (await this.build(wallet)).http;
|
|
9827
|
+
payload = await http.createPaymentPayload(paymentRequired);
|
|
9828
|
+
} catch (err) {
|
|
9829
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9830
|
+
this.warn(wallet, reason);
|
|
9831
|
+
return { kind: "fallback", reason };
|
|
9832
|
+
}
|
|
9833
|
+
const deposit = payload.payload?.type === "deposit";
|
|
9834
|
+
let response;
|
|
9835
|
+
try {
|
|
9836
|
+
response = await send(http.encodePaymentSignatureHeader(payload));
|
|
9837
|
+
} catch (err) {
|
|
9838
|
+
if (deposit) await this.forget(wallet);
|
|
9839
|
+
else await this.settle(http, payload, () => null, 0);
|
|
9840
|
+
throw err;
|
|
9841
|
+
}
|
|
9842
|
+
const getHeader = (name) => response.headers.get(name);
|
|
9843
|
+
const settled = await this.settle(http, payload, getHeader, response.status);
|
|
9844
|
+
if (deposit && !(response.ok && settled?.success === true)) await this.forget(wallet);
|
|
9845
|
+
if (response.ok) {
|
|
9846
|
+
const charged = atomicToUsd(settled?.amount) ?? atomicToUsd(payload.accepted?.amount) ?? 0;
|
|
9847
|
+
return { kind: "paid", response, chargedUsd: charged };
|
|
9848
|
+
}
|
|
9849
|
+
const refusal = await isBatchRefusal(response);
|
|
9850
|
+
if (refusal) {
|
|
9851
|
+
this.warn(wallet, refusal);
|
|
9852
|
+
return { kind: "fallback", reason: refusal };
|
|
9853
|
+
}
|
|
9854
|
+
return { kind: "failed", response };
|
|
9855
|
+
} finally {
|
|
9856
|
+
wallet.busy = false;
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
/**
|
|
9860
|
+
* Hand the gateway's answer to the scheme, which verifies the operator's
|
|
9861
|
+
* voucher and advances (or rolls back) the channel's local state.
|
|
9862
|
+
*
|
|
9863
|
+
* A malformed receipt must not lose a response the caller has been served,
|
|
9864
|
+
* so a scheme error is logged rather than thrown.
|
|
9865
|
+
*/
|
|
9866
|
+
async settle(http, payload, getHeader, status2) {
|
|
9867
|
+
try {
|
|
9868
|
+
return (await http.processPaymentResult(payload, getHeader, status2)).settleResponse;
|
|
9869
|
+
} catch (err) {
|
|
9870
|
+
console.error(
|
|
9871
|
+
`[@blockrun/llm] batch-settlement receipt not reconciled: ${err instanceof Error ? err.message : String(err)}`
|
|
9872
|
+
);
|
|
9873
|
+
return void 0;
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
/**
|
|
9877
|
+
* Close the channel and return its unused escrow to the wallet.
|
|
9878
|
+
*
|
|
9879
|
+
* The scheme keeps a closed channel in its records, and would go on paying
|
|
9880
|
+
* into it, so a successful close forgets the channel. The next batch call
|
|
9881
|
+
* finds no open channel on-chain and opens a new one.
|
|
9882
|
+
*
|
|
9883
|
+
* @param url - any batch-enabled route on the gateway the channel was opened with.
|
|
9884
|
+
*/
|
|
9885
|
+
async close(url) {
|
|
9886
|
+
const wallet = await this.wallet();
|
|
9887
|
+
if (typeof wallet === "string") throw new Error(`batch-settlement unavailable: ${wallet}`);
|
|
9888
|
+
if (wallet.busy) throw new Error("batch-settlement channel has a request in flight; close it when the call returns");
|
|
9889
|
+
wallet.busy = true;
|
|
9890
|
+
try {
|
|
9891
|
+
const result = await (await this.build(wallet)).refund(url);
|
|
9892
|
+
await this.forget(wallet);
|
|
9893
|
+
return result;
|
|
9894
|
+
} finally {
|
|
9895
|
+
wallet.busy = false;
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
};
|
|
9899
|
+
|
|
9572
9900
|
// src/solana-client.ts
|
|
9573
9901
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
9574
9902
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
@@ -9632,7 +9960,7 @@ async function isSafeStaleBlockhashResponse(response) {
|
|
|
9632
9960
|
}
|
|
9633
9961
|
async function waitForStaleRetry(attempt) {
|
|
9634
9962
|
await new Promise(
|
|
9635
|
-
(
|
|
9963
|
+
(resolve2) => setTimeout(resolve2, STALE_BLOCKHASH_RETRY_BACKOFFS_MS[attempt])
|
|
9636
9964
|
);
|
|
9637
9965
|
}
|
|
9638
9966
|
var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
@@ -9676,6 +10004,7 @@ var SolanaLLMClient = class {
|
|
|
9676
10004
|
addressCache = null;
|
|
9677
10005
|
modelPricingCache = null;
|
|
9678
10006
|
modelPricingPromise = null;
|
|
10007
|
+
batchPayer;
|
|
9679
10008
|
constructor(options = {}) {
|
|
9680
10009
|
this.apiAuth = resolveApiKeyAuth(options);
|
|
9681
10010
|
if (!this.apiAuth) {
|
|
@@ -9695,6 +10024,25 @@ var SolanaLLMClient = class {
|
|
|
9695
10024
|
this.rpcUrl = rpc.url;
|
|
9696
10025
|
this.rpcHeaders = rpc.headers;
|
|
9697
10026
|
this.timeout = options.timeout || DEFAULT_TIMEOUT14;
|
|
10027
|
+
if (options.batch && !this.apiAuth) {
|
|
10028
|
+
this.batchPayer = new SolanaBatchPayer({
|
|
10029
|
+
options: options.batch,
|
|
10030
|
+
secretKey: () => solanaKeyToBytes(this.privateKey),
|
|
10031
|
+
address: () => this.getWalletAddress(),
|
|
10032
|
+
rpcUrl: this.rpcUrl
|
|
10033
|
+
});
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
/**
|
|
10037
|
+
* Close this wallet's batch-settlement channel and return its unused escrow.
|
|
10038
|
+
*
|
|
10039
|
+
* The gateway closes it cooperatively when it can; otherwise this starts a
|
|
10040
|
+
* payer-forced close and the escrow comes back after the channel's grace
|
|
10041
|
+
* period. Only meaningful with the `batch` option.
|
|
10042
|
+
*/
|
|
10043
|
+
async closeBatchChannel() {
|
|
10044
|
+
if (!this.batchPayer) throw new Error("closeBatchChannel() requires the `batch` option in wallet mode");
|
|
10045
|
+
return this.batchPayer.close(`${this.apiUrl}/v1/chat/completions`);
|
|
9698
10046
|
}
|
|
9699
10047
|
/** Get Solana wallet address (public key in base58). */
|
|
9700
10048
|
async getWalletAddress() {
|
|
@@ -9888,8 +10236,8 @@ var SolanaLLMClient = class {
|
|
|
9888
10236
|
* how you end up debugging a request you did not send.
|
|
9889
10237
|
* @returns each decoded SSE frame, in order.
|
|
9890
10238
|
*/
|
|
9891
|
-
async *stream(
|
|
9892
|
-
const url = this.buildUrl(
|
|
10239
|
+
async *stream(path7, body) {
|
|
10240
|
+
const url = this.buildUrl(path7);
|
|
9893
10241
|
const response = await this.openPaidStream(url, JSON.stringify(body ?? {}));
|
|
9894
10242
|
yield* readSseFrames(
|
|
9895
10243
|
response,
|
|
@@ -9961,8 +10309,8 @@ var SolanaLLMClient = class {
|
|
|
9961
10309
|
* @param path - endpoint path, with or without a leading slash.
|
|
9962
10310
|
* @returns the absolute URL to call.
|
|
9963
10311
|
*/
|
|
9964
|
-
buildUrl(
|
|
9965
|
-
let normalized =
|
|
10312
|
+
buildUrl(path7) {
|
|
10313
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
9966
10314
|
if (normalized.startsWith("/api/")) normalized = normalized.slice(4);
|
|
9967
10315
|
return `${this.apiUrl}${normalized}`;
|
|
9968
10316
|
}
|
|
@@ -10041,11 +10389,11 @@ var SolanaLLMClient = class {
|
|
|
10041
10389
|
return data;
|
|
10042
10390
|
}
|
|
10043
10391
|
// ── Prediction Markets (Powered by Predexon) ────────────────────────────
|
|
10044
|
-
async pm(
|
|
10045
|
-
return this.getWithPaymentRaw(`/v1/pm/${
|
|
10392
|
+
async pm(path7, params) {
|
|
10393
|
+
return this.getWithPaymentRaw(`/v1/pm/${path7}`, params);
|
|
10046
10394
|
}
|
|
10047
|
-
async pmQuery(
|
|
10048
|
-
return this.requestWithPaymentRaw(`/v1/pm/${
|
|
10395
|
+
async pmQuery(path7, query) {
|
|
10396
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path7}`, query);
|
|
10049
10397
|
}
|
|
10050
10398
|
// ── Exa Web Search (Powered by Exa) ──────────────────────────────────────
|
|
10051
10399
|
/**
|
|
@@ -10057,8 +10405,8 @@ var SolanaLLMClient = class {
|
|
|
10057
10405
|
* @example
|
|
10058
10406
|
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
10059
10407
|
*/
|
|
10060
|
-
async exa(
|
|
10061
|
-
return this.requestWithPaymentRaw(`/v1/exa/${
|
|
10408
|
+
async exa(path7, body) {
|
|
10409
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path7}`, body);
|
|
10062
10410
|
}
|
|
10063
10411
|
/**
|
|
10064
10412
|
* Neural and keyword web search via Exa (Solana payment).
|
|
@@ -10108,8 +10456,8 @@ var SolanaLLMClient = class {
|
|
|
10108
10456
|
* "prices/coingecko:bitcoin,base:0x..."
|
|
10109
10457
|
* @param params - Query parameters passed through to DefiLlama
|
|
10110
10458
|
*/
|
|
10111
|
-
async defi(
|
|
10112
|
-
return this.getWithPaymentRaw(`/v1/defillama/${
|
|
10459
|
+
async defi(path7, params) {
|
|
10460
|
+
return this.getWithPaymentRaw(`/v1/defillama/${path7}`, params);
|
|
10113
10461
|
}
|
|
10114
10462
|
/** All DeFi protocols with TVL. */
|
|
10115
10463
|
async defiProtocols() {
|
|
@@ -10145,9 +10493,9 @@ var SolanaLLMClient = class {
|
|
|
10145
10493
|
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
|
|
10146
10494
|
* @param body - JSON body — pass to switch to POST (gasless/submit only)
|
|
10147
10495
|
*/
|
|
10148
|
-
async dex(
|
|
10149
|
-
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${
|
|
10150
|
-
return this.getWithPaymentRaw(`/v1/zerox/${
|
|
10496
|
+
async dex(path7, params, body) {
|
|
10497
|
+
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${path7}`, body);
|
|
10498
|
+
return this.getWithPaymentRaw(`/v1/zerox/${path7}`, params);
|
|
10151
10499
|
}
|
|
10152
10500
|
/** Indicative Permit2 swap price — no commitment (free). */
|
|
10153
10501
|
async dexPrice(params) {
|
|
@@ -10191,8 +10539,8 @@ var SolanaLLMClient = class {
|
|
|
10191
10539
|
* "sandbox/status", "sandbox/terminate"
|
|
10192
10540
|
* @param body - JSON body for the endpoint
|
|
10193
10541
|
*/
|
|
10194
|
-
async modal(
|
|
10195
|
-
return this.requestWithPaymentRaw(`/v1/modal/${
|
|
10542
|
+
async modal(path7, body) {
|
|
10543
|
+
return this.requestWithPaymentRaw(`/v1/modal/${path7}`, body ?? {});
|
|
10196
10544
|
}
|
|
10197
10545
|
/** Create a sandboxed compute environment (CPU / GPU tiers). */
|
|
10198
10546
|
async modalSandboxCreate(body) {
|
|
@@ -10229,7 +10577,31 @@ var SolanaLLMClient = class {
|
|
|
10229
10577
|
});
|
|
10230
10578
|
if (response.status === 402) {
|
|
10231
10579
|
try {
|
|
10232
|
-
|
|
10580
|
+
const paymentRequired = await this.readPaymentRequired(response);
|
|
10581
|
+
if (this.batchPayer && staleRetries === 0) {
|
|
10582
|
+
const batch = await this.batchPayer.pay(
|
|
10583
|
+
paymentRequired,
|
|
10584
|
+
(paymentHeaders) => this.fetchWithTimeout(url, {
|
|
10585
|
+
method: "POST",
|
|
10586
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT, ...paymentHeaders },
|
|
10587
|
+
body: JSON.stringify(body)
|
|
10588
|
+
})
|
|
10589
|
+
);
|
|
10590
|
+
if (batch.kind === "paid") {
|
|
10591
|
+
this.recordSettlement(batch.chargedUsd);
|
|
10592
|
+
return batch.response.json();
|
|
10593
|
+
}
|
|
10594
|
+
if (batch.kind === "failed") {
|
|
10595
|
+
let errorBody;
|
|
10596
|
+
try {
|
|
10597
|
+
errorBody = await batch.response.json();
|
|
10598
|
+
} catch {
|
|
10599
|
+
errorBody = { error: "Request failed" };
|
|
10600
|
+
}
|
|
10601
|
+
throw new APIError(`API error after payment: ${batch.response.status}`, batch.response.status, sanitizeErrorResponse(errorBody));
|
|
10602
|
+
}
|
|
10603
|
+
}
|
|
10604
|
+
return await this.handlePaymentAndRetry(url, body, paymentRequired, staleRetries > 0);
|
|
10233
10605
|
} catch (error) {
|
|
10234
10606
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
10235
10607
|
await waitForStaleRetry(staleRetries++);
|
|
@@ -10350,6 +10722,15 @@ var SolanaLLMClient = class {
|
|
|
10350
10722
|
);
|
|
10351
10723
|
}
|
|
10352
10724
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10725
|
+
return this.signExactPayment(
|
|
10726
|
+
url,
|
|
10727
|
+
await this.readPaymentRequired(response),
|
|
10728
|
+
forceFreshBlockhash,
|
|
10729
|
+
resourceFallback
|
|
10730
|
+
);
|
|
10731
|
+
}
|
|
10732
|
+
/** Read a `402`'s payment requirements from its header, or its JSON body. */
|
|
10733
|
+
async readPaymentRequired(response) {
|
|
10353
10734
|
let paymentHeader = response.headers.get("payment-required");
|
|
10354
10735
|
if (!paymentHeader) {
|
|
10355
10736
|
try {
|
|
@@ -10363,8 +10744,14 @@ var SolanaLLMClient = class {
|
|
|
10363
10744
|
if (!paymentHeader) {
|
|
10364
10745
|
throw new PaymentError("402 response but no payment requirements found");
|
|
10365
10746
|
}
|
|
10366
|
-
|
|
10367
|
-
|
|
10747
|
+
return parsePaymentRequired(paymentHeader);
|
|
10748
|
+
}
|
|
10749
|
+
/** Sign the `exact` accept of a 402 as an SPL TransferChecked payment. */
|
|
10750
|
+
async signExactPayment(url, paymentRequired, forceFreshBlockhash, resourceFallback = url) {
|
|
10751
|
+
const details = extractPaymentDetails(
|
|
10752
|
+
{ ...paymentRequired, accepts: paymentRequired.accepts.filter((accept) => accept.scheme !== BATCH_SCHEME) },
|
|
10753
|
+
SOLANA_NETWORK
|
|
10754
|
+
);
|
|
10368
10755
|
if (!details.network?.startsWith("solana:")) {
|
|
10369
10756
|
throw new PaymentError(
|
|
10370
10757
|
`Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
|
|
@@ -10397,10 +10784,10 @@ var SolanaLLMClient = class {
|
|
|
10397
10784
|
);
|
|
10398
10785
|
return { paymentPayload, costUsd: parseFloat(details.amount) / 1e6 };
|
|
10399
10786
|
}
|
|
10400
|
-
async handlePaymentAndRetry(url, body,
|
|
10401
|
-
const { paymentPayload, costUsd } = await this.
|
|
10787
|
+
async handlePaymentAndRetry(url, body, paymentRequired, forceFreshBlockhash = false) {
|
|
10788
|
+
const { paymentPayload, costUsd } = await this.signExactPayment(
|
|
10402
10789
|
url,
|
|
10403
|
-
|
|
10790
|
+
paymentRequired,
|
|
10404
10791
|
forceFreshBlockhash,
|
|
10405
10792
|
`${this.apiUrl}/v1/chat/completions`
|
|
10406
10793
|
);
|
|
@@ -10561,13 +10948,13 @@ function solanaClient(options = {}) {
|
|
|
10561
10948
|
}
|
|
10562
10949
|
|
|
10563
10950
|
// src/cache.ts
|
|
10564
|
-
var
|
|
10565
|
-
var
|
|
10951
|
+
var fs7 = __toESM(require("fs"), 1);
|
|
10952
|
+
var path6 = __toESM(require("path"), 1);
|
|
10566
10953
|
var os4 = __toESM(require("os"), 1);
|
|
10567
10954
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
10568
|
-
var CACHE_DIR =
|
|
10569
|
-
var DATA_DIR =
|
|
10570
|
-
var COST_LOG_FILE2 =
|
|
10955
|
+
var CACHE_DIR = path6.join(os4.homedir(), ".blockrun", "cache");
|
|
10956
|
+
var DATA_DIR = path6.join(os4.homedir(), ".blockrun", "data");
|
|
10957
|
+
var COST_LOG_FILE2 = path6.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
|
|
10571
10958
|
var DEFAULT_TTL = {
|
|
10572
10959
|
"/v1/partner/": 3600 * 1e3,
|
|
10573
10960
|
"/v1/pm/": 1800 * 1e3,
|
|
@@ -10587,19 +10974,19 @@ function cacheKey(endpoint, body) {
|
|
|
10587
10974
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
10588
10975
|
}
|
|
10589
10976
|
function cachePath(key) {
|
|
10590
|
-
return
|
|
10977
|
+
return path6.join(CACHE_DIR, `${key}.json`);
|
|
10591
10978
|
}
|
|
10592
10979
|
function getCached(key) {
|
|
10593
10980
|
const filePath = cachePath(key);
|
|
10594
|
-
if (!
|
|
10981
|
+
if (!fs7.existsSync(filePath)) return null;
|
|
10595
10982
|
try {
|
|
10596
|
-
const raw =
|
|
10983
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
10597
10984
|
const entry = JSON.parse(raw);
|
|
10598
10985
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
10599
10986
|
if (ttl <= 0) return null;
|
|
10600
10987
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
10601
10988
|
try {
|
|
10602
|
-
|
|
10989
|
+
fs7.unlinkSync(filePath);
|
|
10603
10990
|
} catch {
|
|
10604
10991
|
}
|
|
10605
10992
|
return null;
|
|
@@ -10618,7 +11005,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
10618
11005
|
function setCache(key, data, ttlMs) {
|
|
10619
11006
|
if (ttlMs <= 0) return;
|
|
10620
11007
|
try {
|
|
10621
|
-
|
|
11008
|
+
fs7.mkdirSync(CACHE_DIR, { recursive: true });
|
|
10622
11009
|
} catch {
|
|
10623
11010
|
}
|
|
10624
11011
|
const entry = {
|
|
@@ -10627,7 +11014,7 @@ function setCache(key, data, ttlMs) {
|
|
|
10627
11014
|
ttlMs
|
|
10628
11015
|
};
|
|
10629
11016
|
try {
|
|
10630
|
-
|
|
11017
|
+
fs7.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
10631
11018
|
} catch {
|
|
10632
11019
|
}
|
|
10633
11020
|
}
|
|
@@ -10648,7 +11035,7 @@ function readableFilename(endpoint, body) {
|
|
|
10648
11035
|
}
|
|
10649
11036
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
10650
11037
|
try {
|
|
10651
|
-
|
|
11038
|
+
fs7.mkdirSync(DATA_DIR, { recursive: true });
|
|
10652
11039
|
} catch {
|
|
10653
11040
|
}
|
|
10654
11041
|
const filename = readableFilename(endpoint, body);
|
|
@@ -10660,14 +11047,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
10660
11047
|
response
|
|
10661
11048
|
};
|
|
10662
11049
|
try {
|
|
10663
|
-
|
|
11050
|
+
fs7.writeFileSync(path6.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
10664
11051
|
} catch {
|
|
10665
11052
|
}
|
|
10666
11053
|
}
|
|
10667
11054
|
function appendCostLog(endpoint, costUsd) {
|
|
10668
11055
|
if (costUsd <= 0) return;
|
|
10669
11056
|
try {
|
|
10670
|
-
|
|
11057
|
+
fs7.mkdirSync(path6.dirname(COST_LOG_FILE2), { recursive: true });
|
|
10671
11058
|
} catch {
|
|
10672
11059
|
}
|
|
10673
11060
|
const entry = {
|
|
@@ -10676,7 +11063,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
10676
11063
|
cost_usd: costUsd
|
|
10677
11064
|
};
|
|
10678
11065
|
try {
|
|
10679
|
-
|
|
11066
|
+
fs7.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
10680
11067
|
} catch {
|
|
10681
11068
|
}
|
|
10682
11069
|
}
|
|
@@ -10684,7 +11071,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10684
11071
|
const ttl = getTtl(endpoint);
|
|
10685
11072
|
if (ttl > 0) {
|
|
10686
11073
|
try {
|
|
10687
|
-
|
|
11074
|
+
fs7.mkdirSync(CACHE_DIR, { recursive: true });
|
|
10688
11075
|
} catch {
|
|
10689
11076
|
}
|
|
10690
11077
|
const key = cacheKey(endpoint, body);
|
|
@@ -10696,7 +11083,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10696
11083
|
costUsd
|
|
10697
11084
|
};
|
|
10698
11085
|
try {
|
|
10699
|
-
|
|
11086
|
+
fs7.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
10700
11087
|
} catch {
|
|
10701
11088
|
}
|
|
10702
11089
|
}
|
|
@@ -10704,14 +11091,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10704
11091
|
appendCostLog(endpoint, costUsd);
|
|
10705
11092
|
}
|
|
10706
11093
|
function clearCache() {
|
|
10707
|
-
if (!
|
|
11094
|
+
if (!fs7.existsSync(CACHE_DIR)) return 0;
|
|
10708
11095
|
let count = 0;
|
|
10709
11096
|
try {
|
|
10710
|
-
const files =
|
|
11097
|
+
const files = fs7.readdirSync(CACHE_DIR);
|
|
10711
11098
|
for (const file of files) {
|
|
10712
11099
|
if (file.endsWith(".json")) {
|
|
10713
11100
|
try {
|
|
10714
|
-
|
|
11101
|
+
fs7.unlinkSync(path6.join(CACHE_DIR, file));
|
|
10715
11102
|
count++;
|
|
10716
11103
|
} catch {
|
|
10717
11104
|
}
|
|
@@ -10722,14 +11109,14 @@ function clearCache() {
|
|
|
10722
11109
|
return count;
|
|
10723
11110
|
}
|
|
10724
11111
|
function getCostLogSummary() {
|
|
10725
|
-
if (!
|
|
11112
|
+
if (!fs7.existsSync(COST_LOG_FILE2)) {
|
|
10726
11113
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
10727
11114
|
}
|
|
10728
11115
|
let totalUsd = 0;
|
|
10729
11116
|
let calls = 0;
|
|
10730
11117
|
const byEndpoint = {};
|
|
10731
11118
|
try {
|
|
10732
|
-
const content =
|
|
11119
|
+
const content = fs7.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
10733
11120
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
10734
11121
|
for (const line of content.split("\n")) {
|
|
10735
11122
|
if (!line) continue;
|
|
@@ -11104,6 +11491,7 @@ var AnthropicClient = class {
|
|
|
11104
11491
|
ARC_CHAIN_ID,
|
|
11105
11492
|
AnthropicClient,
|
|
11106
11493
|
BASE_CHAIN_ID,
|
|
11494
|
+
BLOCKRUN_SOL_OPERATOR,
|
|
11107
11495
|
BlockrunClient,
|
|
11108
11496
|
BlockrunError,
|
|
11109
11497
|
EVM_NETWORKS,
|