@blockrun/llm 1.13.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -81
- package/dist/index.cjs +402 -218
- package/dist/index.d.cts +141 -77
- package/dist/index.d.ts +141 -77
- package/dist/index.js +403 -218
- 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;
|
|
@@ -1537,17 +1702,70 @@ var LLMClient = class _LLMClient {
|
|
|
1537
1702
|
/**
|
|
1538
1703
|
* Structured query for Predexon prediction market data (POST endpoints).
|
|
1539
1704
|
*
|
|
1540
|
-
* For
|
|
1705
|
+
* For endpoints that require a JSON body, e.g. bulk wallet identity lookup.
|
|
1706
|
+
* Tier 1 = $0.001/call, Tier 2 = $0.005/call.
|
|
1541
1707
|
*
|
|
1542
|
-
* @param path - Endpoint path, e.g. "polymarket/
|
|
1708
|
+
* @param path - Endpoint path, e.g. "polymarket/wallet/identities"
|
|
1543
1709
|
* @param query - JSON body for the structured query
|
|
1544
1710
|
*
|
|
1545
1711
|
* @example
|
|
1546
|
-
* const
|
|
1712
|
+
* const batch = await client.pmQuery("polymarket/wallet/identities", {
|
|
1713
|
+
* addresses: ["0xabc...", "0xdef..."],
|
|
1714
|
+
* });
|
|
1547
1715
|
*/
|
|
1548
1716
|
async pmQuery(path5, query) {
|
|
1549
1717
|
return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
|
|
1550
1718
|
}
|
|
1719
|
+
// ── PM convenience helpers (Predexon v2) ─────────────────────────────────
|
|
1720
|
+
// Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
|
|
1721
|
+
/** List canonical cross-venue markets (Predexon v2). Tier 1 ($0.001/call).
|
|
1722
|
+
* Filter with venue, status, category, league, event_id, pagination_key. */
|
|
1723
|
+
async pmMarkets(params) {
|
|
1724
|
+
return this.pm("markets", params);
|
|
1725
|
+
}
|
|
1726
|
+
/** List venue-native executable listings flattened across canonical markets
|
|
1727
|
+
* (Predexon v2). Tier 1 ($0.001/call). */
|
|
1728
|
+
async pmListings(params) {
|
|
1729
|
+
return this.pm("markets/listings", params);
|
|
1730
|
+
}
|
|
1731
|
+
/** Resolve a canonical Predexon outcome ID to its market context and venue
|
|
1732
|
+
* listings. Tier 1 ($0.001/call). */
|
|
1733
|
+
async pmOutcome(predexonId) {
|
|
1734
|
+
return this.pm(`outcomes/${predexonId}`);
|
|
1735
|
+
}
|
|
1736
|
+
/** Polymarket markets with cursor-based keyset pagination (use pagination_key).
|
|
1737
|
+
* Tier 1 ($0.001/call). */
|
|
1738
|
+
async pmPolymarketMarketsKeyset(params) {
|
|
1739
|
+
return this.pm("polymarket/markets/keyset", params);
|
|
1740
|
+
}
|
|
1741
|
+
/** Polymarket events with cursor-based keyset pagination (use pagination_key).
|
|
1742
|
+
* Tier 1 ($0.001/call). */
|
|
1743
|
+
async pmPolymarketEventsKeyset(params) {
|
|
1744
|
+
return this.pm("polymarket/events/keyset", params);
|
|
1745
|
+
}
|
|
1746
|
+
/** List available sports categories. Tier 1 ($0.001/call). */
|
|
1747
|
+
async pmSportsCategories() {
|
|
1748
|
+
return this.pm("sports/categories");
|
|
1749
|
+
}
|
|
1750
|
+
/** List sports markets grouped by game. Filter with league, sport_type,
|
|
1751
|
+
* status, venue. Tier 1 ($0.001/call). */
|
|
1752
|
+
async pmSportsMarkets(params) {
|
|
1753
|
+
return this.pm("sports/markets", params);
|
|
1754
|
+
}
|
|
1755
|
+
/** Fetch identity + profile metadata for one wallet (ENS, Twitter, portfolio,
|
|
1756
|
+
* etc.). Tier 2 ($0.005/call). */
|
|
1757
|
+
async pmWalletIdentity(wallet) {
|
|
1758
|
+
return this.pm(`polymarket/wallet/identity/${wallet}`);
|
|
1759
|
+
}
|
|
1760
|
+
/** Bulk identity lookup for up to 200 wallet addresses (POST). Tier 2 ($0.005/call). */
|
|
1761
|
+
async pmWalletIdentities(addresses) {
|
|
1762
|
+
return this.pmQuery("polymarket/wallet/identities", { addresses });
|
|
1763
|
+
}
|
|
1764
|
+
/** Discover wallets connected to a seed address via on-chain transfers and
|
|
1765
|
+
* identity proofs. Tier 2 ($0.005/call). */
|
|
1766
|
+
async pmWalletCluster(address) {
|
|
1767
|
+
return this.pm(`polymarket/wallet/${address}/cluster`);
|
|
1768
|
+
}
|
|
1551
1769
|
/**
|
|
1552
1770
|
* Get current session spending.
|
|
1553
1771
|
*
|
|
@@ -1569,19 +1787,7 @@ var LLMClient = class _LLMClient {
|
|
|
1569
1787
|
getWalletAddress() {
|
|
1570
1788
|
return this.account.address;
|
|
1571
1789
|
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Check if client is configured for testnet.
|
|
1574
|
-
*/
|
|
1575
|
-
isTestnet() {
|
|
1576
|
-
return this.apiUrl.includes("testnet.blockrun.ai");
|
|
1577
|
-
}
|
|
1578
1790
|
};
|
|
1579
|
-
function testnetClient(options = {}) {
|
|
1580
|
-
return new LLMClient({
|
|
1581
|
-
...options,
|
|
1582
|
-
apiUrl: TESTNET_API_URL
|
|
1583
|
-
});
|
|
1584
|
-
}
|
|
1585
1791
|
var client_default = LLMClient;
|
|
1586
1792
|
|
|
1587
1793
|
// src/image.ts
|
|
@@ -1670,10 +1876,15 @@ var ImageClient = class {
|
|
|
1670
1876
|
}
|
|
1671
1877
|
/**
|
|
1672
1878
|
* List available image generation models with pricing.
|
|
1879
|
+
*
|
|
1880
|
+
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
|
|
1881
|
+
* image models live in the unified `/v1/models` catalog under
|
|
1882
|
+
* `categories: ["image", ...]`. This method filters that catalog so
|
|
1883
|
+
* existing callers keep working.
|
|
1673
1884
|
*/
|
|
1674
1885
|
async listImageModels() {
|
|
1675
1886
|
const response = await this.fetchWithTimeout(
|
|
1676
|
-
`${this.apiUrl}/v1/
|
|
1887
|
+
`${this.apiUrl}/v1/models`,
|
|
1677
1888
|
{ method: "GET" }
|
|
1678
1889
|
);
|
|
1679
1890
|
if (!response.ok) {
|
|
@@ -1683,7 +1894,16 @@ var ImageClient = class {
|
|
|
1683
1894
|
);
|
|
1684
1895
|
}
|
|
1685
1896
|
const data = await response.json();
|
|
1686
|
-
return data.data || []
|
|
1897
|
+
return (data.data || []).filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => ({
|
|
1898
|
+
id: m.id,
|
|
1899
|
+
name: m.name || m.id,
|
|
1900
|
+
provider: m.provider || m.owned_by || "",
|
|
1901
|
+
description: m.description || "",
|
|
1902
|
+
pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
|
|
1903
|
+
supportedSizes: m.supportedSizes ?? m.supported_sizes,
|
|
1904
|
+
maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
|
|
1905
|
+
available: true
|
|
1906
|
+
}));
|
|
1687
1907
|
}
|
|
1688
1908
|
/**
|
|
1689
1909
|
* Make a request with automatic x402 payment handling.
|
|
@@ -2169,12 +2389,19 @@ var SearchClient = class {
|
|
|
2169
2389
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
2170
2390
|
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2171
2391
|
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2172
|
-
var XClient = class {
|
|
2392
|
+
var XClient = class _XClient {
|
|
2173
2393
|
account;
|
|
2174
2394
|
privateKey;
|
|
2175
2395
|
apiUrl;
|
|
2176
2396
|
timeout;
|
|
2397
|
+
static deprecationWarned = false;
|
|
2177
2398
|
constructor(options = {}) {
|
|
2399
|
+
if (!_XClient.deprecationWarned) {
|
|
2400
|
+
console.warn(
|
|
2401
|
+
"[@blockrun/llm] XClient: BlockRun's /v1/x/* (AttentionVC) integration was removed 2026-04-30. All calls will return HTTP 404 until a replacement X data upstream is reintroduced."
|
|
2402
|
+
);
|
|
2403
|
+
_XClient.deprecationWarned = true;
|
|
2404
|
+
}
|
|
2178
2405
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2179
2406
|
const privateKey = options.privateKey || envKey;
|
|
2180
2407
|
if (!privateKey) {
|
|
@@ -2571,13 +2798,13 @@ function buildUrl(base, query) {
|
|
|
2571
2798
|
|
|
2572
2799
|
// src/wallet.ts
|
|
2573
2800
|
import { privateKeyToAccount as privateKeyToAccount7, generatePrivateKey } from "viem/accounts";
|
|
2574
|
-
import * as
|
|
2575
|
-
import * as
|
|
2576
|
-
import * as
|
|
2801
|
+
import * as fs2 from "fs";
|
|
2802
|
+
import * as path2 from "path";
|
|
2803
|
+
import * as os2 from "os";
|
|
2577
2804
|
var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2578
2805
|
var BASE_CHAIN_ID2 = "8453";
|
|
2579
|
-
var WALLET_DIR =
|
|
2580
|
-
var WALLET_FILE =
|
|
2806
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2807
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2581
2808
|
function createWallet() {
|
|
2582
2809
|
const privateKey = generatePrivateKey();
|
|
2583
2810
|
const account = privateKeyToAccount7(privateKey);
|
|
@@ -2587,27 +2814,27 @@ function createWallet() {
|
|
|
2587
2814
|
};
|
|
2588
2815
|
}
|
|
2589
2816
|
function saveWallet(privateKey) {
|
|
2590
|
-
if (!
|
|
2591
|
-
|
|
2817
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2818
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2592
2819
|
}
|
|
2593
|
-
|
|
2820
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2594
2821
|
return WALLET_FILE;
|
|
2595
2822
|
}
|
|
2596
2823
|
function scanWallets() {
|
|
2597
|
-
const home =
|
|
2824
|
+
const home = os2.homedir();
|
|
2598
2825
|
const results = [];
|
|
2599
2826
|
try {
|
|
2600
|
-
const entries =
|
|
2827
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
2601
2828
|
for (const entry of entries) {
|
|
2602
2829
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2603
|
-
const walletFile =
|
|
2604
|
-
if (!
|
|
2830
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2831
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2605
2832
|
try {
|
|
2606
|
-
const data = JSON.parse(
|
|
2833
|
+
const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
|
|
2607
2834
|
const pk = data.privateKey || "";
|
|
2608
2835
|
const addr = data.address || "";
|
|
2609
2836
|
if (pk && addr) {
|
|
2610
|
-
const mtime =
|
|
2837
|
+
const mtime = fs2.statSync(walletFile).mtimeMs;
|
|
2611
2838
|
results.push({ mtime, privateKey: pk, address: addr });
|
|
2612
2839
|
}
|
|
2613
2840
|
} catch {
|
|
@@ -2622,13 +2849,13 @@ function scanWallets() {
|
|
|
2622
2849
|
function loadWallet() {
|
|
2623
2850
|
const wallets = scanWallets();
|
|
2624
2851
|
if (wallets.length > 0) return wallets[0].privateKey;
|
|
2625
|
-
if (
|
|
2626
|
-
const key =
|
|
2852
|
+
if (fs2.existsSync(WALLET_FILE)) {
|
|
2853
|
+
const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
2627
2854
|
if (key) return key;
|
|
2628
2855
|
}
|
|
2629
|
-
const legacyFile =
|
|
2630
|
-
if (
|
|
2631
|
-
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();
|
|
2632
2859
|
if (key) return key;
|
|
2633
2860
|
}
|
|
2634
2861
|
return null;
|
|
@@ -2723,11 +2950,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
|
|
|
2723
2950
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
2724
2951
|
|
|
2725
2952
|
// src/solana-wallet.ts
|
|
2726
|
-
import * as
|
|
2727
|
-
import * as
|
|
2728
|
-
import * as
|
|
2729
|
-
var WALLET_DIR2 =
|
|
2730
|
-
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");
|
|
2731
2958
|
async function createSolanaWallet() {
|
|
2732
2959
|
const { Keypair } = await import("@solana/web3.js");
|
|
2733
2960
|
const bs58 = await import("bs58");
|
|
@@ -2756,39 +2983,39 @@ async function solanaPublicKey(privateKey) {
|
|
|
2756
2983
|
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
2757
2984
|
}
|
|
2758
2985
|
function saveSolanaWallet(privateKey) {
|
|
2759
|
-
if (!
|
|
2760
|
-
|
|
2986
|
+
if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
2987
|
+
fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
2761
2988
|
return SOLANA_WALLET_FILE;
|
|
2762
2989
|
}
|
|
2763
2990
|
function scanSolanaWallets() {
|
|
2764
|
-
const home =
|
|
2991
|
+
const home = os3.homedir();
|
|
2765
2992
|
const results = [];
|
|
2766
2993
|
try {
|
|
2767
|
-
const entries =
|
|
2994
|
+
const entries = fs3.readdirSync(home, { withFileTypes: true });
|
|
2768
2995
|
for (const entry of entries) {
|
|
2769
2996
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2770
|
-
const solanaWalletFile =
|
|
2771
|
-
if (
|
|
2997
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
2998
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2772
2999
|
try {
|
|
2773
|
-
const data = JSON.parse(
|
|
3000
|
+
const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
|
|
2774
3001
|
const pk = data.privateKey || "";
|
|
2775
3002
|
const addr = data.address || "";
|
|
2776
3003
|
if (pk && addr) {
|
|
2777
|
-
const mtime =
|
|
3004
|
+
const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
|
|
2778
3005
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2779
3006
|
}
|
|
2780
3007
|
} catch {
|
|
2781
3008
|
}
|
|
2782
3009
|
}
|
|
2783
3010
|
if (entry.name === ".brcc") {
|
|
2784
|
-
const brccWalletFile =
|
|
2785
|
-
if (
|
|
3011
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3012
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2786
3013
|
try {
|
|
2787
|
-
const data = JSON.parse(
|
|
3014
|
+
const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
|
|
2788
3015
|
const pk = data.privateKey || "";
|
|
2789
3016
|
const addr = data.address || "";
|
|
2790
3017
|
if (pk && addr) {
|
|
2791
|
-
const mtime =
|
|
3018
|
+
const mtime = fs3.statSync(brccWalletFile).mtimeMs;
|
|
2792
3019
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2793
3020
|
}
|
|
2794
3021
|
} catch {
|
|
@@ -2804,8 +3031,8 @@ function scanSolanaWallets() {
|
|
|
2804
3031
|
function loadSolanaWallet() {
|
|
2805
3032
|
const wallets = scanSolanaWallets();
|
|
2806
3033
|
if (wallets.length > 0) return wallets[0].secretKey;
|
|
2807
|
-
if (
|
|
2808
|
-
const key =
|
|
3034
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3035
|
+
const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2809
3036
|
if (key) return key;
|
|
2810
3037
|
}
|
|
2811
3038
|
return null;
|
|
@@ -2820,8 +3047,8 @@ async function getOrCreateSolanaWallet() {
|
|
|
2820
3047
|
if (wallets.length > 0) {
|
|
2821
3048
|
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
2822
3049
|
}
|
|
2823
|
-
if (
|
|
2824
|
-
const fileKey =
|
|
3050
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3051
|
+
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2825
3052
|
if (fileKey) {
|
|
2826
3053
|
const address2 = await solanaPublicKey(fileKey);
|
|
2827
3054
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
@@ -3395,13 +3622,13 @@ function solanaClient(options = {}) {
|
|
|
3395
3622
|
}
|
|
3396
3623
|
|
|
3397
3624
|
// src/cache.ts
|
|
3398
|
-
import * as
|
|
3399
|
-
import * as
|
|
3400
|
-
import * as
|
|
3625
|
+
import * as fs4 from "fs";
|
|
3626
|
+
import * as path4 from "path";
|
|
3627
|
+
import * as os4 from "os";
|
|
3401
3628
|
import * as crypto2 from "crypto";
|
|
3402
|
-
var CACHE_DIR =
|
|
3403
|
-
var DATA_DIR =
|
|
3404
|
-
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");
|
|
3405
3632
|
var DEFAULT_TTL = {
|
|
3406
3633
|
"/v1/x/": 3600 * 1e3,
|
|
3407
3634
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -3422,19 +3649,19 @@ function cacheKey(endpoint, body) {
|
|
|
3422
3649
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
3423
3650
|
}
|
|
3424
3651
|
function cachePath(key) {
|
|
3425
|
-
return
|
|
3652
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3426
3653
|
}
|
|
3427
3654
|
function getCached(key) {
|
|
3428
3655
|
const filePath = cachePath(key);
|
|
3429
|
-
if (!
|
|
3656
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3430
3657
|
try {
|
|
3431
|
-
const raw =
|
|
3658
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3432
3659
|
const entry = JSON.parse(raw);
|
|
3433
3660
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
3434
3661
|
if (ttl <= 0) return null;
|
|
3435
3662
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
3436
3663
|
try {
|
|
3437
|
-
|
|
3664
|
+
fs4.unlinkSync(filePath);
|
|
3438
3665
|
} catch {
|
|
3439
3666
|
}
|
|
3440
3667
|
return null;
|
|
@@ -3453,7 +3680,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
3453
3680
|
function setCache(key, data, ttlMs) {
|
|
3454
3681
|
if (ttlMs <= 0) return;
|
|
3455
3682
|
try {
|
|
3456
|
-
|
|
3683
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3457
3684
|
} catch {
|
|
3458
3685
|
}
|
|
3459
3686
|
const entry = {
|
|
@@ -3462,7 +3689,7 @@ function setCache(key, data, ttlMs) {
|
|
|
3462
3689
|
ttlMs
|
|
3463
3690
|
};
|
|
3464
3691
|
try {
|
|
3465
|
-
|
|
3692
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3466
3693
|
} catch {
|
|
3467
3694
|
}
|
|
3468
3695
|
}
|
|
@@ -3485,7 +3712,7 @@ function readableFilename(endpoint, body) {
|
|
|
3485
3712
|
}
|
|
3486
3713
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
3487
3714
|
try {
|
|
3488
|
-
|
|
3715
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
3489
3716
|
} catch {
|
|
3490
3717
|
}
|
|
3491
3718
|
const filename = readableFilename(endpoint, body);
|
|
@@ -3497,14 +3724,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
3497
3724
|
response
|
|
3498
3725
|
};
|
|
3499
3726
|
try {
|
|
3500
|
-
|
|
3727
|
+
fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
3501
3728
|
} catch {
|
|
3502
3729
|
}
|
|
3503
3730
|
}
|
|
3504
3731
|
function appendCostLog(endpoint, costUsd) {
|
|
3505
3732
|
if (costUsd <= 0) return;
|
|
3506
3733
|
try {
|
|
3507
|
-
|
|
3734
|
+
fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
|
|
3508
3735
|
} catch {
|
|
3509
3736
|
}
|
|
3510
3737
|
const entry = {
|
|
@@ -3513,7 +3740,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
3513
3740
|
cost_usd: costUsd
|
|
3514
3741
|
};
|
|
3515
3742
|
try {
|
|
3516
|
-
|
|
3743
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3517
3744
|
} catch {
|
|
3518
3745
|
}
|
|
3519
3746
|
}
|
|
@@ -3521,7 +3748,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3521
3748
|
const ttl = getTtl(endpoint);
|
|
3522
3749
|
if (ttl > 0) {
|
|
3523
3750
|
try {
|
|
3524
|
-
|
|
3751
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3525
3752
|
} catch {
|
|
3526
3753
|
}
|
|
3527
3754
|
const key = cacheKey(endpoint, body);
|
|
@@ -3533,7 +3760,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3533
3760
|
costUsd
|
|
3534
3761
|
};
|
|
3535
3762
|
try {
|
|
3536
|
-
|
|
3763
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3537
3764
|
} catch {
|
|
3538
3765
|
}
|
|
3539
3766
|
}
|
|
@@ -3541,14 +3768,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3541
3768
|
appendCostLog(endpoint, costUsd);
|
|
3542
3769
|
}
|
|
3543
3770
|
function clearCache() {
|
|
3544
|
-
if (!
|
|
3771
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3545
3772
|
let count = 0;
|
|
3546
3773
|
try {
|
|
3547
|
-
const files =
|
|
3774
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3548
3775
|
for (const file of files) {
|
|
3549
3776
|
if (file.endsWith(".json")) {
|
|
3550
3777
|
try {
|
|
3551
|
-
|
|
3778
|
+
fs4.unlinkSync(path4.join(CACHE_DIR, file));
|
|
3552
3779
|
count++;
|
|
3553
3780
|
} catch {
|
|
3554
3781
|
}
|
|
@@ -3559,14 +3786,14 @@ function clearCache() {
|
|
|
3559
3786
|
return count;
|
|
3560
3787
|
}
|
|
3561
3788
|
function getCostLogSummary() {
|
|
3562
|
-
if (!
|
|
3789
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3563
3790
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3564
3791
|
}
|
|
3565
3792
|
let totalUsd = 0;
|
|
3566
3793
|
let calls = 0;
|
|
3567
3794
|
const byEndpoint = {};
|
|
3568
3795
|
try {
|
|
3569
|
-
const content =
|
|
3796
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3570
3797
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3571
3798
|
for (const line of content.split("\n")) {
|
|
3572
3799
|
if (!line) continue;
|
|
@@ -3621,47 +3848,6 @@ async function status() {
|
|
|
3621
3848
|
return { address, balance };
|
|
3622
3849
|
}
|
|
3623
3850
|
|
|
3624
|
-
// src/cost-log.ts
|
|
3625
|
-
import * as fs4 from "fs";
|
|
3626
|
-
import * as path4 from "path";
|
|
3627
|
-
import * as os4 from "os";
|
|
3628
|
-
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3629
|
-
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
3630
|
-
function logCost(entry) {
|
|
3631
|
-
try {
|
|
3632
|
-
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
3633
|
-
} catch {
|
|
3634
|
-
}
|
|
3635
|
-
try {
|
|
3636
|
-
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3637
|
-
} catch {
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
function getCostSummary() {
|
|
3641
|
-
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3642
|
-
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3643
|
-
}
|
|
3644
|
-
let totalUsd = 0;
|
|
3645
|
-
let calls = 0;
|
|
3646
|
-
const byModel = {};
|
|
3647
|
-
try {
|
|
3648
|
-
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3649
|
-
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3650
|
-
for (const line of content.split("\n")) {
|
|
3651
|
-
if (!line) continue;
|
|
3652
|
-
try {
|
|
3653
|
-
const entry = JSON.parse(line);
|
|
3654
|
-
totalUsd += entry.costUsd;
|
|
3655
|
-
calls += 1;
|
|
3656
|
-
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
3657
|
-
} catch {
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
} catch {
|
|
3661
|
-
}
|
|
3662
|
-
return { totalUsd, calls, byModel };
|
|
3663
|
-
}
|
|
3664
|
-
|
|
3665
3851
|
// src/openai-compat.ts
|
|
3666
3852
|
var StreamingResponse = class {
|
|
3667
3853
|
reader;
|
|
@@ -3969,7 +4155,6 @@ export {
|
|
|
3969
4155
|
solanaKeyToBytes,
|
|
3970
4156
|
solanaPublicKey,
|
|
3971
4157
|
status,
|
|
3972
|
-
testnetClient,
|
|
3973
4158
|
validateMaxTokens,
|
|
3974
4159
|
validateModel,
|
|
3975
4160
|
validateTemperature,
|