@blockrun/llm 3.24.9 → 3.24.10

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 CHANGED
@@ -635,7 +635,10 @@ console.log(res.costUsd, res.settlement); // what you paid, and the tx
635
635
  ```
636
636
 
637
637
  `costUsd`, `settlement` and the per-call `timeout` work the same on `LLMClient`
638
- (Base / Arc). `costUsd` is what the call added to `getSpending()`: the signed
638
+ (Base / Arc). Both clients default to a 600 s request timeout
639
+ (`BLOCKRUN_CHAT_TIMEOUT`, in seconds, overrides it; the `timeout` option, in
640
+ ms, overrides both): a call aborted after its payment was sent may already be
641
+ charged, so a batch payment ends unresolved rather than paid again. `costUsd` is what the call added to `getSpending()`: the signed
639
642
  price under `exact`, the metered `extra.chargedAmount` under batch settlement,
640
643
  `0` for a free model; it is undefined in API-key mode (billed to the account).
641
644
  When the gateway answers a paid request from a free fallback model it skips
@@ -1695,7 +1698,7 @@ const configured = new LLMClient({
1695
1698
  | `SOLANA_RPC_URL` | Solana RPC for payments and balance (also `rpcUrl`) |
1696
1699
  | `SOLANA_RPC_API_KEY` / `SOLANA_RPC_HEADERS` | Header auth for that RPC (also `rpcHeaders`) |
1697
1700
  | `BASE_RPC_URL` | Base RPC tried first by `LLMClient.getBalance()` |
1698
- | `BLOCKRUN_CHAT_TIMEOUT` | `LLMClient` default timeout, in **seconds** (default 600) |
1701
+ | `BLOCKRUN_CHAT_TIMEOUT` | `LLMClient` and `SolanaLLMClient` default timeout, in **seconds** (default 600) |
1699
1702
  | `BLOCKRUN_HOME` | Base directory for the Base wallet files (security-sensitive, see [Wallets](#one-wallet-across-every-blockrun-product)) |
1700
1703
 
1701
1704
  `BLOCKRUN_SOL_OPERATOR` is **not** an environment variable or a secret. It is
package/dist/index.cjs CHANGED
@@ -5120,7 +5120,7 @@ function getCostSummary() {
5120
5120
  }
5121
5121
 
5122
5122
  // src/version.ts
5123
- var SDK_VERSION = "3.24.9";
5123
+ var SDK_VERSION = "3.24.10";
5124
5124
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
5125
5125
 
5126
5126
  // src/client.ts
@@ -10902,6 +10902,38 @@ function withoutClientSignedBatch(paymentRequired) {
10902
10902
  const accepts = paymentRequired.accepts ?? [];
10903
10903
  return accepts.some(clientSignedBatch) ? { ...paymentRequired, accepts: accepts.filter((accept) => !clientSignedBatch(accept)) } : paymentRequired;
10904
10904
  }
10905
+ var DEPOSIT_BLOCKHASH_TTL_MS = 1e4;
10906
+ var MAX_DEPOSIT_BLOCKHASH_ENDPOINTS = 8;
10907
+ var depositBlockhashes = /* @__PURE__ */ new Map();
10908
+ async function finalizedBlockhash(rpcUrl, rpcHeaders) {
10909
+ const cached = depositBlockhashes.get(rpcUrl);
10910
+ if (cached && Date.now() - cached.fetchedAt < DEPOSIT_BLOCKHASH_TTL_MS) return cached;
10911
+ const result = await rpcRequest(rpcUrl, "getLatestBlockhash", [{ commitment: "finalized" }], rpcHeaders);
10912
+ const blockhash = result.value?.blockhash;
10913
+ if (typeof blockhash !== "string" || !blockhash) throw new Error("getLatestBlockhash returned no blockhash");
10914
+ const entry = {
10915
+ blockhash,
10916
+ lastValidBlockHeight: rpcInteger(result.value?.lastValidBlockHeight, "getLatestBlockhash lastValidBlockHeight"),
10917
+ fetchedAt: Date.now()
10918
+ };
10919
+ depositBlockhashes.delete(rpcUrl);
10920
+ depositBlockhashes.set(rpcUrl, entry);
10921
+ while (depositBlockhashes.size > MAX_DEPOSIT_BLOCKHASH_ENDPOINTS) {
10922
+ depositBlockhashes.delete(depositBlockhashes.keys().next().value);
10923
+ }
10924
+ return entry;
10925
+ }
10926
+ function withDepositBlockhash(paymentRequired, blockhash) {
10927
+ const accepts = paymentRequired.accepts ?? [];
10928
+ const lacking = (accept) => accept.scheme === BATCH_SCHEME && !accept.extra?.recentBlockhash;
10929
+ if (!accepts.some(lacking)) return paymentRequired;
10930
+ return {
10931
+ ...paymentRequired,
10932
+ accepts: accepts.map(
10933
+ (accept) => lacking(accept) ? { ...accept, extra: { ...accept.extra, recentBlockhash: blockhash.blockhash, lastValidBlockHeight: blockhash.lastValidBlockHeight } } : accept
10934
+ )
10935
+ };
10936
+ }
10905
10937
  var PAYMENT_IDENTIFIER = "payment-identifier";
10906
10938
  function withPaymentIdentifier(paymentRequired) {
10907
10939
  const declared = paymentRequired.extensions?.[PAYMENT_IDENTIFIER];
@@ -12391,7 +12423,8 @@ var SolanaBatchPayer = class {
12391
12423
  const http = (await this.build(wallet)).http;
12392
12424
  try {
12393
12425
  await this.prepareTopUp(wallet, paymentRequired);
12394
- return { http, payload: await http.createPaymentPayload(withPaymentIdentifier(withoutClientSignedBatch(paymentRequired))) };
12426
+ const required = await this.withDepositBlockhash(wallet, withoutClientSignedBatch(paymentRequired));
12427
+ return { http, payload: await http.createPaymentPayload(withPaymentIdentifier(required)) };
12395
12428
  } catch (err) {
12396
12429
  if (pass > 0 || !isResyncRequired(err) || wallet.resyncs.size === 0) throw err;
12397
12430
  } finally {
@@ -12399,6 +12432,34 @@ var SolanaBatchPayer = class {
12399
12432
  }
12400
12433
  }
12401
12434
  }
12435
+ /**
12436
+ * The 402 to build this payment from: with a finalized blockhash
12437
+ * ({@link withDepositBlockhash}) when the payment will likely carry a
12438
+ * deposit (no usable record for the trusted accept's channel, so an open,
12439
+ * or a top-up {@link prepareTopUp} just sized). An authorization signs no
12440
+ * transaction and costs no RPC read. Best effort: a failed read leaves the
12441
+ * 402 as it is, and `@x402/svm` fetches its own blockhash.
12442
+ */
12443
+ async withDepositBlockhash(wallet, paymentRequired) {
12444
+ const accept = trustedBatchAccept(paymentRequired, this.init.options.operators);
12445
+ const key = accept ? channelKeyOf(accept) : void 0;
12446
+ if (!accept || accept.extra?.recentBlockhash) return paymentRequired;
12447
+ let deposit = wallet.topUp !== void 0;
12448
+ if (!deposit) {
12449
+ try {
12450
+ const record = key ? await wallet.book.base.get(key) : void 0;
12451
+ deposit = !record || typeof record.channelId !== "string" || record.pending !== void 0;
12452
+ } catch {
12453
+ deposit = true;
12454
+ }
12455
+ }
12456
+ if (!deposit) return paymentRequired;
12457
+ try {
12458
+ return withDepositBlockhash(paymentRequired, await finalizedBlockhash(this.init.rpcUrl, this.init.rpcHeaders));
12459
+ } catch {
12460
+ return paymentRequired;
12461
+ }
12462
+ }
12402
12463
  /**
12403
12464
  * Queue a re-read of a stored channel whose account is gone at finalized
12404
12465
  * and at confirmed commitment (closed, its account reclaimed), anchored at
@@ -12705,7 +12766,6 @@ var SolanaBatchPayer = class {
12705
12766
  // src/solana-client.ts
12706
12767
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
12707
12768
  var DEFAULT_MAX_TOKENS2 = 1024;
12708
- var DEFAULT_TIMEOUT14 = 6e4;
12709
12769
  var STALE_BLOCKHASH_RETRY_BACKOFFS_MS = [500, 2e3];
12710
12770
  var MAX_PAYMENT_FAILURE_BYTES = 64 * 1024;
12711
12771
  var DEFAULT_VIDEO_MODEL = "xai/grok-imagine-video";
@@ -12873,7 +12933,7 @@ var SolanaLLMClient = class {
12873
12933
  const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
12874
12934
  this.rpcUrl = rpc.url;
12875
12935
  this.rpcHeaders = rpc.headers;
12876
- this.timeout = options.timeout || DEFAULT_TIMEOUT14;
12936
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
12877
12937
  if (options.batch && !this.apiAuth) {
12878
12938
  this.batchPayer = new SolanaBatchPayer({
12879
12939
  options: options.batch,
package/dist/index.d.cts CHANGED
@@ -3508,6 +3508,15 @@ declare class SolanaBatchPayer {
3508
3508
  * once.
3509
3509
  */
3510
3510
  private createPayload;
3511
+ /**
3512
+ * The 402 to build this payment from: with a finalized blockhash
3513
+ * ({@link withDepositBlockhash}) when the payment will likely carry a
3514
+ * deposit (no usable record for the trusted accept's channel, so an open,
3515
+ * or a top-up {@link prepareTopUp} just sized). An authorization signs no
3516
+ * transaction and costs no RPC read. Best effort: a failed read leaves the
3517
+ * 402 as it is, and `@x402/svm` fetches its own blockhash.
3518
+ */
3519
+ private withDepositBlockhash;
3511
3520
  /**
3512
3521
  * Queue a re-read of a stored channel whose account is gone at finalized
3513
3522
  * and at confirmed commitment (closed, its account reclaimed), anchored at
@@ -3647,7 +3656,12 @@ interface SolanaLLMClientOptions extends ApiKeyOptions {
3647
3656
  * (shortcut for `{ "x-api-key": "..." }`) env vars when omitted.
3648
3657
  */
3649
3658
  rpcHeaders?: Record<string, string>;
3650
- /** Request timeout in milliseconds (default: 60000) */
3659
+ /**
3660
+ * Request timeout in milliseconds. Default: `BLOCKRUN_CHAT_TIMEOUT` (in
3661
+ * seconds) when set, else 600000, the same as `LLMClient`: a reasoning
3662
+ * model's long turn can take minutes, and a batch payment whose call is
3663
+ * aborted after it was sent ends unresolved.
3664
+ */
3651
3665
  timeout?: number;
3652
3666
  /**
3653
3667
  * Opt in to x402 batch-settlement (metered billing) for chat completions.
package/dist/index.d.ts CHANGED
@@ -3508,6 +3508,15 @@ declare class SolanaBatchPayer {
3508
3508
  * once.
3509
3509
  */
3510
3510
  private createPayload;
3511
+ /**
3512
+ * The 402 to build this payment from: with a finalized blockhash
3513
+ * ({@link withDepositBlockhash}) when the payment will likely carry a
3514
+ * deposit (no usable record for the trusted accept's channel, so an open,
3515
+ * or a top-up {@link prepareTopUp} just sized). An authorization signs no
3516
+ * transaction and costs no RPC read. Best effort: a failed read leaves the
3517
+ * 402 as it is, and `@x402/svm` fetches its own blockhash.
3518
+ */
3519
+ private withDepositBlockhash;
3511
3520
  /**
3512
3521
  * Queue a re-read of a stored channel whose account is gone at finalized
3513
3522
  * and at confirmed commitment (closed, its account reclaimed), anchored at
@@ -3647,7 +3656,12 @@ interface SolanaLLMClientOptions extends ApiKeyOptions {
3647
3656
  * (shortcut for `{ "x-api-key": "..." }`) env vars when omitted.
3648
3657
  */
3649
3658
  rpcHeaders?: Record<string, string>;
3650
- /** Request timeout in milliseconds (default: 60000) */
3659
+ /**
3660
+ * Request timeout in milliseconds. Default: `BLOCKRUN_CHAT_TIMEOUT` (in
3661
+ * seconds) when set, else 600000, the same as `LLMClient`: a reasoning
3662
+ * model's long turn can take minutes, and a batch payment whose call is
3663
+ * aborted after it was sent ends unresolved.
3664
+ */
3651
3665
  timeout?: number;
3652
3666
  /**
3653
3667
  * Opt in to x402 batch-settlement (metered billing) for chat completions.
package/dist/index.js CHANGED
@@ -4989,7 +4989,7 @@ function getCostSummary() {
4989
4989
  }
4990
4990
 
4991
4991
  // src/version.ts
4992
- var SDK_VERSION = "3.24.9";
4992
+ var SDK_VERSION = "3.24.10";
4993
4993
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
4994
4994
 
4995
4995
  // src/client.ts
@@ -10771,6 +10771,38 @@ function withoutClientSignedBatch(paymentRequired) {
10771
10771
  const accepts = paymentRequired.accepts ?? [];
10772
10772
  return accepts.some(clientSignedBatch) ? { ...paymentRequired, accepts: accepts.filter((accept) => !clientSignedBatch(accept)) } : paymentRequired;
10773
10773
  }
10774
+ var DEPOSIT_BLOCKHASH_TTL_MS = 1e4;
10775
+ var MAX_DEPOSIT_BLOCKHASH_ENDPOINTS = 8;
10776
+ var depositBlockhashes = /* @__PURE__ */ new Map();
10777
+ async function finalizedBlockhash(rpcUrl, rpcHeaders) {
10778
+ const cached = depositBlockhashes.get(rpcUrl);
10779
+ if (cached && Date.now() - cached.fetchedAt < DEPOSIT_BLOCKHASH_TTL_MS) return cached;
10780
+ const result = await rpcRequest(rpcUrl, "getLatestBlockhash", [{ commitment: "finalized" }], rpcHeaders);
10781
+ const blockhash = result.value?.blockhash;
10782
+ if (typeof blockhash !== "string" || !blockhash) throw new Error("getLatestBlockhash returned no blockhash");
10783
+ const entry = {
10784
+ blockhash,
10785
+ lastValidBlockHeight: rpcInteger(result.value?.lastValidBlockHeight, "getLatestBlockhash lastValidBlockHeight"),
10786
+ fetchedAt: Date.now()
10787
+ };
10788
+ depositBlockhashes.delete(rpcUrl);
10789
+ depositBlockhashes.set(rpcUrl, entry);
10790
+ while (depositBlockhashes.size > MAX_DEPOSIT_BLOCKHASH_ENDPOINTS) {
10791
+ depositBlockhashes.delete(depositBlockhashes.keys().next().value);
10792
+ }
10793
+ return entry;
10794
+ }
10795
+ function withDepositBlockhash(paymentRequired, blockhash) {
10796
+ const accepts = paymentRequired.accepts ?? [];
10797
+ const lacking = (accept) => accept.scheme === BATCH_SCHEME && !accept.extra?.recentBlockhash;
10798
+ if (!accepts.some(lacking)) return paymentRequired;
10799
+ return {
10800
+ ...paymentRequired,
10801
+ accepts: accepts.map(
10802
+ (accept) => lacking(accept) ? { ...accept, extra: { ...accept.extra, recentBlockhash: blockhash.blockhash, lastValidBlockHeight: blockhash.lastValidBlockHeight } } : accept
10803
+ )
10804
+ };
10805
+ }
10774
10806
  var PAYMENT_IDENTIFIER = "payment-identifier";
10775
10807
  function withPaymentIdentifier(paymentRequired) {
10776
10808
  const declared = paymentRequired.extensions?.[PAYMENT_IDENTIFIER];
@@ -12260,7 +12292,8 @@ var SolanaBatchPayer = class {
12260
12292
  const http = (await this.build(wallet)).http;
12261
12293
  try {
12262
12294
  await this.prepareTopUp(wallet, paymentRequired);
12263
- return { http, payload: await http.createPaymentPayload(withPaymentIdentifier(withoutClientSignedBatch(paymentRequired))) };
12295
+ const required = await this.withDepositBlockhash(wallet, withoutClientSignedBatch(paymentRequired));
12296
+ return { http, payload: await http.createPaymentPayload(withPaymentIdentifier(required)) };
12264
12297
  } catch (err) {
12265
12298
  if (pass > 0 || !isResyncRequired(err) || wallet.resyncs.size === 0) throw err;
12266
12299
  } finally {
@@ -12268,6 +12301,34 @@ var SolanaBatchPayer = class {
12268
12301
  }
12269
12302
  }
12270
12303
  }
12304
+ /**
12305
+ * The 402 to build this payment from: with a finalized blockhash
12306
+ * ({@link withDepositBlockhash}) when the payment will likely carry a
12307
+ * deposit (no usable record for the trusted accept's channel, so an open,
12308
+ * or a top-up {@link prepareTopUp} just sized). An authorization signs no
12309
+ * transaction and costs no RPC read. Best effort: a failed read leaves the
12310
+ * 402 as it is, and `@x402/svm` fetches its own blockhash.
12311
+ */
12312
+ async withDepositBlockhash(wallet, paymentRequired) {
12313
+ const accept = trustedBatchAccept(paymentRequired, this.init.options.operators);
12314
+ const key = accept ? channelKeyOf(accept) : void 0;
12315
+ if (!accept || accept.extra?.recentBlockhash) return paymentRequired;
12316
+ let deposit = wallet.topUp !== void 0;
12317
+ if (!deposit) {
12318
+ try {
12319
+ const record = key ? await wallet.book.base.get(key) : void 0;
12320
+ deposit = !record || typeof record.channelId !== "string" || record.pending !== void 0;
12321
+ } catch {
12322
+ deposit = true;
12323
+ }
12324
+ }
12325
+ if (!deposit) return paymentRequired;
12326
+ try {
12327
+ return withDepositBlockhash(paymentRequired, await finalizedBlockhash(this.init.rpcUrl, this.init.rpcHeaders));
12328
+ } catch {
12329
+ return paymentRequired;
12330
+ }
12331
+ }
12271
12332
  /**
12272
12333
  * Queue a re-read of a stored channel whose account is gone at finalized
12273
12334
  * and at confirmed commitment (closed, its account reclaimed), anchored at
@@ -12574,7 +12635,6 @@ var SolanaBatchPayer = class {
12574
12635
  // src/solana-client.ts
12575
12636
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
12576
12637
  var DEFAULT_MAX_TOKENS2 = 1024;
12577
- var DEFAULT_TIMEOUT14 = 6e4;
12578
12638
  var STALE_BLOCKHASH_RETRY_BACKOFFS_MS = [500, 2e3];
12579
12639
  var MAX_PAYMENT_FAILURE_BYTES = 64 * 1024;
12580
12640
  var DEFAULT_VIDEO_MODEL = "xai/grok-imagine-video";
@@ -12742,7 +12802,7 @@ var SolanaLLMClient = class {
12742
12802
  const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
12743
12803
  this.rpcUrl = rpc.url;
12744
12804
  this.rpcHeaders = rpc.headers;
12745
- this.timeout = options.timeout || DEFAULT_TIMEOUT14;
12805
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
12746
12806
  if (options.batch && !this.apiAuth) {
12747
12807
  this.batchPayer = new SolanaBatchPayer({
12748
12808
  options: options.batch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "3.24.9",
3
+ "version": "3.24.10",
4
4
  "type": "module",
5
5
  "description": "TypeScript SDK for BlockRun - every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. No rate limits.",
6
6
  "main": "dist/index.cjs",