@blockrun/llm 0.3.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -24
- package/dist/{chunk-S7BEMV6T.js → chunk-KRDGCX7W.js} +25914 -25839
- package/dist/{esm-EBZAIN5N.js → esm-PTFDM6PE.js} +159 -6
- package/dist/index.cjs +36985 -37
- package/dist/index.d.cts +179 -1
- package/dist/index.d.ts +179 -1
- package/dist/{index.esm-WP2DIBSK.js → index.esm-SXKIFLA7.js} +3 -2
- package/dist/index.js +421 -1
- package/package.json +71 -70
- package/dist/chunk-2ESYSVXG.js +0 -48
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
__require,
|
|
3
|
+
__toCommonJS,
|
|
4
|
+
index_esm_exports,
|
|
5
|
+
init_index_esm
|
|
6
|
+
} from "./chunk-KRDGCX7W.js";
|
|
2
7
|
|
|
3
8
|
// src/client.ts
|
|
4
9
|
import { privateKeyToAccount } from "viem/accounts";
|
|
@@ -27,10 +32,17 @@ var APIError = class extends BlockrunError {
|
|
|
27
32
|
}
|
|
28
33
|
};
|
|
29
34
|
|
|
35
|
+
// src/client.ts
|
|
36
|
+
import { route, DEFAULT_ROUTING_CONFIG } from "@blockrun/clawrouter";
|
|
37
|
+
|
|
30
38
|
// src/x402.ts
|
|
31
39
|
import { signTypedData } from "viem/accounts";
|
|
32
40
|
var BASE_CHAIN_ID = 8453;
|
|
33
41
|
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
42
|
+
var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
43
|
+
var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
44
|
+
var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
45
|
+
var DEFAULT_COMPUTE_UNIT_LIMIT = 8e3;
|
|
34
46
|
var USDC_DOMAIN = {
|
|
35
47
|
name: "USD Coin",
|
|
36
48
|
version: "2",
|
|
@@ -103,6 +115,66 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
103
115
|
};
|
|
104
116
|
return btoa(JSON.stringify(paymentData));
|
|
105
117
|
}
|
|
118
|
+
async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amount, feePayer, options = {}) {
|
|
119
|
+
const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("./index.esm-SXKIFLA7.js");
|
|
120
|
+
const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("./esm-PTFDM6PE.js");
|
|
121
|
+
const { Keypair } = await import("./index.esm-SXKIFLA7.js");
|
|
122
|
+
const rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
123
|
+
const connection = new Connection(rpcUrl);
|
|
124
|
+
const keypair = Keypair.fromSecretKey(secretKey);
|
|
125
|
+
const feePayerPubkey = new PublicKey(feePayer);
|
|
126
|
+
const ownerPubkey = keypair.publicKey;
|
|
127
|
+
const tokenMint = new PublicKey(USDC_SOLANA);
|
|
128
|
+
const payToPubkey = new PublicKey(recipient);
|
|
129
|
+
const mintInfo = await getMint(connection, tokenMint);
|
|
130
|
+
const sourceATA = await getAssociatedTokenAddress(tokenMint, ownerPubkey, false);
|
|
131
|
+
const destinationATA = await getAssociatedTokenAddress(tokenMint, payToPubkey, false);
|
|
132
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
133
|
+
const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
|
|
134
|
+
microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
|
|
135
|
+
});
|
|
136
|
+
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
|
|
137
|
+
units: DEFAULT_COMPUTE_UNIT_LIMIT
|
|
138
|
+
});
|
|
139
|
+
const transferIx = createTransferCheckedInstruction(
|
|
140
|
+
sourceATA,
|
|
141
|
+
tokenMint,
|
|
142
|
+
destinationATA,
|
|
143
|
+
ownerPubkey,
|
|
144
|
+
BigInt(amount),
|
|
145
|
+
mintInfo.decimals
|
|
146
|
+
);
|
|
147
|
+
const messageV0 = new TransactionMessage({
|
|
148
|
+
payerKey: feePayerPubkey,
|
|
149
|
+
recentBlockhash: blockhash,
|
|
150
|
+
instructions: [setComputeUnitLimitIx, setComputeUnitPriceIx, transferIx]
|
|
151
|
+
}).compileToV0Message();
|
|
152
|
+
const transaction = new VersionedTransaction(messageV0);
|
|
153
|
+
transaction.sign([keypair]);
|
|
154
|
+
const serializedTx = Buffer.from(transaction.serialize()).toString("base64");
|
|
155
|
+
const paymentData = {
|
|
156
|
+
x402Version: 2,
|
|
157
|
+
resource: {
|
|
158
|
+
url: options.resourceUrl || "https://blockrun.ai/api/v1/chat/completions",
|
|
159
|
+
description: options.resourceDescription || "BlockRun AI API call",
|
|
160
|
+
mimeType: "application/json"
|
|
161
|
+
},
|
|
162
|
+
accepted: {
|
|
163
|
+
scheme: "exact",
|
|
164
|
+
network: SOLANA_NETWORK,
|
|
165
|
+
amount,
|
|
166
|
+
asset: USDC_SOLANA,
|
|
167
|
+
payTo: recipient,
|
|
168
|
+
maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
|
|
169
|
+
extra: options.extra || { feePayer }
|
|
170
|
+
},
|
|
171
|
+
payload: {
|
|
172
|
+
transaction: serializedTx
|
|
173
|
+
},
|
|
174
|
+
extensions: options.extensions || {}
|
|
175
|
+
};
|
|
176
|
+
return btoa(JSON.stringify(paymentData));
|
|
177
|
+
}
|
|
106
178
|
function parsePaymentRequired(headerValue) {
|
|
107
179
|
try {
|
|
108
180
|
const decoded = atob(headerValue);
|
|
@@ -237,6 +309,8 @@ var LLMClient = class {
|
|
|
237
309
|
timeout;
|
|
238
310
|
sessionTotalUsd = 0;
|
|
239
311
|
sessionCalls = 0;
|
|
312
|
+
modelPricingCache = null;
|
|
313
|
+
modelPricingPromise = null;
|
|
240
314
|
/**
|
|
241
315
|
* Initialize the BlockRun LLM client.
|
|
242
316
|
*
|
|
@@ -285,6 +359,91 @@ var LLMClient = class {
|
|
|
285
359
|
});
|
|
286
360
|
return result.choices[0].message.content || "";
|
|
287
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Smart chat with automatic model routing.
|
|
364
|
+
*
|
|
365
|
+
* Uses ClawRouter's 14-dimension rule-based scoring algorithm (<1ms, 100% local)
|
|
366
|
+
* to select the cheapest model that can handle your request.
|
|
367
|
+
*
|
|
368
|
+
* @param prompt - User message
|
|
369
|
+
* @param options - Optional chat and routing parameters
|
|
370
|
+
* @returns SmartChatResponse with response text, selected model, and routing metadata
|
|
371
|
+
*
|
|
372
|
+
* @example Simple usage (auto profile)
|
|
373
|
+
* ```ts
|
|
374
|
+
* const result = await client.smartChat('What is 2+2?');
|
|
375
|
+
* console.log(result.response); // '4'
|
|
376
|
+
* console.log(result.model); // 'google/gemini-2.5-flash-lite'
|
|
377
|
+
* console.log(result.routing.savings); // 0.78 (78% savings)
|
|
378
|
+
* ```
|
|
379
|
+
*
|
|
380
|
+
* @example With routing profile
|
|
381
|
+
* ```ts
|
|
382
|
+
* // Free tier only (zero cost)
|
|
383
|
+
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
384
|
+
*
|
|
385
|
+
* // Eco mode (budget optimized)
|
|
386
|
+
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
387
|
+
*
|
|
388
|
+
* // Premium mode (best quality)
|
|
389
|
+
* const result = await client.smartChat('Write a business plan', { routingProfile: 'premium' });
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
async smartChat(prompt, options) {
|
|
393
|
+
const modelPricing = await this.getModelPricing();
|
|
394
|
+
const maxOutputTokens = options?.maxOutputTokens || options?.maxTokens || 1024;
|
|
395
|
+
const decision = route(prompt, options?.system, maxOutputTokens, {
|
|
396
|
+
config: DEFAULT_ROUTING_CONFIG,
|
|
397
|
+
modelPricing,
|
|
398
|
+
routingProfile: options?.routingProfile
|
|
399
|
+
});
|
|
400
|
+
const response = await this.chat(decision.model, prompt, {
|
|
401
|
+
system: options?.system,
|
|
402
|
+
maxTokens: options?.maxTokens,
|
|
403
|
+
temperature: options?.temperature,
|
|
404
|
+
topP: options?.topP,
|
|
405
|
+
search: options?.search,
|
|
406
|
+
searchParameters: options?.searchParameters
|
|
407
|
+
});
|
|
408
|
+
return {
|
|
409
|
+
response,
|
|
410
|
+
model: decision.model,
|
|
411
|
+
routing: decision
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get model pricing map (cached).
|
|
416
|
+
* Fetches from API on first call, then returns cached result.
|
|
417
|
+
*/
|
|
418
|
+
async getModelPricing() {
|
|
419
|
+
if (this.modelPricingCache) {
|
|
420
|
+
return this.modelPricingCache;
|
|
421
|
+
}
|
|
422
|
+
if (this.modelPricingPromise) {
|
|
423
|
+
return this.modelPricingPromise;
|
|
424
|
+
}
|
|
425
|
+
this.modelPricingPromise = this.fetchModelPricing();
|
|
426
|
+
try {
|
|
427
|
+
this.modelPricingCache = await this.modelPricingPromise;
|
|
428
|
+
return this.modelPricingCache;
|
|
429
|
+
} finally {
|
|
430
|
+
this.modelPricingPromise = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Fetch model pricing from API.
|
|
435
|
+
*/
|
|
436
|
+
async fetchModelPricing() {
|
|
437
|
+
const models = await this.listModels();
|
|
438
|
+
const pricing = /* @__PURE__ */ new Map();
|
|
439
|
+
for (const model of models) {
|
|
440
|
+
pricing.set(model.id, {
|
|
441
|
+
inputPrice: model.inputPrice,
|
|
442
|
+
outputPrice: model.outputPrice
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return pricing;
|
|
446
|
+
}
|
|
288
447
|
/**
|
|
289
448
|
* Full chat completion interface (OpenAI-compatible).
|
|
290
449
|
*
|
|
@@ -857,6 +1016,255 @@ Check my balance: ${links.basescan}`;
|
|
|
857
1016
|
var WALLET_FILE_PATH = WALLET_FILE;
|
|
858
1017
|
var WALLET_DIR_PATH = WALLET_DIR;
|
|
859
1018
|
|
|
1019
|
+
// src/solana-wallet.ts
|
|
1020
|
+
import * as fs2 from "fs";
|
|
1021
|
+
import * as path2 from "path";
|
|
1022
|
+
import * as os2 from "os";
|
|
1023
|
+
var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
|
|
1024
|
+
var SOLANA_WALLET_FILE = path2.join(WALLET_DIR2, ".solana-session");
|
|
1025
|
+
function createSolanaWallet() {
|
|
1026
|
+
const { Keypair } = (init_index_esm(), __toCommonJS(index_esm_exports));
|
|
1027
|
+
const bs58 = __require("bs58");
|
|
1028
|
+
const keypair = Keypair.generate();
|
|
1029
|
+
return {
|
|
1030
|
+
address: keypair.publicKey.toBase58(),
|
|
1031
|
+
privateKey: bs58.default?.encode(keypair.secretKey) ?? bs58.encode(keypair.secretKey)
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
async function solanaKeyToBytes(privateKey) {
|
|
1035
|
+
try {
|
|
1036
|
+
const bs58 = await import("bs58");
|
|
1037
|
+
const bytes = (bs58.default ?? bs58).decode(privateKey);
|
|
1038
|
+
if (bytes.length !== 64) {
|
|
1039
|
+
throw new Error(`Invalid Solana key length: expected 64 bytes, got ${bytes.length}`);
|
|
1040
|
+
}
|
|
1041
|
+
return bytes;
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1044
|
+
throw new Error(`Invalid Solana private key: ${msg}`);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
async function solanaPublicKey(privateKey) {
|
|
1048
|
+
const { Keypair } = await import("./index.esm-SXKIFLA7.js");
|
|
1049
|
+
const bytes = await solanaKeyToBytes(privateKey);
|
|
1050
|
+
return Keypair.fromSecretKey(bytes).publicKey.toBase58();
|
|
1051
|
+
}
|
|
1052
|
+
function saveSolanaWallet(privateKey) {
|
|
1053
|
+
if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
|
|
1054
|
+
fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
|
|
1055
|
+
return SOLANA_WALLET_FILE;
|
|
1056
|
+
}
|
|
1057
|
+
function loadSolanaWallet() {
|
|
1058
|
+
if (fs2.existsSync(SOLANA_WALLET_FILE)) {
|
|
1059
|
+
const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
1060
|
+
if (key) return key;
|
|
1061
|
+
}
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
async function getOrCreateSolanaWallet() {
|
|
1065
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
|
|
1066
|
+
if (envKey) {
|
|
1067
|
+
const address2 = await solanaPublicKey(envKey);
|
|
1068
|
+
return { privateKey: envKey, address: address2, isNew: false };
|
|
1069
|
+
}
|
|
1070
|
+
const fileKey = loadSolanaWallet();
|
|
1071
|
+
if (fileKey) {
|
|
1072
|
+
const address2 = await solanaPublicKey(fileKey);
|
|
1073
|
+
return { privateKey: fileKey, address: address2, isNew: false };
|
|
1074
|
+
}
|
|
1075
|
+
const { address, privateKey } = createSolanaWallet();
|
|
1076
|
+
saveSolanaWallet(privateKey);
|
|
1077
|
+
return { address, privateKey, isNew: true };
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/solana-client.ts
|
|
1081
|
+
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
1082
|
+
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
1083
|
+
var DEFAULT_TIMEOUT3 = 6e4;
|
|
1084
|
+
var SDK_VERSION2 = "0.3.0";
|
|
1085
|
+
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
1086
|
+
var SolanaLLMClient = class {
|
|
1087
|
+
static SOLANA_API_URL = SOLANA_API_URL;
|
|
1088
|
+
privateKey;
|
|
1089
|
+
apiUrl;
|
|
1090
|
+
rpcUrl;
|
|
1091
|
+
timeout;
|
|
1092
|
+
sessionTotalUsd = 0;
|
|
1093
|
+
sessionCalls = 0;
|
|
1094
|
+
addressCache = null;
|
|
1095
|
+
constructor(options = {}) {
|
|
1096
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
|
|
1097
|
+
const privateKey = options.privateKey || envKey;
|
|
1098
|
+
if (!privateKey) {
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
"Private key required. Pass privateKey in options or set SOLANA_WALLET_KEY environment variable."
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
this.privateKey = privateKey;
|
|
1104
|
+
const apiUrl = options.apiUrl || SOLANA_API_URL;
|
|
1105
|
+
validateApiUrl(apiUrl);
|
|
1106
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
1107
|
+
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
1108
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT3;
|
|
1109
|
+
}
|
|
1110
|
+
/** Get Solana wallet address (public key in base58). */
|
|
1111
|
+
async getWalletAddress() {
|
|
1112
|
+
if (!this.addressCache) {
|
|
1113
|
+
this.addressCache = await solanaPublicKey(this.privateKey);
|
|
1114
|
+
}
|
|
1115
|
+
return this.addressCache;
|
|
1116
|
+
}
|
|
1117
|
+
/** Simple 1-line chat. */
|
|
1118
|
+
async chat(model, prompt, options) {
|
|
1119
|
+
const messages = [];
|
|
1120
|
+
if (options?.system) messages.push({ role: "system", content: options.system });
|
|
1121
|
+
messages.push({ role: "user", content: prompt });
|
|
1122
|
+
const result = await this.chatCompletion(model, messages, {
|
|
1123
|
+
maxTokens: options?.maxTokens,
|
|
1124
|
+
temperature: options?.temperature,
|
|
1125
|
+
topP: options?.topP,
|
|
1126
|
+
search: options?.search,
|
|
1127
|
+
searchParameters: options?.searchParameters
|
|
1128
|
+
});
|
|
1129
|
+
return result.choices[0].message.content || "";
|
|
1130
|
+
}
|
|
1131
|
+
/** Full chat completion (OpenAI-compatible). */
|
|
1132
|
+
async chatCompletion(model, messages, options) {
|
|
1133
|
+
const body = {
|
|
1134
|
+
model,
|
|
1135
|
+
messages,
|
|
1136
|
+
max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS2
|
|
1137
|
+
};
|
|
1138
|
+
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
1139
|
+
if (options?.topP !== void 0) body.top_p = options.topP;
|
|
1140
|
+
if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
|
|
1141
|
+
else if (options?.search === true) body.search_parameters = { mode: "on" };
|
|
1142
|
+
if (options?.tools !== void 0) body.tools = options.tools;
|
|
1143
|
+
if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
1144
|
+
return this.requestWithPayment("/v1/chat/completions", body);
|
|
1145
|
+
}
|
|
1146
|
+
/** List available models. */
|
|
1147
|
+
async listModels() {
|
|
1148
|
+
const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, { method: "GET" });
|
|
1149
|
+
if (!response.ok) {
|
|
1150
|
+
throw new APIError(`Failed to list models: ${response.status}`, response.status);
|
|
1151
|
+
}
|
|
1152
|
+
const data = await response.json();
|
|
1153
|
+
return data.data || [];
|
|
1154
|
+
}
|
|
1155
|
+
/** Get session spending. */
|
|
1156
|
+
getSpending() {
|
|
1157
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
1158
|
+
}
|
|
1159
|
+
/** True if using sol.blockrun.ai. */
|
|
1160
|
+
isSolana() {
|
|
1161
|
+
return this.apiUrl.includes("sol.blockrun.ai");
|
|
1162
|
+
}
|
|
1163
|
+
async requestWithPayment(endpoint, body) {
|
|
1164
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
1165
|
+
const response = await this.fetchWithTimeout(url, {
|
|
1166
|
+
method: "POST",
|
|
1167
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
|
|
1168
|
+
body: JSON.stringify(body)
|
|
1169
|
+
});
|
|
1170
|
+
if (response.status === 402) {
|
|
1171
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
1172
|
+
}
|
|
1173
|
+
if (!response.ok) {
|
|
1174
|
+
let errorBody;
|
|
1175
|
+
try {
|
|
1176
|
+
errorBody = await response.json();
|
|
1177
|
+
} catch {
|
|
1178
|
+
errorBody = { error: "Request failed" };
|
|
1179
|
+
}
|
|
1180
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
1181
|
+
}
|
|
1182
|
+
return response.json();
|
|
1183
|
+
}
|
|
1184
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
1185
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
1186
|
+
if (!paymentHeader) {
|
|
1187
|
+
try {
|
|
1188
|
+
const respBody = await response.json();
|
|
1189
|
+
if (respBody.accepts || respBody.x402Version) {
|
|
1190
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
1191
|
+
}
|
|
1192
|
+
} catch {
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (!paymentHeader) {
|
|
1196
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
1197
|
+
}
|
|
1198
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
1199
|
+
const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
|
|
1200
|
+
if (!details.network?.startsWith("solana:")) {
|
|
1201
|
+
throw new PaymentError(
|
|
1202
|
+
`Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
const feePayer = details.extra?.feePayer;
|
|
1206
|
+
if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
|
|
1207
|
+
const fromAddress = await this.getWalletAddress();
|
|
1208
|
+
const secretKey = await solanaKeyToBytes(this.privateKey);
|
|
1209
|
+
const extensions = paymentRequired.extensions;
|
|
1210
|
+
const paymentPayload = await createSolanaPaymentPayload(
|
|
1211
|
+
secretKey,
|
|
1212
|
+
fromAddress,
|
|
1213
|
+
details.recipient,
|
|
1214
|
+
details.amount,
|
|
1215
|
+
feePayer,
|
|
1216
|
+
{
|
|
1217
|
+
resourceUrl: validateResourceUrl(
|
|
1218
|
+
details.resource?.url || `${this.apiUrl}/v1/chat/completions`,
|
|
1219
|
+
this.apiUrl
|
|
1220
|
+
),
|
|
1221
|
+
resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
|
|
1222
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
1223
|
+
extra: details.extra,
|
|
1224
|
+
extensions,
|
|
1225
|
+
rpcUrl: this.rpcUrl
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
const retryResponse = await this.fetchWithTimeout(url, {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
headers: {
|
|
1231
|
+
"Content-Type": "application/json",
|
|
1232
|
+
"User-Agent": USER_AGENT2,
|
|
1233
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1234
|
+
},
|
|
1235
|
+
body: JSON.stringify(body)
|
|
1236
|
+
});
|
|
1237
|
+
if (retryResponse.status === 402) {
|
|
1238
|
+
throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
|
|
1239
|
+
}
|
|
1240
|
+
if (!retryResponse.ok) {
|
|
1241
|
+
let errorBody;
|
|
1242
|
+
try {
|
|
1243
|
+
errorBody = await retryResponse.json();
|
|
1244
|
+
} catch {
|
|
1245
|
+
errorBody = { error: "Request failed" };
|
|
1246
|
+
}
|
|
1247
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
1248
|
+
}
|
|
1249
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
1250
|
+
this.sessionCalls += 1;
|
|
1251
|
+
this.sessionTotalUsd += costUsd;
|
|
1252
|
+
return retryResponse.json();
|
|
1253
|
+
}
|
|
1254
|
+
async fetchWithTimeout(url, options) {
|
|
1255
|
+
const controller = new AbortController();
|
|
1256
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
1257
|
+
try {
|
|
1258
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
1259
|
+
} finally {
|
|
1260
|
+
clearTimeout(timeoutId);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
function solanaClient(options = {}) {
|
|
1265
|
+
return new SolanaLLMClient({ ...options, apiUrl: SOLANA_API_URL });
|
|
1266
|
+
}
|
|
1267
|
+
|
|
860
1268
|
// src/openai-compat.ts
|
|
861
1269
|
var StreamingResponse = class {
|
|
862
1270
|
reader;
|
|
@@ -1034,20 +1442,32 @@ export {
|
|
|
1034
1442
|
LLMClient,
|
|
1035
1443
|
OpenAI,
|
|
1036
1444
|
PaymentError,
|
|
1445
|
+
SOLANA_NETWORK,
|
|
1446
|
+
SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH,
|
|
1447
|
+
SolanaLLMClient,
|
|
1037
1448
|
USDC_BASE,
|
|
1038
1449
|
USDC_BASE_CONTRACT,
|
|
1450
|
+
USDC_SOLANA,
|
|
1039
1451
|
WALLET_DIR_PATH,
|
|
1040
1452
|
WALLET_FILE_PATH,
|
|
1453
|
+
createSolanaPaymentPayload,
|
|
1454
|
+
createSolanaWallet,
|
|
1041
1455
|
createWallet,
|
|
1042
1456
|
client_default as default,
|
|
1043
1457
|
formatFundingMessageCompact,
|
|
1044
1458
|
formatNeedsFundingMessage,
|
|
1045
1459
|
formatWalletCreatedMessage,
|
|
1046
1460
|
getEip681Uri,
|
|
1461
|
+
getOrCreateSolanaWallet,
|
|
1047
1462
|
getOrCreateWallet,
|
|
1048
1463
|
getPaymentLinks,
|
|
1049
1464
|
getWalletAddress,
|
|
1465
|
+
loadSolanaWallet,
|
|
1050
1466
|
loadWallet,
|
|
1467
|
+
saveSolanaWallet,
|
|
1051
1468
|
saveWallet,
|
|
1469
|
+
solanaClient,
|
|
1470
|
+
solanaKeyToBytes,
|
|
1471
|
+
solanaPublicKey,
|
|
1052
1472
|
testnetClient
|
|
1053
1473
|
};
|
package/package.json
CHANGED
|
@@ -1,70 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@blockrun/llm",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base",
|
|
6
|
-
"main": "dist/index.cjs",
|
|
7
|
-
"module": "dist/index.js",
|
|
8
|
-
"types": "dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"@
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@blockrun/llm",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "BlockRun LLM Gateway SDK - Pay-per-request AI via x402 on Base",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"llm",
|
|
22
|
+
"ai",
|
|
23
|
+
"x402",
|
|
24
|
+
"base",
|
|
25
|
+
"solana",
|
|
26
|
+
"usdc",
|
|
27
|
+
"micropayments",
|
|
28
|
+
"openai",
|
|
29
|
+
"claude",
|
|
30
|
+
"gemini",
|
|
31
|
+
"blockchain"
|
|
32
|
+
],
|
|
33
|
+
"author": "BlockRun <hello@blockrun.ai>",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/BlockRunAI/blockrun-llm-ts"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://blockrun.ai",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/BlockRunAI/blockrun-llm-ts/issues"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@blockrun/clawrouter": "^0.10.18",
|
|
45
|
+
"bs58": "^6.0.0",
|
|
46
|
+
"viem": "^2.21.0"
|
|
47
|
+
},
|
|
48
|
+
"optionalDependencies": {
|
|
49
|
+
"@solana/spl-token": "^0.4.0",
|
|
50
|
+
"@solana/web3.js": "^1.98.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/js": "^9.0.0",
|
|
54
|
+
"@types/node": "^20.0.0",
|
|
55
|
+
"eslint": "^9.0.0",
|
|
56
|
+
"tsup": "^8.0.0",
|
|
57
|
+
"typescript": "^5.0.0",
|
|
58
|
+
"typescript-eslint": "^8.0.0",
|
|
59
|
+
"vitest": "^1.0.0"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=20"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
66
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
67
|
+
"test": "vitest",
|
|
68
|
+
"lint": "eslint src/",
|
|
69
|
+
"typecheck": "tsc --noEmit"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/dist/chunk-2ESYSVXG.js
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
-
}) : x)(function(x) {
|
|
10
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
-
});
|
|
13
|
-
var __esm = (fn, res) => function __init() {
|
|
14
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
15
|
-
};
|
|
16
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
17
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
18
|
-
};
|
|
19
|
-
var __export = (target, all) => {
|
|
20
|
-
for (var name in all)
|
|
21
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
22
|
-
};
|
|
23
|
-
var __copyProps = (to, from, except, desc) => {
|
|
24
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
25
|
-
for (let key of __getOwnPropNames(from))
|
|
26
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
27
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
28
|
-
}
|
|
29
|
-
return to;
|
|
30
|
-
};
|
|
31
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
32
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
33
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
34
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
35
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
36
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
37
|
-
mod
|
|
38
|
-
));
|
|
39
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
__require,
|
|
43
|
-
__esm,
|
|
44
|
-
__commonJS,
|
|
45
|
-
__export,
|
|
46
|
-
__toESM,
|
|
47
|
-
__toCommonJS
|
|
48
|
-
};
|