@blockrun/llm 1.13.0 → 2.0.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
@@ -86,7 +86,6 @@ __export(index_exports, {
86
86
  solanaKeyToBytes: () => solanaKeyToBytes,
87
87
  solanaPublicKey: () => solanaPublicKey,
88
88
  status: () => status,
89
- testnetClient: () => testnetClient,
90
89
  validateMaxTokens: () => validateMaxTokens,
91
90
  validateModel: () => validateModel,
92
91
  validateTemperature: () => validateTemperature,
@@ -437,16 +436,107 @@ function validateResourceUrl(url, baseUrl) {
437
436
  }
438
437
  }
439
438
 
439
+ // src/cost-log.ts
440
+ var fs = __toESM(require("fs"), 1);
441
+ var path = __toESM(require("path"), 1);
442
+ var os = __toESM(require("os"), 1);
443
+ var BLOCKRUN_DIR = path.join(os.homedir(), ".blockrun");
444
+ var COST_LOG_FILE = path.join(BLOCKRUN_DIR, "cost_log.jsonl");
445
+ function logCost(entry) {
446
+ try {
447
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
448
+ } catch {
449
+ }
450
+ try {
451
+ fs.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
452
+ } catch {
453
+ }
454
+ }
455
+ function getCostSummary() {
456
+ if (!fs.existsSync(COST_LOG_FILE)) {
457
+ return { totalUsd: 0, calls: 0, byModel: {}, byEndpoint: {} };
458
+ }
459
+ let totalUsd = 0;
460
+ let calls = 0;
461
+ const byModel = {};
462
+ const byEndpoint = {};
463
+ try {
464
+ const content = fs.readFileSync(COST_LOG_FILE, "utf-8").trim();
465
+ if (!content) return { totalUsd: 0, calls: 0, byModel: {}, byEndpoint: {} };
466
+ for (const line of content.split("\n")) {
467
+ if (!line) continue;
468
+ try {
469
+ const raw = JSON.parse(line);
470
+ const cost = typeof raw.cost_usd === "number" ? raw.cost_usd : raw.costUsd ?? 0;
471
+ if (!cost) continue;
472
+ totalUsd += cost;
473
+ calls += 1;
474
+ if (raw.model) byModel[raw.model] = (byModel[raw.model] || 0) + cost;
475
+ if (raw.endpoint) byEndpoint[raw.endpoint] = (byEndpoint[raw.endpoint] || 0) + cost;
476
+ } catch {
477
+ }
478
+ }
479
+ } catch {
480
+ }
481
+ return { totalUsd, calls, byModel, byEndpoint };
482
+ }
483
+
440
484
  // src/client.ts
485
+ function isTransientError(err) {
486
+ if (err instanceof PaymentError) return false;
487
+ if (err instanceof APIError) {
488
+ return [502, 503, 504, 522, 524].includes(err.statusCode);
489
+ }
490
+ if (err instanceof Error) {
491
+ if (err.name === "AbortError") return true;
492
+ if (err.name === "TypeError" && /fetch|network/i.test(err.message)) return true;
493
+ }
494
+ return false;
495
+ }
496
+ function errSummary(err) {
497
+ if (err instanceof APIError) return `APIError ${err.statusCode}`;
498
+ if (err instanceof Error) {
499
+ const msg = err.message.length > 80 ? err.message.slice(0, 80) : err.message;
500
+ return `${err.name}: ${msg}`;
501
+ }
502
+ return String(err).slice(0, 100);
503
+ }
504
+ function mapRawToModel(m) {
505
+ return {
506
+ id: m.id,
507
+ name: m.name || m.id,
508
+ provider: m.provider || m.owned_by || "",
509
+ description: m.description || "",
510
+ inputPrice: m.inputPrice ?? m.input_price ?? m.pricing?.input ?? 0,
511
+ outputPrice: m.outputPrice ?? m.output_price ?? m.pricing?.output ?? 0,
512
+ contextWindow: m.contextWindow ?? m.context_window ?? 0,
513
+ maxOutput: m.maxOutput ?? m.max_output ?? 0,
514
+ categories: m.categories || [],
515
+ available: true,
516
+ billingMode: m.billingMode ?? m.billing_mode,
517
+ flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
518
+ hidden: m.hidden
519
+ };
520
+ }
521
+ function mapRawToImageModel(m) {
522
+ return {
523
+ id: m.id,
524
+ name: m.name || m.id,
525
+ provider: m.provider || m.owned_by || "",
526
+ description: m.description || "",
527
+ pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
528
+ supportedSizes: m.supportedSizes ?? m.supported_sizes,
529
+ maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
530
+ available: true
531
+ };
532
+ }
441
533
  var DEFAULT_API_URL = "https://blockrun.ai/api";
442
- var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
443
534
  var DEFAULT_MAX_TOKENS = 1024;
444
535
  var DEFAULT_TIMEOUT = 6e4;
445
536
  var SDK_VERSION = "1.5.0";
446
537
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
447
538
  var LLMClient = class _LLMClient {
448
539
  static DEFAULT_API_URL = DEFAULT_API_URL;
449
- static TESTNET_API_URL = TESTNET_API_URL;
450
540
  account;
451
541
  privateKey;
452
542
  apiUrl;
@@ -504,7 +594,8 @@ var LLMClient = class _LLMClient {
504
594
  temperature: options?.temperature,
505
595
  topP: options?.topP,
506
596
  search: options?.search,
507
- searchParameters: options?.searchParameters
597
+ searchParameters: options?.searchParameters,
598
+ fallbackModels: options?.fallbackModels
508
599
  });
509
600
  return result.choices[0].message.content || "";
510
601
  }
@@ -546,18 +637,24 @@ var LLMClient = class _LLMClient {
546
637
  modelPricing,
547
638
  routingProfile: options?.routingProfile
548
639
  });
640
+ const tierConfigs = decision.tierConfigs ?? import_clawrouter.DEFAULT_ROUTING_CONFIG.tiers;
641
+ const fullChain = (0, import_clawrouter.getFallbackChain)(decision.tier, tierConfigs);
642
+ const fallbacks = fullChain.filter(
643
+ (id) => id !== decision.model && modelPricing.has(id)
644
+ );
549
645
  const response = await this.chat(decision.model, prompt, {
550
646
  system: options?.system,
551
647
  maxTokens: options?.maxTokens,
552
648
  temperature: options?.temperature,
553
649
  topP: options?.topP,
554
650
  search: options?.search,
555
- searchParameters: options?.searchParameters
651
+ searchParameters: options?.searchParameters,
652
+ fallbackModels: fallbacks
556
653
  });
557
654
  return {
558
655
  response,
559
656
  model: decision.model,
560
- routing: decision
657
+ routing: { ...decision, fallbacks }
561
658
  };
562
659
  }
563
660
  /**
@@ -581,50 +678,108 @@ var LLMClient = class _LLMClient {
581
678
  }
582
679
  /**
583
680
  * Fetch model pricing from API.
681
+ *
682
+ * For flat-billed models (e.g. ZAI GLM-5 family at $0.001/call) the
683
+ * router still expects per-token rates, so we synthesise an equivalent
684
+ * per-token price assuming ~1500 total tokens per call. Without this,
685
+ * flat models would resolve to inputPrice=outputPrice=0 and the router
686
+ * would treat them as free, biasing routing decisions and reporting
687
+ * inflated savings %.
584
688
  */
585
689
  async fetchModelPricing() {
586
690
  const models = await this.listModels();
587
691
  const pricing = /* @__PURE__ */ new Map();
588
692
  for (const model of models) {
589
- pricing.set(model.id, {
590
- inputPrice: model.inputPrice,
591
- outputPrice: model.outputPrice
592
- });
693
+ if (model.billingMode === "flat" && model.flatPrice && model.flatPrice > 0) {
694
+ const perDirection = model.flatPrice * 1e6 / 1500 / 2;
695
+ pricing.set(model.id, {
696
+ inputPrice: perDirection,
697
+ outputPrice: perDirection
698
+ });
699
+ } else {
700
+ pricing.set(model.id, {
701
+ inputPrice: model.inputPrice,
702
+ outputPrice: model.outputPrice
703
+ });
704
+ }
593
705
  }
594
706
  return pricing;
595
707
  }
596
708
  /**
597
709
  * Full chat completion interface (OpenAI-compatible).
598
710
  *
599
- * @param model - Model ID
711
+ * When `fallbackModels` is set, transient failures (timeouts, network
712
+ * errors, 5xx) on the primary model trigger a retry against the next
713
+ * model in the list before raising. 4xx errors and PaymentError
714
+ * propagate immediately — those aren't "swap upstream and retry"
715
+ * situations. Each fallback hop logs one stderr line.
716
+ *
717
+ * @param model - Primary model ID
600
718
  * @param messages - Array of messages with role and content
601
719
  * @param options - Optional completion parameters
602
720
  * @returns ChatResponse object with choices and usage
603
721
  */
604
722
  async chatCompletion(model, messages, options) {
605
- const body = {
606
- model,
607
- messages,
608
- max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS
723
+ const buildBody = (m) => {
724
+ const body = {
725
+ model: m,
726
+ messages,
727
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS
728
+ };
729
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
730
+ if (options?.topP !== void 0) body.top_p = options.topP;
731
+ if (options?.searchParameters !== void 0) {
732
+ body.search_parameters = options.searchParameters;
733
+ } else if (options?.search === true) {
734
+ body.search_parameters = { mode: "on" };
735
+ }
736
+ if (options?.tools !== void 0) body.tools = options.tools;
737
+ if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
738
+ return body;
609
739
  };
610
- if (options?.temperature !== void 0) {
611
- body.temperature = options.temperature;
612
- }
613
- if (options?.topP !== void 0) {
614
- body.top_p = options.topP;
615
- }
616
- if (options?.searchParameters !== void 0) {
617
- body.search_parameters = options.searchParameters;
618
- } else if (options?.search === true) {
619
- body.search_parameters = { mode: "on" };
620
- }
621
- if (options?.tools !== void 0) {
622
- body.tools = options.tools;
740
+ const chain = [model, ...options?.fallbackModels ?? []];
741
+ let lastErr;
742
+ for (let i = 0; i < chain.length; i++) {
743
+ const candidate = chain[i];
744
+ try {
745
+ return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate));
746
+ } catch (err) {
747
+ lastErr = err;
748
+ const next = chain[i + 1];
749
+ if (!next || !isTransientError(err)) throw err;
750
+ console.error(
751
+ `[@blockrun/llm] ${candidate} -> ${next} (${errSummary(err)})`
752
+ );
753
+ }
623
754
  }
624
- if (options?.toolChoice !== void 0) {
625
- body.tool_choice = options.toolChoice;
755
+ throw lastErr;
756
+ }
757
+ /**
758
+ * Write a canonical cost_log entry after a settled x402 payment.
759
+ * Best-effort: failures here must never break a successful API call.
760
+ * Mirrors what Franklin's AgentClient writes via src/agent/llm.ts so
761
+ * cost_log.jsonl is a single source of truth regardless of caller.
762
+ */
763
+ recordCost(url, costUsd, opts) {
764
+ try {
765
+ let endpoint = "";
766
+ try {
767
+ endpoint = new URL(url).pathname;
768
+ } catch {
769
+ endpoint = "";
770
+ }
771
+ const model = opts?.body && typeof opts.body.model === "string" ? opts.body.model : void 0;
772
+ logCost({
773
+ ts: Date.now() / 1e3,
774
+ endpoint,
775
+ cost_usd: costUsd,
776
+ model,
777
+ wallet: this.account.address,
778
+ network: opts?.network,
779
+ client_kind: "LLMClient"
780
+ });
781
+ } catch {
626
782
  }
627
- return this.requestWithPayment("/v1/chat/completions", body);
628
783
  }
629
784
  /**
630
785
  * Make a request with automatic x402 payment handling.
@@ -747,6 +902,7 @@ var LLMClient = class _LLMClient {
747
902
  const costUsd2 = parseFloat(details.amount) / 1e6;
748
903
  this.sessionCalls += 1;
749
904
  this.sessionTotalUsd += costUsd2;
905
+ this.recordCost(url, costUsd2, { body, network: details.network });
750
906
  return retryResp2.json();
751
907
  }
752
908
  }
@@ -769,6 +925,7 @@ var LLMClient = class _LLMClient {
769
925
  const costUsd = parseFloat(details.amount) / 1e6;
770
926
  this.sessionCalls += 1;
771
927
  this.sessionTotalUsd += costUsd;
928
+ this.recordCost(url, costUsd, { body, network: details.network });
772
929
  return retryResponse.json();
773
930
  }
774
931
  /**
@@ -1018,6 +1175,7 @@ var LLMClient = class _LLMClient {
1018
1175
  const costUsd2 = parseFloat(details.amount) / 1e6;
1019
1176
  this.sessionCalls += 1;
1020
1177
  this.sessionTotalUsd += costUsd2;
1178
+ this.recordCost(url, costUsd2, { body, network: details.network });
1021
1179
  return retryResp2.json();
1022
1180
  }
1023
1181
  }
@@ -1040,6 +1198,7 @@ var LLMClient = class _LLMClient {
1040
1198
  const costUsd = parseFloat(details.amount) / 1e6;
1041
1199
  this.sessionCalls += 1;
1042
1200
  this.sessionTotalUsd += costUsd;
1201
+ this.recordCost(url, costUsd, { body, network: details.network });
1043
1202
  return retryResponse.json();
1044
1203
  }
1045
1204
  /**
@@ -1161,6 +1320,7 @@ var LLMClient = class _LLMClient {
1161
1320
  const costUsd2 = parseFloat(details.amount) / 1e6;
1162
1321
  this.sessionCalls += 1;
1163
1322
  this.sessionTotalUsd += costUsd2;
1323
+ this.recordCost(url, costUsd2, { network: details.network });
1164
1324
  return retryResp2.json();
1165
1325
  }
1166
1326
  }
@@ -1183,6 +1343,7 @@ var LLMClient = class _LLMClient {
1183
1343
  const costUsd = parseFloat(details.amount) / 1e6;
1184
1344
  this.sessionCalls += 1;
1185
1345
  this.sessionTotalUsd += costUsd;
1346
+ this.recordCost(url, costUsd, { network: details.network });
1186
1347
  return retryResponse.json();
1187
1348
  }
1188
1349
  /**
@@ -1202,9 +1363,59 @@ var LLMClient = class _LLMClient {
1202
1363
  }
1203
1364
  }
1204
1365
  /**
1205
- * List available LLM models with pricing.
1366
+ * List available models with pricing.
1367
+ *
1368
+ * Returns the full `/v1/models` unified catalog (chat + image + music).
1369
+ * The shape preserves backwards compatibility — image/music rows have
1370
+ * `inputPrice = outputPrice = 0` since those fields don't apply, and
1371
+ * their per-call price surfaces via `flatPrice`.
1206
1372
  */
1207
1373
  async listModels() {
1374
+ const raw = await this.fetchRawModels();
1375
+ return raw.map((m) => mapRawToModel(m));
1376
+ }
1377
+ /**
1378
+ * List available image generation models with pricing.
1379
+ *
1380
+ * The dedicated `/v1/images/models` endpoint was deprecated server-side;
1381
+ * image models live in the unified `/v1/models` catalog under
1382
+ * `categories: ["image", ...]`. This method filters that catalog so
1383
+ * existing callers keep working.
1384
+ */
1385
+ async listImageModels() {
1386
+ const raw = await this.fetchRawModels();
1387
+ return raw.filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => mapRawToImageModel(m));
1388
+ }
1389
+ /**
1390
+ * List all available models (chat, image, music, etc.) with pricing.
1391
+ *
1392
+ * @returns Array of all models with `type` field set from category
1393
+ * (`llm` for chat, `image` / `music` for media). Backwards-compat:
1394
+ * chat models always report `type: "llm"`.
1395
+ */
1396
+ async listAllModels() {
1397
+ const raw = await this.fetchRawModels();
1398
+ const out = [];
1399
+ for (const m of raw) {
1400
+ const cats = Array.isArray(m.categories) ? m.categories : [];
1401
+ if (cats.includes("image")) {
1402
+ const model = mapRawToImageModel(m);
1403
+ model.type = "image";
1404
+ out.push(model);
1405
+ } else {
1406
+ const model = mapRawToModel(m);
1407
+ model.type = "llm";
1408
+ out.push(model);
1409
+ }
1410
+ }
1411
+ return out;
1412
+ }
1413
+ /**
1414
+ * Internal: fetch the raw `/v1/models` catalog without normalising shape.
1415
+ * Used by listImageModels / listAllModels so each can pick category-
1416
+ * specific fields.
1417
+ */
1418
+ async fetchRawModels() {
1208
1419
  const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, {
1209
1420
  method: "GET"
1210
1421
  });
@@ -1222,65 +1433,8 @@ var LLMClient = class _LLMClient {
1222
1433
  );
1223
1434
  }
1224
1435
  const data = await response.json();
1225
- return (data.data || []).map((m) => ({
1226
- id: m.id,
1227
- name: m.name || m.id,
1228
- provider: m.provider || m.owned_by || "",
1229
- description: m.description || "",
1230
- inputPrice: m.inputPrice ?? m.input_price ?? m.pricing?.input ?? 0,
1231
- outputPrice: m.outputPrice ?? m.output_price ?? m.pricing?.output ?? 0,
1232
- contextWindow: m.contextWindow ?? m.context_window ?? 0,
1233
- maxOutput: m.maxOutput ?? m.max_output ?? 0,
1234
- categories: m.categories || [],
1235
- available: true,
1236
- billingMode: m.billingMode ?? m.billing_mode,
1237
- flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
1238
- hidden: m.hidden
1239
- }));
1240
- }
1241
- /**
1242
- * List available image generation models with pricing.
1243
- */
1244
- async listImageModels() {
1245
- const response = await this.fetchWithTimeout(
1246
- `${this.apiUrl}/v1/images/models`,
1247
- { method: "GET" }
1248
- );
1249
- if (!response.ok) {
1250
- throw new APIError(
1251
- `Failed to list image models: ${response.status}`,
1252
- response.status
1253
- );
1254
- }
1255
- const data = await response.json();
1256
1436
  return data.data || [];
1257
1437
  }
1258
- /**
1259
- * List all available models (both LLM and image) with pricing.
1260
- *
1261
- * @returns Array of all models with 'type' field ('llm' or 'image')
1262
- *
1263
- * @example
1264
- * const models = await client.listAllModels();
1265
- * for (const model of models) {
1266
- * if (model.type === 'llm') {
1267
- * console.log(`LLM: ${model.id} - $${model.inputPrice}/M input`);
1268
- * } else {
1269
- * console.log(`Image: ${model.id} - $${model.pricePerImage}/image`);
1270
- * }
1271
- * }
1272
- */
1273
- async listAllModels() {
1274
- const llmModels = await this.listModels();
1275
- for (const model of llmModels) {
1276
- model.type = "llm";
1277
- }
1278
- const imageModels = await this.listImageModels();
1279
- for (const model of imageModels) {
1280
- model.type = "image";
1281
- }
1282
- return [...llmModels, ...imageModels];
1283
- }
1284
1438
  /**
1285
1439
  * Edit an image using img2img.
1286
1440
  *
@@ -1321,6 +1475,19 @@ var LLMClient = class _LLMClient {
1321
1475
  const data = await this.requestWithPaymentRaw("/v1/search", body);
1322
1476
  return data;
1323
1477
  }
1478
+ /**
1479
+ * Generic Exa endpoint proxy (POST). Useful when you need an Exa API
1480
+ * surface that the typed wrappers below don't expose.
1481
+ *
1482
+ * @param path - Exa endpoint segment: "search" | "find-similar" | "contents" | "answer"
1483
+ * @param body - Request body (see Exa API docs)
1484
+ *
1485
+ * @example
1486
+ * const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
1487
+ */
1488
+ async exa(path5, body) {
1489
+ return this.requestWithPaymentRaw(`/v1/exa/${path5}`, body);
1490
+ }
1324
1491
  /**
1325
1492
  * Neural web search via Exa. Returns semantically relevant URLs and metadata.
1326
1493
  * Understands meaning, not just keywords. $0.01/call.
@@ -1374,9 +1541,7 @@ var LLMClient = class _LLMClient {
1374
1541
  return data.data;
1375
1542
  }
1376
1543
  /**
1377
- * Get USDC balance on Base network.
1378
- *
1379
- * Automatically detects mainnet vs testnet based on API URL.
1544
+ * Get USDC balance on Base mainnet.
1380
1545
  *
1381
1546
  * @returns USDC balance as a float (6 decimal places normalized)
1382
1547
  *
@@ -1385,9 +1550,8 @@ var LLMClient = class _LLMClient {
1385
1550
  * console.log(`Balance: $${balance.toFixed(2)} USDC`);
1386
1551
  */
1387
1552
  async getBalance() {
1388
- const isTestnet = this.isTestnet();
1389
- const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1390
- const rpcs = isTestnet ? ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"] : ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
1553
+ const usdcContract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1554
+ const rpcs = ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
1391
1555
  const selector = "0x70a08231";
1392
1556
  const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
1393
1557
  const data = selector + paddedAddress;
@@ -1633,17 +1797,70 @@ var LLMClient = class _LLMClient {
1633
1797
  /**
1634
1798
  * Structured query for Predexon prediction market data (POST endpoints).
1635
1799
  *
1636
- * For complex queries that require a JSON body. $0.005 per request.
1800
+ * For endpoints that require a JSON body, e.g. bulk wallet identity lookup.
1801
+ * Tier 1 = $0.001/call, Tier 2 = $0.005/call.
1637
1802
  *
1638
- * @param path - Endpoint path, e.g. "polymarket/query", "kalshi/query"
1803
+ * @param path - Endpoint path, e.g. "polymarket/wallet/identities"
1639
1804
  * @param query - JSON body for the structured query
1640
1805
  *
1641
1806
  * @example
1642
- * const data = await client.pmQuery("polymarket/query", { filter: "active", limit: 10 });
1807
+ * const batch = await client.pmQuery("polymarket/wallet/identities", {
1808
+ * addresses: ["0xabc...", "0xdef..."],
1809
+ * });
1643
1810
  */
1644
1811
  async pmQuery(path5, query) {
1645
1812
  return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1646
1813
  }
1814
+ // ── PM convenience helpers (Predexon v2) ─────────────────────────────────
1815
+ // Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
1816
+ /** List canonical cross-venue markets (Predexon v2). Tier 1 ($0.001/call).
1817
+ * Filter with venue, status, category, league, event_id, pagination_key. */
1818
+ async pmMarkets(params) {
1819
+ return this.pm("markets", params);
1820
+ }
1821
+ /** List venue-native executable listings flattened across canonical markets
1822
+ * (Predexon v2). Tier 1 ($0.001/call). */
1823
+ async pmListings(params) {
1824
+ return this.pm("markets/listings", params);
1825
+ }
1826
+ /** Resolve a canonical Predexon outcome ID to its market context and venue
1827
+ * listings. Tier 1 ($0.001/call). */
1828
+ async pmOutcome(predexonId) {
1829
+ return this.pm(`outcomes/${predexonId}`);
1830
+ }
1831
+ /** Polymarket markets with cursor-based keyset pagination (use pagination_key).
1832
+ * Tier 1 ($0.001/call). */
1833
+ async pmPolymarketMarketsKeyset(params) {
1834
+ return this.pm("polymarket/markets/keyset", params);
1835
+ }
1836
+ /** Polymarket events with cursor-based keyset pagination (use pagination_key).
1837
+ * Tier 1 ($0.001/call). */
1838
+ async pmPolymarketEventsKeyset(params) {
1839
+ return this.pm("polymarket/events/keyset", params);
1840
+ }
1841
+ /** List available sports categories. Tier 1 ($0.001/call). */
1842
+ async pmSportsCategories() {
1843
+ return this.pm("sports/categories");
1844
+ }
1845
+ /** List sports markets grouped by game. Filter with league, sport_type,
1846
+ * status, venue. Tier 1 ($0.001/call). */
1847
+ async pmSportsMarkets(params) {
1848
+ return this.pm("sports/markets", params);
1849
+ }
1850
+ /** Fetch identity + profile metadata for one wallet (ENS, Twitter, portfolio,
1851
+ * etc.). Tier 2 ($0.005/call). */
1852
+ async pmWalletIdentity(wallet) {
1853
+ return this.pm(`polymarket/wallet/identity/${wallet}`);
1854
+ }
1855
+ /** Bulk identity lookup for up to 200 wallet addresses (POST). Tier 2 ($0.005/call). */
1856
+ async pmWalletIdentities(addresses) {
1857
+ return this.pmQuery("polymarket/wallet/identities", { addresses });
1858
+ }
1859
+ /** Discover wallets connected to a seed address via on-chain transfers and
1860
+ * identity proofs. Tier 2 ($0.005/call). */
1861
+ async pmWalletCluster(address) {
1862
+ return this.pm(`polymarket/wallet/${address}/cluster`);
1863
+ }
1647
1864
  /**
1648
1865
  * Get current session spending.
1649
1866
  *
@@ -1665,19 +1882,7 @@ var LLMClient = class _LLMClient {
1665
1882
  getWalletAddress() {
1666
1883
  return this.account.address;
1667
1884
  }
1668
- /**
1669
- * Check if client is configured for testnet.
1670
- */
1671
- isTestnet() {
1672
- return this.apiUrl.includes("testnet.blockrun.ai");
1673
- }
1674
1885
  };
1675
- function testnetClient(options = {}) {
1676
- return new LLMClient({
1677
- ...options,
1678
- apiUrl: TESTNET_API_URL
1679
- });
1680
- }
1681
1886
  var client_default = LLMClient;
1682
1887
 
1683
1888
  // src/image.ts
@@ -1766,10 +1971,15 @@ var ImageClient = class {
1766
1971
  }
1767
1972
  /**
1768
1973
  * List available image generation models with pricing.
1974
+ *
1975
+ * The dedicated `/v1/images/models` endpoint was deprecated server-side;
1976
+ * image models live in the unified `/v1/models` catalog under
1977
+ * `categories: ["image", ...]`. This method filters that catalog so
1978
+ * existing callers keep working.
1769
1979
  */
1770
1980
  async listImageModels() {
1771
1981
  const response = await this.fetchWithTimeout(
1772
- `${this.apiUrl}/v1/images/models`,
1982
+ `${this.apiUrl}/v1/models`,
1773
1983
  { method: "GET" }
1774
1984
  );
1775
1985
  if (!response.ok) {
@@ -1779,7 +1989,16 @@ var ImageClient = class {
1779
1989
  );
1780
1990
  }
1781
1991
  const data = await response.json();
1782
- return data.data || [];
1992
+ return (data.data || []).filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => ({
1993
+ id: m.id,
1994
+ name: m.name || m.id,
1995
+ provider: m.provider || m.owned_by || "",
1996
+ description: m.description || "",
1997
+ pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
1998
+ supportedSizes: m.supportedSizes ?? m.supported_sizes,
1999
+ maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
2000
+ available: true
2001
+ }));
1783
2002
  }
1784
2003
  /**
1785
2004
  * Make a request with automatic x402 payment handling.
@@ -2265,12 +2484,19 @@ var SearchClient = class {
2265
2484
  var import_accounts6 = require("viem/accounts");
2266
2485
  var DEFAULT_API_URL5 = "https://blockrun.ai/api";
2267
2486
  var DEFAULT_TIMEOUT5 = 6e4;
2268
- var XClient = class {
2487
+ var XClient = class _XClient {
2269
2488
  account;
2270
2489
  privateKey;
2271
2490
  apiUrl;
2272
2491
  timeout;
2492
+ static deprecationWarned = false;
2273
2493
  constructor(options = {}) {
2494
+ if (!_XClient.deprecationWarned) {
2495
+ console.warn(
2496
+ "[@blockrun/llm] XClient: BlockRun's /v1/x/* (AttentionVC) integration was removed 2026-04-30. All calls will return HTTP 404 until a replacement X data upstream is reintroduced."
2497
+ );
2498
+ _XClient.deprecationWarned = true;
2499
+ }
2274
2500
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2275
2501
  const privateKey = options.privateKey || envKey;
2276
2502
  if (!privateKey) {
@@ -2667,13 +2893,13 @@ function buildUrl(base, query) {
2667
2893
 
2668
2894
  // src/wallet.ts
2669
2895
  var import_accounts8 = require("viem/accounts");
2670
- var fs = __toESM(require("fs"), 1);
2671
- var path = __toESM(require("path"), 1);
2672
- var os = __toESM(require("os"), 1);
2896
+ var fs2 = __toESM(require("fs"), 1);
2897
+ var path2 = __toESM(require("path"), 1);
2898
+ var os2 = __toESM(require("os"), 1);
2673
2899
  var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
2674
2900
  var BASE_CHAIN_ID2 = "8453";
2675
- var WALLET_DIR = path.join(os.homedir(), ".blockrun");
2676
- var WALLET_FILE = path.join(WALLET_DIR, ".session");
2901
+ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
2902
+ var WALLET_FILE = path2.join(WALLET_DIR, ".session");
2677
2903
  function createWallet() {
2678
2904
  const privateKey = (0, import_accounts8.generatePrivateKey)();
2679
2905
  const account = (0, import_accounts8.privateKeyToAccount)(privateKey);
@@ -2683,27 +2909,27 @@ function createWallet() {
2683
2909
  };
2684
2910
  }
2685
2911
  function saveWallet(privateKey) {
2686
- if (!fs.existsSync(WALLET_DIR)) {
2687
- fs.mkdirSync(WALLET_DIR, { recursive: true });
2912
+ if (!fs2.existsSync(WALLET_DIR)) {
2913
+ fs2.mkdirSync(WALLET_DIR, { recursive: true });
2688
2914
  }
2689
- fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2915
+ fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2690
2916
  return WALLET_FILE;
2691
2917
  }
2692
2918
  function scanWallets() {
2693
- const home = os.homedir();
2919
+ const home = os2.homedir();
2694
2920
  const results = [];
2695
2921
  try {
2696
- const entries = fs.readdirSync(home, { withFileTypes: true });
2922
+ const entries = fs2.readdirSync(home, { withFileTypes: true });
2697
2923
  for (const entry of entries) {
2698
2924
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2699
- const walletFile = path.join(home, entry.name, "wallet.json");
2700
- if (!fs.existsSync(walletFile)) continue;
2925
+ const walletFile = path2.join(home, entry.name, "wallet.json");
2926
+ if (!fs2.existsSync(walletFile)) continue;
2701
2927
  try {
2702
- const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
2928
+ const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
2703
2929
  const pk = data.privateKey || "";
2704
2930
  const addr = data.address || "";
2705
2931
  if (pk && addr) {
2706
- const mtime = fs.statSync(walletFile).mtimeMs;
2932
+ const mtime = fs2.statSync(walletFile).mtimeMs;
2707
2933
  results.push({ mtime, privateKey: pk, address: addr });
2708
2934
  }
2709
2935
  } catch {
@@ -2718,13 +2944,13 @@ function scanWallets() {
2718
2944
  function loadWallet() {
2719
2945
  const wallets = scanWallets();
2720
2946
  if (wallets.length > 0) return wallets[0].privateKey;
2721
- if (fs.existsSync(WALLET_FILE)) {
2722
- const key = fs.readFileSync(WALLET_FILE, "utf-8").trim();
2947
+ if (fs2.existsSync(WALLET_FILE)) {
2948
+ const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
2723
2949
  if (key) return key;
2724
2950
  }
2725
- const legacyFile = path.join(WALLET_DIR, "wallet.key");
2726
- if (fs.existsSync(legacyFile)) {
2727
- const key = fs.readFileSync(legacyFile, "utf-8").trim();
2951
+ const legacyFile = path2.join(WALLET_DIR, "wallet.key");
2952
+ if (fs2.existsSync(legacyFile)) {
2953
+ const key = fs2.readFileSync(legacyFile, "utf-8").trim();
2728
2954
  if (key) return key;
2729
2955
  }
2730
2956
  return null;
@@ -2819,11 +3045,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
2819
3045
  var WALLET_DIR_PATH = WALLET_DIR;
2820
3046
 
2821
3047
  // src/solana-wallet.ts
2822
- var fs2 = __toESM(require("fs"), 1);
2823
- var path2 = __toESM(require("path"), 1);
2824
- var os2 = __toESM(require("os"), 1);
2825
- var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
2826
- var SOLANA_WALLET_FILE = path2.join(WALLET_DIR2, ".solana-session");
3048
+ var fs3 = __toESM(require("fs"), 1);
3049
+ var path3 = __toESM(require("path"), 1);
3050
+ var os3 = __toESM(require("os"), 1);
3051
+ var WALLET_DIR2 = path3.join(os3.homedir(), ".blockrun");
3052
+ var SOLANA_WALLET_FILE = path3.join(WALLET_DIR2, ".solana-session");
2827
3053
  async function createSolanaWallet() {
2828
3054
  const { Keypair } = await import("@solana/web3.js");
2829
3055
  const bs58 = await import("bs58");
@@ -2852,39 +3078,39 @@ async function solanaPublicKey(privateKey) {
2852
3078
  return Keypair.fromSecretKey(bytes).publicKey.toBase58();
2853
3079
  }
2854
3080
  function saveSolanaWallet(privateKey) {
2855
- if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
2856
- fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
3081
+ if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
3082
+ fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
2857
3083
  return SOLANA_WALLET_FILE;
2858
3084
  }
2859
3085
  function scanSolanaWallets() {
2860
- const home = os2.homedir();
3086
+ const home = os3.homedir();
2861
3087
  const results = [];
2862
3088
  try {
2863
- const entries = fs2.readdirSync(home, { withFileTypes: true });
3089
+ const entries = fs3.readdirSync(home, { withFileTypes: true });
2864
3090
  for (const entry of entries) {
2865
3091
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2866
- const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
2867
- if (fs2.existsSync(solanaWalletFile)) {
3092
+ const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
3093
+ if (fs3.existsSync(solanaWalletFile)) {
2868
3094
  try {
2869
- const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
3095
+ const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
2870
3096
  const pk = data.privateKey || "";
2871
3097
  const addr = data.address || "";
2872
3098
  if (pk && addr) {
2873
- const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
3099
+ const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
2874
3100
  results.push({ mtime, secretKey: pk, publicKey: addr });
2875
3101
  }
2876
3102
  } catch {
2877
3103
  }
2878
3104
  }
2879
3105
  if (entry.name === ".brcc") {
2880
- const brccWalletFile = path2.join(home, entry.name, "wallet.json");
2881
- if (fs2.existsSync(brccWalletFile)) {
3106
+ const brccWalletFile = path3.join(home, entry.name, "wallet.json");
3107
+ if (fs3.existsSync(brccWalletFile)) {
2882
3108
  try {
2883
- const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
3109
+ const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
2884
3110
  const pk = data.privateKey || "";
2885
3111
  const addr = data.address || "";
2886
3112
  if (pk && addr) {
2887
- const mtime = fs2.statSync(brccWalletFile).mtimeMs;
3113
+ const mtime = fs3.statSync(brccWalletFile).mtimeMs;
2888
3114
  results.push({ mtime, secretKey: pk, publicKey: addr });
2889
3115
  }
2890
3116
  } catch {
@@ -2900,8 +3126,8 @@ function scanSolanaWallets() {
2900
3126
  function loadSolanaWallet() {
2901
3127
  const wallets = scanSolanaWallets();
2902
3128
  if (wallets.length > 0) return wallets[0].secretKey;
2903
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2904
- const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
3129
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
3130
+ const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
2905
3131
  if (key) return key;
2906
3132
  }
2907
3133
  return null;
@@ -2916,8 +3142,8 @@ async function getOrCreateSolanaWallet() {
2916
3142
  if (wallets.length > 0) {
2917
3143
  return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
2918
3144
  }
2919
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2920
- const fileKey = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
3145
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
3146
+ const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
2921
3147
  if (fileKey) {
2922
3148
  const address2 = await solanaPublicKey(fileKey);
2923
3149
  return { privateKey: fileKey, address: address2, isNew: false };
@@ -3491,13 +3717,13 @@ function solanaClient(options = {}) {
3491
3717
  }
3492
3718
 
3493
3719
  // src/cache.ts
3494
- var fs3 = __toESM(require("fs"), 1);
3495
- var path3 = __toESM(require("path"), 1);
3496
- var os3 = __toESM(require("os"), 1);
3720
+ var fs4 = __toESM(require("fs"), 1);
3721
+ var path4 = __toESM(require("path"), 1);
3722
+ var os4 = __toESM(require("os"), 1);
3497
3723
  var crypto2 = __toESM(require("crypto"), 1);
3498
- var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
3499
- var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
3500
- var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
3724
+ var CACHE_DIR = path4.join(os4.homedir(), ".blockrun", "cache");
3725
+ var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
3726
+ var COST_LOG_FILE2 = path4.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
3501
3727
  var DEFAULT_TTL = {
3502
3728
  "/v1/x/": 3600 * 1e3,
3503
3729
  "/v1/partner/": 3600 * 1e3,
@@ -3518,19 +3744,19 @@ function cacheKey(endpoint, body) {
3518
3744
  return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
3519
3745
  }
3520
3746
  function cachePath(key) {
3521
- return path3.join(CACHE_DIR, `${key}.json`);
3747
+ return path4.join(CACHE_DIR, `${key}.json`);
3522
3748
  }
3523
3749
  function getCached(key) {
3524
3750
  const filePath = cachePath(key);
3525
- if (!fs3.existsSync(filePath)) return null;
3751
+ if (!fs4.existsSync(filePath)) return null;
3526
3752
  try {
3527
- const raw = fs3.readFileSync(filePath, "utf-8");
3753
+ const raw = fs4.readFileSync(filePath, "utf-8");
3528
3754
  const entry = JSON.parse(raw);
3529
3755
  const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
3530
3756
  if (ttl <= 0) return null;
3531
3757
  if (Date.now() - entry.cachedAt > ttl) {
3532
3758
  try {
3533
- fs3.unlinkSync(filePath);
3759
+ fs4.unlinkSync(filePath);
3534
3760
  } catch {
3535
3761
  }
3536
3762
  return null;
@@ -3549,7 +3775,7 @@ function getCachedByRequest(endpoint, body) {
3549
3775
  function setCache(key, data, ttlMs) {
3550
3776
  if (ttlMs <= 0) return;
3551
3777
  try {
3552
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3778
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3553
3779
  } catch {
3554
3780
  }
3555
3781
  const entry = {
@@ -3558,7 +3784,7 @@ function setCache(key, data, ttlMs) {
3558
3784
  ttlMs
3559
3785
  };
3560
3786
  try {
3561
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3787
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3562
3788
  } catch {
3563
3789
  }
3564
3790
  }
@@ -3581,7 +3807,7 @@ function readableFilename(endpoint, body) {
3581
3807
  }
3582
3808
  function saveReadable(endpoint, body, response, costUsd) {
3583
3809
  try {
3584
- fs3.mkdirSync(DATA_DIR, { recursive: true });
3810
+ fs4.mkdirSync(DATA_DIR, { recursive: true });
3585
3811
  } catch {
3586
3812
  }
3587
3813
  const filename = readableFilename(endpoint, body);
@@ -3593,14 +3819,14 @@ function saveReadable(endpoint, body, response, costUsd) {
3593
3819
  response
3594
3820
  };
3595
3821
  try {
3596
- fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
3822
+ fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
3597
3823
  } catch {
3598
3824
  }
3599
3825
  }
3600
3826
  function appendCostLog(endpoint, costUsd) {
3601
3827
  if (costUsd <= 0) return;
3602
3828
  try {
3603
- fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
3829
+ fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
3604
3830
  } catch {
3605
3831
  }
3606
3832
  const entry = {
@@ -3609,7 +3835,7 @@ function appendCostLog(endpoint, costUsd) {
3609
3835
  cost_usd: costUsd
3610
3836
  };
3611
3837
  try {
3612
- fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
3838
+ fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3613
3839
  } catch {
3614
3840
  }
3615
3841
  }
@@ -3617,7 +3843,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3617
3843
  const ttl = getTtl(endpoint);
3618
3844
  if (ttl > 0) {
3619
3845
  try {
3620
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3846
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3621
3847
  } catch {
3622
3848
  }
3623
3849
  const key = cacheKey(endpoint, body);
@@ -3629,7 +3855,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3629
3855
  costUsd
3630
3856
  };
3631
3857
  try {
3632
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3858
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3633
3859
  } catch {
3634
3860
  }
3635
3861
  }
@@ -3637,14 +3863,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3637
3863
  appendCostLog(endpoint, costUsd);
3638
3864
  }
3639
3865
  function clearCache() {
3640
- if (!fs3.existsSync(CACHE_DIR)) return 0;
3866
+ if (!fs4.existsSync(CACHE_DIR)) return 0;
3641
3867
  let count = 0;
3642
3868
  try {
3643
- const files = fs3.readdirSync(CACHE_DIR);
3869
+ const files = fs4.readdirSync(CACHE_DIR);
3644
3870
  for (const file of files) {
3645
3871
  if (file.endsWith(".json")) {
3646
3872
  try {
3647
- fs3.unlinkSync(path3.join(CACHE_DIR, file));
3873
+ fs4.unlinkSync(path4.join(CACHE_DIR, file));
3648
3874
  count++;
3649
3875
  } catch {
3650
3876
  }
@@ -3655,14 +3881,14 @@ function clearCache() {
3655
3881
  return count;
3656
3882
  }
3657
3883
  function getCostLogSummary() {
3658
- if (!fs3.existsSync(COST_LOG_FILE)) {
3884
+ if (!fs4.existsSync(COST_LOG_FILE2)) {
3659
3885
  return { totalUsd: 0, calls: 0, byEndpoint: {} };
3660
3886
  }
3661
3887
  let totalUsd = 0;
3662
3888
  let calls = 0;
3663
3889
  const byEndpoint = {};
3664
3890
  try {
3665
- const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
3891
+ const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3666
3892
  if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
3667
3893
  for (const line of content.split("\n")) {
3668
3894
  if (!line) continue;
@@ -3717,47 +3943,6 @@ async function status() {
3717
3943
  return { address, balance };
3718
3944
  }
3719
3945
 
3720
- // src/cost-log.ts
3721
- var fs4 = __toESM(require("fs"), 1);
3722
- var path4 = __toESM(require("path"), 1);
3723
- var os4 = __toESM(require("os"), 1);
3724
- var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
3725
- var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
3726
- function logCost(entry) {
3727
- try {
3728
- fs4.mkdirSync(DATA_DIR2, { recursive: true });
3729
- } catch {
3730
- }
3731
- try {
3732
- fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3733
- } catch {
3734
- }
3735
- }
3736
- function getCostSummary() {
3737
- if (!fs4.existsSync(COST_LOG_FILE2)) {
3738
- return { totalUsd: 0, calls: 0, byModel: {} };
3739
- }
3740
- let totalUsd = 0;
3741
- let calls = 0;
3742
- const byModel = {};
3743
- try {
3744
- const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3745
- if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
3746
- for (const line of content.split("\n")) {
3747
- if (!line) continue;
3748
- try {
3749
- const entry = JSON.parse(line);
3750
- totalUsd += entry.costUsd;
3751
- calls += 1;
3752
- byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
3753
- } catch {
3754
- }
3755
- }
3756
- } catch {
3757
- }
3758
- return { totalUsd, calls, byModel };
3759
- }
3760
-
3761
3946
  // src/openai-compat.ts
3762
3947
  var StreamingResponse = class {
3763
3948
  reader;
@@ -4065,7 +4250,6 @@ var AnthropicClient = class {
4065
4250
  solanaKeyToBytes,
4066
4251
  solanaPublicKey,
4067
4252
  status,
4068
- testnetClient,
4069
4253
  validateMaxTokens,
4070
4254
  validateModel,
4071
4255
  validateTemperature,