@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/README.md +37 -64
- package/dist/index.cjs +338 -214
- package/dist/index.d.cts +97 -74
- package/dist/index.d.ts +97 -74
- package/dist/index.js +339 -214
- package/package.json +1 -1
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
|
-
|
|
590
|
-
|
|
591
|
-
|
|
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
|
-
*
|
|
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
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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
|
|
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
|
|
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
|
|
1389
|
-
const
|
|
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/
|
|
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
|
|
2731
|
-
var
|
|
2732
|
-
var
|
|
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 =
|
|
2736
|
-
var WALLET_FILE =
|
|
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 (!
|
|
2747
|
-
|
|
2912
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2913
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2748
2914
|
}
|
|
2749
|
-
|
|
2915
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2750
2916
|
return WALLET_FILE;
|
|
2751
2917
|
}
|
|
2752
2918
|
function scanWallets() {
|
|
2753
|
-
const home =
|
|
2919
|
+
const home = os2.homedir();
|
|
2754
2920
|
const results = [];
|
|
2755
2921
|
try {
|
|
2756
|
-
const entries =
|
|
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 =
|
|
2760
|
-
if (!
|
|
2925
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2926
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2761
2927
|
try {
|
|
2762
|
-
const data = JSON.parse(
|
|
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 =
|
|
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 (
|
|
2782
|
-
const key =
|
|
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 =
|
|
2786
|
-
if (
|
|
2787
|
-
const key =
|
|
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
|
|
2883
|
-
var
|
|
2884
|
-
var
|
|
2885
|
-
var WALLET_DIR2 =
|
|
2886
|
-
var SOLANA_WALLET_FILE =
|
|
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 (!
|
|
2916
|
-
|
|
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 =
|
|
3086
|
+
const home = os3.homedir();
|
|
2921
3087
|
const results = [];
|
|
2922
3088
|
try {
|
|
2923
|
-
const entries =
|
|
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 =
|
|
2927
|
-
if (
|
|
3092
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
3093
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2928
3094
|
try {
|
|
2929
|
-
const data = JSON.parse(
|
|
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 =
|
|
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 =
|
|
2941
|
-
if (
|
|
3106
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3107
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2942
3108
|
try {
|
|
2943
|
-
const data = JSON.parse(
|
|
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 =
|
|
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 (
|
|
2964
|
-
const key =
|
|
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 (
|
|
2980
|
-
const fileKey =
|
|
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
|
|
3555
|
-
var
|
|
3556
|
-
var
|
|
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 =
|
|
3559
|
-
var DATA_DIR =
|
|
3560
|
-
var
|
|
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
|
|
3747
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3582
3748
|
}
|
|
3583
3749
|
function getCached(key) {
|
|
3584
3750
|
const filePath = cachePath(key);
|
|
3585
|
-
if (!
|
|
3751
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3586
3752
|
try {
|
|
3587
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
3866
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3701
3867
|
let count = 0;
|
|
3702
3868
|
try {
|
|
3703
|
-
const files =
|
|
3869
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3704
3870
|
for (const file of files) {
|
|
3705
3871
|
if (file.endsWith(".json")) {
|
|
3706
3872
|
try {
|
|
3707
|
-
|
|
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 (!
|
|
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 =
|
|
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,
|