@blockrun/llm 1.15.0 → 2.1.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 +364 -218
- package/dist/index.d.cts +127 -74
- package/dist/index.d.ts +127 -74
- package/dist/index.js +365 -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,130 @@ 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
|
-
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
786
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
787
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
788
|
+
* on the response when it served a paid request from a free model
|
|
789
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
790
|
+
* to the caller, the user gets a different model than requested with
|
|
791
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
792
|
+
* balance didn't change.
|
|
793
|
+
*/
|
|
794
|
+
async parseChatResponse(response) {
|
|
795
|
+
const body = await response.json();
|
|
796
|
+
const used = response.headers.get("X-Fallback-Used") === "true";
|
|
797
|
+
if (used) {
|
|
798
|
+
body.fallback = {
|
|
799
|
+
used: true,
|
|
800
|
+
model: response.headers.get("X-Fallback-Model") || void 0,
|
|
801
|
+
settlementSkipped: response.headers.get("X-Settlement-Skipped") === "free-fallback"
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
return body;
|
|
628
805
|
}
|
|
629
806
|
/**
|
|
630
807
|
* Make a request with automatic x402 payment handling.
|
|
@@ -654,7 +831,7 @@ var LLMClient = class _LLMClient {
|
|
|
654
831
|
}
|
|
655
832
|
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
656
833
|
}
|
|
657
|
-
return
|
|
834
|
+
return this.parseChatResponse(retryResp);
|
|
658
835
|
}
|
|
659
836
|
}
|
|
660
837
|
if (response.status === 402) {
|
|
@@ -673,7 +850,7 @@ var LLMClient = class _LLMClient {
|
|
|
673
850
|
sanitizeErrorResponse(errorBody)
|
|
674
851
|
);
|
|
675
852
|
}
|
|
676
|
-
return
|
|
853
|
+
return this.parseChatResponse(response);
|
|
677
854
|
}
|
|
678
855
|
/**
|
|
679
856
|
* Handle 402 response: parse requirements, sign payment, retry.
|
|
@@ -747,7 +924,8 @@ var LLMClient = class _LLMClient {
|
|
|
747
924
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
748
925
|
this.sessionCalls += 1;
|
|
749
926
|
this.sessionTotalUsd += costUsd2;
|
|
750
|
-
|
|
927
|
+
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
928
|
+
return this.parseChatResponse(retryResp2);
|
|
751
929
|
}
|
|
752
930
|
}
|
|
753
931
|
if (retryResponse.status === 402) {
|
|
@@ -769,7 +947,8 @@ var LLMClient = class _LLMClient {
|
|
|
769
947
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
770
948
|
this.sessionCalls += 1;
|
|
771
949
|
this.sessionTotalUsd += costUsd;
|
|
772
|
-
|
|
950
|
+
this.recordCost(url, costUsd, { body, network: details.network });
|
|
951
|
+
return this.parseChatResponse(retryResponse);
|
|
773
952
|
}
|
|
774
953
|
/**
|
|
775
954
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
@@ -1018,6 +1197,7 @@ var LLMClient = class _LLMClient {
|
|
|
1018
1197
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1019
1198
|
this.sessionCalls += 1;
|
|
1020
1199
|
this.sessionTotalUsd += costUsd2;
|
|
1200
|
+
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
1021
1201
|
return retryResp2.json();
|
|
1022
1202
|
}
|
|
1023
1203
|
}
|
|
@@ -1040,6 +1220,7 @@ var LLMClient = class _LLMClient {
|
|
|
1040
1220
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
1041
1221
|
this.sessionCalls += 1;
|
|
1042
1222
|
this.sessionTotalUsd += costUsd;
|
|
1223
|
+
this.recordCost(url, costUsd, { body, network: details.network });
|
|
1043
1224
|
return retryResponse.json();
|
|
1044
1225
|
}
|
|
1045
1226
|
/**
|
|
@@ -1161,6 +1342,7 @@ var LLMClient = class _LLMClient {
|
|
|
1161
1342
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1162
1343
|
this.sessionCalls += 1;
|
|
1163
1344
|
this.sessionTotalUsd += costUsd2;
|
|
1345
|
+
this.recordCost(url, costUsd2, { network: details.network });
|
|
1164
1346
|
return retryResp2.json();
|
|
1165
1347
|
}
|
|
1166
1348
|
}
|
|
@@ -1183,6 +1365,7 @@ var LLMClient = class _LLMClient {
|
|
|
1183
1365
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
1184
1366
|
this.sessionCalls += 1;
|
|
1185
1367
|
this.sessionTotalUsd += costUsd;
|
|
1368
|
+
this.recordCost(url, costUsd, { network: details.network });
|
|
1186
1369
|
return retryResponse.json();
|
|
1187
1370
|
}
|
|
1188
1371
|
/**
|
|
@@ -1202,9 +1385,59 @@ var LLMClient = class _LLMClient {
|
|
|
1202
1385
|
}
|
|
1203
1386
|
}
|
|
1204
1387
|
/**
|
|
1205
|
-
* List available
|
|
1388
|
+
* List available models with pricing.
|
|
1389
|
+
*
|
|
1390
|
+
* Returns the full `/v1/models` unified catalog (chat + image + music).
|
|
1391
|
+
* The shape preserves backwards compatibility — image/music rows have
|
|
1392
|
+
* `inputPrice = outputPrice = 0` since those fields don't apply, and
|
|
1393
|
+
* their per-call price surfaces via `flatPrice`.
|
|
1206
1394
|
*/
|
|
1207
1395
|
async listModels() {
|
|
1396
|
+
const raw = await this.fetchRawModels();
|
|
1397
|
+
return raw.map((m) => mapRawToModel(m));
|
|
1398
|
+
}
|
|
1399
|
+
/**
|
|
1400
|
+
* List available image generation models with pricing.
|
|
1401
|
+
*
|
|
1402
|
+
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
|
|
1403
|
+
* image models live in the unified `/v1/models` catalog under
|
|
1404
|
+
* `categories: ["image", ...]`. This method filters that catalog so
|
|
1405
|
+
* existing callers keep working.
|
|
1406
|
+
*/
|
|
1407
|
+
async listImageModels() {
|
|
1408
|
+
const raw = await this.fetchRawModels();
|
|
1409
|
+
return raw.filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => mapRawToImageModel(m));
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* List all available models (chat, image, music, etc.) with pricing.
|
|
1413
|
+
*
|
|
1414
|
+
* @returns Array of all models with `type` field set from category
|
|
1415
|
+
* (`llm` for chat, `image` / `music` for media). Backwards-compat:
|
|
1416
|
+
* chat models always report `type: "llm"`.
|
|
1417
|
+
*/
|
|
1418
|
+
async listAllModels() {
|
|
1419
|
+
const raw = await this.fetchRawModels();
|
|
1420
|
+
const out = [];
|
|
1421
|
+
for (const m of raw) {
|
|
1422
|
+
const cats = Array.isArray(m.categories) ? m.categories : [];
|
|
1423
|
+
if (cats.includes("image")) {
|
|
1424
|
+
const model = mapRawToImageModel(m);
|
|
1425
|
+
model.type = "image";
|
|
1426
|
+
out.push(model);
|
|
1427
|
+
} else {
|
|
1428
|
+
const model = mapRawToModel(m);
|
|
1429
|
+
model.type = "llm";
|
|
1430
|
+
out.push(model);
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return out;
|
|
1434
|
+
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Internal: fetch the raw `/v1/models` catalog without normalising shape.
|
|
1437
|
+
* Used by listImageModels / listAllModels so each can pick category-
|
|
1438
|
+
* specific fields.
|
|
1439
|
+
*/
|
|
1440
|
+
async fetchRawModels() {
|
|
1208
1441
|
const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, {
|
|
1209
1442
|
method: "GET"
|
|
1210
1443
|
});
|
|
@@ -1222,65 +1455,8 @@ var LLMClient = class _LLMClient {
|
|
|
1222
1455
|
);
|
|
1223
1456
|
}
|
|
1224
1457
|
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
1458
|
return data.data || [];
|
|
1257
1459
|
}
|
|
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
1460
|
/**
|
|
1285
1461
|
* Edit an image using img2img.
|
|
1286
1462
|
*
|
|
@@ -1321,6 +1497,19 @@ var LLMClient = class _LLMClient {
|
|
|
1321
1497
|
const data = await this.requestWithPaymentRaw("/v1/search", body);
|
|
1322
1498
|
return data;
|
|
1323
1499
|
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Generic Exa endpoint proxy (POST). Useful when you need an Exa API
|
|
1502
|
+
* surface that the typed wrappers below don't expose.
|
|
1503
|
+
*
|
|
1504
|
+
* @param path - Exa endpoint segment: "search" | "find-similar" | "contents" | "answer"
|
|
1505
|
+
* @param body - Request body (see Exa API docs)
|
|
1506
|
+
*
|
|
1507
|
+
* @example
|
|
1508
|
+
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
1509
|
+
*/
|
|
1510
|
+
async exa(path5, body) {
|
|
1511
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path5}`, body);
|
|
1512
|
+
}
|
|
1324
1513
|
/**
|
|
1325
1514
|
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
|
|
1326
1515
|
* Understands meaning, not just keywords. $0.01/call.
|
|
@@ -1374,9 +1563,7 @@ var LLMClient = class _LLMClient {
|
|
|
1374
1563
|
return data.data;
|
|
1375
1564
|
}
|
|
1376
1565
|
/**
|
|
1377
|
-
* Get USDC balance on Base
|
|
1378
|
-
*
|
|
1379
|
-
* Automatically detects mainnet vs testnet based on API URL.
|
|
1566
|
+
* Get USDC balance on Base mainnet.
|
|
1380
1567
|
*
|
|
1381
1568
|
* @returns USDC balance as a float (6 decimal places normalized)
|
|
1382
1569
|
*
|
|
@@ -1385,9 +1572,8 @@ var LLMClient = class _LLMClient {
|
|
|
1385
1572
|
* console.log(`Balance: $${balance.toFixed(2)} USDC`);
|
|
1386
1573
|
*/
|
|
1387
1574
|
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"];
|
|
1575
|
+
const usdcContract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
1576
|
+
const rpcs = ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
|
|
1391
1577
|
const selector = "0x70a08231";
|
|
1392
1578
|
const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
|
|
1393
1579
|
const data = selector + paddedAddress;
|
|
@@ -1718,19 +1904,7 @@ var LLMClient = class _LLMClient {
|
|
|
1718
1904
|
getWalletAddress() {
|
|
1719
1905
|
return this.account.address;
|
|
1720
1906
|
}
|
|
1721
|
-
/**
|
|
1722
|
-
* Check if client is configured for testnet.
|
|
1723
|
-
*/
|
|
1724
|
-
isTestnet() {
|
|
1725
|
-
return this.apiUrl.includes("testnet.blockrun.ai");
|
|
1726
|
-
}
|
|
1727
1907
|
};
|
|
1728
|
-
function testnetClient(options = {}) {
|
|
1729
|
-
return new LLMClient({
|
|
1730
|
-
...options,
|
|
1731
|
-
apiUrl: TESTNET_API_URL
|
|
1732
|
-
});
|
|
1733
|
-
}
|
|
1734
1908
|
var client_default = LLMClient;
|
|
1735
1909
|
|
|
1736
1910
|
// src/image.ts
|
|
@@ -1819,10 +1993,15 @@ var ImageClient = class {
|
|
|
1819
1993
|
}
|
|
1820
1994
|
/**
|
|
1821
1995
|
* List available image generation models with pricing.
|
|
1996
|
+
*
|
|
1997
|
+
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
|
|
1998
|
+
* image models live in the unified `/v1/models` catalog under
|
|
1999
|
+
* `categories: ["image", ...]`. This method filters that catalog so
|
|
2000
|
+
* existing callers keep working.
|
|
1822
2001
|
*/
|
|
1823
2002
|
async listImageModels() {
|
|
1824
2003
|
const response = await this.fetchWithTimeout(
|
|
1825
|
-
`${this.apiUrl}/v1/
|
|
2004
|
+
`${this.apiUrl}/v1/models`,
|
|
1826
2005
|
{ method: "GET" }
|
|
1827
2006
|
);
|
|
1828
2007
|
if (!response.ok) {
|
|
@@ -1832,7 +2011,16 @@ var ImageClient = class {
|
|
|
1832
2011
|
);
|
|
1833
2012
|
}
|
|
1834
2013
|
const data = await response.json();
|
|
1835
|
-
return data.data || []
|
|
2014
|
+
return (data.data || []).filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => ({
|
|
2015
|
+
id: m.id,
|
|
2016
|
+
name: m.name || m.id,
|
|
2017
|
+
provider: m.provider || m.owned_by || "",
|
|
2018
|
+
description: m.description || "",
|
|
2019
|
+
pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
|
|
2020
|
+
supportedSizes: m.supportedSizes ?? m.supported_sizes,
|
|
2021
|
+
maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
|
|
2022
|
+
available: true
|
|
2023
|
+
}));
|
|
1836
2024
|
}
|
|
1837
2025
|
/**
|
|
1838
2026
|
* Make a request with automatic x402 payment handling.
|
|
@@ -2727,13 +2915,13 @@ function buildUrl(base, query) {
|
|
|
2727
2915
|
|
|
2728
2916
|
// src/wallet.ts
|
|
2729
2917
|
var import_accounts8 = require("viem/accounts");
|
|
2730
|
-
var
|
|
2731
|
-
var
|
|
2732
|
-
var
|
|
2918
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
2919
|
+
var path2 = __toESM(require("path"), 1);
|
|
2920
|
+
var os2 = __toESM(require("os"), 1);
|
|
2733
2921
|
var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2734
2922
|
var BASE_CHAIN_ID2 = "8453";
|
|
2735
|
-
var WALLET_DIR =
|
|
2736
|
-
var WALLET_FILE =
|
|
2923
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2924
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2737
2925
|
function createWallet() {
|
|
2738
2926
|
const privateKey = (0, import_accounts8.generatePrivateKey)();
|
|
2739
2927
|
const account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
@@ -2743,27 +2931,27 @@ function createWallet() {
|
|
|
2743
2931
|
};
|
|
2744
2932
|
}
|
|
2745
2933
|
function saveWallet(privateKey) {
|
|
2746
|
-
if (!
|
|
2747
|
-
|
|
2934
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2935
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2748
2936
|
}
|
|
2749
|
-
|
|
2937
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2750
2938
|
return WALLET_FILE;
|
|
2751
2939
|
}
|
|
2752
2940
|
function scanWallets() {
|
|
2753
|
-
const home =
|
|
2941
|
+
const home = os2.homedir();
|
|
2754
2942
|
const results = [];
|
|
2755
2943
|
try {
|
|
2756
|
-
const entries =
|
|
2944
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
2757
2945
|
for (const entry of entries) {
|
|
2758
2946
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2759
|
-
const walletFile =
|
|
2760
|
-
if (!
|
|
2947
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2948
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2761
2949
|
try {
|
|
2762
|
-
const data = JSON.parse(
|
|
2950
|
+
const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
|
|
2763
2951
|
const pk = data.privateKey || "";
|
|
2764
2952
|
const addr = data.address || "";
|
|
2765
2953
|
if (pk && addr) {
|
|
2766
|
-
const mtime =
|
|
2954
|
+
const mtime = fs2.statSync(walletFile).mtimeMs;
|
|
2767
2955
|
results.push({ mtime, privateKey: pk, address: addr });
|
|
2768
2956
|
}
|
|
2769
2957
|
} catch {
|
|
@@ -2778,13 +2966,13 @@ function scanWallets() {
|
|
|
2778
2966
|
function loadWallet() {
|
|
2779
2967
|
const wallets = scanWallets();
|
|
2780
2968
|
if (wallets.length > 0) return wallets[0].privateKey;
|
|
2781
|
-
if (
|
|
2782
|
-
const key =
|
|
2969
|
+
if (fs2.existsSync(WALLET_FILE)) {
|
|
2970
|
+
const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
2783
2971
|
if (key) return key;
|
|
2784
2972
|
}
|
|
2785
|
-
const legacyFile =
|
|
2786
|
-
if (
|
|
2787
|
-
const key =
|
|
2973
|
+
const legacyFile = path2.join(WALLET_DIR, "wallet.key");
|
|
2974
|
+
if (fs2.existsSync(legacyFile)) {
|
|
2975
|
+
const key = fs2.readFileSync(legacyFile, "utf-8").trim();
|
|
2788
2976
|
if (key) return key;
|
|
2789
2977
|
}
|
|
2790
2978
|
return null;
|
|
@@ -2879,11 +3067,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
|
|
|
2879
3067
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
2880
3068
|
|
|
2881
3069
|
// src/solana-wallet.ts
|
|
2882
|
-
var
|
|
2883
|
-
var
|
|
2884
|
-
var
|
|
2885
|
-
var WALLET_DIR2 =
|
|
2886
|
-
var SOLANA_WALLET_FILE =
|
|
3070
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
3071
|
+
var path3 = __toESM(require("path"), 1);
|
|
3072
|
+
var os3 = __toESM(require("os"), 1);
|
|
3073
|
+
var WALLET_DIR2 = path3.join(os3.homedir(), ".blockrun");
|
|
3074
|
+
var SOLANA_WALLET_FILE = path3.join(WALLET_DIR2, ".solana-session");
|
|
2887
3075
|
async function createSolanaWallet() {
|
|
2888
3076
|
const { Keypair } = await import("@solana/web3.js");
|
|
2889
3077
|
const bs58 = await import("bs58");
|
|
@@ -2912,39 +3100,39 @@ async function solanaPublicKey(privateKey) {
|
|
|
2912
3100
|
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
2913
3101
|
}
|
|
2914
3102
|
function saveSolanaWallet(privateKey) {
|
|
2915
|
-
if (!
|
|
2916
|
-
|
|
3103
|
+
if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
3104
|
+
fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
2917
3105
|
return SOLANA_WALLET_FILE;
|
|
2918
3106
|
}
|
|
2919
3107
|
function scanSolanaWallets() {
|
|
2920
|
-
const home =
|
|
3108
|
+
const home = os3.homedir();
|
|
2921
3109
|
const results = [];
|
|
2922
3110
|
try {
|
|
2923
|
-
const entries =
|
|
3111
|
+
const entries = fs3.readdirSync(home, { withFileTypes: true });
|
|
2924
3112
|
for (const entry of entries) {
|
|
2925
3113
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2926
|
-
const solanaWalletFile =
|
|
2927
|
-
if (
|
|
3114
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
3115
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2928
3116
|
try {
|
|
2929
|
-
const data = JSON.parse(
|
|
3117
|
+
const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
|
|
2930
3118
|
const pk = data.privateKey || "";
|
|
2931
3119
|
const addr = data.address || "";
|
|
2932
3120
|
if (pk && addr) {
|
|
2933
|
-
const mtime =
|
|
3121
|
+
const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
|
|
2934
3122
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2935
3123
|
}
|
|
2936
3124
|
} catch {
|
|
2937
3125
|
}
|
|
2938
3126
|
}
|
|
2939
3127
|
if (entry.name === ".brcc") {
|
|
2940
|
-
const brccWalletFile =
|
|
2941
|
-
if (
|
|
3128
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3129
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2942
3130
|
try {
|
|
2943
|
-
const data = JSON.parse(
|
|
3131
|
+
const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
|
|
2944
3132
|
const pk = data.privateKey || "";
|
|
2945
3133
|
const addr = data.address || "";
|
|
2946
3134
|
if (pk && addr) {
|
|
2947
|
-
const mtime =
|
|
3135
|
+
const mtime = fs3.statSync(brccWalletFile).mtimeMs;
|
|
2948
3136
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2949
3137
|
}
|
|
2950
3138
|
} catch {
|
|
@@ -2960,8 +3148,8 @@ function scanSolanaWallets() {
|
|
|
2960
3148
|
function loadSolanaWallet() {
|
|
2961
3149
|
const wallets = scanSolanaWallets();
|
|
2962
3150
|
if (wallets.length > 0) return wallets[0].secretKey;
|
|
2963
|
-
if (
|
|
2964
|
-
const key =
|
|
3151
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3152
|
+
const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2965
3153
|
if (key) return key;
|
|
2966
3154
|
}
|
|
2967
3155
|
return null;
|
|
@@ -2976,8 +3164,8 @@ async function getOrCreateSolanaWallet() {
|
|
|
2976
3164
|
if (wallets.length > 0) {
|
|
2977
3165
|
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
2978
3166
|
}
|
|
2979
|
-
if (
|
|
2980
|
-
const fileKey =
|
|
3167
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3168
|
+
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2981
3169
|
if (fileKey) {
|
|
2982
3170
|
const address2 = await solanaPublicKey(fileKey);
|
|
2983
3171
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
@@ -3551,13 +3739,13 @@ function solanaClient(options = {}) {
|
|
|
3551
3739
|
}
|
|
3552
3740
|
|
|
3553
3741
|
// src/cache.ts
|
|
3554
|
-
var
|
|
3555
|
-
var
|
|
3556
|
-
var
|
|
3742
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
3743
|
+
var path4 = __toESM(require("path"), 1);
|
|
3744
|
+
var os4 = __toESM(require("os"), 1);
|
|
3557
3745
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
3558
|
-
var CACHE_DIR =
|
|
3559
|
-
var DATA_DIR =
|
|
3560
|
-
var
|
|
3746
|
+
var CACHE_DIR = path4.join(os4.homedir(), ".blockrun", "cache");
|
|
3747
|
+
var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3748
|
+
var COST_LOG_FILE2 = path4.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
|
|
3561
3749
|
var DEFAULT_TTL = {
|
|
3562
3750
|
"/v1/x/": 3600 * 1e3,
|
|
3563
3751
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -3578,19 +3766,19 @@ function cacheKey(endpoint, body) {
|
|
|
3578
3766
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
3579
3767
|
}
|
|
3580
3768
|
function cachePath(key) {
|
|
3581
|
-
return
|
|
3769
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3582
3770
|
}
|
|
3583
3771
|
function getCached(key) {
|
|
3584
3772
|
const filePath = cachePath(key);
|
|
3585
|
-
if (!
|
|
3773
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3586
3774
|
try {
|
|
3587
|
-
const raw =
|
|
3775
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3588
3776
|
const entry = JSON.parse(raw);
|
|
3589
3777
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
3590
3778
|
if (ttl <= 0) return null;
|
|
3591
3779
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
3592
3780
|
try {
|
|
3593
|
-
|
|
3781
|
+
fs4.unlinkSync(filePath);
|
|
3594
3782
|
} catch {
|
|
3595
3783
|
}
|
|
3596
3784
|
return null;
|
|
@@ -3609,7 +3797,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
3609
3797
|
function setCache(key, data, ttlMs) {
|
|
3610
3798
|
if (ttlMs <= 0) return;
|
|
3611
3799
|
try {
|
|
3612
|
-
|
|
3800
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3613
3801
|
} catch {
|
|
3614
3802
|
}
|
|
3615
3803
|
const entry = {
|
|
@@ -3618,7 +3806,7 @@ function setCache(key, data, ttlMs) {
|
|
|
3618
3806
|
ttlMs
|
|
3619
3807
|
};
|
|
3620
3808
|
try {
|
|
3621
|
-
|
|
3809
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3622
3810
|
} catch {
|
|
3623
3811
|
}
|
|
3624
3812
|
}
|
|
@@ -3641,7 +3829,7 @@ function readableFilename(endpoint, body) {
|
|
|
3641
3829
|
}
|
|
3642
3830
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
3643
3831
|
try {
|
|
3644
|
-
|
|
3832
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
3645
3833
|
} catch {
|
|
3646
3834
|
}
|
|
3647
3835
|
const filename = readableFilename(endpoint, body);
|
|
@@ -3653,14 +3841,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
3653
3841
|
response
|
|
3654
3842
|
};
|
|
3655
3843
|
try {
|
|
3656
|
-
|
|
3844
|
+
fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
3657
3845
|
} catch {
|
|
3658
3846
|
}
|
|
3659
3847
|
}
|
|
3660
3848
|
function appendCostLog(endpoint, costUsd) {
|
|
3661
3849
|
if (costUsd <= 0) return;
|
|
3662
3850
|
try {
|
|
3663
|
-
|
|
3851
|
+
fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
|
|
3664
3852
|
} catch {
|
|
3665
3853
|
}
|
|
3666
3854
|
const entry = {
|
|
@@ -3669,7 +3857,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
3669
3857
|
cost_usd: costUsd
|
|
3670
3858
|
};
|
|
3671
3859
|
try {
|
|
3672
|
-
|
|
3860
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3673
3861
|
} catch {
|
|
3674
3862
|
}
|
|
3675
3863
|
}
|
|
@@ -3677,7 +3865,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3677
3865
|
const ttl = getTtl(endpoint);
|
|
3678
3866
|
if (ttl > 0) {
|
|
3679
3867
|
try {
|
|
3680
|
-
|
|
3868
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3681
3869
|
} catch {
|
|
3682
3870
|
}
|
|
3683
3871
|
const key = cacheKey(endpoint, body);
|
|
@@ -3689,7 +3877,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3689
3877
|
costUsd
|
|
3690
3878
|
};
|
|
3691
3879
|
try {
|
|
3692
|
-
|
|
3880
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3693
3881
|
} catch {
|
|
3694
3882
|
}
|
|
3695
3883
|
}
|
|
@@ -3697,14 +3885,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3697
3885
|
appendCostLog(endpoint, costUsd);
|
|
3698
3886
|
}
|
|
3699
3887
|
function clearCache() {
|
|
3700
|
-
if (!
|
|
3888
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3701
3889
|
let count = 0;
|
|
3702
3890
|
try {
|
|
3703
|
-
const files =
|
|
3891
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3704
3892
|
for (const file of files) {
|
|
3705
3893
|
if (file.endsWith(".json")) {
|
|
3706
3894
|
try {
|
|
3707
|
-
|
|
3895
|
+
fs4.unlinkSync(path4.join(CACHE_DIR, file));
|
|
3708
3896
|
count++;
|
|
3709
3897
|
} catch {
|
|
3710
3898
|
}
|
|
@@ -3715,14 +3903,14 @@ function clearCache() {
|
|
|
3715
3903
|
return count;
|
|
3716
3904
|
}
|
|
3717
3905
|
function getCostLogSummary() {
|
|
3718
|
-
if (!
|
|
3906
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3719
3907
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3720
3908
|
}
|
|
3721
3909
|
let totalUsd = 0;
|
|
3722
3910
|
let calls = 0;
|
|
3723
3911
|
const byEndpoint = {};
|
|
3724
3912
|
try {
|
|
3725
|
-
const content =
|
|
3913
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3726
3914
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3727
3915
|
for (const line of content.split("\n")) {
|
|
3728
3916
|
if (!line) continue;
|
|
@@ -3777,47 +3965,6 @@ async function status() {
|
|
|
3777
3965
|
return { address, balance };
|
|
3778
3966
|
}
|
|
3779
3967
|
|
|
3780
|
-
// src/cost-log.ts
|
|
3781
|
-
var fs4 = __toESM(require("fs"), 1);
|
|
3782
|
-
var path4 = __toESM(require("path"), 1);
|
|
3783
|
-
var os4 = __toESM(require("os"), 1);
|
|
3784
|
-
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3785
|
-
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
3786
|
-
function logCost(entry) {
|
|
3787
|
-
try {
|
|
3788
|
-
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
3789
|
-
} catch {
|
|
3790
|
-
}
|
|
3791
|
-
try {
|
|
3792
|
-
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3793
|
-
} catch {
|
|
3794
|
-
}
|
|
3795
|
-
}
|
|
3796
|
-
function getCostSummary() {
|
|
3797
|
-
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3798
|
-
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3799
|
-
}
|
|
3800
|
-
let totalUsd = 0;
|
|
3801
|
-
let calls = 0;
|
|
3802
|
-
const byModel = {};
|
|
3803
|
-
try {
|
|
3804
|
-
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3805
|
-
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3806
|
-
for (const line of content.split("\n")) {
|
|
3807
|
-
if (!line) continue;
|
|
3808
|
-
try {
|
|
3809
|
-
const entry = JSON.parse(line);
|
|
3810
|
-
totalUsd += entry.costUsd;
|
|
3811
|
-
calls += 1;
|
|
3812
|
-
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
3813
|
-
} catch {
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
} catch {
|
|
3817
|
-
}
|
|
3818
|
-
return { totalUsd, calls, byModel };
|
|
3819
|
-
}
|
|
3820
|
-
|
|
3821
3968
|
// src/openai-compat.ts
|
|
3822
3969
|
var StreamingResponse = class {
|
|
3823
3970
|
reader;
|
|
@@ -4125,7 +4272,6 @@ var AnthropicClient = class {
|
|
|
4125
4272
|
solanaKeyToBytes,
|
|
4126
4273
|
solanaPublicKey,
|
|
4127
4274
|
status,
|
|
4128
|
-
testnetClient,
|
|
4129
4275
|
validateMaxTokens,
|
|
4130
4276
|
validateModel,
|
|
4131
4277
|
validateTemperature,
|