@blockrun/llm 2.4.0 → 2.7.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.js CHANGED
@@ -112,8 +112,8 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
112
112
  const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
113
113
  const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("@solana/spl-token");
114
114
  const { Keypair } = await import("@solana/web3.js");
115
- const rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
116
- const connection = new Connection(rpcUrl);
115
+ const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
116
+ const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
117
117
  const keypair = Keypair.fromSecretKey(secretKey);
118
118
  const feePayerPubkey = new PublicKey(feePayer);
119
119
  const ownerPubkey = keypair.publicKey;
@@ -2310,11 +2310,22 @@ var VideoClient = class {
2310
2310
  * @param options - Optional generation parameters
2311
2311
  */
2312
2312
  async generate(prompt, options) {
2313
+ if (options?.imageUrl && options?.realFaceAssetId) {
2314
+ throw new Error(
2315
+ "imageUrl and realFaceAssetId are mutually exclusive; pass at most one."
2316
+ );
2317
+ }
2318
+ if (options?.realFaceAssetId && !/^ta_[A-Za-z0-9]+$/.test(options.realFaceAssetId)) {
2319
+ throw new Error(
2320
+ "realFaceAssetId must be a Token360 asset id matching 'ta_[A-Za-z0-9]+' (e.g. 'ta_abc123xyz')"
2321
+ );
2322
+ }
2313
2323
  const body = {
2314
2324
  model: options?.model || DEFAULT_MODEL3,
2315
2325
  prompt
2316
2326
  };
2317
2327
  if (options?.imageUrl) body.image_url = options.imageUrl;
2328
+ if (options?.realFaceAssetId) body.real_face_asset_id = options.realFaceAssetId;
2318
2329
  if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
2319
2330
  if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
2320
2331
  if (options?.resolution) body.resolution = options.resolution;
@@ -2668,15 +2679,37 @@ var VoiceClient = class {
2668
2679
  }
2669
2680
  };
2670
2681
 
2671
- // src/search.ts
2682
+ // src/phone.ts
2672
2683
  import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
2673
2684
  var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2674
2685
  var DEFAULT_TIMEOUT6 = 6e4;
2675
- var SearchClient = class {
2686
+ var PHONE_PRICES = Object.freeze({
2687
+ lookup: 0.01,
2688
+ "lookup/fraud": 0.05,
2689
+ "numbers/buy": 5,
2690
+ "numbers/renew": 5,
2691
+ "numbers/list": 1e-3,
2692
+ "numbers/release": 0
2693
+ });
2694
+ function requireE164(value) {
2695
+ if (!value || typeof value !== "string") {
2696
+ throw new Error("phoneNumber is required (E.164 format, e.g. '+14155552671')");
2697
+ }
2698
+ const v = value.trim();
2699
+ if (!/^\+\d{7,15}$/.test(v)) {
2700
+ throw new Error(
2701
+ `phoneNumber must be E.164 (e.g. '+14155552671'), got ${JSON.stringify(value)}`
2702
+ );
2703
+ }
2704
+ return v;
2705
+ }
2706
+ var PhoneClient = class {
2676
2707
  account;
2677
2708
  privateKey;
2678
2709
  apiUrl;
2679
2710
  timeout;
2711
+ sessionTotalUsd = 0;
2712
+ sessionCalls = 0;
2680
2713
  constructor(options = {}) {
2681
2714
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2682
2715
  const privateKey = options.privateKey || envKey;
@@ -2693,6 +2726,203 @@ var SearchClient = class {
2693
2726
  this.apiUrl = apiUrl.replace(/\/$/, "");
2694
2727
  this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2695
2728
  }
2729
+ /** EVM wallet address used for payments. */
2730
+ getWalletAddress() {
2731
+ return this.account.address;
2732
+ }
2733
+ /** Session-cumulative USD spend + call count across this client instance. */
2734
+ getSpending() {
2735
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
2736
+ }
2737
+ // ─── Lookup ────────────────────────────────────────────────────────────
2738
+ /**
2739
+ * Carrier + line-type lookup. ~$0.01.
2740
+ *
2741
+ * @param phoneNumber E.164 number (e.g. "+14155552671").
2742
+ */
2743
+ async lookup(phoneNumber) {
2744
+ const num = requireE164(phoneNumber);
2745
+ return this.request("lookup", { phoneNumber: num });
2746
+ }
2747
+ /**
2748
+ * Lookup + fraud signals (SIM swap, call forwarding). ~$0.05.
2749
+ *
2750
+ * @param phoneNumber E.164 number.
2751
+ */
2752
+ async lookupFraud(phoneNumber) {
2753
+ const num = requireE164(phoneNumber);
2754
+ return this.request("lookup/fraud", { phoneNumber: num });
2755
+ }
2756
+ // ─── Provisioning ──────────────────────────────────────────────────────
2757
+ /**
2758
+ * Provision a dedicated phone number for 30 days. $5.00.
2759
+ *
2760
+ * Payment is settled only after Twilio confirms the purchase, so failed
2761
+ * buys never charge your wallet.
2762
+ *
2763
+ * @param options.country ISO country code, "US" or "CA" (default "US").
2764
+ * @param options.areaCode Optional 3-digit area-code hint. Availability is
2765
+ * not guaranteed — the backend falls back to any number in the country if
2766
+ * the area code can't be matched.
2767
+ */
2768
+ async buyNumber(options = {}) {
2769
+ const country = options.country ?? "US";
2770
+ if (country !== "US" && country !== "CA") {
2771
+ throw new Error("country must be 'US' or 'CA'");
2772
+ }
2773
+ const body = { country };
2774
+ if (options.areaCode !== void 0) {
2775
+ const ac = options.areaCode;
2776
+ if (typeof ac !== "string" || !/^\d{3}$/.test(ac)) {
2777
+ throw new Error("areaCode must be a 3-digit string, e.g. '415'");
2778
+ }
2779
+ body.areaCode = ac;
2780
+ }
2781
+ const data = await this.request("numbers/buy", body);
2782
+ return data;
2783
+ }
2784
+ /**
2785
+ * Extend an existing provisioned number by 30 days. $5.00.
2786
+ *
2787
+ * @throws {APIError} 403 when the wallet doesn't own the number or it has expired.
2788
+ */
2789
+ async renewNumber(phoneNumber) {
2790
+ const num = requireE164(phoneNumber);
2791
+ const data = await this.request("numbers/renew", {
2792
+ phoneNumber: num
2793
+ });
2794
+ return data;
2795
+ }
2796
+ /** List the wallet's active phone numbers. ~$0.001. */
2797
+ async listNumbers() {
2798
+ const data = await this.request("numbers/list", {});
2799
+ return data;
2800
+ }
2801
+ /**
2802
+ * Release a provisioned number back to the Twilio pool. Free, but still
2803
+ * flows through x402 so the backend can verify ownership.
2804
+ */
2805
+ async releaseNumber(phoneNumber) {
2806
+ const num = requireE164(phoneNumber);
2807
+ const data = await this.request("numbers/release", {
2808
+ phoneNumber: num
2809
+ });
2810
+ return data;
2811
+ }
2812
+ // ─── Internals ─────────────────────────────────────────────────────────
2813
+ async request(path5, body) {
2814
+ const url = `${this.apiUrl}/v1/phone/${path5}`;
2815
+ const response = await this.fetchWithTimeout(url, {
2816
+ method: "POST",
2817
+ headers: { "Content-Type": "application/json" },
2818
+ body: JSON.stringify(body)
2819
+ });
2820
+ if (response.status === 402) {
2821
+ return this.handlePaymentAndRetry(url, body, response);
2822
+ }
2823
+ return this.unwrap(response, false);
2824
+ }
2825
+ async handlePaymentAndRetry(url, body, response) {
2826
+ let paymentHeader = response.headers.get("payment-required");
2827
+ if (!paymentHeader) {
2828
+ try {
2829
+ const rb = await response.json();
2830
+ if (rb.x402 || rb.accepts) paymentHeader = btoa(JSON.stringify(rb));
2831
+ } catch {
2832
+ }
2833
+ }
2834
+ if (!paymentHeader) {
2835
+ throw new PaymentError("402 response but no payment requirements found");
2836
+ }
2837
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2838
+ const details = extractPaymentDetails(paymentRequired);
2839
+ const paymentPayload = await createPaymentPayload(
2840
+ this.privateKey,
2841
+ this.account.address,
2842
+ details.recipient,
2843
+ details.amount,
2844
+ details.network || "eip155:8453",
2845
+ {
2846
+ resourceUrl: details.resource?.url || url,
2847
+ resourceDescription: details.resource?.description || "BlockRun Phone",
2848
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2849
+ extra: details.extra
2850
+ }
2851
+ );
2852
+ const retry = await this.fetchWithTimeout(url, {
2853
+ method: "POST",
2854
+ headers: {
2855
+ "Content-Type": "application/json",
2856
+ "PAYMENT-SIGNATURE": paymentPayload
2857
+ },
2858
+ body: JSON.stringify(body)
2859
+ });
2860
+ if (retry.status === 402) {
2861
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
2862
+ }
2863
+ const data = await this.unwrap(retry, true);
2864
+ const costUsd = Number.parseFloat(details.amount) / 1e6;
2865
+ this.sessionCalls += 1;
2866
+ this.sessionTotalUsd += costUsd;
2867
+ const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
2868
+ if (txHash && data && typeof data === "object" && !("txHash" in data)) {
2869
+ data.txHash = txHash;
2870
+ }
2871
+ return data;
2872
+ }
2873
+ async unwrap(response, afterPayment) {
2874
+ if (response.ok) {
2875
+ return response.json();
2876
+ }
2877
+ let errorBody;
2878
+ try {
2879
+ errorBody = await response.json();
2880
+ } catch {
2881
+ errorBody = { error: "Request failed" };
2882
+ }
2883
+ const prefix = afterPayment ? "API error after payment" : "API error";
2884
+ throw new APIError(
2885
+ `${prefix}: ${response.status}`,
2886
+ response.status,
2887
+ sanitizeErrorResponse(errorBody)
2888
+ );
2889
+ }
2890
+ async fetchWithTimeout(url, init) {
2891
+ const controller = new AbortController();
2892
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2893
+ try {
2894
+ return await fetch(url, { ...init, signal: controller.signal });
2895
+ } finally {
2896
+ clearTimeout(timeoutId);
2897
+ }
2898
+ }
2899
+ };
2900
+
2901
+ // src/search.ts
2902
+ import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
2903
+ var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2904
+ var DEFAULT_TIMEOUT7 = 6e4;
2905
+ var SearchClient = class {
2906
+ account;
2907
+ privateKey;
2908
+ apiUrl;
2909
+ timeout;
2910
+ constructor(options = {}) {
2911
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2912
+ const privateKey = options.privateKey || envKey;
2913
+ if (!privateKey) {
2914
+ throw new Error(
2915
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
2916
+ );
2917
+ }
2918
+ validatePrivateKey(privateKey);
2919
+ this.privateKey = privateKey;
2920
+ this.account = privateKeyToAccount7(privateKey);
2921
+ const apiUrl = options.apiUrl || DEFAULT_API_URL7;
2922
+ validateApiUrl(apiUrl);
2923
+ this.apiUrl = apiUrl.replace(/\/$/, "");
2924
+ this.timeout = options.timeout || DEFAULT_TIMEOUT7;
2925
+ }
2696
2926
  async search(query, options) {
2697
2927
  if (!query || query.length > 1e3) {
2698
2928
  throw new Error("query must be 1-1000 characters");
@@ -2805,9 +3035,9 @@ var SearchClient = class {
2805
3035
  };
2806
3036
 
2807
3037
  // src/x-client.ts
2808
- import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
2809
- var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2810
- var DEFAULT_TIMEOUT7 = 6e4;
3038
+ import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
3039
+ var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3040
+ var DEFAULT_TIMEOUT8 = 6e4;
2811
3041
  var XClient = class _XClient {
2812
3042
  account;
2813
3043
  privateKey;
@@ -2830,11 +3060,11 @@ var XClient = class _XClient {
2830
3060
  }
2831
3061
  validatePrivateKey(privateKey);
2832
3062
  this.privateKey = privateKey;
2833
- this.account = privateKeyToAccount7(privateKey);
2834
- const apiUrl = options.apiUrl || DEFAULT_API_URL7;
3063
+ this.account = privateKeyToAccount8(privateKey);
3064
+ const apiUrl = options.apiUrl || DEFAULT_API_URL8;
2835
3065
  validateApiUrl(apiUrl);
2836
3066
  this.apiUrl = apiUrl.replace(/\/$/, "");
2837
- this.timeout = options.timeout || DEFAULT_TIMEOUT7;
3067
+ this.timeout = options.timeout || DEFAULT_TIMEOUT8;
2838
3068
  }
2839
3069
  // ───────── User endpoints ─────────
2840
3070
  userLookup(usernames) {
@@ -3012,9 +3242,9 @@ var XClient = class _XClient {
3012
3242
  };
3013
3243
 
3014
3244
  // src/price.ts
3015
- import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
3016
- var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3017
- var DEFAULT_TIMEOUT8 = 3e4;
3245
+ import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
3246
+ var DEFAULT_API_URL9 = "https://blockrun.ai/api";
3247
+ var DEFAULT_TIMEOUT9 = 3e4;
3018
3248
  var PriceClient = class {
3019
3249
  account = null;
3020
3250
  privateKey = null;
@@ -3032,12 +3262,12 @@ var PriceClient = class {
3032
3262
  if (privateKey) {
3033
3263
  validatePrivateKey(privateKey);
3034
3264
  this.privateKey = privateKey;
3035
- this.account = privateKeyToAccount8(privateKey);
3265
+ this.account = privateKeyToAccount9(privateKey);
3036
3266
  }
3037
- const apiUrl = options.apiUrl || DEFAULT_API_URL8;
3267
+ const apiUrl = options.apiUrl || DEFAULT_API_URL9;
3038
3268
  validateApiUrl(apiUrl);
3039
3269
  this.apiUrl = apiUrl.replace(/\/$/, "");
3040
- this.timeout = options.timeout || DEFAULT_TIMEOUT8;
3270
+ this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3041
3271
  }
3042
3272
  async price(category, symbol, options) {
3043
3273
  if (!symbol) throw new Error("symbol is required");
@@ -3216,9 +3446,9 @@ function buildUrl(base, query) {
3216
3446
  }
3217
3447
 
3218
3448
  // src/surf.ts
3219
- import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
3220
- var DEFAULT_API_URL9 = "https://blockrun.ai/api";
3221
- var DEFAULT_TIMEOUT9 = 6e4;
3449
+ import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
3450
+ var DEFAULT_API_URL10 = "https://blockrun.ai/api";
3451
+ var DEFAULT_TIMEOUT10 = 6e4;
3222
3452
  var SURF_PREFIX = "/v1/surf";
3223
3453
  var SurfClient = class {
3224
3454
  account;
@@ -3235,11 +3465,11 @@ var SurfClient = class {
3235
3465
  }
3236
3466
  validatePrivateKey(privateKey);
3237
3467
  this.privateKey = privateKey;
3238
- this.account = privateKeyToAccount9(privateKey);
3239
- const apiUrl = options.apiUrl || DEFAULT_API_URL9;
3468
+ this.account = privateKeyToAccount10(privateKey);
3469
+ const apiUrl = options.apiUrl || DEFAULT_API_URL10;
3240
3470
  validateApiUrl(apiUrl);
3241
3471
  this.apiUrl = apiUrl.replace(/\/$/, "");
3242
- this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3472
+ this.timeout = options.timeout || DEFAULT_TIMEOUT10;
3243
3473
  }
3244
3474
  /**
3245
3475
  * GET a Surf endpoint. `path` is everything after `/v1/surf` (a leading
@@ -3370,8 +3600,386 @@ var SurfClient = class {
3370
3600
  }
3371
3601
  };
3372
3602
 
3603
+ // src/blockrun.ts
3604
+ import { privateKeyToAccount as privateKeyToAccount11 } from "viem/accounts";
3605
+ var DEFAULT_API_URL11 = "https://blockrun.ai/api";
3606
+ var DEFAULT_TIMEOUT11 = 6e4;
3607
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
3608
+ var DEFAULT_POLL_BUDGET_MS = 3e5;
3609
+ var MAX_SIGNED_AUTH_SECONDS = 600;
3610
+ var BlockrunClient = class {
3611
+ account;
3612
+ privateKey;
3613
+ apiUrl;
3614
+ timeout;
3615
+ sessionTotalUsd = 0;
3616
+ sessionCalls = 0;
3617
+ constructor(options = {}) {
3618
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3619
+ const privateKey = options.privateKey || envKey;
3620
+ if (!privateKey) {
3621
+ throw new Error(
3622
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
3623
+ );
3624
+ }
3625
+ validatePrivateKey(privateKey);
3626
+ this.privateKey = privateKey;
3627
+ this.account = privateKeyToAccount11(privateKey);
3628
+ const apiUrl = options.apiUrl || DEFAULT_API_URL11;
3629
+ validateApiUrl(apiUrl);
3630
+ this.apiUrl = apiUrl.replace(/\/$/, "");
3631
+ this.timeout = options.timeout || DEFAULT_TIMEOUT11;
3632
+ }
3633
+ /**
3634
+ * GET a BlockRun endpoint. `path` is everything after `/api` (a leading
3635
+ * `/api` is tolerated and stripped). Query params are URL-encoded; arrays
3636
+ * become repeated keys (`?a=1&a=2`); undefined/null are dropped.
3637
+ */
3638
+ async get(path5, params) {
3639
+ const url = this.buildUrl(path5, params);
3640
+ return this.requestWithPayment(url, "GET");
3641
+ }
3642
+ /**
3643
+ * POST a BlockRun endpoint with a JSON body.
3644
+ */
3645
+ async post(path5, body) {
3646
+ const url = this.buildUrl(path5);
3647
+ return this.requestWithPayment(url, "POST", body);
3648
+ }
3649
+ /**
3650
+ * Submit a long-running job and poll until it completes.
3651
+ *
3652
+ * Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
3653
+ * the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
3654
+ * deadline exceeded). Settlement happens only when upstream returns 200 +
3655
+ * completed — upstream failure or caller giving up = no charge.
3656
+ *
3657
+ * If the gateway returns 200 directly on submit (no async surface), this
3658
+ * short-circuits and returns the body. Most long-running endpoints (image,
3659
+ * video, music, voice) return 202 with a poll_url.
3660
+ */
3661
+ async poll(path5, body, options) {
3662
+ const submitUrl = this.buildUrl(path5);
3663
+ const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
3664
+ const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3665
+ const resp402 = await this.fetchWithTimeout(submitUrl, {
3666
+ method: "POST",
3667
+ headers: { "Content-Type": "application/json" },
3668
+ body: JSON.stringify(body ?? {})
3669
+ });
3670
+ if (resp402.status === 200) {
3671
+ return resp402.json();
3672
+ }
3673
+ if (resp402.status !== 402) {
3674
+ await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
3675
+ }
3676
+ const paymentPayload = await this.signFrom402(resp402, submitUrl, {
3677
+ description: "BlockRun async job",
3678
+ maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
3679
+ });
3680
+ const submitResp = await this.fetchWithTimeout(submitUrl, {
3681
+ method: "POST",
3682
+ headers: {
3683
+ "Content-Type": "application/json",
3684
+ "PAYMENT-SIGNATURE": paymentPayload
3685
+ },
3686
+ body: JSON.stringify(body ?? {})
3687
+ });
3688
+ if (submitResp.status === 402) {
3689
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
3690
+ }
3691
+ if (submitResp.status !== 200 && submitResp.status !== 202) {
3692
+ await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
3693
+ }
3694
+ if (submitResp.status === 200) {
3695
+ this.recordSpending();
3696
+ return submitResp.json();
3697
+ }
3698
+ const submitData = await submitResp.json();
3699
+ if (!submitData.id || !submitData.poll_url) {
3700
+ throw new APIError(
3701
+ "Async submit response missing id/poll_url",
3702
+ submitResp.status,
3703
+ { response: submitData }
3704
+ );
3705
+ }
3706
+ const pollUrl = this.absolute(submitData.poll_url);
3707
+ const deadline = Date.now() + budgetMs;
3708
+ let lastStatus = submitData.status || "queued";
3709
+ while (Date.now() < deadline) {
3710
+ await sleep(intervalMs);
3711
+ const pollResp = await this.fetchWithTimeout(pollUrl, {
3712
+ method: "GET",
3713
+ headers: { "PAYMENT-SIGNATURE": paymentPayload }
3714
+ });
3715
+ let pollData = {};
3716
+ try {
3717
+ pollData = await pollResp.json();
3718
+ } catch {
3719
+ }
3720
+ lastStatus = pollData.status || lastStatus;
3721
+ if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
3722
+ continue;
3723
+ }
3724
+ if (lastStatus === "failed") {
3725
+ throw new APIError(
3726
+ `Upstream job failed: ${pollData.error || "unknown"}`,
3727
+ pollResp.status,
3728
+ sanitizeErrorResponse(pollData)
3729
+ );
3730
+ }
3731
+ if (pollResp.status === 200 && lastStatus === "completed") {
3732
+ this.recordSpending();
3733
+ return pollData;
3734
+ }
3735
+ if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
3736
+ await this.throwApiError(pollResp, `poll failed (${pollUrl})`);
3737
+ }
3738
+ }
3739
+ throw new APIError(
3740
+ `Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
3741
+ 504,
3742
+ { id: submitData.id, last_status: lastStatus }
3743
+ );
3744
+ }
3745
+ /**
3746
+ * Stream a Server-Sent Events endpoint.
3747
+ *
3748
+ * Yields each `data: …` line parsed as JSON. Stops when the upstream emits
3749
+ * `data: [DONE]` or closes the connection. Caller is responsible for typing
3750
+ * the chunk shape; pass a generic for typed yields.
3751
+ *
3752
+ * Example — streaming chat:
3753
+ * for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
3754
+ * model: "anthropic/claude-sonnet-4-6",
3755
+ * messages: [{ role: "user", content: "Hi" }],
3756
+ * stream: true,
3757
+ * })) {
3758
+ * process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
3759
+ * }
3760
+ */
3761
+ async *stream(path5, body) {
3762
+ const url = this.buildUrl(path5);
3763
+ const requestBody = JSON.stringify(body ?? {});
3764
+ const resp402 = await this.fetchWithTimeout(url, {
3765
+ method: "POST",
3766
+ headers: { "Content-Type": "application/json" },
3767
+ body: requestBody
3768
+ });
3769
+ let streamResp;
3770
+ if (resp402.status === 200) {
3771
+ streamResp = resp402;
3772
+ } else if (resp402.status === 402) {
3773
+ const paymentPayload = await this.signFrom402(resp402, url, {
3774
+ description: "BlockRun stream"
3775
+ });
3776
+ streamResp = await this.fetchWithTimeout(url, {
3777
+ method: "POST",
3778
+ headers: {
3779
+ "Content-Type": "application/json",
3780
+ "PAYMENT-SIGNATURE": paymentPayload
3781
+ },
3782
+ body: requestBody
3783
+ });
3784
+ if (streamResp.status === 402) {
3785
+ throw new PaymentError(
3786
+ "Payment was rejected. Check your wallet balance."
3787
+ );
3788
+ }
3789
+ if (!streamResp.ok) {
3790
+ await this.throwApiError(streamResp, `stream failed after payment (${url})`);
3791
+ }
3792
+ this.recordSpending();
3793
+ } else {
3794
+ await this.throwApiError(resp402, `stream failed (${url})`);
3795
+ return;
3796
+ }
3797
+ if (!streamResp.body) {
3798
+ throw new APIError("Stream response has no body", streamResp.status, {});
3799
+ }
3800
+ const reader = streamResp.body.getReader();
3801
+ const decoder = new TextDecoder();
3802
+ let buffer = "";
3803
+ try {
3804
+ while (true) {
3805
+ const { done, value } = await reader.read();
3806
+ if (done) break;
3807
+ buffer += decoder.decode(value, { stream: true });
3808
+ const lines = buffer.split("\n");
3809
+ buffer = lines.pop() || "";
3810
+ for (const line of lines) {
3811
+ const trimmed = line.trim();
3812
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
3813
+ const data = trimmed.slice(6);
3814
+ if (data === "[DONE]") return;
3815
+ try {
3816
+ yield JSON.parse(data);
3817
+ } catch {
3818
+ }
3819
+ }
3820
+ }
3821
+ } finally {
3822
+ reader.releaseLock();
3823
+ }
3824
+ }
3825
+ // --------------------------------------------------------------------
3826
+ // Internal: shared infrastructure
3827
+ // --------------------------------------------------------------------
3828
+ buildUrl(path5, params) {
3829
+ let normalized = path5.startsWith("/") ? path5 : `/${path5}`;
3830
+ if (normalized.startsWith("/api/")) {
3831
+ normalized = normalized.slice(4);
3832
+ }
3833
+ const base = `${this.apiUrl}${normalized}`;
3834
+ if (!params) return base;
3835
+ const qs = new URLSearchParams();
3836
+ for (const [key, value] of Object.entries(params)) {
3837
+ if (value === void 0 || value === null) continue;
3838
+ if (Array.isArray(value)) {
3839
+ for (const v of value) {
3840
+ if (v === void 0 || v === null) continue;
3841
+ qs.append(key, String(v));
3842
+ }
3843
+ } else {
3844
+ qs.append(key, String(value));
3845
+ }
3846
+ }
3847
+ const query = qs.toString();
3848
+ return query ? `${base}?${query}` : base;
3849
+ }
3850
+ absolute(url) {
3851
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
3852
+ const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
3853
+ return `${base}${url}`;
3854
+ }
3855
+ async requestWithPayment(url, method, body) {
3856
+ const init = { method };
3857
+ if (method === "POST") {
3858
+ init.headers = { "Content-Type": "application/json" };
3859
+ init.body = JSON.stringify(body ?? {});
3860
+ }
3861
+ const response = await this.fetchWithTimeout(url, init);
3862
+ if (response.status === 402) {
3863
+ return this.handlePaymentAndRetry(url, method, body, response);
3864
+ }
3865
+ if (!response.ok) {
3866
+ await this.throwApiError(response, `${method} ${url} failed`);
3867
+ }
3868
+ return response.json();
3869
+ }
3870
+ async handlePaymentAndRetry(url, method, body, response) {
3871
+ const paymentPayload = await this.signFrom402(response, url, {
3872
+ description: "BlockRun"
3873
+ });
3874
+ const retryInit = {
3875
+ method,
3876
+ headers: { "PAYMENT-SIGNATURE": paymentPayload }
3877
+ };
3878
+ if (method === "POST") {
3879
+ retryInit.headers = {
3880
+ ...retryInit.headers,
3881
+ "Content-Type": "application/json"
3882
+ };
3883
+ retryInit.body = JSON.stringify(body ?? {});
3884
+ }
3885
+ const retry = await this.fetchWithTimeout(url, retryInit);
3886
+ if (retry.status === 402) {
3887
+ throw new PaymentError(
3888
+ "Payment was rejected. Check your wallet balance."
3889
+ );
3890
+ }
3891
+ if (!retry.ok) {
3892
+ await this.throwApiError(retry, `${method} ${url} failed after payment`);
3893
+ }
3894
+ this.recordSpending();
3895
+ return retry.json();
3896
+ }
3897
+ /**
3898
+ * Read a 402 response's payment requirements (header or body), then sign and
3899
+ * return the base64 PAYMENT-SIGNATURE payload. Also records the cost-to-be
3900
+ * onto the response context (settled on `recordSpending`).
3901
+ */
3902
+ async signFrom402(response, url, opts) {
3903
+ let paymentHeader = response.headers.get("payment-required");
3904
+ if (!paymentHeader) {
3905
+ try {
3906
+ const respBody = await response.json();
3907
+ if (respBody.x402Version !== void 0 || respBody.accepts !== void 0) {
3908
+ paymentHeader = btoa(JSON.stringify(respBody));
3909
+ }
3910
+ } catch {
3911
+ }
3912
+ }
3913
+ if (!paymentHeader) {
3914
+ throw new PaymentError("402 response but no payment requirements found");
3915
+ }
3916
+ const paymentRequired = parsePaymentRequired(paymentHeader);
3917
+ const details = extractPaymentDetails(paymentRequired);
3918
+ this.pendingCostUsd = parseFloat(details.amount) / 1e6;
3919
+ return createPaymentPayload(
3920
+ this.privateKey,
3921
+ this.account.address,
3922
+ details.recipient,
3923
+ details.amount,
3924
+ details.network || "eip155:8453",
3925
+ {
3926
+ resourceUrl: details.resource?.url || url,
3927
+ resourceDescription: details.resource?.description || opts.description,
3928
+ maxTimeoutSeconds: Math.max(
3929
+ details.maxTimeoutSeconds || 0,
3930
+ opts.maxTimeoutSeconds || 300
3931
+ ),
3932
+ extra: details.extra
3933
+ }
3934
+ );
3935
+ }
3936
+ /** Accumulates the most-recent pending cost; settled by recordSpending. */
3937
+ pendingCostUsd = 0;
3938
+ recordSpending() {
3939
+ if (this.pendingCostUsd > 0) {
3940
+ this.sessionCalls += 1;
3941
+ this.sessionTotalUsd += this.pendingCostUsd;
3942
+ this.pendingCostUsd = 0;
3943
+ }
3944
+ }
3945
+ async throwApiError(resp, prefix) {
3946
+ let errorBody;
3947
+ try {
3948
+ errorBody = await resp.json();
3949
+ } catch {
3950
+ errorBody = { error: "Request failed" };
3951
+ }
3952
+ throw new APIError(
3953
+ `${prefix}: HTTP ${resp.status}`,
3954
+ resp.status,
3955
+ sanitizeErrorResponse(errorBody)
3956
+ );
3957
+ }
3958
+ async fetchWithTimeout(url, init) {
3959
+ const controller = new AbortController();
3960
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
3961
+ try {
3962
+ return await fetch(url, { ...init, signal: controller.signal });
3963
+ } finally {
3964
+ clearTimeout(timeoutId);
3965
+ }
3966
+ }
3967
+ // --------------------------------------------------------------------
3968
+ // Public surface: wallet + spending
3969
+ // --------------------------------------------------------------------
3970
+ getWalletAddress() {
3971
+ return this.account.address;
3972
+ }
3973
+ getSpending() {
3974
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
3975
+ }
3976
+ };
3977
+ function sleep(ms) {
3978
+ return new Promise((r) => setTimeout(r, ms));
3979
+ }
3980
+
3373
3981
  // src/wallet.ts
3374
- import { privateKeyToAccount as privateKeyToAccount10, generatePrivateKey } from "viem/accounts";
3982
+ import { privateKeyToAccount as privateKeyToAccount12, generatePrivateKey } from "viem/accounts";
3375
3983
  import * as fs2 from "fs";
3376
3984
  import * as path2 from "path";
3377
3985
  import * as os2 from "os";
@@ -3381,7 +3989,7 @@ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
3381
3989
  var WALLET_FILE = path2.join(WALLET_DIR, ".session");
3382
3990
  function createWallet() {
3383
3991
  const privateKey = generatePrivateKey();
3384
- const account = privateKeyToAccount10(privateKey);
3992
+ const account = privateKeyToAccount12(privateKey);
3385
3993
  return {
3386
3994
  address: account.address,
3387
3995
  privateKey
@@ -3437,12 +4045,12 @@ function loadWallet() {
3437
4045
  function getOrCreateWallet() {
3438
4046
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3439
4047
  if (envKey) {
3440
- const account = privateKeyToAccount10(envKey);
4048
+ const account = privateKeyToAccount12(envKey);
3441
4049
  return { address: account.address, privateKey: envKey, isNew: false };
3442
4050
  }
3443
4051
  const fileKey = loadWallet();
3444
4052
  if (fileKey) {
3445
- const account = privateKeyToAccount10(fileKey);
4053
+ const account = privateKeyToAccount12(fileKey);
3446
4054
  return { address: account.address, privateKey: fileKey, isNew: false };
3447
4055
  }
3448
4056
  const { address, privateKey } = createWallet();
@@ -3452,11 +4060,11 @@ function getOrCreateWallet() {
3452
4060
  function getWalletAddress() {
3453
4061
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3454
4062
  if (envKey) {
3455
- return privateKeyToAccount10(envKey).address;
4063
+ return privateKeyToAccount12(envKey).address;
3456
4064
  }
3457
4065
  const fileKey = loadWallet();
3458
4066
  if (fileKey) {
3459
- return privateKeyToAccount10(fileKey).address;
4067
+ return privateKeyToAccount12(fileKey).address;
3460
4068
  }
3461
4069
  return null;
3462
4070
  }
@@ -3636,14 +4244,37 @@ async function getOrCreateSolanaWallet() {
3636
4244
  // src/solana-client.ts
3637
4245
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
3638
4246
  var DEFAULT_MAX_TOKENS2 = 1024;
3639
- var DEFAULT_TIMEOUT10 = 6e4;
4247
+ var DEFAULT_TIMEOUT12 = 6e4;
3640
4248
  var SDK_VERSION2 = "0.3.0";
3641
4249
  var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
4250
+ var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
4251
+ function resolveRpcConfig(rpcUrl, rpcHeaders) {
4252
+ const env = typeof process !== "undefined" && process.env ? process.env : {};
4253
+ const url = rpcUrl || env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC_URL;
4254
+ let headers;
4255
+ if (rpcHeaders) {
4256
+ headers = { ...rpcHeaders };
4257
+ } else if (env.SOLANA_RPC_HEADERS) {
4258
+ try {
4259
+ const parsed = JSON.parse(env.SOLANA_RPC_HEADERS);
4260
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4261
+ headers = Object.fromEntries(
4262
+ Object.entries(parsed).map(([k, v]) => [String(k), String(v)])
4263
+ );
4264
+ }
4265
+ } catch {
4266
+ }
4267
+ } else if (env.SOLANA_RPC_API_KEY) {
4268
+ headers = { "x-api-key": env.SOLANA_RPC_API_KEY };
4269
+ }
4270
+ return headers ? { url, headers } : { url };
4271
+ }
3642
4272
  var SolanaLLMClient = class {
3643
4273
  static SOLANA_API_URL = SOLANA_API_URL;
3644
4274
  privateKey;
3645
4275
  apiUrl;
3646
4276
  rpcUrl;
4277
+ rpcHeaders;
3647
4278
  timeout;
3648
4279
  sessionTotalUsd = 0;
3649
4280
  sessionCalls = 0;
@@ -3660,8 +4291,10 @@ var SolanaLLMClient = class {
3660
4291
  const apiUrl = options.apiUrl || SOLANA_API_URL;
3661
4292
  validateApiUrl(apiUrl);
3662
4293
  this.apiUrl = apiUrl.replace(/\/$/, "");
3663
- this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
3664
- this.timeout = options.timeout || DEFAULT_TIMEOUT10;
4294
+ const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
4295
+ this.rpcUrl = rpc.url;
4296
+ this.rpcHeaders = rpc.headers;
4297
+ this.timeout = options.timeout || DEFAULT_TIMEOUT12;
3665
4298
  }
3666
4299
  /** Get Solana wallet address (public key in base58). */
3667
4300
  async getWalletAddress() {
@@ -3719,7 +4352,7 @@ var SolanaLLMClient = class {
3719
4352
  try {
3720
4353
  const response = await fetch(this.rpcUrl, {
3721
4354
  method: "POST",
3722
- headers: { "Content-Type": "application/json" },
4355
+ headers: { "Content-Type": "application/json", ...this.rpcHeaders ?? {} },
3723
4356
  body: JSON.stringify({
3724
4357
  jsonrpc: "2.0",
3725
4358
  id: 1,
@@ -3970,7 +4603,8 @@ var SolanaLLMClient = class {
3970
4603
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
3971
4604
  extra: details.extra,
3972
4605
  extensions,
3973
- rpcUrl: this.rpcUrl
4606
+ rpcUrl: this.rpcUrl,
4607
+ rpcHeaders: this.rpcHeaders
3974
4608
  }
3975
4609
  );
3976
4610
  const retryResponse = await this.fetchWithTimeout(url, {
@@ -4061,7 +4695,8 @@ var SolanaLLMClient = class {
4061
4695
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
4062
4696
  extra: details.extra,
4063
4697
  extensions,
4064
- rpcUrl: this.rpcUrl
4698
+ rpcUrl: this.rpcUrl,
4699
+ rpcHeaders: this.rpcHeaders
4065
4700
  }
4066
4701
  );
4067
4702
  const retryResponse = await this.fetchWithTimeout(url, {
@@ -4152,7 +4787,8 @@ var SolanaLLMClient = class {
4152
4787
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
4153
4788
  extra: details.extra,
4154
4789
  extensions,
4155
- rpcUrl: this.rpcUrl
4790
+ rpcUrl: this.rpcUrl,
4791
+ rpcHeaders: this.rpcHeaders
4156
4792
  }
4157
4793
  );
4158
4794
  const query = params ? "?" + new URLSearchParams(params).toString() : "";
@@ -4568,7 +5204,7 @@ var OpenAI = class {
4568
5204
  };
4569
5205
 
4570
5206
  // src/anthropic-compat.ts
4571
- import { privateKeyToAccount as privateKeyToAccount11 } from "viem/accounts";
5207
+ import { privateKeyToAccount as privateKeyToAccount13 } from "viem/accounts";
4572
5208
  var AnthropicClient = class {
4573
5209
  _client = null;
4574
5210
  _clientPromise = null;
@@ -4581,7 +5217,7 @@ var AnthropicClient = class {
4581
5217
  const key = options.privateKey ?? wallet.privateKey;
4582
5218
  validatePrivateKey(key);
4583
5219
  this._privateKey = key;
4584
- this._account = privateKeyToAccount11(this._privateKey);
5220
+ this._account = privateKeyToAccount13(this._privateKey);
4585
5221
  const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
4586
5222
  validateApiUrl(apiUrl);
4587
5223
  this._apiUrl = apiUrl.replace(/\/$/, "");
@@ -4679,13 +5315,16 @@ export {
4679
5315
  APIError,
4680
5316
  AnthropicClient,
4681
5317
  BASE_CHAIN_ID,
5318
+ BlockrunClient,
4682
5319
  BlockrunError,
4683
5320
  ImageClient,
4684
5321
  KNOWN_PROVIDERS,
4685
5322
  LLMClient,
4686
5323
  MusicClient,
4687
5324
  OpenAI,
5325
+ PHONE_PRICES,
4688
5326
  PaymentError,
5327
+ PhoneClient,
4689
5328
  PriceClient,
4690
5329
  SOLANA_NETWORK,
4691
5330
  SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH,