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