@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.js
CHANGED
|
@@ -163,7 +163,7 @@ var TRANSIENT_RETRIES = 2;
|
|
|
163
163
|
var TRANSIENT_BACKOFF_MS = 1e3;
|
|
164
164
|
function retryDelay(ms, signal) {
|
|
165
165
|
signal?.throwIfAborted();
|
|
166
|
-
return new Promise((
|
|
166
|
+
return new Promise((resolve2, reject) => {
|
|
167
167
|
const onAbort = () => {
|
|
168
168
|
clearTimeout(timer);
|
|
169
169
|
signal?.removeEventListener("abort", onAbort);
|
|
@@ -171,7 +171,7 @@ function retryDelay(ms, signal) {
|
|
|
171
171
|
};
|
|
172
172
|
const timer = setTimeout(() => {
|
|
173
173
|
signal?.removeEventListener("abort", onAbort);
|
|
174
|
-
|
|
174
|
+
resolve2();
|
|
175
175
|
}, ms);
|
|
176
176
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
177
177
|
});
|
|
@@ -207,8 +207,8 @@ var ApiKeyAuth = class {
|
|
|
207
207
|
this.#key = key;
|
|
208
208
|
}
|
|
209
209
|
/** Resolve gateway poll paths without forwarding credentials to another origin. */
|
|
210
|
-
resolveUrl(
|
|
211
|
-
const url = new URL(
|
|
210
|
+
resolveUrl(path7) {
|
|
211
|
+
const url = new URL(path7, this.apiUrl + "/");
|
|
212
212
|
if (url.origin !== new URL(this.apiUrl).origin || url.username || url.password) {
|
|
213
213
|
throw new Error("Refusing to send a BlockRun API key to a different origin.");
|
|
214
214
|
}
|
|
@@ -274,7 +274,7 @@ var ApiKeyAuth = class {
|
|
|
274
274
|
if (["failed", "cancelled", "canceled"].includes(String(data.status))) {
|
|
275
275
|
throw new APIError("Account API job failed or was cancelled", 502, sanitizeErrorResponse(data));
|
|
276
276
|
}
|
|
277
|
-
await new Promise((
|
|
277
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(interval, Math.max(0, deadline - Date.now()))));
|
|
278
278
|
const remaining = deadline - Date.now();
|
|
279
279
|
if (remaining <= 0) break;
|
|
280
280
|
try {
|
|
@@ -4539,7 +4539,7 @@ function getCostSummary() {
|
|
|
4539
4539
|
}
|
|
4540
4540
|
|
|
4541
4541
|
// src/version.ts
|
|
4542
|
-
var SDK_VERSION = "3.
|
|
4542
|
+
var SDK_VERSION = "3.19.1";
|
|
4543
4543
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4544
4544
|
|
|
4545
4545
|
// src/client.ts
|
|
@@ -5648,8 +5648,8 @@ var LLMClient = class _LLMClient {
|
|
|
5648
5648
|
* @example
|
|
5649
5649
|
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
5650
5650
|
*/
|
|
5651
|
-
async exa(
|
|
5652
|
-
return this.requestWithPaymentRaw(`/v1/exa/${
|
|
5651
|
+
async exa(path7, body) {
|
|
5652
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path7}`, body);
|
|
5653
5653
|
}
|
|
5654
5654
|
/**
|
|
5655
5655
|
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
|
|
@@ -5797,8 +5797,8 @@ var LLMClient = class _LLMClient {
|
|
|
5797
5797
|
* const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
|
|
5798
5798
|
* const results = await client.pm("polymarket/search", { q: "bitcoin" });
|
|
5799
5799
|
*/
|
|
5800
|
-
async pm(
|
|
5801
|
-
return this.getWithPaymentRaw(`/v1/pm/${
|
|
5800
|
+
async pm(path7, params) {
|
|
5801
|
+
return this.getWithPaymentRaw(`/v1/pm/${path7}`, params);
|
|
5802
5802
|
}
|
|
5803
5803
|
/**
|
|
5804
5804
|
* Structured query for Predexon prediction market data (POST endpoints).
|
|
@@ -5814,8 +5814,8 @@ var LLMClient = class _LLMClient {
|
|
|
5814
5814
|
* addresses: ["0xabc...", "0xdef..."],
|
|
5815
5815
|
* });
|
|
5816
5816
|
*/
|
|
5817
|
-
async pmQuery(
|
|
5818
|
-
return this.requestWithPaymentRaw(`/v1/pm/${
|
|
5817
|
+
async pmQuery(path7, query) {
|
|
5818
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path7}`, query);
|
|
5819
5819
|
}
|
|
5820
5820
|
// ── PM convenience helpers (Predexon v2) ─────────────────────────────────
|
|
5821
5821
|
// Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
|
|
@@ -5903,8 +5903,8 @@ var LLMClient = class _LLMClient {
|
|
|
5903
5903
|
* "prices/coingecko:bitcoin,base:0x..."
|
|
5904
5904
|
* @param params - Query parameters passed through to DefiLlama
|
|
5905
5905
|
*/
|
|
5906
|
-
async defi(
|
|
5907
|
-
return this.getWithPaymentRaw(`/v1/defillama/${
|
|
5906
|
+
async defi(path7, params) {
|
|
5907
|
+
return this.getWithPaymentRaw(`/v1/defillama/${path7}`, params);
|
|
5908
5908
|
}
|
|
5909
5909
|
/** All DeFi protocols with TVL. */
|
|
5910
5910
|
async defiProtocols() {
|
|
@@ -5940,9 +5940,9 @@ var LLMClient = class _LLMClient {
|
|
|
5940
5940
|
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
|
|
5941
5941
|
* @param body - JSON body — pass to switch to POST (gasless/submit only)
|
|
5942
5942
|
*/
|
|
5943
|
-
async dex(
|
|
5944
|
-
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${
|
|
5945
|
-
return this.getWithPaymentRaw(`/v1/zerox/${
|
|
5943
|
+
async dex(path7, params, body) {
|
|
5944
|
+
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${path7}`, body);
|
|
5945
|
+
return this.getWithPaymentRaw(`/v1/zerox/${path7}`, params);
|
|
5946
5946
|
}
|
|
5947
5947
|
/** Indicative Permit2 swap price — no commitment (free). */
|
|
5948
5948
|
async dexPrice(params) {
|
|
@@ -5986,8 +5986,8 @@ var LLMClient = class _LLMClient {
|
|
|
5986
5986
|
* "sandbox/status", "sandbox/terminate"
|
|
5987
5987
|
* @param body - JSON body for the endpoint
|
|
5988
5988
|
*/
|
|
5989
|
-
async modal(
|
|
5990
|
-
return this.requestWithPaymentRaw(`/v1/modal/${
|
|
5989
|
+
async modal(path7, body) {
|
|
5990
|
+
return this.requestWithPaymentRaw(`/v1/modal/${path7}`, body ?? {});
|
|
5991
5991
|
}
|
|
5992
5992
|
/** Create a sandboxed compute environment (CPU / GPU tiers). */
|
|
5993
5993
|
async modalSandboxCreate(body) {
|
|
@@ -6295,7 +6295,7 @@ var ImageClient = class {
|
|
|
6295
6295
|
const pollUrl = pollPath.startsWith("/api/") ? `${this.apiUrl.replace(/\/api$/, "")}${pollPath}` : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
6296
6296
|
const startedAt = Date.now();
|
|
6297
6297
|
while (Date.now() - startedAt < POLL_MAX_DURATION_MS) {
|
|
6298
|
-
await new Promise((
|
|
6298
|
+
await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL_MS));
|
|
6299
6299
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
6300
6300
|
method: "GET",
|
|
6301
6301
|
headers: {
|
|
@@ -7493,8 +7493,8 @@ var PhoneClient = class {
|
|
|
7493
7493
|
return data;
|
|
7494
7494
|
}
|
|
7495
7495
|
// ─── Internals ─────────────────────────────────────────────────────────
|
|
7496
|
-
async request(
|
|
7497
|
-
const url = `${this.apiUrl}/v1/phone/${
|
|
7496
|
+
async request(path7, body) {
|
|
7497
|
+
const url = `${this.apiUrl}/v1/phone/${path7}`;
|
|
7498
7498
|
const response = await this.fetchWithTimeout(url, {
|
|
7499
7499
|
method: "POST",
|
|
7500
7500
|
headers: { "Content-Type": "application/json" },
|
|
@@ -7935,10 +7935,10 @@ var PriceClient = class {
|
|
|
7935
7935
|
}
|
|
7936
7936
|
async price(category, symbol, options) {
|
|
7937
7937
|
if (!symbol) throw new Error("symbol is required");
|
|
7938
|
-
const
|
|
7938
|
+
const path7 = categoryPath(category, options?.market, "price", symbol);
|
|
7939
7939
|
const query = {};
|
|
7940
7940
|
if (options?.session) query.session = options.session;
|
|
7941
|
-
const data = await this.getWithPayment(
|
|
7941
|
+
const data = await this.getWithPayment(path7, query);
|
|
7942
7942
|
return {
|
|
7943
7943
|
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
7944
7944
|
price: data.price,
|
|
@@ -7957,14 +7957,14 @@ var PriceClient = class {
|
|
|
7957
7957
|
if (!options.from || options.from <= 0) {
|
|
7958
7958
|
throw new Error("history requires options.from (unix seconds)");
|
|
7959
7959
|
}
|
|
7960
|
-
const
|
|
7960
|
+
const path7 = categoryPath(category, options.market, "history", symbol);
|
|
7961
7961
|
const query = {
|
|
7962
7962
|
resolution: options.resolution ?? "D",
|
|
7963
7963
|
from: String(options.from)
|
|
7964
7964
|
};
|
|
7965
7965
|
if (options.to) query.to = String(options.to);
|
|
7966
7966
|
if (options.session) query.session = options.session;
|
|
7967
|
-
const data = await this.getWithPayment(
|
|
7967
|
+
const data = await this.getWithPayment(path7, query);
|
|
7968
7968
|
return {
|
|
7969
7969
|
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
7970
7970
|
resolution: data.resolution ?? (options.resolution ?? "D"),
|
|
@@ -7976,12 +7976,12 @@ var PriceClient = class {
|
|
|
7976
7976
|
};
|
|
7977
7977
|
}
|
|
7978
7978
|
async listSymbols(category, options) {
|
|
7979
|
-
const
|
|
7979
|
+
const path7 = categoryPath(category, options?.market, "list");
|
|
7980
7980
|
const query = {
|
|
7981
7981
|
limit: String(options?.limit && options.limit > 0 ? options.limit : 100)
|
|
7982
7982
|
};
|
|
7983
7983
|
if (options?.query) query.q = options.query;
|
|
7984
|
-
const data = await this.getWithPayment(
|
|
7984
|
+
const data = await this.getWithPayment(path7, query);
|
|
7985
7985
|
if (Array.isArray(data)) {
|
|
7986
7986
|
return { symbols: data, count: data.length };
|
|
7987
7987
|
}
|
|
@@ -8154,19 +8154,19 @@ var SurfClient = class {
|
|
|
8154
8154
|
* `/v1/surf` is tolerated and stripped). Query params are URL-encoded;
|
|
8155
8155
|
* arrays become repeated keys (`?a=1&a=2`).
|
|
8156
8156
|
*/
|
|
8157
|
-
async get(
|
|
8158
|
-
const url = this.buildUrl(
|
|
8157
|
+
async get(path7, params) {
|
|
8158
|
+
const url = this.buildUrl(path7, params);
|
|
8159
8159
|
return this.requestWithPayment(url, "GET");
|
|
8160
8160
|
}
|
|
8161
8161
|
/**
|
|
8162
8162
|
* POST a Surf endpoint with a JSON body. Same path normalization as `get`.
|
|
8163
8163
|
*/
|
|
8164
|
-
async post(
|
|
8165
|
-
const url = this.buildUrl(
|
|
8164
|
+
async post(path7, body) {
|
|
8165
|
+
const url = this.buildUrl(path7);
|
|
8166
8166
|
return this.requestWithPayment(url, "POST", body);
|
|
8167
8167
|
}
|
|
8168
|
-
buildUrl(
|
|
8169
|
-
let normalized =
|
|
8168
|
+
buildUrl(path7, params) {
|
|
8169
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
8170
8170
|
if (!normalized.startsWith(SURF_PREFIX)) {
|
|
8171
8171
|
normalized = `${SURF_PREFIX}${normalized}`;
|
|
8172
8172
|
}
|
|
@@ -8624,15 +8624,15 @@ var BlockrunClient = class {
|
|
|
8624
8624
|
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
|
|
8625
8625
|
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
|
|
8626
8626
|
*/
|
|
8627
|
-
async get(
|
|
8628
|
-
const url = this.buildUrl(
|
|
8627
|
+
async get(path7, params) {
|
|
8628
|
+
const url = this.buildUrl(path7, params);
|
|
8629
8629
|
return this.requestWithPayment(url, "GET");
|
|
8630
8630
|
}
|
|
8631
8631
|
/**
|
|
8632
8632
|
* POST a BlockRun endpoint with a JSON body.
|
|
8633
8633
|
*/
|
|
8634
|
-
async post(
|
|
8635
|
-
const url = this.buildUrl(
|
|
8634
|
+
async post(path7, body) {
|
|
8635
|
+
const url = this.buildUrl(path7);
|
|
8636
8636
|
return this.requestWithPayment(url, "POST", body);
|
|
8637
8637
|
}
|
|
8638
8638
|
/**
|
|
@@ -8656,8 +8656,8 @@ var BlockrunClient = class {
|
|
|
8656
8656
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8657
8657
|
* video, music, voice) return 202 with a poll_url.
|
|
8658
8658
|
*/
|
|
8659
|
-
async poll(
|
|
8660
|
-
const submitUrl = this.buildUrl(
|
|
8659
|
+
async poll(path7, body, options) {
|
|
8660
|
+
const submitUrl = this.buildUrl(path7);
|
|
8661
8661
|
const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
|
|
8662
8662
|
const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
8663
8663
|
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
@@ -8757,8 +8757,8 @@ var BlockrunClient = class {
|
|
|
8757
8757
|
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
8758
8758
|
* }
|
|
8759
8759
|
*/
|
|
8760
|
-
async *stream(
|
|
8761
|
-
const url = this.buildUrl(
|
|
8760
|
+
async *stream(path7, body) {
|
|
8761
|
+
const url = this.buildUrl(path7);
|
|
8762
8762
|
const requestBody = JSON.stringify(body ?? {});
|
|
8763
8763
|
const resp402 = await this.fetchWithTimeout(url, {
|
|
8764
8764
|
method: "POST",
|
|
@@ -8801,8 +8801,8 @@ var BlockrunClient = class {
|
|
|
8801
8801
|
// --------------------------------------------------------------------
|
|
8802
8802
|
// Internal: shared infrastructure
|
|
8803
8803
|
// --------------------------------------------------------------------
|
|
8804
|
-
buildUrl(
|
|
8805
|
-
let normalized =
|
|
8804
|
+
buildUrl(path7, params) {
|
|
8805
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
8806
8806
|
if (normalized.startsWith("/api/")) {
|
|
8807
8807
|
normalized = normalized.slice(4);
|
|
8808
8808
|
}
|
|
@@ -9448,6 +9448,333 @@ async function getOrCreateSolanaWallet() {
|
|
|
9448
9448
|
return { address, privateKey, isNew: true };
|
|
9449
9449
|
}
|
|
9450
9450
|
|
|
9451
|
+
// src/solana-batch.ts
|
|
9452
|
+
import * as fs6 from "fs";
|
|
9453
|
+
import * as path5 from "path";
|
|
9454
|
+
var BATCH_SCHEME = "batch-settlement";
|
|
9455
|
+
var BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
|
|
9456
|
+
var INSTALL_HINT2 = "npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit";
|
|
9457
|
+
async function load(pkg, importer) {
|
|
9458
|
+
try {
|
|
9459
|
+
return await importer();
|
|
9460
|
+
} catch (err) {
|
|
9461
|
+
throw new Error(
|
|
9462
|
+
`@blockrun/llm: Solana batch-settlement requires the optional peer dependency "${pkg}", which is not installed.
|
|
9463
|
+
|
|
9464
|
+
${INSTALL_HINT2}
|
|
9465
|
+
|
|
9466
|
+
Original error: ${err instanceof Error ? err.message : String(err)}`,
|
|
9467
|
+
{ cause: err }
|
|
9468
|
+
);
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
var FileChannelStorage = class {
|
|
9472
|
+
constructor(file) {
|
|
9473
|
+
this.file = file;
|
|
9474
|
+
}
|
|
9475
|
+
file;
|
|
9476
|
+
read() {
|
|
9477
|
+
try {
|
|
9478
|
+
const parsed = JSON.parse(fs6.readFileSync(this.file, "utf8"));
|
|
9479
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
9480
|
+
} catch (err) {
|
|
9481
|
+
if (err.code === "ENOENT") return {};
|
|
9482
|
+
throw err;
|
|
9483
|
+
}
|
|
9484
|
+
}
|
|
9485
|
+
write(all) {
|
|
9486
|
+
fs6.mkdirSync(path5.dirname(this.file), { recursive: true, mode: 448 });
|
|
9487
|
+
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
9488
|
+
fs6.writeFileSync(tmp, JSON.stringify(all, null, 2), { mode: 384 });
|
|
9489
|
+
fs6.renameSync(tmp, this.file);
|
|
9490
|
+
}
|
|
9491
|
+
async get(key) {
|
|
9492
|
+
return this.read()[key];
|
|
9493
|
+
}
|
|
9494
|
+
async set(key, record) {
|
|
9495
|
+
const all = this.read();
|
|
9496
|
+
all[key] = record;
|
|
9497
|
+
this.write(all);
|
|
9498
|
+
}
|
|
9499
|
+
async delete(key) {
|
|
9500
|
+
const all = this.read();
|
|
9501
|
+
if (!(key in all)) return;
|
|
9502
|
+
delete all[key];
|
|
9503
|
+
this.write(all);
|
|
9504
|
+
}
|
|
9505
|
+
/** Forget every channel in this file. */
|
|
9506
|
+
clear() {
|
|
9507
|
+
try {
|
|
9508
|
+
fs6.unlinkSync(this.file);
|
|
9509
|
+
} catch (err) {
|
|
9510
|
+
if (err.code !== "ENOENT") throw err;
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
};
|
|
9514
|
+
function processAlive(pid) {
|
|
9515
|
+
try {
|
|
9516
|
+
process.kill(pid, 0);
|
|
9517
|
+
return true;
|
|
9518
|
+
} catch (err) {
|
|
9519
|
+
return err.code === "EPERM";
|
|
9520
|
+
}
|
|
9521
|
+
}
|
|
9522
|
+
var heldLocks = /* @__PURE__ */ new Set();
|
|
9523
|
+
var exitHookInstalled = false;
|
|
9524
|
+
function lockChannelFile(file) {
|
|
9525
|
+
if (heldLocks.has(file)) return void 0;
|
|
9526
|
+
const lock = `${file}.lock`;
|
|
9527
|
+
fs6.mkdirSync(path5.dirname(lock), { recursive: true, mode: 448 });
|
|
9528
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
9529
|
+
try {
|
|
9530
|
+
fs6.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
|
|
9531
|
+
heldLocks.add(file);
|
|
9532
|
+
if (!exitHookInstalled) {
|
|
9533
|
+
exitHookInstalled = true;
|
|
9534
|
+
process.once("exit", () => {
|
|
9535
|
+
for (const held of heldLocks) {
|
|
9536
|
+
try {
|
|
9537
|
+
fs6.unlinkSync(`${held}.lock`);
|
|
9538
|
+
} catch {
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
});
|
|
9542
|
+
}
|
|
9543
|
+
return void 0;
|
|
9544
|
+
} catch (err) {
|
|
9545
|
+
if (err.code !== "EEXIST") throw err;
|
|
9546
|
+
}
|
|
9547
|
+
const owner = Number(fs6.readFileSync(lock, "utf8").trim());
|
|
9548
|
+
if (Number.isInteger(owner) && owner > 0 && owner !== process.pid && processAlive(owner)) return owner;
|
|
9549
|
+
try {
|
|
9550
|
+
fs6.unlinkSync(lock);
|
|
9551
|
+
} catch {
|
|
9552
|
+
}
|
|
9553
|
+
}
|
|
9554
|
+
return -1;
|
|
9555
|
+
}
|
|
9556
|
+
function pendingDeposit(pending) {
|
|
9557
|
+
const entries = Array.isArray(pending) ? pending : [pending];
|
|
9558
|
+
return entries.some(
|
|
9559
|
+
(entry) => entry?.payment?.payload?.type === "deposit"
|
|
9560
|
+
);
|
|
9561
|
+
}
|
|
9562
|
+
function dropOrphanedPending(storage) {
|
|
9563
|
+
return {
|
|
9564
|
+
get: async (key) => {
|
|
9565
|
+
const record = await storage.get(key);
|
|
9566
|
+
if (!record?.pending || record.channelConfig?.voucherSigner !== "server") return record;
|
|
9567
|
+
if (!record.hasConfirmedState || pendingDeposit(record.pending)) {
|
|
9568
|
+
await storage.delete(key);
|
|
9569
|
+
return void 0;
|
|
9570
|
+
}
|
|
9571
|
+
const { pending: _pending, hasConfirmedState: _confirmed, ...confirmed } = record;
|
|
9572
|
+
await storage.set(key, confirmed);
|
|
9573
|
+
return confirmed;
|
|
9574
|
+
},
|
|
9575
|
+
set: (key, record) => storage.set(key, record),
|
|
9576
|
+
delete: (key) => storage.delete(key)
|
|
9577
|
+
};
|
|
9578
|
+
}
|
|
9579
|
+
function offersBatch(paymentRequired) {
|
|
9580
|
+
return (paymentRequired.accepts ?? []).some((accept) => accept.scheme === BATCH_SCHEME);
|
|
9581
|
+
}
|
|
9582
|
+
function atomicToUsd(amount) {
|
|
9583
|
+
return amount !== void 0 && /^\d+$/.test(amount) ? Number(amount) / 1e6 : void 0;
|
|
9584
|
+
}
|
|
9585
|
+
async function isBatchRefusal(response) {
|
|
9586
|
+
if (response.status === 402) return "payment_required";
|
|
9587
|
+
if (![400, 403, 409, 503].includes(response.status)) return void 0;
|
|
9588
|
+
let body;
|
|
9589
|
+
try {
|
|
9590
|
+
body = await response.clone().json();
|
|
9591
|
+
} catch {
|
|
9592
|
+
return void 0;
|
|
9593
|
+
}
|
|
9594
|
+
const { error, code } = body ?? {};
|
|
9595
|
+
if (code === "PAYMENT_VERIFICATION_UNAVAILABLE") return code;
|
|
9596
|
+
return typeof error === "string" && error.startsWith("batch_") ? error : void 0;
|
|
9597
|
+
}
|
|
9598
|
+
var wallets = /* @__PURE__ */ new Map();
|
|
9599
|
+
var SolanaBatchPayer = class {
|
|
9600
|
+
constructor(init) {
|
|
9601
|
+
this.init = init;
|
|
9602
|
+
const { operators } = init.options;
|
|
9603
|
+
if (!Array.isArray(operators) || operators.length === 0 || operators.some((o) => typeof o !== "string" || !o)) {
|
|
9604
|
+
throw new Error("batch.operators must list at least one base58 operator public key");
|
|
9605
|
+
}
|
|
9606
|
+
}
|
|
9607
|
+
init;
|
|
9608
|
+
state;
|
|
9609
|
+
async storeFile() {
|
|
9610
|
+
const { channelStore } = this.init.options;
|
|
9611
|
+
if (channelStore === false) return void 0;
|
|
9612
|
+
return path5.resolve(channelStore ?? path5.join(paths().dir, "solana-batch", `${await this.init.address()}.json`));
|
|
9613
|
+
}
|
|
9614
|
+
/**
|
|
9615
|
+
* This wallet's shared batch state, or why this client cannot use batch:
|
|
9616
|
+
* another client in this process holds the wallet with a different trust
|
|
9617
|
+
* configuration, or another live process owns its channel file.
|
|
9618
|
+
*/
|
|
9619
|
+
wallet() {
|
|
9620
|
+
this.state ??= (async () => {
|
|
9621
|
+
const address = await this.init.address();
|
|
9622
|
+
const file = await this.storeFile();
|
|
9623
|
+
const { operators, maxDeposit } = this.init.options;
|
|
9624
|
+
const config = JSON.stringify([[...operators].sort(), String(maxDeposit ?? ""), file ?? null, this.init.rpcUrl]);
|
|
9625
|
+
const existing = wallets.get(address);
|
|
9626
|
+
if (existing) {
|
|
9627
|
+
return existing.config === config ? existing : "another client in this process uses this wallet with different batch options";
|
|
9628
|
+
}
|
|
9629
|
+
if (file) {
|
|
9630
|
+
const owner = lockChannelFile(file);
|
|
9631
|
+
if (owner !== void 0) return `channel store ${file} is in use by process ${owner}`;
|
|
9632
|
+
}
|
|
9633
|
+
const created = { config, busy: false, warned: /* @__PURE__ */ new Set() };
|
|
9634
|
+
wallets.set(address, created);
|
|
9635
|
+
return created;
|
|
9636
|
+
})();
|
|
9637
|
+
return this.state;
|
|
9638
|
+
}
|
|
9639
|
+
build(wallet) {
|
|
9640
|
+
wallet.client ??= (async () => {
|
|
9641
|
+
const [core, svm, kit] = await Promise.all([
|
|
9642
|
+
load("@x402/core", () => import("@x402/core/client")),
|
|
9643
|
+
load("@x402/svm", () => import("@x402/svm/batch-settlement/client")),
|
|
9644
|
+
load("@solana/kit", () => import("@solana/kit"))
|
|
9645
|
+
]);
|
|
9646
|
+
const signer = await kit.createKeyPairSignerFromBytes(await this.init.secretKey());
|
|
9647
|
+
const file = await this.storeFile();
|
|
9648
|
+
const storage = file ? dropOrphanedPending(new FileChannelStorage(file)) : void 0;
|
|
9649
|
+
const scheme = new svm.BatchSvmScheme(signer, {
|
|
9650
|
+
rpcUrl: this.init.rpcUrl,
|
|
9651
|
+
serverSignedChannelsPolicy: {
|
|
9652
|
+
allowedOperators: this.init.options.operators,
|
|
9653
|
+
maxDeposit: this.init.options.maxDeposit ?? svm.DEFAULT_SERVER_SIGNED_MAX_DEPOSIT
|
|
9654
|
+
},
|
|
9655
|
+
...storage ? { channelStorage: storage } : {}
|
|
9656
|
+
});
|
|
9657
|
+
const client = new core.x402Client().register("solana:*", scheme).registerPolicy(scheme.paymentPolicy);
|
|
9658
|
+
client.setSpendControls({ maxAmountPerPayment: false });
|
|
9659
|
+
return {
|
|
9660
|
+
http: new core.x402HTTPClient(client),
|
|
9661
|
+
refund: (url) => scheme.refund(url)
|
|
9662
|
+
};
|
|
9663
|
+
})();
|
|
9664
|
+
wallet.client.catch(() => {
|
|
9665
|
+
wallet.client = void 0;
|
|
9666
|
+
});
|
|
9667
|
+
return wallet.client;
|
|
9668
|
+
}
|
|
9669
|
+
/**
|
|
9670
|
+
* Drop everything this wallet knows about its channel, on disk and in
|
|
9671
|
+
* memory. The next call rebuilds the scheme, which reads the channel's real
|
|
9672
|
+
* state (deposit, open or closed) from the chain.
|
|
9673
|
+
*/
|
|
9674
|
+
async forget(wallet) {
|
|
9675
|
+
wallet.client = void 0;
|
|
9676
|
+
const file = await this.storeFile();
|
|
9677
|
+
if (file) new FileChannelStorage(file).clear();
|
|
9678
|
+
}
|
|
9679
|
+
warn(wallet, reason) {
|
|
9680
|
+
if (wallet?.warned.has(reason)) return;
|
|
9681
|
+
wallet?.warned.add(reason);
|
|
9682
|
+
console.error(`[@blockrun/llm] batch-settlement unavailable (${reason}); paying with exact`);
|
|
9683
|
+
}
|
|
9684
|
+
/**
|
|
9685
|
+
* Pay one 402 with batch-settlement, or say why the caller should use exact.
|
|
9686
|
+
*
|
|
9687
|
+
* `send` replays the original request with the payment headers. The
|
|
9688
|
+
* returned response is 2xx; any other answer either becomes a fallback (the
|
|
9689
|
+
* gateway charged nothing) or is returned to the caller's own error path.
|
|
9690
|
+
*/
|
|
9691
|
+
async pay(paymentRequired, send) {
|
|
9692
|
+
if (!offersBatch(paymentRequired)) return { kind: "fallback", reason: "not_offered" };
|
|
9693
|
+
const wallet = await this.wallet();
|
|
9694
|
+
if (typeof wallet === "string") {
|
|
9695
|
+
this.warn(void 0, wallet);
|
|
9696
|
+
return { kind: "fallback", reason: wallet };
|
|
9697
|
+
}
|
|
9698
|
+
if (wallet.busy) return { kind: "fallback", reason: "channel_busy" };
|
|
9699
|
+
wallet.busy = true;
|
|
9700
|
+
try {
|
|
9701
|
+
let http;
|
|
9702
|
+
let payload;
|
|
9703
|
+
try {
|
|
9704
|
+
http = (await this.build(wallet)).http;
|
|
9705
|
+
payload = await http.createPaymentPayload(paymentRequired);
|
|
9706
|
+
} catch (err) {
|
|
9707
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9708
|
+
this.warn(wallet, reason);
|
|
9709
|
+
return { kind: "fallback", reason };
|
|
9710
|
+
}
|
|
9711
|
+
const deposit = payload.payload?.type === "deposit";
|
|
9712
|
+
let response;
|
|
9713
|
+
try {
|
|
9714
|
+
response = await send(http.encodePaymentSignatureHeader(payload));
|
|
9715
|
+
} catch (err) {
|
|
9716
|
+
if (deposit) await this.forget(wallet);
|
|
9717
|
+
else await this.settle(http, payload, () => null, 0);
|
|
9718
|
+
throw err;
|
|
9719
|
+
}
|
|
9720
|
+
const getHeader = (name) => response.headers.get(name);
|
|
9721
|
+
const settled = await this.settle(http, payload, getHeader, response.status);
|
|
9722
|
+
if (deposit && !(response.ok && settled?.success === true)) await this.forget(wallet);
|
|
9723
|
+
if (response.ok) {
|
|
9724
|
+
const charged = atomicToUsd(settled?.amount) ?? atomicToUsd(payload.accepted?.amount) ?? 0;
|
|
9725
|
+
return { kind: "paid", response, chargedUsd: charged };
|
|
9726
|
+
}
|
|
9727
|
+
const refusal = await isBatchRefusal(response);
|
|
9728
|
+
if (refusal) {
|
|
9729
|
+
this.warn(wallet, refusal);
|
|
9730
|
+
return { kind: "fallback", reason: refusal };
|
|
9731
|
+
}
|
|
9732
|
+
return { kind: "failed", response };
|
|
9733
|
+
} finally {
|
|
9734
|
+
wallet.busy = false;
|
|
9735
|
+
}
|
|
9736
|
+
}
|
|
9737
|
+
/**
|
|
9738
|
+
* Hand the gateway's answer to the scheme, which verifies the operator's
|
|
9739
|
+
* voucher and advances (or rolls back) the channel's local state.
|
|
9740
|
+
*
|
|
9741
|
+
* A malformed receipt must not lose a response the caller has been served,
|
|
9742
|
+
* so a scheme error is logged rather than thrown.
|
|
9743
|
+
*/
|
|
9744
|
+
async settle(http, payload, getHeader, status2) {
|
|
9745
|
+
try {
|
|
9746
|
+
return (await http.processPaymentResult(payload, getHeader, status2)).settleResponse;
|
|
9747
|
+
} catch (err) {
|
|
9748
|
+
console.error(
|
|
9749
|
+
`[@blockrun/llm] batch-settlement receipt not reconciled: ${err instanceof Error ? err.message : String(err)}`
|
|
9750
|
+
);
|
|
9751
|
+
return void 0;
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9754
|
+
/**
|
|
9755
|
+
* Close the channel and return its unused escrow to the wallet.
|
|
9756
|
+
*
|
|
9757
|
+
* The scheme keeps a closed channel in its records, and would go on paying
|
|
9758
|
+
* into it, so a successful close forgets the channel. The next batch call
|
|
9759
|
+
* finds no open channel on-chain and opens a new one.
|
|
9760
|
+
*
|
|
9761
|
+
* @param url - any batch-enabled route on the gateway the channel was opened with.
|
|
9762
|
+
*/
|
|
9763
|
+
async close(url) {
|
|
9764
|
+
const wallet = await this.wallet();
|
|
9765
|
+
if (typeof wallet === "string") throw new Error(`batch-settlement unavailable: ${wallet}`);
|
|
9766
|
+
if (wallet.busy) throw new Error("batch-settlement channel has a request in flight; close it when the call returns");
|
|
9767
|
+
wallet.busy = true;
|
|
9768
|
+
try {
|
|
9769
|
+
const result = await (await this.build(wallet)).refund(url);
|
|
9770
|
+
await this.forget(wallet);
|
|
9771
|
+
return result;
|
|
9772
|
+
} finally {
|
|
9773
|
+
wallet.busy = false;
|
|
9774
|
+
}
|
|
9775
|
+
}
|
|
9776
|
+
};
|
|
9777
|
+
|
|
9451
9778
|
// src/solana-client.ts
|
|
9452
9779
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
9453
9780
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
@@ -9511,7 +9838,7 @@ async function isSafeStaleBlockhashResponse(response) {
|
|
|
9511
9838
|
}
|
|
9512
9839
|
async function waitForStaleRetry(attempt) {
|
|
9513
9840
|
await new Promise(
|
|
9514
|
-
(
|
|
9841
|
+
(resolve2) => setTimeout(resolve2, STALE_BLOCKHASH_RETRY_BACKOFFS_MS[attempt])
|
|
9515
9842
|
);
|
|
9516
9843
|
}
|
|
9517
9844
|
var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
@@ -9555,6 +9882,7 @@ var SolanaLLMClient = class {
|
|
|
9555
9882
|
addressCache = null;
|
|
9556
9883
|
modelPricingCache = null;
|
|
9557
9884
|
modelPricingPromise = null;
|
|
9885
|
+
batchPayer;
|
|
9558
9886
|
constructor(options = {}) {
|
|
9559
9887
|
this.apiAuth = resolveApiKeyAuth(options);
|
|
9560
9888
|
if (!this.apiAuth) {
|
|
@@ -9574,6 +9902,25 @@ var SolanaLLMClient = class {
|
|
|
9574
9902
|
this.rpcUrl = rpc.url;
|
|
9575
9903
|
this.rpcHeaders = rpc.headers;
|
|
9576
9904
|
this.timeout = options.timeout || DEFAULT_TIMEOUT14;
|
|
9905
|
+
if (options.batch && !this.apiAuth) {
|
|
9906
|
+
this.batchPayer = new SolanaBatchPayer({
|
|
9907
|
+
options: options.batch,
|
|
9908
|
+
secretKey: () => solanaKeyToBytes(this.privateKey),
|
|
9909
|
+
address: () => this.getWalletAddress(),
|
|
9910
|
+
rpcUrl: this.rpcUrl
|
|
9911
|
+
});
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
/**
|
|
9915
|
+
* Close this wallet's batch-settlement channel and return its unused escrow.
|
|
9916
|
+
*
|
|
9917
|
+
* The gateway closes it cooperatively when it can; otherwise this starts a
|
|
9918
|
+
* payer-forced close and the escrow comes back after the channel's grace
|
|
9919
|
+
* period. Only meaningful with the `batch` option.
|
|
9920
|
+
*/
|
|
9921
|
+
async closeBatchChannel() {
|
|
9922
|
+
if (!this.batchPayer) throw new Error("closeBatchChannel() requires the `batch` option in wallet mode");
|
|
9923
|
+
return this.batchPayer.close(`${this.apiUrl}/v1/chat/completions`);
|
|
9577
9924
|
}
|
|
9578
9925
|
/** Get Solana wallet address (public key in base58). */
|
|
9579
9926
|
async getWalletAddress() {
|
|
@@ -9767,8 +10114,8 @@ var SolanaLLMClient = class {
|
|
|
9767
10114
|
* how you end up debugging a request you did not send.
|
|
9768
10115
|
* @returns each decoded SSE frame, in order.
|
|
9769
10116
|
*/
|
|
9770
|
-
async *stream(
|
|
9771
|
-
const url = this.buildUrl(
|
|
10117
|
+
async *stream(path7, body) {
|
|
10118
|
+
const url = this.buildUrl(path7);
|
|
9772
10119
|
const response = await this.openPaidStream(url, JSON.stringify(body ?? {}));
|
|
9773
10120
|
yield* readSseFrames(
|
|
9774
10121
|
response,
|
|
@@ -9840,8 +10187,8 @@ var SolanaLLMClient = class {
|
|
|
9840
10187
|
* @param path - endpoint path, with or without a leading slash.
|
|
9841
10188
|
* @returns the absolute URL to call.
|
|
9842
10189
|
*/
|
|
9843
|
-
buildUrl(
|
|
9844
|
-
let normalized =
|
|
10190
|
+
buildUrl(path7) {
|
|
10191
|
+
let normalized = path7.startsWith("/") ? path7 : `/${path7}`;
|
|
9845
10192
|
if (normalized.startsWith("/api/")) normalized = normalized.slice(4);
|
|
9846
10193
|
return `${this.apiUrl}${normalized}`;
|
|
9847
10194
|
}
|
|
@@ -9920,11 +10267,11 @@ var SolanaLLMClient = class {
|
|
|
9920
10267
|
return data;
|
|
9921
10268
|
}
|
|
9922
10269
|
// ── Prediction Markets (Powered by Predexon) ────────────────────────────
|
|
9923
|
-
async pm(
|
|
9924
|
-
return this.getWithPaymentRaw(`/v1/pm/${
|
|
10270
|
+
async pm(path7, params) {
|
|
10271
|
+
return this.getWithPaymentRaw(`/v1/pm/${path7}`, params);
|
|
9925
10272
|
}
|
|
9926
|
-
async pmQuery(
|
|
9927
|
-
return this.requestWithPaymentRaw(`/v1/pm/${
|
|
10273
|
+
async pmQuery(path7, query) {
|
|
10274
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path7}`, query);
|
|
9928
10275
|
}
|
|
9929
10276
|
// ── Exa Web Search (Powered by Exa) ──────────────────────────────────────
|
|
9930
10277
|
/**
|
|
@@ -9936,8 +10283,8 @@ var SolanaLLMClient = class {
|
|
|
9936
10283
|
* @example
|
|
9937
10284
|
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
9938
10285
|
*/
|
|
9939
|
-
async exa(
|
|
9940
|
-
return this.requestWithPaymentRaw(`/v1/exa/${
|
|
10286
|
+
async exa(path7, body) {
|
|
10287
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path7}`, body);
|
|
9941
10288
|
}
|
|
9942
10289
|
/**
|
|
9943
10290
|
* Neural and keyword web search via Exa (Solana payment).
|
|
@@ -9987,8 +10334,8 @@ var SolanaLLMClient = class {
|
|
|
9987
10334
|
* "prices/coingecko:bitcoin,base:0x..."
|
|
9988
10335
|
* @param params - Query parameters passed through to DefiLlama
|
|
9989
10336
|
*/
|
|
9990
|
-
async defi(
|
|
9991
|
-
return this.getWithPaymentRaw(`/v1/defillama/${
|
|
10337
|
+
async defi(path7, params) {
|
|
10338
|
+
return this.getWithPaymentRaw(`/v1/defillama/${path7}`, params);
|
|
9992
10339
|
}
|
|
9993
10340
|
/** All DeFi protocols with TVL. */
|
|
9994
10341
|
async defiProtocols() {
|
|
@@ -10024,9 +10371,9 @@ var SolanaLLMClient = class {
|
|
|
10024
10371
|
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
|
|
10025
10372
|
* @param body - JSON body — pass to switch to POST (gasless/submit only)
|
|
10026
10373
|
*/
|
|
10027
|
-
async dex(
|
|
10028
|
-
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${
|
|
10029
|
-
return this.getWithPaymentRaw(`/v1/zerox/${
|
|
10374
|
+
async dex(path7, params, body) {
|
|
10375
|
+
if (body) return this.requestWithPaymentRaw(`/v1/zerox/${path7}`, body);
|
|
10376
|
+
return this.getWithPaymentRaw(`/v1/zerox/${path7}`, params);
|
|
10030
10377
|
}
|
|
10031
10378
|
/** Indicative Permit2 swap price — no commitment (free). */
|
|
10032
10379
|
async dexPrice(params) {
|
|
@@ -10070,8 +10417,8 @@ var SolanaLLMClient = class {
|
|
|
10070
10417
|
* "sandbox/status", "sandbox/terminate"
|
|
10071
10418
|
* @param body - JSON body for the endpoint
|
|
10072
10419
|
*/
|
|
10073
|
-
async modal(
|
|
10074
|
-
return this.requestWithPaymentRaw(`/v1/modal/${
|
|
10420
|
+
async modal(path7, body) {
|
|
10421
|
+
return this.requestWithPaymentRaw(`/v1/modal/${path7}`, body ?? {});
|
|
10075
10422
|
}
|
|
10076
10423
|
/** Create a sandboxed compute environment (CPU / GPU tiers). */
|
|
10077
10424
|
async modalSandboxCreate(body) {
|
|
@@ -10108,7 +10455,31 @@ var SolanaLLMClient = class {
|
|
|
10108
10455
|
});
|
|
10109
10456
|
if (response.status === 402) {
|
|
10110
10457
|
try {
|
|
10111
|
-
|
|
10458
|
+
const paymentRequired = await this.readPaymentRequired(response);
|
|
10459
|
+
if (this.batchPayer && staleRetries === 0) {
|
|
10460
|
+
const batch = await this.batchPayer.pay(
|
|
10461
|
+
paymentRequired,
|
|
10462
|
+
(paymentHeaders) => this.fetchWithTimeout(url, {
|
|
10463
|
+
method: "POST",
|
|
10464
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT, ...paymentHeaders },
|
|
10465
|
+
body: JSON.stringify(body)
|
|
10466
|
+
})
|
|
10467
|
+
);
|
|
10468
|
+
if (batch.kind === "paid") {
|
|
10469
|
+
this.recordSettlement(batch.chargedUsd);
|
|
10470
|
+
return batch.response.json();
|
|
10471
|
+
}
|
|
10472
|
+
if (batch.kind === "failed") {
|
|
10473
|
+
let errorBody;
|
|
10474
|
+
try {
|
|
10475
|
+
errorBody = await batch.response.json();
|
|
10476
|
+
} catch {
|
|
10477
|
+
errorBody = { error: "Request failed" };
|
|
10478
|
+
}
|
|
10479
|
+
throw new APIError(`API error after payment: ${batch.response.status}`, batch.response.status, sanitizeErrorResponse(errorBody));
|
|
10480
|
+
}
|
|
10481
|
+
}
|
|
10482
|
+
return await this.handlePaymentAndRetry(url, body, paymentRequired, staleRetries > 0);
|
|
10112
10483
|
} catch (error) {
|
|
10113
10484
|
if (!(error instanceof SafeStaleBlockhashError) || staleRetries >= STALE_BLOCKHASH_RETRY_BACKOFFS_MS.length) throw error;
|
|
10114
10485
|
await waitForStaleRetry(staleRetries++);
|
|
@@ -10229,6 +10600,15 @@ var SolanaLLMClient = class {
|
|
|
10229
10600
|
);
|
|
10230
10601
|
}
|
|
10231
10602
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10603
|
+
return this.signExactPayment(
|
|
10604
|
+
url,
|
|
10605
|
+
await this.readPaymentRequired(response),
|
|
10606
|
+
forceFreshBlockhash,
|
|
10607
|
+
resourceFallback
|
|
10608
|
+
);
|
|
10609
|
+
}
|
|
10610
|
+
/** Read a `402`'s payment requirements from its header, or its JSON body. */
|
|
10611
|
+
async readPaymentRequired(response) {
|
|
10232
10612
|
let paymentHeader = response.headers.get("payment-required");
|
|
10233
10613
|
if (!paymentHeader) {
|
|
10234
10614
|
try {
|
|
@@ -10242,8 +10622,14 @@ var SolanaLLMClient = class {
|
|
|
10242
10622
|
if (!paymentHeader) {
|
|
10243
10623
|
throw new PaymentError("402 response but no payment requirements found");
|
|
10244
10624
|
}
|
|
10245
|
-
|
|
10246
|
-
|
|
10625
|
+
return parsePaymentRequired(paymentHeader);
|
|
10626
|
+
}
|
|
10627
|
+
/** Sign the `exact` accept of a 402 as an SPL TransferChecked payment. */
|
|
10628
|
+
async signExactPayment(url, paymentRequired, forceFreshBlockhash, resourceFallback = url) {
|
|
10629
|
+
const details = extractPaymentDetails(
|
|
10630
|
+
{ ...paymentRequired, accepts: paymentRequired.accepts.filter((accept) => accept.scheme !== BATCH_SCHEME) },
|
|
10631
|
+
SOLANA_NETWORK
|
|
10632
|
+
);
|
|
10247
10633
|
if (!details.network?.startsWith("solana:")) {
|
|
10248
10634
|
throw new PaymentError(
|
|
10249
10635
|
`Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
|
|
@@ -10276,10 +10662,10 @@ var SolanaLLMClient = class {
|
|
|
10276
10662
|
);
|
|
10277
10663
|
return { paymentPayload, costUsd: parseFloat(details.amount) / 1e6 };
|
|
10278
10664
|
}
|
|
10279
|
-
async handlePaymentAndRetry(url, body,
|
|
10280
|
-
const { paymentPayload, costUsd } = await this.
|
|
10665
|
+
async handlePaymentAndRetry(url, body, paymentRequired, forceFreshBlockhash = false) {
|
|
10666
|
+
const { paymentPayload, costUsd } = await this.signExactPayment(
|
|
10281
10667
|
url,
|
|
10282
|
-
|
|
10668
|
+
paymentRequired,
|
|
10283
10669
|
forceFreshBlockhash,
|
|
10284
10670
|
`${this.apiUrl}/v1/chat/completions`
|
|
10285
10671
|
);
|
|
@@ -10440,13 +10826,13 @@ function solanaClient(options = {}) {
|
|
|
10440
10826
|
}
|
|
10441
10827
|
|
|
10442
10828
|
// src/cache.ts
|
|
10443
|
-
import * as
|
|
10444
|
-
import * as
|
|
10829
|
+
import * as fs7 from "fs";
|
|
10830
|
+
import * as path6 from "path";
|
|
10445
10831
|
import * as os4 from "os";
|
|
10446
10832
|
import * as crypto2 from "crypto";
|
|
10447
|
-
var CACHE_DIR =
|
|
10448
|
-
var DATA_DIR =
|
|
10449
|
-
var COST_LOG_FILE2 =
|
|
10833
|
+
var CACHE_DIR = path6.join(os4.homedir(), ".blockrun", "cache");
|
|
10834
|
+
var DATA_DIR = path6.join(os4.homedir(), ".blockrun", "data");
|
|
10835
|
+
var COST_LOG_FILE2 = path6.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
|
|
10450
10836
|
var DEFAULT_TTL = {
|
|
10451
10837
|
"/v1/partner/": 3600 * 1e3,
|
|
10452
10838
|
"/v1/pm/": 1800 * 1e3,
|
|
@@ -10466,19 +10852,19 @@ function cacheKey(endpoint, body) {
|
|
|
10466
10852
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
10467
10853
|
}
|
|
10468
10854
|
function cachePath(key) {
|
|
10469
|
-
return
|
|
10855
|
+
return path6.join(CACHE_DIR, `${key}.json`);
|
|
10470
10856
|
}
|
|
10471
10857
|
function getCached(key) {
|
|
10472
10858
|
const filePath = cachePath(key);
|
|
10473
|
-
if (!
|
|
10859
|
+
if (!fs7.existsSync(filePath)) return null;
|
|
10474
10860
|
try {
|
|
10475
|
-
const raw =
|
|
10861
|
+
const raw = fs7.readFileSync(filePath, "utf-8");
|
|
10476
10862
|
const entry = JSON.parse(raw);
|
|
10477
10863
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
10478
10864
|
if (ttl <= 0) return null;
|
|
10479
10865
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
10480
10866
|
try {
|
|
10481
|
-
|
|
10867
|
+
fs7.unlinkSync(filePath);
|
|
10482
10868
|
} catch {
|
|
10483
10869
|
}
|
|
10484
10870
|
return null;
|
|
@@ -10497,7 +10883,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
10497
10883
|
function setCache(key, data, ttlMs) {
|
|
10498
10884
|
if (ttlMs <= 0) return;
|
|
10499
10885
|
try {
|
|
10500
|
-
|
|
10886
|
+
fs7.mkdirSync(CACHE_DIR, { recursive: true });
|
|
10501
10887
|
} catch {
|
|
10502
10888
|
}
|
|
10503
10889
|
const entry = {
|
|
@@ -10506,7 +10892,7 @@ function setCache(key, data, ttlMs) {
|
|
|
10506
10892
|
ttlMs
|
|
10507
10893
|
};
|
|
10508
10894
|
try {
|
|
10509
|
-
|
|
10895
|
+
fs7.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
10510
10896
|
} catch {
|
|
10511
10897
|
}
|
|
10512
10898
|
}
|
|
@@ -10527,7 +10913,7 @@ function readableFilename(endpoint, body) {
|
|
|
10527
10913
|
}
|
|
10528
10914
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
10529
10915
|
try {
|
|
10530
|
-
|
|
10916
|
+
fs7.mkdirSync(DATA_DIR, { recursive: true });
|
|
10531
10917
|
} catch {
|
|
10532
10918
|
}
|
|
10533
10919
|
const filename = readableFilename(endpoint, body);
|
|
@@ -10539,14 +10925,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
10539
10925
|
response
|
|
10540
10926
|
};
|
|
10541
10927
|
try {
|
|
10542
|
-
|
|
10928
|
+
fs7.writeFileSync(path6.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
10543
10929
|
} catch {
|
|
10544
10930
|
}
|
|
10545
10931
|
}
|
|
10546
10932
|
function appendCostLog(endpoint, costUsd) {
|
|
10547
10933
|
if (costUsd <= 0) return;
|
|
10548
10934
|
try {
|
|
10549
|
-
|
|
10935
|
+
fs7.mkdirSync(path6.dirname(COST_LOG_FILE2), { recursive: true });
|
|
10550
10936
|
} catch {
|
|
10551
10937
|
}
|
|
10552
10938
|
const entry = {
|
|
@@ -10555,7 +10941,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
10555
10941
|
cost_usd: costUsd
|
|
10556
10942
|
};
|
|
10557
10943
|
try {
|
|
10558
|
-
|
|
10944
|
+
fs7.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
10559
10945
|
} catch {
|
|
10560
10946
|
}
|
|
10561
10947
|
}
|
|
@@ -10563,7 +10949,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10563
10949
|
const ttl = getTtl(endpoint);
|
|
10564
10950
|
if (ttl > 0) {
|
|
10565
10951
|
try {
|
|
10566
|
-
|
|
10952
|
+
fs7.mkdirSync(CACHE_DIR, { recursive: true });
|
|
10567
10953
|
} catch {
|
|
10568
10954
|
}
|
|
10569
10955
|
const key = cacheKey(endpoint, body);
|
|
@@ -10575,7 +10961,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10575
10961
|
costUsd
|
|
10576
10962
|
};
|
|
10577
10963
|
try {
|
|
10578
|
-
|
|
10964
|
+
fs7.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
10579
10965
|
} catch {
|
|
10580
10966
|
}
|
|
10581
10967
|
}
|
|
@@ -10583,14 +10969,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
10583
10969
|
appendCostLog(endpoint, costUsd);
|
|
10584
10970
|
}
|
|
10585
10971
|
function clearCache() {
|
|
10586
|
-
if (!
|
|
10972
|
+
if (!fs7.existsSync(CACHE_DIR)) return 0;
|
|
10587
10973
|
let count = 0;
|
|
10588
10974
|
try {
|
|
10589
|
-
const files =
|
|
10975
|
+
const files = fs7.readdirSync(CACHE_DIR);
|
|
10590
10976
|
for (const file of files) {
|
|
10591
10977
|
if (file.endsWith(".json")) {
|
|
10592
10978
|
try {
|
|
10593
|
-
|
|
10979
|
+
fs7.unlinkSync(path6.join(CACHE_DIR, file));
|
|
10594
10980
|
count++;
|
|
10595
10981
|
} catch {
|
|
10596
10982
|
}
|
|
@@ -10601,14 +10987,14 @@ function clearCache() {
|
|
|
10601
10987
|
return count;
|
|
10602
10988
|
}
|
|
10603
10989
|
function getCostLogSummary() {
|
|
10604
|
-
if (!
|
|
10990
|
+
if (!fs7.existsSync(COST_LOG_FILE2)) {
|
|
10605
10991
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
10606
10992
|
}
|
|
10607
10993
|
let totalUsd = 0;
|
|
10608
10994
|
let calls = 0;
|
|
10609
10995
|
const byEndpoint = {};
|
|
10610
10996
|
try {
|
|
10611
|
-
const content =
|
|
10997
|
+
const content = fs7.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
10612
10998
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
10613
10999
|
for (const line of content.split("\n")) {
|
|
10614
11000
|
if (!line) continue;
|
|
@@ -10628,9 +11014,9 @@ function getCostLogSummary() {
|
|
|
10628
11014
|
}
|
|
10629
11015
|
|
|
10630
11016
|
// src/setup.ts
|
|
10631
|
-
import { readFileSync as
|
|
11017
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
10632
11018
|
import { homedir as homedir5 } from "os";
|
|
10633
|
-
import { join as
|
|
11019
|
+
import { join as join7 } from "path";
|
|
10634
11020
|
function setupAgentWallet(options) {
|
|
10635
11021
|
const { address, privateKey, isNew } = getOrCreateWallet();
|
|
10636
11022
|
if (isNew) {
|
|
@@ -10679,7 +11065,7 @@ async function setupAgentClient(options = {}) {
|
|
|
10679
11065
|
if (!chain) {
|
|
10680
11066
|
for (const file of ["payment-chain", ".chain"]) {
|
|
10681
11067
|
try {
|
|
10682
|
-
const saved =
|
|
11068
|
+
const saved = readFileSync7(join7(homedir5(), ".blockrun", file), "utf8").trim();
|
|
10683
11069
|
if (saved === "solana" || saved === "base") {
|
|
10684
11070
|
chain = saved;
|
|
10685
11071
|
break;
|
|
@@ -10982,6 +11368,7 @@ export {
|
|
|
10982
11368
|
ARC_CHAIN_ID,
|
|
10983
11369
|
AnthropicClient,
|
|
10984
11370
|
BASE_CHAIN_ID,
|
|
11371
|
+
BLOCKRUN_SOL_OPERATOR,
|
|
10985
11372
|
BlockrunClient,
|
|
10986
11373
|
BlockrunError,
|
|
10987
11374
|
EVM_NETWORKS,
|