@blockrun/llm 1.15.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;
@@ -1718,19 +1882,7 @@ var LLMClient = class _LLMClient {
1718
1882
  getWalletAddress() {
1719
1883
  return this.account.address;
1720
1884
  }
1721
- /**
1722
- * Check if client is configured for testnet.
1723
- */
1724
- isTestnet() {
1725
- return this.apiUrl.includes("testnet.blockrun.ai");
1726
- }
1727
1885
  };
1728
- function testnetClient(options = {}) {
1729
- return new LLMClient({
1730
- ...options,
1731
- apiUrl: TESTNET_API_URL
1732
- });
1733
- }
1734
1886
  var client_default = LLMClient;
1735
1887
 
1736
1888
  // src/image.ts
@@ -1819,10 +1971,15 @@ var ImageClient = class {
1819
1971
  }
1820
1972
  /**
1821
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.
1822
1979
  */
1823
1980
  async listImageModels() {
1824
1981
  const response = await this.fetchWithTimeout(
1825
- `${this.apiUrl}/v1/images/models`,
1982
+ `${this.apiUrl}/v1/models`,
1826
1983
  { method: "GET" }
1827
1984
  );
1828
1985
  if (!response.ok) {
@@ -1832,7 +1989,16 @@ var ImageClient = class {
1832
1989
  );
1833
1990
  }
1834
1991
  const data = await response.json();
1835
- 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
+ }));
1836
2002
  }
1837
2003
  /**
1838
2004
  * Make a request with automatic x402 payment handling.
@@ -2727,13 +2893,13 @@ function buildUrl(base, query) {
2727
2893
 
2728
2894
  // src/wallet.ts
2729
2895
  var import_accounts8 = require("viem/accounts");
2730
- var fs = __toESM(require("fs"), 1);
2731
- var path = __toESM(require("path"), 1);
2732
- 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);
2733
2899
  var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
2734
2900
  var BASE_CHAIN_ID2 = "8453";
2735
- var WALLET_DIR = path.join(os.homedir(), ".blockrun");
2736
- 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");
2737
2903
  function createWallet() {
2738
2904
  const privateKey = (0, import_accounts8.generatePrivateKey)();
2739
2905
  const account = (0, import_accounts8.privateKeyToAccount)(privateKey);
@@ -2743,27 +2909,27 @@ function createWallet() {
2743
2909
  };
2744
2910
  }
2745
2911
  function saveWallet(privateKey) {
2746
- if (!fs.existsSync(WALLET_DIR)) {
2747
- fs.mkdirSync(WALLET_DIR, { recursive: true });
2912
+ if (!fs2.existsSync(WALLET_DIR)) {
2913
+ fs2.mkdirSync(WALLET_DIR, { recursive: true });
2748
2914
  }
2749
- fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2915
+ fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
2750
2916
  return WALLET_FILE;
2751
2917
  }
2752
2918
  function scanWallets() {
2753
- const home = os.homedir();
2919
+ const home = os2.homedir();
2754
2920
  const results = [];
2755
2921
  try {
2756
- const entries = fs.readdirSync(home, { withFileTypes: true });
2922
+ const entries = fs2.readdirSync(home, { withFileTypes: true });
2757
2923
  for (const entry of entries) {
2758
2924
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2759
- const walletFile = path.join(home, entry.name, "wallet.json");
2760
- if (!fs.existsSync(walletFile)) continue;
2925
+ const walletFile = path2.join(home, entry.name, "wallet.json");
2926
+ if (!fs2.existsSync(walletFile)) continue;
2761
2927
  try {
2762
- const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
2928
+ const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
2763
2929
  const pk = data.privateKey || "";
2764
2930
  const addr = data.address || "";
2765
2931
  if (pk && addr) {
2766
- const mtime = fs.statSync(walletFile).mtimeMs;
2932
+ const mtime = fs2.statSync(walletFile).mtimeMs;
2767
2933
  results.push({ mtime, privateKey: pk, address: addr });
2768
2934
  }
2769
2935
  } catch {
@@ -2778,13 +2944,13 @@ function scanWallets() {
2778
2944
  function loadWallet() {
2779
2945
  const wallets = scanWallets();
2780
2946
  if (wallets.length > 0) return wallets[0].privateKey;
2781
- if (fs.existsSync(WALLET_FILE)) {
2782
- 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();
2783
2949
  if (key) return key;
2784
2950
  }
2785
- const legacyFile = path.join(WALLET_DIR, "wallet.key");
2786
- if (fs.existsSync(legacyFile)) {
2787
- 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();
2788
2954
  if (key) return key;
2789
2955
  }
2790
2956
  return null;
@@ -2879,11 +3045,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
2879
3045
  var WALLET_DIR_PATH = WALLET_DIR;
2880
3046
 
2881
3047
  // src/solana-wallet.ts
2882
- var fs2 = __toESM(require("fs"), 1);
2883
- var path2 = __toESM(require("path"), 1);
2884
- var os2 = __toESM(require("os"), 1);
2885
- var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
2886
- 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");
2887
3053
  async function createSolanaWallet() {
2888
3054
  const { Keypair } = await import("@solana/web3.js");
2889
3055
  const bs58 = await import("bs58");
@@ -2912,39 +3078,39 @@ async function solanaPublicKey(privateKey) {
2912
3078
  return Keypair.fromSecretKey(bytes).publicKey.toBase58();
2913
3079
  }
2914
3080
  function saveSolanaWallet(privateKey) {
2915
- if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
2916
- 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 });
2917
3083
  return SOLANA_WALLET_FILE;
2918
3084
  }
2919
3085
  function scanSolanaWallets() {
2920
- const home = os2.homedir();
3086
+ const home = os3.homedir();
2921
3087
  const results = [];
2922
3088
  try {
2923
- const entries = fs2.readdirSync(home, { withFileTypes: true });
3089
+ const entries = fs3.readdirSync(home, { withFileTypes: true });
2924
3090
  for (const entry of entries) {
2925
3091
  if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
2926
- const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
2927
- if (fs2.existsSync(solanaWalletFile)) {
3092
+ const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
3093
+ if (fs3.existsSync(solanaWalletFile)) {
2928
3094
  try {
2929
- const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
3095
+ const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
2930
3096
  const pk = data.privateKey || "";
2931
3097
  const addr = data.address || "";
2932
3098
  if (pk && addr) {
2933
- const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
3099
+ const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
2934
3100
  results.push({ mtime, secretKey: pk, publicKey: addr });
2935
3101
  }
2936
3102
  } catch {
2937
3103
  }
2938
3104
  }
2939
3105
  if (entry.name === ".brcc") {
2940
- const brccWalletFile = path2.join(home, entry.name, "wallet.json");
2941
- if (fs2.existsSync(brccWalletFile)) {
3106
+ const brccWalletFile = path3.join(home, entry.name, "wallet.json");
3107
+ if (fs3.existsSync(brccWalletFile)) {
2942
3108
  try {
2943
- const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
3109
+ const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
2944
3110
  const pk = data.privateKey || "";
2945
3111
  const addr = data.address || "";
2946
3112
  if (pk && addr) {
2947
- const mtime = fs2.statSync(brccWalletFile).mtimeMs;
3113
+ const mtime = fs3.statSync(brccWalletFile).mtimeMs;
2948
3114
  results.push({ mtime, secretKey: pk, publicKey: addr });
2949
3115
  }
2950
3116
  } catch {
@@ -2960,8 +3126,8 @@ function scanSolanaWallets() {
2960
3126
  function loadSolanaWallet() {
2961
3127
  const wallets = scanSolanaWallets();
2962
3128
  if (wallets.length > 0) return wallets[0].secretKey;
2963
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2964
- 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();
2965
3131
  if (key) return key;
2966
3132
  }
2967
3133
  return null;
@@ -2976,8 +3142,8 @@ async function getOrCreateSolanaWallet() {
2976
3142
  if (wallets.length > 0) {
2977
3143
  return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
2978
3144
  }
2979
- if (fs2.existsSync(SOLANA_WALLET_FILE)) {
2980
- 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();
2981
3147
  if (fileKey) {
2982
3148
  const address2 = await solanaPublicKey(fileKey);
2983
3149
  return { privateKey: fileKey, address: address2, isNew: false };
@@ -3551,13 +3717,13 @@ function solanaClient(options = {}) {
3551
3717
  }
3552
3718
 
3553
3719
  // src/cache.ts
3554
- var fs3 = __toESM(require("fs"), 1);
3555
- var path3 = __toESM(require("path"), 1);
3556
- 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);
3557
3723
  var crypto2 = __toESM(require("crypto"), 1);
3558
- var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
3559
- var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
3560
- 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");
3561
3727
  var DEFAULT_TTL = {
3562
3728
  "/v1/x/": 3600 * 1e3,
3563
3729
  "/v1/partner/": 3600 * 1e3,
@@ -3578,19 +3744,19 @@ function cacheKey(endpoint, body) {
3578
3744
  return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
3579
3745
  }
3580
3746
  function cachePath(key) {
3581
- return path3.join(CACHE_DIR, `${key}.json`);
3747
+ return path4.join(CACHE_DIR, `${key}.json`);
3582
3748
  }
3583
3749
  function getCached(key) {
3584
3750
  const filePath = cachePath(key);
3585
- if (!fs3.existsSync(filePath)) return null;
3751
+ if (!fs4.existsSync(filePath)) return null;
3586
3752
  try {
3587
- const raw = fs3.readFileSync(filePath, "utf-8");
3753
+ const raw = fs4.readFileSync(filePath, "utf-8");
3588
3754
  const entry = JSON.parse(raw);
3589
3755
  const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
3590
3756
  if (ttl <= 0) return null;
3591
3757
  if (Date.now() - entry.cachedAt > ttl) {
3592
3758
  try {
3593
- fs3.unlinkSync(filePath);
3759
+ fs4.unlinkSync(filePath);
3594
3760
  } catch {
3595
3761
  }
3596
3762
  return null;
@@ -3609,7 +3775,7 @@ function getCachedByRequest(endpoint, body) {
3609
3775
  function setCache(key, data, ttlMs) {
3610
3776
  if (ttlMs <= 0) return;
3611
3777
  try {
3612
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3778
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3613
3779
  } catch {
3614
3780
  }
3615
3781
  const entry = {
@@ -3618,7 +3784,7 @@ function setCache(key, data, ttlMs) {
3618
3784
  ttlMs
3619
3785
  };
3620
3786
  try {
3621
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3787
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3622
3788
  } catch {
3623
3789
  }
3624
3790
  }
@@ -3641,7 +3807,7 @@ function readableFilename(endpoint, body) {
3641
3807
  }
3642
3808
  function saveReadable(endpoint, body, response, costUsd) {
3643
3809
  try {
3644
- fs3.mkdirSync(DATA_DIR, { recursive: true });
3810
+ fs4.mkdirSync(DATA_DIR, { recursive: true });
3645
3811
  } catch {
3646
3812
  }
3647
3813
  const filename = readableFilename(endpoint, body);
@@ -3653,14 +3819,14 @@ function saveReadable(endpoint, body, response, costUsd) {
3653
3819
  response
3654
3820
  };
3655
3821
  try {
3656
- 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));
3657
3823
  } catch {
3658
3824
  }
3659
3825
  }
3660
3826
  function appendCostLog(endpoint, costUsd) {
3661
3827
  if (costUsd <= 0) return;
3662
3828
  try {
3663
- fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
3829
+ fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
3664
3830
  } catch {
3665
3831
  }
3666
3832
  const entry = {
@@ -3669,7 +3835,7 @@ function appendCostLog(endpoint, costUsd) {
3669
3835
  cost_usd: costUsd
3670
3836
  };
3671
3837
  try {
3672
- fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
3838
+ fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3673
3839
  } catch {
3674
3840
  }
3675
3841
  }
@@ -3677,7 +3843,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3677
3843
  const ttl = getTtl(endpoint);
3678
3844
  if (ttl > 0) {
3679
3845
  try {
3680
- fs3.mkdirSync(CACHE_DIR, { recursive: true });
3846
+ fs4.mkdirSync(CACHE_DIR, { recursive: true });
3681
3847
  } catch {
3682
3848
  }
3683
3849
  const key = cacheKey(endpoint, body);
@@ -3689,7 +3855,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3689
3855
  costUsd
3690
3856
  };
3691
3857
  try {
3692
- fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
3858
+ fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
3693
3859
  } catch {
3694
3860
  }
3695
3861
  }
@@ -3697,14 +3863,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
3697
3863
  appendCostLog(endpoint, costUsd);
3698
3864
  }
3699
3865
  function clearCache() {
3700
- if (!fs3.existsSync(CACHE_DIR)) return 0;
3866
+ if (!fs4.existsSync(CACHE_DIR)) return 0;
3701
3867
  let count = 0;
3702
3868
  try {
3703
- const files = fs3.readdirSync(CACHE_DIR);
3869
+ const files = fs4.readdirSync(CACHE_DIR);
3704
3870
  for (const file of files) {
3705
3871
  if (file.endsWith(".json")) {
3706
3872
  try {
3707
- fs3.unlinkSync(path3.join(CACHE_DIR, file));
3873
+ fs4.unlinkSync(path4.join(CACHE_DIR, file));
3708
3874
  count++;
3709
3875
  } catch {
3710
3876
  }
@@ -3715,14 +3881,14 @@ function clearCache() {
3715
3881
  return count;
3716
3882
  }
3717
3883
  function getCostLogSummary() {
3718
- if (!fs3.existsSync(COST_LOG_FILE)) {
3884
+ if (!fs4.existsSync(COST_LOG_FILE2)) {
3719
3885
  return { totalUsd: 0, calls: 0, byEndpoint: {} };
3720
3886
  }
3721
3887
  let totalUsd = 0;
3722
3888
  let calls = 0;
3723
3889
  const byEndpoint = {};
3724
3890
  try {
3725
- const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
3891
+ const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3726
3892
  if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
3727
3893
  for (const line of content.split("\n")) {
3728
3894
  if (!line) continue;
@@ -3777,47 +3943,6 @@ async function status() {
3777
3943
  return { address, balance };
3778
3944
  }
3779
3945
 
3780
- // src/cost-log.ts
3781
- var fs4 = __toESM(require("fs"), 1);
3782
- var path4 = __toESM(require("path"), 1);
3783
- var os4 = __toESM(require("os"), 1);
3784
- var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
3785
- var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
3786
- function logCost(entry) {
3787
- try {
3788
- fs4.mkdirSync(DATA_DIR2, { recursive: true });
3789
- } catch {
3790
- }
3791
- try {
3792
- fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
3793
- } catch {
3794
- }
3795
- }
3796
- function getCostSummary() {
3797
- if (!fs4.existsSync(COST_LOG_FILE2)) {
3798
- return { totalUsd: 0, calls: 0, byModel: {} };
3799
- }
3800
- let totalUsd = 0;
3801
- let calls = 0;
3802
- const byModel = {};
3803
- try {
3804
- const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
3805
- if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
3806
- for (const line of content.split("\n")) {
3807
- if (!line) continue;
3808
- try {
3809
- const entry = JSON.parse(line);
3810
- totalUsd += entry.costUsd;
3811
- calls += 1;
3812
- byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
3813
- } catch {
3814
- }
3815
- }
3816
- } catch {
3817
- }
3818
- return { totalUsd, calls, byModel };
3819
- }
3820
-
3821
3946
  // src/openai-compat.ts
3822
3947
  var StreamingResponse = class {
3823
3948
  reader;
@@ -4125,7 +4250,6 @@ var AnthropicClient = class {
4125
4250
  solanaKeyToBytes,
4126
4251
  solanaPublicKey,
4127
4252
  status,
4128
- testnetClient,
4129
4253
  validateMaxTokens,
4130
4254
  validateModel,
4131
4255
  validateTemperature,