@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.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;
|
|
@@ -1633,17 +1797,70 @@ var LLMClient = class _LLMClient {
|
|
|
1633
1797
|
/**
|
|
1634
1798
|
* Structured query for Predexon prediction market data (POST endpoints).
|
|
1635
1799
|
*
|
|
1636
|
-
* For
|
|
1800
|
+
* For endpoints that require a JSON body, e.g. bulk wallet identity lookup.
|
|
1801
|
+
* Tier 1 = $0.001/call, Tier 2 = $0.005/call.
|
|
1637
1802
|
*
|
|
1638
|
-
* @param path - Endpoint path, e.g. "polymarket/
|
|
1803
|
+
* @param path - Endpoint path, e.g. "polymarket/wallet/identities"
|
|
1639
1804
|
* @param query - JSON body for the structured query
|
|
1640
1805
|
*
|
|
1641
1806
|
* @example
|
|
1642
|
-
* const
|
|
1807
|
+
* const batch = await client.pmQuery("polymarket/wallet/identities", {
|
|
1808
|
+
* addresses: ["0xabc...", "0xdef..."],
|
|
1809
|
+
* });
|
|
1643
1810
|
*/
|
|
1644
1811
|
async pmQuery(path5, query) {
|
|
1645
1812
|
return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
|
|
1646
1813
|
}
|
|
1814
|
+
// ── PM convenience helpers (Predexon v2) ─────────────────────────────────
|
|
1815
|
+
// Thin wrappers over pm() / pmQuery() for the most common v2 endpoints.
|
|
1816
|
+
/** List canonical cross-venue markets (Predexon v2). Tier 1 ($0.001/call).
|
|
1817
|
+
* Filter with venue, status, category, league, event_id, pagination_key. */
|
|
1818
|
+
async pmMarkets(params) {
|
|
1819
|
+
return this.pm("markets", params);
|
|
1820
|
+
}
|
|
1821
|
+
/** List venue-native executable listings flattened across canonical markets
|
|
1822
|
+
* (Predexon v2). Tier 1 ($0.001/call). */
|
|
1823
|
+
async pmListings(params) {
|
|
1824
|
+
return this.pm("markets/listings", params);
|
|
1825
|
+
}
|
|
1826
|
+
/** Resolve a canonical Predexon outcome ID to its market context and venue
|
|
1827
|
+
* listings. Tier 1 ($0.001/call). */
|
|
1828
|
+
async pmOutcome(predexonId) {
|
|
1829
|
+
return this.pm(`outcomes/${predexonId}`);
|
|
1830
|
+
}
|
|
1831
|
+
/** Polymarket markets with cursor-based keyset pagination (use pagination_key).
|
|
1832
|
+
* Tier 1 ($0.001/call). */
|
|
1833
|
+
async pmPolymarketMarketsKeyset(params) {
|
|
1834
|
+
return this.pm("polymarket/markets/keyset", params);
|
|
1835
|
+
}
|
|
1836
|
+
/** Polymarket events with cursor-based keyset pagination (use pagination_key).
|
|
1837
|
+
* Tier 1 ($0.001/call). */
|
|
1838
|
+
async pmPolymarketEventsKeyset(params) {
|
|
1839
|
+
return this.pm("polymarket/events/keyset", params);
|
|
1840
|
+
}
|
|
1841
|
+
/** List available sports categories. Tier 1 ($0.001/call). */
|
|
1842
|
+
async pmSportsCategories() {
|
|
1843
|
+
return this.pm("sports/categories");
|
|
1844
|
+
}
|
|
1845
|
+
/** List sports markets grouped by game. Filter with league, sport_type,
|
|
1846
|
+
* status, venue. Tier 1 ($0.001/call). */
|
|
1847
|
+
async pmSportsMarkets(params) {
|
|
1848
|
+
return this.pm("sports/markets", params);
|
|
1849
|
+
}
|
|
1850
|
+
/** Fetch identity + profile metadata for one wallet (ENS, Twitter, portfolio,
|
|
1851
|
+
* etc.). Tier 2 ($0.005/call). */
|
|
1852
|
+
async pmWalletIdentity(wallet) {
|
|
1853
|
+
return this.pm(`polymarket/wallet/identity/${wallet}`);
|
|
1854
|
+
}
|
|
1855
|
+
/** Bulk identity lookup for up to 200 wallet addresses (POST). Tier 2 ($0.005/call). */
|
|
1856
|
+
async pmWalletIdentities(addresses) {
|
|
1857
|
+
return this.pmQuery("polymarket/wallet/identities", { addresses });
|
|
1858
|
+
}
|
|
1859
|
+
/** Discover wallets connected to a seed address via on-chain transfers and
|
|
1860
|
+
* identity proofs. Tier 2 ($0.005/call). */
|
|
1861
|
+
async pmWalletCluster(address) {
|
|
1862
|
+
return this.pm(`polymarket/wallet/${address}/cluster`);
|
|
1863
|
+
}
|
|
1647
1864
|
/**
|
|
1648
1865
|
* Get current session spending.
|
|
1649
1866
|
*
|
|
@@ -1665,19 +1882,7 @@ var LLMClient = class _LLMClient {
|
|
|
1665
1882
|
getWalletAddress() {
|
|
1666
1883
|
return this.account.address;
|
|
1667
1884
|
}
|
|
1668
|
-
/**
|
|
1669
|
-
* Check if client is configured for testnet.
|
|
1670
|
-
*/
|
|
1671
|
-
isTestnet() {
|
|
1672
|
-
return this.apiUrl.includes("testnet.blockrun.ai");
|
|
1673
|
-
}
|
|
1674
1885
|
};
|
|
1675
|
-
function testnetClient(options = {}) {
|
|
1676
|
-
return new LLMClient({
|
|
1677
|
-
...options,
|
|
1678
|
-
apiUrl: TESTNET_API_URL
|
|
1679
|
-
});
|
|
1680
|
-
}
|
|
1681
1886
|
var client_default = LLMClient;
|
|
1682
1887
|
|
|
1683
1888
|
// src/image.ts
|
|
@@ -1766,10 +1971,15 @@ var ImageClient = class {
|
|
|
1766
1971
|
}
|
|
1767
1972
|
/**
|
|
1768
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.
|
|
1769
1979
|
*/
|
|
1770
1980
|
async listImageModels() {
|
|
1771
1981
|
const response = await this.fetchWithTimeout(
|
|
1772
|
-
`${this.apiUrl}/v1/
|
|
1982
|
+
`${this.apiUrl}/v1/models`,
|
|
1773
1983
|
{ method: "GET" }
|
|
1774
1984
|
);
|
|
1775
1985
|
if (!response.ok) {
|
|
@@ -1779,7 +1989,16 @@ var ImageClient = class {
|
|
|
1779
1989
|
);
|
|
1780
1990
|
}
|
|
1781
1991
|
const data = await response.json();
|
|
1782
|
-
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
|
+
}));
|
|
1783
2002
|
}
|
|
1784
2003
|
/**
|
|
1785
2004
|
* Make a request with automatic x402 payment handling.
|
|
@@ -2265,12 +2484,19 @@ var SearchClient = class {
|
|
|
2265
2484
|
var import_accounts6 = require("viem/accounts");
|
|
2266
2485
|
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2267
2486
|
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2268
|
-
var XClient = class {
|
|
2487
|
+
var XClient = class _XClient {
|
|
2269
2488
|
account;
|
|
2270
2489
|
privateKey;
|
|
2271
2490
|
apiUrl;
|
|
2272
2491
|
timeout;
|
|
2492
|
+
static deprecationWarned = false;
|
|
2273
2493
|
constructor(options = {}) {
|
|
2494
|
+
if (!_XClient.deprecationWarned) {
|
|
2495
|
+
console.warn(
|
|
2496
|
+
"[@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."
|
|
2497
|
+
);
|
|
2498
|
+
_XClient.deprecationWarned = true;
|
|
2499
|
+
}
|
|
2274
2500
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2275
2501
|
const privateKey = options.privateKey || envKey;
|
|
2276
2502
|
if (!privateKey) {
|
|
@@ -2667,13 +2893,13 @@ function buildUrl(base, query) {
|
|
|
2667
2893
|
|
|
2668
2894
|
// src/wallet.ts
|
|
2669
2895
|
var import_accounts8 = require("viem/accounts");
|
|
2670
|
-
var
|
|
2671
|
-
var
|
|
2672
|
-
var
|
|
2896
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
2897
|
+
var path2 = __toESM(require("path"), 1);
|
|
2898
|
+
var os2 = __toESM(require("os"), 1);
|
|
2673
2899
|
var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2674
2900
|
var BASE_CHAIN_ID2 = "8453";
|
|
2675
|
-
var WALLET_DIR =
|
|
2676
|
-
var WALLET_FILE =
|
|
2901
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2902
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2677
2903
|
function createWallet() {
|
|
2678
2904
|
const privateKey = (0, import_accounts8.generatePrivateKey)();
|
|
2679
2905
|
const account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
@@ -2683,27 +2909,27 @@ function createWallet() {
|
|
|
2683
2909
|
};
|
|
2684
2910
|
}
|
|
2685
2911
|
function saveWallet(privateKey) {
|
|
2686
|
-
if (!
|
|
2687
|
-
|
|
2912
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2913
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2688
2914
|
}
|
|
2689
|
-
|
|
2915
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2690
2916
|
return WALLET_FILE;
|
|
2691
2917
|
}
|
|
2692
2918
|
function scanWallets() {
|
|
2693
|
-
const home =
|
|
2919
|
+
const home = os2.homedir();
|
|
2694
2920
|
const results = [];
|
|
2695
2921
|
try {
|
|
2696
|
-
const entries =
|
|
2922
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
2697
2923
|
for (const entry of entries) {
|
|
2698
2924
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2699
|
-
const walletFile =
|
|
2700
|
-
if (!
|
|
2925
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2926
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2701
2927
|
try {
|
|
2702
|
-
const data = JSON.parse(
|
|
2928
|
+
const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
|
|
2703
2929
|
const pk = data.privateKey || "";
|
|
2704
2930
|
const addr = data.address || "";
|
|
2705
2931
|
if (pk && addr) {
|
|
2706
|
-
const mtime =
|
|
2932
|
+
const mtime = fs2.statSync(walletFile).mtimeMs;
|
|
2707
2933
|
results.push({ mtime, privateKey: pk, address: addr });
|
|
2708
2934
|
}
|
|
2709
2935
|
} catch {
|
|
@@ -2718,13 +2944,13 @@ function scanWallets() {
|
|
|
2718
2944
|
function loadWallet() {
|
|
2719
2945
|
const wallets = scanWallets();
|
|
2720
2946
|
if (wallets.length > 0) return wallets[0].privateKey;
|
|
2721
|
-
if (
|
|
2722
|
-
const key =
|
|
2947
|
+
if (fs2.existsSync(WALLET_FILE)) {
|
|
2948
|
+
const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
2723
2949
|
if (key) return key;
|
|
2724
2950
|
}
|
|
2725
|
-
const legacyFile =
|
|
2726
|
-
if (
|
|
2727
|
-
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();
|
|
2728
2954
|
if (key) return key;
|
|
2729
2955
|
}
|
|
2730
2956
|
return null;
|
|
@@ -2819,11 +3045,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
|
|
|
2819
3045
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
2820
3046
|
|
|
2821
3047
|
// src/solana-wallet.ts
|
|
2822
|
-
var
|
|
2823
|
-
var
|
|
2824
|
-
var
|
|
2825
|
-
var WALLET_DIR2 =
|
|
2826
|
-
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");
|
|
2827
3053
|
async function createSolanaWallet() {
|
|
2828
3054
|
const { Keypair } = await import("@solana/web3.js");
|
|
2829
3055
|
const bs58 = await import("bs58");
|
|
@@ -2852,39 +3078,39 @@ async function solanaPublicKey(privateKey) {
|
|
|
2852
3078
|
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
2853
3079
|
}
|
|
2854
3080
|
function saveSolanaWallet(privateKey) {
|
|
2855
|
-
if (!
|
|
2856
|
-
|
|
3081
|
+
if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
3082
|
+
fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
2857
3083
|
return SOLANA_WALLET_FILE;
|
|
2858
3084
|
}
|
|
2859
3085
|
function scanSolanaWallets() {
|
|
2860
|
-
const home =
|
|
3086
|
+
const home = os3.homedir();
|
|
2861
3087
|
const results = [];
|
|
2862
3088
|
try {
|
|
2863
|
-
const entries =
|
|
3089
|
+
const entries = fs3.readdirSync(home, { withFileTypes: true });
|
|
2864
3090
|
for (const entry of entries) {
|
|
2865
3091
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2866
|
-
const solanaWalletFile =
|
|
2867
|
-
if (
|
|
3092
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
3093
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2868
3094
|
try {
|
|
2869
|
-
const data = JSON.parse(
|
|
3095
|
+
const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
|
|
2870
3096
|
const pk = data.privateKey || "";
|
|
2871
3097
|
const addr = data.address || "";
|
|
2872
3098
|
if (pk && addr) {
|
|
2873
|
-
const mtime =
|
|
3099
|
+
const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
|
|
2874
3100
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2875
3101
|
}
|
|
2876
3102
|
} catch {
|
|
2877
3103
|
}
|
|
2878
3104
|
}
|
|
2879
3105
|
if (entry.name === ".brcc") {
|
|
2880
|
-
const brccWalletFile =
|
|
2881
|
-
if (
|
|
3106
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3107
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2882
3108
|
try {
|
|
2883
|
-
const data = JSON.parse(
|
|
3109
|
+
const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
|
|
2884
3110
|
const pk = data.privateKey || "";
|
|
2885
3111
|
const addr = data.address || "";
|
|
2886
3112
|
if (pk && addr) {
|
|
2887
|
-
const mtime =
|
|
3113
|
+
const mtime = fs3.statSync(brccWalletFile).mtimeMs;
|
|
2888
3114
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2889
3115
|
}
|
|
2890
3116
|
} catch {
|
|
@@ -2900,8 +3126,8 @@ function scanSolanaWallets() {
|
|
|
2900
3126
|
function loadSolanaWallet() {
|
|
2901
3127
|
const wallets = scanSolanaWallets();
|
|
2902
3128
|
if (wallets.length > 0) return wallets[0].secretKey;
|
|
2903
|
-
if (
|
|
2904
|
-
const key =
|
|
3129
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3130
|
+
const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2905
3131
|
if (key) return key;
|
|
2906
3132
|
}
|
|
2907
3133
|
return null;
|
|
@@ -2916,8 +3142,8 @@ async function getOrCreateSolanaWallet() {
|
|
|
2916
3142
|
if (wallets.length > 0) {
|
|
2917
3143
|
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
2918
3144
|
}
|
|
2919
|
-
if (
|
|
2920
|
-
const fileKey =
|
|
3145
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3146
|
+
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2921
3147
|
if (fileKey) {
|
|
2922
3148
|
const address2 = await solanaPublicKey(fileKey);
|
|
2923
3149
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
@@ -3491,13 +3717,13 @@ function solanaClient(options = {}) {
|
|
|
3491
3717
|
}
|
|
3492
3718
|
|
|
3493
3719
|
// src/cache.ts
|
|
3494
|
-
var
|
|
3495
|
-
var
|
|
3496
|
-
var
|
|
3720
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
3721
|
+
var path4 = __toESM(require("path"), 1);
|
|
3722
|
+
var os4 = __toESM(require("os"), 1);
|
|
3497
3723
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
3498
|
-
var CACHE_DIR =
|
|
3499
|
-
var DATA_DIR =
|
|
3500
|
-
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");
|
|
3501
3727
|
var DEFAULT_TTL = {
|
|
3502
3728
|
"/v1/x/": 3600 * 1e3,
|
|
3503
3729
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -3518,19 +3744,19 @@ function cacheKey(endpoint, body) {
|
|
|
3518
3744
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
3519
3745
|
}
|
|
3520
3746
|
function cachePath(key) {
|
|
3521
|
-
return
|
|
3747
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3522
3748
|
}
|
|
3523
3749
|
function getCached(key) {
|
|
3524
3750
|
const filePath = cachePath(key);
|
|
3525
|
-
if (!
|
|
3751
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3526
3752
|
try {
|
|
3527
|
-
const raw =
|
|
3753
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3528
3754
|
const entry = JSON.parse(raw);
|
|
3529
3755
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
3530
3756
|
if (ttl <= 0) return null;
|
|
3531
3757
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
3532
3758
|
try {
|
|
3533
|
-
|
|
3759
|
+
fs4.unlinkSync(filePath);
|
|
3534
3760
|
} catch {
|
|
3535
3761
|
}
|
|
3536
3762
|
return null;
|
|
@@ -3549,7 +3775,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
3549
3775
|
function setCache(key, data, ttlMs) {
|
|
3550
3776
|
if (ttlMs <= 0) return;
|
|
3551
3777
|
try {
|
|
3552
|
-
|
|
3778
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3553
3779
|
} catch {
|
|
3554
3780
|
}
|
|
3555
3781
|
const entry = {
|
|
@@ -3558,7 +3784,7 @@ function setCache(key, data, ttlMs) {
|
|
|
3558
3784
|
ttlMs
|
|
3559
3785
|
};
|
|
3560
3786
|
try {
|
|
3561
|
-
|
|
3787
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3562
3788
|
} catch {
|
|
3563
3789
|
}
|
|
3564
3790
|
}
|
|
@@ -3581,7 +3807,7 @@ function readableFilename(endpoint, body) {
|
|
|
3581
3807
|
}
|
|
3582
3808
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
3583
3809
|
try {
|
|
3584
|
-
|
|
3810
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
3585
3811
|
} catch {
|
|
3586
3812
|
}
|
|
3587
3813
|
const filename = readableFilename(endpoint, body);
|
|
@@ -3593,14 +3819,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
3593
3819
|
response
|
|
3594
3820
|
};
|
|
3595
3821
|
try {
|
|
3596
|
-
|
|
3822
|
+
fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
3597
3823
|
} catch {
|
|
3598
3824
|
}
|
|
3599
3825
|
}
|
|
3600
3826
|
function appendCostLog(endpoint, costUsd) {
|
|
3601
3827
|
if (costUsd <= 0) return;
|
|
3602
3828
|
try {
|
|
3603
|
-
|
|
3829
|
+
fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
|
|
3604
3830
|
} catch {
|
|
3605
3831
|
}
|
|
3606
3832
|
const entry = {
|
|
@@ -3609,7 +3835,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
3609
3835
|
cost_usd: costUsd
|
|
3610
3836
|
};
|
|
3611
3837
|
try {
|
|
3612
|
-
|
|
3838
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3613
3839
|
} catch {
|
|
3614
3840
|
}
|
|
3615
3841
|
}
|
|
@@ -3617,7 +3843,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3617
3843
|
const ttl = getTtl(endpoint);
|
|
3618
3844
|
if (ttl > 0) {
|
|
3619
3845
|
try {
|
|
3620
|
-
|
|
3846
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3621
3847
|
} catch {
|
|
3622
3848
|
}
|
|
3623
3849
|
const key = cacheKey(endpoint, body);
|
|
@@ -3629,7 +3855,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3629
3855
|
costUsd
|
|
3630
3856
|
};
|
|
3631
3857
|
try {
|
|
3632
|
-
|
|
3858
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3633
3859
|
} catch {
|
|
3634
3860
|
}
|
|
3635
3861
|
}
|
|
@@ -3637,14 +3863,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3637
3863
|
appendCostLog(endpoint, costUsd);
|
|
3638
3864
|
}
|
|
3639
3865
|
function clearCache() {
|
|
3640
|
-
if (!
|
|
3866
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3641
3867
|
let count = 0;
|
|
3642
3868
|
try {
|
|
3643
|
-
const files =
|
|
3869
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3644
3870
|
for (const file of files) {
|
|
3645
3871
|
if (file.endsWith(".json")) {
|
|
3646
3872
|
try {
|
|
3647
|
-
|
|
3873
|
+
fs4.unlinkSync(path4.join(CACHE_DIR, file));
|
|
3648
3874
|
count++;
|
|
3649
3875
|
} catch {
|
|
3650
3876
|
}
|
|
@@ -3655,14 +3881,14 @@ function clearCache() {
|
|
|
3655
3881
|
return count;
|
|
3656
3882
|
}
|
|
3657
3883
|
function getCostLogSummary() {
|
|
3658
|
-
if (!
|
|
3884
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3659
3885
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3660
3886
|
}
|
|
3661
3887
|
let totalUsd = 0;
|
|
3662
3888
|
let calls = 0;
|
|
3663
3889
|
const byEndpoint = {};
|
|
3664
3890
|
try {
|
|
3665
|
-
const content =
|
|
3891
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3666
3892
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3667
3893
|
for (const line of content.split("\n")) {
|
|
3668
3894
|
if (!line) continue;
|
|
@@ -3717,47 +3943,6 @@ async function status() {
|
|
|
3717
3943
|
return { address, balance };
|
|
3718
3944
|
}
|
|
3719
3945
|
|
|
3720
|
-
// src/cost-log.ts
|
|
3721
|
-
var fs4 = __toESM(require("fs"), 1);
|
|
3722
|
-
var path4 = __toESM(require("path"), 1);
|
|
3723
|
-
var os4 = __toESM(require("os"), 1);
|
|
3724
|
-
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3725
|
-
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
3726
|
-
function logCost(entry) {
|
|
3727
|
-
try {
|
|
3728
|
-
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
3729
|
-
} catch {
|
|
3730
|
-
}
|
|
3731
|
-
try {
|
|
3732
|
-
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3733
|
-
} catch {
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
function getCostSummary() {
|
|
3737
|
-
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3738
|
-
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3739
|
-
}
|
|
3740
|
-
let totalUsd = 0;
|
|
3741
|
-
let calls = 0;
|
|
3742
|
-
const byModel = {};
|
|
3743
|
-
try {
|
|
3744
|
-
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3745
|
-
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3746
|
-
for (const line of content.split("\n")) {
|
|
3747
|
-
if (!line) continue;
|
|
3748
|
-
try {
|
|
3749
|
-
const entry = JSON.parse(line);
|
|
3750
|
-
totalUsd += entry.costUsd;
|
|
3751
|
-
calls += 1;
|
|
3752
|
-
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
3753
|
-
} catch {
|
|
3754
|
-
}
|
|
3755
|
-
}
|
|
3756
|
-
} catch {
|
|
3757
|
-
}
|
|
3758
|
-
return { totalUsd, calls, byModel };
|
|
3759
|
-
}
|
|
3760
|
-
|
|
3761
3946
|
// src/openai-compat.ts
|
|
3762
3947
|
var StreamingResponse = class {
|
|
3763
3948
|
reader;
|
|
@@ -4065,7 +4250,6 @@ var AnthropicClient = class {
|
|
|
4065
4250
|
solanaKeyToBytes,
|
|
4066
4251
|
solanaPublicKey,
|
|
4067
4252
|
status,
|
|
4068
|
-
testnetClient,
|
|
4069
4253
|
validateMaxTokens,
|
|
4070
4254
|
validateModel,
|
|
4071
4255
|
validateTemperature,
|