@blockrun/llm 1.2.0 → 1.3.1
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 +162 -2
- package/dist/index.cjs +601 -87
- package/dist/index.d.cts +113 -4
- package/dist/index.d.ts +113 -4
- package/dist/index.js +506 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -670,6 +670,103 @@ var LLMClient = class {
|
|
|
670
670
|
this.sessionTotalUsd += costUsd;
|
|
671
671
|
return retryResponse.json();
|
|
672
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* GET with automatic x402 payment handling, returning raw JSON.
|
|
675
|
+
* Used for Predexon prediction market endpoints that use GET + query params.
|
|
676
|
+
*/
|
|
677
|
+
async getWithPaymentRaw(endpoint, params) {
|
|
678
|
+
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
679
|
+
const url = `${this.apiUrl}${endpoint}${query}`;
|
|
680
|
+
const response = await this.fetchWithTimeout(url, {
|
|
681
|
+
method: "GET",
|
|
682
|
+
headers: { "User-Agent": USER_AGENT }
|
|
683
|
+
});
|
|
684
|
+
if (response.status === 402) {
|
|
685
|
+
return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
|
|
686
|
+
}
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
let errorBody;
|
|
689
|
+
try {
|
|
690
|
+
errorBody = await response.json();
|
|
691
|
+
} catch {
|
|
692
|
+
errorBody = { error: "Request failed" };
|
|
693
|
+
}
|
|
694
|
+
throw new APIError(
|
|
695
|
+
`API error: ${response.status}`,
|
|
696
|
+
response.status,
|
|
697
|
+
sanitizeErrorResponse(errorBody)
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
return response.json();
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Handle 402 response for GET endpoints: parse requirements, sign payment, retry with GET.
|
|
704
|
+
*/
|
|
705
|
+
async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
|
|
706
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
707
|
+
if (!paymentHeader) {
|
|
708
|
+
try {
|
|
709
|
+
const respBody = await response.json();
|
|
710
|
+
if (respBody.x402 || respBody.accepts) {
|
|
711
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
console.debug("Failed to parse payment header from response body");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (!paymentHeader) {
|
|
718
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
719
|
+
}
|
|
720
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
721
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
722
|
+
const extensions = paymentRequired.extensions;
|
|
723
|
+
const paymentPayload = await createPaymentPayload(
|
|
724
|
+
this.privateKey,
|
|
725
|
+
this.account.address,
|
|
726
|
+
details.recipient,
|
|
727
|
+
details.amount,
|
|
728
|
+
details.network || "eip155:8453",
|
|
729
|
+
{
|
|
730
|
+
resourceUrl: validateResourceUrl(
|
|
731
|
+
details.resource?.url || url,
|
|
732
|
+
this.apiUrl
|
|
733
|
+
),
|
|
734
|
+
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
735
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
736
|
+
extra: details.extra,
|
|
737
|
+
extensions
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
741
|
+
const retryUrl = `${this.apiUrl}${endpoint}${query}`;
|
|
742
|
+
const retryResponse = await this.fetchWithTimeout(retryUrl, {
|
|
743
|
+
method: "GET",
|
|
744
|
+
headers: {
|
|
745
|
+
"User-Agent": USER_AGENT,
|
|
746
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
if (retryResponse.status === 402) {
|
|
750
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
751
|
+
}
|
|
752
|
+
if (!retryResponse.ok) {
|
|
753
|
+
let errorBody;
|
|
754
|
+
try {
|
|
755
|
+
errorBody = await retryResponse.json();
|
|
756
|
+
} catch {
|
|
757
|
+
errorBody = { error: "Request failed" };
|
|
758
|
+
}
|
|
759
|
+
throw new APIError(
|
|
760
|
+
`API error after payment: ${retryResponse.status}`,
|
|
761
|
+
retryResponse.status,
|
|
762
|
+
sanitizeErrorResponse(errorBody)
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
766
|
+
this.sessionCalls += 1;
|
|
767
|
+
this.sessionTotalUsd += costUsd;
|
|
768
|
+
return retryResponse.json();
|
|
769
|
+
}
|
|
673
770
|
/**
|
|
674
771
|
* Fetch with timeout.
|
|
675
772
|
*/
|
|
@@ -1031,6 +1128,38 @@ var LLMClient = class {
|
|
|
1031
1128
|
const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
|
|
1032
1129
|
return data;
|
|
1033
1130
|
}
|
|
1131
|
+
// ── Prediction Markets (Powered by Predexon) ────────────────────────────
|
|
1132
|
+
/**
|
|
1133
|
+
* Query Predexon prediction market data (GET endpoints).
|
|
1134
|
+
*
|
|
1135
|
+
* Access real-time data from Polymarket, Kalshi, dFlow, and Binance Futures.
|
|
1136
|
+
* Powered by Predexon. $0.001 per request.
|
|
1137
|
+
*
|
|
1138
|
+
* @param path - Endpoint path, e.g. "polymarket/events", "kalshi/markets/12345"
|
|
1139
|
+
* @param params - Query parameters passed to the endpoint
|
|
1140
|
+
*
|
|
1141
|
+
* @example
|
|
1142
|
+
* const events = await client.pm("polymarket/events");
|
|
1143
|
+
* const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
|
|
1144
|
+
* const results = await client.pm("polymarket/search", { q: "bitcoin" });
|
|
1145
|
+
*/
|
|
1146
|
+
async pm(path5, params) {
|
|
1147
|
+
return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Structured query for Predexon prediction market data (POST endpoints).
|
|
1151
|
+
*
|
|
1152
|
+
* For complex queries that require a JSON body. $0.005 per request.
|
|
1153
|
+
*
|
|
1154
|
+
* @param path - Endpoint path, e.g. "polymarket/query", "kalshi/query"
|
|
1155
|
+
* @param query - JSON body for the structured query
|
|
1156
|
+
*
|
|
1157
|
+
* @example
|
|
1158
|
+
* const data = await client.pmQuery("polymarket/query", { filter: "active", limit: 10 });
|
|
1159
|
+
*/
|
|
1160
|
+
async pmQuery(path5, query) {
|
|
1161
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
|
|
1162
|
+
}
|
|
1034
1163
|
/**
|
|
1035
1164
|
* Get current session spending.
|
|
1036
1165
|
*
|
|
@@ -1316,7 +1445,35 @@ function saveWallet(privateKey) {
|
|
|
1316
1445
|
fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
|
|
1317
1446
|
return WALLET_FILE;
|
|
1318
1447
|
}
|
|
1448
|
+
function scanWallets() {
|
|
1449
|
+
const home = os.homedir();
|
|
1450
|
+
const results = [];
|
|
1451
|
+
try {
|
|
1452
|
+
const entries = fs.readdirSync(home, { withFileTypes: true });
|
|
1453
|
+
for (const entry of entries) {
|
|
1454
|
+
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
1455
|
+
const walletFile = path.join(home, entry.name, "wallet.json");
|
|
1456
|
+
if (!fs.existsSync(walletFile)) continue;
|
|
1457
|
+
try {
|
|
1458
|
+
const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
|
|
1459
|
+
const pk = data.privateKey || "";
|
|
1460
|
+
const addr = data.address || "";
|
|
1461
|
+
if (pk && addr) {
|
|
1462
|
+
const mtime = fs.statSync(walletFile).mtimeMs;
|
|
1463
|
+
results.push({ mtime, privateKey: pk, address: addr });
|
|
1464
|
+
}
|
|
1465
|
+
} catch {
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
} catch {
|
|
1470
|
+
}
|
|
1471
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
1472
|
+
return results.map(({ privateKey, address }) => ({ privateKey, address }));
|
|
1473
|
+
}
|
|
1319
1474
|
function loadWallet() {
|
|
1475
|
+
const wallets = scanWallets();
|
|
1476
|
+
if (wallets.length > 0) return wallets[0].privateKey;
|
|
1320
1477
|
if (fs.existsSync(WALLET_FILE)) {
|
|
1321
1478
|
const key = fs.readFileSync(WALLET_FILE, "utf-8").trim();
|
|
1322
1479
|
if (key) return key;
|
|
@@ -1455,7 +1612,50 @@ function saveSolanaWallet(privateKey) {
|
|
|
1455
1612
|
fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
1456
1613
|
return SOLANA_WALLET_FILE;
|
|
1457
1614
|
}
|
|
1615
|
+
function scanSolanaWallets() {
|
|
1616
|
+
const home = os2.homedir();
|
|
1617
|
+
const results = [];
|
|
1618
|
+
try {
|
|
1619
|
+
const entries = fs2.readdirSync(home, { withFileTypes: true });
|
|
1620
|
+
for (const entry of entries) {
|
|
1621
|
+
if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
|
|
1622
|
+
const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
|
|
1623
|
+
if (fs2.existsSync(solanaWalletFile)) {
|
|
1624
|
+
try {
|
|
1625
|
+
const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
|
|
1626
|
+
const pk = data.privateKey || "";
|
|
1627
|
+
const addr = data.address || "";
|
|
1628
|
+
if (pk && addr) {
|
|
1629
|
+
const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
|
|
1630
|
+
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
1631
|
+
}
|
|
1632
|
+
} catch {
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
if (entry.name === ".brcc") {
|
|
1636
|
+
const brccWalletFile = path2.join(home, entry.name, "wallet.json");
|
|
1637
|
+
if (fs2.existsSync(brccWalletFile)) {
|
|
1638
|
+
try {
|
|
1639
|
+
const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
|
|
1640
|
+
const pk = data.privateKey || "";
|
|
1641
|
+
const addr = data.address || "";
|
|
1642
|
+
if (pk && addr) {
|
|
1643
|
+
const mtime = fs2.statSync(brccWalletFile).mtimeMs;
|
|
1644
|
+
results.push({ mtime, secretKey: pk, publicKey: addr });
|
|
1645
|
+
}
|
|
1646
|
+
} catch {
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
} catch {
|
|
1652
|
+
}
|
|
1653
|
+
results.sort((a, b) => b.mtime - a.mtime);
|
|
1654
|
+
return results.map(({ secretKey, publicKey }) => ({ secretKey, publicKey }));
|
|
1655
|
+
}
|
|
1458
1656
|
function loadSolanaWallet() {
|
|
1657
|
+
const wallets = scanSolanaWallets();
|
|
1658
|
+
if (wallets.length > 0) return wallets[0].secretKey;
|
|
1459
1659
|
if (fs2.existsSync(SOLANA_WALLET_FILE)) {
|
|
1460
1660
|
const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
1461
1661
|
if (key) return key;
|
|
@@ -1468,10 +1668,16 @@ async function getOrCreateSolanaWallet() {
|
|
|
1468
1668
|
const address2 = await solanaPublicKey(envKey);
|
|
1469
1669
|
return { privateKey: envKey, address: address2, isNew: false };
|
|
1470
1670
|
}
|
|
1471
|
-
const
|
|
1472
|
-
if (
|
|
1473
|
-
|
|
1474
|
-
|
|
1671
|
+
const wallets = scanSolanaWallets();
|
|
1672
|
+
if (wallets.length > 0) {
|
|
1673
|
+
return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
|
|
1674
|
+
}
|
|
1675
|
+
if (fs2.existsSync(SOLANA_WALLET_FILE)) {
|
|
1676
|
+
const fileKey = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
1677
|
+
if (fileKey) {
|
|
1678
|
+
const address2 = await solanaPublicKey(fileKey);
|
|
1679
|
+
return { privateKey: fileKey, address: address2, isNew: false };
|
|
1680
|
+
}
|
|
1475
1681
|
}
|
|
1476
1682
|
const { address, privateKey } = createSolanaWallet();
|
|
1477
1683
|
saveSolanaWallet(privateKey);
|
|
@@ -1689,6 +1895,13 @@ var SolanaLLMClient = class {
|
|
|
1689
1895
|
const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
|
|
1690
1896
|
return data;
|
|
1691
1897
|
}
|
|
1898
|
+
// ── Prediction Markets (Powered by Predexon) ────────────────────────────
|
|
1899
|
+
async pm(path5, params) {
|
|
1900
|
+
return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
|
|
1901
|
+
}
|
|
1902
|
+
async pmQuery(path5, query) {
|
|
1903
|
+
return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
|
|
1904
|
+
}
|
|
1692
1905
|
/** Get session spending. */
|
|
1693
1906
|
getSpending() {
|
|
1694
1907
|
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
@@ -1879,6 +2092,97 @@ var SolanaLLMClient = class {
|
|
|
1879
2092
|
this.sessionTotalUsd += costUsd;
|
|
1880
2093
|
return retryResponse.json();
|
|
1881
2094
|
}
|
|
2095
|
+
async getWithPaymentRaw(endpoint, params) {
|
|
2096
|
+
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
2097
|
+
const url = `${this.apiUrl}${endpoint}${query}`;
|
|
2098
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2099
|
+
method: "GET",
|
|
2100
|
+
headers: { "User-Agent": USER_AGENT2 }
|
|
2101
|
+
});
|
|
2102
|
+
if (response.status === 402) {
|
|
2103
|
+
return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
|
|
2104
|
+
}
|
|
2105
|
+
if (!response.ok) {
|
|
2106
|
+
let errorBody;
|
|
2107
|
+
try {
|
|
2108
|
+
errorBody = await response.json();
|
|
2109
|
+
} catch {
|
|
2110
|
+
errorBody = { error: "Request failed" };
|
|
2111
|
+
}
|
|
2112
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
2113
|
+
}
|
|
2114
|
+
return response.json();
|
|
2115
|
+
}
|
|
2116
|
+
async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
|
|
2117
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2118
|
+
if (!paymentHeader) {
|
|
2119
|
+
try {
|
|
2120
|
+
const respBody = await response.json();
|
|
2121
|
+
if (respBody.accepts || respBody.x402Version) {
|
|
2122
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2123
|
+
}
|
|
2124
|
+
} catch {
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
if (!paymentHeader) {
|
|
2128
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2129
|
+
}
|
|
2130
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2131
|
+
const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
|
|
2132
|
+
if (!details.network?.startsWith("solana:")) {
|
|
2133
|
+
throw new PaymentError(
|
|
2134
|
+
`Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
|
|
2135
|
+
);
|
|
2136
|
+
}
|
|
2137
|
+
const feePayer = details.extra?.feePayer;
|
|
2138
|
+
if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
|
|
2139
|
+
const fromAddress = await this.getWalletAddress();
|
|
2140
|
+
const secretKey = await solanaKeyToBytes(this.privateKey);
|
|
2141
|
+
const extensions = paymentRequired.extensions;
|
|
2142
|
+
const paymentPayload = await createSolanaPaymentPayload(
|
|
2143
|
+
secretKey,
|
|
2144
|
+
fromAddress,
|
|
2145
|
+
details.recipient,
|
|
2146
|
+
details.amount,
|
|
2147
|
+
feePayer,
|
|
2148
|
+
{
|
|
2149
|
+
resourceUrl: validateResourceUrl(
|
|
2150
|
+
details.resource?.url || url,
|
|
2151
|
+
this.apiUrl
|
|
2152
|
+
),
|
|
2153
|
+
resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
|
|
2154
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2155
|
+
extra: details.extra,
|
|
2156
|
+
extensions,
|
|
2157
|
+
rpcUrl: this.rpcUrl
|
|
2158
|
+
}
|
|
2159
|
+
);
|
|
2160
|
+
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
2161
|
+
const retryUrl = `${this.apiUrl}${endpoint}${query}`;
|
|
2162
|
+
const retryResponse = await this.fetchWithTimeout(retryUrl, {
|
|
2163
|
+
method: "GET",
|
|
2164
|
+
headers: {
|
|
2165
|
+
"User-Agent": USER_AGENT2,
|
|
2166
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
if (retryResponse.status === 402) {
|
|
2170
|
+
throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
|
|
2171
|
+
}
|
|
2172
|
+
if (!retryResponse.ok) {
|
|
2173
|
+
let errorBody;
|
|
2174
|
+
try {
|
|
2175
|
+
errorBody = await retryResponse.json();
|
|
2176
|
+
} catch {
|
|
2177
|
+
errorBody = { error: "Request failed" };
|
|
2178
|
+
}
|
|
2179
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
2180
|
+
}
|
|
2181
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
2182
|
+
this.sessionCalls += 1;
|
|
2183
|
+
this.sessionTotalUsd += costUsd;
|
|
2184
|
+
return retryResponse.json();
|
|
2185
|
+
}
|
|
1882
2186
|
async fetchWithTimeout(url, options) {
|
|
1883
2187
|
const controller = new AbortController();
|
|
1884
2188
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
@@ -1893,6 +2197,192 @@ function solanaClient(options = {}) {
|
|
|
1893
2197
|
return new SolanaLLMClient({ ...options, apiUrl: SOLANA_API_URL });
|
|
1894
2198
|
}
|
|
1895
2199
|
|
|
2200
|
+
// src/cache.ts
|
|
2201
|
+
import * as fs3 from "fs";
|
|
2202
|
+
import * as path3 from "path";
|
|
2203
|
+
import * as os3 from "os";
|
|
2204
|
+
import * as crypto2 from "crypto";
|
|
2205
|
+
var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
|
|
2206
|
+
var DEFAULT_TTL = {
|
|
2207
|
+
"/v1/x/": 3600 * 1e3,
|
|
2208
|
+
"/v1/partner/": 3600 * 1e3,
|
|
2209
|
+
"/v1/pm/": 1800 * 1e3,
|
|
2210
|
+
"/v1/chat/": 0,
|
|
2211
|
+
"/v1/search": 900 * 1e3,
|
|
2212
|
+
"/v1/image": 0,
|
|
2213
|
+
"/v1/models": 86400 * 1e3
|
|
2214
|
+
};
|
|
2215
|
+
function getTtl(endpoint) {
|
|
2216
|
+
for (const [pattern, ttl] of Object.entries(DEFAULT_TTL)) {
|
|
2217
|
+
if (endpoint.includes(pattern)) return ttl;
|
|
2218
|
+
}
|
|
2219
|
+
return 3600 * 1e3;
|
|
2220
|
+
}
|
|
2221
|
+
function cacheKey(endpoint, body) {
|
|
2222
|
+
const keyData = JSON.stringify({ endpoint, body }, Object.keys({ endpoint, body }).sort());
|
|
2223
|
+
return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
|
|
2224
|
+
}
|
|
2225
|
+
function cachePath(key) {
|
|
2226
|
+
return path3.join(CACHE_DIR, `${key}.json`);
|
|
2227
|
+
}
|
|
2228
|
+
function getCached(key) {
|
|
2229
|
+
const filePath = cachePath(key);
|
|
2230
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
2231
|
+
try {
|
|
2232
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
2233
|
+
const entry = JSON.parse(raw);
|
|
2234
|
+
const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
|
|
2235
|
+
if (ttl <= 0) return null;
|
|
2236
|
+
if (Date.now() - entry.cachedAt > ttl) {
|
|
2237
|
+
try {
|
|
2238
|
+
fs3.unlinkSync(filePath);
|
|
2239
|
+
} catch {
|
|
2240
|
+
}
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
return entry.response;
|
|
2244
|
+
} catch {
|
|
2245
|
+
return null;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function getCachedByRequest(endpoint, body) {
|
|
2249
|
+
const ttl = getTtl(endpoint);
|
|
2250
|
+
if (ttl <= 0) return null;
|
|
2251
|
+
const key = cacheKey(endpoint, body);
|
|
2252
|
+
return getCached(key);
|
|
2253
|
+
}
|
|
2254
|
+
function setCache(key, data, ttlMs) {
|
|
2255
|
+
if (ttlMs <= 0) return;
|
|
2256
|
+
try {
|
|
2257
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
2258
|
+
} catch {
|
|
2259
|
+
}
|
|
2260
|
+
const entry = {
|
|
2261
|
+
cachedAt: Date.now(),
|
|
2262
|
+
response: data,
|
|
2263
|
+
ttlMs
|
|
2264
|
+
};
|
|
2265
|
+
try {
|
|
2266
|
+
fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
2267
|
+
} catch {
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
2271
|
+
const ttl = getTtl(endpoint);
|
|
2272
|
+
if (ttl <= 0) return;
|
|
2273
|
+
try {
|
|
2274
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
2275
|
+
} catch {
|
|
2276
|
+
}
|
|
2277
|
+
const key = cacheKey(endpoint, body);
|
|
2278
|
+
const entry = {
|
|
2279
|
+
cachedAt: Date.now(),
|
|
2280
|
+
endpoint,
|
|
2281
|
+
body,
|
|
2282
|
+
response,
|
|
2283
|
+
costUsd
|
|
2284
|
+
};
|
|
2285
|
+
try {
|
|
2286
|
+
fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
2287
|
+
} catch {
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
function clearCache() {
|
|
2291
|
+
if (!fs3.existsSync(CACHE_DIR)) return 0;
|
|
2292
|
+
let count = 0;
|
|
2293
|
+
try {
|
|
2294
|
+
const files = fs3.readdirSync(CACHE_DIR);
|
|
2295
|
+
for (const file of files) {
|
|
2296
|
+
if (file.endsWith(".json")) {
|
|
2297
|
+
try {
|
|
2298
|
+
fs3.unlinkSync(path3.join(CACHE_DIR, file));
|
|
2299
|
+
count++;
|
|
2300
|
+
} catch {
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
} catch {
|
|
2305
|
+
}
|
|
2306
|
+
return count;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// src/setup.ts
|
|
2310
|
+
function setupAgentWallet(options) {
|
|
2311
|
+
const { address, privateKey, isNew } = getOrCreateWallet();
|
|
2312
|
+
if (isNew && !options?.silent) {
|
|
2313
|
+
console.error(
|
|
2314
|
+
`
|
|
2315
|
+
BlockRun Agent Wallet Created!
|
|
2316
|
+
Address: ${address}
|
|
2317
|
+
Send USDC on Base to get started.
|
|
2318
|
+
`
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
return new LLMClient({ privateKey });
|
|
2322
|
+
}
|
|
2323
|
+
async function setupAgentSolanaWallet(options) {
|
|
2324
|
+
const result = await getOrCreateSolanaWallet();
|
|
2325
|
+
if (result.isNew && !options?.silent) {
|
|
2326
|
+
console.error(
|
|
2327
|
+
`
|
|
2328
|
+
BlockRun Solana Agent Wallet Created!
|
|
2329
|
+
Address: ${result.address}
|
|
2330
|
+
Send USDC on Solana to get started.
|
|
2331
|
+
`
|
|
2332
|
+
);
|
|
2333
|
+
}
|
|
2334
|
+
return new SolanaLLMClient({ privateKey: result.privateKey });
|
|
2335
|
+
}
|
|
2336
|
+
async function status() {
|
|
2337
|
+
const client = setupAgentWallet({ silent: true });
|
|
2338
|
+
const address = client.getWalletAddress();
|
|
2339
|
+
const balance = await client.getBalance();
|
|
2340
|
+
console.log(`Wallet: ${address}`);
|
|
2341
|
+
console.log(`Balance: $${balance.toFixed(2)} USDC`);
|
|
2342
|
+
return { address, balance };
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
// src/cost-log.ts
|
|
2346
|
+
import * as fs4 from "fs";
|
|
2347
|
+
import * as path4 from "path";
|
|
2348
|
+
import * as os4 from "os";
|
|
2349
|
+
var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
|
|
2350
|
+
var COST_LOG_FILE = path4.join(DATA_DIR, "costs.jsonl");
|
|
2351
|
+
function logCost(entry) {
|
|
2352
|
+
try {
|
|
2353
|
+
fs4.mkdirSync(DATA_DIR, { recursive: true });
|
|
2354
|
+
} catch {
|
|
2355
|
+
}
|
|
2356
|
+
try {
|
|
2357
|
+
fs4.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
2358
|
+
} catch {
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function getCostSummary() {
|
|
2362
|
+
if (!fs4.existsSync(COST_LOG_FILE)) {
|
|
2363
|
+
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2364
|
+
}
|
|
2365
|
+
let totalUsd = 0;
|
|
2366
|
+
let calls = 0;
|
|
2367
|
+
const byModel = {};
|
|
2368
|
+
try {
|
|
2369
|
+
const content = fs4.readFileSync(COST_LOG_FILE, "utf-8").trim();
|
|
2370
|
+
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2371
|
+
for (const line of content.split("\n")) {
|
|
2372
|
+
if (!line) continue;
|
|
2373
|
+
try {
|
|
2374
|
+
const entry = JSON.parse(line);
|
|
2375
|
+
totalUsd += entry.costUsd;
|
|
2376
|
+
calls += 1;
|
|
2377
|
+
byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
|
|
2378
|
+
} catch {
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2383
|
+
return { totalUsd, calls, byModel };
|
|
2384
|
+
}
|
|
2385
|
+
|
|
1896
2386
|
// src/openai-compat.ts
|
|
1897
2387
|
var StreamingResponse = class {
|
|
1898
2388
|
reader;
|
|
@@ -2078,6 +2568,7 @@ export {
|
|
|
2078
2568
|
USDC_SOLANA,
|
|
2079
2569
|
WALLET_DIR_PATH,
|
|
2080
2570
|
WALLET_FILE_PATH,
|
|
2571
|
+
clearCache,
|
|
2081
2572
|
createSolanaPaymentPayload,
|
|
2082
2573
|
createSolanaWallet,
|
|
2083
2574
|
createWallet,
|
|
@@ -2085,6 +2576,9 @@ export {
|
|
|
2085
2576
|
formatFundingMessageCompact,
|
|
2086
2577
|
formatNeedsFundingMessage,
|
|
2087
2578
|
formatWalletCreatedMessage,
|
|
2579
|
+
getCached,
|
|
2580
|
+
getCachedByRequest,
|
|
2581
|
+
getCostSummary,
|
|
2088
2582
|
getEip681Uri,
|
|
2089
2583
|
getOrCreateSolanaWallet,
|
|
2090
2584
|
getOrCreateWallet,
|
|
@@ -2092,10 +2586,18 @@ export {
|
|
|
2092
2586
|
getWalletAddress,
|
|
2093
2587
|
loadSolanaWallet,
|
|
2094
2588
|
loadWallet,
|
|
2589
|
+
logCost,
|
|
2095
2590
|
saveSolanaWallet,
|
|
2591
|
+
saveToCache,
|
|
2096
2592
|
saveWallet,
|
|
2593
|
+
scanSolanaWallets,
|
|
2594
|
+
scanWallets,
|
|
2595
|
+
setCache,
|
|
2596
|
+
setupAgentSolanaWallet,
|
|
2597
|
+
setupAgentWallet,
|
|
2097
2598
|
solanaClient,
|
|
2098
2599
|
solanaKeyToBytes,
|
|
2099
2600
|
solanaPublicKey,
|
|
2601
|
+
status,
|
|
2100
2602
|
testnetClient
|
|
2101
2603
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base",
|
|
5
|
+
"description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base and Solana",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|