@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.js
CHANGED
|
@@ -1143,7 +1143,10 @@ var LLMClient = class _LLMClient {
|
|
|
1143
1143
|
contextWindow: m.contextWindow ?? m.context_window ?? 0,
|
|
1144
1144
|
maxOutput: m.maxOutput ?? m.max_output ?? 0,
|
|
1145
1145
|
categories: m.categories || [],
|
|
1146
|
-
available: true
|
|
1146
|
+
available: true,
|
|
1147
|
+
billingMode: m.billingMode ?? m.billing_mode,
|
|
1148
|
+
flatPrice: m.flatPrice ?? m.flat_price ?? m.pricing?.flat,
|
|
1149
|
+
hidden: m.hidden
|
|
1147
1150
|
}));
|
|
1148
1151
|
}
|
|
1149
1152
|
/**
|
|
@@ -1813,8 +1816,703 @@ var ImageClient = class {
|
|
|
1813
1816
|
}
|
|
1814
1817
|
};
|
|
1815
1818
|
|
|
1819
|
+
// src/music.ts
|
|
1820
|
+
import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
|
|
1821
|
+
var DEFAULT_API_URL3 = "https://blockrun.ai/api";
|
|
1822
|
+
var DEFAULT_MODEL2 = "minimax/music-2.5+";
|
|
1823
|
+
var DEFAULT_TIMEOUT3 = 21e4;
|
|
1824
|
+
var MusicClient = class {
|
|
1825
|
+
account;
|
|
1826
|
+
privateKey;
|
|
1827
|
+
apiUrl;
|
|
1828
|
+
timeout;
|
|
1829
|
+
sessionTotalUsd = 0;
|
|
1830
|
+
sessionCalls = 0;
|
|
1831
|
+
constructor(options = {}) {
|
|
1832
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1833
|
+
const privateKey = options.privateKey || envKey;
|
|
1834
|
+
if (!privateKey) {
|
|
1835
|
+
throw new Error(
|
|
1836
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
validatePrivateKey(privateKey);
|
|
1840
|
+
this.privateKey = privateKey;
|
|
1841
|
+
this.account = privateKeyToAccount3(privateKey);
|
|
1842
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL3;
|
|
1843
|
+
validateApiUrl(apiUrl);
|
|
1844
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
1845
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT3;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Generate a music track from a text prompt.
|
|
1849
|
+
*
|
|
1850
|
+
* Takes 1-3 minutes. Returns a CDN URL valid for ~24h.
|
|
1851
|
+
*
|
|
1852
|
+
* @param prompt - Music style, mood, or description
|
|
1853
|
+
* @param options - Optional generation parameters
|
|
1854
|
+
* @returns MusicResponse with track URL and metadata
|
|
1855
|
+
*
|
|
1856
|
+
* @example
|
|
1857
|
+
* const result = await client.generate('chill lo-fi beats with piano');
|
|
1858
|
+
* console.log(result.data[0].url); // Download this URL — expires in 24h
|
|
1859
|
+
*
|
|
1860
|
+
* @example With lyrics
|
|
1861
|
+
* const result = await client.generate('upbeat pop song', {
|
|
1862
|
+
* instrumental: false,
|
|
1863
|
+
* lyrics: 'Hello world, this is my song...'
|
|
1864
|
+
* });
|
|
1865
|
+
*/
|
|
1866
|
+
async generate(prompt, options) {
|
|
1867
|
+
const instrumental = options?.instrumental ?? true;
|
|
1868
|
+
const lyrics = options?.lyrics?.trim();
|
|
1869
|
+
if (instrumental && lyrics) {
|
|
1870
|
+
throw new Error("Cannot specify lyrics when instrumental is true");
|
|
1871
|
+
}
|
|
1872
|
+
const body = {
|
|
1873
|
+
model: options?.model || DEFAULT_MODEL2,
|
|
1874
|
+
prompt,
|
|
1875
|
+
instrumental
|
|
1876
|
+
};
|
|
1877
|
+
if (lyrics) body.lyrics = lyrics;
|
|
1878
|
+
return this.requestWithPayment("/v1/audio/generations", body);
|
|
1879
|
+
}
|
|
1880
|
+
async requestWithPayment(endpoint, body) {
|
|
1881
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
1882
|
+
const response = await this.fetchWithTimeout(url, {
|
|
1883
|
+
method: "POST",
|
|
1884
|
+
headers: { "Content-Type": "application/json" },
|
|
1885
|
+
body: JSON.stringify(body)
|
|
1886
|
+
});
|
|
1887
|
+
if (response.status === 402) {
|
|
1888
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
1889
|
+
}
|
|
1890
|
+
if (!response.ok) {
|
|
1891
|
+
let errorBody;
|
|
1892
|
+
try {
|
|
1893
|
+
errorBody = await response.json();
|
|
1894
|
+
} catch {
|
|
1895
|
+
errorBody = { error: "Request failed" };
|
|
1896
|
+
}
|
|
1897
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
1898
|
+
}
|
|
1899
|
+
return response.json();
|
|
1900
|
+
}
|
|
1901
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
1902
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
1903
|
+
if (!paymentHeader) {
|
|
1904
|
+
try {
|
|
1905
|
+
const respBody = await response.json();
|
|
1906
|
+
if (respBody.x402 || respBody.accepts) {
|
|
1907
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
1908
|
+
}
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
if (!paymentHeader) {
|
|
1913
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
1914
|
+
}
|
|
1915
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
1916
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
1917
|
+
const paymentPayload = await createPaymentPayload(
|
|
1918
|
+
this.privateKey,
|
|
1919
|
+
this.account.address,
|
|
1920
|
+
details.recipient,
|
|
1921
|
+
details.amount,
|
|
1922
|
+
details.network || "eip155:8453",
|
|
1923
|
+
{
|
|
1924
|
+
resourceUrl: details.resource?.url || `${this.apiUrl}/v1/audio/generations`,
|
|
1925
|
+
resourceDescription: details.resource?.description || "BlockRun Music Generation",
|
|
1926
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
1927
|
+
extra: details.extra
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
const retryResponse = await this.fetchWithTimeout(url, {
|
|
1931
|
+
method: "POST",
|
|
1932
|
+
headers: {
|
|
1933
|
+
"Content-Type": "application/json",
|
|
1934
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1935
|
+
},
|
|
1936
|
+
body: JSON.stringify(body)
|
|
1937
|
+
});
|
|
1938
|
+
if (retryResponse.status === 402) {
|
|
1939
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
1940
|
+
}
|
|
1941
|
+
if (!retryResponse.ok) {
|
|
1942
|
+
let errorBody;
|
|
1943
|
+
try {
|
|
1944
|
+
errorBody = await retryResponse.json();
|
|
1945
|
+
} catch {
|
|
1946
|
+
errorBody = { error: "Request failed" };
|
|
1947
|
+
}
|
|
1948
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
1949
|
+
}
|
|
1950
|
+
const data = await retryResponse.json();
|
|
1951
|
+
this.sessionCalls++;
|
|
1952
|
+
this.sessionTotalUsd += 0.1575;
|
|
1953
|
+
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
1954
|
+
if (txHash) data.txHash = txHash;
|
|
1955
|
+
return data;
|
|
1956
|
+
}
|
|
1957
|
+
async fetchWithTimeout(url, options) {
|
|
1958
|
+
const controller = new AbortController();
|
|
1959
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1960
|
+
try {
|
|
1961
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
1962
|
+
} finally {
|
|
1963
|
+
clearTimeout(timeoutId);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
getWalletAddress() {
|
|
1967
|
+
return this.account.address;
|
|
1968
|
+
}
|
|
1969
|
+
getSpending() {
|
|
1970
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1974
|
+
// src/search.ts
|
|
1975
|
+
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
1976
|
+
var DEFAULT_API_URL4 = "https://blockrun.ai/api";
|
|
1977
|
+
var DEFAULT_TIMEOUT4 = 6e4;
|
|
1978
|
+
var SearchClient = class {
|
|
1979
|
+
account;
|
|
1980
|
+
privateKey;
|
|
1981
|
+
apiUrl;
|
|
1982
|
+
timeout;
|
|
1983
|
+
constructor(options = {}) {
|
|
1984
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1985
|
+
const privateKey = options.privateKey || envKey;
|
|
1986
|
+
if (!privateKey) {
|
|
1987
|
+
throw new Error(
|
|
1988
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
validatePrivateKey(privateKey);
|
|
1992
|
+
this.privateKey = privateKey;
|
|
1993
|
+
this.account = privateKeyToAccount4(privateKey);
|
|
1994
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL4;
|
|
1995
|
+
validateApiUrl(apiUrl);
|
|
1996
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
1997
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT4;
|
|
1998
|
+
}
|
|
1999
|
+
async search(query, options) {
|
|
2000
|
+
if (!query || query.length > 1e3) {
|
|
2001
|
+
throw new Error("query must be 1-1000 characters");
|
|
2002
|
+
}
|
|
2003
|
+
const maxResults = options?.maxResults ?? 10;
|
|
2004
|
+
if (maxResults < 1 || maxResults > 50) {
|
|
2005
|
+
throw new Error("maxResults must be between 1 and 50");
|
|
2006
|
+
}
|
|
2007
|
+
const body = {
|
|
2008
|
+
query,
|
|
2009
|
+
max_results: maxResults
|
|
2010
|
+
};
|
|
2011
|
+
if (options?.sources) body.sources = options.sources;
|
|
2012
|
+
if (options?.fromDate) body.from_date = options.fromDate;
|
|
2013
|
+
if (options?.toDate) body.to_date = options.toDate;
|
|
2014
|
+
return this.requestWithPayment("/v1/search", body);
|
|
2015
|
+
}
|
|
2016
|
+
async requestWithPayment(endpoint, body) {
|
|
2017
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2018
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2019
|
+
method: "POST",
|
|
2020
|
+
headers: { "Content-Type": "application/json" },
|
|
2021
|
+
body: JSON.stringify(body)
|
|
2022
|
+
});
|
|
2023
|
+
if (response.status === 402) {
|
|
2024
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2025
|
+
}
|
|
2026
|
+
if (!response.ok) {
|
|
2027
|
+
let errorBody;
|
|
2028
|
+
try {
|
|
2029
|
+
errorBody = await response.json();
|
|
2030
|
+
} catch {
|
|
2031
|
+
errorBody = { error: "Request failed" };
|
|
2032
|
+
}
|
|
2033
|
+
throw new APIError(
|
|
2034
|
+
`API error: ${response.status}`,
|
|
2035
|
+
response.status,
|
|
2036
|
+
sanitizeErrorResponse(errorBody)
|
|
2037
|
+
);
|
|
2038
|
+
}
|
|
2039
|
+
return response.json();
|
|
2040
|
+
}
|
|
2041
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2042
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2043
|
+
if (!paymentHeader) {
|
|
2044
|
+
try {
|
|
2045
|
+
const respBody = await response.json();
|
|
2046
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2047
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2048
|
+
}
|
|
2049
|
+
} catch {
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
if (!paymentHeader) {
|
|
2053
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2054
|
+
}
|
|
2055
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2056
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2057
|
+
const paymentPayload = await createPaymentPayload(
|
|
2058
|
+
this.privateKey,
|
|
2059
|
+
this.account.address,
|
|
2060
|
+
details.recipient,
|
|
2061
|
+
details.amount,
|
|
2062
|
+
details.network || "eip155:8453",
|
|
2063
|
+
{
|
|
2064
|
+
resourceUrl: details.resource?.url || url,
|
|
2065
|
+
resourceDescription: details.resource?.description || "BlockRun Search",
|
|
2066
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2067
|
+
extra: details.extra
|
|
2068
|
+
}
|
|
2069
|
+
);
|
|
2070
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2071
|
+
method: "POST",
|
|
2072
|
+
headers: {
|
|
2073
|
+
"Content-Type": "application/json",
|
|
2074
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2075
|
+
},
|
|
2076
|
+
body: JSON.stringify(body)
|
|
2077
|
+
});
|
|
2078
|
+
if (retry.status === 402) {
|
|
2079
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2080
|
+
}
|
|
2081
|
+
if (!retry.ok) {
|
|
2082
|
+
let errorBody;
|
|
2083
|
+
try {
|
|
2084
|
+
errorBody = await retry.json();
|
|
2085
|
+
} catch {
|
|
2086
|
+
errorBody = { error: "Request failed" };
|
|
2087
|
+
}
|
|
2088
|
+
throw new APIError(
|
|
2089
|
+
`API error after payment: ${retry.status}`,
|
|
2090
|
+
retry.status,
|
|
2091
|
+
sanitizeErrorResponse(errorBody)
|
|
2092
|
+
);
|
|
2093
|
+
}
|
|
2094
|
+
return retry.json();
|
|
2095
|
+
}
|
|
2096
|
+
async fetchWithTimeout(url, init) {
|
|
2097
|
+
const controller = new AbortController();
|
|
2098
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2099
|
+
try {
|
|
2100
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2101
|
+
} finally {
|
|
2102
|
+
clearTimeout(timeoutId);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
getWalletAddress() {
|
|
2106
|
+
return this.account.address;
|
|
2107
|
+
}
|
|
2108
|
+
};
|
|
2109
|
+
|
|
2110
|
+
// src/x-client.ts
|
|
2111
|
+
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
2112
|
+
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2113
|
+
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2114
|
+
var XClient = class {
|
|
2115
|
+
account;
|
|
2116
|
+
privateKey;
|
|
2117
|
+
apiUrl;
|
|
2118
|
+
timeout;
|
|
2119
|
+
constructor(options = {}) {
|
|
2120
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2121
|
+
const privateKey = options.privateKey || envKey;
|
|
2122
|
+
if (!privateKey) {
|
|
2123
|
+
throw new Error(
|
|
2124
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
validatePrivateKey(privateKey);
|
|
2128
|
+
this.privateKey = privateKey;
|
|
2129
|
+
this.account = privateKeyToAccount5(privateKey);
|
|
2130
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL5;
|
|
2131
|
+
validateApiUrl(apiUrl);
|
|
2132
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2133
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2134
|
+
}
|
|
2135
|
+
// ───────── User endpoints ─────────
|
|
2136
|
+
userLookup(usernames) {
|
|
2137
|
+
return this.post("/v1/x/users/lookup", { usernames });
|
|
2138
|
+
}
|
|
2139
|
+
userInfo(username) {
|
|
2140
|
+
return this.post("/v1/x/users/info", { username });
|
|
2141
|
+
}
|
|
2142
|
+
followers(username, cursor) {
|
|
2143
|
+
const body = { username };
|
|
2144
|
+
if (cursor) body.cursor = cursor;
|
|
2145
|
+
return this.post("/v1/x/users/followers", body);
|
|
2146
|
+
}
|
|
2147
|
+
/** `/v1/x/users/following` (singular path variant). */
|
|
2148
|
+
following(username, cursor) {
|
|
2149
|
+
const body = { username };
|
|
2150
|
+
if (cursor) body.cursor = cursor;
|
|
2151
|
+
return this.post("/v1/x/users/following", body);
|
|
2152
|
+
}
|
|
2153
|
+
/** `/v1/x/users/followings` (plural path variant). */
|
|
2154
|
+
followings(username, cursor) {
|
|
2155
|
+
const body = { username };
|
|
2156
|
+
if (cursor) body.cursor = cursor;
|
|
2157
|
+
return this.post("/v1/x/users/followings", body);
|
|
2158
|
+
}
|
|
2159
|
+
verifiedFollowers(userId, cursor) {
|
|
2160
|
+
const body = { userId };
|
|
2161
|
+
if (cursor) body.cursor = cursor;
|
|
2162
|
+
return this.post(
|
|
2163
|
+
"/v1/x/users/verified-followers",
|
|
2164
|
+
body
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
userTweets(options) {
|
|
2168
|
+
if (!options.username && !options.userId) {
|
|
2169
|
+
throw new Error("Either username or userId is required");
|
|
2170
|
+
}
|
|
2171
|
+
const body = {};
|
|
2172
|
+
if (options.username) body.username = options.username;
|
|
2173
|
+
if (options.userId) body.userId = options.userId;
|
|
2174
|
+
if (options.cursor) body.cursor = options.cursor;
|
|
2175
|
+
if (options.includeReplies !== void 0) body.includeReplies = options.includeReplies;
|
|
2176
|
+
return this.post("/v1/x/users/tweets", body);
|
|
2177
|
+
}
|
|
2178
|
+
mentions(username, options) {
|
|
2179
|
+
const body = { username };
|
|
2180
|
+
if (options?.sinceTime) body.sinceTime = options.sinceTime;
|
|
2181
|
+
if (options?.untilTime) body.untilTime = options.untilTime;
|
|
2182
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2183
|
+
return this.post("/v1/x/users/mentions", body);
|
|
2184
|
+
}
|
|
2185
|
+
// ───────── Tweet endpoints ─────────
|
|
2186
|
+
tweetLookup(tweetIds) {
|
|
2187
|
+
return this.post("/v1/x/tweets/lookup", {
|
|
2188
|
+
tweet_ids: tweetIds
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
tweetReplies(tweetId, options) {
|
|
2192
|
+
const body = { tweetId };
|
|
2193
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2194
|
+
if (options?.queryType) body.queryType = options.queryType;
|
|
2195
|
+
return this.post("/v1/x/tweets/replies", body);
|
|
2196
|
+
}
|
|
2197
|
+
tweetThread(tweetId, cursor) {
|
|
2198
|
+
const body = { tweetId };
|
|
2199
|
+
if (cursor) body.cursor = cursor;
|
|
2200
|
+
return this.post("/v1/x/tweets/thread", body);
|
|
2201
|
+
}
|
|
2202
|
+
// ───────── Search & discovery ─────────
|
|
2203
|
+
search(query, options) {
|
|
2204
|
+
const body = { query };
|
|
2205
|
+
if (options?.queryType) body.queryType = options.queryType;
|
|
2206
|
+
if (options?.cursor) body.cursor = options.cursor;
|
|
2207
|
+
return this.post("/v1/x/search", body);
|
|
2208
|
+
}
|
|
2209
|
+
trending() {
|
|
2210
|
+
return this.post("/v1/x/trending", {});
|
|
2211
|
+
}
|
|
2212
|
+
articlesRising() {
|
|
2213
|
+
return this.post("/v1/x/articles/rising", {});
|
|
2214
|
+
}
|
|
2215
|
+
// ───────── Internals ─────────
|
|
2216
|
+
async post(endpoint, body) {
|
|
2217
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2218
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2219
|
+
method: "POST",
|
|
2220
|
+
headers: { "Content-Type": "application/json" },
|
|
2221
|
+
body: JSON.stringify(body)
|
|
2222
|
+
});
|
|
2223
|
+
if (response.status === 402) {
|
|
2224
|
+
return this.payAndRetry(url, body, response);
|
|
2225
|
+
}
|
|
2226
|
+
if (!response.ok) {
|
|
2227
|
+
let errorBody;
|
|
2228
|
+
try {
|
|
2229
|
+
errorBody = await response.json();
|
|
2230
|
+
} catch {
|
|
2231
|
+
errorBody = { error: "Request failed" };
|
|
2232
|
+
}
|
|
2233
|
+
throw new APIError(
|
|
2234
|
+
`API error: ${response.status}`,
|
|
2235
|
+
response.status,
|
|
2236
|
+
sanitizeErrorResponse(errorBody)
|
|
2237
|
+
);
|
|
2238
|
+
}
|
|
2239
|
+
return response.json();
|
|
2240
|
+
}
|
|
2241
|
+
async payAndRetry(url, body, response) {
|
|
2242
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2243
|
+
if (!paymentHeader) {
|
|
2244
|
+
try {
|
|
2245
|
+
const respBody = await response.json();
|
|
2246
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2247
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2248
|
+
}
|
|
2249
|
+
} catch {
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
if (!paymentHeader) {
|
|
2253
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2254
|
+
}
|
|
2255
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2256
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2257
|
+
const paymentPayload = await createPaymentPayload(
|
|
2258
|
+
this.privateKey,
|
|
2259
|
+
this.account.address,
|
|
2260
|
+
details.recipient,
|
|
2261
|
+
details.amount,
|
|
2262
|
+
details.network || "eip155:8453",
|
|
2263
|
+
{
|
|
2264
|
+
resourceUrl: details.resource?.url || url,
|
|
2265
|
+
resourceDescription: details.resource?.description || "BlockRun X API",
|
|
2266
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2267
|
+
extra: details.extra
|
|
2268
|
+
}
|
|
2269
|
+
);
|
|
2270
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2271
|
+
method: "POST",
|
|
2272
|
+
headers: {
|
|
2273
|
+
"Content-Type": "application/json",
|
|
2274
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2275
|
+
},
|
|
2276
|
+
body: JSON.stringify(body)
|
|
2277
|
+
});
|
|
2278
|
+
if (retry.status === 402) {
|
|
2279
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2280
|
+
}
|
|
2281
|
+
if (!retry.ok) {
|
|
2282
|
+
let errorBody;
|
|
2283
|
+
try {
|
|
2284
|
+
errorBody = await retry.json();
|
|
2285
|
+
} catch {
|
|
2286
|
+
errorBody = { error: "Request failed" };
|
|
2287
|
+
}
|
|
2288
|
+
throw new APIError(
|
|
2289
|
+
`API error after payment: ${retry.status}`,
|
|
2290
|
+
retry.status,
|
|
2291
|
+
sanitizeErrorResponse(errorBody)
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
return retry.json();
|
|
2295
|
+
}
|
|
2296
|
+
async fetchWithTimeout(url, init) {
|
|
2297
|
+
const controller = new AbortController();
|
|
2298
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2299
|
+
try {
|
|
2300
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2301
|
+
} finally {
|
|
2302
|
+
clearTimeout(timeoutId);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
getWalletAddress() {
|
|
2306
|
+
return this.account.address;
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2310
|
+
// src/price.ts
|
|
2311
|
+
import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
|
|
2312
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2313
|
+
var DEFAULT_TIMEOUT6 = 3e4;
|
|
2314
|
+
var PriceClient = class {
|
|
2315
|
+
account = null;
|
|
2316
|
+
privateKey = null;
|
|
2317
|
+
apiUrl;
|
|
2318
|
+
timeout;
|
|
2319
|
+
constructor(options = {}) {
|
|
2320
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2321
|
+
const privateKey = options.privateKey || envKey;
|
|
2322
|
+
const requireWallet = options.requireWallet ?? true;
|
|
2323
|
+
if (!privateKey && requireWallet) {
|
|
2324
|
+
throw new Error(
|
|
2325
|
+
"Private key required for paid endpoints. Pass privateKey in options, set BLOCKRUN_WALLET_KEY, or pass requireWallet: false for free-only usage."
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
if (privateKey) {
|
|
2329
|
+
validatePrivateKey(privateKey);
|
|
2330
|
+
this.privateKey = privateKey;
|
|
2331
|
+
this.account = privateKeyToAccount6(privateKey);
|
|
2332
|
+
}
|
|
2333
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2334
|
+
validateApiUrl(apiUrl);
|
|
2335
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2336
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2337
|
+
}
|
|
2338
|
+
async price(category, symbol, options) {
|
|
2339
|
+
if (!symbol) throw new Error("symbol is required");
|
|
2340
|
+
const path5 = categoryPath(category, options?.market, "price", symbol);
|
|
2341
|
+
const query = {};
|
|
2342
|
+
if (options?.session) query.session = options.session;
|
|
2343
|
+
const data = await this.getWithPayment(path5, query);
|
|
2344
|
+
return {
|
|
2345
|
+
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
2346
|
+
price: data.price,
|
|
2347
|
+
publishTime: data.publishTime,
|
|
2348
|
+
confidence: data.confidence,
|
|
2349
|
+
feedId: data.feedId,
|
|
2350
|
+
timestamp: data.timestamp,
|
|
2351
|
+
assetType: data.assetType,
|
|
2352
|
+
category: data.category,
|
|
2353
|
+
source: data.source,
|
|
2354
|
+
free: data.free
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
async history(category, symbol, options) {
|
|
2358
|
+
if (!symbol) throw new Error("symbol is required");
|
|
2359
|
+
if (!options.from || options.from <= 0) {
|
|
2360
|
+
throw new Error("history requires options.from (unix seconds)");
|
|
2361
|
+
}
|
|
2362
|
+
const path5 = categoryPath(category, options.market, "history", symbol);
|
|
2363
|
+
const query = {
|
|
2364
|
+
resolution: options.resolution ?? "D",
|
|
2365
|
+
from: String(options.from)
|
|
2366
|
+
};
|
|
2367
|
+
if (options.to) query.to = String(options.to);
|
|
2368
|
+
if (options.session) query.session = options.session;
|
|
2369
|
+
const data = await this.getWithPayment(path5, query);
|
|
2370
|
+
return {
|
|
2371
|
+
symbol: data.symbol ?? symbol.toUpperCase(),
|
|
2372
|
+
resolution: data.resolution ?? (options.resolution ?? "D"),
|
|
2373
|
+
from: data.from,
|
|
2374
|
+
to: data.to,
|
|
2375
|
+
bars: data.bars ?? [],
|
|
2376
|
+
source: data.source,
|
|
2377
|
+
category: data.category
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
async listSymbols(category, options) {
|
|
2381
|
+
const path5 = categoryPath(category, options?.market, "list");
|
|
2382
|
+
const query = {
|
|
2383
|
+
limit: String(options?.limit && options.limit > 0 ? options.limit : 100)
|
|
2384
|
+
};
|
|
2385
|
+
if (options?.query) query.q = options.query;
|
|
2386
|
+
const data = await this.getWithPayment(path5, query);
|
|
2387
|
+
if (Array.isArray(data)) {
|
|
2388
|
+
return { symbols: data, count: data.length };
|
|
2389
|
+
}
|
|
2390
|
+
const obj = data;
|
|
2391
|
+
const symbols = obj.symbols ?? obj.feeds ?? [];
|
|
2392
|
+
return {
|
|
2393
|
+
symbols,
|
|
2394
|
+
count: obj.count ?? symbols.length
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
getWalletAddress() {
|
|
2398
|
+
return this.account?.address ?? null;
|
|
2399
|
+
}
|
|
2400
|
+
async getWithPayment(endpoint, query) {
|
|
2401
|
+
const url = buildUrl(`${this.apiUrl}${endpoint}`, query);
|
|
2402
|
+
const response = await this.fetchWithTimeout(url, { method: "GET" });
|
|
2403
|
+
if (response.status === 402) {
|
|
2404
|
+
if (!this.privateKey || !this.account) {
|
|
2405
|
+
throw new PaymentError(
|
|
2406
|
+
`${endpoint} returned 402 Payment Required but no wallet is configured.`
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
return this.payAndRetry(url, response);
|
|
2410
|
+
}
|
|
2411
|
+
if (!response.ok) {
|
|
2412
|
+
let errorBody;
|
|
2413
|
+
try {
|
|
2414
|
+
errorBody = await response.json();
|
|
2415
|
+
} catch {
|
|
2416
|
+
errorBody = { error: "Request failed" };
|
|
2417
|
+
}
|
|
2418
|
+
throw new APIError(
|
|
2419
|
+
`API error: ${response.status}`,
|
|
2420
|
+
response.status,
|
|
2421
|
+
sanitizeErrorResponse(errorBody)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
return response.json();
|
|
2425
|
+
}
|
|
2426
|
+
async payAndRetry(url, response) {
|
|
2427
|
+
if (!this.privateKey || !this.account) {
|
|
2428
|
+
throw new PaymentError("Wallet required to sign payment.");
|
|
2429
|
+
}
|
|
2430
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2431
|
+
if (!paymentHeader) {
|
|
2432
|
+
try {
|
|
2433
|
+
const respBody = await response.json();
|
|
2434
|
+
if (respBody.x402) {
|
|
2435
|
+
paymentHeader = btoa(JSON.stringify(respBody.x402));
|
|
2436
|
+
} else if (respBody.accepts) {
|
|
2437
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2438
|
+
}
|
|
2439
|
+
} catch {
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
if (!paymentHeader) {
|
|
2443
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2444
|
+
}
|
|
2445
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2446
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2447
|
+
const paymentPayload = await createPaymentPayload(
|
|
2448
|
+
this.privateKey,
|
|
2449
|
+
this.account.address,
|
|
2450
|
+
details.recipient,
|
|
2451
|
+
details.amount,
|
|
2452
|
+
details.network || "eip155:8453",
|
|
2453
|
+
{
|
|
2454
|
+
resourceUrl: details.resource?.url || url,
|
|
2455
|
+
resourceDescription: details.resource?.description || "BlockRun Price Data",
|
|
2456
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2457
|
+
extra: details.extra
|
|
2458
|
+
}
|
|
2459
|
+
);
|
|
2460
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2461
|
+
method: "GET",
|
|
2462
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
2463
|
+
});
|
|
2464
|
+
if (retry.status === 402) {
|
|
2465
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2466
|
+
}
|
|
2467
|
+
if (!retry.ok) {
|
|
2468
|
+
let errorBody;
|
|
2469
|
+
try {
|
|
2470
|
+
errorBody = await retry.json();
|
|
2471
|
+
} catch {
|
|
2472
|
+
errorBody = { error: "Request failed" };
|
|
2473
|
+
}
|
|
2474
|
+
throw new APIError(
|
|
2475
|
+
`API error after payment: ${retry.status}`,
|
|
2476
|
+
retry.status,
|
|
2477
|
+
sanitizeErrorResponse(errorBody)
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
return retry.json();
|
|
2481
|
+
}
|
|
2482
|
+
async fetchWithTimeout(url, init) {
|
|
2483
|
+
const controller = new AbortController();
|
|
2484
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2485
|
+
try {
|
|
2486
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2487
|
+
} finally {
|
|
2488
|
+
clearTimeout(timeoutId);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
};
|
|
2492
|
+
function categoryPath(category, market, kind, symbol) {
|
|
2493
|
+
let base;
|
|
2494
|
+
if (category === "stocks") {
|
|
2495
|
+
if (!market) {
|
|
2496
|
+
throw new Error("market is required when category === 'stocks'");
|
|
2497
|
+
}
|
|
2498
|
+
base = `/v1/stocks/${market}`;
|
|
2499
|
+
} else if (["crypto", "fx", "commodity", "usstock"].includes(category)) {
|
|
2500
|
+
base = `/v1/${category}`;
|
|
2501
|
+
} else {
|
|
2502
|
+
throw new Error(`unknown category: ${category}`);
|
|
2503
|
+
}
|
|
2504
|
+
if (!symbol) return `${base}/${kind}`;
|
|
2505
|
+
return `${base}/${kind}/${encodeURIComponent(symbol.toUpperCase())}`;
|
|
2506
|
+
}
|
|
2507
|
+
function buildUrl(base, query) {
|
|
2508
|
+
const params = Object.entries(query);
|
|
2509
|
+
if (params.length === 0) return base;
|
|
2510
|
+
const qs = params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
2511
|
+
return `${base}?${qs}`;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
1816
2514
|
// src/wallet.ts
|
|
1817
|
-
import { privateKeyToAccount as
|
|
2515
|
+
import { privateKeyToAccount as privateKeyToAccount7, generatePrivateKey } from "viem/accounts";
|
|
1818
2516
|
import * as fs from "fs";
|
|
1819
2517
|
import * as path from "path";
|
|
1820
2518
|
import * as os from "os";
|
|
@@ -1824,7 +2522,7 @@ var WALLET_DIR = path.join(os.homedir(), ".blockrun");
|
|
|
1824
2522
|
var WALLET_FILE = path.join(WALLET_DIR, ".session");
|
|
1825
2523
|
function createWallet() {
|
|
1826
2524
|
const privateKey = generatePrivateKey();
|
|
1827
|
-
const account =
|
|
2525
|
+
const account = privateKeyToAccount7(privateKey);
|
|
1828
2526
|
return {
|
|
1829
2527
|
address: account.address,
|
|
1830
2528
|
privateKey
|
|
@@ -1880,12 +2578,12 @@ function loadWallet() {
|
|
|
1880
2578
|
function getOrCreateWallet() {
|
|
1881
2579
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1882
2580
|
if (envKey) {
|
|
1883
|
-
const account =
|
|
2581
|
+
const account = privateKeyToAccount7(envKey);
|
|
1884
2582
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
1885
2583
|
}
|
|
1886
2584
|
const fileKey = loadWallet();
|
|
1887
2585
|
if (fileKey) {
|
|
1888
|
-
const account =
|
|
2586
|
+
const account = privateKeyToAccount7(fileKey);
|
|
1889
2587
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
1890
2588
|
}
|
|
1891
2589
|
const { address, privateKey } = createWallet();
|
|
@@ -1895,11 +2593,11 @@ function getOrCreateWallet() {
|
|
|
1895
2593
|
function getWalletAddress() {
|
|
1896
2594
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
1897
2595
|
if (envKey) {
|
|
1898
|
-
return
|
|
2596
|
+
return privateKeyToAccount7(envKey).address;
|
|
1899
2597
|
}
|
|
1900
2598
|
const fileKey = loadWallet();
|
|
1901
2599
|
if (fileKey) {
|
|
1902
|
-
return
|
|
2600
|
+
return privateKeyToAccount7(fileKey).address;
|
|
1903
2601
|
}
|
|
1904
2602
|
return null;
|
|
1905
2603
|
}
|
|
@@ -2079,7 +2777,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
2079
2777
|
// src/solana-client.ts
|
|
2080
2778
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
2081
2779
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
2082
|
-
var
|
|
2780
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2083
2781
|
var SDK_VERSION2 = "0.3.0";
|
|
2084
2782
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
2085
2783
|
var SolanaLLMClient = class {
|
|
@@ -2104,7 +2802,7 @@ var SolanaLLMClient = class {
|
|
|
2104
2802
|
validateApiUrl(apiUrl);
|
|
2105
2803
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2106
2804
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
2107
|
-
this.timeout = options.timeout ||
|
|
2805
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2108
2806
|
}
|
|
2109
2807
|
/** Get Solana wallet address (public key in base58). */
|
|
2110
2808
|
async getWalletAddress() {
|
|
@@ -3049,7 +3747,7 @@ var OpenAI = class {
|
|
|
3049
3747
|
};
|
|
3050
3748
|
|
|
3051
3749
|
// src/anthropic-compat.ts
|
|
3052
|
-
import { privateKeyToAccount as
|
|
3750
|
+
import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
|
|
3053
3751
|
var AnthropicClient = class {
|
|
3054
3752
|
_client = null;
|
|
3055
3753
|
_clientPromise = null;
|
|
@@ -3062,7 +3760,7 @@ var AnthropicClient = class {
|
|
|
3062
3760
|
const key = options.privateKey ?? wallet.privateKey;
|
|
3063
3761
|
validatePrivateKey(key);
|
|
3064
3762
|
this._privateKey = key;
|
|
3065
|
-
this._account =
|
|
3763
|
+
this._account = privateKeyToAccount8(this._privateKey);
|
|
3066
3764
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
3067
3765
|
validateApiUrl(apiUrl);
|
|
3068
3766
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -3164,16 +3862,20 @@ export {
|
|
|
3164
3862
|
ImageClient,
|
|
3165
3863
|
KNOWN_PROVIDERS,
|
|
3166
3864
|
LLMClient,
|
|
3865
|
+
MusicClient,
|
|
3167
3866
|
OpenAI,
|
|
3168
3867
|
PaymentError,
|
|
3868
|
+
PriceClient,
|
|
3169
3869
|
SOLANA_NETWORK,
|
|
3170
3870
|
SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH,
|
|
3871
|
+
SearchClient,
|
|
3171
3872
|
SolanaLLMClient,
|
|
3172
3873
|
USDC_BASE,
|
|
3173
3874
|
USDC_BASE_CONTRACT,
|
|
3174
3875
|
USDC_SOLANA,
|
|
3175
3876
|
WALLET_DIR_PATH,
|
|
3176
3877
|
WALLET_FILE_PATH,
|
|
3878
|
+
XClient,
|
|
3177
3879
|
clearCache,
|
|
3178
3880
|
createPaymentPayload,
|
|
3179
3881
|
createSolanaPaymentPayload,
|