@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.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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
*
|
|
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
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
529
|
-
|
|
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
|
|
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
|
|
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
|
|
1293
|
-
const
|
|
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;
|
|
@@ -1622,19 +1787,7 @@ var LLMClient = class _LLMClient {
|
|
|
1622
1787
|
getWalletAddress() {
|
|
1623
1788
|
return this.account.address;
|
|
1624
1789
|
}
|
|
1625
|
-
/**
|
|
1626
|
-
* Check if client is configured for testnet.
|
|
1627
|
-
*/
|
|
1628
|
-
isTestnet() {
|
|
1629
|
-
return this.apiUrl.includes("testnet.blockrun.ai");
|
|
1630
|
-
}
|
|
1631
1790
|
};
|
|
1632
|
-
function testnetClient(options = {}) {
|
|
1633
|
-
return new LLMClient({
|
|
1634
|
-
...options,
|
|
1635
|
-
apiUrl: TESTNET_API_URL
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
1791
|
var client_default = LLMClient;
|
|
1639
1792
|
|
|
1640
1793
|
// src/image.ts
|
|
@@ -1723,10 +1876,15 @@ var ImageClient = class {
|
|
|
1723
1876
|
}
|
|
1724
1877
|
/**
|
|
1725
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.
|
|
1726
1884
|
*/
|
|
1727
1885
|
async listImageModels() {
|
|
1728
1886
|
const response = await this.fetchWithTimeout(
|
|
1729
|
-
`${this.apiUrl}/v1/
|
|
1887
|
+
`${this.apiUrl}/v1/models`,
|
|
1730
1888
|
{ method: "GET" }
|
|
1731
1889
|
);
|
|
1732
1890
|
if (!response.ok) {
|
|
@@ -1736,7 +1894,16 @@ var ImageClient = class {
|
|
|
1736
1894
|
);
|
|
1737
1895
|
}
|
|
1738
1896
|
const data = await response.json();
|
|
1739
|
-
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
|
+
}));
|
|
1740
1907
|
}
|
|
1741
1908
|
/**
|
|
1742
1909
|
* Make a request with automatic x402 payment handling.
|
|
@@ -2631,13 +2798,13 @@ function buildUrl(base, query) {
|
|
|
2631
2798
|
|
|
2632
2799
|
// src/wallet.ts
|
|
2633
2800
|
import { privateKeyToAccount as privateKeyToAccount7, generatePrivateKey } from "viem/accounts";
|
|
2634
|
-
import * as
|
|
2635
|
-
import * as
|
|
2636
|
-
import * as
|
|
2801
|
+
import * as fs2 from "fs";
|
|
2802
|
+
import * as path2 from "path";
|
|
2803
|
+
import * as os2 from "os";
|
|
2637
2804
|
var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2638
2805
|
var BASE_CHAIN_ID2 = "8453";
|
|
2639
|
-
var WALLET_DIR =
|
|
2640
|
-
var WALLET_FILE =
|
|
2806
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2807
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2641
2808
|
function createWallet() {
|
|
2642
2809
|
const privateKey = generatePrivateKey();
|
|
2643
2810
|
const account = privateKeyToAccount7(privateKey);
|
|
@@ -2647,27 +2814,27 @@ function createWallet() {
|
|
|
2647
2814
|
};
|
|
2648
2815
|
}
|
|
2649
2816
|
function saveWallet(privateKey) {
|
|
2650
|
-
if (!
|
|
2651
|
-
|
|
2817
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2818
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2652
2819
|
}
|
|
2653
|
-
|
|
2820
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2654
2821
|
return WALLET_FILE;
|
|
2655
2822
|
}
|
|
2656
2823
|
function scanWallets() {
|
|
2657
|
-
const home =
|
|
2824
|
+
const home = os2.homedir();
|
|
2658
2825
|
const results = [];
|
|
2659
2826
|
try {
|
|
2660
|
-
const entries =
|
|
2827
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
2661
2828
|
for (const entry of entries) {
|
|
2662
2829
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2663
|
-
const walletFile =
|
|
2664
|
-
if (!
|
|
2830
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2831
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2665
2832
|
try {
|
|
2666
|
-
const data = JSON.parse(
|
|
2833
|
+
const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
|
|
2667
2834
|
const pk = data.privateKey || "";
|
|
2668
2835
|
const addr = data.address || "";
|
|
2669
2836
|
if (pk && addr) {
|
|
2670
|
-
const mtime =
|
|
2837
|
+
const mtime = fs2.statSync(walletFile).mtimeMs;
|
|
2671
2838
|
results.push({ mtime, privateKey: pk, address: addr });
|
|
2672
2839
|
}
|
|
2673
2840
|
} catch {
|
|
@@ -2682,13 +2849,13 @@ function scanWallets() {
|
|
|
2682
2849
|
function loadWallet() {
|
|
2683
2850
|
const wallets = scanWallets();
|
|
2684
2851
|
if (wallets.length > 0) return wallets[0].privateKey;
|
|
2685
|
-
if (
|
|
2686
|
-
const key =
|
|
2852
|
+
if (fs2.existsSync(WALLET_FILE)) {
|
|
2853
|
+
const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
2687
2854
|
if (key) return key;
|
|
2688
2855
|
}
|
|
2689
|
-
const legacyFile =
|
|
2690
|
-
if (
|
|
2691
|
-
const key =
|
|
2856
|
+
const legacyFile = path2.join(WALLET_DIR, "wallet.key");
|
|
2857
|
+
if (fs2.existsSync(legacyFile)) {
|
|
2858
|
+
const key = fs2.readFileSync(legacyFile, "utf-8").trim();
|
|
2692
2859
|
if (key) return key;
|
|
2693
2860
|
}
|
|
2694
2861
|
return null;
|
|
@@ -2783,11 +2950,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
|
|
|
2783
2950
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
2784
2951
|
|
|
2785
2952
|
// src/solana-wallet.ts
|
|
2786
|
-
import * as
|
|
2787
|
-
import * as
|
|
2788
|
-
import * as
|
|
2789
|
-
var WALLET_DIR2 =
|
|
2790
|
-
var SOLANA_WALLET_FILE =
|
|
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");
|
|
2791
2958
|
async function createSolanaWallet() {
|
|
2792
2959
|
const { Keypair } = await import("@solana/web3.js");
|
|
2793
2960
|
const bs58 = await import("bs58");
|
|
@@ -2816,39 +2983,39 @@ async function solanaPublicKey(privateKey) {
|
|
|
2816
2983
|
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
2817
2984
|
}
|
|
2818
2985
|
function saveSolanaWallet(privateKey) {
|
|
2819
|
-
if (!
|
|
2820
|
-
|
|
2986
|
+
if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
2987
|
+
fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
2821
2988
|
return SOLANA_WALLET_FILE;
|
|
2822
2989
|
}
|
|
2823
2990
|
function scanSolanaWallets() {
|
|
2824
|
-
const home =
|
|
2991
|
+
const home = os3.homedir();
|
|
2825
2992
|
const results = [];
|
|
2826
2993
|
try {
|
|
2827
|
-
const entries =
|
|
2994
|
+
const entries = fs3.readdirSync(home, { withFileTypes: true });
|
|
2828
2995
|
for (const entry of entries) {
|
|
2829
2996
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2830
|
-
const solanaWalletFile =
|
|
2831
|
-
if (
|
|
2997
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
2998
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2832
2999
|
try {
|
|
2833
|
-
const data = JSON.parse(
|
|
3000
|
+
const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
|
|
2834
3001
|
const pk = data.privateKey || "";
|
|
2835
3002
|
const addr = data.address || "";
|
|
2836
3003
|
if (pk && addr) {
|
|
2837
|
-
const mtime =
|
|
3004
|
+
const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
|
|
2838
3005
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2839
3006
|
}
|
|
2840
3007
|
} catch {
|
|
2841
3008
|
}
|
|
2842
3009
|
}
|
|
2843
3010
|
if (entry.name === ".brcc") {
|
|
2844
|
-
const brccWalletFile =
|
|
2845
|
-
if (
|
|
3011
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3012
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2846
3013
|
try {
|
|
2847
|
-
const data = JSON.parse(
|
|
3014
|
+
const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
|
|
2848
3015
|
const pk = data.privateKey || "";
|
|
2849
3016
|
const addr = data.address || "";
|
|
2850
3017
|
if (pk && addr) {
|
|
2851
|
-
const mtime =
|
|
3018
|
+
const mtime = fs3.statSync(brccWalletFile).mtimeMs;
|
|
2852
3019
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2853
3020
|
}
|
|
2854
3021
|
} catch {
|
|
@@ -2864,8 +3031,8 @@ function scanSolanaWallets() {
|
|
|
2864
3031
|
function loadSolanaWallet() {
|
|
2865
3032
|
const wallets = scanSolanaWallets();
|
|
2866
3033
|
if (wallets.length > 0) return wallets[0].secretKey;
|
|
2867
|
-
if (
|
|
2868
|
-
const key =
|
|
3034
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3035
|
+
const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2869
3036
|
if (key) return key;
|
|
2870
3037
|
}
|
|
2871
3038
|
return null;
|
|
@@ -2880,8 +3047,8 @@ async function getOrCreateSolanaWallet() {
|
|
|
2880
3047
|
if (wallets.length > 0) {
|
|
2881
3048
|
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
2882
3049
|
}
|
|
2883
|
-
if (
|
|
2884
|
-
const fileKey =
|
|
3050
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3051
|
+
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2885
3052
|
if (fileKey) {
|
|
2886
3053
|
const address2 = await solanaPublicKey(fileKey);
|
|
2887
3054
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
@@ -3455,13 +3622,13 @@ function solanaClient(options = {}) {
|
|
|
3455
3622
|
}
|
|
3456
3623
|
|
|
3457
3624
|
// src/cache.ts
|
|
3458
|
-
import * as
|
|
3459
|
-
import * as
|
|
3460
|
-
import * as
|
|
3625
|
+
import * as fs4 from "fs";
|
|
3626
|
+
import * as path4 from "path";
|
|
3627
|
+
import * as os4 from "os";
|
|
3461
3628
|
import * as crypto2 from "crypto";
|
|
3462
|
-
var CACHE_DIR =
|
|
3463
|
-
var DATA_DIR =
|
|
3464
|
-
var
|
|
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");
|
|
3465
3632
|
var DEFAULT_TTL = {
|
|
3466
3633
|
"/v1/x/": 3600 * 1e3,
|
|
3467
3634
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -3482,19 +3649,19 @@ function cacheKey(endpoint, body) {
|
|
|
3482
3649
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
3483
3650
|
}
|
|
3484
3651
|
function cachePath(key) {
|
|
3485
|
-
return
|
|
3652
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3486
3653
|
}
|
|
3487
3654
|
function getCached(key) {
|
|
3488
3655
|
const filePath = cachePath(key);
|
|
3489
|
-
if (!
|
|
3656
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3490
3657
|
try {
|
|
3491
|
-
const raw =
|
|
3658
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3492
3659
|
const entry = JSON.parse(raw);
|
|
3493
3660
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
3494
3661
|
if (ttl <= 0) return null;
|
|
3495
3662
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
3496
3663
|
try {
|
|
3497
|
-
|
|
3664
|
+
fs4.unlinkSync(filePath);
|
|
3498
3665
|
} catch {
|
|
3499
3666
|
}
|
|
3500
3667
|
return null;
|
|
@@ -3513,7 +3680,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
3513
3680
|
function setCache(key, data, ttlMs) {
|
|
3514
3681
|
if (ttlMs <= 0) return;
|
|
3515
3682
|
try {
|
|
3516
|
-
|
|
3683
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3517
3684
|
} catch {
|
|
3518
3685
|
}
|
|
3519
3686
|
const entry = {
|
|
@@ -3522,7 +3689,7 @@ function setCache(key, data, ttlMs) {
|
|
|
3522
3689
|
ttlMs
|
|
3523
3690
|
};
|
|
3524
3691
|
try {
|
|
3525
|
-
|
|
3692
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3526
3693
|
} catch {
|
|
3527
3694
|
}
|
|
3528
3695
|
}
|
|
@@ -3545,7 +3712,7 @@ function readableFilename(endpoint, body) {
|
|
|
3545
3712
|
}
|
|
3546
3713
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
3547
3714
|
try {
|
|
3548
|
-
|
|
3715
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
3549
3716
|
} catch {
|
|
3550
3717
|
}
|
|
3551
3718
|
const filename = readableFilename(endpoint, body);
|
|
@@ -3557,14 +3724,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
3557
3724
|
response
|
|
3558
3725
|
};
|
|
3559
3726
|
try {
|
|
3560
|
-
|
|
3727
|
+
fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
3561
3728
|
} catch {
|
|
3562
3729
|
}
|
|
3563
3730
|
}
|
|
3564
3731
|
function appendCostLog(endpoint, costUsd) {
|
|
3565
3732
|
if (costUsd <= 0) return;
|
|
3566
3733
|
try {
|
|
3567
|
-
|
|
3734
|
+
fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
|
|
3568
3735
|
} catch {
|
|
3569
3736
|
}
|
|
3570
3737
|
const entry = {
|
|
@@ -3573,7 +3740,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
3573
3740
|
cost_usd: costUsd
|
|
3574
3741
|
};
|
|
3575
3742
|
try {
|
|
3576
|
-
|
|
3743
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3577
3744
|
} catch {
|
|
3578
3745
|
}
|
|
3579
3746
|
}
|
|
@@ -3581,7 +3748,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3581
3748
|
const ttl = getTtl(endpoint);
|
|
3582
3749
|
if (ttl > 0) {
|
|
3583
3750
|
try {
|
|
3584
|
-
|
|
3751
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3585
3752
|
} catch {
|
|
3586
3753
|
}
|
|
3587
3754
|
const key = cacheKey(endpoint, body);
|
|
@@ -3593,7 +3760,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3593
3760
|
costUsd
|
|
3594
3761
|
};
|
|
3595
3762
|
try {
|
|
3596
|
-
|
|
3763
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3597
3764
|
} catch {
|
|
3598
3765
|
}
|
|
3599
3766
|
}
|
|
@@ -3601,14 +3768,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3601
3768
|
appendCostLog(endpoint, costUsd);
|
|
3602
3769
|
}
|
|
3603
3770
|
function clearCache() {
|
|
3604
|
-
if (!
|
|
3771
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3605
3772
|
let count = 0;
|
|
3606
3773
|
try {
|
|
3607
|
-
const files =
|
|
3774
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3608
3775
|
for (const file of files) {
|
|
3609
3776
|
if (file.endsWith(".json")) {
|
|
3610
3777
|
try {
|
|
3611
|
-
|
|
3778
|
+
fs4.unlinkSync(path4.join(CACHE_DIR, file));
|
|
3612
3779
|
count++;
|
|
3613
3780
|
} catch {
|
|
3614
3781
|
}
|
|
@@ -3619,14 +3786,14 @@ function clearCache() {
|
|
|
3619
3786
|
return count;
|
|
3620
3787
|
}
|
|
3621
3788
|
function getCostLogSummary() {
|
|
3622
|
-
if (!
|
|
3789
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3623
3790
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3624
3791
|
}
|
|
3625
3792
|
let totalUsd = 0;
|
|
3626
3793
|
let calls = 0;
|
|
3627
3794
|
const byEndpoint = {};
|
|
3628
3795
|
try {
|
|
3629
|
-
const content =
|
|
3796
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3630
3797
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3631
3798
|
for (const line of content.split("\n")) {
|
|
3632
3799
|
if (!line) continue;
|
|
@@ -3681,47 +3848,6 @@ async function status() {
|
|
|
3681
3848
|
return { address, balance };
|
|
3682
3849
|
}
|
|
3683
3850
|
|
|
3684
|
-
// src/cost-log.ts
|
|
3685
|
-
import * as fs4 from "fs";
|
|
3686
|
-
import * as path4 from "path";
|
|
3687
|
-
import * as os4 from "os";
|
|
3688
|
-
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3689
|
-
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
3690
|
-
function logCost(entry) {
|
|
3691
|
-
try {
|
|
3692
|
-
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
3693
|
-
} catch {
|
|
3694
|
-
}
|
|
3695
|
-
try {
|
|
3696
|
-
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3697
|
-
} catch {
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
function getCostSummary() {
|
|
3701
|
-
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3702
|
-
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3703
|
-
}
|
|
3704
|
-
let totalUsd = 0;
|
|
3705
|
-
let calls = 0;
|
|
3706
|
-
const byModel = {};
|
|
3707
|
-
try {
|
|
3708
|
-
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3709
|
-
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3710
|
-
for (const line of content.split("\n")) {
|
|
3711
|
-
if (!line) continue;
|
|
3712
|
-
try {
|
|
3713
|
-
const entry = JSON.parse(line);
|
|
3714
|
-
totalUsd += entry.costUsd;
|
|
3715
|
-
calls += 1;
|
|
3716
|
-
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
3717
|
-
} catch {
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
} catch {
|
|
3721
|
-
}
|
|
3722
|
-
return { totalUsd, calls, byModel };
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
3851
|
// src/openai-compat.ts
|
|
3726
3852
|
var StreamingResponse = class {
|
|
3727
3853
|
reader;
|
|
@@ -4029,7 +4155,6 @@ export {
|
|
|
4029
4155
|
solanaKeyToBytes,
|
|
4030
4156
|
solanaPublicKey,
|
|
4031
4157
|
status,
|
|
4032
|
-
testnetClient,
|
|
4033
4158
|
validateMaxTokens,
|
|
4034
4159
|
validateModel,
|
|
4035
4160
|
validateTemperature,
|