@blockrun/llm 1.6.2 → 1.8.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 +88 -1
- package/dist/index.cjs +718 -12
- package/dist/index.d.cts +363 -3
- package/dist/index.d.ts +363 -3
- package/dist/index.js +713 -11
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -37,16 +37,20 @@ __export(index_exports, {
|
|
|
37
37
|
ImageClient: () => ImageClient,
|
|
38
38
|
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
39
39
|
LLMClient: () => LLMClient,
|
|
40
|
+
MusicClient: () => MusicClient,
|
|
40
41
|
OpenAI: () => OpenAI,
|
|
41
42
|
PaymentError: () => PaymentError,
|
|
43
|
+
PriceClient: () => PriceClient,
|
|
42
44
|
SOLANA_NETWORK: () => SOLANA_NETWORK,
|
|
43
45
|
SOLANA_WALLET_FILE_PATH: () => SOLANA_WALLET_FILE,
|
|
46
|
+
SearchClient: () => SearchClient,
|
|
44
47
|
SolanaLLMClient: () => SolanaLLMClient,
|
|
45
48
|
USDC_BASE: () => USDC_BASE,
|
|
46
49
|
USDC_BASE_CONTRACT: () => USDC_BASE_CONTRACT,
|
|
47
50
|
USDC_SOLANA: () => USDC_SOLANA,
|
|
48
51
|
WALLET_DIR_PATH: () => WALLET_DIR_PATH,
|
|
49
52
|
WALLET_FILE_PATH: () => WALLET_FILE_PATH,
|
|
53
|
+
XClient: () => XClient,
|
|
50
54
|
clearCache: () => clearCache,
|
|
51
55
|
createPaymentPayload: () => createPaymentPayload,
|
|
52
56
|
createSolanaPaymentPayload: () => createSolanaPaymentPayload,
|
|
@@ -1228,7 +1232,10 @@ var LLMClient = class _LLMClient {
|
|
|
1228
1232
|
contextWindow: m.contextWindow ?? m.context_window ?? 0,
|
|
1229
1233
|
maxOutput: m.maxOutput ?? m.max_output ?? 0,
|
|
1230
1234
|
categories: m.categories || [],
|
|
1231
|
-
available: true
|
|
1235
|
+
available: true,
|
|
1236
|
+
billingMode: m.billingMode ?? m.billing_mode,
|
|
1237
|
+
flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
|
|
1238
|
+
hidden: m.hidden
|
|
1232
1239
|
}));
|
|
1233
1240
|
}
|
|
1234
1241
|
/**
|
|
@@ -1898,8 +1905,703 @@ var ImageClient = class {
|
|
|
1898
1905
|
}
|
|
1899
1906
|
};
|
|
1900
1907
|
|
|
1901
|
-
// src/
|
|
1908
|
+
// src/music.ts
|
|
1902
1909
|
var import_accounts4 = require("viem/accounts");
|
|
1910
|
+
var DEFAULT_API_URL3 = "https://blockrun.ai/api";
|
|
1911
|
+
var DEFAULT_MODEL2 = "minimax/music-2.5+";
|
|
1912
|
+
var DEFAULT_TIMEOUT3 = 21e4;
|
|
1913
|
+
var MusicClient = class {
|
|
1914
|
+
account;
|
|
1915
|
+
privateKey;
|
|
1916
|
+
apiUrl;
|
|
1917
|
+
timeout;
|
|
1918
|
+
sessionTotalUsd = 0;
|
|
1919
|
+
sessionCalls = 0;
|
|
1920
|
+
constructor(options = {}) {
|
|
1921
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1922
|
+
const privateKey = options.privateKey || envKey;
|
|
1923
|
+
if (!privateKey) {
|
|
1924
|
+
throw new Error(
|
|
1925
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
validatePrivateKey(privateKey);
|
|
1929
|
+
this.privateKey = privateKey;
|
|
1930
|
+
this.account = (0, import_accounts4.privateKeyToAccount)(privateKey);
|
|
1931
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL3;
|
|
1932
|
+
validateApiUrl(apiUrl);
|
|
1933
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
1934
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT3;
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Generate a music track from a text prompt.
|
|
1938
|
+
*
|
|
1939
|
+
* Takes 1-3 minutes. Returns a CDN URL valid for ~24h.
|
|
1940
|
+
*
|
|
1941
|
+
* @param prompt - Music style, mood, or description
|
|
1942
|
+
* @param options - Optional generation parameters
|
|
1943
|
+
* @returns MusicResponse with track URL and metadata
|
|
1944
|
+
*
|
|
1945
|
+
* @example
|
|
1946
|
+
* const result = await client.generate('chill lo-fi beats with piano');
|
|
1947
|
+
* console.log(result.data[0].url); // Download this URL — expires in 24h
|
|
1948
|
+
*
|
|
1949
|
+
* @example With lyrics
|
|
1950
|
+
* const result = await client.generate('upbeat pop song', {
|
|
1951
|
+
* instrumental: false,
|
|
1952
|
+
* lyrics: 'Hello world, this is my song...'
|
|
1953
|
+
* });
|
|
1954
|
+
*/
|
|
1955
|
+
async generate(prompt, options) {
|
|
1956
|
+
const instrumental = options?.instrumental ?? true;
|
|
1957
|
+
const lyrics = options?.lyrics?.trim();
|
|
1958
|
+
if (instrumental && lyrics) {
|
|
1959
|
+
throw new Error("Cannot specify lyrics when instrumental is true");
|
|
1960
|
+
}
|
|
1961
|
+
const body = {
|
|
1962
|
+
model: options?.model || DEFAULT_MODEL2,
|
|
1963
|
+
prompt,
|
|
1964
|
+
instrumental
|
|
1965
|
+
};
|
|
1966
|
+
if (lyrics) body.lyrics = lyrics;
|
|
1967
|
+
return this.requestWithPayment("/v1/audio/generations", body);
|
|
1968
|
+
}
|
|
1969
|
+
async requestWithPayment(endpoint, body) {
|
|
1970
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
1971
|
+
const response = await this.fetchWithTimeout(url, {
|
|
1972
|
+
method: "POST",
|
|
1973
|
+
headers: { "Content-Type": "application/json" },
|
|
1974
|
+
body: JSON.stringify(body)
|
|
1975
|
+
});
|
|
1976
|
+
if (response.status === 402) {
|
|
1977
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
1978
|
+
}
|
|
1979
|
+
if (!response.ok) {
|
|
1980
|
+
let errorBody;
|
|
1981
|
+
try {
|
|
1982
|
+
errorBody = await response.json();
|
|
1983
|
+
} catch {
|
|
1984
|
+
errorBody = { error: "Request failed" };
|
|
1985
|
+
}
|
|
1986
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
1987
|
+
}
|
|
1988
|
+
return response.json();
|
|
1989
|
+
}
|
|
1990
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
1991
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
1992
|
+
if (!paymentHeader) {
|
|
1993
|
+
try {
|
|
1994
|
+
const respBody = await response.json();
|
|
1995
|
+
if (respBody.x402 || respBody.accepts) {
|
|
1996
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
1997
|
+
}
|
|
1998
|
+
} catch {
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (!paymentHeader) {
|
|
2002
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2003
|
+
}
|
|
2004
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2005
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2006
|
+
const paymentPayload = await createPaymentPayload(
|
|
2007
|
+
this.privateKey,
|
|
2008
|
+
this.account.address,
|
|
2009
|
+
details.recipient,
|
|
2010
|
+
details.amount,
|
|
2011
|
+
details.network || "eip155:8453",
|
|
2012
|
+
{
|
|
2013
|
+
resourceUrl: details.resource?.url || `${this.apiUrl}/v1/audio/generations`,
|
|
2014
|
+
resourceDescription: details.resource?.description || "BlockRun Music Generation",
|
|
2015
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2016
|
+
extra: details.extra
|
|
2017
|
+
}
|
|
2018
|
+
);
|
|
2019
|
+
const retryResponse = await this.fetchWithTimeout(url, {
|
|
2020
|
+
method: "POST",
|
|
2021
|
+
headers: {
|
|
2022
|
+
"Content-Type": "application/json",
|
|
2023
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2024
|
+
},
|
|
2025
|
+
body: JSON.stringify(body)
|
|
2026
|
+
});
|
|
2027
|
+
if (retryResponse.status === 402) {
|
|
2028
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2029
|
+
}
|
|
2030
|
+
if (!retryResponse.ok) {
|
|
2031
|
+
let errorBody;
|
|
2032
|
+
try {
|
|
2033
|
+
errorBody = await retryResponse.json();
|
|
2034
|
+
} catch {
|
|
2035
|
+
errorBody = { error: "Request failed" };
|
|
2036
|
+
}
|
|
2037
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
2038
|
+
}
|
|
2039
|
+
const data = await retryResponse.json();
|
|
2040
|
+
this.sessionCalls++;
|
|
2041
|
+
this.sessionTotalUsd += 0.1575;
|
|
2042
|
+
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
2043
|
+
if (txHash) data.txHash = txHash;
|
|
2044
|
+
return data;
|
|
2045
|
+
}
|
|
2046
|
+
async fetchWithTimeout(url, options) {
|
|
2047
|
+
const controller = new AbortController();
|
|
2048
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2049
|
+
try {
|
|
2050
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2051
|
+
} finally {
|
|
2052
|
+
clearTimeout(timeoutId);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
getWalletAddress() {
|
|
2056
|
+
return this.account.address;
|
|
2057
|
+
}
|
|
2058
|
+
getSpending() {
|
|
2059
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
|
|
2063
|
+
// src/search.ts
|
|
2064
|
+
var import_accounts5 = require("viem/accounts");
|
|
2065
|
+
var DEFAULT_API_URL4 = "https://blockrun.ai/api";
|
|
2066
|
+
var DEFAULT_TIMEOUT4 = 6e4;
|
|
2067
|
+
var SearchClient = class {
|
|
2068
|
+
account;
|
|
2069
|
+
privateKey;
|
|
2070
|
+
apiUrl;
|
|
2071
|
+
timeout;
|
|
2072
|
+
constructor(options = {}) {
|
|
2073
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2074
|
+
const privateKey = options.privateKey || envKey;
|
|
2075
|
+
if (!privateKey) {
|
|
2076
|
+
throw new Error(
|
|
2077
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2078
|
+
);
|
|
2079
|
+
}
|
|
2080
|
+
validatePrivateKey(privateKey);
|
|
2081
|
+
this.privateKey = privateKey;
|
|
2082
|
+
this.account = (0, import_accounts5.privateKeyToAccount)(privateKey);
|
|
2083
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL4;
|
|
2084
|
+
validateApiUrl(apiUrl);
|
|
2085
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2086
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT4;
|
|
2087
|
+
}
|
|
2088
|
+
async search(query, options) {
|
|
2089
|
+
if (!query || query.length > 1e3) {
|
|
2090
|
+
throw new Error("query must be 1-1000 characters");
|
|
2091
|
+
}
|
|
2092
|
+
const maxResults = options?.maxResults ?? 10;
|
|
2093
|
+
if (maxResults < 1 || maxResults > 50) {
|
|
2094
|
+
throw new Error("maxResults must be between 1 and 50");
|
|
2095
|
+
}
|
|
2096
|
+
const body = {
|
|
2097
|
+
query,
|
|
2098
|
+
max_results: maxResults
|
|
2099
|
+
};
|
|
2100
|
+
if (options?.sources) body.sources = options.sources;
|
|
2101
|
+
if (options?.fromDate) body.from_date = options.fromDate;
|
|
2102
|
+
if (options?.toDate) body.to_date = options.toDate;
|
|
2103
|
+
return this.requestWithPayment("/v1/search", body);
|
|
2104
|
+
}
|
|
2105
|
+
async requestWithPayment(endpoint, body) {
|
|
2106
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2107
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2108
|
+
method: "POST",
|
|
2109
|
+
headers: { "Content-Type": "application/json" },
|
|
2110
|
+
body: JSON.stringify(body)
|
|
2111
|
+
});
|
|
2112
|
+
if (response.status === 402) {
|
|
2113
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2114
|
+
}
|
|
2115
|
+
if (!response.ok) {
|
|
2116
|
+
let errorBody;
|
|
2117
|
+
try {
|
|
2118
|
+
errorBody = await response.json();
|
|
2119
|
+
} catch {
|
|
2120
|
+
errorBody = { error: "Request failed" };
|
|
2121
|
+
}
|
|
2122
|
+
throw new APIError(
|
|
2123
|
+
`API error: ${response.status}`,
|
|
2124
|
+
response.status,
|
|
2125
|
+
sanitizeErrorResponse(errorBody)
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
return response.json();
|
|
2129
|
+
}
|
|
2130
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2131
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2132
|
+
if (!paymentHeader) {
|
|
2133
|
+
try {
|
|
2134
|
+
const respBody = await response.json();
|
|
2135
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2136
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2137
|
+
}
|
|
2138
|
+
} catch {
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
if (!paymentHeader) {
|
|
2142
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2143
|
+
}
|
|
2144
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2145
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2146
|
+
const paymentPayload = await createPaymentPayload(
|
|
2147
|
+
this.privateKey,
|
|
2148
|
+
this.account.address,
|
|
2149
|
+
details.recipient,
|
|
2150
|
+
details.amount,
|
|
2151
|
+
details.network || "eip155:8453",
|
|
2152
|
+
{
|
|
2153
|
+
resourceUrl: details.resource?.url || url,
|
|
2154
|
+
resourceDescription: details.resource?.description || "BlockRun Search",
|
|
2155
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2156
|
+
extra: details.extra
|
|
2157
|
+
}
|
|
2158
|
+
);
|
|
2159
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2160
|
+
method: "POST",
|
|
2161
|
+
headers: {
|
|
2162
|
+
"Content-Type": "application/json",
|
|
2163
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2164
|
+
},
|
|
2165
|
+
body: JSON.stringify(body)
|
|
2166
|
+
});
|
|
2167
|
+
if (retry.status === 402) {
|
|
2168
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2169
|
+
}
|
|
2170
|
+
if (!retry.ok) {
|
|
2171
|
+
let errorBody;
|
|
2172
|
+
try {
|
|
2173
|
+
errorBody = await retry.json();
|
|
2174
|
+
} catch {
|
|
2175
|
+
errorBody = { error: "Request failed" };
|
|
2176
|
+
}
|
|
2177
|
+
throw new APIError(
|
|
2178
|
+
`API error after payment: ${retry.status}`,
|
|
2179
|
+
retry.status,
|
|
2180
|
+
sanitizeErrorResponse(errorBody)
|
|
2181
|
+
);
|
|
2182
|
+
}
|
|
2183
|
+
return retry.json();
|
|
2184
|
+
}
|
|
2185
|
+
async fetchWithTimeout(url, init) {
|
|
2186
|
+
const controller = new AbortController();
|
|
2187
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2188
|
+
try {
|
|
2189
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2190
|
+
} finally {
|
|
2191
|
+
clearTimeout(timeoutId);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
getWalletAddress() {
|
|
2195
|
+
return this.account.address;
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
// src/x-client.ts
|
|
2200
|
+
var import_accounts6 = require("viem/accounts");
|
|
2201
|
+
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2202
|
+
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2203
|
+
var XClient = class {
|
|
2204
|
+
account;
|
|
2205
|
+
privateKey;
|
|
2206
|
+
apiUrl;
|
|
2207
|
+
timeout;
|
|
2208
|
+
constructor(options = {}) {
|
|
2209
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2210
|
+
const privateKey = options.privateKey || envKey;
|
|
2211
|
+
if (!privateKey) {
|
|
2212
|
+
throw new Error(
|
|
2213
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
validatePrivateKey(privateKey);
|
|
2217
|
+
this.privateKey = privateKey;
|
|
2218
|
+
this.account = (0, import_accounts6.privateKeyToAccount)(privateKey);
|
|
2219
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL5;
|
|
2220
|
+
validateApiUrl(apiUrl);
|
|
2221
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2222
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2223
|
+
}
|
|
2224
|
+
// ───────── User endpoints ─────────
|
|
2225
|
+
userLookup(usernames) {
|
|
2226
|
+
return this.post("/v1/x/users/lookup", { usernames });
|
|
2227
|
+
}
|
|
2228
|
+
userInfo(username) {
|
|
2229
|
+
return this.post("/v1/x/users/info", { username });
|
|
2230
|
+
}
|
|
2231
|
+
followers(username, cursor) {
|
|
2232
|
+
const body = { username };
|
|
2233
|
+
if (cursor) body.cursor = cursor;
|
|
2234
|
+
return this.post("/v1/x/users/followers", body);
|
|
2235
|
+
}
|
|
2236
|
+
/** `/v1/x/users/following` (singular path variant). */
|
|
2237
|
+
following(username, cursor) {
|
|
2238
|
+
const body = { username };
|
|
2239
|
+
if (cursor) body.cursor = cursor;
|
|
2240
|
+
return this.post("/v1/x/users/following", body);
|
|
2241
|
+
}
|
|
2242
|
+
/** `/v1/x/users/followings` (plural path variant). */
|
|
2243
|
+
followings(username, cursor) {
|
|
2244
|
+
const body = { username };
|
|
2245
|
+
if (cursor) body.cursor = cursor;
|
|
2246
|
+
return this.post("/v1/x/users/followings", body);
|
|
2247
|
+
}
|
|
2248
|
+
verifiedFollowers(userId, cursor) {
|
|
2249
|
+
const body = { userId };
|
|
2250
|
+
if (cursor) body.cursor = cursor;
|
|
2251
|
+
return this.post(
|
|
2252
|
+
"/v1/x/users/verified-followers",
|
|
2253
|
+
body
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
userTweets(options) {
|
|
2257
|
+
if (!options.username && !options.userId) {
|
|
2258
|
+
throw new Error("Either username or userId is required");
|
|
2259
|
+
}
|
|
2260
|
+
const body = {};
|
|
2261
|
+
if (options.username) body.username = options.username;
|
|
2262
|
+
if (options.userId) body.userId = options.userId;
|
|
2263
|
+
if (options.cursor) body.cursor = options.cursor;
|
|
2264
|
+
if (options.includeReplies !== void 0) body.includeReplies = options.includeReplies;
|
|
2265
|
+
return this.post("/v1/x/users/tweets", body);
|
|
2266
|
+
}
|
|
2267
|
+
mentions(username, options) {
|
|
2268
|
+
const body = { username };
|
|
2269
|
+
if (options?.sinceTime) body.sinceTime = options.sinceTime;
|
|
2270
|
+
if (options?.untilTime) body.untilTime = options.untilTime;
|
|
2271
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2272
|
+
return this.post("/v1/x/users/mentions", body);
|
|
2273
|
+
}
|
|
2274
|
+
// ───────── Tweet endpoints ─────────
|
|
2275
|
+
tweetLookup(tweetIds) {
|
|
2276
|
+
return this.post("/v1/x/tweets/lookup", {
|
|
2277
|
+
tweet_ids: tweetIds
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
tweetReplies(tweetId, options) {
|
|
2281
|
+
const body = { tweetId };
|
|
2282
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2283
|
+
if (options?.queryType) body.queryType = options.queryType;
|
|
2284
|
+
return this.post("/v1/x/tweets/replies", body);
|
|
2285
|
+
}
|
|
2286
|
+
tweetThread(tweetId, cursor) {
|
|
2287
|
+
const body = { tweetId };
|
|
2288
|
+
if (cursor) body.cursor = cursor;
|
|
2289
|
+
return this.post("/v1/x/tweets/thread", body);
|
|
2290
|
+
}
|
|
2291
|
+
// ───────── Search & discovery ─────────
|
|
2292
|
+
search(query, options) {
|
|
2293
|
+
const body = { query };
|
|
2294
|
+
if (options?.queryType) body.queryType = options.queryType;
|
|
2295
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2296
|
+
return this.post("/v1/x/search", body);
|
|
2297
|
+
}
|
|
2298
|
+
trending() {
|
|
2299
|
+
return this.post("/v1/x/trending", {});
|
|
2300
|
+
}
|
|
2301
|
+
articlesRising() {
|
|
2302
|
+
return this.post("/v1/x/articles/rising", {});
|
|
2303
|
+
}
|
|
2304
|
+
// ───────── Internals ─────────
|
|
2305
|
+
async post(endpoint, body) {
|
|
2306
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2307
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2308
|
+
method: "POST",
|
|
2309
|
+
headers: { "Content-Type": "application/json" },
|
|
2310
|
+
body: JSON.stringify(body)
|
|
2311
|
+
});
|
|
2312
|
+
if (response.status === 402) {
|
|
2313
|
+
return this.payAndRetry(url, body, response);
|
|
2314
|
+
}
|
|
2315
|
+
if (!response.ok) {
|
|
2316
|
+
let errorBody;
|
|
2317
|
+
try {
|
|
2318
|
+
errorBody = await response.json();
|
|
2319
|
+
} catch {
|
|
2320
|
+
errorBody = { error: "Request failed" };
|
|
2321
|
+
}
|
|
2322
|
+
throw new APIError(
|
|
2323
|
+
`API error: ${response.status}`,
|
|
2324
|
+
response.status,
|
|
2325
|
+
sanitizeErrorResponse(errorBody)
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
return response.json();
|
|
2329
|
+
}
|
|
2330
|
+
async payAndRetry(url, body, response) {
|
|
2331
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2332
|
+
if (!paymentHeader) {
|
|
2333
|
+
try {
|
|
2334
|
+
const respBody = await response.json();
|
|
2335
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2336
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2337
|
+
}
|
|
2338
|
+
} catch {
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
if (!paymentHeader) {
|
|
2342
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2343
|
+
}
|
|
2344
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2345
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2346
|
+
const paymentPayload = await createPaymentPayload(
|
|
2347
|
+
this.privateKey,
|
|
2348
|
+
this.account.address,
|
|
2349
|
+
details.recipient,
|
|
2350
|
+
details.amount,
|
|
2351
|
+
details.network || "eip155:8453",
|
|
2352
|
+
{
|
|
2353
|
+
resourceUrl: details.resource?.url || url,
|
|
2354
|
+
resourceDescription: details.resource?.description || "BlockRun X API",
|
|
2355
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2356
|
+
extra: details.extra
|
|
2357
|
+
}
|
|
2358
|
+
);
|
|
2359
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2360
|
+
method: "POST",
|
|
2361
|
+
headers: {
|
|
2362
|
+
"Content-Type": "application/json",
|
|
2363
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2364
|
+
},
|
|
2365
|
+
body: JSON.stringify(body)
|
|
2366
|
+
});
|
|
2367
|
+
if (retry.status === 402) {
|
|
2368
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2369
|
+
}
|
|
2370
|
+
if (!retry.ok) {
|
|
2371
|
+
let errorBody;
|
|
2372
|
+
try {
|
|
2373
|
+
errorBody = await retry.json();
|
|
2374
|
+
} catch {
|
|
2375
|
+
errorBody = { error: "Request failed" };
|
|
2376
|
+
}
|
|
2377
|
+
throw new APIError(
|
|
2378
|
+
`API error after payment: ${retry.status}`,
|
|
2379
|
+
retry.status,
|
|
2380
|
+
sanitizeErrorResponse(errorBody)
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
return retry.json();
|
|
2384
|
+
}
|
|
2385
|
+
async fetchWithTimeout(url, init) {
|
|
2386
|
+
const controller = new AbortController();
|
|
2387
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2388
|
+
try {
|
|
2389
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2390
|
+
} finally {
|
|
2391
|
+
clearTimeout(timeoutId);
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
getWalletAddress() {
|
|
2395
|
+
return this.account.address;
|
|
2396
|
+
}
|
|
2397
|
+
};
|
|
2398
|
+
|
|
2399
|
+
// src/price.ts
|
|
2400
|
+
var import_accounts7 = require("viem/accounts");
|
|
2401
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2402
|
+
var DEFAULT_TIMEOUT6 = 3e4;
|
|
2403
|
+
var PriceClient = class {
|
|
2404
|
+
account = null;
|
|
2405
|
+
privateKey = null;
|
|
2406
|
+
apiUrl;
|
|
2407
|
+
timeout;
|
|
2408
|
+
constructor(options = {}) {
|
|
2409
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2410
|
+
const privateKey = options.privateKey || envKey;
|
|
2411
|
+
const requireWallet = options.requireWallet ?? true;
|
|
2412
|
+
if (!privateKey && requireWallet) {
|
|
2413
|
+
throw new Error(
|
|
2414
|
+
"Private key required for paid endpoints. Pass privateKey in options, set BLOCKRUN_WALLET_KEY, or pass requireWallet: false for free-only usage."
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
if (privateKey) {
|
|
2418
|
+
validatePrivateKey(privateKey);
|
|
2419
|
+
this.privateKey = privateKey;
|
|
2420
|
+
this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
|
|
2421
|
+
}
|
|
2422
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2423
|
+
validateApiUrl(apiUrl);
|
|
2424
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2425
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2426
|
+
}
|
|
2427
|
+
async price(category, symbol, options) {
|
|
2428
|
+
if (!symbol) throw new Error("symbol is required");
|
|
2429
|
+
const path5 = categoryPath(category, options?.market, "price", symbol);
|
|
2430
|
+
const query = {};
|
|
2431
|
+
if (options?.session) query.session = options.session;
|
|
2432
|
+
const data = await this.getWithPayment(path5, query);
|
|
2433
|
+
return {
|
|
2434
|
+
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
2435
|
+
price: data.price,
|
|
2436
|
+
publishTime: data.publishTime,
|
|
2437
|
+
confidence: data.confidence,
|
|
2438
|
+
feedId: data.feedId,
|
|
2439
|
+
timestamp: data.timestamp,
|
|
2440
|
+
assetType: data.assetType,
|
|
2441
|
+
category: data.category,
|
|
2442
|
+
source: data.source,
|
|
2443
|
+
free: data.free
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
async history(category, symbol, options) {
|
|
2447
|
+
if (!symbol) throw new Error("symbol is required");
|
|
2448
|
+
if (!options.from || options.from <= 0) {
|
|
2449
|
+
throw new Error("history requires options.from (unix seconds)");
|
|
2450
|
+
}
|
|
2451
|
+
const path5 = categoryPath(category, options.market, "history", symbol);
|
|
2452
|
+
const query = {
|
|
2453
|
+
resolution: options.resolution ?? "D",
|
|
2454
|
+
from: String(options.from)
|
|
2455
|
+
};
|
|
2456
|
+
if (options.to) query.to = String(options.to);
|
|
2457
|
+
if (options.session) query.session = options.session;
|
|
2458
|
+
const data = await this.getWithPayment(path5, query);
|
|
2459
|
+
return {
|
|
2460
|
+
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
2461
|
+
resolution: data.resolution ?? (options.resolution ?? "D"),
|
|
2462
|
+
from: data.from,
|
|
2463
|
+
to: data.to,
|
|
2464
|
+
bars: data.bars ?? [],
|
|
2465
|
+
source: data.source,
|
|
2466
|
+
category: data.category
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
async listSymbols(category, options) {
|
|
2470
|
+
const path5 = categoryPath(category, options?.market, "list");
|
|
2471
|
+
const query = {
|
|
2472
|
+
limit: String(options?.limit && options.limit > 0 ? options.limit : 100)
|
|
2473
|
+
};
|
|
2474
|
+
if (options?.query) query.q = options.query;
|
|
2475
|
+
const data = await this.getWithPayment(path5, query);
|
|
2476
|
+
if (Array.isArray(data)) {
|
|
2477
|
+
return { symbols: data, count: data.length };
|
|
2478
|
+
}
|
|
2479
|
+
const obj = data;
|
|
2480
|
+
const symbols = obj.symbols ?? obj.feeds ?? [];
|
|
2481
|
+
return {
|
|
2482
|
+
symbols,
|
|
2483
|
+
count: obj.count ?? symbols.length
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
getWalletAddress() {
|
|
2487
|
+
return this.account?.address ?? null;
|
|
2488
|
+
}
|
|
2489
|
+
async getWithPayment(endpoint, query) {
|
|
2490
|
+
const url = buildUrl(`${this.apiUrl}${endpoint}`, query);
|
|
2491
|
+
const response = await this.fetchWithTimeout(url, { method: "GET" });
|
|
2492
|
+
if (response.status === 402) {
|
|
2493
|
+
if (!this.privateKey || !this.account) {
|
|
2494
|
+
throw new PaymentError(
|
|
2495
|
+
`${endpoint} returned 402 Payment Required but no wallet is configured.`
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
return this.payAndRetry(url, response);
|
|
2499
|
+
}
|
|
2500
|
+
if (!response.ok) {
|
|
2501
|
+
let errorBody;
|
|
2502
|
+
try {
|
|
2503
|
+
errorBody = await response.json();
|
|
2504
|
+
} catch {
|
|
2505
|
+
errorBody = { error: "Request failed" };
|
|
2506
|
+
}
|
|
2507
|
+
throw new APIError(
|
|
2508
|
+
`API error: ${response.status}`,
|
|
2509
|
+
response.status,
|
|
2510
|
+
sanitizeErrorResponse(errorBody)
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
return response.json();
|
|
2514
|
+
}
|
|
2515
|
+
async payAndRetry(url, response) {
|
|
2516
|
+
if (!this.privateKey || !this.account) {
|
|
2517
|
+
throw new PaymentError("Wallet required to sign payment.");
|
|
2518
|
+
}
|
|
2519
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2520
|
+
if (!paymentHeader) {
|
|
2521
|
+
try {
|
|
2522
|
+
const respBody = await response.json();
|
|
2523
|
+
if (respBody.x402) {
|
|
2524
|
+
paymentHeader = btoa(JSON.stringify(respBody.x402));
|
|
2525
|
+
} else if (respBody.accepts) {
|
|
2526
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2527
|
+
}
|
|
2528
|
+
} catch {
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
if (!paymentHeader) {
|
|
2532
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2533
|
+
}
|
|
2534
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2535
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2536
|
+
const paymentPayload = await createPaymentPayload(
|
|
2537
|
+
this.privateKey,
|
|
2538
|
+
this.account.address,
|
|
2539
|
+
details.recipient,
|
|
2540
|
+
details.amount,
|
|
2541
|
+
details.network || "eip155:8453",
|
|
2542
|
+
{
|
|
2543
|
+
resourceUrl: details.resource?.url || url,
|
|
2544
|
+
resourceDescription: details.resource?.description || "BlockRun Price Data",
|
|
2545
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2546
|
+
extra: details.extra
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2550
|
+
method: "GET",
|
|
2551
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
2552
|
+
});
|
|
2553
|
+
if (retry.status === 402) {
|
|
2554
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2555
|
+
}
|
|
2556
|
+
if (!retry.ok) {
|
|
2557
|
+
let errorBody;
|
|
2558
|
+
try {
|
|
2559
|
+
errorBody = await retry.json();
|
|
2560
|
+
} catch {
|
|
2561
|
+
errorBody = { error: "Request failed" };
|
|
2562
|
+
}
|
|
2563
|
+
throw new APIError(
|
|
2564
|
+
`API error after payment: ${retry.status}`,
|
|
2565
|
+
retry.status,
|
|
2566
|
+
sanitizeErrorResponse(errorBody)
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
return retry.json();
|
|
2570
|
+
}
|
|
2571
|
+
async fetchWithTimeout(url, init) {
|
|
2572
|
+
const controller = new AbortController();
|
|
2573
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2574
|
+
try {
|
|
2575
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2576
|
+
} finally {
|
|
2577
|
+
clearTimeout(timeoutId);
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
function categoryPath(category, market, kind, symbol) {
|
|
2582
|
+
let base;
|
|
2583
|
+
if (category === "stocks") {
|
|
2584
|
+
if (!market) {
|
|
2585
|
+
throw new Error("market is required when category === 'stocks'");
|
|
2586
|
+
}
|
|
2587
|
+
base = `/v1/stocks/${market}`;
|
|
2588
|
+
} else if (["crypto", "fx", "commodity", "usstock"].includes(category)) {
|
|
2589
|
+
base = `/v1/${category}`;
|
|
2590
|
+
} else {
|
|
2591
|
+
throw new Error(`unknown category: ${category}`);
|
|
2592
|
+
}
|
|
2593
|
+
if (!symbol) return `${base}/${kind}`;
|
|
2594
|
+
return `${base}/${kind}/${encodeURIComponent(symbol.toUpperCase())}`;
|
|
2595
|
+
}
|
|
2596
|
+
function buildUrl(base, query) {
|
|
2597
|
+
const params = Object.entries(query);
|
|
2598
|
+
if (params.length === 0) return base;
|
|
2599
|
+
const qs = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
2600
|
+
return `${base}?${qs}`;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// src/wallet.ts
|
|
2604
|
+
var import_accounts8 = require("viem/accounts");
|
|
1903
2605
|
var fs = __toESM(require("fs"), 1);
|
|
1904
2606
|
var path = __toESM(require("path"), 1);
|
|
1905
2607
|
var os = __toESM(require("os"), 1);
|
|
@@ -1908,8 +2610,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
1908
2610
|
var WALLET_DIR = path.join(os.homedir(), ".blockrun");
|
|
1909
2611
|
var WALLET_FILE = path.join(WALLET_DIR, ".session");
|
|
1910
2612
|
function createWallet() {
|
|
1911
|
-
const privateKey = (0,
|
|
1912
|
-
const account = (0,
|
|
2613
|
+
const privateKey = (0, import_accounts8.generatePrivateKey)();
|
|
2614
|
+
const account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
1913
2615
|
return {
|
|
1914
2616
|
address: account.address,
|
|
1915
2617
|
privateKey
|
|
@@ -1965,12 +2667,12 @@ function loadWallet() {
|
|
|
1965
2667
|
function getOrCreateWallet() {
|
|
1966
2668
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1967
2669
|
if (envKey) {
|
|
1968
|
-
const account = (0,
|
|
2670
|
+
const account = (0, import_accounts8.privateKeyToAccount)(envKey);
|
|
1969
2671
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
1970
2672
|
}
|
|
1971
2673
|
const fileKey = loadWallet();
|
|
1972
2674
|
if (fileKey) {
|
|
1973
|
-
const account = (0,
|
|
2675
|
+
const account = (0, import_accounts8.privateKeyToAccount)(fileKey);
|
|
1974
2676
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
1975
2677
|
}
|
|
1976
2678
|
const { address, privateKey } = createWallet();
|
|
@@ -1980,11 +2682,11 @@ function getOrCreateWallet() {
|
|
|
1980
2682
|
function getWalletAddress() {
|
|
1981
2683
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1982
2684
|
if (envKey) {
|
|
1983
|
-
return (0,
|
|
2685
|
+
return (0, import_accounts8.privateKeyToAccount)(envKey).address;
|
|
1984
2686
|
}
|
|
1985
2687
|
const fileKey = loadWallet();
|
|
1986
2688
|
if (fileKey) {
|
|
1987
|
-
return (0,
|
|
2689
|
+
return (0, import_accounts8.privateKeyToAccount)(fileKey).address;
|
|
1988
2690
|
}
|
|
1989
2691
|
return null;
|
|
1990
2692
|
}
|
|
@@ -2164,7 +2866,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
2164
2866
|
// src/solana-client.ts
|
|
2165
2867
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
2166
2868
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
2167
|
-
var
|
|
2869
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2168
2870
|
var SDK_VERSION2 = "0.3.0";
|
|
2169
2871
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
2170
2872
|
var SolanaLLMClient = class {
|
|
@@ -2189,7 +2891,7 @@ var SolanaLLMClient = class {
|
|
|
2189
2891
|
validateApiUrl(apiUrl);
|
|
2190
2892
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2191
2893
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
2192
|
-
this.timeout = options.timeout ||
|
|
2894
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2193
2895
|
}
|
|
2194
2896
|
/** Get Solana wallet address (public key in base58). */
|
|
2195
2897
|
async getWalletAddress() {
|
|
@@ -3134,7 +3836,7 @@ var OpenAI = class {
|
|
|
3134
3836
|
};
|
|
3135
3837
|
|
|
3136
3838
|
// src/anthropic-compat.ts
|
|
3137
|
-
var
|
|
3839
|
+
var import_accounts9 = require("viem/accounts");
|
|
3138
3840
|
var AnthropicClient = class {
|
|
3139
3841
|
_client = null;
|
|
3140
3842
|
_clientPromise = null;
|
|
@@ -3147,7 +3849,7 @@ var AnthropicClient = class {
|
|
|
3147
3849
|
const key = options.privateKey ?? wallet.privateKey;
|
|
3148
3850
|
validatePrivateKey(key);
|
|
3149
3851
|
this._privateKey = key;
|
|
3150
|
-
this._account = (0,
|
|
3852
|
+
this._account = (0, import_accounts9.privateKeyToAccount)(this._privateKey);
|
|
3151
3853
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
3152
3854
|
validateApiUrl(apiUrl);
|
|
3153
3855
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -3250,16 +3952,20 @@ var AnthropicClient = class {
|
|
|
3250
3952
|
ImageClient,
|
|
3251
3953
|
KNOWN_PROVIDERS,
|
|
3252
3954
|
LLMClient,
|
|
3955
|
+
MusicClient,
|
|
3253
3956
|
OpenAI,
|
|
3254
3957
|
PaymentError,
|
|
3958
|
+
PriceClient,
|
|
3255
3959
|
SOLANA_NETWORK,
|
|
3256
3960
|
SOLANA_WALLET_FILE_PATH,
|
|
3961
|
+
SearchClient,
|
|
3257
3962
|
SolanaLLMClient,
|
|
3258
3963
|
USDC_BASE,
|
|
3259
3964
|
USDC_BASE_CONTRACT,
|
|
3260
3965
|
USDC_SOLANA,
|
|
3261
3966
|
WALLET_DIR_PATH,
|
|
3262
3967
|
WALLET_FILE_PATH,
|
|
3968
|
+
XClient,
|
|
3263
3969
|
clearCache,
|
|
3264
3970
|
createPaymentPayload,
|
|
3265
3971
|
createSolanaPaymentPayload,
|