@blockrun/llm 3.7.1 → 3.8.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 CHANGED
@@ -1330,10 +1330,30 @@ belonging to other applications on this system:
1330
1330
  ...
1331
1331
  ```
1332
1332
 
1333
- Import it deliberately with `export BLOCKRUN_WALLET_KEY=<private-key>`, or by
1334
- writing the key to `~/.blockrun/.session`. Addresses in that notice are derived
1335
- from the discovered key itself, so a wallet file cannot claim an address it
1336
- cannot sign for.
1333
+ Adopt one deliberately:
1334
+
1335
+ ```typescript
1336
+ import { listDiscoveredWallets, importWallet } from '@blockrun/llm';
1337
+
1338
+ for (const w of listDiscoveredWallets()) {
1339
+ console.log(w.address, 'from', w.source);
1340
+ }
1341
+
1342
+ importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');
1343
+ ```
1344
+
1345
+ `importWallet()` writes your current wallet to
1346
+ `~/.blockrun/.session.backup-<timestamp>` before switching, so adopting a wallet
1347
+ never strands funds in the old one. Solana: `listDiscoveredSolanaWallets()` and
1348
+ `importSolanaWallet()`.
1349
+
1350
+ Addresses shown are derived from the discovered key itself, and `importWallet()`
1351
+ matches on that derived address — so a wallet file cannot claim an address it
1352
+ cannot sign for, nor be adopted by one. `listDiscoveredWallets()` never returns
1353
+ private keys.
1354
+
1355
+ For a single run without changing anything, use
1356
+ `export BLOCKRUN_WALLET_KEY=<private-key>`.
1337
1357
 
1338
1358
  ## Response Caching
1339
1359
 
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  ImageClient: () => ImageClient,
39
39
  KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
40
40
  LLMClient: () => LLMClient,
41
+ MAX_TOKENS_SANITY_LIMIT: () => MAX_TOKENS_SANITY_LIMIT,
41
42
  MusicClient: () => MusicClient,
42
43
  NETWORK_ALIASES: () => NETWORK_ALIASES,
43
44
  OpenAI: () => OpenAI,
@@ -84,6 +85,10 @@ __export(index_exports, {
84
85
  getOrCreateWallet: () => getOrCreateWallet,
85
86
  getPaymentLinks: () => getPaymentLinks,
86
87
  getWalletAddress: () => getWalletAddress,
88
+ importSolanaWallet: () => importSolanaWallet,
89
+ importWallet: () => importWallet,
90
+ listDiscoveredSolanaWallets: () => listDiscoveredSolanaWallets,
91
+ listDiscoveredWallets: () => listDiscoveredWallets,
87
92
  loadSolanaWallet: () => loadSolanaWallet,
88
93
  loadWallet: () => loadWallet,
89
94
  logCost: () => logCost,
@@ -351,6 +356,7 @@ function validateModel(model) {
351
356
  throw new Error("Model must be a non-empty string");
352
357
  }
353
358
  }
359
+ var MAX_TOKENS_SANITY_LIMIT = 1e6;
354
360
  function validateMaxTokens(maxTokens) {
355
361
  if (maxTokens === void 0 || maxTokens === null) {
356
362
  return;
@@ -361,8 +367,13 @@ function validateMaxTokens(maxTokens) {
361
367
  if (maxTokens < 1) {
362
368
  throw new Error("maxTokens must be positive (minimum: 1)");
363
369
  }
364
- if (maxTokens > 1e5) {
365
- throw new Error("maxTokens too large (maximum: 100000)");
370
+ if (maxTokens > MAX_TOKENS_SANITY_LIMIT) {
371
+ throw Object.assign(
372
+ new Error(
373
+ `maxTokens implausibly large (client-side sanity limit: ${MAX_TOKENS_SANITY_LIMIT}). This is not a model limit \u2014 the gateway enforces the real per-model ceiling and reports it.`
374
+ ),
375
+ { code: "MAX_TOKENS_SANITY_LIMIT", limit: MAX_TOKENS_SANITY_LIMIT }
376
+ );
366
377
  }
367
378
  }
368
379
  function validateTemperature(temperature) {
@@ -762,6 +773,7 @@ var LLMClient = class _LLMClient {
762
773
  * @returns ChatResponse object with choices and usage
763
774
  */
764
775
  async chatCompletion(model, messages, options) {
776
+ validateMaxTokens(options?.maxTokens);
765
777
  const buildBody = (m) => {
766
778
  const body = {
767
779
  model: m,
@@ -1032,6 +1044,7 @@ var LLMClient = class _LLMClient {
1032
1044
  * @returns Raw fetch Response with a streaming SSE body.
1033
1045
  */
1034
1046
  async chatCompletionStream(model, messages, options) {
1047
+ validateMaxTokens(options?.maxTokens);
1035
1048
  const url = `${this.apiUrl}/v1/chat/completions`;
1036
1049
  const body = {
1037
1050
  model,
@@ -4631,7 +4644,7 @@ function scanWallets() {
4631
4644
  const addr = data.address || "";
4632
4645
  if (pk && addr) {
4633
4646
  const mtime = fs2.statSync(walletFile).mtimeMs;
4634
- results.push({ mtime, privateKey: pk, address: addr });
4647
+ results.push({ mtime, privateKey: pk, address: addr, source: walletFile });
4635
4648
  }
4636
4649
  } catch {
4637
4650
  continue;
@@ -4640,7 +4653,46 @@ function scanWallets() {
4640
4653
  } catch {
4641
4654
  }
4642
4655
  results.sort((a, b) => b.mtime - a.mtime);
4643
- return results.map(({ privateKey, address }) => ({ privateKey, address }));
4656
+ return results.map(({ privateKey, address, source }) => ({ privateKey, address, source }));
4657
+ }
4658
+ function listDiscoveredWallets() {
4659
+ const listed = [];
4660
+ for (const entry of scanWallets()) {
4661
+ try {
4662
+ listed.push({
4663
+ address: (0, import_accounts15.privateKeyToAccount)(entry.privateKey).address,
4664
+ source: entry.source
4665
+ });
4666
+ } catch {
4667
+ continue;
4668
+ }
4669
+ }
4670
+ return listed;
4671
+ }
4672
+ function importWallet(address) {
4673
+ const wanted = address.trim().toLowerCase();
4674
+ for (const entry of scanWallets()) {
4675
+ let derived;
4676
+ try {
4677
+ derived = (0, import_accounts15.privateKeyToAccount)(entry.privateKey).address;
4678
+ } catch {
4679
+ continue;
4680
+ }
4681
+ if (derived.toLowerCase() !== wanted) continue;
4682
+ if (fs2.existsSync(WALLET_FILE)) {
4683
+ const current = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
4684
+ if (current && current !== entry.privateKey) {
4685
+ const backup = path2.join(WALLET_DIR, `.session.backup-${Math.floor(Date.now() / 1e3)}`);
4686
+ fs2.writeFileSync(backup, current, { mode: 384 });
4687
+ }
4688
+ }
4689
+ saveWallet(entry.privateKey);
4690
+ return derived;
4691
+ }
4692
+ const available = listDiscoveredWallets().map((w) => w.address);
4693
+ throw new Error(
4694
+ `No discovered wallet controls ${address}. Available: ${available.length ? available.join(", ") : "none"}`
4695
+ );
4644
4696
  }
4645
4697
  function loadWallet() {
4646
4698
  if (fs2.existsSync(WALLET_FILE)) {
@@ -4685,11 +4737,13 @@ Discovered wallets are never adopted automatically \u2014 one may belong to a
4685
4737
  different application, or have been planted to make you fund an address you
4686
4738
  do not control.
4687
4739
 
4688
- If an address above is yours and holds your USDC, import it deliberately:
4740
+ If an address above is yours and holds your USDC, adopt it deliberately:
4689
4741
 
4690
- export BLOCKRUN_WALLET_KEY=<private-key>
4742
+ import { importWallet } from '@blockrun/llm';
4743
+ importWallet("<address-from-the-list-above>");
4691
4744
 
4692
- or write that key to ~/.blockrun/.session
4745
+ Your current wallet is backed up first. You can also set
4746
+ BLOCKRUN_WALLET_KEY=<private-key> for a single run without changing anything.
4693
4747
  `;
4694
4748
  }
4695
4749
  function getOrCreateWallet() {
@@ -4834,7 +4888,7 @@ function scanSolanaWallets() {
4834
4888
  const addr = data.address || "";
4835
4889
  if (pk && addr) {
4836
4890
  const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
4837
- results.push({ mtime, secretKey: pk, publicKey: addr });
4891
+ results.push({ mtime, secretKey: pk, publicKey: addr, source: solanaWalletFile });
4838
4892
  }
4839
4893
  } catch {
4840
4894
  }
@@ -4848,7 +4902,7 @@ function scanSolanaWallets() {
4848
4902
  const addr = data.address || "";
4849
4903
  if (pk && addr) {
4850
4904
  const mtime = fs3.statSync(brccWalletFile).mtimeMs;
4851
- results.push({ mtime, secretKey: pk, publicKey: addr });
4905
+ results.push({ mtime, secretKey: pk, publicKey: addr, source: brccWalletFile });
4852
4906
  }
4853
4907
  } catch {
4854
4908
  }
@@ -4858,7 +4912,7 @@ function scanSolanaWallets() {
4858
4912
  } catch {
4859
4913
  }
4860
4914
  results.sort((a, b) => b.mtime - a.mtime);
4861
- return results.map(({ secretKey, publicKey }) => ({ secretKey, publicKey }));
4915
+ return results.map(({ secretKey, publicKey, source }) => ({ secretKey, publicKey, source }));
4862
4916
  }
4863
4917
  function loadSolanaWallet() {
4864
4918
  if (fs3.existsSync(SOLANA_WALLET_FILE)) {
@@ -4867,6 +4921,45 @@ function loadSolanaWallet() {
4867
4921
  }
4868
4922
  return null;
4869
4923
  }
4924
+ async function listDiscoveredSolanaWallets() {
4925
+ const listed = [];
4926
+ for (const entry of scanSolanaWallets()) {
4927
+ try {
4928
+ listed.push({ address: await solanaPublicKey(entry.secretKey), source: entry.source });
4929
+ } catch {
4930
+ continue;
4931
+ }
4932
+ }
4933
+ return listed;
4934
+ }
4935
+ async function importSolanaWallet(address) {
4936
+ const wanted = address.trim();
4937
+ for (const entry of scanSolanaWallets()) {
4938
+ let derived;
4939
+ try {
4940
+ derived = await solanaPublicKey(entry.secretKey);
4941
+ } catch {
4942
+ continue;
4943
+ }
4944
+ if (derived !== wanted) continue;
4945
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
4946
+ const current = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
4947
+ if (current && current !== entry.secretKey) {
4948
+ const backup = path3.join(
4949
+ WALLET_DIR2,
4950
+ `.solana-session.backup-${Math.floor(Date.now() / 1e3)}`
4951
+ );
4952
+ fs3.writeFileSync(backup, current, { mode: 384 });
4953
+ }
4954
+ }
4955
+ saveSolanaWallet(entry.secretKey);
4956
+ return derived;
4957
+ }
4958
+ const available = (await listDiscoveredSolanaWallets()).map((w) => w.address);
4959
+ throw new Error(
4960
+ `No discovered wallet controls ${address}. Available: ${available.length ? available.join(", ") : "none"}`
4961
+ );
4962
+ }
4870
4963
  async function formatSolanaWalletMigrationNotice(newAddress) {
4871
4964
  let discovered;
4872
4965
  try {
@@ -4898,11 +4991,13 @@ Discovered wallets are never adopted automatically \u2014 one may belong to a
4898
4991
  different application, or have been planted to make you fund an address you
4899
4992
  do not control.
4900
4993
 
4901
- If an address above is yours and holds your USDC, import it deliberately:
4994
+ If an address above is yours and holds your USDC, adopt it deliberately:
4902
4995
 
4903
- export SOLANA_WALLET_KEY=<private-key>
4996
+ import { importSolanaWallet } from '@blockrun/llm';
4997
+ await importSolanaWallet("<address-from-the-list-above>");
4904
4998
 
4905
- or write that key to ~/.blockrun/.solana-session
4999
+ Your current wallet is backed up first. You can also set
5000
+ SOLANA_WALLET_KEY=<private-key> for a single run without changing anything.
4906
5001
  `;
4907
5002
  }
4908
5003
  async function getOrCreateSolanaWallet() {
@@ -5001,6 +5096,7 @@ var SolanaLLMClient = class {
5001
5096
  }
5002
5097
  /** Full chat completion (OpenAI-compatible). */
5003
5098
  async chatCompletion(model, messages, options) {
5099
+ validateMaxTokens(options?.maxTokens);
5004
5100
  const body = {
5005
5101
  model,
5006
5102
  messages,
@@ -6043,6 +6139,7 @@ var AnthropicClient = class {
6043
6139
  ImageClient,
6044
6140
  KNOWN_PROVIDERS,
6045
6141
  LLMClient,
6142
+ MAX_TOKENS_SANITY_LIMIT,
6046
6143
  MusicClient,
6047
6144
  NETWORK_ALIASES,
6048
6145
  OpenAI,
@@ -6088,6 +6185,10 @@ var AnthropicClient = class {
6088
6185
  getOrCreateWallet,
6089
6186
  getPaymentLinks,
6090
6187
  getWalletAddress,
6188
+ importSolanaWallet,
6189
+ importWallet,
6190
+ listDiscoveredSolanaWallets,
6191
+ listDiscoveredWallets,
6091
6192
  loadSolanaWallet,
6092
6193
  loadWallet,
6093
6194
  logCost,
package/dist/index.d.cts CHANGED
@@ -2539,7 +2539,39 @@ declare function saveWallet(privateKey: string): string;
2539
2539
  declare function scanWallets(): Array<{
2540
2540
  privateKey: string;
2541
2541
  address: string;
2542
+ source: string;
2542
2543
  }>;
2544
+ /**
2545
+ * List wallets from other applications, safe to show to a user.
2546
+ *
2547
+ * Unlike `scanWallets()`, no private key is returned and the address is derived
2548
+ * from the key rather than read from the file, so a wallet file cannot claim an
2549
+ * address it has no key for.
2550
+ *
2551
+ * Nothing here is active. Adopt one deliberately with `importWallet()`.
2552
+ *
2553
+ * @returns Discovered wallets as `{ address, source }`, most recent first
2554
+ */
2555
+ declare function listDiscoveredWallets(): Array<{
2556
+ address: string;
2557
+ source: string;
2558
+ }>;
2559
+ /**
2560
+ * Adopt a discovered wallet by address, making it the active BlockRun wallet.
2561
+ *
2562
+ * This is the deliberate migration path: automatic selection never adopts a
2563
+ * discovered wallet, but you can choose one whose funds you want to spend.
2564
+ * Matching is done against the address *derived from each discovered key*, so a
2565
+ * wallet file claiming someone else's address can never be selected by it.
2566
+ *
2567
+ * The current `~/.blockrun/.session` is backed up beside itself before being
2568
+ * overwritten, so adopting a wallet can't strand the funds in the old one.
2569
+ *
2570
+ * @param address Address to adopt, as shown by `listDiscoveredWallets()`
2571
+ * @returns The adopted address
2572
+ * @throws If no discovered wallet derives to that address
2573
+ */
2574
+ declare function importWallet(address: string): string;
2543
2575
  /**
2544
2576
  * Load wallet private key from file.
2545
2577
  *
@@ -2850,8 +2882,34 @@ declare function saveSolanaWallet(privateKey: string): string;
2850
2882
  declare function scanSolanaWallets(): Array<{
2851
2883
  secretKey: string;
2852
2884
  publicKey: string;
2885
+ source: string;
2853
2886
  }>;
2854
2887
  declare function loadSolanaWallet(): string | null;
2888
+ /**
2889
+ * List Solana wallets from other applications, safe to show to a user.
2890
+ *
2891
+ * Solana counterpart of `listDiscoveredWallets()`: no secret key is returned
2892
+ * and the address is derived from the key rather than trusted from the file.
2893
+ * Nothing here is active — adopt one with `importSolanaWallet()`.
2894
+ *
2895
+ * @returns Discovered wallets as `{ address, source }`, most recent first
2896
+ */
2897
+ declare function listDiscoveredSolanaWallets(): Promise<Array<{
2898
+ address: string;
2899
+ source: string;
2900
+ }>>;
2901
+ /**
2902
+ * Adopt a discovered Solana wallet, making it the active BlockRun wallet.
2903
+ *
2904
+ * Solana counterpart of `importWallet()`. Matching is done against the address
2905
+ * derived from each discovered key, and the current
2906
+ * `~/.blockrun/.solana-session` is backed up before being overwritten.
2907
+ *
2908
+ * @param address Address to adopt, as shown by `listDiscoveredSolanaWallets()`
2909
+ * @returns The adopted address
2910
+ * @throws If no discovered wallet derives to that address
2911
+ */
2912
+ declare function importSolanaWallet(address: string): Promise<string>;
2855
2913
  /**
2856
2914
  * Warn when a new Solana wallet was created while provider wallets exist.
2857
2915
  *
@@ -3103,7 +3161,34 @@ declare const KNOWN_PROVIDERS: Set<string>;
3103
3161
  */
3104
3162
  declare function validateModel(model: string): void;
3105
3163
  /**
3106
- * Validates that max_tokens is an integer between 1 and 100,000.
3164
+ * Client-side typo guard, NOT a model limit. The gateway already enforces the
3165
+ * real per-model ceiling and rejects with that model's own number, so anything
3166
+ * hardcoded here can only be wrong in one direction: too low.
3167
+ *
3168
+ * This was 100000. Note what it was and was not doing: until this change no
3169
+ * client called `validateMaxTokens` at all, so the bound bound nothing on a
3170
+ * real request — it only rejected callers who imported the validator directly
3171
+ * off the package's public surface. The guard is now wired into
3172
+ * `LLMClient.chatCompletion`, `LLMClient.chatCompletionStream`, and
3173
+ * `SolanaLLMClient.chatCompletion`, which is exactly why the number had to go
3174
+ * up first: at 100000 it would have started rejecting real requests.
3175
+ *
3176
+ * Verified against the live gateway 2026-07-21
3177
+ * with the guard bypassed: zai/glm-5.2 accepted 262144, and the whole 128000
3178
+ * class accepted 128000 (claude-opus-4.8, claude-sonnet-5, claude-fable-5,
3179
+ * gpt-5.6-sol/terra/luna, gpt-5.5, gpt-5.4, gpt-5.3-codex, glm-5/5.1/5-turbo).
3180
+ * 19 models advertised above 100000; all 19 accepted their ceiling. Those are
3181
+ * as-of-date probe results, not a maintained invariant — nothing in this repo
3182
+ * ships a model catalog, so CI cannot detect when they go stale.
3183
+ *
3184
+ * Keep a bound so an obvious mistake (1e9, a byte count, a timestamp) fails
3185
+ * fast locally instead of becoming a payment quote. Set it far above any real
3186
+ * model so it can never be the binding constraint again.
3187
+ */
3188
+ declare const MAX_TOKENS_SANITY_LIMIT = 1000000;
3189
+ /**
3190
+ * Validates that max_tokens is a positive integer no larger than
3191
+ * {@link MAX_TOKENS_SANITY_LIMIT}.
3107
3192
  *
3108
3193
  * @param maxTokens - Maximum number of tokens to generate
3109
3194
  * @throws {Error} If maxTokens is invalid
@@ -3133,4 +3218,4 @@ declare function validateTemperature(temperature?: number): void;
3133
3218
  */
3134
3219
  declare function validateTopP(topP?: number): void;
3135
3220
 
3136
- export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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, 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 ResponseFormat, type RoutingDecision, type RoutingProfile, type RoutingTier, 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 SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
3221
+ export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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, MAX_TOKENS_SANITY_LIMIT, 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, 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 ResponseFormat, type RoutingDecision, type RoutingProfile, type RoutingTier, 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 SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.d.ts CHANGED
@@ -2539,7 +2539,39 @@ declare function saveWallet(privateKey: string): string;
2539
2539
  declare function scanWallets(): Array<{
2540
2540
  privateKey: string;
2541
2541
  address: string;
2542
+ source: string;
2542
2543
  }>;
2544
+ /**
2545
+ * List wallets from other applications, safe to show to a user.
2546
+ *
2547
+ * Unlike `scanWallets()`, no private key is returned and the address is derived
2548
+ * from the key rather than read from the file, so a wallet file cannot claim an
2549
+ * address it has no key for.
2550
+ *
2551
+ * Nothing here is active. Adopt one deliberately with `importWallet()`.
2552
+ *
2553
+ * @returns Discovered wallets as `{ address, source }`, most recent first
2554
+ */
2555
+ declare function listDiscoveredWallets(): Array<{
2556
+ address: string;
2557
+ source: string;
2558
+ }>;
2559
+ /**
2560
+ * Adopt a discovered wallet by address, making it the active BlockRun wallet.
2561
+ *
2562
+ * This is the deliberate migration path: automatic selection never adopts a
2563
+ * discovered wallet, but you can choose one whose funds you want to spend.
2564
+ * Matching is done against the address *derived from each discovered key*, so a
2565
+ * wallet file claiming someone else's address can never be selected by it.
2566
+ *
2567
+ * The current `~/.blockrun/.session` is backed up beside itself before being
2568
+ * overwritten, so adopting a wallet can't strand the funds in the old one.
2569
+ *
2570
+ * @param address Address to adopt, as shown by `listDiscoveredWallets()`
2571
+ * @returns The adopted address
2572
+ * @throws If no discovered wallet derives to that address
2573
+ */
2574
+ declare function importWallet(address: string): string;
2543
2575
  /**
2544
2576
  * Load wallet private key from file.
2545
2577
  *
@@ -2850,8 +2882,34 @@ declare function saveSolanaWallet(privateKey: string): string;
2850
2882
  declare function scanSolanaWallets(): Array<{
2851
2883
  secretKey: string;
2852
2884
  publicKey: string;
2885
+ source: string;
2853
2886
  }>;
2854
2887
  declare function loadSolanaWallet(): string | null;
2888
+ /**
2889
+ * List Solana wallets from other applications, safe to show to a user.
2890
+ *
2891
+ * Solana counterpart of `listDiscoveredWallets()`: no secret key is returned
2892
+ * and the address is derived from the key rather than trusted from the file.
2893
+ * Nothing here is active — adopt one with `importSolanaWallet()`.
2894
+ *
2895
+ * @returns Discovered wallets as `{ address, source }`, most recent first
2896
+ */
2897
+ declare function listDiscoveredSolanaWallets(): Promise<Array<{
2898
+ address: string;
2899
+ source: string;
2900
+ }>>;
2901
+ /**
2902
+ * Adopt a discovered Solana wallet, making it the active BlockRun wallet.
2903
+ *
2904
+ * Solana counterpart of `importWallet()`. Matching is done against the address
2905
+ * derived from each discovered key, and the current
2906
+ * `~/.blockrun/.solana-session` is backed up before being overwritten.
2907
+ *
2908
+ * @param address Address to adopt, as shown by `listDiscoveredSolanaWallets()`
2909
+ * @returns The adopted address
2910
+ * @throws If no discovered wallet derives to that address
2911
+ */
2912
+ declare function importSolanaWallet(address: string): Promise<string>;
2855
2913
  /**
2856
2914
  * Warn when a new Solana wallet was created while provider wallets exist.
2857
2915
  *
@@ -3103,7 +3161,34 @@ declare const KNOWN_PROVIDERS: Set<string>;
3103
3161
  */
3104
3162
  declare function validateModel(model: string): void;
3105
3163
  /**
3106
- * Validates that max_tokens is an integer between 1 and 100,000.
3164
+ * Client-side typo guard, NOT a model limit. The gateway already enforces the
3165
+ * real per-model ceiling and rejects with that model's own number, so anything
3166
+ * hardcoded here can only be wrong in one direction: too low.
3167
+ *
3168
+ * This was 100000. Note what it was and was not doing: until this change no
3169
+ * client called `validateMaxTokens` at all, so the bound bound nothing on a
3170
+ * real request — it only rejected callers who imported the validator directly
3171
+ * off the package's public surface. The guard is now wired into
3172
+ * `LLMClient.chatCompletion`, `LLMClient.chatCompletionStream`, and
3173
+ * `SolanaLLMClient.chatCompletion`, which is exactly why the number had to go
3174
+ * up first: at 100000 it would have started rejecting real requests.
3175
+ *
3176
+ * Verified against the live gateway 2026-07-21
3177
+ * with the guard bypassed: zai/glm-5.2 accepted 262144, and the whole 128000
3178
+ * class accepted 128000 (claude-opus-4.8, claude-sonnet-5, claude-fable-5,
3179
+ * gpt-5.6-sol/terra/luna, gpt-5.5, gpt-5.4, gpt-5.3-codex, glm-5/5.1/5-turbo).
3180
+ * 19 models advertised above 100000; all 19 accepted their ceiling. Those are
3181
+ * as-of-date probe results, not a maintained invariant — nothing in this repo
3182
+ * ships a model catalog, so CI cannot detect when they go stale.
3183
+ *
3184
+ * Keep a bound so an obvious mistake (1e9, a byte count, a timestamp) fails
3185
+ * fast locally instead of becoming a payment quote. Set it far above any real
3186
+ * model so it can never be the binding constraint again.
3187
+ */
3188
+ declare const MAX_TOKENS_SANITY_LIMIT = 1000000;
3189
+ /**
3190
+ * Validates that max_tokens is a positive integer no larger than
3191
+ * {@link MAX_TOKENS_SANITY_LIMIT}.
3107
3192
  *
3108
3193
  * @param maxTokens - Maximum number of tokens to generate
3109
3194
  * @throws {Error} If maxTokens is invalid
@@ -3133,4 +3218,4 @@ declare function validateTemperature(temperature?: number): void;
3133
3218
  */
3134
3219
  declare function validateTopP(topP?: number): void;
3135
3220
 
3136
- export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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, 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 ResponseFormat, type RoutingDecision, type RoutingProfile, type RoutingTier, 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 SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
3221
+ export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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, MAX_TOKENS_SANITY_LIMIT, 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, 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 ResponseFormat, type RoutingDecision, type RoutingProfile, type RoutingTier, 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 SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.js CHANGED
@@ -242,6 +242,7 @@ function validateModel(model) {
242
242
  throw new Error("Model must be a non-empty string");
243
243
  }
244
244
  }
245
+ var MAX_TOKENS_SANITY_LIMIT = 1e6;
245
246
  function validateMaxTokens(maxTokens) {
246
247
  if (maxTokens === void 0 || maxTokens === null) {
247
248
  return;
@@ -252,8 +253,13 @@ function validateMaxTokens(maxTokens) {
252
253
  if (maxTokens < 1) {
253
254
  throw new Error("maxTokens must be positive (minimum: 1)");
254
255
  }
255
- if (maxTokens > 1e5) {
256
- throw new Error("maxTokens too large (maximum: 100000)");
256
+ if (maxTokens > MAX_TOKENS_SANITY_LIMIT) {
257
+ throw Object.assign(
258
+ new Error(
259
+ `maxTokens implausibly large (client-side sanity limit: ${MAX_TOKENS_SANITY_LIMIT}). This is not a model limit \u2014 the gateway enforces the real per-model ceiling and reports it.`
260
+ ),
261
+ { code: "MAX_TOKENS_SANITY_LIMIT", limit: MAX_TOKENS_SANITY_LIMIT }
262
+ );
257
263
  }
258
264
  }
259
265
  function validateTemperature(temperature) {
@@ -653,6 +659,7 @@ var LLMClient = class _LLMClient {
653
659
  * @returns ChatResponse object with choices and usage
654
660
  */
655
661
  async chatCompletion(model, messages, options) {
662
+ validateMaxTokens(options?.maxTokens);
656
663
  const buildBody = (m) => {
657
664
  const body = {
658
665
  model: m,
@@ -923,6 +930,7 @@ var LLMClient = class _LLMClient {
923
930
  * @returns Raw fetch Response with a streaming SSE body.
924
931
  */
925
932
  async chatCompletionStream(model, messages, options) {
933
+ validateMaxTokens(options?.maxTokens);
926
934
  const url = `${this.apiUrl}/v1/chat/completions`;
927
935
  const body = {
928
936
  model,
@@ -4522,7 +4530,7 @@ function scanWallets() {
4522
4530
  const addr = data.address || "";
4523
4531
  if (pk && addr) {
4524
4532
  const mtime = fs2.statSync(walletFile).mtimeMs;
4525
- results.push({ mtime, privateKey: pk, address: addr });
4533
+ results.push({ mtime, privateKey: pk, address: addr, source: walletFile });
4526
4534
  }
4527
4535
  } catch {
4528
4536
  continue;
@@ -4531,7 +4539,46 @@ function scanWallets() {
4531
4539
  } catch {
4532
4540
  }
4533
4541
  results.sort((a, b) => b.mtime - a.mtime);
4534
- return results.map(({ privateKey, address }) => ({ privateKey, address }));
4542
+ return results.map(({ privateKey, address, source }) => ({ privateKey, address, source }));
4543
+ }
4544
+ function listDiscoveredWallets() {
4545
+ const listed = [];
4546
+ for (const entry of scanWallets()) {
4547
+ try {
4548
+ listed.push({
4549
+ address: privateKeyToAccount14(entry.privateKey).address,
4550
+ source: entry.source
4551
+ });
4552
+ } catch {
4553
+ continue;
4554
+ }
4555
+ }
4556
+ return listed;
4557
+ }
4558
+ function importWallet(address) {
4559
+ const wanted = address.trim().toLowerCase();
4560
+ for (const entry of scanWallets()) {
4561
+ let derived;
4562
+ try {
4563
+ derived = privateKeyToAccount14(entry.privateKey).address;
4564
+ } catch {
4565
+ continue;
4566
+ }
4567
+ if (derived.toLowerCase() !== wanted) continue;
4568
+ if (fs2.existsSync(WALLET_FILE)) {
4569
+ const current = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
4570
+ if (current && current !== entry.privateKey) {
4571
+ const backup = path2.join(WALLET_DIR, `.session.backup-${Math.floor(Date.now() / 1e3)}`);
4572
+ fs2.writeFileSync(backup, current, { mode: 384 });
4573
+ }
4574
+ }
4575
+ saveWallet(entry.privateKey);
4576
+ return derived;
4577
+ }
4578
+ const available = listDiscoveredWallets().map((w) => w.address);
4579
+ throw new Error(
4580
+ `No discovered wallet controls ${address}. Available: ${available.length ? available.join(", ") : "none"}`
4581
+ );
4535
4582
  }
4536
4583
  function loadWallet() {
4537
4584
  if (fs2.existsSync(WALLET_FILE)) {
@@ -4576,11 +4623,13 @@ Discovered wallets are never adopted automatically \u2014 one may belong to a
4576
4623
  different application, or have been planted to make you fund an address you
4577
4624
  do not control.
4578
4625
 
4579
- If an address above is yours and holds your USDC, import it deliberately:
4626
+ If an address above is yours and holds your USDC, adopt it deliberately:
4580
4627
 
4581
- export BLOCKRUN_WALLET_KEY=<private-key>
4628
+ import { importWallet } from '@blockrun/llm';
4629
+ importWallet("<address-from-the-list-above>");
4582
4630
 
4583
- or write that key to ~/.blockrun/.session
4631
+ Your current wallet is backed up first. You can also set
4632
+ BLOCKRUN_WALLET_KEY=<private-key> for a single run without changing anything.
4584
4633
  `;
4585
4634
  }
4586
4635
  function getOrCreateWallet() {
@@ -4725,7 +4774,7 @@ function scanSolanaWallets() {
4725
4774
  const addr = data.address || "";
4726
4775
  if (pk && addr) {
4727
4776
  const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
4728
- results.push({ mtime, secretKey: pk, publicKey: addr });
4777
+ results.push({ mtime, secretKey: pk, publicKey: addr, source: solanaWalletFile });
4729
4778
  }
4730
4779
  } catch {
4731
4780
  }
@@ -4739,7 +4788,7 @@ function scanSolanaWallets() {
4739
4788
  const addr = data.address || "";
4740
4789
  if (pk && addr) {
4741
4790
  const mtime = fs3.statSync(brccWalletFile).mtimeMs;
4742
- results.push({ mtime, secretKey: pk, publicKey: addr });
4791
+ results.push({ mtime, secretKey: pk, publicKey: addr, source: brccWalletFile });
4743
4792
  }
4744
4793
  } catch {
4745
4794
  }
@@ -4749,7 +4798,7 @@ function scanSolanaWallets() {
4749
4798
  } catch {
4750
4799
  }
4751
4800
  results.sort((a, b) => b.mtime - a.mtime);
4752
- return results.map(({ secretKey, publicKey }) => ({ secretKey, publicKey }));
4801
+ return results.map(({ secretKey, publicKey, source }) => ({ secretKey, publicKey, source }));
4753
4802
  }
4754
4803
  function loadSolanaWallet() {
4755
4804
  if (fs3.existsSync(SOLANA_WALLET_FILE)) {
@@ -4758,6 +4807,45 @@ function loadSolanaWallet() {
4758
4807
  }
4759
4808
  return null;
4760
4809
  }
4810
+ async function listDiscoveredSolanaWallets() {
4811
+ const listed = [];
4812
+ for (const entry of scanSolanaWallets()) {
4813
+ try {
4814
+ listed.push({ address: await solanaPublicKey(entry.secretKey), source: entry.source });
4815
+ } catch {
4816
+ continue;
4817
+ }
4818
+ }
4819
+ return listed;
4820
+ }
4821
+ async function importSolanaWallet(address) {
4822
+ const wanted = address.trim();
4823
+ for (const entry of scanSolanaWallets()) {
4824
+ let derived;
4825
+ try {
4826
+ derived = await solanaPublicKey(entry.secretKey);
4827
+ } catch {
4828
+ continue;
4829
+ }
4830
+ if (derived !== wanted) continue;
4831
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
4832
+ const current = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
4833
+ if (current && current !== entry.secretKey) {
4834
+ const backup = path3.join(
4835
+ WALLET_DIR2,
4836
+ `.solana-session.backup-${Math.floor(Date.now() / 1e3)}`
4837
+ );
4838
+ fs3.writeFileSync(backup, current, { mode: 384 });
4839
+ }
4840
+ }
4841
+ saveSolanaWallet(entry.secretKey);
4842
+ return derived;
4843
+ }
4844
+ const available = (await listDiscoveredSolanaWallets()).map((w) => w.address);
4845
+ throw new Error(
4846
+ `No discovered wallet controls ${address}. Available: ${available.length ? available.join(", ") : "none"}`
4847
+ );
4848
+ }
4761
4849
  async function formatSolanaWalletMigrationNotice(newAddress) {
4762
4850
  let discovered;
4763
4851
  try {
@@ -4789,11 +4877,13 @@ Discovered wallets are never adopted automatically \u2014 one may belong to a
4789
4877
  different application, or have been planted to make you fund an address you
4790
4878
  do not control.
4791
4879
 
4792
- If an address above is yours and holds your USDC, import it deliberately:
4880
+ If an address above is yours and holds your USDC, adopt it deliberately:
4793
4881
 
4794
- export SOLANA_WALLET_KEY=<private-key>
4882
+ import { importSolanaWallet } from '@blockrun/llm';
4883
+ await importSolanaWallet("<address-from-the-list-above>");
4795
4884
 
4796
- or write that key to ~/.blockrun/.solana-session
4885
+ Your current wallet is backed up first. You can also set
4886
+ SOLANA_WALLET_KEY=<private-key> for a single run without changing anything.
4797
4887
  `;
4798
4888
  }
4799
4889
  async function getOrCreateSolanaWallet() {
@@ -4892,6 +4982,7 @@ var SolanaLLMClient = class {
4892
4982
  }
4893
4983
  /** Full chat completion (OpenAI-compatible). */
4894
4984
  async chatCompletion(model, messages, options) {
4985
+ validateMaxTokens(options?.maxTokens);
4895
4986
  const body = {
4896
4987
  model,
4897
4988
  messages,
@@ -5933,6 +6024,7 @@ export {
5933
6024
  ImageClient,
5934
6025
  KNOWN_PROVIDERS,
5935
6026
  LLMClient,
6027
+ MAX_TOKENS_SANITY_LIMIT,
5936
6028
  MusicClient,
5937
6029
  NETWORK_ALIASES,
5938
6030
  OpenAI,
@@ -5979,6 +6071,10 @@ export {
5979
6071
  getOrCreateWallet,
5980
6072
  getPaymentLinks,
5981
6073
  getWalletAddress,
6074
+ importSolanaWallet,
6075
+ importWallet,
6076
+ listDiscoveredSolanaWallets,
6077
+ listDiscoveredWallets,
5982
6078
  loadSolanaWallet,
5983
6079
  loadWallet,
5984
6080
  logCost,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "3.7.1",
3
+ "version": "3.8.1",
4
4
  "type": "module",
5
5
  "description": "BlockRun SDK - Pay-per-request AI (LLM, Image, Video, Music, Voice) via x402 on Base and Solana",
6
6
  "main": "dist/index.cjs",