@blockrun/llm 3.24.10 → 3.25.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 CHANGED
@@ -723,6 +723,7 @@ await client.closeBatchChannel();
723
723
  - **Never pays twice.** Anything that goes wrong before a batch payment is sent falls back to `exact`. Once one is sent, a call whose payment may have been charged throws `BatchPaymentUnresolvedError` and is never paid again (no new payment, no `exact`, no fallback model).
724
724
  - **Non-streaming chat only.** `chat`, `chatCompletion`, `smartChat` and `smartChatCompletion` use batch; `stream()` and media jobs pay `exact`.
725
725
  - **One channel per wallet.** All clients for a wallet share one channel, saved to `~/.blockrun/solana-batch/<wallet>.json` (mode `0600`) so a restart reuses it.
726
+ - **One process per wallet.** The process that opens the channel holds its store's lock until it exits. Other processes on that wallet (worker pools, PM2/cluster workers, parallel runs) pay `exact`, reported as `channel_store_locked`. Give each worker its own wallet, or route calls through one process. `batch.onLocked: "wait"` waits up to `batch.lockWaitMs` (default 60 s) for the lock, and `"throw"` raises `BatchChannelLockedError` (unpaid, nothing signed) instead of paying `exact`.
726
727
  - **Observable.** Every fallback, backoff, recovery and re-read writes one stderr line; `batch.onEvent` and `client.getBatchStats()` expose the same events and counters.
727
728
 
728
729
  The full runtime contract (the answer-by-answer table, payment-identifier
package/dist/index.cjs CHANGED
@@ -36,11 +36,13 @@ __export(index_exports, {
36
36
  AnthropicClient: () => AnthropicClient,
37
37
  BASE_CHAIN_ID: () => BASE_CHAIN_ID,
38
38
  BLOCKRUN_SOL_OPERATOR: () => BLOCKRUN_SOL_OPERATOR,
39
+ BatchChannelLockedError: () => BatchChannelLockedError,
39
40
  BatchCloseDeferredError: () => BatchCloseDeferredError,
40
41
  BatchPaymentUnresolvedError: () => BatchPaymentUnresolvedError,
41
42
  BlockrunClient: () => BlockrunClient,
42
43
  BlockrunError: () => BlockrunError,
43
44
  DEFAULT_BATCH_IN_DOUBT: () => DEFAULT_BATCH_IN_DOUBT,
45
+ DEFAULT_BATCH_LOCK_WAIT_MS: () => DEFAULT_BATCH_LOCK_WAIT_MS,
44
46
  DEFAULT_BATCH_RATE_LIMIT: () => DEFAULT_BATCH_RATE_LIMIT,
45
47
  EVM_NETWORKS: () => EVM_NETWORKS,
46
48
  ImageClient: () => ImageClient,
@@ -5120,7 +5122,7 @@ function getCostSummary() {
5120
5122
  }
5121
5123
 
5122
5124
  // src/version.ts
5123
- var SDK_VERSION = "3.24.10";
5125
+ var SDK_VERSION = "3.25.0";
5124
5126
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
5125
5127
 
5126
5128
  // src/client.ts
@@ -10222,6 +10224,8 @@ var BATCH_SCHEME = "batch-settlement";
10222
10224
  var BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
10223
10225
  var DEFAULT_BATCH_RATE_LIMIT = { maxAttempts: 3, maxWaitMs: 6e4 };
10224
10226
  var DEFAULT_BATCH_IN_DOUBT = { maxWaitMs: 3e5 };
10227
+ var DEFAULT_BATCH_LOCK_WAIT_MS = 6e4;
10228
+ var LOCK_POLL_MS = 500;
10225
10229
  var BatchPaymentUnresolvedError = class extends PaymentError {
10226
10230
  /** The {@link SolanaBatchEvent.reason} of the `unresolved` event reported with it. */
10227
10231
  reason;
@@ -10267,6 +10271,22 @@ var BatchCloseDeferredError = class extends BlockrunError {
10267
10271
  this.channelIds = init.channelIds ?? [];
10268
10272
  }
10269
10273
  };
10274
+ var BatchChannelLockedError = class extends BlockrunError {
10275
+ /** The paying wallet's address. */
10276
+ wallet;
10277
+ /** The live owner's pid, when the SDK knows it. */
10278
+ ownerPid;
10279
+ /** How long this call waited for the lock, in ms (0 unless `onLocked` is `"wait"`). */
10280
+ waitedMs;
10281
+ constructor(init) {
10282
+ super(`batch-settlement unavailable for wallet ${init.wallet}: ${init.detail}`);
10283
+ this.name = "BatchChannelLockedError";
10284
+ this.wallet = init.wallet;
10285
+ if (init.ownerPid !== void 0) this.ownerPid = init.ownerPid;
10286
+ this.waitedMs = init.waitedMs;
10287
+ withDisposition(this, "unpaid");
10288
+ }
10289
+ };
10270
10290
  var INSTALL_HINT2 = "npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit";
10271
10291
  async function load(pkg, importer) {
10272
10292
  try {
@@ -11331,12 +11351,22 @@ var SolanaBatchPayer = class {
11331
11351
  if (!Number.isFinite(this.inDoubtMaxWaitMs) || this.inDoubtMaxWaitMs < 0) {
11332
11352
  throw new Error("batch.inDoubt.maxWaitMs must be a finite number of ms >= 0");
11333
11353
  }
11354
+ this.onLocked = init.options.onLocked ?? "exact";
11355
+ if (this.onLocked !== "exact" && this.onLocked !== "wait" && this.onLocked !== "throw") {
11356
+ throw new Error('batch.onLocked must be "exact", "wait" or "throw"');
11357
+ }
11358
+ this.lockWaitMs = init.options.lockWaitMs ?? DEFAULT_BATCH_LOCK_WAIT_MS;
11359
+ if (!Number.isFinite(this.lockWaitMs) || this.lockWaitMs < 0) {
11360
+ throw new Error("batch.lockWaitMs must be a finite number of ms >= 0");
11361
+ }
11334
11362
  }
11335
11363
  init;
11336
11364
  state;
11337
11365
  maxAttempts;
11338
11366
  maxWaitMs;
11339
11367
  inDoubtMaxWaitMs;
11368
+ onLocked;
11369
+ lockWaitMs;
11340
11370
  counters = {
11341
11371
  fallbacks: 0,
11342
11372
  fallbacksByReason: {},
@@ -11385,6 +11415,7 @@ var SolanaBatchPayer = class {
11385
11415
  if (owner !== void 0) {
11386
11416
  return {
11387
11417
  reason: "channel_store_locked",
11418
+ ownerPid: owner,
11388
11419
  detail: owner === process.pid ? `channel store ${file} is in use by another copy of @blockrun/llm in this process (or by a process that had this pid and died: remove ${file}.lock if nothing uses it)` : `channel store ${file} is in use by process ${owner}`
11389
11420
  };
11390
11421
  }
@@ -11730,12 +11761,23 @@ var SolanaBatchPayer = class {
11730
11761
  const unavailable = batchUnavailable(paymentRequired);
11731
11762
  if (unavailable) return this.fallback(unavailable);
11732
11763
  let wallet;
11764
+ let waitedMs = 0;
11733
11765
  try {
11734
11766
  wallet = await this.wallet();
11767
+ if ("reason" in wallet && wallet.reason === "channel_store_locked" && this.onLocked === "wait") {
11768
+ const started = Date.now();
11769
+ wallet = await this.waitForLock(wallet);
11770
+ waitedMs = Date.now() - started;
11771
+ }
11735
11772
  } catch (err) {
11736
11773
  return this.fallback(creationFailure(err));
11737
11774
  }
11738
- if ("reason" in wallet) return this.fallback({ reason: wallet.reason, detail: wallet.detail });
11775
+ if ("reason" in wallet) {
11776
+ if (wallet.reason === "channel_store_locked" && this.onLocked !== "exact") {
11777
+ return this.lockedOut(wallet.detail, wallet.ownerPid, waitedMs);
11778
+ }
11779
+ return this.fallback({ reason: wallet.reason, detail: wallet.detail });
11780
+ }
11739
11781
  if (closesAtStart !== void 0 && wallet.closes !== closesAtStart) {
11740
11782
  return this.fallback({
11741
11783
  reason: "closed_during_call",
@@ -11752,6 +11794,40 @@ var SolanaBatchPayer = class {
11752
11794
  wallet.active -= 1;
11753
11795
  }
11754
11796
  }
11797
+ /**
11798
+ * `onLocked: "wait"`: try the wallet's channel store again every
11799
+ * {@link LOCK_POLL_MS} until its owner lets go of it or `lockWaitMs` runs
11800
+ * out. Reported once as a `backoff` event (`channel_store_locked`).
11801
+ */
11802
+ async waitForLock(refusal) {
11803
+ const deadline = Date.now() + this.lockWaitMs;
11804
+ let current = refusal;
11805
+ if (this.lockWaitMs > 0) {
11806
+ await this.report({
11807
+ type: "backoff",
11808
+ reason: "channel_store_locked",
11809
+ retryAfterMs: this.lockWaitMs,
11810
+ detail: refusal.detail
11811
+ });
11812
+ }
11813
+ while ("reason" in current && current.reason === "channel_store_locked") {
11814
+ const left = deadline - Date.now();
11815
+ if (left <= 0) break;
11816
+ await sleep2(Math.min(LOCK_POLL_MS, left));
11817
+ current = await this.wallet();
11818
+ }
11819
+ return current;
11820
+ }
11821
+ /** `onLocked` other than `"exact"`: the call's `"unpaid"` error, nothing signed. */
11822
+ async lockedOut(detail, ownerPid, waitedMs) {
11823
+ const error = new BatchChannelLockedError({
11824
+ wallet: await this.init.address(),
11825
+ detail,
11826
+ waitedMs,
11827
+ ...ownerPid !== void 0 ? { ownerPid } : {}
11828
+ });
11829
+ return { kind: "failed", error };
11830
+ }
11755
11831
  /**
11756
11832
  * The close fence for a call about to send its first request: how many
11757
11833
  * closes this wallet has completed so far, to hand to {@link pay}.
@@ -11842,11 +11918,9 @@ var SolanaBatchPayer = class {
11842
11918
  }
11843
11919
  if (wallet.busy) return fallback({ reason: "channel_busy", attempt: budget.attempt });
11844
11920
  if (wallet.file && !holdsChannelFile(wallet.file)) {
11845
- return fallback({
11846
- reason: "channel_store_locked",
11847
- attempt: budget.attempt,
11848
- detail: `this process no longer holds the lock on ${wallet.file}; another owner may be paying into its channels`
11849
- });
11921
+ const detail = `this process no longer holds the lock on ${wallet.file}; another owner may be paying into its channels`;
11922
+ if (this.onLocked !== "exact") return this.lockedOut(detail, void 0, 0);
11923
+ return fallback({ reason: "channel_store_locked", attempt: budget.attempt, detail });
11850
11924
  }
11851
11925
  wallet.busy = true;
11852
11926
  holding = true;
@@ -14701,11 +14775,13 @@ var AnthropicClient = class {
14701
14775
  AnthropicClient,
14702
14776
  BASE_CHAIN_ID,
14703
14777
  BLOCKRUN_SOL_OPERATOR,
14778
+ BatchChannelLockedError,
14704
14779
  BatchCloseDeferredError,
14705
14780
  BatchPaymentUnresolvedError,
14706
14781
  BlockrunClient,
14707
14782
  BlockrunError,
14708
14783
  DEFAULT_BATCH_IN_DOUBT,
14784
+ DEFAULT_BATCH_LOCK_WAIT_MS,
14709
14785
  DEFAULT_BATCH_RATE_LIMIT,
14710
14786
  EVM_NETWORKS,
14711
14787
  ImageClient,
package/dist/index.d.cts CHANGED
@@ -2923,6 +2923,27 @@ interface SolanaBatchOptions {
2923
2923
  * by scanning the chain for it.
2924
2924
  */
2925
2925
  channelStore?: string | false;
2926
+ /**
2927
+ * What a call does when another live process owns this wallet's channel
2928
+ * store (`channel_store_locked`). One process per wallet pays with batch:
2929
+ * it holds the store's lock until it exits, so worker pools, PM2/cluster
2930
+ * workers or parallel runs on one wallet must each use their own wallet
2931
+ * (or route their calls through one process).
2932
+ * - `"exact"` (default): the call pays with `exact`, as before.
2933
+ * - `"wait"`: the call waits up to {@link lockWaitMs} for the lock (the
2934
+ * owner has to exit), then pays with batch, or raises
2935
+ * {@link BatchChannelLockedError} once the wait runs out.
2936
+ * - `"throw"`: the call raises {@link BatchChannelLockedError} at once.
2937
+ *
2938
+ * The error is raised before anything is signed, so it is `"unpaid"`:
2939
+ * `fallbackModels` (and `smartChat()`'s chain) may move on, and will meet
2940
+ * the same lock. A process that loses its lock to another owner raises it
2941
+ * too unless `onLocked` is `"exact"` (waiting cannot give it back).
2942
+ * Locks between clients of one process do not apply: they share a channel.
2943
+ */
2944
+ onLocked?: "exact" | "wait" | "throw";
2945
+ /** Most time, in ms, one call waits for the lock under `onLocked: "wait"`. Default 60000. */
2946
+ lockWaitMs?: number;
2926
2947
  /**
2927
2948
  * How a call rides out an HTTP 429 (rate limited, or channel capacity
2928
2949
  * exhausted) on a batch payment before it pays with `exact`. The call waits
@@ -2982,6 +3003,8 @@ declare const DEFAULT_BATCH_RATE_LIMIT: {
2982
3003
  declare const DEFAULT_BATCH_IN_DOUBT: {
2983
3004
  readonly maxWaitMs: 300000;
2984
3005
  };
3006
+ /** Default {@link SolanaBatchOptions.lockWaitMs}. */
3007
+ declare const DEFAULT_BATCH_LOCK_WAIT_MS = 60000;
2985
3008
  /**
2986
3009
  * One observable batch-settlement event.
2987
3010
  *
@@ -2996,7 +3019,10 @@ declare const DEFAULT_BATCH_IN_DOUBT: {
2996
3019
  * (`reason: "rate_limited"`) or because another call for the wallet hit one
2997
3020
  * (`reason: "cooldown"`), or before each replay of a payment in doubt that
2998
3021
  * carries a payment identifier (`reason: "in_doubt"`, or `rate_limited`
2999
- * after a 429), with the answer's `status` and the wait in `retryAfterMs`.
3022
+ * after a 429), with the answer's `status` and the wait in `retryAfterMs`;
3023
+ * or, once, when a call starts waiting up to `lockWaitMs` for another
3024
+ * process's channel store lock (`reason: "channel_store_locked"`,
3025
+ * `onLocked: "wait"`).
3000
3026
  * - `recovered`: a call that backed off was then paid with batch (`reason:
3001
3027
  * "in_doubt"` when the replay of a payment in doubt resolved it), or its
3002
3028
  * fresh challenge after the wait was served without a payment (`reason:
@@ -3167,6 +3193,26 @@ declare class BatchCloseDeferredError extends BlockrunError {
3167
3193
  detail: string;
3168
3194
  });
3169
3195
  }
3196
+ /**
3197
+ * Batch is on, another owner holds this wallet's channel store, and the
3198
+ * client's `batch.onLocked` is `"wait"` (the wait ran out) or `"throw"`.
3199
+ * Raised before anything is signed: nothing was paid for the call, and its
3200
+ * {@link RetryDisposition} is `"unpaid"`.
3201
+ */
3202
+ declare class BatchChannelLockedError extends BlockrunError {
3203
+ /** The paying wallet's address. */
3204
+ readonly wallet: string;
3205
+ /** The live owner's pid, when the SDK knows it. */
3206
+ readonly ownerPid?: number;
3207
+ /** How long this call waited for the lock, in ms (0 unless `onLocked` is `"wait"`). */
3208
+ readonly waitedMs: number;
3209
+ constructor(init: {
3210
+ wallet: string;
3211
+ detail: string;
3212
+ ownerPid?: number;
3213
+ waitedMs: number;
3214
+ });
3215
+ }
3170
3216
  /**
3171
3217
  * The outcome of one batch attempt.
3172
3218
  *
@@ -3270,6 +3316,8 @@ declare class SolanaBatchPayer {
3270
3316
  private readonly maxAttempts;
3271
3317
  private readonly maxWaitMs;
3272
3318
  private readonly inDoubtMaxWaitMs;
3319
+ private readonly onLocked;
3320
+ private readonly lockWaitMs;
3273
3321
  private readonly counters;
3274
3322
  private readonly hooks;
3275
3323
  constructor(init: SolanaBatchPayerInit);
@@ -3387,6 +3435,14 @@ declare class SolanaBatchPayer {
3387
3435
  * open a channel the caller believes is closed.
3388
3436
  */
3389
3437
  pay(paymentRequired: PaymentRequired, send: (headers: Record<string, string>) => Promise<Response>, rechallenge: () => Promise<Rechallenge>, closesAtStart?: number): Promise<BatchAttempt>;
3438
+ /**
3439
+ * `onLocked: "wait"`: try the wallet's channel store again every
3440
+ * {@link LOCK_POLL_MS} until its owner lets go of it or `lockWaitMs` runs
3441
+ * out. Reported once as a `backoff` event (`channel_store_locked`).
3442
+ */
3443
+ private waitForLock;
3444
+ /** `onLocked` other than `"exact"`: the call's `"unpaid"` error, nothing signed. */
3445
+ private lockedOut;
3390
3446
  /**
3391
3447
  * The close fence for a call about to send its first request: how many
3392
3448
  * closes this wallet has completed so far, to hand to {@link pay}.
@@ -4490,4 +4546,4 @@ declare function readSettlement(response: Response, quotedUsd: number, scheme: S
4490
4546
  settlement: Settlement;
4491
4547
  };
4492
4548
 
4493
- export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type AnyTool, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, BLOCKRUN_SOL_OPERATOR, type BarResolution, type BatchAttempt, BatchCloseDeferredError, type BatchPayloadKind, BatchPaymentUnresolvedError, type BatchUnresolvedReason, type BlockRunAnthropicOptions, BlockrunClient, type BlockrunClientOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, DEFAULT_BATCH_IN_DOUBT, DEFAULT_BATCH_RATE_LIMIT, EVM_NETWORKS, type EvmNetwork, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, NETWORK_ALIASES, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PHONE_PRICES, PORTAL_URL, PORTRAIT_ENROLLMENT_PRICE_USD, PaymentError, type PaymentLinks, type PhoneBuyOptions, type PhoneBuyResponse, PhoneClient, type PhoneClientOptions, type PhoneListResponse, type PhoneLookupResponse, type PhoneNumberRecord, type PhoneReleaseResponse, type PhoneRenewResponse, type PollOptions, PortraitClient, type PortraitClientOptions, type PortraitEnrollOptions, type PortraitEnrollResponse, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, RPC_PRICE_USD, type Rechallenge, type ResponseFormat, RetiredEndpointError, type RetryDisposition, type RoutingDecision, type RoutingProfile, type RoutingTaskType, type RoutingTier, type RoutingTierConfig, type RpcBatchRequest, RpcClient, type RpcClientOptions, type RpcError, type RpcNetwork, type RpcResponse, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SUPPORTED_NETWORKS, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type Settlement, type SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, type SolanaBatchEvent, type SolanaBatchOptions, SolanaBatchPayer, type SolanaBatchPayerInit, type SolanaBatchStats, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type SoundEffectOptions, type SpeechAudio, SpeechClient, type SpeechClientOptions, type SpeechGenerateOptions, type SpeechModel, type SpeechResponse, type SpeechVoice, type Spending, type SpendingReport, type StockMarket, SurfClient, type SurfClientOptions, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_ARC, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, type UrlCitation, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoiceInfo, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type WebSearchTool, type X402ClientLike, type X402HookContext, type XSearchSource, type XSearchTool, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, evmNetwork, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, readSettlement, retryDisposition, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, toWireSearchParameters, toWireTools, validateMaxTokens, validateModel, validateTemperature, validateTopP };
4549
+ export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type AnyTool, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, BLOCKRUN_SOL_OPERATOR, type BarResolution, type BatchAttempt, BatchChannelLockedError, BatchCloseDeferredError, type BatchPayloadKind, BatchPaymentUnresolvedError, type BatchUnresolvedReason, type BlockRunAnthropicOptions, BlockrunClient, type BlockrunClientOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, DEFAULT_BATCH_IN_DOUBT, DEFAULT_BATCH_LOCK_WAIT_MS, DEFAULT_BATCH_RATE_LIMIT, EVM_NETWORKS, type EvmNetwork, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, NETWORK_ALIASES, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PHONE_PRICES, PORTAL_URL, PORTRAIT_ENROLLMENT_PRICE_USD, PaymentError, type PaymentLinks, type PhoneBuyOptions, type PhoneBuyResponse, PhoneClient, type PhoneClientOptions, type PhoneListResponse, type PhoneLookupResponse, type PhoneNumberRecord, type PhoneReleaseResponse, type PhoneRenewResponse, type PollOptions, PortraitClient, type PortraitClientOptions, type PortraitEnrollOptions, type PortraitEnrollResponse, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, RPC_PRICE_USD, type Rechallenge, type ResponseFormat, RetiredEndpointError, type RetryDisposition, type RoutingDecision, type RoutingProfile, type RoutingTaskType, type RoutingTier, type RoutingTierConfig, type RpcBatchRequest, RpcClient, type RpcClientOptions, type RpcError, type RpcNetwork, type RpcResponse, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SUPPORTED_NETWORKS, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type Settlement, type SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, type SolanaBatchEvent, type SolanaBatchOptions, SolanaBatchPayer, type SolanaBatchPayerInit, type SolanaBatchStats, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type SoundEffectOptions, type SpeechAudio, SpeechClient, type SpeechClientOptions, type SpeechGenerateOptions, type SpeechModel, type SpeechResponse, type SpeechVoice, type Spending, type SpendingReport, type StockMarket, SurfClient, type SurfClientOptions, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_ARC, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, type UrlCitation, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoiceInfo, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type WebSearchTool, type X402ClientLike, type X402HookContext, type XSearchSource, type XSearchTool, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, evmNetwork, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, readSettlement, retryDisposition, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, toWireSearchParameters, toWireTools, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.d.ts CHANGED
@@ -2923,6 +2923,27 @@ interface SolanaBatchOptions {
2923
2923
  * by scanning the chain for it.
2924
2924
  */
2925
2925
  channelStore?: string | false;
2926
+ /**
2927
+ * What a call does when another live process owns this wallet's channel
2928
+ * store (`channel_store_locked`). One process per wallet pays with batch:
2929
+ * it holds the store's lock until it exits, so worker pools, PM2/cluster
2930
+ * workers or parallel runs on one wallet must each use their own wallet
2931
+ * (or route their calls through one process).
2932
+ * - `"exact"` (default): the call pays with `exact`, as before.
2933
+ * - `"wait"`: the call waits up to {@link lockWaitMs} for the lock (the
2934
+ * owner has to exit), then pays with batch, or raises
2935
+ * {@link BatchChannelLockedError} once the wait runs out.
2936
+ * - `"throw"`: the call raises {@link BatchChannelLockedError} at once.
2937
+ *
2938
+ * The error is raised before anything is signed, so it is `"unpaid"`:
2939
+ * `fallbackModels` (and `smartChat()`'s chain) may move on, and will meet
2940
+ * the same lock. A process that loses its lock to another owner raises it
2941
+ * too unless `onLocked` is `"exact"` (waiting cannot give it back).
2942
+ * Locks between clients of one process do not apply: they share a channel.
2943
+ */
2944
+ onLocked?: "exact" | "wait" | "throw";
2945
+ /** Most time, in ms, one call waits for the lock under `onLocked: "wait"`. Default 60000. */
2946
+ lockWaitMs?: number;
2926
2947
  /**
2927
2948
  * How a call rides out an HTTP 429 (rate limited, or channel capacity
2928
2949
  * exhausted) on a batch payment before it pays with `exact`. The call waits
@@ -2982,6 +3003,8 @@ declare const DEFAULT_BATCH_RATE_LIMIT: {
2982
3003
  declare const DEFAULT_BATCH_IN_DOUBT: {
2983
3004
  readonly maxWaitMs: 300000;
2984
3005
  };
3006
+ /** Default {@link SolanaBatchOptions.lockWaitMs}. */
3007
+ declare const DEFAULT_BATCH_LOCK_WAIT_MS = 60000;
2985
3008
  /**
2986
3009
  * One observable batch-settlement event.
2987
3010
  *
@@ -2996,7 +3019,10 @@ declare const DEFAULT_BATCH_IN_DOUBT: {
2996
3019
  * (`reason: "rate_limited"`) or because another call for the wallet hit one
2997
3020
  * (`reason: "cooldown"`), or before each replay of a payment in doubt that
2998
3021
  * carries a payment identifier (`reason: "in_doubt"`, or `rate_limited`
2999
- * after a 429), with the answer's `status` and the wait in `retryAfterMs`.
3022
+ * after a 429), with the answer's `status` and the wait in `retryAfterMs`;
3023
+ * or, once, when a call starts waiting up to `lockWaitMs` for another
3024
+ * process's channel store lock (`reason: "channel_store_locked"`,
3025
+ * `onLocked: "wait"`).
3000
3026
  * - `recovered`: a call that backed off was then paid with batch (`reason:
3001
3027
  * "in_doubt"` when the replay of a payment in doubt resolved it), or its
3002
3028
  * fresh challenge after the wait was served without a payment (`reason:
@@ -3167,6 +3193,26 @@ declare class BatchCloseDeferredError extends BlockrunError {
3167
3193
  detail: string;
3168
3194
  });
3169
3195
  }
3196
+ /**
3197
+ * Batch is on, another owner holds this wallet's channel store, and the
3198
+ * client's `batch.onLocked` is `"wait"` (the wait ran out) or `"throw"`.
3199
+ * Raised before anything is signed: nothing was paid for the call, and its
3200
+ * {@link RetryDisposition} is `"unpaid"`.
3201
+ */
3202
+ declare class BatchChannelLockedError extends BlockrunError {
3203
+ /** The paying wallet's address. */
3204
+ readonly wallet: string;
3205
+ /** The live owner's pid, when the SDK knows it. */
3206
+ readonly ownerPid?: number;
3207
+ /** How long this call waited for the lock, in ms (0 unless `onLocked` is `"wait"`). */
3208
+ readonly waitedMs: number;
3209
+ constructor(init: {
3210
+ wallet: string;
3211
+ detail: string;
3212
+ ownerPid?: number;
3213
+ waitedMs: number;
3214
+ });
3215
+ }
3170
3216
  /**
3171
3217
  * The outcome of one batch attempt.
3172
3218
  *
@@ -3270,6 +3316,8 @@ declare class SolanaBatchPayer {
3270
3316
  private readonly maxAttempts;
3271
3317
  private readonly maxWaitMs;
3272
3318
  private readonly inDoubtMaxWaitMs;
3319
+ private readonly onLocked;
3320
+ private readonly lockWaitMs;
3273
3321
  private readonly counters;
3274
3322
  private readonly hooks;
3275
3323
  constructor(init: SolanaBatchPayerInit);
@@ -3387,6 +3435,14 @@ declare class SolanaBatchPayer {
3387
3435
  * open a channel the caller believes is closed.
3388
3436
  */
3389
3437
  pay(paymentRequired: PaymentRequired, send: (headers: Record<string, string>) => Promise<Response>, rechallenge: () => Promise<Rechallenge>, closesAtStart?: number): Promise<BatchAttempt>;
3438
+ /**
3439
+ * `onLocked: "wait"`: try the wallet's channel store again every
3440
+ * {@link LOCK_POLL_MS} until its owner lets go of it or `lockWaitMs` runs
3441
+ * out. Reported once as a `backoff` event (`channel_store_locked`).
3442
+ */
3443
+ private waitForLock;
3444
+ /** `onLocked` other than `"exact"`: the call's `"unpaid"` error, nothing signed. */
3445
+ private lockedOut;
3390
3446
  /**
3391
3447
  * The close fence for a call about to send its first request: how many
3392
3448
  * closes this wallet has completed so far, to hand to {@link pay}.
@@ -4490,4 +4546,4 @@ declare function readSettlement(response: Response, quotedUsd: number, scheme: S
4490
4546
  settlement: Settlement;
4491
4547
  };
4492
4548
 
4493
- export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type AnyTool, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, BLOCKRUN_SOL_OPERATOR, type BarResolution, type BatchAttempt, BatchCloseDeferredError, type BatchPayloadKind, BatchPaymentUnresolvedError, type BatchUnresolvedReason, type BlockRunAnthropicOptions, BlockrunClient, type BlockrunClientOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, DEFAULT_BATCH_IN_DOUBT, DEFAULT_BATCH_RATE_LIMIT, EVM_NETWORKS, type EvmNetwork, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, NETWORK_ALIASES, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PHONE_PRICES, PORTAL_URL, PORTRAIT_ENROLLMENT_PRICE_USD, PaymentError, type PaymentLinks, type PhoneBuyOptions, type PhoneBuyResponse, PhoneClient, type PhoneClientOptions, type PhoneListResponse, type PhoneLookupResponse, type PhoneNumberRecord, type PhoneReleaseResponse, type PhoneRenewResponse, type PollOptions, PortraitClient, type PortraitClientOptions, type PortraitEnrollOptions, type PortraitEnrollResponse, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, RPC_PRICE_USD, type Rechallenge, type ResponseFormat, RetiredEndpointError, type RetryDisposition, type RoutingDecision, type RoutingProfile, type RoutingTaskType, type RoutingTier, type RoutingTierConfig, type RpcBatchRequest, RpcClient, type RpcClientOptions, type RpcError, type RpcNetwork, type RpcResponse, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SUPPORTED_NETWORKS, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type Settlement, type SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, type SolanaBatchEvent, type SolanaBatchOptions, SolanaBatchPayer, type SolanaBatchPayerInit, type SolanaBatchStats, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type SoundEffectOptions, type SpeechAudio, SpeechClient, type SpeechClientOptions, type SpeechGenerateOptions, type SpeechModel, type SpeechResponse, type SpeechVoice, type Spending, type SpendingReport, type StockMarket, SurfClient, type SurfClientOptions, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_ARC, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, type UrlCitation, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoiceInfo, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type WebSearchTool, type X402ClientLike, type X402HookContext, type XSearchSource, type XSearchTool, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, evmNetwork, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, readSettlement, retryDisposition, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, toWireSearchParameters, toWireTools, validateMaxTokens, validateModel, validateTemperature, validateTopP };
4549
+ export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type AnyTool, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, BLOCKRUN_SOL_OPERATOR, type BarResolution, type BatchAttempt, BatchChannelLockedError, BatchCloseDeferredError, type BatchPayloadKind, BatchPaymentUnresolvedError, type BatchUnresolvedReason, type BlockRunAnthropicOptions, BlockrunClient, type BlockrunClientOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, DEFAULT_BATCH_IN_DOUBT, DEFAULT_BATCH_LOCK_WAIT_MS, DEFAULT_BATCH_RATE_LIMIT, EVM_NETWORKS, type EvmNetwork, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, NETWORK_ALIASES, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PHONE_PRICES, PORTAL_URL, PORTRAIT_ENROLLMENT_PRICE_USD, PaymentError, type PaymentLinks, type PhoneBuyOptions, type PhoneBuyResponse, PhoneClient, type PhoneClientOptions, type PhoneListResponse, type PhoneLookupResponse, type PhoneNumberRecord, type PhoneReleaseResponse, type PhoneRenewResponse, type PollOptions, PortraitClient, type PortraitClientOptions, type PortraitEnrollOptions, type PortraitEnrollResponse, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, RPC_PRICE_USD, type Rechallenge, type ResponseFormat, RetiredEndpointError, type RetryDisposition, type RoutingDecision, type RoutingProfile, type RoutingTaskType, type RoutingTier, type RoutingTierConfig, type RpcBatchRequest, RpcClient, type RpcClientOptions, type RpcError, type RpcNetwork, type RpcResponse, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SUPPORTED_NETWORKS, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type Settlement, type SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, type SolanaBatchEvent, type SolanaBatchOptions, SolanaBatchPayer, type SolanaBatchPayerInit, type SolanaBatchStats, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type SoundEffectOptions, type SpeechAudio, SpeechClient, type SpeechClientOptions, type SpeechGenerateOptions, type SpeechModel, type SpeechResponse, type SpeechVoice, type Spending, type SpendingReport, type StockMarket, SurfClient, type SurfClientOptions, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_ARC, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, type UrlCitation, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoiceInfo, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type WebSearchTool, type X402ClientLike, type X402HookContext, type XSearchSource, type XSearchTool, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, evmNetwork, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, readSettlement, retryDisposition, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, toWireSearchParameters, toWireTools, validateMaxTokens, validateModel, validateTemperature, validateTopP };
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.10";
4992
+ var SDK_VERSION = "3.25.0";
4993
4993
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
4994
4994
 
4995
4995
  // src/client.ts
@@ -10091,6 +10091,8 @@ var BATCH_SCHEME = "batch-settlement";
10091
10091
  var BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
10092
10092
  var DEFAULT_BATCH_RATE_LIMIT = { maxAttempts: 3, maxWaitMs: 6e4 };
10093
10093
  var DEFAULT_BATCH_IN_DOUBT = { maxWaitMs: 3e5 };
10094
+ var DEFAULT_BATCH_LOCK_WAIT_MS = 6e4;
10095
+ var LOCK_POLL_MS = 500;
10094
10096
  var BatchPaymentUnresolvedError = class extends PaymentError {
10095
10097
  /** The {@link SolanaBatchEvent.reason} of the `unresolved` event reported with it. */
10096
10098
  reason;
@@ -10136,6 +10138,22 @@ var BatchCloseDeferredError = class extends BlockrunError {
10136
10138
  this.channelIds = init.channelIds ?? [];
10137
10139
  }
10138
10140
  };
10141
+ var BatchChannelLockedError = class extends BlockrunError {
10142
+ /** The paying wallet's address. */
10143
+ wallet;
10144
+ /** The live owner's pid, when the SDK knows it. */
10145
+ ownerPid;
10146
+ /** How long this call waited for the lock, in ms (0 unless `onLocked` is `"wait"`). */
10147
+ waitedMs;
10148
+ constructor(init) {
10149
+ super(`batch-settlement unavailable for wallet ${init.wallet}: ${init.detail}`);
10150
+ this.name = "BatchChannelLockedError";
10151
+ this.wallet = init.wallet;
10152
+ if (init.ownerPid !== void 0) this.ownerPid = init.ownerPid;
10153
+ this.waitedMs = init.waitedMs;
10154
+ withDisposition(this, "unpaid");
10155
+ }
10156
+ };
10139
10157
  var INSTALL_HINT2 = "npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit";
10140
10158
  async function load(pkg, importer) {
10141
10159
  try {
@@ -11200,12 +11218,22 @@ var SolanaBatchPayer = class {
11200
11218
  if (!Number.isFinite(this.inDoubtMaxWaitMs) || this.inDoubtMaxWaitMs < 0) {
11201
11219
  throw new Error("batch.inDoubt.maxWaitMs must be a finite number of ms >= 0");
11202
11220
  }
11221
+ this.onLocked = init.options.onLocked ?? "exact";
11222
+ if (this.onLocked !== "exact" && this.onLocked !== "wait" && this.onLocked !== "throw") {
11223
+ throw new Error('batch.onLocked must be "exact", "wait" or "throw"');
11224
+ }
11225
+ this.lockWaitMs = init.options.lockWaitMs ?? DEFAULT_BATCH_LOCK_WAIT_MS;
11226
+ if (!Number.isFinite(this.lockWaitMs) || this.lockWaitMs < 0) {
11227
+ throw new Error("batch.lockWaitMs must be a finite number of ms >= 0");
11228
+ }
11203
11229
  }
11204
11230
  init;
11205
11231
  state;
11206
11232
  maxAttempts;
11207
11233
  maxWaitMs;
11208
11234
  inDoubtMaxWaitMs;
11235
+ onLocked;
11236
+ lockWaitMs;
11209
11237
  counters = {
11210
11238
  fallbacks: 0,
11211
11239
  fallbacksByReason: {},
@@ -11254,6 +11282,7 @@ var SolanaBatchPayer = class {
11254
11282
  if (owner !== void 0) {
11255
11283
  return {
11256
11284
  reason: "channel_store_locked",
11285
+ ownerPid: owner,
11257
11286
  detail: owner === process.pid ? `channel store ${file} is in use by another copy of @blockrun/llm in this process (or by a process that had this pid and died: remove ${file}.lock if nothing uses it)` : `channel store ${file} is in use by process ${owner}`
11258
11287
  };
11259
11288
  }
@@ -11599,12 +11628,23 @@ var SolanaBatchPayer = class {
11599
11628
  const unavailable = batchUnavailable(paymentRequired);
11600
11629
  if (unavailable) return this.fallback(unavailable);
11601
11630
  let wallet;
11631
+ let waitedMs = 0;
11602
11632
  try {
11603
11633
  wallet = await this.wallet();
11634
+ if ("reason" in wallet && wallet.reason === "channel_store_locked" && this.onLocked === "wait") {
11635
+ const started = Date.now();
11636
+ wallet = await this.waitForLock(wallet);
11637
+ waitedMs = Date.now() - started;
11638
+ }
11604
11639
  } catch (err) {
11605
11640
  return this.fallback(creationFailure(err));
11606
11641
  }
11607
- if ("reason" in wallet) return this.fallback({ reason: wallet.reason, detail: wallet.detail });
11642
+ if ("reason" in wallet) {
11643
+ if (wallet.reason === "channel_store_locked" && this.onLocked !== "exact") {
11644
+ return this.lockedOut(wallet.detail, wallet.ownerPid, waitedMs);
11645
+ }
11646
+ return this.fallback({ reason: wallet.reason, detail: wallet.detail });
11647
+ }
11608
11648
  if (closesAtStart !== void 0 && wallet.closes !== closesAtStart) {
11609
11649
  return this.fallback({
11610
11650
  reason: "closed_during_call",
@@ -11621,6 +11661,40 @@ var SolanaBatchPayer = class {
11621
11661
  wallet.active -= 1;
11622
11662
  }
11623
11663
  }
11664
+ /**
11665
+ * `onLocked: "wait"`: try the wallet's channel store again every
11666
+ * {@link LOCK_POLL_MS} until its owner lets go of it or `lockWaitMs` runs
11667
+ * out. Reported once as a `backoff` event (`channel_store_locked`).
11668
+ */
11669
+ async waitForLock(refusal) {
11670
+ const deadline = Date.now() + this.lockWaitMs;
11671
+ let current = refusal;
11672
+ if (this.lockWaitMs > 0) {
11673
+ await this.report({
11674
+ type: "backoff",
11675
+ reason: "channel_store_locked",
11676
+ retryAfterMs: this.lockWaitMs,
11677
+ detail: refusal.detail
11678
+ });
11679
+ }
11680
+ while ("reason" in current && current.reason === "channel_store_locked") {
11681
+ const left = deadline - Date.now();
11682
+ if (left <= 0) break;
11683
+ await sleep2(Math.min(LOCK_POLL_MS, left));
11684
+ current = await this.wallet();
11685
+ }
11686
+ return current;
11687
+ }
11688
+ /** `onLocked` other than `"exact"`: the call's `"unpaid"` error, nothing signed. */
11689
+ async lockedOut(detail, ownerPid, waitedMs) {
11690
+ const error = new BatchChannelLockedError({
11691
+ wallet: await this.init.address(),
11692
+ detail,
11693
+ waitedMs,
11694
+ ...ownerPid !== void 0 ? { ownerPid } : {}
11695
+ });
11696
+ return { kind: "failed", error };
11697
+ }
11624
11698
  /**
11625
11699
  * The close fence for a call about to send its first request: how many
11626
11700
  * closes this wallet has completed so far, to hand to {@link pay}.
@@ -11711,11 +11785,9 @@ var SolanaBatchPayer = class {
11711
11785
  }
11712
11786
  if (wallet.busy) return fallback({ reason: "channel_busy", attempt: budget.attempt });
11713
11787
  if (wallet.file && !holdsChannelFile(wallet.file)) {
11714
- return fallback({
11715
- reason: "channel_store_locked",
11716
- attempt: budget.attempt,
11717
- detail: `this process no longer holds the lock on ${wallet.file}; another owner may be paying into its channels`
11718
- });
11788
+ const detail = `this process no longer holds the lock on ${wallet.file}; another owner may be paying into its channels`;
11789
+ if (this.onLocked !== "exact") return this.lockedOut(detail, void 0, 0);
11790
+ return fallback({ reason: "channel_store_locked", attempt: budget.attempt, detail });
11719
11791
  }
11720
11792
  wallet.busy = true;
11721
11793
  holding = true;
@@ -14569,11 +14641,13 @@ export {
14569
14641
  AnthropicClient,
14570
14642
  BASE_CHAIN_ID,
14571
14643
  BLOCKRUN_SOL_OPERATOR,
14644
+ BatchChannelLockedError,
14572
14645
  BatchCloseDeferredError,
14573
14646
  BatchPaymentUnresolvedError,
14574
14647
  BlockrunClient,
14575
14648
  BlockrunError,
14576
14649
  DEFAULT_BATCH_IN_DOUBT,
14650
+ DEFAULT_BATCH_LOCK_WAIT_MS,
14577
14651
  DEFAULT_BATCH_RATE_LIMIT,
14578
14652
  EVM_NETWORKS,
14579
14653
  ImageClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "3.24.10",
3
+ "version": "3.25.0",
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",