@blockrun/llm 1.4.3 → 1.5.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/dist/index.cjs +314 -21
- package/dist/index.d.cts +109 -11
- package/dist/index.d.ts +109 -11
- package/dist/index.js +307 -20
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
36
36
|
BlockrunError: () => BlockrunError,
|
|
37
37
|
ImageClient: () => ImageClient,
|
|
38
|
+
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
38
39
|
LLMClient: () => LLMClient,
|
|
39
40
|
OpenAI: () => OpenAI,
|
|
40
41
|
PaymentError: () => PaymentError,
|
|
@@ -58,6 +59,7 @@ __export(index_exports, {
|
|
|
58
59
|
formatWalletCreatedMessage: () => formatWalletCreatedMessage,
|
|
59
60
|
getCached: () => getCached,
|
|
60
61
|
getCachedByRequest: () => getCachedByRequest,
|
|
62
|
+
getCostLogSummary: () => getCostLogSummary,
|
|
61
63
|
getCostSummary: () => getCostSummary,
|
|
62
64
|
getEip681Uri: () => getEip681Uri,
|
|
63
65
|
getOrCreateSolanaWallet: () => getOrCreateSolanaWallet,
|
|
@@ -80,7 +82,11 @@ __export(index_exports, {
|
|
|
80
82
|
solanaKeyToBytes: () => solanaKeyToBytes,
|
|
81
83
|
solanaPublicKey: () => solanaPublicKey,
|
|
82
84
|
status: () => status,
|
|
83
|
-
testnetClient: () => testnetClient
|
|
85
|
+
testnetClient: () => testnetClient,
|
|
86
|
+
validateMaxTokens: () => validateMaxTokens,
|
|
87
|
+
validateModel: () => validateModel,
|
|
88
|
+
validateTemperature: () => validateTemperature,
|
|
89
|
+
validateTopP: () => validateTopP
|
|
84
90
|
});
|
|
85
91
|
module.exports = __toCommonJS(index_exports);
|
|
86
92
|
|
|
@@ -302,6 +308,61 @@ function extractPaymentDetails(paymentRequired, preferredNetwork) {
|
|
|
302
308
|
|
|
303
309
|
// src/validation.ts
|
|
304
310
|
var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
|
|
311
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set([
|
|
312
|
+
"openai",
|
|
313
|
+
"anthropic",
|
|
314
|
+
"google",
|
|
315
|
+
"deepseek",
|
|
316
|
+
"mistralai",
|
|
317
|
+
"meta-llama",
|
|
318
|
+
"together",
|
|
319
|
+
"xai",
|
|
320
|
+
"moonshot",
|
|
321
|
+
"nvidia",
|
|
322
|
+
"minimax",
|
|
323
|
+
"zai"
|
|
324
|
+
]);
|
|
325
|
+
function validateModel(model) {
|
|
326
|
+
if (!model || typeof model !== "string") {
|
|
327
|
+
throw new Error("Model must be a non-empty string");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function validateMaxTokens(maxTokens) {
|
|
331
|
+
if (maxTokens === void 0 || maxTokens === null) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens)) {
|
|
335
|
+
throw new Error("maxTokens must be an integer");
|
|
336
|
+
}
|
|
337
|
+
if (maxTokens < 1) {
|
|
338
|
+
throw new Error("maxTokens must be positive (minimum: 1)");
|
|
339
|
+
}
|
|
340
|
+
if (maxTokens > 1e5) {
|
|
341
|
+
throw new Error("maxTokens too large (maximum: 100000)");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function validateTemperature(temperature) {
|
|
345
|
+
if (temperature === void 0 || temperature === null) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (typeof temperature !== "number") {
|
|
349
|
+
throw new Error("temperature must be a number");
|
|
350
|
+
}
|
|
351
|
+
if (temperature < 0 || temperature > 2) {
|
|
352
|
+
throw new Error("temperature must be between 0 and 2");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function validateTopP(topP) {
|
|
356
|
+
if (topP === void 0 || topP === null) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (typeof topP !== "number") {
|
|
360
|
+
throw new Error("topP must be a number");
|
|
361
|
+
}
|
|
362
|
+
if (topP < 0 || topP > 1) {
|
|
363
|
+
throw new Error("topP must be between 0 and 1");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
305
366
|
function validatePrivateKey(key) {
|
|
306
367
|
if (typeof key !== "string") {
|
|
307
368
|
throw new Error("Private key must be a string");
|
|
@@ -377,7 +438,7 @@ var DEFAULT_API_URL = "https://blockrun.ai/api";
|
|
|
377
438
|
var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
|
|
378
439
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
379
440
|
var DEFAULT_TIMEOUT = 6e4;
|
|
380
|
-
var SDK_VERSION = "
|
|
441
|
+
var SDK_VERSION = "1.5.0";
|
|
381
442
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
382
443
|
var LLMClient = class {
|
|
383
444
|
static DEFAULT_API_URL = DEFAULT_API_URL;
|
|
@@ -414,13 +475,13 @@ var LLMClient = class {
|
|
|
414
475
|
/**
|
|
415
476
|
* Simple 1-line chat interface.
|
|
416
477
|
*
|
|
417
|
-
* @param model - Model ID (e.g., 'openai/gpt-
|
|
478
|
+
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
|
|
418
479
|
* @param prompt - User message
|
|
419
480
|
* @param options - Optional chat parameters
|
|
420
481
|
* @returns Assistant's response text
|
|
421
482
|
*
|
|
422
483
|
* @example
|
|
423
|
-
* const response = await client.chat('gpt-
|
|
484
|
+
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
|
|
424
485
|
* console.log(response); // 'The capital of France is Paris.'
|
|
425
486
|
*/
|
|
426
487
|
async chat(model, prompt, options) {
|
|
@@ -566,6 +627,27 @@ var LLMClient = class {
|
|
|
566
627
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
567
628
|
body: JSON.stringify(body)
|
|
568
629
|
});
|
|
630
|
+
if (response.status === 502 || response.status === 503) {
|
|
631
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
632
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
635
|
+
body: JSON.stringify(body)
|
|
636
|
+
});
|
|
637
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
638
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
|
|
639
|
+
if (!retryResp.ok) {
|
|
640
|
+
let errorBody;
|
|
641
|
+
try {
|
|
642
|
+
errorBody = await retryResp.json();
|
|
643
|
+
} catch {
|
|
644
|
+
errorBody = { error: "Request failed" };
|
|
645
|
+
}
|
|
646
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
647
|
+
}
|
|
648
|
+
return retryResp.json();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
569
651
|
if (response.status === 402) {
|
|
570
652
|
return this.handlePaymentAndRetry(url, body, response);
|
|
571
653
|
}
|
|
@@ -631,6 +713,34 @@ var LLMClient = class {
|
|
|
631
713
|
},
|
|
632
714
|
body: JSON.stringify(body)
|
|
633
715
|
});
|
|
716
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
717
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
718
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
719
|
+
method: "POST",
|
|
720
|
+
headers: {
|
|
721
|
+
"Content-Type": "application/json",
|
|
722
|
+
"User-Agent": USER_AGENT,
|
|
723
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
724
|
+
},
|
|
725
|
+
body: JSON.stringify(body)
|
|
726
|
+
});
|
|
727
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
728
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
729
|
+
if (!retryResp2.ok) {
|
|
730
|
+
let errorBody;
|
|
731
|
+
try {
|
|
732
|
+
errorBody = await retryResp2.json();
|
|
733
|
+
} catch {
|
|
734
|
+
errorBody = { error: "Request failed" };
|
|
735
|
+
}
|
|
736
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
737
|
+
}
|
|
738
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
739
|
+
this.sessionCalls += 1;
|
|
740
|
+
this.sessionTotalUsd += costUsd2;
|
|
741
|
+
return retryResp2.json();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
634
744
|
if (retryResponse.status === 402) {
|
|
635
745
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
636
746
|
}
|
|
@@ -663,6 +773,27 @@ var LLMClient = class {
|
|
|
663
773
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
664
774
|
body: JSON.stringify(body)
|
|
665
775
|
});
|
|
776
|
+
if (response.status === 502 || response.status === 503) {
|
|
777
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
778
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
781
|
+
body: JSON.stringify(body)
|
|
782
|
+
});
|
|
783
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
784
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetryRaw(url, body, retryResp);
|
|
785
|
+
if (!retryResp.ok) {
|
|
786
|
+
let errorBody;
|
|
787
|
+
try {
|
|
788
|
+
errorBody = await retryResp.json();
|
|
789
|
+
} catch {
|
|
790
|
+
errorBody = { error: "Request failed" };
|
|
791
|
+
}
|
|
792
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
793
|
+
}
|
|
794
|
+
return retryResp.json();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
666
797
|
if (response.status === 402) {
|
|
667
798
|
return this.handlePaymentAndRetryRaw(url, body, response);
|
|
668
799
|
}
|
|
@@ -728,6 +859,34 @@ var LLMClient = class {
|
|
|
728
859
|
},
|
|
729
860
|
body: JSON.stringify(body)
|
|
730
861
|
});
|
|
862
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
863
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
864
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
865
|
+
method: "POST",
|
|
866
|
+
headers: {
|
|
867
|
+
"Content-Type": "application/json",
|
|
868
|
+
"User-Agent": USER_AGENT,
|
|
869
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
870
|
+
},
|
|
871
|
+
body: JSON.stringify(body)
|
|
872
|
+
});
|
|
873
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
874
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
875
|
+
if (!retryResp2.ok) {
|
|
876
|
+
let errorBody;
|
|
877
|
+
try {
|
|
878
|
+
errorBody = await retryResp2.json();
|
|
879
|
+
} catch {
|
|
880
|
+
errorBody = { error: "Request failed" };
|
|
881
|
+
}
|
|
882
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
883
|
+
}
|
|
884
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
885
|
+
this.sessionCalls += 1;
|
|
886
|
+
this.sessionTotalUsd += costUsd2;
|
|
887
|
+
return retryResp2.json();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
731
890
|
if (retryResponse.status === 402) {
|
|
732
891
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
733
892
|
}
|
|
@@ -760,6 +919,26 @@ var LLMClient = class {
|
|
|
760
919
|
method: "GET",
|
|
761
920
|
headers: { "User-Agent": USER_AGENT }
|
|
762
921
|
});
|
|
922
|
+
if (response.status === 502 || response.status === 503) {
|
|
923
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
924
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
925
|
+
method: "GET",
|
|
926
|
+
headers: { "User-Agent": USER_AGENT }
|
|
927
|
+
});
|
|
928
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
929
|
+
if (retryResp.status === 402) return this.handleGetPaymentAndRetryRaw(url, endpoint, params, retryResp);
|
|
930
|
+
if (!retryResp.ok) {
|
|
931
|
+
let errorBody;
|
|
932
|
+
try {
|
|
933
|
+
errorBody = await retryResp.json();
|
|
934
|
+
} catch {
|
|
935
|
+
errorBody = { error: "Request failed" };
|
|
936
|
+
}
|
|
937
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
938
|
+
}
|
|
939
|
+
return retryResp.json();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
763
942
|
if (response.status === 402) {
|
|
764
943
|
return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
|
|
765
944
|
}
|
|
@@ -825,6 +1004,32 @@ var LLMClient = class {
|
|
|
825
1004
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
826
1005
|
}
|
|
827
1006
|
});
|
|
1007
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
1008
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1009
|
+
const retryResp2 = await this.fetchWithTimeout(retryUrl, {
|
|
1010
|
+
method: "GET",
|
|
1011
|
+
headers: {
|
|
1012
|
+
"User-Agent": USER_AGENT,
|
|
1013
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
1017
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
1018
|
+
if (!retryResp2.ok) {
|
|
1019
|
+
let errorBody;
|
|
1020
|
+
try {
|
|
1021
|
+
errorBody = await retryResp2.json();
|
|
1022
|
+
} catch {
|
|
1023
|
+
errorBody = { error: "Request failed" };
|
|
1024
|
+
}
|
|
1025
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
1026
|
+
}
|
|
1027
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1028
|
+
this.sessionCalls += 1;
|
|
1029
|
+
this.sessionTotalUsd += costUsd2;
|
|
1030
|
+
return retryResp2.json();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
828
1033
|
if (retryResponse.status === 402) {
|
|
829
1034
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
830
1035
|
}
|
|
@@ -2293,6 +2498,8 @@ var path3 = __toESM(require("path"), 1);
|
|
|
2293
2498
|
var os3 = __toESM(require("os"), 1);
|
|
2294
2499
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
2295
2500
|
var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
|
|
2501
|
+
var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
|
|
2502
|
+
var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
|
|
2296
2503
|
var DEFAULT_TTL = {
|
|
2297
2504
|
"/v1/x/": 3600 * 1e3,
|
|
2298
2505
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -2357,26 +2564,80 @@ function setCache(key, data, ttlMs) {
|
|
|
2357
2564
|
} catch {
|
|
2358
2565
|
}
|
|
2359
2566
|
}
|
|
2360
|
-
function
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2567
|
+
function readableFilename(endpoint, body) {
|
|
2568
|
+
const now = /* @__PURE__ */ new Date();
|
|
2569
|
+
const ts = now.toISOString().slice(0, 10) + "_" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
|
|
2570
|
+
let ep = endpoint.replace(/\/+$/, "").split("/").pop() || "";
|
|
2571
|
+
if (endpoint.includes("/v1/chat/")) {
|
|
2572
|
+
ep = "chat";
|
|
2573
|
+
} else if (endpoint.includes("/v1/x/")) {
|
|
2574
|
+
ep = "x_" + ep;
|
|
2575
|
+
} else if (endpoint.includes("/v1/search")) {
|
|
2576
|
+
ep = "search";
|
|
2577
|
+
} else if (endpoint.includes("/v1/image")) {
|
|
2578
|
+
ep = "image";
|
|
2579
|
+
}
|
|
2580
|
+
let label = body.query || body.username || body.handle || body.model || (typeof body.prompt === "string" ? body.prompt.slice(0, 40) : "") || "";
|
|
2581
|
+
label = String(label).replace(/[^a-zA-Z0-9_\-]/g, "_").slice(0, 40).replace(/^_+|_+$/g, "");
|
|
2582
|
+
return label ? `${ep}_${ts}_${label}.json` : `${ep}_${ts}.json`;
|
|
2583
|
+
}
|
|
2584
|
+
function saveReadable(endpoint, body, response, costUsd) {
|
|
2363
2585
|
try {
|
|
2364
|
-
fs3.mkdirSync(
|
|
2586
|
+
fs3.mkdirSync(DATA_DIR, { recursive: true });
|
|
2365
2587
|
} catch {
|
|
2366
2588
|
}
|
|
2367
|
-
const
|
|
2589
|
+
const filename = readableFilename(endpoint, body);
|
|
2368
2590
|
const entry = {
|
|
2369
|
-
|
|
2591
|
+
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2370
2592
|
endpoint,
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2593
|
+
cost_usd: costUsd,
|
|
2594
|
+
request: body,
|
|
2595
|
+
response
|
|
2374
2596
|
};
|
|
2375
2597
|
try {
|
|
2376
|
-
fs3.writeFileSync(
|
|
2598
|
+
fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
2599
|
+
} catch {
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
function appendCostLog(endpoint, costUsd) {
|
|
2603
|
+
if (costUsd <= 0) return;
|
|
2604
|
+
try {
|
|
2605
|
+
fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
|
|
2606
|
+
} catch {
|
|
2607
|
+
}
|
|
2608
|
+
const entry = {
|
|
2609
|
+
ts: Date.now() / 1e3,
|
|
2610
|
+
endpoint,
|
|
2611
|
+
cost_usd: costUsd
|
|
2612
|
+
};
|
|
2613
|
+
try {
|
|
2614
|
+
fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
2377
2615
|
} catch {
|
|
2378
2616
|
}
|
|
2379
2617
|
}
|
|
2618
|
+
function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
2619
|
+
const ttl = getTtl(endpoint);
|
|
2620
|
+
if (ttl > 0) {
|
|
2621
|
+
try {
|
|
2622
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
2623
|
+
} catch {
|
|
2624
|
+
}
|
|
2625
|
+
const key = cacheKey(endpoint, body);
|
|
2626
|
+
const entry = {
|
|
2627
|
+
cachedAt: Date.now(),
|
|
2628
|
+
endpoint,
|
|
2629
|
+
body,
|
|
2630
|
+
response,
|
|
2631
|
+
costUsd
|
|
2632
|
+
};
|
|
2633
|
+
try {
|
|
2634
|
+
fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
2635
|
+
} catch {
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
saveReadable(endpoint, body, response, costUsd);
|
|
2639
|
+
appendCostLog(endpoint, costUsd);
|
|
2640
|
+
}
|
|
2380
2641
|
function clearCache() {
|
|
2381
2642
|
if (!fs3.existsSync(CACHE_DIR)) return 0;
|
|
2382
2643
|
let count = 0;
|
|
@@ -2395,6 +2656,32 @@ function clearCache() {
|
|
|
2395
2656
|
}
|
|
2396
2657
|
return count;
|
|
2397
2658
|
}
|
|
2659
|
+
function getCostLogSummary() {
|
|
2660
|
+
if (!fs3.existsSync(COST_LOG_FILE)) {
|
|
2661
|
+
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2662
|
+
}
|
|
2663
|
+
let totalUsd = 0;
|
|
2664
|
+
let calls = 0;
|
|
2665
|
+
const byEndpoint = {};
|
|
2666
|
+
try {
|
|
2667
|
+
const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
|
|
2668
|
+
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2669
|
+
for (const line of content.split("\n")) {
|
|
2670
|
+
if (!line) continue;
|
|
2671
|
+
try {
|
|
2672
|
+
const entry = JSON.parse(line);
|
|
2673
|
+
const cost = entry.cost_usd ?? 0;
|
|
2674
|
+
const ep = entry.endpoint ?? "unknown";
|
|
2675
|
+
totalUsd += cost;
|
|
2676
|
+
calls += 1;
|
|
2677
|
+
byEndpoint[ep] = (byEndpoint[ep] || 0) + cost;
|
|
2678
|
+
} catch {
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
} catch {
|
|
2682
|
+
}
|
|
2683
|
+
return { totalUsd, calls, byEndpoint };
|
|
2684
|
+
}
|
|
2398
2685
|
|
|
2399
2686
|
// src/setup.ts
|
|
2400
2687
|
function setupAgentWallet(options) {
|
|
@@ -2436,27 +2723,27 @@ async function status() {
|
|
|
2436
2723
|
var fs4 = __toESM(require("fs"), 1);
|
|
2437
2724
|
var path4 = __toESM(require("path"), 1);
|
|
2438
2725
|
var os4 = __toESM(require("os"), 1);
|
|
2439
|
-
var
|
|
2440
|
-
var
|
|
2726
|
+
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
2727
|
+
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
2441
2728
|
function logCost(entry) {
|
|
2442
2729
|
try {
|
|
2443
|
-
fs4.mkdirSync(
|
|
2730
|
+
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
2444
2731
|
} catch {
|
|
2445
2732
|
}
|
|
2446
2733
|
try {
|
|
2447
|
-
fs4.appendFileSync(
|
|
2734
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
2448
2735
|
} catch {
|
|
2449
2736
|
}
|
|
2450
2737
|
}
|
|
2451
2738
|
function getCostSummary() {
|
|
2452
|
-
if (!fs4.existsSync(
|
|
2739
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
2453
2740
|
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2454
2741
|
}
|
|
2455
2742
|
let totalUsd = 0;
|
|
2456
2743
|
let calls = 0;
|
|
2457
2744
|
const byModel = {};
|
|
2458
2745
|
try {
|
|
2459
|
-
const content = fs4.readFileSync(
|
|
2746
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
2460
2747
|
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2461
2748
|
for (const line of content.split("\n")) {
|
|
2462
2749
|
if (!line) continue;
|
|
@@ -2758,6 +3045,7 @@ var AnthropicClient = class {
|
|
|
2758
3045
|
BASE_CHAIN_ID,
|
|
2759
3046
|
BlockrunError,
|
|
2760
3047
|
ImageClient,
|
|
3048
|
+
KNOWN_PROVIDERS,
|
|
2761
3049
|
LLMClient,
|
|
2762
3050
|
OpenAI,
|
|
2763
3051
|
PaymentError,
|
|
@@ -2780,6 +3068,7 @@ var AnthropicClient = class {
|
|
|
2780
3068
|
formatWalletCreatedMessage,
|
|
2781
3069
|
getCached,
|
|
2782
3070
|
getCachedByRequest,
|
|
3071
|
+
getCostLogSummary,
|
|
2783
3072
|
getCostSummary,
|
|
2784
3073
|
getEip681Uri,
|
|
2785
3074
|
getOrCreateSolanaWallet,
|
|
@@ -2802,5 +3091,9 @@ var AnthropicClient = class {
|
|
|
2802
3091
|
solanaKeyToBytes,
|
|
2803
3092
|
solanaPublicKey,
|
|
2804
3093
|
status,
|
|
2805
|
-
testnetClient
|
|
3094
|
+
testnetClient,
|
|
3095
|
+
validateMaxTokens,
|
|
3096
|
+
validateModel,
|
|
3097
|
+
validateTemperature,
|
|
3098
|
+
validateTopP
|
|
2806
3099
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -140,10 +140,48 @@ interface SearchParameters {
|
|
|
140
140
|
toDate?: string;
|
|
141
141
|
maxSearchResults?: number;
|
|
142
142
|
}
|
|
143
|
+
/** Usage info for Live Search sources */
|
|
144
|
+
interface SearchUsage {
|
|
145
|
+
/** Number of search sources used in the response */
|
|
146
|
+
numSourcesUsed?: number;
|
|
147
|
+
}
|
|
143
148
|
interface Spending {
|
|
144
149
|
totalUsd: number;
|
|
145
150
|
calls: number;
|
|
146
151
|
}
|
|
152
|
+
/** Pre-request cost estimate for a chat call */
|
|
153
|
+
interface CostEstimate {
|
|
154
|
+
/** Model ID used for the estimate */
|
|
155
|
+
model: string;
|
|
156
|
+
/** Estimated input token count */
|
|
157
|
+
estimatedInputTokens: number;
|
|
158
|
+
/** Estimated output token count */
|
|
159
|
+
estimatedOutputTokens: number;
|
|
160
|
+
/** Estimated cost in USD */
|
|
161
|
+
estimatedCostUsd: number;
|
|
162
|
+
}
|
|
163
|
+
/** Per-call spending report with running session totals */
|
|
164
|
+
interface SpendingReport {
|
|
165
|
+
/** Model ID used */
|
|
166
|
+
model: string;
|
|
167
|
+
/** Input tokens consumed */
|
|
168
|
+
inputTokens: number;
|
|
169
|
+
/** Output tokens consumed */
|
|
170
|
+
outputTokens: number;
|
|
171
|
+
/** Cost of this call in USD */
|
|
172
|
+
costUsd: number;
|
|
173
|
+
/** Cumulative session spend in USD */
|
|
174
|
+
sessionTotalUsd: number;
|
|
175
|
+
/** Total number of calls in this session */
|
|
176
|
+
sessionCalls: number;
|
|
177
|
+
}
|
|
178
|
+
/** Chat response bundled with its spending report */
|
|
179
|
+
interface ChatResponseWithCost {
|
|
180
|
+
/** The chat completion response */
|
|
181
|
+
response: ChatResponse;
|
|
182
|
+
/** Spending report for this call */
|
|
183
|
+
spendingReport: SpendingReport;
|
|
184
|
+
}
|
|
147
185
|
interface ResourceInfo {
|
|
148
186
|
url: string;
|
|
149
187
|
description?: string;
|
|
@@ -184,9 +222,9 @@ interface ChatOptions {
|
|
|
184
222
|
temperature?: number;
|
|
185
223
|
/** Nucleus sampling parameter */
|
|
186
224
|
topP?: number;
|
|
187
|
-
/** Enable
|
|
225
|
+
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
|
|
188
226
|
search?: boolean;
|
|
189
|
-
/** Full
|
|
227
|
+
/** Full Live Search configuration (for search-enabled models) */
|
|
190
228
|
searchParameters?: SearchParameters;
|
|
191
229
|
}
|
|
192
230
|
interface ChatCompletionOptions {
|
|
@@ -196,9 +234,9 @@ interface ChatCompletionOptions {
|
|
|
196
234
|
temperature?: number;
|
|
197
235
|
/** Nucleus sampling parameter */
|
|
198
236
|
topP?: number;
|
|
199
|
-
/** Enable
|
|
237
|
+
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
|
|
200
238
|
search?: boolean;
|
|
201
|
-
/** Full
|
|
239
|
+
/** Full Live Search configuration (for search-enabled models) */
|
|
202
240
|
searchParameters?: SearchParameters;
|
|
203
241
|
/** Tool definitions for function calling */
|
|
204
242
|
tools?: Tool[];
|
|
@@ -406,7 +444,7 @@ declare class APIError extends BlockrunError {
|
|
|
406
444
|
* // Option 2: Pass private key directly
|
|
407
445
|
* const client = new LLMClient({ privateKey: '0x...' });
|
|
408
446
|
*
|
|
409
|
-
* const response = await client.chat('openai/gpt-
|
|
447
|
+
* const response = await client.chat('openai/gpt-5.2', 'Hello!');
|
|
410
448
|
* console.log(response);
|
|
411
449
|
*/
|
|
412
450
|
|
|
@@ -454,13 +492,13 @@ declare class LLMClient {
|
|
|
454
492
|
/**
|
|
455
493
|
* Simple 1-line chat interface.
|
|
456
494
|
*
|
|
457
|
-
* @param model - Model ID (e.g., 'openai/gpt-
|
|
495
|
+
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
|
|
458
496
|
* @param prompt - User message
|
|
459
497
|
* @param options - Optional chat parameters
|
|
460
498
|
* @returns Assistant's response text
|
|
461
499
|
*
|
|
462
500
|
* @example
|
|
463
|
-
* const response = await client.chat('gpt-
|
|
501
|
+
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
|
|
464
502
|
* console.log(response); // 'The capital of France is Paris.'
|
|
465
503
|
*/
|
|
466
504
|
chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
|
|
@@ -1108,7 +1146,7 @@ declare const WALLET_DIR_PATH: string;
|
|
|
1108
1146
|
* // Or pass key directly
|
|
1109
1147
|
* const client = new SolanaLLMClient({ privateKey: 'your-bs58-key' });
|
|
1110
1148
|
*
|
|
1111
|
-
* const response = await client.chat('openai/gpt-
|
|
1149
|
+
* const response = await client.chat('openai/gpt-5.2', 'gm Solana');
|
|
1112
1150
|
*/
|
|
1113
1151
|
|
|
1114
1152
|
interface SolanaLLMClientOptions {
|
|
@@ -1228,6 +1266,11 @@ declare function getCachedByRequest(endpoint: string, body: Record<string, unkno
|
|
|
1228
1266
|
declare function setCache(key: string, data: unknown, ttlMs: number): void;
|
|
1229
1267
|
declare function saveToCache(endpoint: string, body: Record<string, unknown>, response: unknown, costUsd?: number): void;
|
|
1230
1268
|
declare function clearCache(): number;
|
|
1269
|
+
declare function getCostLogSummary(): {
|
|
1270
|
+
totalUsd: number;
|
|
1271
|
+
calls: number;
|
|
1272
|
+
byEndpoint: Record<string, number>;
|
|
1273
|
+
};
|
|
1231
1274
|
|
|
1232
1275
|
/**
|
|
1233
1276
|
* Agent wallet setup utilities.
|
|
@@ -1277,7 +1320,7 @@ declare function getCostSummary(): {
|
|
|
1277
1320
|
*
|
|
1278
1321
|
* // Rest of your code stays exactly the same!
|
|
1279
1322
|
* const response = await client.chat.completions.create({
|
|
1280
|
-
* model: 'gpt-
|
|
1323
|
+
* model: 'gpt-5.2',
|
|
1281
1324
|
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1282
1325
|
* });
|
|
1283
1326
|
*/
|
|
@@ -1384,7 +1427,7 @@ declare class Chat {
|
|
|
1384
1427
|
* const client = new OpenAI({ walletKey: '0x...' });
|
|
1385
1428
|
*
|
|
1386
1429
|
* const response = await client.chat.completions.create({
|
|
1387
|
-
* model: 'gpt-
|
|
1430
|
+
* model: 'gpt-5.2',
|
|
1388
1431
|
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1389
1432
|
* });
|
|
1390
1433
|
*
|
|
@@ -1419,4 +1462,59 @@ declare class AnthropicClient {
|
|
|
1419
1462
|
getWalletAddress(): string;
|
|
1420
1463
|
}
|
|
1421
1464
|
|
|
1422
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Input validation and security utilities for BlockRun LLM SDK.
|
|
1467
|
+
*
|
|
1468
|
+
* This module provides validation functions to ensure:
|
|
1469
|
+
* - Private keys are properly formatted
|
|
1470
|
+
* - API URLs use HTTPS
|
|
1471
|
+
* - Server responses don't leak sensitive information
|
|
1472
|
+
* - Resource URLs match expected domains
|
|
1473
|
+
*/
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Known LLM providers (for optional validation).
|
|
1477
|
+
*/
|
|
1478
|
+
declare const KNOWN_PROVIDERS: Set<string>;
|
|
1479
|
+
/**
|
|
1480
|
+
* Validates that a model ID is a non-empty string.
|
|
1481
|
+
*
|
|
1482
|
+
* @param model - The model ID (e.g., "openai/gpt-5.2", "anthropic/claude-sonnet-4.5")
|
|
1483
|
+
* @throws {Error} If the model is invalid
|
|
1484
|
+
*
|
|
1485
|
+
* @example
|
|
1486
|
+
* validateModel("openai/gpt-5.2");
|
|
1487
|
+
*/
|
|
1488
|
+
declare function validateModel(model: string): void;
|
|
1489
|
+
/**
|
|
1490
|
+
* Validates that max_tokens is an integer between 1 and 100,000.
|
|
1491
|
+
*
|
|
1492
|
+
* @param maxTokens - Maximum number of tokens to generate
|
|
1493
|
+
* @throws {Error} If maxTokens is invalid
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* validateMaxTokens(1000);
|
|
1497
|
+
*/
|
|
1498
|
+
declare function validateMaxTokens(maxTokens?: number): void;
|
|
1499
|
+
/**
|
|
1500
|
+
* Validates that temperature is a number between 0 and 2.
|
|
1501
|
+
*
|
|
1502
|
+
* @param temperature - Sampling temperature (0-2)
|
|
1503
|
+
* @throws {Error} If temperature is invalid
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* validateTemperature(0.7);
|
|
1507
|
+
*/
|
|
1508
|
+
declare function validateTemperature(temperature?: number): void;
|
|
1509
|
+
/**
|
|
1510
|
+
* Validates that top_p is a number between 0 and 1.
|
|
1511
|
+
*
|
|
1512
|
+
* @param topP - Top-p sampling parameter (0-1)
|
|
1513
|
+
* @throws {Error} If topP is invalid
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* validateTopP(0.9);
|
|
1517
|
+
*/
|
|
1518
|
+
declare function validateTopP(topP?: number): void;
|
|
1519
|
+
|
|
1520
|
+
export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, testnetClient, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
package/dist/index.d.ts
CHANGED
|
@@ -140,10 +140,48 @@ interface SearchParameters {
|
|
|
140
140
|
toDate?: string;
|
|
141
141
|
maxSearchResults?: number;
|
|
142
142
|
}
|
|
143
|
+
/** Usage info for Live Search sources */
|
|
144
|
+
interface SearchUsage {
|
|
145
|
+
/** Number of search sources used in the response */
|
|
146
|
+
numSourcesUsed?: number;
|
|
147
|
+
}
|
|
143
148
|
interface Spending {
|
|
144
149
|
totalUsd: number;
|
|
145
150
|
calls: number;
|
|
146
151
|
}
|
|
152
|
+
/** Pre-request cost estimate for a chat call */
|
|
153
|
+
interface CostEstimate {
|
|
154
|
+
/** Model ID used for the estimate */
|
|
155
|
+
model: string;
|
|
156
|
+
/** Estimated input token count */
|
|
157
|
+
estimatedInputTokens: number;
|
|
158
|
+
/** Estimated output token count */
|
|
159
|
+
estimatedOutputTokens: number;
|
|
160
|
+
/** Estimated cost in USD */
|
|
161
|
+
estimatedCostUsd: number;
|
|
162
|
+
}
|
|
163
|
+
/** Per-call spending report with running session totals */
|
|
164
|
+
interface SpendingReport {
|
|
165
|
+
/** Model ID used */
|
|
166
|
+
model: string;
|
|
167
|
+
/** Input tokens consumed */
|
|
168
|
+
inputTokens: number;
|
|
169
|
+
/** Output tokens consumed */
|
|
170
|
+
outputTokens: number;
|
|
171
|
+
/** Cost of this call in USD */
|
|
172
|
+
costUsd: number;
|
|
173
|
+
/** Cumulative session spend in USD */
|
|
174
|
+
sessionTotalUsd: number;
|
|
175
|
+
/** Total number of calls in this session */
|
|
176
|
+
sessionCalls: number;
|
|
177
|
+
}
|
|
178
|
+
/** Chat response bundled with its spending report */
|
|
179
|
+
interface ChatResponseWithCost {
|
|
180
|
+
/** The chat completion response */
|
|
181
|
+
response: ChatResponse;
|
|
182
|
+
/** Spending report for this call */
|
|
183
|
+
spendingReport: SpendingReport;
|
|
184
|
+
}
|
|
147
185
|
interface ResourceInfo {
|
|
148
186
|
url: string;
|
|
149
187
|
description?: string;
|
|
@@ -184,9 +222,9 @@ interface ChatOptions {
|
|
|
184
222
|
temperature?: number;
|
|
185
223
|
/** Nucleus sampling parameter */
|
|
186
224
|
topP?: number;
|
|
187
|
-
/** Enable
|
|
225
|
+
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
|
|
188
226
|
search?: boolean;
|
|
189
|
-
/** Full
|
|
227
|
+
/** Full Live Search configuration (for search-enabled models) */
|
|
190
228
|
searchParameters?: SearchParameters;
|
|
191
229
|
}
|
|
192
230
|
interface ChatCompletionOptions {
|
|
@@ -196,9 +234,9 @@ interface ChatCompletionOptions {
|
|
|
196
234
|
temperature?: number;
|
|
197
235
|
/** Nucleus sampling parameter */
|
|
198
236
|
topP?: number;
|
|
199
|
-
/** Enable
|
|
237
|
+
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
|
|
200
238
|
search?: boolean;
|
|
201
|
-
/** Full
|
|
239
|
+
/** Full Live Search configuration (for search-enabled models) */
|
|
202
240
|
searchParameters?: SearchParameters;
|
|
203
241
|
/** Tool definitions for function calling */
|
|
204
242
|
tools?: Tool[];
|
|
@@ -406,7 +444,7 @@ declare class APIError extends BlockrunError {
|
|
|
406
444
|
* // Option 2: Pass private key directly
|
|
407
445
|
* const client = new LLMClient({ privateKey: '0x...' });
|
|
408
446
|
*
|
|
409
|
-
* const response = await client.chat('openai/gpt-
|
|
447
|
+
* const response = await client.chat('openai/gpt-5.2', 'Hello!');
|
|
410
448
|
* console.log(response);
|
|
411
449
|
*/
|
|
412
450
|
|
|
@@ -454,13 +492,13 @@ declare class LLMClient {
|
|
|
454
492
|
/**
|
|
455
493
|
* Simple 1-line chat interface.
|
|
456
494
|
*
|
|
457
|
-
* @param model - Model ID (e.g., 'openai/gpt-
|
|
495
|
+
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
|
|
458
496
|
* @param prompt - User message
|
|
459
497
|
* @param options - Optional chat parameters
|
|
460
498
|
* @returns Assistant's response text
|
|
461
499
|
*
|
|
462
500
|
* @example
|
|
463
|
-
* const response = await client.chat('gpt-
|
|
501
|
+
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
|
|
464
502
|
* console.log(response); // 'The capital of France is Paris.'
|
|
465
503
|
*/
|
|
466
504
|
chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
|
|
@@ -1108,7 +1146,7 @@ declare const WALLET_DIR_PATH: string;
|
|
|
1108
1146
|
* // Or pass key directly
|
|
1109
1147
|
* const client = new SolanaLLMClient({ privateKey: 'your-bs58-key' });
|
|
1110
1148
|
*
|
|
1111
|
-
* const response = await client.chat('openai/gpt-
|
|
1149
|
+
* const response = await client.chat('openai/gpt-5.2', 'gm Solana');
|
|
1112
1150
|
*/
|
|
1113
1151
|
|
|
1114
1152
|
interface SolanaLLMClientOptions {
|
|
@@ -1228,6 +1266,11 @@ declare function getCachedByRequest(endpoint: string, body: Record<string, unkno
|
|
|
1228
1266
|
declare function setCache(key: string, data: unknown, ttlMs: number): void;
|
|
1229
1267
|
declare function saveToCache(endpoint: string, body: Record<string, unknown>, response: unknown, costUsd?: number): void;
|
|
1230
1268
|
declare function clearCache(): number;
|
|
1269
|
+
declare function getCostLogSummary(): {
|
|
1270
|
+
totalUsd: number;
|
|
1271
|
+
calls: number;
|
|
1272
|
+
byEndpoint: Record<string, number>;
|
|
1273
|
+
};
|
|
1231
1274
|
|
|
1232
1275
|
/**
|
|
1233
1276
|
* Agent wallet setup utilities.
|
|
@@ -1277,7 +1320,7 @@ declare function getCostSummary(): {
|
|
|
1277
1320
|
*
|
|
1278
1321
|
* // Rest of your code stays exactly the same!
|
|
1279
1322
|
* const response = await client.chat.completions.create({
|
|
1280
|
-
* model: 'gpt-
|
|
1323
|
+
* model: 'gpt-5.2',
|
|
1281
1324
|
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1282
1325
|
* });
|
|
1283
1326
|
*/
|
|
@@ -1384,7 +1427,7 @@ declare class Chat {
|
|
|
1384
1427
|
* const client = new OpenAI({ walletKey: '0x...' });
|
|
1385
1428
|
*
|
|
1386
1429
|
* const response = await client.chat.completions.create({
|
|
1387
|
-
* model: 'gpt-
|
|
1430
|
+
* model: 'gpt-5.2',
|
|
1388
1431
|
* messages: [{ role: 'user', content: 'Hello!' }]
|
|
1389
1432
|
* });
|
|
1390
1433
|
*
|
|
@@ -1419,4 +1462,59 @@ declare class AnthropicClient {
|
|
|
1419
1462
|
getWalletAddress(): string;
|
|
1420
1463
|
}
|
|
1421
1464
|
|
|
1422
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* Input validation and security utilities for BlockRun LLM SDK.
|
|
1467
|
+
*
|
|
1468
|
+
* This module provides validation functions to ensure:
|
|
1469
|
+
* - Private keys are properly formatted
|
|
1470
|
+
* - API URLs use HTTPS
|
|
1471
|
+
* - Server responses don't leak sensitive information
|
|
1472
|
+
* - Resource URLs match expected domains
|
|
1473
|
+
*/
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Known LLM providers (for optional validation).
|
|
1477
|
+
*/
|
|
1478
|
+
declare const KNOWN_PROVIDERS: Set<string>;
|
|
1479
|
+
/**
|
|
1480
|
+
* Validates that a model ID is a non-empty string.
|
|
1481
|
+
*
|
|
1482
|
+
* @param model - The model ID (e.g., "openai/gpt-5.2", "anthropic/claude-sonnet-4.5")
|
|
1483
|
+
* @throws {Error} If the model is invalid
|
|
1484
|
+
*
|
|
1485
|
+
* @example
|
|
1486
|
+
* validateModel("openai/gpt-5.2");
|
|
1487
|
+
*/
|
|
1488
|
+
declare function validateModel(model: string): void;
|
|
1489
|
+
/**
|
|
1490
|
+
* Validates that max_tokens is an integer between 1 and 100,000.
|
|
1491
|
+
*
|
|
1492
|
+
* @param maxTokens - Maximum number of tokens to generate
|
|
1493
|
+
* @throws {Error} If maxTokens is invalid
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* validateMaxTokens(1000);
|
|
1497
|
+
*/
|
|
1498
|
+
declare function validateMaxTokens(maxTokens?: number): void;
|
|
1499
|
+
/**
|
|
1500
|
+
* Validates that temperature is a number between 0 and 2.
|
|
1501
|
+
*
|
|
1502
|
+
* @param temperature - Sampling temperature (0-2)
|
|
1503
|
+
* @throws {Error} If temperature is invalid
|
|
1504
|
+
*
|
|
1505
|
+
* @example
|
|
1506
|
+
* validateTemperature(0.7);
|
|
1507
|
+
*/
|
|
1508
|
+
declare function validateTemperature(temperature?: number): void;
|
|
1509
|
+
/**
|
|
1510
|
+
* Validates that top_p is a number between 0 and 1.
|
|
1511
|
+
*
|
|
1512
|
+
* @param topP - Top-p sampling parameter (0-1)
|
|
1513
|
+
* @throws {Error} If topP is invalid
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* validateTopP(0.9);
|
|
1517
|
+
*/
|
|
1518
|
+
declare function validateTopP(topP?: number): void;
|
|
1519
|
+
|
|
1520
|
+
export { APIError, AnthropicClient, BASE_CHAIN_ID, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type Model, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsResponse, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, testnetClient, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
package/dist/index.js
CHANGED
|
@@ -223,6 +223,61 @@ function extractPaymentDetails(paymentRequired, preferredNetwork) {
|
|
|
223
223
|
|
|
224
224
|
// src/validation.ts
|
|
225
225
|
var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
|
|
226
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set([
|
|
227
|
+
"openai",
|
|
228
|
+
"anthropic",
|
|
229
|
+
"google",
|
|
230
|
+
"deepseek",
|
|
231
|
+
"mistralai",
|
|
232
|
+
"meta-llama",
|
|
233
|
+
"together",
|
|
234
|
+
"xai",
|
|
235
|
+
"moonshot",
|
|
236
|
+
"nvidia",
|
|
237
|
+
"minimax",
|
|
238
|
+
"zai"
|
|
239
|
+
]);
|
|
240
|
+
function validateModel(model) {
|
|
241
|
+
if (!model || typeof model !== "string") {
|
|
242
|
+
throw new Error("Model must be a non-empty string");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function validateMaxTokens(maxTokens) {
|
|
246
|
+
if (maxTokens === void 0 || maxTokens === null) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens)) {
|
|
250
|
+
throw new Error("maxTokens must be an integer");
|
|
251
|
+
}
|
|
252
|
+
if (maxTokens < 1) {
|
|
253
|
+
throw new Error("maxTokens must be positive (minimum: 1)");
|
|
254
|
+
}
|
|
255
|
+
if (maxTokens > 1e5) {
|
|
256
|
+
throw new Error("maxTokens too large (maximum: 100000)");
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function validateTemperature(temperature) {
|
|
260
|
+
if (temperature === void 0 || temperature === null) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (typeof temperature !== "number") {
|
|
264
|
+
throw new Error("temperature must be a number");
|
|
265
|
+
}
|
|
266
|
+
if (temperature < 0 || temperature > 2) {
|
|
267
|
+
throw new Error("temperature must be between 0 and 2");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function validateTopP(topP) {
|
|
271
|
+
if (topP === void 0 || topP === null) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (typeof topP !== "number") {
|
|
275
|
+
throw new Error("topP must be a number");
|
|
276
|
+
}
|
|
277
|
+
if (topP < 0 || topP > 1) {
|
|
278
|
+
throw new Error("topP must be between 0 and 1");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
226
281
|
function validatePrivateKey(key) {
|
|
227
282
|
if (typeof key !== "string") {
|
|
228
283
|
throw new Error("Private key must be a string");
|
|
@@ -298,7 +353,7 @@ var DEFAULT_API_URL = "https://blockrun.ai/api";
|
|
|
298
353
|
var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
|
|
299
354
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
300
355
|
var DEFAULT_TIMEOUT = 6e4;
|
|
301
|
-
var SDK_VERSION = "
|
|
356
|
+
var SDK_VERSION = "1.5.0";
|
|
302
357
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
303
358
|
var LLMClient = class {
|
|
304
359
|
static DEFAULT_API_URL = DEFAULT_API_URL;
|
|
@@ -335,13 +390,13 @@ var LLMClient = class {
|
|
|
335
390
|
/**
|
|
336
391
|
* Simple 1-line chat interface.
|
|
337
392
|
*
|
|
338
|
-
* @param model - Model ID (e.g., 'openai/gpt-
|
|
393
|
+
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
|
|
339
394
|
* @param prompt - User message
|
|
340
395
|
* @param options - Optional chat parameters
|
|
341
396
|
* @returns Assistant's response text
|
|
342
397
|
*
|
|
343
398
|
* @example
|
|
344
|
-
* const response = await client.chat('gpt-
|
|
399
|
+
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
|
|
345
400
|
* console.log(response); // 'The capital of France is Paris.'
|
|
346
401
|
*/
|
|
347
402
|
async chat(model, prompt, options) {
|
|
@@ -487,6 +542,27 @@ var LLMClient = class {
|
|
|
487
542
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
488
543
|
body: JSON.stringify(body)
|
|
489
544
|
});
|
|
545
|
+
if (response.status === 502 || response.status === 503) {
|
|
546
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
547
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
548
|
+
method: "POST",
|
|
549
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
550
|
+
body: JSON.stringify(body)
|
|
551
|
+
});
|
|
552
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
553
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
|
|
554
|
+
if (!retryResp.ok) {
|
|
555
|
+
let errorBody;
|
|
556
|
+
try {
|
|
557
|
+
errorBody = await retryResp.json();
|
|
558
|
+
} catch {
|
|
559
|
+
errorBody = { error: "Request failed" };
|
|
560
|
+
}
|
|
561
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
562
|
+
}
|
|
563
|
+
return retryResp.json();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
490
566
|
if (response.status === 402) {
|
|
491
567
|
return this.handlePaymentAndRetry(url, body, response);
|
|
492
568
|
}
|
|
@@ -552,6 +628,34 @@ var LLMClient = class {
|
|
|
552
628
|
},
|
|
553
629
|
body: JSON.stringify(body)
|
|
554
630
|
});
|
|
631
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
632
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
633
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
634
|
+
method: "POST",
|
|
635
|
+
headers: {
|
|
636
|
+
"Content-Type": "application/json",
|
|
637
|
+
"User-Agent": USER_AGENT,
|
|
638
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
639
|
+
},
|
|
640
|
+
body: JSON.stringify(body)
|
|
641
|
+
});
|
|
642
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
643
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
644
|
+
if (!retryResp2.ok) {
|
|
645
|
+
let errorBody;
|
|
646
|
+
try {
|
|
647
|
+
errorBody = await retryResp2.json();
|
|
648
|
+
} catch {
|
|
649
|
+
errorBody = { error: "Request failed" };
|
|
650
|
+
}
|
|
651
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
652
|
+
}
|
|
653
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
654
|
+
this.sessionCalls += 1;
|
|
655
|
+
this.sessionTotalUsd += costUsd2;
|
|
656
|
+
return retryResp2.json();
|
|
657
|
+
}
|
|
658
|
+
}
|
|
555
659
|
if (retryResponse.status === 402) {
|
|
556
660
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
557
661
|
}
|
|
@@ -584,6 +688,27 @@ var LLMClient = class {
|
|
|
584
688
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
585
689
|
body: JSON.stringify(body)
|
|
586
690
|
});
|
|
691
|
+
if (response.status === 502 || response.status === 503) {
|
|
692
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
693
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
694
|
+
method: "POST",
|
|
695
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
696
|
+
body: JSON.stringify(body)
|
|
697
|
+
});
|
|
698
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
699
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetryRaw(url, body, retryResp);
|
|
700
|
+
if (!retryResp.ok) {
|
|
701
|
+
let errorBody;
|
|
702
|
+
try {
|
|
703
|
+
errorBody = await retryResp.json();
|
|
704
|
+
} catch {
|
|
705
|
+
errorBody = { error: "Request failed" };
|
|
706
|
+
}
|
|
707
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
708
|
+
}
|
|
709
|
+
return retryResp.json();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
587
712
|
if (response.status === 402) {
|
|
588
713
|
return this.handlePaymentAndRetryRaw(url, body, response);
|
|
589
714
|
}
|
|
@@ -649,6 +774,34 @@ var LLMClient = class {
|
|
|
649
774
|
},
|
|
650
775
|
body: JSON.stringify(body)
|
|
651
776
|
});
|
|
777
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
778
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
779
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
780
|
+
method: "POST",
|
|
781
|
+
headers: {
|
|
782
|
+
"Content-Type": "application/json",
|
|
783
|
+
"User-Agent": USER_AGENT,
|
|
784
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
785
|
+
},
|
|
786
|
+
body: JSON.stringify(body)
|
|
787
|
+
});
|
|
788
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
789
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
790
|
+
if (!retryResp2.ok) {
|
|
791
|
+
let errorBody;
|
|
792
|
+
try {
|
|
793
|
+
errorBody = await retryResp2.json();
|
|
794
|
+
} catch {
|
|
795
|
+
errorBody = { error: "Request failed" };
|
|
796
|
+
}
|
|
797
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
798
|
+
}
|
|
799
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
800
|
+
this.sessionCalls += 1;
|
|
801
|
+
this.sessionTotalUsd += costUsd2;
|
|
802
|
+
return retryResp2.json();
|
|
803
|
+
}
|
|
804
|
+
}
|
|
652
805
|
if (retryResponse.status === 402) {
|
|
653
806
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
654
807
|
}
|
|
@@ -681,6 +834,26 @@ var LLMClient = class {
|
|
|
681
834
|
method: "GET",
|
|
682
835
|
headers: { "User-Agent": USER_AGENT }
|
|
683
836
|
});
|
|
837
|
+
if (response.status === 502 || response.status === 503) {
|
|
838
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
839
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
840
|
+
method: "GET",
|
|
841
|
+
headers: { "User-Agent": USER_AGENT }
|
|
842
|
+
});
|
|
843
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
844
|
+
if (retryResp.status === 402) return this.handleGetPaymentAndRetryRaw(url, endpoint, params, retryResp);
|
|
845
|
+
if (!retryResp.ok) {
|
|
846
|
+
let errorBody;
|
|
847
|
+
try {
|
|
848
|
+
errorBody = await retryResp.json();
|
|
849
|
+
} catch {
|
|
850
|
+
errorBody = { error: "Request failed" };
|
|
851
|
+
}
|
|
852
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
853
|
+
}
|
|
854
|
+
return retryResp.json();
|
|
855
|
+
}
|
|
856
|
+
}
|
|
684
857
|
if (response.status === 402) {
|
|
685
858
|
return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
|
|
686
859
|
}
|
|
@@ -746,6 +919,32 @@ var LLMClient = class {
|
|
|
746
919
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
747
920
|
}
|
|
748
921
|
});
|
|
922
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
923
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
924
|
+
const retryResp2 = await this.fetchWithTimeout(retryUrl, {
|
|
925
|
+
method: "GET",
|
|
926
|
+
headers: {
|
|
927
|
+
"User-Agent": USER_AGENT,
|
|
928
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
932
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
933
|
+
if (!retryResp2.ok) {
|
|
934
|
+
let errorBody;
|
|
935
|
+
try {
|
|
936
|
+
errorBody = await retryResp2.json();
|
|
937
|
+
} catch {
|
|
938
|
+
errorBody = { error: "Request failed" };
|
|
939
|
+
}
|
|
940
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
941
|
+
}
|
|
942
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
943
|
+
this.sessionCalls += 1;
|
|
944
|
+
this.sessionTotalUsd += costUsd2;
|
|
945
|
+
return retryResp2.json();
|
|
946
|
+
}
|
|
947
|
+
}
|
|
749
948
|
if (retryResponse.status === 402) {
|
|
750
949
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
751
950
|
}
|
|
@@ -2214,6 +2413,8 @@ import * as path3 from "path";
|
|
|
2214
2413
|
import * as os3 from "os";
|
|
2215
2414
|
import * as crypto2 from "crypto";
|
|
2216
2415
|
var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
|
|
2416
|
+
var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
|
|
2417
|
+
var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
|
|
2217
2418
|
var DEFAULT_TTL = {
|
|
2218
2419
|
"/v1/x/": 3600 * 1e3,
|
|
2219
2420
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -2278,26 +2479,80 @@ function setCache(key, data, ttlMs) {
|
|
|
2278
2479
|
} catch {
|
|
2279
2480
|
}
|
|
2280
2481
|
}
|
|
2281
|
-
function
|
|
2282
|
-
const
|
|
2283
|
-
|
|
2482
|
+
function readableFilename(endpoint, body) {
|
|
2483
|
+
const now = /* @__PURE__ */ new Date();
|
|
2484
|
+
const ts = now.toISOString().slice(0, 10) + "_" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
|
|
2485
|
+
let ep = endpoint.replace(/\/+$/, "").split("/").pop() || "";
|
|
2486
|
+
if (endpoint.includes("/v1/chat/")) {
|
|
2487
|
+
ep = "chat";
|
|
2488
|
+
} else if (endpoint.includes("/v1/x/")) {
|
|
2489
|
+
ep = "x_" + ep;
|
|
2490
|
+
} else if (endpoint.includes("/v1/search")) {
|
|
2491
|
+
ep = "search";
|
|
2492
|
+
} else if (endpoint.includes("/v1/image")) {
|
|
2493
|
+
ep = "image";
|
|
2494
|
+
}
|
|
2495
|
+
let label = body.query || body.username || body.handle || body.model || (typeof body.prompt === "string" ? body.prompt.slice(0, 40) : "") || "";
|
|
2496
|
+
label = String(label).replace(/[^a-zA-Z0-9_\-]/g, "_").slice(0, 40).replace(/^_+|_+$/g, "");
|
|
2497
|
+
return label ? `${ep}_${ts}_${label}.json` : `${ep}_${ts}.json`;
|
|
2498
|
+
}
|
|
2499
|
+
function saveReadable(endpoint, body, response, costUsd) {
|
|
2284
2500
|
try {
|
|
2285
|
-
fs3.mkdirSync(
|
|
2501
|
+
fs3.mkdirSync(DATA_DIR, { recursive: true });
|
|
2286
2502
|
} catch {
|
|
2287
2503
|
}
|
|
2288
|
-
const
|
|
2504
|
+
const filename = readableFilename(endpoint, body);
|
|
2289
2505
|
const entry = {
|
|
2290
|
-
|
|
2506
|
+
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2291
2507
|
endpoint,
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2508
|
+
cost_usd: costUsd,
|
|
2509
|
+
request: body,
|
|
2510
|
+
response
|
|
2295
2511
|
};
|
|
2296
2512
|
try {
|
|
2297
|
-
fs3.writeFileSync(
|
|
2513
|
+
fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
2514
|
+
} catch {
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
function appendCostLog(endpoint, costUsd) {
|
|
2518
|
+
if (costUsd <= 0) return;
|
|
2519
|
+
try {
|
|
2520
|
+
fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
|
|
2521
|
+
} catch {
|
|
2522
|
+
}
|
|
2523
|
+
const entry = {
|
|
2524
|
+
ts: Date.now() / 1e3,
|
|
2525
|
+
endpoint,
|
|
2526
|
+
cost_usd: costUsd
|
|
2527
|
+
};
|
|
2528
|
+
try {
|
|
2529
|
+
fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
2298
2530
|
} catch {
|
|
2299
2531
|
}
|
|
2300
2532
|
}
|
|
2533
|
+
function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
2534
|
+
const ttl = getTtl(endpoint);
|
|
2535
|
+
if (ttl > 0) {
|
|
2536
|
+
try {
|
|
2537
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
2538
|
+
} catch {
|
|
2539
|
+
}
|
|
2540
|
+
const key = cacheKey(endpoint, body);
|
|
2541
|
+
const entry = {
|
|
2542
|
+
cachedAt: Date.now(),
|
|
2543
|
+
endpoint,
|
|
2544
|
+
body,
|
|
2545
|
+
response,
|
|
2546
|
+
costUsd
|
|
2547
|
+
};
|
|
2548
|
+
try {
|
|
2549
|
+
fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
2550
|
+
} catch {
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
saveReadable(endpoint, body, response, costUsd);
|
|
2554
|
+
appendCostLog(endpoint, costUsd);
|
|
2555
|
+
}
|
|
2301
2556
|
function clearCache() {
|
|
2302
2557
|
if (!fs3.existsSync(CACHE_DIR)) return 0;
|
|
2303
2558
|
let count = 0;
|
|
@@ -2316,6 +2571,32 @@ function clearCache() {
|
|
|
2316
2571
|
}
|
|
2317
2572
|
return count;
|
|
2318
2573
|
}
|
|
2574
|
+
function getCostLogSummary() {
|
|
2575
|
+
if (!fs3.existsSync(COST_LOG_FILE)) {
|
|
2576
|
+
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2577
|
+
}
|
|
2578
|
+
let totalUsd = 0;
|
|
2579
|
+
let calls = 0;
|
|
2580
|
+
const byEndpoint = {};
|
|
2581
|
+
try {
|
|
2582
|
+
const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
|
|
2583
|
+
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2584
|
+
for (const line of content.split("\n")) {
|
|
2585
|
+
if (!line) continue;
|
|
2586
|
+
try {
|
|
2587
|
+
const entry = JSON.parse(line);
|
|
2588
|
+
const cost = entry.cost_usd ?? 0;
|
|
2589
|
+
const ep = entry.endpoint ?? "unknown";
|
|
2590
|
+
totalUsd += cost;
|
|
2591
|
+
calls += 1;
|
|
2592
|
+
byEndpoint[ep] = (byEndpoint[ep] || 0) + cost;
|
|
2593
|
+
} catch {
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
} catch {
|
|
2597
|
+
}
|
|
2598
|
+
return { totalUsd, calls, byEndpoint };
|
|
2599
|
+
}
|
|
2319
2600
|
|
|
2320
2601
|
// src/setup.ts
|
|
2321
2602
|
function setupAgentWallet(options) {
|
|
@@ -2357,27 +2638,27 @@ async function status() {
|
|
|
2357
2638
|
import * as fs4 from "fs";
|
|
2358
2639
|
import * as path4 from "path";
|
|
2359
2640
|
import * as os4 from "os";
|
|
2360
|
-
var
|
|
2361
|
-
var
|
|
2641
|
+
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
2642
|
+
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
2362
2643
|
function logCost(entry) {
|
|
2363
2644
|
try {
|
|
2364
|
-
fs4.mkdirSync(
|
|
2645
|
+
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
2365
2646
|
} catch {
|
|
2366
2647
|
}
|
|
2367
2648
|
try {
|
|
2368
|
-
fs4.appendFileSync(
|
|
2649
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
2369
2650
|
} catch {
|
|
2370
2651
|
}
|
|
2371
2652
|
}
|
|
2372
2653
|
function getCostSummary() {
|
|
2373
|
-
if (!fs4.existsSync(
|
|
2654
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
2374
2655
|
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2375
2656
|
}
|
|
2376
2657
|
let totalUsd = 0;
|
|
2377
2658
|
let calls = 0;
|
|
2378
2659
|
const byModel = {};
|
|
2379
2660
|
try {
|
|
2380
|
-
const content = fs4.readFileSync(
|
|
2661
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
2381
2662
|
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2382
2663
|
for (const line of content.split("\n")) {
|
|
2383
2664
|
if (!line) continue;
|
|
@@ -2678,6 +2959,7 @@ export {
|
|
|
2678
2959
|
BASE_CHAIN_ID,
|
|
2679
2960
|
BlockrunError,
|
|
2680
2961
|
ImageClient,
|
|
2962
|
+
KNOWN_PROVIDERS,
|
|
2681
2963
|
LLMClient,
|
|
2682
2964
|
OpenAI,
|
|
2683
2965
|
PaymentError,
|
|
@@ -2701,6 +2983,7 @@ export {
|
|
|
2701
2983
|
formatWalletCreatedMessage,
|
|
2702
2984
|
getCached,
|
|
2703
2985
|
getCachedByRequest,
|
|
2986
|
+
getCostLogSummary,
|
|
2704
2987
|
getCostSummary,
|
|
2705
2988
|
getEip681Uri,
|
|
2706
2989
|
getOrCreateSolanaWallet,
|
|
@@ -2723,5 +3006,9 @@ export {
|
|
|
2723
3006
|
solanaKeyToBytes,
|
|
2724
3007
|
solanaPublicKey,
|
|
2725
3008
|
status,
|
|
2726
|
-
testnetClient
|
|
3009
|
+
testnetClient,
|
|
3010
|
+
validateMaxTokens,
|
|
3011
|
+
validateModel,
|
|
3012
|
+
validateTemperature,
|
|
3013
|
+
validateTopP
|
|
2727
3014
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base and Solana",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"url": "https://github.com/BlockRunAI/blockrun-llm-ts/issues"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@blockrun/clawrouter": "^0.
|
|
51
|
+
"@blockrun/clawrouter": "^0.12.71",
|
|
52
52
|
"bs58": "^6.0.0",
|
|
53
53
|
"viem": "^2.21.0"
|
|
54
54
|
},
|