@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.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,130 @@ 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
|
-
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
691
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
692
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
693
|
+
* on the response when it served a paid request from a free model
|
|
694
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
695
|
+
* to the caller, the user gets a different model than requested with
|
|
696
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
697
|
+
* balance didn't change.
|
|
698
|
+
*/
|
|
699
|
+
async parseChatResponse(response) {
|
|
700
|
+
const body = await response.json();
|
|
701
|
+
const used = response.headers.get("X-Fallback-Used") === "true";
|
|
702
|
+
if (used) {
|
|
703
|
+
body.fallback = {
|
|
704
|
+
used: true,
|
|
705
|
+
model: response.headers.get("X-Fallback-Model") || void 0,
|
|
706
|
+
settlementSkipped: response.headers.get("X-Settlement-Skipped") === "free-fallback"
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return body;
|
|
532
710
|
}
|
|
533
711
|
/**
|
|
534
712
|
* Make a request with automatic x402 payment handling.
|
|
@@ -558,7 +736,7 @@ var LLMClient = class _LLMClient {
|
|
|
558
736
|
}
|
|
559
737
|
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
560
738
|
}
|
|
561
|
-
return
|
|
739
|
+
return this.parseChatResponse(retryResp);
|
|
562
740
|
}
|
|
563
741
|
}
|
|
564
742
|
if (response.status === 402) {
|
|
@@ -577,7 +755,7 @@ var LLMClient = class _LLMClient {
|
|
|
577
755
|
sanitizeErrorResponse(errorBody)
|
|
578
756
|
);
|
|
579
757
|
}
|
|
580
|
-
return
|
|
758
|
+
return this.parseChatResponse(response);
|
|
581
759
|
}
|
|
582
760
|
/**
|
|
583
761
|
* Handle 402 response: parse requirements, sign payment, retry.
|
|
@@ -651,7 +829,8 @@ var LLMClient = class _LLMClient {
|
|
|
651
829
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
652
830
|
this.sessionCalls += 1;
|
|
653
831
|
this.sessionTotalUsd += costUsd2;
|
|
654
|
-
|
|
832
|
+
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
833
|
+
return this.parseChatResponse(retryResp2);
|
|
655
834
|
}
|
|
656
835
|
}
|
|
657
836
|
if (retryResponse.status === 402) {
|
|
@@ -673,7 +852,8 @@ var LLMClient = class _LLMClient {
|
|
|
673
852
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
674
853
|
this.sessionCalls += 1;
|
|
675
854
|
this.sessionTotalUsd += costUsd;
|
|
676
|
-
|
|
855
|
+
this.recordCost(url, costUsd, { body, network: details.network });
|
|
856
|
+
return this.parseChatResponse(retryResponse);
|
|
677
857
|
}
|
|
678
858
|
/**
|
|
679
859
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
@@ -922,6 +1102,7 @@ var LLMClient = class _LLMClient {
|
|
|
922
1102
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
923
1103
|
this.sessionCalls += 1;
|
|
924
1104
|
this.sessionTotalUsd += costUsd2;
|
|
1105
|
+
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
925
1106
|
return retryResp2.json();
|
|
926
1107
|
}
|
|
927
1108
|
}
|
|
@@ -944,6 +1125,7 @@ var LLMClient = class _LLMClient {
|
|
|
944
1125
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
945
1126
|
this.sessionCalls += 1;
|
|
946
1127
|
this.sessionTotalUsd += costUsd;
|
|
1128
|
+
this.recordCost(url, costUsd, { body, network: details.network });
|
|
947
1129
|
return retryResponse.json();
|
|
948
1130
|
}
|
|
949
1131
|
/**
|
|
@@ -1065,6 +1247,7 @@ var LLMClient = class _LLMClient {
|
|
|
1065
1247
|
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1066
1248
|
this.sessionCalls += 1;
|
|
1067
1249
|
this.sessionTotalUsd += costUsd2;
|
|
1250
|
+
this.recordCost(url, costUsd2, { network: details.network });
|
|
1068
1251
|
return retryResp2.json();
|
|
1069
1252
|
}
|
|
1070
1253
|
}
|
|
@@ -1087,6 +1270,7 @@ var LLMClient = class _LLMClient {
|
|
|
1087
1270
|
const costUsd = parseFloat(details.amount) / 1e6;
|
|
1088
1271
|
this.sessionCalls += 1;
|
|
1089
1272
|
this.sessionTotalUsd += costUsd;
|
|
1273
|
+
this.recordCost(url, costUsd, { network: details.network });
|
|
1090
1274
|
return retryResponse.json();
|
|
1091
1275
|
}
|
|
1092
1276
|
/**
|
|
@@ -1106,9 +1290,59 @@ var LLMClient = class _LLMClient {
|
|
|
1106
1290
|
}
|
|
1107
1291
|
}
|
|
1108
1292
|
/**
|
|
1109
|
-
* List available
|
|
1293
|
+
* List available models with pricing.
|
|
1294
|
+
*
|
|
1295
|
+
* Returns the full `/v1/models` unified catalog (chat + image + music).
|
|
1296
|
+
* The shape preserves backwards compatibility — image/music rows have
|
|
1297
|
+
* `inputPrice = outputPrice = 0` since those fields don't apply, and
|
|
1298
|
+
* their per-call price surfaces via `flatPrice`.
|
|
1110
1299
|
*/
|
|
1111
1300
|
async listModels() {
|
|
1301
|
+
const raw = await this.fetchRawModels();
|
|
1302
|
+
return raw.map((m) => mapRawToModel(m));
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* List available image generation models with pricing.
|
|
1306
|
+
*
|
|
1307
|
+
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
|
|
1308
|
+
* image models live in the unified `/v1/models` catalog under
|
|
1309
|
+
* `categories: ["image", ...]`. This method filters that catalog so
|
|
1310
|
+
* existing callers keep working.
|
|
1311
|
+
*/
|
|
1312
|
+
async listImageModels() {
|
|
1313
|
+
const raw = await this.fetchRawModels();
|
|
1314
|
+
return raw.filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => mapRawToImageModel(m));
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* List all available models (chat, image, music, etc.) with pricing.
|
|
1318
|
+
*
|
|
1319
|
+
* @returns Array of all models with `type` field set from category
|
|
1320
|
+
* (`llm` for chat, `image` / `music` for media). Backwards-compat:
|
|
1321
|
+
* chat models always report `type: "llm"`.
|
|
1322
|
+
*/
|
|
1323
|
+
async listAllModels() {
|
|
1324
|
+
const raw = await this.fetchRawModels();
|
|
1325
|
+
const out = [];
|
|
1326
|
+
for (const m of raw) {
|
|
1327
|
+
const cats = Array.isArray(m.categories) ? m.categories : [];
|
|
1328
|
+
if (cats.includes("image")) {
|
|
1329
|
+
const model = mapRawToImageModel(m);
|
|
1330
|
+
model.type = "image";
|
|
1331
|
+
out.push(model);
|
|
1332
|
+
} else {
|
|
1333
|
+
const model = mapRawToModel(m);
|
|
1334
|
+
model.type = "llm";
|
|
1335
|
+
out.push(model);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
return out;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Internal: fetch the raw `/v1/models` catalog without normalising shape.
|
|
1342
|
+
* Used by listImageModels / listAllModels so each can pick category-
|
|
1343
|
+
* specific fields.
|
|
1344
|
+
*/
|
|
1345
|
+
async fetchRawModels() {
|
|
1112
1346
|
const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, {
|
|
1113
1347
|
method: "GET"
|
|
1114
1348
|
});
|
|
@@ -1126,65 +1360,8 @@ var LLMClient = class _LLMClient {
|
|
|
1126
1360
|
);
|
|
1127
1361
|
}
|
|
1128
1362
|
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
1363
|
return data.data || [];
|
|
1161
1364
|
}
|
|
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
1365
|
/**
|
|
1189
1366
|
* Edit an image using img2img.
|
|
1190
1367
|
*
|
|
@@ -1225,6 +1402,19 @@ var LLMClient = class _LLMClient {
|
|
|
1225
1402
|
const data = await this.requestWithPaymentRaw("/v1/search", body);
|
|
1226
1403
|
return data;
|
|
1227
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Generic Exa endpoint proxy (POST). Useful when you need an Exa API
|
|
1407
|
+
* surface that the typed wrappers below don't expose.
|
|
1408
|
+
*
|
|
1409
|
+
* @param path - Exa endpoint segment: "search" | "find-similar" | "contents" | "answer"
|
|
1410
|
+
* @param body - Request body (see Exa API docs)
|
|
1411
|
+
*
|
|
1412
|
+
* @example
|
|
1413
|
+
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
1414
|
+
*/
|
|
1415
|
+
async exa(path5, body) {
|
|
1416
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path5}`, body);
|
|
1417
|
+
}
|
|
1228
1418
|
/**
|
|
1229
1419
|
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
|
|
1230
1420
|
* Understands meaning, not just keywords. $0.01/call.
|
|
@@ -1278,9 +1468,7 @@ var LLMClient = class _LLMClient {
|
|
|
1278
1468
|
return data.data;
|
|
1279
1469
|
}
|
|
1280
1470
|
/**
|
|
1281
|
-
* Get USDC balance on Base
|
|
1282
|
-
*
|
|
1283
|
-
* Automatically detects mainnet vs testnet based on API URL.
|
|
1471
|
+
* Get USDC balance on Base mainnet.
|
|
1284
1472
|
*
|
|
1285
1473
|
* @returns USDC balance as a float (6 decimal places normalized)
|
|
1286
1474
|
*
|
|
@@ -1289,9 +1477,8 @@ var LLMClient = class _LLMClient {
|
|
|
1289
1477
|
* console.log(`Balance: $${balance.toFixed(2)} USDC`);
|
|
1290
1478
|
*/
|
|
1291
1479
|
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"];
|
|
1480
|
+
const usdcContract = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
1481
|
+
const rpcs = ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
|
|
1295
1482
|
const selector = "0x70a08231";
|
|
1296
1483
|
const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
|
|
1297
1484
|
const data = selector + paddedAddress;
|
|
@@ -1622,19 +1809,7 @@ var LLMClient = class _LLMClient {
|
|
|
1622
1809
|
getWalletAddress() {
|
|
1623
1810
|
return this.account.address;
|
|
1624
1811
|
}
|
|
1625
|
-
/**
|
|
1626
|
-
* Check if client is configured for testnet.
|
|
1627
|
-
*/
|
|
1628
|
-
isTestnet() {
|
|
1629
|
-
return this.apiUrl.includes("testnet.blockrun.ai");
|
|
1630
|
-
}
|
|
1631
1812
|
};
|
|
1632
|
-
function testnetClient(options = {}) {
|
|
1633
|
-
return new LLMClient({
|
|
1634
|
-
...options,
|
|
1635
|
-
apiUrl: TESTNET_API_URL
|
|
1636
|
-
});
|
|
1637
|
-
}
|
|
1638
1813
|
var client_default = LLMClient;
|
|
1639
1814
|
|
|
1640
1815
|
// src/image.ts
|
|
@@ -1723,10 +1898,15 @@ var ImageClient = class {
|
|
|
1723
1898
|
}
|
|
1724
1899
|
/**
|
|
1725
1900
|
* List available image generation models with pricing.
|
|
1901
|
+
*
|
|
1902
|
+
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
|
|
1903
|
+
* image models live in the unified `/v1/models` catalog under
|
|
1904
|
+
* `categories: ["image", ...]`. This method filters that catalog so
|
|
1905
|
+
* existing callers keep working.
|
|
1726
1906
|
*/
|
|
1727
1907
|
async listImageModels() {
|
|
1728
1908
|
const response = await this.fetchWithTimeout(
|
|
1729
|
-
`${this.apiUrl}/v1/
|
|
1909
|
+
`${this.apiUrl}/v1/models`,
|
|
1730
1910
|
{ method: "GET" }
|
|
1731
1911
|
);
|
|
1732
1912
|
if (!response.ok) {
|
|
@@ -1736,7 +1916,16 @@ var ImageClient = class {
|
|
|
1736
1916
|
);
|
|
1737
1917
|
}
|
|
1738
1918
|
const data = await response.json();
|
|
1739
|
-
return data.data || []
|
|
1919
|
+
return (data.data || []).filter((m) => Array.isArray(m.categories) && m.categories.includes("image")).map((m) => ({
|
|
1920
|
+
id: m.id,
|
|
1921
|
+
name: m.name || m.id,
|
|
1922
|
+
provider: m.provider || m.owned_by || "",
|
|
1923
|
+
description: m.description || "",
|
|
1924
|
+
pricePerImage: m.pricePerImage ?? m.price_per_image ?? m.pricing?.flat ?? m.flatPrice ?? m.flat_price ?? 0,
|
|
1925
|
+
supportedSizes: m.supportedSizes ?? m.supported_sizes,
|
|
1926
|
+
maxPromptLength: m.maxPromptLength ?? m.max_prompt_length,
|
|
1927
|
+
available: true
|
|
1928
|
+
}));
|
|
1740
1929
|
}
|
|
1741
1930
|
/**
|
|
1742
1931
|
* Make a request with automatic x402 payment handling.
|
|
@@ -2631,13 +2820,13 @@ function buildUrl(base, query) {
|
|
|
2631
2820
|
|
|
2632
2821
|
// src/wallet.ts
|
|
2633
2822
|
import { privateKeyToAccount as privateKeyToAccount7, generatePrivateKey } from "viem/accounts";
|
|
2634
|
-
import * as
|
|
2635
|
-
import * as
|
|
2636
|
-
import * as
|
|
2823
|
+
import * as fs2 from "fs";
|
|
2824
|
+
import * as path2 from "path";
|
|
2825
|
+
import * as os2 from "os";
|
|
2637
2826
|
var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2638
2827
|
var BASE_CHAIN_ID2 = "8453";
|
|
2639
|
-
var WALLET_DIR =
|
|
2640
|
-
var WALLET_FILE =
|
|
2828
|
+
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2829
|
+
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2641
2830
|
function createWallet() {
|
|
2642
2831
|
const privateKey = generatePrivateKey();
|
|
2643
2832
|
const account = privateKeyToAccount7(privateKey);
|
|
@@ -2647,27 +2836,27 @@ function createWallet() {
|
|
|
2647
2836
|
};
|
|
2648
2837
|
}
|
|
2649
2838
|
function saveWallet(privateKey) {
|
|
2650
|
-
if (!
|
|
2651
|
-
|
|
2839
|
+
if (!fs2.existsSync(WALLET_DIR)) {
|
|
2840
|
+
fs2.mkdirSync(WALLET_DIR, { recursive: true });
|
|
2652
2841
|
}
|
|
2653
|
-
|
|
2842
|
+
fs2.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
2654
2843
|
return WALLET_FILE;
|
|
2655
2844
|
}
|
|
2656
2845
|
function scanWallets() {
|
|
2657
|
-
const home =
|
|
2846
|
+
const home = os2.homedir();
|
|
2658
2847
|
const results = [];
|
|
2659
2848
|
try {
|
|
2660
|
-
const entries =
|
|
2849
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
2661
2850
|
for (const entry of entries) {
|
|
2662
2851
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2663
|
-
const walletFile =
|
|
2664
|
-
if (!
|
|
2852
|
+
const walletFile = path2.join(home, entry.name, "wallet.json");
|
|
2853
|
+
if (!fs2.existsSync(walletFile)) continue;
|
|
2665
2854
|
try {
|
|
2666
|
-
const data = JSON.parse(
|
|
2855
|
+
const data = JSON.parse(fs2.readFileSync(walletFile, "utf-8"));
|
|
2667
2856
|
const pk = data.privateKey || "";
|
|
2668
2857
|
const addr = data.address || "";
|
|
2669
2858
|
if (pk && addr) {
|
|
2670
|
-
const mtime =
|
|
2859
|
+
const mtime = fs2.statSync(walletFile).mtimeMs;
|
|
2671
2860
|
results.push({ mtime, privateKey: pk, address: addr });
|
|
2672
2861
|
}
|
|
2673
2862
|
} catch {
|
|
@@ -2682,13 +2871,13 @@ function scanWallets() {
|
|
|
2682
2871
|
function loadWallet() {
|
|
2683
2872
|
const wallets = scanWallets();
|
|
2684
2873
|
if (wallets.length > 0) return wallets[0].privateKey;
|
|
2685
|
-
if (
|
|
2686
|
-
const key =
|
|
2874
|
+
if (fs2.existsSync(WALLET_FILE)) {
|
|
2875
|
+
const key = fs2.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
2687
2876
|
if (key) return key;
|
|
2688
2877
|
}
|
|
2689
|
-
const legacyFile =
|
|
2690
|
-
if (
|
|
2691
|
-
const key =
|
|
2878
|
+
const legacyFile = path2.join(WALLET_DIR, "wallet.key");
|
|
2879
|
+
if (fs2.existsSync(legacyFile)) {
|
|
2880
|
+
const key = fs2.readFileSync(legacyFile, "utf-8").trim();
|
|
2692
2881
|
if (key) return key;
|
|
2693
2882
|
}
|
|
2694
2883
|
return null;
|
|
@@ -2783,11 +2972,11 @@ var WALLET_FILE_PATH = WALLET_FILE;
|
|
|
2783
2972
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
2784
2973
|
|
|
2785
2974
|
// src/solana-wallet.ts
|
|
2786
|
-
import * as
|
|
2787
|
-
import * as
|
|
2788
|
-
import * as
|
|
2789
|
-
var WALLET_DIR2 =
|
|
2790
|
-
var SOLANA_WALLET_FILE =
|
|
2975
|
+
import * as fs3 from "fs";
|
|
2976
|
+
import * as path3 from "path";
|
|
2977
|
+
import * as os3 from "os";
|
|
2978
|
+
var WALLET_DIR2 = path3.join(os3.homedir(), ".blockrun");
|
|
2979
|
+
var SOLANA_WALLET_FILE = path3.join(WALLET_DIR2, ".solana-session");
|
|
2791
2980
|
async function createSolanaWallet() {
|
|
2792
2981
|
const { Keypair } = await import("@solana/web3.js");
|
|
2793
2982
|
const bs58 = await import("bs58");
|
|
@@ -2816,39 +3005,39 @@ async function solanaPublicKey(privateKey) {
|
|
|
2816
3005
|
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
2817
3006
|
}
|
|
2818
3007
|
function saveSolanaWallet(privateKey) {
|
|
2819
|
-
if (!
|
|
2820
|
-
|
|
3008
|
+
if (!fs3.existsSync(WALLET_DIR2)) fs3.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
3009
|
+
fs3.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
2821
3010
|
return SOLANA_WALLET_FILE;
|
|
2822
3011
|
}
|
|
2823
3012
|
function scanSolanaWallets() {
|
|
2824
|
-
const home =
|
|
3013
|
+
const home = os3.homedir();
|
|
2825
3014
|
const results = [];
|
|
2826
3015
|
try {
|
|
2827
|
-
const entries =
|
|
3016
|
+
const entries = fs3.readdirSync(home, { withFileTypes: true });
|
|
2828
3017
|
for (const entry of entries) {
|
|
2829
3018
|
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
2830
|
-
const solanaWalletFile =
|
|
2831
|
-
if (
|
|
3019
|
+
const solanaWalletFile = path3.join(home, entry.name, "solana-wallet.json");
|
|
3020
|
+
if (fs3.existsSync(solanaWalletFile)) {
|
|
2832
3021
|
try {
|
|
2833
|
-
const data = JSON.parse(
|
|
3022
|
+
const data = JSON.parse(fs3.readFileSync(solanaWalletFile, "utf-8"));
|
|
2834
3023
|
const pk = data.privateKey || "";
|
|
2835
3024
|
const addr = data.address || "";
|
|
2836
3025
|
if (pk && addr) {
|
|
2837
|
-
const mtime =
|
|
3026
|
+
const mtime = fs3.statSync(solanaWalletFile).mtimeMs;
|
|
2838
3027
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2839
3028
|
}
|
|
2840
3029
|
} catch {
|
|
2841
3030
|
}
|
|
2842
3031
|
}
|
|
2843
3032
|
if (entry.name === ".brcc") {
|
|
2844
|
-
const brccWalletFile =
|
|
2845
|
-
if (
|
|
3033
|
+
const brccWalletFile = path3.join(home, entry.name, "wallet.json");
|
|
3034
|
+
if (fs3.existsSync(brccWalletFile)) {
|
|
2846
3035
|
try {
|
|
2847
|
-
const data = JSON.parse(
|
|
3036
|
+
const data = JSON.parse(fs3.readFileSync(brccWalletFile, "utf-8"));
|
|
2848
3037
|
const pk = data.privateKey || "";
|
|
2849
3038
|
const addr = data.address || "";
|
|
2850
3039
|
if (pk && addr) {
|
|
2851
|
-
const mtime =
|
|
3040
|
+
const mtime = fs3.statSync(brccWalletFile).mtimeMs;
|
|
2852
3041
|
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
2853
3042
|
}
|
|
2854
3043
|
} catch {
|
|
@@ -2864,8 +3053,8 @@ function scanSolanaWallets() {
|
|
|
2864
3053
|
function loadSolanaWallet() {
|
|
2865
3054
|
const wallets = scanSolanaWallets();
|
|
2866
3055
|
if (wallets.length > 0) return wallets[0].secretKey;
|
|
2867
|
-
if (
|
|
2868
|
-
const key =
|
|
3056
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3057
|
+
const key = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2869
3058
|
if (key) return key;
|
|
2870
3059
|
}
|
|
2871
3060
|
return null;
|
|
@@ -2880,8 +3069,8 @@ async function getOrCreateSolanaWallet() {
|
|
|
2880
3069
|
if (wallets.length > 0) {
|
|
2881
3070
|
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
2882
3071
|
}
|
|
2883
|
-
if (
|
|
2884
|
-
const fileKey =
|
|
3072
|
+
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
3073
|
+
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
2885
3074
|
if (fileKey) {
|
|
2886
3075
|
const address2 = await solanaPublicKey(fileKey);
|
|
2887
3076
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
@@ -3455,13 +3644,13 @@ function solanaClient(options = {}) {
|
|
|
3455
3644
|
}
|
|
3456
3645
|
|
|
3457
3646
|
// src/cache.ts
|
|
3458
|
-
import * as
|
|
3459
|
-
import * as
|
|
3460
|
-
import * as
|
|
3647
|
+
import * as fs4 from "fs";
|
|
3648
|
+
import * as path4 from "path";
|
|
3649
|
+
import * as os4 from "os";
|
|
3461
3650
|
import * as crypto2 from "crypto";
|
|
3462
|
-
var CACHE_DIR =
|
|
3463
|
-
var DATA_DIR =
|
|
3464
|
-
var
|
|
3651
|
+
var CACHE_DIR = path4.join(os4.homedir(), ".blockrun", "cache");
|
|
3652
|
+
var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3653
|
+
var COST_LOG_FILE2 = path4.join(os4.homedir(), ".blockrun", "cost_log.jsonl");
|
|
3465
3654
|
var DEFAULT_TTL = {
|
|
3466
3655
|
"/v1/x/": 3600 * 1e3,
|
|
3467
3656
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -3482,19 +3671,19 @@ function cacheKey(endpoint, body) {
|
|
|
3482
3671
|
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
3483
3672
|
}
|
|
3484
3673
|
function cachePath(key) {
|
|
3485
|
-
return
|
|
3674
|
+
return path4.join(CACHE_DIR, `${key}.json`);
|
|
3486
3675
|
}
|
|
3487
3676
|
function getCached(key) {
|
|
3488
3677
|
const filePath = cachePath(key);
|
|
3489
|
-
if (!
|
|
3678
|
+
if (!fs4.existsSync(filePath)) return null;
|
|
3490
3679
|
try {
|
|
3491
|
-
const raw =
|
|
3680
|
+
const raw = fs4.readFileSync(filePath, "utf-8");
|
|
3492
3681
|
const entry = JSON.parse(raw);
|
|
3493
3682
|
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
3494
3683
|
if (ttl <= 0) return null;
|
|
3495
3684
|
if (Date.now() - entry.cachedAt > ttl) {
|
|
3496
3685
|
try {
|
|
3497
|
-
|
|
3686
|
+
fs4.unlinkSync(filePath);
|
|
3498
3687
|
} catch {
|
|
3499
3688
|
}
|
|
3500
3689
|
return null;
|
|
@@ -3513,7 +3702,7 @@ function getCachedByRequest(endpoint, body) {
|
|
|
3513
3702
|
function setCache(key, data, ttlMs) {
|
|
3514
3703
|
if (ttlMs <= 0) return;
|
|
3515
3704
|
try {
|
|
3516
|
-
|
|
3705
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3517
3706
|
} catch {
|
|
3518
3707
|
}
|
|
3519
3708
|
const entry = {
|
|
@@ -3522,7 +3711,7 @@ function setCache(key, data, ttlMs) {
|
|
|
3522
3711
|
ttlMs
|
|
3523
3712
|
};
|
|
3524
3713
|
try {
|
|
3525
|
-
|
|
3714
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3526
3715
|
} catch {
|
|
3527
3716
|
}
|
|
3528
3717
|
}
|
|
@@ -3545,7 +3734,7 @@ function readableFilename(endpoint, body) {
|
|
|
3545
3734
|
}
|
|
3546
3735
|
function saveReadable(endpoint, body, response, costUsd) {
|
|
3547
3736
|
try {
|
|
3548
|
-
|
|
3737
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
3549
3738
|
} catch {
|
|
3550
3739
|
}
|
|
3551
3740
|
const filename = readableFilename(endpoint, body);
|
|
@@ -3557,14 +3746,14 @@ function saveReadable(endpoint, body, response, costUsd) {
|
|
|
3557
3746
|
response
|
|
3558
3747
|
};
|
|
3559
3748
|
try {
|
|
3560
|
-
|
|
3749
|
+
fs4.writeFileSync(path4.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
3561
3750
|
} catch {
|
|
3562
3751
|
}
|
|
3563
3752
|
}
|
|
3564
3753
|
function appendCostLog(endpoint, costUsd) {
|
|
3565
3754
|
if (costUsd <= 0) return;
|
|
3566
3755
|
try {
|
|
3567
|
-
|
|
3756
|
+
fs4.mkdirSync(path4.dirname(COST_LOG_FILE2), { recursive: true });
|
|
3568
3757
|
} catch {
|
|
3569
3758
|
}
|
|
3570
3759
|
const entry = {
|
|
@@ -3573,7 +3762,7 @@ function appendCostLog(endpoint, costUsd) {
|
|
|
3573
3762
|
cost_usd: costUsd
|
|
3574
3763
|
};
|
|
3575
3764
|
try {
|
|
3576
|
-
|
|
3765
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3577
3766
|
} catch {
|
|
3578
3767
|
}
|
|
3579
3768
|
}
|
|
@@ -3581,7 +3770,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3581
3770
|
const ttl = getTtl(endpoint);
|
|
3582
3771
|
if (ttl > 0) {
|
|
3583
3772
|
try {
|
|
3584
|
-
|
|
3773
|
+
fs4.mkdirSync(CACHE_DIR, { recursive: true });
|
|
3585
3774
|
} catch {
|
|
3586
3775
|
}
|
|
3587
3776
|
const key = cacheKey(endpoint, body);
|
|
@@ -3593,7 +3782,7 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3593
3782
|
costUsd
|
|
3594
3783
|
};
|
|
3595
3784
|
try {
|
|
3596
|
-
|
|
3785
|
+
fs4.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
3597
3786
|
} catch {
|
|
3598
3787
|
}
|
|
3599
3788
|
}
|
|
@@ -3601,14 +3790,14 @@ function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
|
3601
3790
|
appendCostLog(endpoint, costUsd);
|
|
3602
3791
|
}
|
|
3603
3792
|
function clearCache() {
|
|
3604
|
-
if (!
|
|
3793
|
+
if (!fs4.existsSync(CACHE_DIR)) return 0;
|
|
3605
3794
|
let count = 0;
|
|
3606
3795
|
try {
|
|
3607
|
-
const files =
|
|
3796
|
+
const files = fs4.readdirSync(CACHE_DIR);
|
|
3608
3797
|
for (const file of files) {
|
|
3609
3798
|
if (file.endsWith(".json")) {
|
|
3610
3799
|
try {
|
|
3611
|
-
|
|
3800
|
+
fs4.unlinkSync(path4.join(CACHE_DIR, file));
|
|
3612
3801
|
count++;
|
|
3613
3802
|
} catch {
|
|
3614
3803
|
}
|
|
@@ -3619,14 +3808,14 @@ function clearCache() {
|
|
|
3619
3808
|
return count;
|
|
3620
3809
|
}
|
|
3621
3810
|
function getCostLogSummary() {
|
|
3622
|
-
if (!
|
|
3811
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3623
3812
|
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3624
3813
|
}
|
|
3625
3814
|
let totalUsd = 0;
|
|
3626
3815
|
let calls = 0;
|
|
3627
3816
|
const byEndpoint = {};
|
|
3628
3817
|
try {
|
|
3629
|
-
const content =
|
|
3818
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3630
3819
|
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
3631
3820
|
for (const line of content.split("\n")) {
|
|
3632
3821
|
if (!line) continue;
|
|
@@ -3681,47 +3870,6 @@ async function status() {
|
|
|
3681
3870
|
return { address, balance };
|
|
3682
3871
|
}
|
|
3683
3872
|
|
|
3684
|
-
// src/cost-log.ts
|
|
3685
|
-
import * as fs4 from "fs";
|
|
3686
|
-
import * as path4 from "path";
|
|
3687
|
-
import * as os4 from "os";
|
|
3688
|
-
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
3689
|
-
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
3690
|
-
function logCost(entry) {
|
|
3691
|
-
try {
|
|
3692
|
-
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
3693
|
-
} catch {
|
|
3694
|
-
}
|
|
3695
|
-
try {
|
|
3696
|
-
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
3697
|
-
} catch {
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
function getCostSummary() {
|
|
3701
|
-
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
3702
|
-
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3703
|
-
}
|
|
3704
|
-
let totalUsd = 0;
|
|
3705
|
-
let calls = 0;
|
|
3706
|
-
const byModel = {};
|
|
3707
|
-
try {
|
|
3708
|
-
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
3709
|
-
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
3710
|
-
for (const line of content.split("\n")) {
|
|
3711
|
-
if (!line) continue;
|
|
3712
|
-
try {
|
|
3713
|
-
const entry = JSON.parse(line);
|
|
3714
|
-
totalUsd += entry.costUsd;
|
|
3715
|
-
calls += 1;
|
|
3716
|
-
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
3717
|
-
} catch {
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
} catch {
|
|
3721
|
-
}
|
|
3722
|
-
return { totalUsd, calls, byModel };
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
3873
|
// src/openai-compat.ts
|
|
3726
3874
|
var StreamingResponse = class {
|
|
3727
3875
|
reader;
|
|
@@ -4029,7 +4177,6 @@ export {
|
|
|
4029
4177
|
solanaKeyToBytes,
|
|
4030
4178
|
solanaPublicKey,
|
|
4031
4179
|
status,
|
|
4032
|
-
testnetClient,
|
|
4033
4180
|
validateMaxTokens,
|
|
4034
4181
|
validateModel,
|
|
4035
4182
|
validateTemperature,
|