@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.js CHANGED
@@ -26,7 +26,7 @@ var APIError = class extends BlockrunError {
26
26
  };
27
27
 
28
28
  // src/client.ts
29
- import { route, DEFAULT_ROUTING_CONFIG } from "@blockrun/clawrouter";
29
+ import { route, DEFAULT_ROUTING_CONFIG, getFallbackChain } from "@blockrun/clawrouter";
30
30
 
31
31
  // src/x402.ts
32
32
  import { signTypedData } from "viem/accounts";
@@ -341,16 +341,107 @@ function validateResourceUrl(url, baseUrl) {
341
341
  }
342
342
  }
343
343
 
344
+ // src/cost-log.ts
345
+ import * as fs from "fs";
346
+ import * as path from "path";
347
+ import * as os from "os";
348
+ var BLOCKRUN_DIR = path.join(os.homedir(), ".blockrun");
349
+ var COST_LOG_FILE = path.join(BLOCKRUN_DIR, "cost_log.jsonl");
350
+ function logCost(entry) {
351
+ try {
352
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
353
+ } catch {
354
+ }
355
+ try {
356
+ fs.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
357
+ } catch {
358
+ }
359
+ }
360
+ function getCostSummary() {
361
+ if (!fs.existsSync(COST_LOG_FILE)) {
362
+ return { totalUsd: 0, calls: 0, byModel: {}, byEndpoint: {} };
363
+ }
364
+ let totalUsd = 0;
365
+ let calls = 0;
366
+ const byModel = {};
367
+ const byEndpoint = {};
368
+ try {
369
+ const content = fs.readFileSync(COST_LOG_FILE, "utf-8").trim();
370
+ if (!content) return { totalUsd: 0, calls: 0, byModel: {}, byEndpoint: {} };
371
+ for (const line of content.split("\n")) {
372
+ if (!line) continue;
373
+ try {
374
+ const raw = JSON.parse(line);
375
+ const cost = typeof raw.cost_usd === "number" ? raw.cost_usd : raw.costUsd ?? 0;
376
+ if (!cost) continue;
377
+ totalUsd += cost;
378
+ calls += 1;
379
+ if (raw.model) byModel[raw.model] = (byModel[raw.model] || 0) + cost;
380
+ if (raw.endpoint) byEndpoint[raw.endpoint] = (byEndpoint[raw.endpoint] || 0) + cost;
381
+ } catch {
382
+ }
383
+ }
384
+ } catch {
385
+ }
386
+ return { totalUsd, calls, byModel, byEndpoint };
387
+ }
388
+
344
389
  // src/client.ts
390
+ function isTransientError(err) {
391
+ if (err instanceof PaymentError) return false;
392
+ if (err instanceof APIError) {
393
+ return [502, 503, 504, 522, 524].includes(err.statusCode);
394
+ }
395
+ if (err instanceof Error) {
396
+ if (err.name === "AbortError") return true;
397
+ if (err.name === "TypeError" && /fetch|network/i.test(err.message)) return true;
398
+ }
399
+ return false;
400
+ }
401
+ function errSummary(err) {
402
+ if (err instanceof APIError) return `APIError ${err.statusCode}`;
403
+ if (err instanceof Error) {
404
+ const msg = err.message.length > 80 ? err.message.slice(0, 80) : err.message;
405
+ return `${err.name}: ${msg}`;
406
+ }
407
+ return String(err).slice(0, 100);
408
+ }
409
+ function mapRawToModel(m) {
410
+ return {
411
+ id: m.id,
412
+ name: m.name || m.id,
413
+ provider: m.provider || m.owned_by || "",
414
+ description: m.description || "",
415
+ inputPrice: m.inputPrice ?? m.input_price ?? m.pricing?.input ?? 0,
416
+ outputPrice: m.outputPrice ?? m.output_price ?? m.pricing?.output ?? 0,
417
+ contextWindow: m.contextWindow ?? m.context_window ?? 0,
418
+ maxOutput: m.maxOutput ?? m.max_output ?? 0,
419
+ categories: m.categories || [],
420
+ available: true,
421
+ billingMode: m.billingMode ?? m.billing_mode,
422
+ flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
423
+ hidden: m.hidden
424
+ };
425
+ }
426
+ function mapRawToImageModel(m) {
427
+ return {
428
+ id: m.id,
429
+ name: m.name || m.id,
430
+ provider: m.provider || m.owned_by || "",
431
+ description: m.description || "",
432
+ pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
433
+ supportedSizes: m.supportedSizes ?? m.supported_sizes,
434
+ maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
435
+ available: true
436
+ };
437
+ }
345
438
  var DEFAULT_API_URL = "https://blockrun.ai/api";
346
- var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
347
439
  var DEFAULT_MAX_TOKENS = 1024;
348
440
  var DEFAULT_TIMEOUT = 6e4;
349
441
  var SDK_VERSION = "1.5.0";
350
442
  var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
351
443
  var LLMClient = class _LLMClient {
352
444
  static DEFAULT_API_URL = DEFAULT_API_URL;
353
- static TESTNET_API_URL = TESTNET_API_URL;
354
445
  account;
355
446
  privateKey;
356
447
  apiUrl;
@@ -408,7 +499,8 @@ var LLMClient = class _LLMClient {
408
499
  temperature: options?.temperature,
409
500
  topP: options?.topP,
410
501
  search: options?.search,
411
- searchParameters: options?.searchParameters
502
+ searchParameters: options?.searchParameters,
503
+ fallbackModels: options?.fallbackModels
412
504
  });
413
505
  return result.choices[0].message.content || "";
414
506
  }
@@ -450,18 +542,24 @@ var LLMClient = class _LLMClient {
450
542
  modelPricing,
451
543
  routingProfile: options?.routingProfile
452
544
  });
545
+ const tierConfigs = decision.tierConfigs ?? DEFAULT_ROUTING_CONFIG.tiers;
546
+ const fullChain = getFallbackChain(decision.tier, tierConfigs);
547
+ const fallbacks = fullChain.filter(
548
+ (id) => id !== decision.model && modelPricing.has(id)
549
+ );
453
550
  const response = await this.chat(decision.model, prompt, {
454
551
  system: options?.system,
455
552
  maxTokens: options?.maxTokens,
456
553
  temperature: options?.temperature,
457
554
  topP: options?.topP,
458
555
  search: options?.search,
459
- searchParameters: options?.searchParameters
556
+ searchParameters: options?.searchParameters,
557
+ fallbackModels: fallbacks
460
558
  });
461
559
  return {
462
560
  response,
463
561
  model: decision.model,
464
- routing: decision
562
+ routing: { ...decision, fallbacks }
465
563
  };
466
564
  }
467
565
  /**
@@ -485,50 +583,108 @@ var LLMClient = class _LLMClient {
485
583
  }
486
584
  /**
487
585
  * Fetch model pricing from API.
586
+ *
587
+ * For flat-billed models (e.g. ZAI GLM-5 family at $0.001/call) the
588
+ * router still expects per-token rates, so we synthesise an equivalent
589
+ * per-token price assuming ~1500 total tokens per call. Without this,
590
+ * flat models would resolve to inputPrice=outputPrice=0 and the router
591
+ * would treat them as free, biasing routing decisions and reporting
592
+ * inflated savings %.
488
593
  */
489
594
  async fetchModelPricing() {
490
595
  const models = await this.listModels();
491
596
  const pricing = /* @__PURE__ */ new Map();
492
597
  for (const model of models) {
493
- pricing.set(model.id, {
494
- inputPrice: model.inputPrice,
495
- outputPrice: model.outputPrice
496
- });
598
+ if (model.billingMode === "flat" && model.flatPrice && model.flatPrice > 0) {
599
+ const perDirection = model.flatPrice * 1e6 / 1500 / 2;
600
+ pricing.set(model.id, {
601
+ inputPrice: perDirection,
602
+ outputPrice: perDirection
603
+ });
604
+ } else {
605
+ pricing.set(model.id, {
606
+ inputPrice: model.inputPrice,
607
+ outputPrice: model.outputPrice
608
+ });
609
+ }
497
610
  }
498
611
  return pricing;
499
612
  }
500
613
  /**
501
614
  * Full chat completion interface (OpenAI-compatible).
502
615
  *
503
- * @param model - Model ID
616
+ * When `fallbackModels` is set, transient failures (timeouts, network
617
+ * errors, 5xx) on the primary model trigger a retry against the next
618
+ * model in the list before raising. 4xx errors and PaymentError
619
+ * propagate immediately — those aren't "swap upstream and retry"
620
+ * situations. Each fallback hop logs one stderr line.
621
+ *
622
+ * @param model - Primary model ID
504
623
  * @param messages - Array of messages with role and content
505
624
  * @param options - Optional completion parameters
506
625
  * @returns ChatResponse object with choices and usage
507
626
  */
508
627
  async chatCompletion(model, messages, options) {
509
- const body = {
510
- model,
511
- messages,
512
- max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS
628
+ const buildBody = (m) => {
629
+ const body = {
630
+ model: m,
631
+ messages,
632
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS
633
+ };
634
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
635
+ if (options?.topP !== void 0) body.top_p = options.topP;
636
+ if (options?.searchParameters !== void 0) {
637
+ body.search_parameters = options.searchParameters;
638
+ } else if (options?.search === true) {
639
+ body.search_parameters = { mode: "on" };
640
+ }
641
+ if (options?.tools !== void 0) body.tools = options.tools;
642
+ if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
643
+ return body;
513
644
  };
514
- if (options?.temperature !== void 0) {
515
- body.temperature = options.temperature;
516
- }
517
- if (options?.topP !== void 0) {
518
- body.top_p = options.topP;
519
- }
520
- if (options?.searchParameters !== void 0) {
521
- body.search_parameters = options.searchParameters;
522
- } else if (options?.search === true) {
523
- body.search_parameters = { mode: "on" };
524
- }
525
- if (options?.tools !== void 0) {
526
- body.tools = options.tools;
645
+ const chain = [model, ...options?.fallbackModels ?? []];
646
+ let lastErr;
647
+ for (let i = 0; i < chain.length; i++) {
648
+ const candidate = chain[i];
649
+ try {
650
+ return await this.requestWithPayment("/v1/chat/completions", buildBody(candidate));
651
+ } catch (err) {
652
+ lastErr = err;
653
+ const next = chain[i + 1];
654
+ if (!next || !isTransientError(err)) throw err;
655
+ console.error(
656
+ `[@blockrun/llm] ${candidate} -> ${next} (${errSummary(err)})`
657
+ );
658
+ }
527
659
  }
528
- if (options?.toolChoice !== void 0) {
529
- body.tool_choice = options.toolChoice;
660
+ throw lastErr;
661
+ }
662
+ /**
663
+ * Write a canonical cost_log entry after a settled x402 payment.
664
+ * Best-effort: failures here must never break a successful API call.
665
+ * Mirrors what Franklin's AgentClient writes via src/agent/llm.ts so
666
+ * cost_log.jsonl is a single source of truth regardless of caller.
667
+ */
668
+ recordCost(url, costUsd, opts) {
669
+ try {
670
+ let endpoint = "";
671
+ try {
672
+ endpoint = new URL(url).pathname;
673
+ } catch {
674
+ endpoint = "";
675
+ }
676
+ const model = opts?.body && typeof opts.body.model === "string" ? opts.body.model : void 0;
677
+ logCost({
678
+ ts: Date.now() / 1e3,
679
+ endpoint,
680
+ cost_usd: costUsd,
681
+ model,
682
+ wallet: this.account.address,
683
+ network: opts?.network,
684
+ client_kind: "LLMClient"
685
+ });
686
+ } catch {
530
687
  }
531
- return this.requestWithPayment("/v1/chat/completions", body);
532
688
  }
533
689
  /**
534
690
  * Make a request with automatic x402 payment handling.
@@ -651,6 +807,7 @@ var LLMClient = class _LLMClient {
651
807
  const costUsd2 = parseFloat(details.amount) / 1e6;
652
808
  this.sessionCalls += 1;
653
809
  this.sessionTotalUsd += costUsd2;
810
+ this.recordCost(url, costUsd2, { body, network: details.network });
654
811
  return retryResp2.json();
655
812
  }
656
813
  }
@@ -673,6 +830,7 @@ var LLMClient = class _LLMClient {
673
830
  const costUsd = parseFloat(details.amount) / 1e6;
674
831
  this.sessionCalls += 1;
675
832
  this.sessionTotalUsd += costUsd;
833
+ this.recordCost(url, costUsd, { body, network: details.network });
676
834
  return retryResponse.json();
677
835
  }
678
836
  /**
@@ -922,6 +1080,7 @@ var LLMClient = class _LLMClient {
922
1080
  const costUsd2 = parseFloat(details.amount) / 1e6;
923
1081
  this.sessionCalls += 1;
924
1082
  this.sessionTotalUsd += costUsd2;
1083
+ this.recordCost(url, costUsd2, { body, network: details.network });
925
1084
  return retryResp2.json();
926
1085
  }
927
1086
  }
@@ -944,6 +1103,7 @@ var LLMClient = class _LLMClient {
944
1103
  const costUsd = parseFloat(details.amount) / 1e6;
945
1104
  this.sessionCalls += 1;
946
1105
  this.sessionTotalUsd += costUsd;
1106
+ this.recordCost(url, costUsd, { body, network: details.network });
947
1107
  return retryResponse.json();
948
1108
  }
949
1109
  /**
@@ -1065,6 +1225,7 @@ var LLMClient = class _LLMClient {
1065
1225
  const costUsd2 = parseFloat(details.amount) / 1e6;
1066
1226
  this.sessionCalls += 1;
1067
1227
  this.sessionTotalUsd += costUsd2;
1228
+ this.recordCost(url, costUsd2, { network: details.network });
1068
1229
  return retryResp2.json();
1069
1230
  }
1070
1231
  }
@@ -1087,6 +1248,7 @@ var LLMClient = class _LLMClient {
1087
1248
  const costUsd = parseFloat(details.amount) / 1e6;
1088
1249
  this.sessionCalls += 1;
1089
1250
  this.sessionTotalUsd += costUsd;
1251
+ this.recordCost(url, costUsd, { network: details.network });
1090
1252
  return retryResponse.json();
1091
1253
  }
1092
1254
  /**
@@ -1106,9 +1268,59 @@ var LLMClient = class _LLMClient {
1106
1268
  }
1107
1269
  }
1108
1270
  /**
1109
- * List available LLM models with pricing.
1271
+ * List available models with pricing.
1272
+ *
1273
+ * Returns the full `/v1/models` unified catalog (chat + image + music).
1274
+ * The shape preserves backwards compatibility — image/music rows have
1275
+ * `inputPrice = outputPrice = 0` since those fields don't apply, and
1276
+ * their per-call price surfaces via `flatPrice`.
1110
1277
  */
1111
1278
  async listModels() {
1279
+ const raw = await this.fetchRawModels();
1280
+ return raw.map((m) => mapRawToModel(m));
1281
+ }
1282
+ /**
1283
+ * List available image generation models with pricing.
1284
+ *
1285
+ * The dedicated `/v1/images/models` endpoint was deprecated server-side;
1286
+ * image models live in the unified `/v1/models` catalog under
1287
+ * `categories: ["image", ...]`. This method filters that catalog so
1288
+ * existing callers keep working.
1289
+ */
1290
+ async listImageModels() {
1291
+ const raw = await this.fetchRawModels();
1292
+ return raw.filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => mapRawToImageModel(m));
1293
+ }
1294
+ /**
1295
+ * List all available models (chat, image, music, etc.) with pricing.
1296
+ *
1297
+ * @returns Array of all models with `type` field set from category
1298
+ * (`llm` for chat, `image` / `music` for media). Backwards-compat:
1299
+ * chat models always report `type: "llm"`.
1300
+ */
1301
+ async listAllModels() {
1302
+ const raw = await this.fetchRawModels();
1303
+ const out = [];
1304
+ for (const m of raw) {
1305
+ const cats = Array.isArray(m.categories) ? m.categories : [];
1306
+ if (cats.includes("image")) {
1307
+ const model = mapRawToImageModel(m);
1308
+ model.type = "image";
1309
+ out.push(model);
1310
+ } else {
1311
+ const model = mapRawToModel(m);
1312
+ model.type = "llm";
1313
+ out.push(model);
1314
+ }
1315
+ }
1316
+ return out;
1317
+ }
1318
+ /**
1319
+ * Internal: fetch the raw `/v1/models` catalog without normalising shape.
1320
+ * Used by listImageModels / listAllModels so each can pick category-
1321
+ * specific fields.
1322
+ */
1323
+ async fetchRawModels() {
1112
1324
  const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, {
1113
1325
  method: "GET"
1114
1326
  });
@@ -1126,65 +1338,8 @@ var LLMClient = class _LLMClient {
1126
1338
  );
1127
1339
  }
1128
1340
  const data = await response.json();
1129
- return (data.data || []).map((m) => ({
1130
- id: m.id,
1131
- name: m.name || m.id,
1132
- provider: m.provider || m.owned_by || "",
1133
- description: m.description || "",
1134
- inputPrice: m.inputPrice ?? m.input_price ?? m.pricing?.input ?? 0,
1135
- outputPrice: m.outputPrice ?? m.output_price ?? m.pricing?.output ?? 0,
1136
- contextWindow: m.contextWindow ?? m.context_window ?? 0,
1137
- maxOutput: m.maxOutput ?? m.max_output ?? 0,
1138
- categories: m.categories || [],
1139
- available: true,
1140
- billingMode: m.billingMode ?? m.billing_mode,
1141
- flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
1142
- hidden: m.hidden
1143
- }));
1144
- }
1145
- /**
1146
- * List available image generation models with pricing.
1147
- */
1148
- async listImageModels() {
1149
- const response = await this.fetchWithTimeout(
1150
- `${this.apiUrl}/v1/images/models`,
1151
- { method: "GET" }
1152
- );
1153
- if (!response.ok) {
1154
- throw new APIError(
1155
- `Failed to list image models: ${response.status}`,
1156
- response.status
1157
- );
1158
- }
1159
- const data = await response.json();
1160
1341
  return data.data || [];
1161
1342
  }
1162
- /**
1163
- * List all available models (both LLM and image) with pricing.
1164
- *
1165
- * @returns Array of all models with 'type' field ('llm' or 'image')
1166
- *
1167
- * @example
1168
- * const models = await client.listAllModels();
1169
- * for (const model of models) {
1170
- * if (model.type === 'llm') {
1171
- * console.log(`LLM: ${model.id} - $${model.inputPrice}/M input`);
1172
- * } else {
1173
- * console.log(`Image: ${model.id} - $${model.pricePerImage}/image`);
1174
- * }
1175
- * }
1176
- */
1177
- async listAllModels() {
1178
- const llmModels = await this.listModels();
1179
- for (const model of llmModels) {
1180
- model.type = "llm";
1181
- }
1182
- const imageModels = await this.listImageModels();
1183
- for (const model of imageModels) {
1184
- model.type = "image";
1185
- }
1186
- return [...llmModels, ...imageModels];
1187
- }
1188
1343
  /**
1189
1344
  * Edit an image using img2img.
1190
1345
  *
@@ -1225,6 +1380,19 @@ var LLMClient = class _LLMClient {
1225
1380
  const data = await this.requestWithPaymentRaw("/v1/search", body);
1226
1381
  return data;
1227
1382
  }
1383
+ /**
1384
+ * Generic Exa endpoint proxy (POST). Useful when you need an Exa API
1385
+ * surface that the typed wrappers below don't expose.
1386
+ *
1387
+ * @param path - Exa endpoint segment: "search" | "find-similar" | "contents" | "answer"
1388
+ * @param body - Request body (see Exa API docs)
1389
+ *
1390
+ * @example
1391
+ * const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
1392
+ */
1393
+ async exa(path5, body) {
1394
+ return this.requestWithPaymentRaw(`/v1/exa/${path5}`, body);
1395
+ }
1228
1396
  /**
1229
1397
  * Neural web search via Exa. Returns semantically relevant URLs and metadata.
1230
1398
  * Understands meaning, not just keywords. $0.01/call.
@@ -1278,9 +1446,7 @@ var LLMClient = class _LLMClient {
1278
1446
  return data.data;
1279
1447
  }
1280
1448
  /**
1281
- * Get USDC balance on Base network.
1282
- *
1283
- * Automatically detects mainnet vs testnet based on API URL.
1449
+ * Get USDC balance on Base mainnet.
1284
1450
  *
1285
1451
  * @returns USDC balance as a float (6 decimal places normalized)
1286
1452
  *
@@ -1289,9 +1455,8 @@ var LLMClient = class _LLMClient {
1289
1455
  * console.log(`Balance: $${balance.toFixed(2)} USDC`);
1290
1456
  */
1291
1457
  async getBalance() {
1292
- const isTestnet = this.isTestnet();
1293
- const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1294
- 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"];
1458
+ const usdcContract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1459
+ const rpcs = ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
1295
1460
  const selector = "0x70a08231";
1296
1461
  const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
1297
1462
  const data = selector + paddedAddress;
@@ -1537,17 +1702,70 @@ var LLMClient = class _LLMClient {
1537
1702
  /**
1538
1703
  * Structured query for Predexon prediction market data (POST endpoints).
1539
1704
  *
1540
- * For complex queries that require a JSON body. $0.005 per request.
1705
+ * For endpoints that require a JSON body, e.g. bulk wallet identity lookup.
1706
+ * Tier 1 = $0.001/call, Tier 2 = $0.005/call.
1541
1707
  *
1542
- * @param path - Endpoint path, e.g. "polymarket/query", "kalshi/query"
1708
+ * @param path - Endpoint path, e.g. "polymarket/wallet/identities"
1543
1709
  * @param query - JSON body for the structured query
1544
1710
  *
1545
1711
  * @example
1546
- * const data = await client.pmQuery("polymarket/query", { filter: "active", limit: 10 });
1712
+ * const batch = await client.pmQuery("polymarket/wallet/identities", {
1713
+ * addresses: ["0xabc...", "0xdef..."],
1714
+ * });
1547
1715
  */
1548
1716
  async pmQuery(path5, query) {
1549
1717
  return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1550
1718
  }
1719
+ // ── PM convenience helpers (Predexon v2) ─────────────────────────────────
1720
+ // Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
1721
+ /** List canonical cross-venue markets (Predexon v2). Tier 1 ($0.001/call).
1722
+ * Filter with venue, status, category, league, event_id, pagination_key. */
1723
+ async pmMarkets(params) {
1724
+ return this.pm("markets", params);
1725
+ }
1726
+ /** List venue-native executable listings flattened across canonical markets
1727
+ * (Predexon v2). Tier 1 ($0.001/call). */
1728
+ async pmListings(params) {
1729
+ return this.pm("markets/listings", params);
1730
+ }
1731
+ /** Resolve a canonical Predexon outcome ID to its market context and venue
1732
+ * listings. Tier 1 ($0.001/call). */
1733
+ async pmOutcome(predexonId) {
1734
+ return this.pm(`outcomes/${predexonId}`);
1735
+ }
1736
+ /** Polymarket markets with cursor-based keyset pagination (use pagination_key).
1737
+ * Tier 1 ($0.001/call). */
1738
+ async pmPolymarketMarketsKeyset(params) {
1739
+ return this.pm("polymarket/markets/keyset", params);
1740
+ }
1741
+ /** Polymarket events with cursor-based keyset pagination (use pagination_key).
1742
+ * Tier 1 ($0.001/call). */
1743
+ async pmPolymarketEventsKeyset(params) {
1744
+ return this.pm("polymarket/events/keyset", params);
1745
+ }
1746
+ /** List available sports categories. Tier 1 ($0.001/call). */
1747
+ async pmSportsCategories() {
1748
+ return this.pm("sports/categories");
1749
+ }
1750
+ /** List sports markets grouped by game. Filter with league, sport_type,
1751
+ * status, venue. Tier 1 ($0.001/call). */
1752
+ async pmSportsMarkets(params) {
1753
+ return this.pm("sports/markets", params);
1754
+ }
1755
+ /** Fetch identity + profile metadata for one wallet (ENS, Twitter, portfolio,
1756
+ * etc.). Tier 2 ($0.005/call). */
1757
+ async pmWalletIdentity(wallet) {
1758
+ return this.pm(`polymarket/wallet/identity/${wallet}`);
1759
+ }
1760
+ /** Bulk identity lookup for up to 200 wallet addresses (POST). Tier 2 ($0.005/call). */
1761
+ async pmWalletIdentities(addresses) {
1762
+ return this.pmQuery("polymarket/wallet/identities", { addresses });
1763
+ }
1764
+ /** Discover wallets connected to a seed address via on-chain transfers and
1765
+ * identity proofs. Tier 2 ($0.005/call). */
1766
+ async pmWalletCluster(address) {
1767
+ return this.pm(`polymarket/wallet/${address}/cluster`);
1768
+ }
1551
1769
  /**
1552
1770
  * Get current session spending.
1553
1771
  *
@@ -1569,19 +1787,7 @@ var LLMClient = class _LLMClient {
1569
1787
  getWalletAddress() {
1570
1788
  return this.account.address;
1571
1789
  }
1572
- /**
1573
- * Check if client is configured for testnet.
1574
- */
1575
- isTestnet() {
1576
- return this.apiUrl.includes("testnet.blockrun.ai");
1577
- }
1578
1790
  };
1579
- function testnetClient(options = {}) {
1580
- return new LLMClient({
1581
- ...options,
1582
- apiUrl: TESTNET_API_URL
1583
- });
1584
- }
1585
1791
  var client_default = LLMClient;
1586
1792
 
1587
1793
  // src/image.ts
@@ -1670,10 +1876,15 @@ var ImageClient = class {
1670
1876
  }
1671
1877
  /**
1672
1878
  * List available image generation models with pricing.
1879
+ *
1880
+ * The dedicated `/v1/images/models` endpoint was deprecated server-side;
1881
+ * image models live in the unified `/v1/models` catalog under
1882
+ * `categories: ["image", ...]`. This method filters that catalog so
1883
+ * existing callers keep working.
1673
1884
  */
1674
1885
  async listImageModels() {
1675
1886
  const response = await this.fetchWithTimeout(
1676
- `${this.apiUrl}/v1/images/models`,
1887
+ `${this.apiUrl}/v1/models`,
1677
1888
  { method: "GET" }
1678
1889
  );
1679
1890
  if (!response.ok) {
@@ -1683,7 +1894,16 @@ var ImageClient = class {
1683
1894
  );
1684
1895
  }
1685
1896
  const data = await response.json();
1686
- return data.data || [];
1897
+ return (data.data || []).filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => ({
1898
+ id: m.id,
1899
+ name: m.name || m.id,
1900
+ provider: m.provider || m.owned_by || "",
1901
+ description: m.description || "",
1902
+ pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
1903
+ supportedSizes: m.supportedSizes ?? m.supported_sizes,
1904
+ maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
1905
+ available: true
1906
+ }));
1687
1907
  }
1688
1908
  /**
1689
1909
  * Make a request with automatic x402 payment handling.
@@ -2169,12 +2389,19 @@ var SearchClient = class {
2169
2389
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
2170
2390
  var DEFAULT_API_URL5 = "https://blockrun.ai/api";
2171
2391
  var DEFAULT_TIMEOUT5 = 6e4;
2172
- var XClient = class {
2392
+ var XClient = class _XClient {
2173
2393
  account;
2174
2394
  privateKey;
2175
2395
  apiUrl;
2176
2396
  timeout;
2397
+ static deprecationWarned = false;
2177
2398
  constructor(options = {}) {
2399
+ if (!_XClient.deprecationWarned) {
2400
+ console.warn(
2401
+ "[@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."
2402
+ );
2403
+ _XClient.deprecationWarned = true;
2404
+ }
2178
2405
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2179
2406
  const privateKey = options.privateKey || envKey;
2180
2407
  if (!privateKey) {
@@ -2571,13 +2798,13 @@ function buildUrl(base, query) {
2571
2798
 
2572
2799
  // src/wallet.ts
2573
2800
  import { privateKeyToAccount as privateKeyToAccount7, generatePrivateKey } from "viem/accounts";
2574
- import * as fs from "fs";
2575
- import * as path from "path";
2576
- import * as os from "os";
2801
+ import * as fs2 from "fs";
2802
+ import * as path2 from "path";
2803
+ import * as os2 from "os";
2577
2804
  var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
2578
2805
  var BASE_CHAIN_ID2 = "8453";
2579
- var WALLET_DIR = path.join(os.homedir(), ".blockrun");
2580
- var WALLET_FILE = path.join(WALLET_DIR, ".session");
2806
+ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
2807
+ var WALLET_FILE = path2.join(WALLET_DIR, ".session");
2581
2808
  function createWallet() {
2582
2809
  const privateKey = generatePrivateKey();
2583
2810
  const account = privateKeyToAccount7(privateKey);
@@ -2587,27 +2814,27 @@ function createWallet() {
2587
2814
  };
2588
2815
  }
2589
2816
  function saveWallet(privateKey) {
2590
- if (!fs.existsSync(WALLET_DIR)) {
2591
- fs.mkdirSync(WALLET_DIR, { recursive: true });
2817
+ if (!fs2.existsSync(WALLET_DIR)) {
2818
+ fs2.mkdirSync(WALLET_DIR, { recursive: true });
2592
2819
  }
2593
- fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2820
+ fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2594
2821
  return WALLET_FILE;
2595
2822
  }
2596
2823
  function scanWallets() {
2597
- const home = os.homedir();
2824
+ const home = os2.homedir();
2598
2825
  const results = [];
2599
2826
  try {
2600
- const entries = fs.readdirSync(home, { withFileTypes: true });
2827
+ const entries = fs2.readdirSync(home, { withFileTypes: true });
2601
2828
  for (const entry of entries) {
2602
2829
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2603
- const walletFile = path.join(home, entry.name, "wallet.json");
2604
- if (!fs.existsSync(walletFile)) continue;
2830
+ const walletFile = path2.join(home, entry.name, "wallet.json");
2831
+ if (!fs2.existsSync(walletFile)) continue;
2605
2832
  try {
2606
- const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
2833
+ const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
2607
2834
  const pk = data.privateKey || "";
2608
2835
  const addr = data.address || "";
2609
2836
  if (pk && addr) {
2610
- const mtime = fs.statSync(walletFile).mtimeMs;
2837
+ const mtime = fs2.statSync(walletFile).mtimeMs;
2611
2838
  results.push({ mtime, privateKey: pk, address: addr });
2612
2839
  }
2613
2840
  } catch {
@@ -2622,13 +2849,13 @@ function scanWallets() {
2622
2849
  function loadWallet() {
2623
2850
  const wallets = scanWallets();
2624
2851
  if (wallets.length > 0) return wallets[0].privateKey;
2625
- if (fs.existsSync(WALLET_FILE)) {
2626
- const key = fs.readFileSync(WALLET_FILE, "utf-8").trim();
2852
+ if (fs2.existsSync(WALLET_FILE)) {
2853
+ const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
2627
2854
  if (key) return key;
2628
2855
  }
2629
- const legacyFile = path.join(WALLET_DIR, "wallet.key");
2630
- if (fs.existsSync(legacyFile)) {
2631
- const key = fs.readFileSync(legacyFile, "utf-8").trim();
2856
+ const legacyFile = path2.join(WALLET_DIR, "wallet.key");
2857
+ if (fs2.existsSync(legacyFile)) {
2858
+ const key = fs2.readFileSync(legacyFile, "utf-8").trim();
2632
2859
  if (key) return key;
2633
2860
  }
2634
2861
  return null;
@@ -2723,11 +2950,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
2723
2950
  var WALLET_DIR_PATH = WALLET_DIR;
2724
2951
 
2725
2952
  // src/solana-wallet.ts
2726
- import * as fs2 from "fs";
2727
- import * as path2 from "path";
2728
- import * as os2 from "os";
2729
- var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
2730
- var SOLANA_WALLET_FILE = path2.join(WALLET_DIR2, ".solana-session");
2953
+ import * as fs3 from "fs";
2954
+ import * as path3 from "path";
2955
+ import * as os3 from "os";
2956
+ var WALLET_DIR2 = path3.join(os3.homedir(), ".blockrun");
2957
+ var SOLANA_WALLET_FILE = path3.join(WALLET_DIR2, ".solana-session");
2731
2958
  async function createSolanaWallet() {
2732
2959
  const { Keypair } = await import("@solana/web3.js");
2733
2960
  const bs58 = await import("bs58");
@@ -2756,39 +2983,39 @@ async function solanaPublicKey(privateKey) {
2756
2983
  return Keypair.fromSecretKey(bytes).publicKey.toBase58();
2757
2984
  }
2758
2985
  function saveSolanaWallet(privateKey) {
2759
- if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
2760
- fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
2986
+ if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
2987
+ fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
2761
2988
  return SOLANA_WALLET_FILE;
2762
2989
  }
2763
2990
  function scanSolanaWallets() {
2764
- const home = os2.homedir();
2991
+ const home = os3.homedir();
2765
2992
  const results = [];
2766
2993
  try {
2767
- const entries = fs2.readdirSync(home, { withFileTypes: true });
2994
+ const entries = fs3.readdirSync(home, { withFileTypes: true });
2768
2995
  for (const entry of entries) {
2769
2996
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2770
- const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
2771
- if (fs2.existsSync(solanaWalletFile)) {
2997
+ const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
2998
+ if (fs3.existsSync(solanaWalletFile)) {
2772
2999
  try {
2773
- const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
3000
+ const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
2774
3001
  const pk = data.privateKey || "";
2775
3002
  const addr = data.address || "";
2776
3003
  if (pk && addr) {
2777
- const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
3004
+ const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
2778
3005
  results.push({ mtime, secretKey: pk, publicKey: addr });
2779
3006
  }
2780
3007
  } catch {
2781
3008
  }
2782
3009
  }
2783
3010
  if (entry.name === ".brcc") {
2784
- const brccWalletFile = path2.join(home, entry.name, "wallet.json");
2785
- if (fs2.existsSync(brccWalletFile)) {
3011
+ const brccWalletFile = path3.join(home, entry.name, "wallet.json");
3012
+ if (fs3.existsSync(brccWalletFile)) {
2786
3013
  try {
2787
- const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
3014
+ const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
2788
3015
  const pk = data.privateKey || "";
2789
3016
  const addr = data.address || "";
2790
3017
  if (pk && addr) {
2791
- const mtime = fs2.statSync(brccWalletFile).mtimeMs;
3018
+ const mtime = fs3.statSync(brccWalletFile).mtimeMs;
2792
3019
  results.push({ mtime, secretKey: pk, publicKey: addr });
2793
3020
  }
2794
3021
  } catch {
@@ -2804,8 +3031,8 @@ function scanSolanaWallets() {
2804
3031
  function loadSolanaWallet() {
2805
3032
  const wallets = scanSolanaWallets();
2806
3033
  if (wallets.length > 0) return wallets[0].secretKey;
2807
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2808
- const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
3034
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
3035
+ const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
2809
3036
  if (key) return key;
2810
3037
  }
2811
3038
  return null;
@@ -2820,8 +3047,8 @@ async function getOrCreateSolanaWallet() {
2820
3047
  if (wallets.length > 0) {
2821
3048
  return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
2822
3049
  }
2823
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2824
- const fileKey = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
3050
+ if (fs3.existsSync(SOLANA_WALLET_FILE)) {
3051
+ const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
2825
3052
  if (fileKey) {
2826
3053
  const address2 = await solanaPublicKey(fileKey);
2827
3054
  return { privateKey: fileKey, address: address2, isNew: false };
@@ -3395,13 +3622,13 @@ function solanaClient(options = {}) {
3395
3622
  }
3396
3623
 
3397
3624
  // src/cache.ts
3398
- import * as fs3 from "fs";
3399
- import * as path3 from "path";
3400
- import * as os3 from "os";
3625
+ import * as fs4 from "fs";
3626
+ import * as path4 from "path";
3627
+ import * as os4 from "os";
3401
3628
  import * as crypto2 from "crypto";
3402
- var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
3403
- var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
3404
- var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
3629
+ var CACHE_DIR = path4.join(os4.homedir(), ".blockrun", "cache");
3630
+ var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
3631
+ var COST_LOG_FILE2 = path4.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
3405
3632
  var DEFAULT_TTL = {
3406
3633
  "/v1/x/": 3600 * 1e3,
3407
3634
  "/v1/partner/": 3600 * 1e3,
@@ -3422,19 +3649,19 @@ function cacheKey(endpoint, body) {
3422
3649
  return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
3423
3650
  }
3424
3651
  function cachePath(key) {
3425
- return path3.join(CACHE_DIR, `${key}.json`);
3652
+ return path4.join(CACHE_DIR, `${key}.json`);
3426
3653
  }
3427
3654
  function getCached(key) {
3428
3655
  const filePath = cachePath(key);
3429
- if (!fs3.existsSync(filePath)) return null;
3656
+ if (!fs4.existsSync(filePath)) return null;
3430
3657
  try {
3431
- const raw = fs3.readFileSync(filePath, "utf-8");
3658
+ const raw = fs4.readFileSync(filePath, "utf-8");
3432
3659
  const entry = JSON.parse(raw);
3433
3660
  const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
3434
3661
  if (ttl <= 0) return null;
3435
3662
  if (Date.now() - entry.cachedAt > ttl) {
3436
3663
  try {
3437
- fs3.unlinkSync(filePath);
3664
+ fs4.unlinkSync(filePath);
3438
3665
  } catch {
3439
3666
  }
3440
3667
  return null;
@@ -3453,7 +3680,7 @@ function getCachedByRequest(endpoint, body) {
3453
3680
  function setCache(key, data, ttlMs) {
3454
3681
  if (ttlMs <= 0) return;
3455
3682
  try {
3456
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3683
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3457
3684
  } catch {
3458
3685
  }
3459
3686
  const entry = {
@@ -3462,7 +3689,7 @@ function setCache(key, data, ttlMs) {
3462
3689
  ttlMs
3463
3690
  };
3464
3691
  try {
3465
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3692
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3466
3693
  } catch {
3467
3694
  }
3468
3695
  }
@@ -3485,7 +3712,7 @@ function readableFilename(endpoint, body) {
3485
3712
  }
3486
3713
  function saveReadable(endpoint, body, response, costUsd) {
3487
3714
  try {
3488
- fs3.mkdirSync(DATA_DIR, { recursive: true });
3715
+ fs4.mkdirSync(DATA_DIR, { recursive: true });
3489
3716
  } catch {
3490
3717
  }
3491
3718
  const filename = readableFilename(endpoint, body);
@@ -3497,14 +3724,14 @@ function saveReadable(endpoint, body, response, costUsd) {
3497
3724
  response
3498
3725
  };
3499
3726
  try {
3500
- fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
3727
+ fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
3501
3728
  } catch {
3502
3729
  }
3503
3730
  }
3504
3731
  function appendCostLog(endpoint, costUsd) {
3505
3732
  if (costUsd <= 0) return;
3506
3733
  try {
3507
- fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
3734
+ fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
3508
3735
  } catch {
3509
3736
  }
3510
3737
  const entry = {
@@ -3513,7 +3740,7 @@ function appendCostLog(endpoint, costUsd) {
3513
3740
  cost_usd: costUsd
3514
3741
  };
3515
3742
  try {
3516
- fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
3743
+ fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3517
3744
  } catch {
3518
3745
  }
3519
3746
  }
@@ -3521,7 +3748,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3521
3748
  const ttl = getTtl(endpoint);
3522
3749
  if (ttl > 0) {
3523
3750
  try {
3524
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3751
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3525
3752
  } catch {
3526
3753
  }
3527
3754
  const key = cacheKey(endpoint, body);
@@ -3533,7 +3760,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3533
3760
  costUsd
3534
3761
  };
3535
3762
  try {
3536
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3763
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3537
3764
  } catch {
3538
3765
  }
3539
3766
  }
@@ -3541,14 +3768,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3541
3768
  appendCostLog(endpoint, costUsd);
3542
3769
  }
3543
3770
  function clearCache() {
3544
- if (!fs3.existsSync(CACHE_DIR)) return 0;
3771
+ if (!fs4.existsSync(CACHE_DIR)) return 0;
3545
3772
  let count = 0;
3546
3773
  try {
3547
- const files = fs3.readdirSync(CACHE_DIR);
3774
+ const files = fs4.readdirSync(CACHE_DIR);
3548
3775
  for (const file of files) {
3549
3776
  if (file.endsWith(".json")) {
3550
3777
  try {
3551
- fs3.unlinkSync(path3.join(CACHE_DIR, file));
3778
+ fs4.unlinkSync(path4.join(CACHE_DIR, file));
3552
3779
  count++;
3553
3780
  } catch {
3554
3781
  }
@@ -3559,14 +3786,14 @@ function clearCache() {
3559
3786
  return count;
3560
3787
  }
3561
3788
  function getCostLogSummary() {
3562
- if (!fs3.existsSync(COST_LOG_FILE)) {
3789
+ if (!fs4.existsSync(COST_LOG_FILE2)) {
3563
3790
  return { totalUsd: 0, calls: 0, byEndpoint: {} };
3564
3791
  }
3565
3792
  let totalUsd = 0;
3566
3793
  let calls = 0;
3567
3794
  const byEndpoint = {};
3568
3795
  try {
3569
- const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
3796
+ const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3570
3797
  if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
3571
3798
  for (const line of content.split("\n")) {
3572
3799
  if (!line) continue;
@@ -3621,47 +3848,6 @@ async function status() {
3621
3848
  return { address, balance };
3622
3849
  }
3623
3850
 
3624
- // src/cost-log.ts
3625
- import * as fs4 from "fs";
3626
- import * as path4 from "path";
3627
- import * as os4 from "os";
3628
- var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
3629
- var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
3630
- function logCost(entry) {
3631
- try {
3632
- fs4.mkdirSync(DATA_DIR2, { recursive: true });
3633
- } catch {
3634
- }
3635
- try {
3636
- fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3637
- } catch {
3638
- }
3639
- }
3640
- function getCostSummary() {
3641
- if (!fs4.existsSync(COST_LOG_FILE2)) {
3642
- return { totalUsd: 0, calls: 0, byModel: {} };
3643
- }
3644
- let totalUsd = 0;
3645
- let calls = 0;
3646
- const byModel = {};
3647
- try {
3648
- const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3649
- if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
3650
- for (const line of content.split("\n")) {
3651
- if (!line) continue;
3652
- try {
3653
- const entry = JSON.parse(line);
3654
- totalUsd += entry.costUsd;
3655
- calls += 1;
3656
- byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
3657
- } catch {
3658
- }
3659
- }
3660
- } catch {
3661
- }
3662
- return { totalUsd, calls, byModel };
3663
- }
3664
-
3665
3851
  // src/openai-compat.ts
3666
3852
  var StreamingResponse = class {
3667
3853
  reader;
@@ -3969,7 +4155,6 @@ export {
3969
4155
  solanaKeyToBytes,
3970
4156
  solanaPublicKey,
3971
4157
  status,
3972
- testnetClient,
3973
4158
  validateMaxTokens,
3974
4159
  validateModel,
3975
4160
  validateTemperature,