@blockrun/llm 1.4.3 → 1.5.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/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  BASE_CHAIN_ID: () => BASE_CHAIN_ID,
36
36
  BlockrunError: () => BlockrunError,
37
37
  ImageClient: () => ImageClient,
38
+ KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
38
39
  LLMClient: () => LLMClient,
39
40
  OpenAI: () => OpenAI,
40
41
  PaymentError: () => PaymentError,
@@ -58,6 +59,7 @@ __export(index_exports, {
58
59
  formatWalletCreatedMessage: () => formatWalletCreatedMessage,
59
60
  getCached: () => getCached,
60
61
  getCachedByRequest: () => getCachedByRequest,
62
+ getCostLogSummary: () => getCostLogSummary,
61
63
  getCostSummary: () => getCostSummary,
62
64
  getEip681Uri: () => getEip681Uri,
63
65
  getOrCreateSolanaWallet: () => getOrCreateSolanaWallet,
@@ -80,7 +82,11 @@ __export(index_exports, {
80
82
  solanaKeyToBytes: () => solanaKeyToBytes,
81
83
  solanaPublicKey: () => solanaPublicKey,
82
84
  status: () => status,
83
- testnetClient: () => testnetClient
85
+ testnetClient: () => testnetClient,
86
+ validateMaxTokens: () => validateMaxTokens,
87
+ validateModel: () => validateModel,
88
+ validateTemperature: () => validateTemperature,
89
+ validateTopP: () => validateTopP
84
90
  });
85
91
  module.exports = __toCommonJS(index_exports);
86
92
 
@@ -302,6 +308,61 @@ function extractPaymentDetails(paymentRequired, preferredNetwork) {
302
308
 
303
309
  // src/validation.ts
304
310
  var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
311
+ var KNOWN_PROVIDERS = /* @__PURE__ */ new Set([
312
+ "openai",
313
+ "anthropic",
314
+ "google",
315
+ "deepseek",
316
+ "mistralai",
317
+ "meta-llama",
318
+ "together",
319
+ "xai",
320
+ "moonshot",
321
+ "nvidia",
322
+ "minimax",
323
+ "zai"
324
+ ]);
325
+ function validateModel(model) {
326
+ if (!model || typeof model !== "string") {
327
+ throw new Error("Model must be a non-empty string");
328
+ }
329
+ }
330
+ function validateMaxTokens(maxTokens) {
331
+ if (maxTokens === void 0 || maxTokens === null) {
332
+ return;
333
+ }
334
+ if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens)) {
335
+ throw new Error("maxTokens must be an integer");
336
+ }
337
+ if (maxTokens < 1) {
338
+ throw new Error("maxTokens must be positive (minimum: 1)");
339
+ }
340
+ if (maxTokens > 1e5) {
341
+ throw new Error("maxTokens too large (maximum: 100000)");
342
+ }
343
+ }
344
+ function validateTemperature(temperature) {
345
+ if (temperature === void 0 || temperature === null) {
346
+ return;
347
+ }
348
+ if (typeof temperature !== "number") {
349
+ throw new Error("temperature must be a number");
350
+ }
351
+ if (temperature < 0 || temperature > 2) {
352
+ throw new Error("temperature must be between 0 and 2");
353
+ }
354
+ }
355
+ function validateTopP(topP) {
356
+ if (topP === void 0 || topP === null) {
357
+ return;
358
+ }
359
+ if (typeof topP !== "number") {
360
+ throw new Error("topP must be a number");
361
+ }
362
+ if (topP < 0 || topP > 1) {
363
+ throw new Error("topP must be between 0 and 1");
364
+ }
365
+ }
305
366
  function validatePrivateKey(key) {
306
367
  if (typeof key !== "string") {
307
368
  throw new Error("Private key must be a string");
@@ -377,7 +438,7 @@ var DEFAULT_API_URL = "https://blockrun.ai/api";
377
438
  var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
378
439
  var DEFAULT_MAX_TOKENS = 1024;
379
440
  var DEFAULT_TIMEOUT = 6e4;
380
- var SDK_VERSION = "0.3.0";
441
+ var SDK_VERSION = "1.5.0";
381
442
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
382
443
  var LLMClient = class {
383
444
  static DEFAULT_API_URL = DEFAULT_API_URL;
@@ -414,13 +475,13 @@ var LLMClient = class {
414
475
  /**
415
476
  * Simple 1-line chat interface.
416
477
  *
417
- * @param model - Model ID (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4')
478
+ * @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
418
479
  * @param prompt - User message
419
480
  * @param options - Optional chat parameters
420
481
  * @returns Assistant's response text
421
482
  *
422
483
  * @example
423
- * const response = await client.chat('gpt-4o', 'What is the capital of France?');
484
+ * const response = await client.chat('gpt-5.2', 'What is the capital of France?');
424
485
  * console.log(response); // 'The capital of France is Paris.'
425
486
  */
426
487
  async chat(model, prompt, options) {
@@ -566,6 +627,27 @@ var LLMClient = class {
566
627
  headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
567
628
  body: JSON.stringify(body)
568
629
  });
630
+ if (response.status === 502 || response.status === 503) {
631
+ await new Promise((r) => setTimeout(r, 1e3));
632
+ const retryResp = await this.fetchWithTimeout(url, {
633
+ method: "POST",
634
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
635
+ body: JSON.stringify(body)
636
+ });
637
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
638
+ if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
639
+ if (!retryResp.ok) {
640
+ let errorBody;
641
+ try {
642
+ errorBody = await retryResp.json();
643
+ } catch {
644
+ errorBody = { error: "Request failed" };
645
+ }
646
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
647
+ }
648
+ return retryResp.json();
649
+ }
650
+ }
569
651
  if (response.status === 402) {
570
652
  return this.handlePaymentAndRetry(url, body, response);
571
653
  }
@@ -631,6 +713,34 @@ var LLMClient = class {
631
713
  },
632
714
  body: JSON.stringify(body)
633
715
  });
716
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
717
+ await new Promise((r) => setTimeout(r, 1e3));
718
+ const retryResp2 = await this.fetchWithTimeout(url, {
719
+ method: "POST",
720
+ headers: {
721
+ "Content-Type": "application/json",
722
+ "User-Agent": USER_AGENT,
723
+ "PAYMENT-SIGNATURE": paymentPayload
724
+ },
725
+ body: JSON.stringify(body)
726
+ });
727
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
728
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
729
+ if (!retryResp2.ok) {
730
+ let errorBody;
731
+ try {
732
+ errorBody = await retryResp2.json();
733
+ } catch {
734
+ errorBody = { error: "Request failed" };
735
+ }
736
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
737
+ }
738
+ const costUsd2 = parseFloat(details.amount) / 1e6;
739
+ this.sessionCalls += 1;
740
+ this.sessionTotalUsd += costUsd2;
741
+ return retryResp2.json();
742
+ }
743
+ }
634
744
  if (retryResponse.status === 402) {
635
745
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
636
746
  }
@@ -663,6 +773,27 @@ var LLMClient = class {
663
773
  headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
664
774
  body: JSON.stringify(body)
665
775
  });
776
+ if (response.status === 502 || response.status === 503) {
777
+ await new Promise((r) => setTimeout(r, 1e3));
778
+ const retryResp = await this.fetchWithTimeout(url, {
779
+ method: "POST",
780
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
781
+ body: JSON.stringify(body)
782
+ });
783
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
784
+ if (retryResp.status === 402) return this.handlePaymentAndRetryRaw(url, body, retryResp);
785
+ if (!retryResp.ok) {
786
+ let errorBody;
787
+ try {
788
+ errorBody = await retryResp.json();
789
+ } catch {
790
+ errorBody = { error: "Request failed" };
791
+ }
792
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
793
+ }
794
+ return retryResp.json();
795
+ }
796
+ }
666
797
  if (response.status === 402) {
667
798
  return this.handlePaymentAndRetryRaw(url, body, response);
668
799
  }
@@ -728,6 +859,34 @@ var LLMClient = class {
728
859
  },
729
860
  body: JSON.stringify(body)
730
861
  });
862
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
863
+ await new Promise((r) => setTimeout(r, 1e3));
864
+ const retryResp2 = await this.fetchWithTimeout(url, {
865
+ method: "POST",
866
+ headers: {
867
+ "Content-Type": "application/json",
868
+ "User-Agent": USER_AGENT,
869
+ "PAYMENT-SIGNATURE": paymentPayload
870
+ },
871
+ body: JSON.stringify(body)
872
+ });
873
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
874
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
875
+ if (!retryResp2.ok) {
876
+ let errorBody;
877
+ try {
878
+ errorBody = await retryResp2.json();
879
+ } catch {
880
+ errorBody = { error: "Request failed" };
881
+ }
882
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
883
+ }
884
+ const costUsd2 = parseFloat(details.amount) / 1e6;
885
+ this.sessionCalls += 1;
886
+ this.sessionTotalUsd += costUsd2;
887
+ return retryResp2.json();
888
+ }
889
+ }
731
890
  if (retryResponse.status === 402) {
732
891
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
733
892
  }
@@ -760,6 +919,26 @@ var LLMClient = class {
760
919
  method: "GET",
761
920
  headers: { "User-Agent": USER_AGENT }
762
921
  });
922
+ if (response.status === 502 || response.status === 503) {
923
+ await new Promise((r) => setTimeout(r, 1e3));
924
+ const retryResp = await this.fetchWithTimeout(url, {
925
+ method: "GET",
926
+ headers: { "User-Agent": USER_AGENT }
927
+ });
928
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
929
+ if (retryResp.status === 402) return this.handleGetPaymentAndRetryRaw(url, endpoint, params, retryResp);
930
+ if (!retryResp.ok) {
931
+ let errorBody;
932
+ try {
933
+ errorBody = await retryResp.json();
934
+ } catch {
935
+ errorBody = { error: "Request failed" };
936
+ }
937
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
938
+ }
939
+ return retryResp.json();
940
+ }
941
+ }
763
942
  if (response.status === 402) {
764
943
  return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
765
944
  }
@@ -825,6 +1004,32 @@ var LLMClient = class {
825
1004
  "PAYMENT-SIGNATURE": paymentPayload
826
1005
  }
827
1006
  });
1007
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
1008
+ await new Promise((r) => setTimeout(r, 1e3));
1009
+ const retryResp2 = await this.fetchWithTimeout(retryUrl, {
1010
+ method: "GET",
1011
+ headers: {
1012
+ "User-Agent": USER_AGENT,
1013
+ "PAYMENT-SIGNATURE": paymentPayload
1014
+ }
1015
+ });
1016
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
1017
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
1018
+ if (!retryResp2.ok) {
1019
+ let errorBody;
1020
+ try {
1021
+ errorBody = await retryResp2.json();
1022
+ } catch {
1023
+ errorBody = { error: "Request failed" };
1024
+ }
1025
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
1026
+ }
1027
+ const costUsd2 = parseFloat(details.amount) / 1e6;
1028
+ this.sessionCalls += 1;
1029
+ this.sessionTotalUsd += costUsd2;
1030
+ return retryResp2.json();
1031
+ }
1032
+ }
828
1033
  if (retryResponse.status === 402) {
829
1034
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
830
1035
  }
@@ -2293,6 +2498,8 @@ var path3 = __toESM(require("path"), 1);
2293
2498
  var os3 = __toESM(require("os"), 1);
2294
2499
  var crypto2 = __toESM(require("crypto"), 1);
2295
2500
  var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
2501
+ var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
2502
+ var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
2296
2503
  var DEFAULT_TTL = {
2297
2504
  "/v1/x/": 3600 * 1e3,
2298
2505
  "/v1/partner/": 3600 * 1e3,
@@ -2357,26 +2564,80 @@ function setCache(key, data, ttlMs) {
2357
2564
  } catch {
2358
2565
  }
2359
2566
  }
2360
- function saveToCache(endpoint, body, response, costUsd = 0) {
2361
- const ttl = getTtl(endpoint);
2362
- if (ttl <= 0) return;
2567
+ function readableFilename(endpoint, body) {
2568
+ const now = /* @__PURE__ */ new Date();
2569
+ const ts = now.toISOString().slice(0, 10) + "_" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
2570
+ let ep = endpoint.replace(/\/+$/, "").split("/").pop() || "";
2571
+ if (endpoint.includes("/v1/chat/")) {
2572
+ ep = "chat";
2573
+ } else if (endpoint.includes("/v1/x/")) {
2574
+ ep = "x_" + ep;
2575
+ } else if (endpoint.includes("/v1/search")) {
2576
+ ep = "search";
2577
+ } else if (endpoint.includes("/v1/image")) {
2578
+ ep = "image";
2579
+ }
2580
+ let label = body.query || body.username || body.handle || body.model || (typeof body.prompt === "string" ? body.prompt.slice(0, 40) : "") || "";
2581
+ label = String(label).replace(/[^a-zA-Z0-9_\-]/g, "_").slice(0, 40).replace(/^_+|_+$/g, "");
2582
+ return label ? `${ep}_${ts}_${label}.json` : `${ep}_${ts}.json`;
2583
+ }
2584
+ function saveReadable(endpoint, body, response, costUsd) {
2363
2585
  try {
2364
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
2586
+ fs3.mkdirSync(DATA_DIR, { recursive: true });
2365
2587
  } catch {
2366
2588
  }
2367
- const key = cacheKey(endpoint, body);
2589
+ const filename = readableFilename(endpoint, body);
2368
2590
  const entry = {
2369
- cachedAt: Date.now(),
2591
+ saved_at: (/* @__PURE__ */ new Date()).toISOString(),
2370
2592
  endpoint,
2371
- body,
2372
- response,
2373
- costUsd
2593
+ cost_usd: costUsd,
2594
+ request: body,
2595
+ response
2374
2596
  };
2375
2597
  try {
2376
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2598
+ fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
2599
+ } catch {
2600
+ }
2601
+ }
2602
+ function appendCostLog(endpoint, costUsd) {
2603
+ if (costUsd <= 0) return;
2604
+ try {
2605
+ fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
2606
+ } catch {
2607
+ }
2608
+ const entry = {
2609
+ ts: Date.now() / 1e3,
2610
+ endpoint,
2611
+ cost_usd: costUsd
2612
+ };
2613
+ try {
2614
+ fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2377
2615
  } catch {
2378
2616
  }
2379
2617
  }
2618
+ function saveToCache(endpoint, body, response, costUsd = 0) {
2619
+ const ttl = getTtl(endpoint);
2620
+ if (ttl > 0) {
2621
+ try {
2622
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2623
+ } catch {
2624
+ }
2625
+ const key = cacheKey(endpoint, body);
2626
+ const entry = {
2627
+ cachedAt: Date.now(),
2628
+ endpoint,
2629
+ body,
2630
+ response,
2631
+ costUsd
2632
+ };
2633
+ try {
2634
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2635
+ } catch {
2636
+ }
2637
+ }
2638
+ saveReadable(endpoint, body, response, costUsd);
2639
+ appendCostLog(endpoint, costUsd);
2640
+ }
2380
2641
  function clearCache() {
2381
2642
  if (!fs3.existsSync(CACHE_DIR)) return 0;
2382
2643
  let count = 0;
@@ -2395,6 +2656,32 @@ function clearCache() {
2395
2656
  }
2396
2657
  return count;
2397
2658
  }
2659
+ function getCostLogSummary() {
2660
+ if (!fs3.existsSync(COST_LOG_FILE)) {
2661
+ return { totalUsd: 0, calls: 0, byEndpoint: {} };
2662
+ }
2663
+ let totalUsd = 0;
2664
+ let calls = 0;
2665
+ const byEndpoint = {};
2666
+ try {
2667
+ const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
2668
+ if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
2669
+ for (const line of content.split("\n")) {
2670
+ if (!line) continue;
2671
+ try {
2672
+ const entry = JSON.parse(line);
2673
+ const cost = entry.cost_usd ?? 0;
2674
+ const ep = entry.endpoint ?? "unknown";
2675
+ totalUsd += cost;
2676
+ calls += 1;
2677
+ byEndpoint[ep] = (byEndpoint[ep] || 0) + cost;
2678
+ } catch {
2679
+ }
2680
+ }
2681
+ } catch {
2682
+ }
2683
+ return { totalUsd, calls, byEndpoint };
2684
+ }
2398
2685
 
2399
2686
  // src/setup.ts
2400
2687
  function setupAgentWallet(options) {
@@ -2436,27 +2723,27 @@ async function status() {
2436
2723
  var fs4 = __toESM(require("fs"), 1);
2437
2724
  var path4 = __toESM(require("path"), 1);
2438
2725
  var os4 = __toESM(require("os"), 1);
2439
- var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
2440
- var COST_LOG_FILE = path4.join(DATA_DIR, "costs.jsonl");
2726
+ var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
2727
+ var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
2441
2728
  function logCost(entry) {
2442
2729
  try {
2443
- fs4.mkdirSync(DATA_DIR, { recursive: true });
2730
+ fs4.mkdirSync(DATA_DIR2, { recursive: true });
2444
2731
  } catch {
2445
2732
  }
2446
2733
  try {
2447
- fs4.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2734
+ fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
2448
2735
  } catch {
2449
2736
  }
2450
2737
  }
2451
2738
  function getCostSummary() {
2452
- if (!fs4.existsSync(COST_LOG_FILE)) {
2739
+ if (!fs4.existsSync(COST_LOG_FILE2)) {
2453
2740
  return { totalUsd: 0, calls: 0, byModel: {} };
2454
2741
  }
2455
2742
  let totalUsd = 0;
2456
2743
  let calls = 0;
2457
2744
  const byModel = {};
2458
2745
  try {
2459
- const content = fs4.readFileSync(COST_LOG_FILE, "utf-8").trim();
2746
+ const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
2460
2747
  if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
2461
2748
  for (const line of content.split("\n")) {
2462
2749
  if (!line) continue;
@@ -2758,6 +3045,7 @@ var AnthropicClient = class {
2758
3045
  BASE_CHAIN_ID,
2759
3046
  BlockrunError,
2760
3047
  ImageClient,
3048
+ KNOWN_PROVIDERS,
2761
3049
  LLMClient,
2762
3050
  OpenAI,
2763
3051
  PaymentError,
@@ -2780,6 +3068,7 @@ var AnthropicClient = class {
2780
3068
  formatWalletCreatedMessage,
2781
3069
  getCached,
2782
3070
  getCachedByRequest,
3071
+ getCostLogSummary,
2783
3072
  getCostSummary,
2784
3073
  getEip681Uri,
2785
3074
  getOrCreateSolanaWallet,
@@ -2802,5 +3091,9 @@ var AnthropicClient = class {
2802
3091
  solanaKeyToBytes,
2803
3092
  solanaPublicKey,
2804
3093
  status,
2805
- testnetClient
3094
+ testnetClient,
3095
+ validateMaxTokens,
3096
+ validateModel,
3097
+ validateTemperature,
3098
+ validateTopP
2806
3099
  });
package/dist/index.d.cts CHANGED
@@ -140,10 +140,48 @@ interface SearchParameters {
140
140
  toDate?: string;
141
141
  maxSearchResults?: number;
142
142
  }
143
+ /** Usage info for Live Search sources */
144
+ interface SearchUsage {
145
+ /** Number of search sources used in the response */
146
+ numSourcesUsed?: number;
147
+ }
143
148
  interface Spending {
144
149
  totalUsd: number;
145
150
  calls: number;
146
151
  }
152
+ /** Pre-request cost estimate for a chat call */
153
+ interface CostEstimate {
154
+ /** Model ID used for the estimate */
155
+ model: string;
156
+ /** Estimated input token count */
157
+ estimatedInputTokens: number;
158
+ /** Estimated output token count */
159
+ estimatedOutputTokens: number;
160
+ /** Estimated cost in USD */
161
+ estimatedCostUsd: number;
162
+ }
163
+ /** Per-call spending report with running session totals */
164
+ interface SpendingReport {
165
+ /** Model ID used */
166
+ model: string;
167
+ /** Input tokens consumed */
168
+ inputTokens: number;
169
+ /** Output tokens consumed */
170
+ outputTokens: number;
171
+ /** Cost of this call in USD */
172
+ costUsd: number;
173
+ /** Cumulative session spend in USD */
174
+ sessionTotalUsd: number;
175
+ /** Total number of calls in this session */
176
+ sessionCalls: number;
177
+ }
178
+ /** Chat response bundled with its spending report */
179
+ interface ChatResponseWithCost {
180
+ /** The chat completion response */
181
+ response: ChatResponse;
182
+ /** Spending report for this call */
183
+ spendingReport: SpendingReport;
184
+ }
147
185
  interface ResourceInfo {
148
186
  url: string;
149
187
  description?: string;
@@ -184,9 +222,9 @@ interface ChatOptions {
184
222
  temperature?: number;
185
223
  /** Nucleus sampling parameter */
186
224
  topP?: number;
187
- /** Enable xAI Live Search (shortcut for searchParameters.mode = "on") */
225
+ /** Enable Live Search (shortcut for searchParameters.mode = "on") */
188
226
  search?: boolean;
189
- /** Full xAI Live Search configuration (for Grok models) */
227
+ /** Full Live Search configuration (for search-enabled models) */
190
228
  searchParameters?: SearchParameters;
191
229
  }
192
230
  interface ChatCompletionOptions {
@@ -196,9 +234,9 @@ interface ChatCompletionOptions {
196
234
  temperature?: number;
197
235
  /** Nucleus sampling parameter */
198
236
  topP?: number;
199
- /** Enable xAI Live Search (shortcut for searchParameters.mode = "on") */
237
+ /** Enable Live Search (shortcut for searchParameters.mode = "on") */
200
238
  search?: boolean;
201
- /** Full xAI Live Search configuration (for Grok models) */
239
+ /** Full Live Search configuration (for search-enabled models) */
202
240
  searchParameters?: SearchParameters;
203
241
  /** Tool definitions for function calling */
204
242
  tools?: Tool[];
@@ -406,7 +444,7 @@ declare class APIError extends BlockrunError {
406
444
  * // Option 2: Pass private key directly
407
445
  * const client = new LLMClient({ privateKey: '0x...' });
408
446
  *
409
- * const response = await client.chat('openai/gpt-4o', 'Hello!');
447
+ * const response = await client.chat('openai/gpt-5.2', 'Hello!');
410
448
  * console.log(response);
411
449
  */
412
450
 
@@ -454,13 +492,13 @@ declare class LLMClient {
454
492
  /**
455
493
  * Simple 1-line chat interface.
456
494
  *
457
- * @param model - Model ID (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4')
495
+ * @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
458
496
  * @param prompt - User message
459
497
  * @param options - Optional chat parameters
460
498
  * @returns Assistant's response text
461
499
  *
462
500
  * @example
463
- * const response = await client.chat('gpt-4o', 'What is the capital of France?');
501
+ * const response = await client.chat('gpt-5.2', 'What is the capital of France?');
464
502
  * console.log(response); // 'The capital of France is Paris.'
465
503
  */
466
504
  chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
@@ -1108,7 +1146,7 @@ declare const WALLET_DIR_PATH: string;
1108
1146
  * // Or pass key directly
1109
1147
  * const client = new SolanaLLMClient({ privateKey: 'your-bs58-key' });
1110
1148
  *
1111
- * const response = await client.chat('openai/gpt-4o', 'gm Solana');
1149
+ * const response = await client.chat('openai/gpt-5.2', 'gm Solana');
1112
1150
  */
1113
1151
 
1114
1152
  interface SolanaLLMClientOptions {
@@ -1228,6 +1266,11 @@ declare function getCachedByRequest(endpoint: string, body: Record<string, unkno
1228
1266
  declare function setCache(key: string, data: unknown, ttlMs: number): void;
1229
1267
  declare function saveToCache(endpoint: string, body: Record<string, unknown>, response: unknown, costUsd?: number): void;
1230
1268
  declare function clearCache(): number;
1269
+ declare function getCostLogSummary(): {
1270
+ totalUsd: number;
1271
+ calls: number;
1272
+ byEndpoint: Record<string, number>;
1273
+ };
1231
1274
 
1232
1275
  /**
1233
1276
  * Agent wallet setup utilities.
@@ -1277,7 +1320,7 @@ declare function getCostSummary(): {
1277
1320
  *
1278
1321
  * // Rest of your code stays exactly the same!
1279
1322
  * const response = await client.chat.completions.create({
1280
- * model: 'gpt-4o',
1323
+ * model: 'gpt-5.2',
1281
1324
  * messages: [{ role: 'user', content: 'Hello!' }]
1282
1325
  * });
1283
1326
  */
@@ -1384,7 +1427,7 @@ declare class Chat {
1384
1427
  * const client = new OpenAI({ walletKey: '0x...' });
1385
1428
  *
1386
1429
  * const response = await client.chat.completions.create({
1387
- * model: 'gpt-4o',
1430
+ * model: 'gpt-5.2',
1388
1431
  * messages: [{ role: 'user', content: 'Hello!' }]
1389
1432
  * });
1390
1433
  *
@@ -1419,4 +1462,59 @@ declare class AnthropicClient {
1419
1462
  getWalletAddress(): string;
1420
1463
  }
1421
1464
 
1422
- export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatUsage, type CostEntry, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, testnetClient };
1465
+ /**
1466
+ * Input validation and security utilities for BlockRun LLM SDK.
1467
+ *
1468
+ * This module provides validation functions to ensure:
1469
+ * - Private keys are properly formatted
1470
+ * - API URLs use HTTPS
1471
+ * - Server responses don't leak sensitive information
1472
+ * - Resource URLs match expected domains
1473
+ */
1474
+
1475
+ /**
1476
+ * Known LLM providers (for optional validation).
1477
+ */
1478
+ declare const KNOWN_PROVIDERS: Set<string>;
1479
+ /**
1480
+ * Validates that a model ID is a non-empty string.
1481
+ *
1482
+ * @param model - The model ID (e.g., "openai/gpt-5.2", "anthropic/claude-sonnet-4.5")
1483
+ * @throws {Error} If the model is invalid
1484
+ *
1485
+ * @example
1486
+ * validateModel("openai/gpt-5.2");
1487
+ */
1488
+ declare function validateModel(model: string): void;
1489
+ /**
1490
+ * Validates that max_tokens is an integer between 1 and 100,000.
1491
+ *
1492
+ * @param maxTokens - Maximum number of tokens to generate
1493
+ * @throws {Error} If maxTokens is invalid
1494
+ *
1495
+ * @example
1496
+ * validateMaxTokens(1000);
1497
+ */
1498
+ declare function validateMaxTokens(maxTokens?: number): void;
1499
+ /**
1500
+ * Validates that temperature is a number between 0 and 2.
1501
+ *
1502
+ * @param temperature - Sampling temperature (0-2)
1503
+ * @throws {Error} If temperature is invalid
1504
+ *
1505
+ * @example
1506
+ * validateTemperature(0.7);
1507
+ */
1508
+ declare function validateTemperature(temperature?: number): void;
1509
+ /**
1510
+ * Validates that top_p is a number between 0 and 1.
1511
+ *
1512
+ * @param topP - Top-p sampling parameter (0-1)
1513
+ * @throws {Error} If topP is invalid
1514
+ *
1515
+ * @example
1516
+ * validateTopP(0.9);
1517
+ */
1518
+ declare function validateTopP(topP?: number): void;
1519
+
1520
+ export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, 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, testnetClient, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.d.ts CHANGED
@@ -140,10 +140,48 @@ interface SearchParameters {
140
140
  toDate?: string;
141
141
  maxSearchResults?: number;
142
142
  }
143
+ /** Usage info for Live Search sources */
144
+ interface SearchUsage {
145
+ /** Number of search sources used in the response */
146
+ numSourcesUsed?: number;
147
+ }
143
148
  interface Spending {
144
149
  totalUsd: number;
145
150
  calls: number;
146
151
  }
152
+ /** Pre-request cost estimate for a chat call */
153
+ interface CostEstimate {
154
+ /** Model ID used for the estimate */
155
+ model: string;
156
+ /** Estimated input token count */
157
+ estimatedInputTokens: number;
158
+ /** Estimated output token count */
159
+ estimatedOutputTokens: number;
160
+ /** Estimated cost in USD */
161
+ estimatedCostUsd: number;
162
+ }
163
+ /** Per-call spending report with running session totals */
164
+ interface SpendingReport {
165
+ /** Model ID used */
166
+ model: string;
167
+ /** Input tokens consumed */
168
+ inputTokens: number;
169
+ /** Output tokens consumed */
170
+ outputTokens: number;
171
+ /** Cost of this call in USD */
172
+ costUsd: number;
173
+ /** Cumulative session spend in USD */
174
+ sessionTotalUsd: number;
175
+ /** Total number of calls in this session */
176
+ sessionCalls: number;
177
+ }
178
+ /** Chat response bundled with its spending report */
179
+ interface ChatResponseWithCost {
180
+ /** The chat completion response */
181
+ response: ChatResponse;
182
+ /** Spending report for this call */
183
+ spendingReport: SpendingReport;
184
+ }
147
185
  interface ResourceInfo {
148
186
  url: string;
149
187
  description?: string;
@@ -184,9 +222,9 @@ interface ChatOptions {
184
222
  temperature?: number;
185
223
  /** Nucleus sampling parameter */
186
224
  topP?: number;
187
- /** Enable xAI Live Search (shortcut for searchParameters.mode = "on") */
225
+ /** Enable Live Search (shortcut for searchParameters.mode = "on") */
188
226
  search?: boolean;
189
- /** Full xAI Live Search configuration (for Grok models) */
227
+ /** Full Live Search configuration (for search-enabled models) */
190
228
  searchParameters?: SearchParameters;
191
229
  }
192
230
  interface ChatCompletionOptions {
@@ -196,9 +234,9 @@ interface ChatCompletionOptions {
196
234
  temperature?: number;
197
235
  /** Nucleus sampling parameter */
198
236
  topP?: number;
199
- /** Enable xAI Live Search (shortcut for searchParameters.mode = "on") */
237
+ /** Enable Live Search (shortcut for searchParameters.mode = "on") */
200
238
  search?: boolean;
201
- /** Full xAI Live Search configuration (for Grok models) */
239
+ /** Full Live Search configuration (for search-enabled models) */
202
240
  searchParameters?: SearchParameters;
203
241
  /** Tool definitions for function calling */
204
242
  tools?: Tool[];
@@ -406,7 +444,7 @@ declare class APIError extends BlockrunError {
406
444
  * // Option 2: Pass private key directly
407
445
  * const client = new LLMClient({ privateKey: '0x...' });
408
446
  *
409
- * const response = await client.chat('openai/gpt-4o', 'Hello!');
447
+ * const response = await client.chat('openai/gpt-5.2', 'Hello!');
410
448
  * console.log(response);
411
449
  */
412
450
 
@@ -454,13 +492,13 @@ declare class LLMClient {
454
492
  /**
455
493
  * Simple 1-line chat interface.
456
494
  *
457
- * @param model - Model ID (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4')
495
+ * @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
458
496
  * @param prompt - User message
459
497
  * @param options - Optional chat parameters
460
498
  * @returns Assistant's response text
461
499
  *
462
500
  * @example
463
- * const response = await client.chat('gpt-4o', 'What is the capital of France?');
501
+ * const response = await client.chat('gpt-5.2', 'What is the capital of France?');
464
502
  * console.log(response); // 'The capital of France is Paris.'
465
503
  */
466
504
  chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
@@ -1108,7 +1146,7 @@ declare const WALLET_DIR_PATH: string;
1108
1146
  * // Or pass key directly
1109
1147
  * const client = new SolanaLLMClient({ privateKey: 'your-bs58-key' });
1110
1148
  *
1111
- * const response = await client.chat('openai/gpt-4o', 'gm Solana');
1149
+ * const response = await client.chat('openai/gpt-5.2', 'gm Solana');
1112
1150
  */
1113
1151
 
1114
1152
  interface SolanaLLMClientOptions {
@@ -1228,6 +1266,11 @@ declare function getCachedByRequest(endpoint: string, body: Record<string, unkno
1228
1266
  declare function setCache(key: string, data: unknown, ttlMs: number): void;
1229
1267
  declare function saveToCache(endpoint: string, body: Record<string, unknown>, response: unknown, costUsd?: number): void;
1230
1268
  declare function clearCache(): number;
1269
+ declare function getCostLogSummary(): {
1270
+ totalUsd: number;
1271
+ calls: number;
1272
+ byEndpoint: Record<string, number>;
1273
+ };
1231
1274
 
1232
1275
  /**
1233
1276
  * Agent wallet setup utilities.
@@ -1277,7 +1320,7 @@ declare function getCostSummary(): {
1277
1320
  *
1278
1321
  * // Rest of your code stays exactly the same!
1279
1322
  * const response = await client.chat.completions.create({
1280
- * model: 'gpt-4o',
1323
+ * model: 'gpt-5.2',
1281
1324
  * messages: [{ role: 'user', content: 'Hello!' }]
1282
1325
  * });
1283
1326
  */
@@ -1384,7 +1427,7 @@ declare class Chat {
1384
1427
  * const client = new OpenAI({ walletKey: '0x...' });
1385
1428
  *
1386
1429
  * const response = await client.chat.completions.create({
1387
- * model: 'gpt-4o',
1430
+ * model: 'gpt-5.2',
1388
1431
  * messages: [{ role: 'user', content: 'Hello!' }]
1389
1432
  * });
1390
1433
  *
@@ -1419,4 +1462,59 @@ declare class AnthropicClient {
1419
1462
  getWalletAddress(): string;
1420
1463
  }
1421
1464
 
1422
- export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatUsage, type CostEntry, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, testnetClient };
1465
+ /**
1466
+ * Input validation and security utilities for BlockRun LLM SDK.
1467
+ *
1468
+ * This module provides validation functions to ensure:
1469
+ * - Private keys are properly formatted
1470
+ * - API URLs use HTTPS
1471
+ * - Server responses don't leak sensitive information
1472
+ * - Resource URLs match expected domains
1473
+ */
1474
+
1475
+ /**
1476
+ * Known LLM providers (for optional validation).
1477
+ */
1478
+ declare const KNOWN_PROVIDERS: Set<string>;
1479
+ /**
1480
+ * Validates that a model ID is a non-empty string.
1481
+ *
1482
+ * @param model - The model ID (e.g., "openai/gpt-5.2", "anthropic/claude-sonnet-4.5")
1483
+ * @throws {Error} If the model is invalid
1484
+ *
1485
+ * @example
1486
+ * validateModel("openai/gpt-5.2");
1487
+ */
1488
+ declare function validateModel(model: string): void;
1489
+ /**
1490
+ * Validates that max_tokens is an integer between 1 and 100,000.
1491
+ *
1492
+ * @param maxTokens - Maximum number of tokens to generate
1493
+ * @throws {Error} If maxTokens is invalid
1494
+ *
1495
+ * @example
1496
+ * validateMaxTokens(1000);
1497
+ */
1498
+ declare function validateMaxTokens(maxTokens?: number): void;
1499
+ /**
1500
+ * Validates that temperature is a number between 0 and 2.
1501
+ *
1502
+ * @param temperature - Sampling temperature (0-2)
1503
+ * @throws {Error} If temperature is invalid
1504
+ *
1505
+ * @example
1506
+ * validateTemperature(0.7);
1507
+ */
1508
+ declare function validateTemperature(temperature?: number): void;
1509
+ /**
1510
+ * Validates that top_p is a number between 0 and 1.
1511
+ *
1512
+ * @param topP - Top-p sampling parameter (0-1)
1513
+ * @throws {Error} If topP is invalid
1514
+ *
1515
+ * @example
1516
+ * validateTopP(0.9);
1517
+ */
1518
+ declare function validateTopP(topP?: number): void;
1519
+
1520
+ export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, 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, testnetClient, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.js CHANGED
@@ -223,6 +223,61 @@ function extractPaymentDetails(paymentRequired, preferredNetwork) {
223
223
 
224
224
  // src/validation.ts
225
225
  var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
226
+ var KNOWN_PROVIDERS = /* @__PURE__ */ new Set([
227
+ "openai",
228
+ "anthropic",
229
+ "google",
230
+ "deepseek",
231
+ "mistralai",
232
+ "meta-llama",
233
+ "together",
234
+ "xai",
235
+ "moonshot",
236
+ "nvidia",
237
+ "minimax",
238
+ "zai"
239
+ ]);
240
+ function validateModel(model) {
241
+ if (!model || typeof model !== "string") {
242
+ throw new Error("Model must be a non-empty string");
243
+ }
244
+ }
245
+ function validateMaxTokens(maxTokens) {
246
+ if (maxTokens === void 0 || maxTokens === null) {
247
+ return;
248
+ }
249
+ if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens)) {
250
+ throw new Error("maxTokens must be an integer");
251
+ }
252
+ if (maxTokens < 1) {
253
+ throw new Error("maxTokens must be positive (minimum: 1)");
254
+ }
255
+ if (maxTokens > 1e5) {
256
+ throw new Error("maxTokens too large (maximum: 100000)");
257
+ }
258
+ }
259
+ function validateTemperature(temperature) {
260
+ if (temperature === void 0 || temperature === null) {
261
+ return;
262
+ }
263
+ if (typeof temperature !== "number") {
264
+ throw new Error("temperature must be a number");
265
+ }
266
+ if (temperature < 0 || temperature > 2) {
267
+ throw new Error("temperature must be between 0 and 2");
268
+ }
269
+ }
270
+ function validateTopP(topP) {
271
+ if (topP === void 0 || topP === null) {
272
+ return;
273
+ }
274
+ if (typeof topP !== "number") {
275
+ throw new Error("topP must be a number");
276
+ }
277
+ if (topP < 0 || topP > 1) {
278
+ throw new Error("topP must be between 0 and 1");
279
+ }
280
+ }
226
281
  function validatePrivateKey(key) {
227
282
  if (typeof key !== "string") {
228
283
  throw new Error("Private key must be a string");
@@ -298,7 +353,7 @@ var DEFAULT_API_URL = "https://blockrun.ai/api";
298
353
  var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
299
354
  var DEFAULT_MAX_TOKENS = 1024;
300
355
  var DEFAULT_TIMEOUT = 6e4;
301
- var SDK_VERSION = "0.3.0";
356
+ var SDK_VERSION = "1.5.0";
302
357
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
303
358
  var LLMClient = class {
304
359
  static DEFAULT_API_URL = DEFAULT_API_URL;
@@ -335,13 +390,13 @@ var LLMClient = class {
335
390
  /**
336
391
  * Simple 1-line chat interface.
337
392
  *
338
- * @param model - Model ID (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4')
393
+ * @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
339
394
  * @param prompt - User message
340
395
  * @param options - Optional chat parameters
341
396
  * @returns Assistant's response text
342
397
  *
343
398
  * @example
344
- * const response = await client.chat('gpt-4o', 'What is the capital of France?');
399
+ * const response = await client.chat('gpt-5.2', 'What is the capital of France?');
345
400
  * console.log(response); // 'The capital of France is Paris.'
346
401
  */
347
402
  async chat(model, prompt, options) {
@@ -487,6 +542,27 @@ var LLMClient = class {
487
542
  headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
488
543
  body: JSON.stringify(body)
489
544
  });
545
+ if (response.status === 502 || response.status === 503) {
546
+ await new Promise((r) => setTimeout(r, 1e3));
547
+ const retryResp = await this.fetchWithTimeout(url, {
548
+ method: "POST",
549
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
550
+ body: JSON.stringify(body)
551
+ });
552
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
553
+ if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
554
+ if (!retryResp.ok) {
555
+ let errorBody;
556
+ try {
557
+ errorBody = await retryResp.json();
558
+ } catch {
559
+ errorBody = { error: "Request failed" };
560
+ }
561
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
562
+ }
563
+ return retryResp.json();
564
+ }
565
+ }
490
566
  if (response.status === 402) {
491
567
  return this.handlePaymentAndRetry(url, body, response);
492
568
  }
@@ -552,6 +628,34 @@ var LLMClient = class {
552
628
  },
553
629
  body: JSON.stringify(body)
554
630
  });
631
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
632
+ await new Promise((r) => setTimeout(r, 1e3));
633
+ const retryResp2 = await this.fetchWithTimeout(url, {
634
+ method: "POST",
635
+ headers: {
636
+ "Content-Type": "application/json",
637
+ "User-Agent": USER_AGENT,
638
+ "PAYMENT-SIGNATURE": paymentPayload
639
+ },
640
+ body: JSON.stringify(body)
641
+ });
642
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
643
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
644
+ if (!retryResp2.ok) {
645
+ let errorBody;
646
+ try {
647
+ errorBody = await retryResp2.json();
648
+ } catch {
649
+ errorBody = { error: "Request failed" };
650
+ }
651
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
652
+ }
653
+ const costUsd2 = parseFloat(details.amount) / 1e6;
654
+ this.sessionCalls += 1;
655
+ this.sessionTotalUsd += costUsd2;
656
+ return retryResp2.json();
657
+ }
658
+ }
555
659
  if (retryResponse.status === 402) {
556
660
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
557
661
  }
@@ -584,6 +688,27 @@ var LLMClient = class {
584
688
  headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
585
689
  body: JSON.stringify(body)
586
690
  });
691
+ if (response.status === 502 || response.status === 503) {
692
+ await new Promise((r) => setTimeout(r, 1e3));
693
+ const retryResp = await this.fetchWithTimeout(url, {
694
+ method: "POST",
695
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
696
+ body: JSON.stringify(body)
697
+ });
698
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
699
+ if (retryResp.status === 402) return this.handlePaymentAndRetryRaw(url, body, retryResp);
700
+ if (!retryResp.ok) {
701
+ let errorBody;
702
+ try {
703
+ errorBody = await retryResp.json();
704
+ } catch {
705
+ errorBody = { error: "Request failed" };
706
+ }
707
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
708
+ }
709
+ return retryResp.json();
710
+ }
711
+ }
587
712
  if (response.status === 402) {
588
713
  return this.handlePaymentAndRetryRaw(url, body, response);
589
714
  }
@@ -649,6 +774,34 @@ var LLMClient = class {
649
774
  },
650
775
  body: JSON.stringify(body)
651
776
  });
777
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
778
+ await new Promise((r) => setTimeout(r, 1e3));
779
+ const retryResp2 = await this.fetchWithTimeout(url, {
780
+ method: "POST",
781
+ headers: {
782
+ "Content-Type": "application/json",
783
+ "User-Agent": USER_AGENT,
784
+ "PAYMENT-SIGNATURE": paymentPayload
785
+ },
786
+ body: JSON.stringify(body)
787
+ });
788
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
789
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
790
+ if (!retryResp2.ok) {
791
+ let errorBody;
792
+ try {
793
+ errorBody = await retryResp2.json();
794
+ } catch {
795
+ errorBody = { error: "Request failed" };
796
+ }
797
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
798
+ }
799
+ const costUsd2 = parseFloat(details.amount) / 1e6;
800
+ this.sessionCalls += 1;
801
+ this.sessionTotalUsd += costUsd2;
802
+ return retryResp2.json();
803
+ }
804
+ }
652
805
  if (retryResponse.status === 402) {
653
806
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
654
807
  }
@@ -681,6 +834,26 @@ var LLMClient = class {
681
834
  method: "GET",
682
835
  headers: { "User-Agent": USER_AGENT }
683
836
  });
837
+ if (response.status === 502 || response.status === 503) {
838
+ await new Promise((r) => setTimeout(r, 1e3));
839
+ const retryResp = await this.fetchWithTimeout(url, {
840
+ method: "GET",
841
+ headers: { "User-Agent": USER_AGENT }
842
+ });
843
+ if (retryResp.status !== 502 && retryResp.status !== 503) {
844
+ if (retryResp.status === 402) return this.handleGetPaymentAndRetryRaw(url, endpoint, params, retryResp);
845
+ if (!retryResp.ok) {
846
+ let errorBody;
847
+ try {
848
+ errorBody = await retryResp.json();
849
+ } catch {
850
+ errorBody = { error: "Request failed" };
851
+ }
852
+ throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
853
+ }
854
+ return retryResp.json();
855
+ }
856
+ }
684
857
  if (response.status === 402) {
685
858
  return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
686
859
  }
@@ -746,6 +919,32 @@ var LLMClient = class {
746
919
  "PAYMENT-SIGNATURE": paymentPayload
747
920
  }
748
921
  });
922
+ if (retryResponse.status === 502 || retryResponse.status === 503) {
923
+ await new Promise((r) => setTimeout(r, 1e3));
924
+ const retryResp2 = await this.fetchWithTimeout(retryUrl, {
925
+ method: "GET",
926
+ headers: {
927
+ "User-Agent": USER_AGENT,
928
+ "PAYMENT-SIGNATURE": paymentPayload
929
+ }
930
+ });
931
+ if (retryResp2.status !== 502 && retryResp2.status !== 503) {
932
+ if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
933
+ if (!retryResp2.ok) {
934
+ let errorBody;
935
+ try {
936
+ errorBody = await retryResp2.json();
937
+ } catch {
938
+ errorBody = { error: "Request failed" };
939
+ }
940
+ throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
941
+ }
942
+ const costUsd2 = parseFloat(details.amount) / 1e6;
943
+ this.sessionCalls += 1;
944
+ this.sessionTotalUsd += costUsd2;
945
+ return retryResp2.json();
946
+ }
947
+ }
749
948
  if (retryResponse.status === 402) {
750
949
  throw new PaymentError("Payment was rejected. Check your wallet balance.");
751
950
  }
@@ -2214,6 +2413,8 @@ import * as path3 from "path";
2214
2413
  import * as os3 from "os";
2215
2414
  import * as crypto2 from "crypto";
2216
2415
  var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
2416
+ var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
2417
+ var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
2217
2418
  var DEFAULT_TTL = {
2218
2419
  "/v1/x/": 3600 * 1e3,
2219
2420
  "/v1/partner/": 3600 * 1e3,
@@ -2278,26 +2479,80 @@ function setCache(key, data, ttlMs) {
2278
2479
  } catch {
2279
2480
  }
2280
2481
  }
2281
- function saveToCache(endpoint, body, response, costUsd = 0) {
2282
- const ttl = getTtl(endpoint);
2283
- if (ttl <= 0) return;
2482
+ function readableFilename(endpoint, body) {
2483
+ const now = /* @__PURE__ */ new Date();
2484
+ const ts = now.toISOString().slice(0, 10) + "_" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
2485
+ let ep = endpoint.replace(/\/+$/, "").split("/").pop() || "";
2486
+ if (endpoint.includes("/v1/chat/")) {
2487
+ ep = "chat";
2488
+ } else if (endpoint.includes("/v1/x/")) {
2489
+ ep = "x_" + ep;
2490
+ } else if (endpoint.includes("/v1/search")) {
2491
+ ep = "search";
2492
+ } else if (endpoint.includes("/v1/image")) {
2493
+ ep = "image";
2494
+ }
2495
+ let label = body.query || body.username || body.handle || body.model || (typeof body.prompt === "string" ? body.prompt.slice(0, 40) : "") || "";
2496
+ label = String(label).replace(/[^a-zA-Z0-9_\-]/g, "_").slice(0, 40).replace(/^_+|_+$/g, "");
2497
+ return label ? `${ep}_${ts}_${label}.json` : `${ep}_${ts}.json`;
2498
+ }
2499
+ function saveReadable(endpoint, body, response, costUsd) {
2284
2500
  try {
2285
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
2501
+ fs3.mkdirSync(DATA_DIR, { recursive: true });
2286
2502
  } catch {
2287
2503
  }
2288
- const key = cacheKey(endpoint, body);
2504
+ const filename = readableFilename(endpoint, body);
2289
2505
  const entry = {
2290
- cachedAt: Date.now(),
2506
+ saved_at: (/* @__PURE__ */ new Date()).toISOString(),
2291
2507
  endpoint,
2292
- body,
2293
- response,
2294
- costUsd
2508
+ cost_usd: costUsd,
2509
+ request: body,
2510
+ response
2295
2511
  };
2296
2512
  try {
2297
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2513
+ fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
2514
+ } catch {
2515
+ }
2516
+ }
2517
+ function appendCostLog(endpoint, costUsd) {
2518
+ if (costUsd <= 0) return;
2519
+ try {
2520
+ fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
2521
+ } catch {
2522
+ }
2523
+ const entry = {
2524
+ ts: Date.now() / 1e3,
2525
+ endpoint,
2526
+ cost_usd: costUsd
2527
+ };
2528
+ try {
2529
+ fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2298
2530
  } catch {
2299
2531
  }
2300
2532
  }
2533
+ function saveToCache(endpoint, body, response, costUsd = 0) {
2534
+ const ttl = getTtl(endpoint);
2535
+ if (ttl > 0) {
2536
+ try {
2537
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2538
+ } catch {
2539
+ }
2540
+ const key = cacheKey(endpoint, body);
2541
+ const entry = {
2542
+ cachedAt: Date.now(),
2543
+ endpoint,
2544
+ body,
2545
+ response,
2546
+ costUsd
2547
+ };
2548
+ try {
2549
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2550
+ } catch {
2551
+ }
2552
+ }
2553
+ saveReadable(endpoint, body, response, costUsd);
2554
+ appendCostLog(endpoint, costUsd);
2555
+ }
2301
2556
  function clearCache() {
2302
2557
  if (!fs3.existsSync(CACHE_DIR)) return 0;
2303
2558
  let count = 0;
@@ -2316,6 +2571,32 @@ function clearCache() {
2316
2571
  }
2317
2572
  return count;
2318
2573
  }
2574
+ function getCostLogSummary() {
2575
+ if (!fs3.existsSync(COST_LOG_FILE)) {
2576
+ return { totalUsd: 0, calls: 0, byEndpoint: {} };
2577
+ }
2578
+ let totalUsd = 0;
2579
+ let calls = 0;
2580
+ const byEndpoint = {};
2581
+ try {
2582
+ const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
2583
+ if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
2584
+ for (const line of content.split("\n")) {
2585
+ if (!line) continue;
2586
+ try {
2587
+ const entry = JSON.parse(line);
2588
+ const cost = entry.cost_usd ?? 0;
2589
+ const ep = entry.endpoint ?? "unknown";
2590
+ totalUsd += cost;
2591
+ calls += 1;
2592
+ byEndpoint[ep] = (byEndpoint[ep] || 0) + cost;
2593
+ } catch {
2594
+ }
2595
+ }
2596
+ } catch {
2597
+ }
2598
+ return { totalUsd, calls, byEndpoint };
2599
+ }
2319
2600
 
2320
2601
  // src/setup.ts
2321
2602
  function setupAgentWallet(options) {
@@ -2357,27 +2638,27 @@ async function status() {
2357
2638
  import * as fs4 from "fs";
2358
2639
  import * as path4 from "path";
2359
2640
  import * as os4 from "os";
2360
- var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
2361
- var COST_LOG_FILE = path4.join(DATA_DIR, "costs.jsonl");
2641
+ var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
2642
+ var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
2362
2643
  function logCost(entry) {
2363
2644
  try {
2364
- fs4.mkdirSync(DATA_DIR, { recursive: true });
2645
+ fs4.mkdirSync(DATA_DIR2, { recursive: true });
2365
2646
  } catch {
2366
2647
  }
2367
2648
  try {
2368
- fs4.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2649
+ fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
2369
2650
  } catch {
2370
2651
  }
2371
2652
  }
2372
2653
  function getCostSummary() {
2373
- if (!fs4.existsSync(COST_LOG_FILE)) {
2654
+ if (!fs4.existsSync(COST_LOG_FILE2)) {
2374
2655
  return { totalUsd: 0, calls: 0, byModel: {} };
2375
2656
  }
2376
2657
  let totalUsd = 0;
2377
2658
  let calls = 0;
2378
2659
  const byModel = {};
2379
2660
  try {
2380
- const content = fs4.readFileSync(COST_LOG_FILE, "utf-8").trim();
2661
+ const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
2381
2662
  if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
2382
2663
  for (const line of content.split("\n")) {
2383
2664
  if (!line) continue;
@@ -2678,6 +2959,7 @@ export {
2678
2959
  BASE_CHAIN_ID,
2679
2960
  BlockrunError,
2680
2961
  ImageClient,
2962
+ KNOWN_PROVIDERS,
2681
2963
  LLMClient,
2682
2964
  OpenAI,
2683
2965
  PaymentError,
@@ -2701,6 +2983,7 @@ export {
2701
2983
  formatWalletCreatedMessage,
2702
2984
  getCached,
2703
2985
  getCachedByRequest,
2986
+ getCostLogSummary,
2704
2987
  getCostSummary,
2705
2988
  getEip681Uri,
2706
2989
  getOrCreateSolanaWallet,
@@ -2723,5 +3006,9 @@ export {
2723
3006
  solanaKeyToBytes,
2724
3007
  solanaPublicKey,
2725
3008
  status,
2726
- testnetClient
3009
+ testnetClient,
3010
+ validateMaxTokens,
3011
+ validateModel,
3012
+ validateTemperature,
3013
+ validateTopP
2727
3014
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "1.4.3",
3
+ "version": "1.5.0",
4
4
  "type": "module",
5
5
  "description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base and Solana",
6
6
  "main": "dist/index.cjs",
@@ -48,7 +48,7 @@
48
48
  "url": "https://github.com/BlockRunAI/blockrun-llm-ts/issues"
49
49
  },
50
50
  "dependencies": {
51
- "@blockrun/clawrouter": "^0.10.18",
51
+ "@blockrun/clawrouter": "^0.12.71",
52
52
  "bs58": "^6.0.0",
53
53
  "viem": "^2.21.0"
54
54
  },