@blockrun/llm 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37025,6 +37025,103 @@ var LLMClient = class {
37025
37025
  this.sessionTotalUsd += costUsd;
37026
37026
  return retryResponse.json();
37027
37027
  }
37028
+ /**
37029
+ * Make a request with automatic x402 payment handling, returning raw JSON.
37030
+ * Used for non-ChatResponse endpoints (X/Twitter, search, image edit, etc.).
37031
+ */
37032
+ async requestWithPaymentRaw(endpoint2, body) {
37033
+ const url2 = `${this.apiUrl}${endpoint2}`;
37034
+ const response = await this.fetchWithTimeout(url2, {
37035
+ method: "POST",
37036
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
37037
+ body: JSON.stringify(body)
37038
+ });
37039
+ if (response.status === 402) {
37040
+ return this.handlePaymentAndRetryRaw(url2, body, response);
37041
+ }
37042
+ if (!response.ok) {
37043
+ let errorBody;
37044
+ try {
37045
+ errorBody = await response.json();
37046
+ } catch {
37047
+ errorBody = { error: "Request failed" };
37048
+ }
37049
+ throw new APIError(
37050
+ `API error: ${response.status}`,
37051
+ response.status,
37052
+ sanitizeErrorResponse(errorBody)
37053
+ );
37054
+ }
37055
+ return response.json();
37056
+ }
37057
+ /**
37058
+ * Handle 402 response for raw endpoints: parse requirements, sign payment, retry.
37059
+ */
37060
+ async handlePaymentAndRetryRaw(url2, body, response) {
37061
+ let paymentHeader = response.headers.get("payment-required");
37062
+ if (!paymentHeader) {
37063
+ try {
37064
+ const respBody = await response.json();
37065
+ if (respBody.x402 || respBody.accepts) {
37066
+ paymentHeader = btoa(JSON.stringify(respBody));
37067
+ }
37068
+ } catch {
37069
+ console.debug("Failed to parse payment header from response body");
37070
+ }
37071
+ }
37072
+ if (!paymentHeader) {
37073
+ throw new PaymentError("402 response but no payment requirements found");
37074
+ }
37075
+ const paymentRequired = parsePaymentRequired(paymentHeader);
37076
+ const details = extractPaymentDetails(paymentRequired);
37077
+ const extensions = paymentRequired.extensions;
37078
+ const paymentPayload = await createPaymentPayload(
37079
+ this.privateKey,
37080
+ this.account.address,
37081
+ details.recipient,
37082
+ details.amount,
37083
+ details.network || "eip155:8453",
37084
+ {
37085
+ resourceUrl: validateResourceUrl(
37086
+ details.resource?.url || url2,
37087
+ this.apiUrl
37088
+ ),
37089
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
37090
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
37091
+ extra: details.extra,
37092
+ extensions
37093
+ }
37094
+ );
37095
+ const retryResponse = await this.fetchWithTimeout(url2, {
37096
+ method: "POST",
37097
+ headers: {
37098
+ "Content-Type": "application/json",
37099
+ "User-Agent": USER_AGENT,
37100
+ "PAYMENT-SIGNATURE": paymentPayload
37101
+ },
37102
+ body: JSON.stringify(body)
37103
+ });
37104
+ if (retryResponse.status === 402) {
37105
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
37106
+ }
37107
+ if (!retryResponse.ok) {
37108
+ let errorBody;
37109
+ try {
37110
+ errorBody = await retryResponse.json();
37111
+ } catch {
37112
+ errorBody = { error: "Request failed" };
37113
+ }
37114
+ throw new APIError(
37115
+ `API error after payment: ${retryResponse.status}`,
37116
+ retryResponse.status,
37117
+ sanitizeErrorResponse(errorBody)
37118
+ );
37119
+ }
37120
+ const costUsd = parseFloat(details.amount) / 1e6;
37121
+ this.sessionCalls += 1;
37122
+ this.sessionTotalUsd += costUsd;
37123
+ return retryResponse.json();
37124
+ }
37028
37125
  /**
37029
37126
  * Fetch with timeout.
37030
37127
  */
@@ -37107,6 +37204,285 @@ var LLMClient = class {
37107
37204
  }
37108
37205
  return [...llmModels, ...imageModels];
37109
37206
  }
37207
+ /**
37208
+ * Edit an image using img2img.
37209
+ *
37210
+ * @param prompt - Text description of the desired edit
37211
+ * @param image - Base64-encoded image or URL of the source image
37212
+ * @param options - Optional edit parameters
37213
+ * @returns ImageResponse with edited image URLs
37214
+ */
37215
+ async imageEdit(prompt, image, options) {
37216
+ const body = {
37217
+ model: options?.model || "openai/gpt-image-1",
37218
+ prompt,
37219
+ image,
37220
+ size: options?.size || "1024x1024",
37221
+ n: options?.n || 1
37222
+ };
37223
+ if (options?.mask !== void 0) {
37224
+ body.mask = options.mask;
37225
+ }
37226
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
37227
+ return data;
37228
+ }
37229
+ /**
37230
+ * Standalone search (web, X/Twitter, news).
37231
+ *
37232
+ * @param query - Search query
37233
+ * @param options - Optional search parameters
37234
+ * @returns SearchResult with summary and citations
37235
+ */
37236
+ async search(query, options) {
37237
+ const body = {
37238
+ query,
37239
+ max_results: options?.maxResults || 10
37240
+ };
37241
+ if (options?.sources !== void 0) body.sources = options.sources;
37242
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
37243
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
37244
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
37245
+ return data;
37246
+ }
37247
+ /**
37248
+ * Get USDC balance on Base network.
37249
+ *
37250
+ * Automatically detects mainnet vs testnet based on API URL.
37251
+ *
37252
+ * @returns USDC balance as a float (6 decimal places normalized)
37253
+ *
37254
+ * @example
37255
+ * const balance = await client.getBalance();
37256
+ * console.log(`Balance: $${balance.toFixed(2)} USDC`);
37257
+ */
37258
+ async getBalance() {
37259
+ const isTestnet = this.isTestnet();
37260
+ const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
37261
+ const rpcs = isTestnet ? ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"] : ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
37262
+ const selector = "0x70a08231";
37263
+ const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
37264
+ const data = selector + paddedAddress;
37265
+ const payload = {
37266
+ jsonrpc: "2.0",
37267
+ method: "eth_call",
37268
+ params: [{ to: usdcContract, data }, "latest"],
37269
+ id: 1
37270
+ };
37271
+ let lastError;
37272
+ for (const rpc of rpcs) {
37273
+ try {
37274
+ const response = await fetch(rpc, {
37275
+ method: "POST",
37276
+ headers: { "Content-Type": "application/json" },
37277
+ body: JSON.stringify(payload)
37278
+ });
37279
+ const result = await response.json();
37280
+ const balanceRaw = parseInt(result.result || "0x0", 16);
37281
+ return balanceRaw / 1e6;
37282
+ } catch (e2) {
37283
+ lastError = e2;
37284
+ }
37285
+ }
37286
+ throw lastError || new Error("All RPCs failed");
37287
+ }
37288
+ // ============================================================
37289
+ // X/Twitter endpoints (powered by AttentionVC)
37290
+ // ============================================================
37291
+ /**
37292
+ * Look up X/Twitter user profiles by username.
37293
+ *
37294
+ * Powered by AttentionVC. $0.002 per user (min $0.02, max $0.20).
37295
+ *
37296
+ * @param usernames - Single username or array of usernames (without @)
37297
+ */
37298
+ async xUserLookup(usernames) {
37299
+ const names = Array.isArray(usernames) ? usernames : [usernames];
37300
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
37301
+ return data;
37302
+ }
37303
+ /**
37304
+ * Get followers of an X/Twitter user.
37305
+ *
37306
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
37307
+ *
37308
+ * @param username - X/Twitter username (without @)
37309
+ * @param cursor - Pagination cursor from previous response
37310
+ */
37311
+ async xFollowers(username, cursor) {
37312
+ const body = { username };
37313
+ if (cursor !== void 0) body.cursor = cursor;
37314
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
37315
+ return data;
37316
+ }
37317
+ /**
37318
+ * Get accounts an X/Twitter user is following.
37319
+ *
37320
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
37321
+ *
37322
+ * @param username - X/Twitter username (without @)
37323
+ * @param cursor - Pagination cursor from previous response
37324
+ */
37325
+ async xFollowings(username, cursor) {
37326
+ const body = { username };
37327
+ if (cursor !== void 0) body.cursor = cursor;
37328
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
37329
+ return data;
37330
+ }
37331
+ /**
37332
+ * Get detailed profile info for a single X/Twitter user.
37333
+ *
37334
+ * Powered by AttentionVC. $0.002 per request.
37335
+ *
37336
+ * @param username - X/Twitter username (without @)
37337
+ */
37338
+ async xUserInfo(username) {
37339
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
37340
+ return data;
37341
+ }
37342
+ /**
37343
+ * Get verified (blue-check) followers of an X/Twitter user.
37344
+ *
37345
+ * Powered by AttentionVC. $0.048 per page.
37346
+ *
37347
+ * @param userId - X/Twitter user ID (not username)
37348
+ * @param cursor - Pagination cursor from previous response
37349
+ */
37350
+ async xVerifiedFollowers(userId, cursor) {
37351
+ const body = { userId };
37352
+ if (cursor !== void 0) body.cursor = cursor;
37353
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
37354
+ return data;
37355
+ }
37356
+ /**
37357
+ * Get tweets posted by an X/Twitter user.
37358
+ *
37359
+ * Powered by AttentionVC. $0.032 per page.
37360
+ *
37361
+ * @param username - X/Twitter username (without @)
37362
+ * @param includeReplies - Include reply tweets (default: false)
37363
+ * @param cursor - Pagination cursor from previous response
37364
+ */
37365
+ async xUserTweets(username, includeReplies = false, cursor) {
37366
+ const body = { username, includeReplies };
37367
+ if (cursor !== void 0) body.cursor = cursor;
37368
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
37369
+ return data;
37370
+ }
37371
+ /**
37372
+ * Get tweets that mention an X/Twitter user.
37373
+ *
37374
+ * Powered by AttentionVC. $0.032 per page.
37375
+ *
37376
+ * @param username - X/Twitter username (without @)
37377
+ * @param sinceTime - Start time filter (ISO8601 or Unix timestamp)
37378
+ * @param untilTime - End time filter (ISO8601 or Unix timestamp)
37379
+ * @param cursor - Pagination cursor from previous response
37380
+ */
37381
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
37382
+ const body = { username };
37383
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
37384
+ if (untilTime !== void 0) body.untilTime = untilTime;
37385
+ if (cursor !== void 0) body.cursor = cursor;
37386
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
37387
+ return data;
37388
+ }
37389
+ /**
37390
+ * Fetch full tweet data for up to 200 tweet IDs.
37391
+ *
37392
+ * Powered by AttentionVC. $0.16 per batch.
37393
+ *
37394
+ * @param tweetIds - Single tweet ID or array of tweet IDs (max 200)
37395
+ */
37396
+ async xTweetLookup(tweetIds) {
37397
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
37398
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
37399
+ return data;
37400
+ }
37401
+ /**
37402
+ * Get replies to a specific tweet.
37403
+ *
37404
+ * Powered by AttentionVC. $0.032 per page.
37405
+ *
37406
+ * @param tweetId - The tweet ID to get replies for
37407
+ * @param queryType - Sort order: 'Latest' or 'Default'
37408
+ * @param cursor - Pagination cursor from previous response
37409
+ */
37410
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
37411
+ const body = { tweetId, queryType };
37412
+ if (cursor !== void 0) body.cursor = cursor;
37413
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
37414
+ return data;
37415
+ }
37416
+ /**
37417
+ * Get the full thread context for a tweet.
37418
+ *
37419
+ * Powered by AttentionVC. $0.032 per page.
37420
+ *
37421
+ * @param tweetId - The tweet ID to get thread for
37422
+ * @param cursor - Pagination cursor from previous response
37423
+ */
37424
+ async xTweetThread(tweetId, cursor) {
37425
+ const body = { tweetId };
37426
+ if (cursor !== void 0) body.cursor = cursor;
37427
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
37428
+ return data;
37429
+ }
37430
+ /**
37431
+ * Search X/Twitter with advanced query operators.
37432
+ *
37433
+ * Powered by AttentionVC. $0.032 per page.
37434
+ *
37435
+ * @param query - Search query (supports Twitter search operators)
37436
+ * @param queryType - Sort order: 'Latest', 'Top', or 'Default'
37437
+ * @param cursor - Pagination cursor from previous response
37438
+ */
37439
+ async xSearch(query, queryType = "Latest", cursor) {
37440
+ const body = { query, queryType };
37441
+ if (cursor !== void 0) body.cursor = cursor;
37442
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
37443
+ return data;
37444
+ }
37445
+ /**
37446
+ * Get current trending topics on X/Twitter.
37447
+ *
37448
+ * Powered by AttentionVC. $0.002 per request.
37449
+ */
37450
+ async xTrending() {
37451
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
37452
+ return data;
37453
+ }
37454
+ /**
37455
+ * Get rising/viral articles from X/Twitter.
37456
+ *
37457
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
37458
+ */
37459
+ async xArticlesRising() {
37460
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
37461
+ return data;
37462
+ }
37463
+ /**
37464
+ * Get author analytics and intelligence metrics for an X/Twitter user.
37465
+ *
37466
+ * Powered by AttentionVC intelligence layer. $0.02 per request.
37467
+ *
37468
+ * @param handle - X/Twitter handle (without @)
37469
+ */
37470
+ async xAuthorAnalytics(handle) {
37471
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
37472
+ return data;
37473
+ }
37474
+ /**
37475
+ * Compare two X/Twitter authors side-by-side with intelligence metrics.
37476
+ *
37477
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
37478
+ *
37479
+ * @param handle1 - First X/Twitter handle (without @)
37480
+ * @param handle2 - Second X/Twitter handle (without @)
37481
+ */
37482
+ async xCompareAuthors(handle1, handle2) {
37483
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
37484
+ return data;
37485
+ }
37110
37486
  /**
37111
37487
  * Get current session spending.
37112
37488
  *
@@ -37200,6 +37576,31 @@ var ImageClient = class {
37200
37576
  }
37201
37577
  return this.requestWithPayment("/v1/images/generations", body);
37202
37578
  }
37579
+ /**
37580
+ * Edit an image using img2img.
37581
+ *
37582
+ * @param prompt - Text description of the desired edit
37583
+ * @param image - Base64-encoded image or URL of the source image
37584
+ * @param options - Optional edit parameters
37585
+ * @returns ImageResponse with edited image URLs
37586
+ *
37587
+ * @example
37588
+ * const result = await client.edit('Make it a painting', imageBase64);
37589
+ * console.log(result.data[0].url);
37590
+ */
37591
+ async edit(prompt, image, options) {
37592
+ const body = {
37593
+ model: options?.model || "openai/gpt-image-1",
37594
+ prompt,
37595
+ image,
37596
+ size: options?.size || "1024x1024",
37597
+ n: options?.n || 1
37598
+ };
37599
+ if (options?.mask !== void 0) {
37600
+ body.mask = options.mask;
37601
+ }
37602
+ return this.requestWithPayment("/v1/images/image2image", body);
37603
+ }
37203
37604
  /**
37204
37605
  * List available image generation models with pricing.
37205
37606
  */
@@ -37604,6 +38005,142 @@ var SolanaLLMClient = class {
37604
38005
  const data = await response.json();
37605
38006
  return data.data || [];
37606
38007
  }
38008
+ /**
38009
+ * Get Solana USDC balance.
38010
+ *
38011
+ * @returns USDC balance as a float
38012
+ */
38013
+ async getBalance() {
38014
+ const usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
38015
+ const address = await this.getWalletAddress();
38016
+ try {
38017
+ const response = await fetch(this.rpcUrl, {
38018
+ method: "POST",
38019
+ headers: { "Content-Type": "application/json" },
38020
+ body: JSON.stringify({
38021
+ jsonrpc: "2.0",
38022
+ id: 1,
38023
+ method: "getTokenAccountsByOwner",
38024
+ params: [address, { mint: usdc_mint }, { encoding: "jsonParsed" }]
38025
+ })
38026
+ });
38027
+ const data = await response.json();
38028
+ const accounts = data.result?.value || [];
38029
+ if (!accounts.length) return 0;
38030
+ let total = 0;
38031
+ for (const acct of accounts) {
38032
+ total += acct.account?.data?.parsed?.info?.tokenAmount?.uiAmount || 0;
38033
+ }
38034
+ return total;
38035
+ } catch {
38036
+ return 0;
38037
+ }
38038
+ }
38039
+ /** Edit an image using img2img (Solana payment). */
38040
+ async imageEdit(prompt, image, options) {
38041
+ const body = {
38042
+ model: options?.model || "openai/gpt-image-1",
38043
+ prompt,
38044
+ image,
38045
+ size: options?.size || "1024x1024",
38046
+ n: options?.n || 1
38047
+ };
38048
+ if (options?.mask !== void 0) body.mask = options.mask;
38049
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
38050
+ return data;
38051
+ }
38052
+ /** Standalone search (Solana payment). */
38053
+ async search(query, options) {
38054
+ const body = { query, max_results: options?.maxResults || 10 };
38055
+ if (options?.sources !== void 0) body.sources = options.sources;
38056
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
38057
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
38058
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
38059
+ return data;
38060
+ }
38061
+ // ============================================================
38062
+ // X/Twitter endpoints (powered by AttentionVC)
38063
+ // ============================================================
38064
+ async xUserLookup(usernames) {
38065
+ const names = Array.isArray(usernames) ? usernames : [usernames];
38066
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
38067
+ return data;
38068
+ }
38069
+ async xFollowers(username, cursor) {
38070
+ const body = { username };
38071
+ if (cursor !== void 0) body.cursor = cursor;
38072
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
38073
+ return data;
38074
+ }
38075
+ async xFollowings(username, cursor) {
38076
+ const body = { username };
38077
+ if (cursor !== void 0) body.cursor = cursor;
38078
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
38079
+ return data;
38080
+ }
38081
+ async xUserInfo(username) {
38082
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
38083
+ return data;
38084
+ }
38085
+ async xVerifiedFollowers(userId, cursor) {
38086
+ const body = { userId };
38087
+ if (cursor !== void 0) body.cursor = cursor;
38088
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
38089
+ return data;
38090
+ }
38091
+ async xUserTweets(username, includeReplies = false, cursor) {
38092
+ const body = { username, includeReplies };
38093
+ if (cursor !== void 0) body.cursor = cursor;
38094
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
38095
+ return data;
38096
+ }
38097
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
38098
+ const body = { username };
38099
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
38100
+ if (untilTime !== void 0) body.untilTime = untilTime;
38101
+ if (cursor !== void 0) body.cursor = cursor;
38102
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
38103
+ return data;
38104
+ }
38105
+ async xTweetLookup(tweetIds) {
38106
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
38107
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
38108
+ return data;
38109
+ }
38110
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
38111
+ const body = { tweetId, queryType };
38112
+ if (cursor !== void 0) body.cursor = cursor;
38113
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
38114
+ return data;
38115
+ }
38116
+ async xTweetThread(tweetId, cursor) {
38117
+ const body = { tweetId };
38118
+ if (cursor !== void 0) body.cursor = cursor;
38119
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
38120
+ return data;
38121
+ }
38122
+ async xSearch(query, queryType = "Latest", cursor) {
38123
+ const body = { query, queryType };
38124
+ if (cursor !== void 0) body.cursor = cursor;
38125
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
38126
+ return data;
38127
+ }
38128
+ async xTrending() {
38129
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
38130
+ return data;
38131
+ }
38132
+ async xArticlesRising() {
38133
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
38134
+ return data;
38135
+ }
38136
+ async xAuthorAnalytics(handle) {
38137
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
38138
+ return data;
38139
+ }
38140
+ async xCompareAuthors(handle1, handle2) {
38141
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
38142
+ return data;
38143
+ }
37607
38144
  /** Get session spending. */
37608
38145
  getSpending() {
37609
38146
  return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
@@ -37703,6 +38240,97 @@ var SolanaLLMClient = class {
37703
38240
  this.sessionTotalUsd += costUsd;
37704
38241
  return retryResponse.json();
37705
38242
  }
38243
+ async requestWithPaymentRaw(endpoint2, body) {
38244
+ const url2 = `${this.apiUrl}${endpoint2}`;
38245
+ const response = await this.fetchWithTimeout(url2, {
38246
+ method: "POST",
38247
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
38248
+ body: JSON.stringify(body)
38249
+ });
38250
+ if (response.status === 402) {
38251
+ return this.handlePaymentAndRetryRaw(url2, body, response);
38252
+ }
38253
+ if (!response.ok) {
38254
+ let errorBody;
38255
+ try {
38256
+ errorBody = await response.json();
38257
+ } catch {
38258
+ errorBody = { error: "Request failed" };
38259
+ }
38260
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
38261
+ }
38262
+ return response.json();
38263
+ }
38264
+ async handlePaymentAndRetryRaw(url2, body, response) {
38265
+ let paymentHeader = response.headers.get("payment-required");
38266
+ if (!paymentHeader) {
38267
+ try {
38268
+ const respBody = await response.json();
38269
+ if (respBody.accepts || respBody.x402Version) {
38270
+ paymentHeader = btoa(JSON.stringify(respBody));
38271
+ }
38272
+ } catch {
38273
+ }
38274
+ }
38275
+ if (!paymentHeader) {
38276
+ throw new PaymentError("402 response but no payment requirements found");
38277
+ }
38278
+ const paymentRequired = parsePaymentRequired(paymentHeader);
38279
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
38280
+ if (!details.network?.startsWith("solana:")) {
38281
+ throw new PaymentError(
38282
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
38283
+ );
38284
+ }
38285
+ const feePayer = details.extra?.feePayer;
38286
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
38287
+ const fromAddress = await this.getWalletAddress();
38288
+ const secretKey = await solanaKeyToBytes(this.privateKey);
38289
+ const extensions = paymentRequired.extensions;
38290
+ const paymentPayload = await createSolanaPaymentPayload(
38291
+ secretKey,
38292
+ fromAddress,
38293
+ details.recipient,
38294
+ details.amount,
38295
+ feePayer,
38296
+ {
38297
+ resourceUrl: validateResourceUrl(
38298
+ details.resource?.url || url2,
38299
+ this.apiUrl
38300
+ ),
38301
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
38302
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
38303
+ extra: details.extra,
38304
+ extensions,
38305
+ rpcUrl: this.rpcUrl
38306
+ }
38307
+ );
38308
+ const retryResponse = await this.fetchWithTimeout(url2, {
38309
+ method: "POST",
38310
+ headers: {
38311
+ "Content-Type": "application/json",
38312
+ "User-Agent": USER_AGENT2,
38313
+ "PAYMENT-SIGNATURE": paymentPayload
38314
+ },
38315
+ body: JSON.stringify(body)
38316
+ });
38317
+ if (retryResponse.status === 402) {
38318
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
38319
+ }
38320
+ if (!retryResponse.ok) {
38321
+ let errorBody;
38322
+ try {
38323
+ errorBody = await retryResponse.json();
38324
+ } catch {
38325
+ errorBody = { error: "Request failed" };
38326
+ }
38327
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
38328
+ }
38329
+ const costUsd = parseFloat(details.amount) / 1e6;
38330
+ this.sessionCalls += 1;
38331
+ this.sessionTotalUsd += costUsd;
38332
+ return retryResponse.json();
38333
+ }
37706
38334
  async fetchWithTimeout(url2, options) {
37707
38335
  const controller = new AbortController();
37708
38336
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);