@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.cjs CHANGED
@@ -33,13 +33,16 @@ __export(index_exports, {
33
33
  APIError: () => APIError,
34
34
  AnthropicClient: () => AnthropicClient,
35
35
  BASE_CHAIN_ID: () => BASE_CHAIN_ID,
36
+ BlockrunClient: () => BlockrunClient,
36
37
  BlockrunError: () => BlockrunError,
37
38
  ImageClient: () => ImageClient,
38
39
  KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
39
40
  LLMClient: () => LLMClient,
40
41
  MusicClient: () => MusicClient,
41
42
  OpenAI: () => OpenAI,
43
+ PHONE_PRICES: () => PHONE_PRICES,
42
44
  PaymentError: () => PaymentError,
45
+ PhoneClient: () => PhoneClient,
43
46
  PriceClient: () => PriceClient,
44
47
  SOLANA_NETWORK: () => SOLANA_NETWORK,
45
48
  SOLANA_WALLET_FILE_PATH: () => SOLANA_WALLET_FILE,
@@ -210,8 +213,8 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
210
213
  const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
211
214
  const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("@solana/spl-token");
212
215
  const { Keypair } = await import("@solana/web3.js");
213
- const rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
214
- const connection = new Connection(rpcUrl);
216
+ const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
217
+ const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
215
218
  const keypair = Keypair.fromSecretKey(secretKey);
216
219
  const feePayerPubkey = new PublicKey(feePayer);
217
220
  const ownerPubkey = keypair.publicKey;
@@ -2408,11 +2411,22 @@ var VideoClient = class {
2408
2411
  * @param options - Optional generation parameters
2409
2412
  */
2410
2413
  async generate(prompt, options) {
2414
+ if (options?.imageUrl && options?.realFaceAssetId) {
2415
+ throw new Error(
2416
+ "imageUrl and realFaceAssetId are mutually exclusive; pass at most one."
2417
+ );
2418
+ }
2419
+ if (options?.realFaceAssetId && !/^ta_[A-Za-z0-9]+$/.test(options.realFaceAssetId)) {
2420
+ throw new Error(
2421
+ "realFaceAssetId must be a Token360 asset id matching 'ta_[A-Za-z0-9]+' (e.g. 'ta_abc123xyz')"
2422
+ );
2423
+ }
2411
2424
  const body = {
2412
2425
  model: options?.model || DEFAULT_MODEL3,
2413
2426
  prompt
2414
2427
  };
2415
2428
  if (options?.imageUrl) body.image_url = options.imageUrl;
2429
+ if (options?.realFaceAssetId) body.real_face_asset_id = options.realFaceAssetId;
2416
2430
  if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
2417
2431
  if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
2418
2432
  if (options?.resolution) body.resolution = options.resolution;
@@ -2766,15 +2780,37 @@ var VoiceClient = class {
2766
2780
  }
2767
2781
  };
2768
2782
 
2769
- // src/search.ts
2783
+ // src/phone.ts
2770
2784
  var import_accounts7 = require("viem/accounts");
2771
2785
  var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2772
2786
  var DEFAULT_TIMEOUT6 = 6e4;
2773
- var SearchClient = class {
2787
+ var PHONE_PRICES = Object.freeze({
2788
+ lookup: 0.01,
2789
+ "lookup/fraud": 0.05,
2790
+ "numbers/buy": 5,
2791
+ "numbers/renew": 5,
2792
+ "numbers/list": 1e-3,
2793
+ "numbers/release": 0
2794
+ });
2795
+ function requireE164(value) {
2796
+ if (!value || typeof value !== "string") {
2797
+ throw new Error("phoneNumber is required (E.164 format, e.g. '+14155552671')");
2798
+ }
2799
+ const v = value.trim();
2800
+ if (!/^\+\d{7,15}$/.test(v)) {
2801
+ throw new Error(
2802
+ `phoneNumber must be E.164 (e.g. '+14155552671'), got ${JSON.stringify(value)}`
2803
+ );
2804
+ }
2805
+ return v;
2806
+ }
2807
+ var PhoneClient = class {
2774
2808
  account;
2775
2809
  privateKey;
2776
2810
  apiUrl;
2777
2811
  timeout;
2812
+ sessionTotalUsd = 0;
2813
+ sessionCalls = 0;
2778
2814
  constructor(options = {}) {
2779
2815
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2780
2816
  const privateKey = options.privateKey || envKey;
@@ -2791,6 +2827,203 @@ var SearchClient = class {
2791
2827
  this.apiUrl = apiUrl.replace(/\/$/, "");
2792
2828
  this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2793
2829
  }
2830
+ /** EVM wallet address used for payments. */
2831
+ getWalletAddress() {
2832
+ return this.account.address;
2833
+ }
2834
+ /** Session-cumulative USD spend + call count across this client instance. */
2835
+ getSpending() {
2836
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
2837
+ }
2838
+ // ─── Lookup ────────────────────────────────────────────────────────────
2839
+ /**
2840
+ * Carrier + line-type lookup. ~$0.01.
2841
+ *
2842
+ * @param phoneNumber E.164 number (e.g. "+14155552671").
2843
+ */
2844
+ async lookup(phoneNumber) {
2845
+ const num = requireE164(phoneNumber);
2846
+ return this.request("lookup", { phoneNumber: num });
2847
+ }
2848
+ /**
2849
+ * Lookup + fraud signals (SIM swap, call forwarding). ~$0.05.
2850
+ *
2851
+ * @param phoneNumber E.164 number.
2852
+ */
2853
+ async lookupFraud(phoneNumber) {
2854
+ const num = requireE164(phoneNumber);
2855
+ return this.request("lookup/fraud", { phoneNumber: num });
2856
+ }
2857
+ // ─── Provisioning ──────────────────────────────────────────────────────
2858
+ /**
2859
+ * Provision a dedicated phone number for 30 days. $5.00.
2860
+ *
2861
+ * Payment is settled only after Twilio confirms the purchase, so failed
2862
+ * buys never charge your wallet.
2863
+ *
2864
+ * @param options.country ISO country code, "US" or "CA" (default "US").
2865
+ * @param options.areaCode Optional 3-digit area-code hint. Availability is
2866
+ * not guaranteed — the backend falls back to any number in the country if
2867
+ * the area code can't be matched.
2868
+ */
2869
+ async buyNumber(options = {}) {
2870
+ const country = options.country ?? "US";
2871
+ if (country !== "US" && country !== "CA") {
2872
+ throw new Error("country must be 'US' or 'CA'");
2873
+ }
2874
+ const body = { country };
2875
+ if (options.areaCode !== void 0) {
2876
+ const ac = options.areaCode;
2877
+ if (typeof ac !== "string" || !/^\d{3}$/.test(ac)) {
2878
+ throw new Error("areaCode must be a 3-digit string, e.g. '415'");
2879
+ }
2880
+ body.areaCode = ac;
2881
+ }
2882
+ const data = await this.request("numbers/buy", body);
2883
+ return data;
2884
+ }
2885
+ /**
2886
+ * Extend an existing provisioned number by 30 days. $5.00.
2887
+ *
2888
+ * @throws {APIError} 403 when the wallet doesn't own the number or it has expired.
2889
+ */
2890
+ async renewNumber(phoneNumber) {
2891
+ const num = requireE164(phoneNumber);
2892
+ const data = await this.request("numbers/renew", {
2893
+ phoneNumber: num
2894
+ });
2895
+ return data;
2896
+ }
2897
+ /** List the wallet's active phone numbers. ~$0.001. */
2898
+ async listNumbers() {
2899
+ const data = await this.request("numbers/list", {});
2900
+ return data;
2901
+ }
2902
+ /**
2903
+ * Release a provisioned number back to the Twilio pool. Free, but still
2904
+ * flows through x402 so the backend can verify ownership.
2905
+ */
2906
+ async releaseNumber(phoneNumber) {
2907
+ const num = requireE164(phoneNumber);
2908
+ const data = await this.request("numbers/release", {
2909
+ phoneNumber: num
2910
+ });
2911
+ return data;
2912
+ }
2913
+ // ─── Internals ─────────────────────────────────────────────────────────
2914
+ async request(path5, body) {
2915
+ const url = `${this.apiUrl}/v1/phone/${path5}`;
2916
+ const response = await this.fetchWithTimeout(url, {
2917
+ method: "POST",
2918
+ headers: { "Content-Type": "application/json" },
2919
+ body: JSON.stringify(body)
2920
+ });
2921
+ if (response.status === 402) {
2922
+ return this.handlePaymentAndRetry(url, body, response);
2923
+ }
2924
+ return this.unwrap(response, false);
2925
+ }
2926
+ async handlePaymentAndRetry(url, body, response) {
2927
+ let paymentHeader = response.headers.get("payment-required");
2928
+ if (!paymentHeader) {
2929
+ try {
2930
+ const rb = await response.json();
2931
+ if (rb.x402 || rb.accepts) paymentHeader = btoa(JSON.stringify(rb));
2932
+ } catch {
2933
+ }
2934
+ }
2935
+ if (!paymentHeader) {
2936
+ throw new PaymentError("402 response but no payment requirements found");
2937
+ }
2938
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2939
+ const details = extractPaymentDetails(paymentRequired);
2940
+ const paymentPayload = await createPaymentPayload(
2941
+ this.privateKey,
2942
+ this.account.address,
2943
+ details.recipient,
2944
+ details.amount,
2945
+ details.network || "eip155:8453",
2946
+ {
2947
+ resourceUrl: details.resource?.url || url,
2948
+ resourceDescription: details.resource?.description || "BlockRun Phone",
2949
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2950
+ extra: details.extra
2951
+ }
2952
+ );
2953
+ const retry = await this.fetchWithTimeout(url, {
2954
+ method: "POST",
2955
+ headers: {
2956
+ "Content-Type": "application/json",
2957
+ "PAYMENT-SIGNATURE": paymentPayload
2958
+ },
2959
+ body: JSON.stringify(body)
2960
+ });
2961
+ if (retry.status === 402) {
2962
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
2963
+ }
2964
+ const data = await this.unwrap(retry, true);
2965
+ const costUsd = Number.parseFloat(details.amount) / 1e6;
2966
+ this.sessionCalls += 1;
2967
+ this.sessionTotalUsd += costUsd;
2968
+ const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
2969
+ if (txHash && data && typeof data === "object" && !("txHash" in data)) {
2970
+ data.txHash = txHash;
2971
+ }
2972
+ return data;
2973
+ }
2974
+ async unwrap(response, afterPayment) {
2975
+ if (response.ok) {
2976
+ return response.json();
2977
+ }
2978
+ let errorBody;
2979
+ try {
2980
+ errorBody = await response.json();
2981
+ } catch {
2982
+ errorBody = { error: "Request failed" };
2983
+ }
2984
+ const prefix = afterPayment ? "API error after payment" : "API error";
2985
+ throw new APIError(
2986
+ `${prefix}: ${response.status}`,
2987
+ response.status,
2988
+ sanitizeErrorResponse(errorBody)
2989
+ );
2990
+ }
2991
+ async fetchWithTimeout(url, init) {
2992
+ const controller = new AbortController();
2993
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2994
+ try {
2995
+ return await fetch(url, { ...init, signal: controller.signal });
2996
+ } finally {
2997
+ clearTimeout(timeoutId);
2998
+ }
2999
+ }
3000
+ };
3001
+
3002
+ // src/search.ts
3003
+ var import_accounts8 = require("viem/accounts");
3004
+ var DEFAULT_API_URL7 = "https://blockrun.ai/api";
3005
+ var DEFAULT_TIMEOUT7 = 6e4;
3006
+ var SearchClient = class {
3007
+ account;
3008
+ privateKey;
3009
+ apiUrl;
3010
+ timeout;
3011
+ constructor(options = {}) {
3012
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3013
+ const privateKey = options.privateKey || envKey;
3014
+ if (!privateKey) {
3015
+ throw new Error(
3016
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
3017
+ );
3018
+ }
3019
+ validatePrivateKey(privateKey);
3020
+ this.privateKey = privateKey;
3021
+ this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
3022
+ const apiUrl = options.apiUrl || DEFAULT_API_URL7;
3023
+ validateApiUrl(apiUrl);
3024
+ this.apiUrl = apiUrl.replace(/\/$/, "");
3025
+ this.timeout = options.timeout || DEFAULT_TIMEOUT7;
3026
+ }
2794
3027
  async search(query, options) {
2795
3028
  if (!query || query.length > 1e3) {
2796
3029
  throw new Error("query must be 1-1000 characters");
@@ -2903,9 +3136,9 @@ var SearchClient = class {
2903
3136
  };
2904
3137
 
2905
3138
  // src/x-client.ts
2906
- var import_accounts8 = require("viem/accounts");
2907
- var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2908
- var DEFAULT_TIMEOUT7 = 6e4;
3139
+ var import_accounts9 = require("viem/accounts");
3140
+ var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3141
+ var DEFAULT_TIMEOUT8 = 6e4;
2909
3142
  var XClient = class _XClient {
2910
3143
  account;
2911
3144
  privateKey;
@@ -2928,11 +3161,11 @@ var XClient = class _XClient {
2928
3161
  }
2929
3162
  validatePrivateKey(privateKey);
2930
3163
  this.privateKey = privateKey;
2931
- this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
2932
- const apiUrl = options.apiUrl || DEFAULT_API_URL7;
3164
+ this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
3165
+ const apiUrl = options.apiUrl || DEFAULT_API_URL8;
2933
3166
  validateApiUrl(apiUrl);
2934
3167
  this.apiUrl = apiUrl.replace(/\/$/, "");
2935
- this.timeout = options.timeout || DEFAULT_TIMEOUT7;
3168
+ this.timeout = options.timeout || DEFAULT_TIMEOUT8;
2936
3169
  }
2937
3170
  // ───────── User endpoints ─────────
2938
3171
  userLookup(usernames) {
@@ -3110,9 +3343,9 @@ var XClient = class _XClient {
3110
3343
  };
3111
3344
 
3112
3345
  // src/price.ts
3113
- var import_accounts9 = require("viem/accounts");
3114
- var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3115
- var DEFAULT_TIMEOUT8 = 3e4;
3346
+ var import_accounts10 = require("viem/accounts");
3347
+ var DEFAULT_API_URL9 = "https://blockrun.ai/api";
3348
+ var DEFAULT_TIMEOUT9 = 3e4;
3116
3349
  var PriceClient = class {
3117
3350
  account = null;
3118
3351
  privateKey = null;
@@ -3130,12 +3363,12 @@ var PriceClient = class {
3130
3363
  if (privateKey) {
3131
3364
  validatePrivateKey(privateKey);
3132
3365
  this.privateKey = privateKey;
3133
- this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
3366
+ this.account = (0, import_accounts10.privateKeyToAccount)(privateKey);
3134
3367
  }
3135
- const apiUrl = options.apiUrl || DEFAULT_API_URL8;
3368
+ const apiUrl = options.apiUrl || DEFAULT_API_URL9;
3136
3369
  validateApiUrl(apiUrl);
3137
3370
  this.apiUrl = apiUrl.replace(/\/$/, "");
3138
- this.timeout = options.timeout || DEFAULT_TIMEOUT8;
3371
+ this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3139
3372
  }
3140
3373
  async price(category, symbol, options) {
3141
3374
  if (!symbol) throw new Error("symbol is required");
@@ -3314,9 +3547,9 @@ function buildUrl(base, query) {
3314
3547
  }
3315
3548
 
3316
3549
  // src/surf.ts
3317
- var import_accounts10 = require("viem/accounts");
3318
- var DEFAULT_API_URL9 = "https://blockrun.ai/api";
3319
- var DEFAULT_TIMEOUT9 = 6e4;
3550
+ var import_accounts11 = require("viem/accounts");
3551
+ var DEFAULT_API_URL10 = "https://blockrun.ai/api";
3552
+ var DEFAULT_TIMEOUT10 = 6e4;
3320
3553
  var SURF_PREFIX = "/v1/surf";
3321
3554
  var SurfClient = class {
3322
3555
  account;
@@ -3333,11 +3566,11 @@ var SurfClient = class {
3333
3566
  }
3334
3567
  validatePrivateKey(privateKey);
3335
3568
  this.privateKey = privateKey;
3336
- this.account = (0, import_accounts10.privateKeyToAccount)(privateKey);
3337
- const apiUrl = options.apiUrl || DEFAULT_API_URL9;
3569
+ this.account = (0, import_accounts11.privateKeyToAccount)(privateKey);
3570
+ const apiUrl = options.apiUrl || DEFAULT_API_URL10;
3338
3571
  validateApiUrl(apiUrl);
3339
3572
  this.apiUrl = apiUrl.replace(/\/$/, "");
3340
- this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3573
+ this.timeout = options.timeout || DEFAULT_TIMEOUT10;
3341
3574
  }
3342
3575
  /**
3343
3576
  * GET a Surf endpoint. `path` is everything after `/v1/surf` (a leading
@@ -3468,8 +3701,386 @@ var SurfClient = class {
3468
3701
  }
3469
3702
  };
3470
3703
 
3704
+ // src/blockrun.ts
3705
+ var import_accounts12 = require("viem/accounts");
3706
+ var DEFAULT_API_URL11 = "https://blockrun.ai/api";
3707
+ var DEFAULT_TIMEOUT11 = 6e4;
3708
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
3709
+ var DEFAULT_POLL_BUDGET_MS = 3e5;
3710
+ var MAX_SIGNED_AUTH_SECONDS = 600;
3711
+ var BlockrunClient = class {
3712
+ account;
3713
+ privateKey;
3714
+ apiUrl;
3715
+ timeout;
3716
+ sessionTotalUsd = 0;
3717
+ sessionCalls = 0;
3718
+ constructor(options = {}) {
3719
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3720
+ const privateKey = options.privateKey || envKey;
3721
+ if (!privateKey) {
3722
+ throw new Error(
3723
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
3724
+ );
3725
+ }
3726
+ validatePrivateKey(privateKey);
3727
+ this.privateKey = privateKey;
3728
+ this.account = (0, import_accounts12.privateKeyToAccount)(privateKey);
3729
+ const apiUrl = options.apiUrl || DEFAULT_API_URL11;
3730
+ validateApiUrl(apiUrl);
3731
+ this.apiUrl = apiUrl.replace(/\/$/, "");
3732
+ this.timeout = options.timeout || DEFAULT_TIMEOUT11;
3733
+ }
3734
+ /**
3735
+ * GET a BlockRun endpoint. `path` is everything after `/api` (a leading
3736
+ * `/api` is tolerated and stripped). Query params are URL-encoded; arrays
3737
+ * become repeated keys (`?a=1&a=2`); undefined/null are dropped.
3738
+ */
3739
+ async get(path5, params) {
3740
+ const url = this.buildUrl(path5, params);
3741
+ return this.requestWithPayment(url, "GET");
3742
+ }
3743
+ /**
3744
+ * POST a BlockRun endpoint with a JSON body.
3745
+ */
3746
+ async post(path5, body) {
3747
+ const url = this.buildUrl(path5);
3748
+ return this.requestWithPayment(url, "POST", body);
3749
+ }
3750
+ /**
3751
+ * Submit a long-running job and poll until it completes.
3752
+ *
3753
+ * Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
3754
+ * the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
3755
+ * deadline exceeded). Settlement happens only when upstream returns 200 +
3756
+ * completed — upstream failure or caller giving up = no charge.
3757
+ *
3758
+ * If the gateway returns 200 directly on submit (no async surface), this
3759
+ * short-circuits and returns the body. Most long-running endpoints (image,
3760
+ * video, music, voice) return 202 with a poll_url.
3761
+ */
3762
+ async poll(path5, body, options) {
3763
+ const submitUrl = this.buildUrl(path5);
3764
+ const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
3765
+ const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
3766
+ const resp402 = await this.fetchWithTimeout(submitUrl, {
3767
+ method: "POST",
3768
+ headers: { "Content-Type": "application/json" },
3769
+ body: JSON.stringify(body ?? {})
3770
+ });
3771
+ if (resp402.status === 200) {
3772
+ return resp402.json();
3773
+ }
3774
+ if (resp402.status !== 402) {
3775
+ await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
3776
+ }
3777
+ const paymentPayload = await this.signFrom402(resp402, submitUrl, {
3778
+ description: "BlockRun async job",
3779
+ maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
3780
+ });
3781
+ const submitResp = await this.fetchWithTimeout(submitUrl, {
3782
+ method: "POST",
3783
+ headers: {
3784
+ "Content-Type": "application/json",
3785
+ "PAYMENT-SIGNATURE": paymentPayload
3786
+ },
3787
+ body: JSON.stringify(body ?? {})
3788
+ });
3789
+ if (submitResp.status === 402) {
3790
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
3791
+ }
3792
+ if (submitResp.status !== 200 && submitResp.status !== 202) {
3793
+ await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
3794
+ }
3795
+ if (submitResp.status === 200) {
3796
+ this.recordSpending();
3797
+ return submitResp.json();
3798
+ }
3799
+ const submitData = await submitResp.json();
3800
+ if (!submitData.id || !submitData.poll_url) {
3801
+ throw new APIError(
3802
+ "Async submit response missing id/poll_url",
3803
+ submitResp.status,
3804
+ { response: submitData }
3805
+ );
3806
+ }
3807
+ const pollUrl = this.absolute(submitData.poll_url);
3808
+ const deadline = Date.now() + budgetMs;
3809
+ let lastStatus = submitData.status || "queued";
3810
+ while (Date.now() < deadline) {
3811
+ await sleep(intervalMs);
3812
+ const pollResp = await this.fetchWithTimeout(pollUrl, {
3813
+ method: "GET",
3814
+ headers: { "PAYMENT-SIGNATURE": paymentPayload }
3815
+ });
3816
+ let pollData = {};
3817
+ try {
3818
+ pollData = await pollResp.json();
3819
+ } catch {
3820
+ }
3821
+ lastStatus = pollData.status || lastStatus;
3822
+ if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
3823
+ continue;
3824
+ }
3825
+ if (lastStatus === "failed") {
3826
+ throw new APIError(
3827
+ `Upstream job failed: ${pollData.error || "unknown"}`,
3828
+ pollResp.status,
3829
+ sanitizeErrorResponse(pollData)
3830
+ );
3831
+ }
3832
+ if (pollResp.status === 200 && lastStatus === "completed") {
3833
+ this.recordSpending();
3834
+ return pollData;
3835
+ }
3836
+ if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
3837
+ await this.throwApiError(pollResp, `poll failed (${pollUrl})`);
3838
+ }
3839
+ }
3840
+ throw new APIError(
3841
+ `Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
3842
+ 504,
3843
+ { id: submitData.id, last_status: lastStatus }
3844
+ );
3845
+ }
3846
+ /**
3847
+ * Stream a Server-Sent Events endpoint.
3848
+ *
3849
+ * Yields each `data: …` line parsed as JSON. Stops when the upstream emits
3850
+ * `data: [DONE]` or closes the connection. Caller is responsible for typing
3851
+ * the chunk shape; pass a generic for typed yields.
3852
+ *
3853
+ * Example — streaming chat:
3854
+ * for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
3855
+ * model: "anthropic/claude-sonnet-4-6",
3856
+ * messages: [{ role: "user", content: "Hi" }],
3857
+ * stream: true,
3858
+ * })) {
3859
+ * process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
3860
+ * }
3861
+ */
3862
+ async *stream(path5, body) {
3863
+ const url = this.buildUrl(path5);
3864
+ const requestBody = JSON.stringify(body ?? {});
3865
+ const resp402 = await this.fetchWithTimeout(url, {
3866
+ method: "POST",
3867
+ headers: { "Content-Type": "application/json" },
3868
+ body: requestBody
3869
+ });
3870
+ let streamResp;
3871
+ if (resp402.status === 200) {
3872
+ streamResp = resp402;
3873
+ } else if (resp402.status === 402) {
3874
+ const paymentPayload = await this.signFrom402(resp402, url, {
3875
+ description: "BlockRun stream"
3876
+ });
3877
+ streamResp = await this.fetchWithTimeout(url, {
3878
+ method: "POST",
3879
+ headers: {
3880
+ "Content-Type": "application/json",
3881
+ "PAYMENT-SIGNATURE": paymentPayload
3882
+ },
3883
+ body: requestBody
3884
+ });
3885
+ if (streamResp.status === 402) {
3886
+ throw new PaymentError(
3887
+ "Payment was rejected. Check your wallet balance."
3888
+ );
3889
+ }
3890
+ if (!streamResp.ok) {
3891
+ await this.throwApiError(streamResp, `stream failed after payment (${url})`);
3892
+ }
3893
+ this.recordSpending();
3894
+ } else {
3895
+ await this.throwApiError(resp402, `stream failed (${url})`);
3896
+ return;
3897
+ }
3898
+ if (!streamResp.body) {
3899
+ throw new APIError("Stream response has no body", streamResp.status, {});
3900
+ }
3901
+ const reader = streamResp.body.getReader();
3902
+ const decoder = new TextDecoder();
3903
+ let buffer = "";
3904
+ try {
3905
+ while (true) {
3906
+ const { done, value } = await reader.read();
3907
+ if (done) break;
3908
+ buffer += decoder.decode(value, { stream: true });
3909
+ const lines = buffer.split("\n");
3910
+ buffer = lines.pop() || "";
3911
+ for (const line of lines) {
3912
+ const trimmed = line.trim();
3913
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
3914
+ const data = trimmed.slice(6);
3915
+ if (data === "[DONE]") return;
3916
+ try {
3917
+ yield JSON.parse(data);
3918
+ } catch {
3919
+ }
3920
+ }
3921
+ }
3922
+ } finally {
3923
+ reader.releaseLock();
3924
+ }
3925
+ }
3926
+ // --------------------------------------------------------------------
3927
+ // Internal: shared infrastructure
3928
+ // --------------------------------------------------------------------
3929
+ buildUrl(path5, params) {
3930
+ let normalized = path5.startsWith("/") ? path5 : `/${path5}`;
3931
+ if (normalized.startsWith("/api/")) {
3932
+ normalized = normalized.slice(4);
3933
+ }
3934
+ const base = `${this.apiUrl}${normalized}`;
3935
+ if (!params) return base;
3936
+ const qs = new URLSearchParams();
3937
+ for (const [key, value] of Object.entries(params)) {
3938
+ if (value === void 0 || value === null) continue;
3939
+ if (Array.isArray(value)) {
3940
+ for (const v of value) {
3941
+ if (v === void 0 || v === null) continue;
3942
+ qs.append(key, String(v));
3943
+ }
3944
+ } else {
3945
+ qs.append(key, String(value));
3946
+ }
3947
+ }
3948
+ const query = qs.toString();
3949
+ return query ? `${base}?${query}` : base;
3950
+ }
3951
+ absolute(url) {
3952
+ if (url.startsWith("http://") || url.startsWith("https://")) return url;
3953
+ const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
3954
+ return `${base}${url}`;
3955
+ }
3956
+ async requestWithPayment(url, method, body) {
3957
+ const init = { method };
3958
+ if (method === "POST") {
3959
+ init.headers = { "Content-Type": "application/json" };
3960
+ init.body = JSON.stringify(body ?? {});
3961
+ }
3962
+ const response = await this.fetchWithTimeout(url, init);
3963
+ if (response.status === 402) {
3964
+ return this.handlePaymentAndRetry(url, method, body, response);
3965
+ }
3966
+ if (!response.ok) {
3967
+ await this.throwApiError(response, `${method} ${url} failed`);
3968
+ }
3969
+ return response.json();
3970
+ }
3971
+ async handlePaymentAndRetry(url, method, body, response) {
3972
+ const paymentPayload = await this.signFrom402(response, url, {
3973
+ description: "BlockRun"
3974
+ });
3975
+ const retryInit = {
3976
+ method,
3977
+ headers: { "PAYMENT-SIGNATURE": paymentPayload }
3978
+ };
3979
+ if (method === "POST") {
3980
+ retryInit.headers = {
3981
+ ...retryInit.headers,
3982
+ "Content-Type": "application/json"
3983
+ };
3984
+ retryInit.body = JSON.stringify(body ?? {});
3985
+ }
3986
+ const retry = await this.fetchWithTimeout(url, retryInit);
3987
+ if (retry.status === 402) {
3988
+ throw new PaymentError(
3989
+ "Payment was rejected. Check your wallet balance."
3990
+ );
3991
+ }
3992
+ if (!retry.ok) {
3993
+ await this.throwApiError(retry, `${method} ${url} failed after payment`);
3994
+ }
3995
+ this.recordSpending();
3996
+ return retry.json();
3997
+ }
3998
+ /**
3999
+ * Read a 402 response's payment requirements (header or body), then sign and
4000
+ * return the base64 PAYMENT-SIGNATURE payload. Also records the cost-to-be
4001
+ * onto the response context (settled on `recordSpending`).
4002
+ */
4003
+ async signFrom402(response, url, opts) {
4004
+ let paymentHeader = response.headers.get("payment-required");
4005
+ if (!paymentHeader) {
4006
+ try {
4007
+ const respBody = await response.json();
4008
+ if (respBody.x402Version !== void 0 || respBody.accepts !== void 0) {
4009
+ paymentHeader = btoa(JSON.stringify(respBody));
4010
+ }
4011
+ } catch {
4012
+ }
4013
+ }
4014
+ if (!paymentHeader) {
4015
+ throw new PaymentError("402 response but no payment requirements found");
4016
+ }
4017
+ const paymentRequired = parsePaymentRequired(paymentHeader);
4018
+ const details = extractPaymentDetails(paymentRequired);
4019
+ this.pendingCostUsd = parseFloat(details.amount) / 1e6;
4020
+ return createPaymentPayload(
4021
+ this.privateKey,
4022
+ this.account.address,
4023
+ details.recipient,
4024
+ details.amount,
4025
+ details.network || "eip155:8453",
4026
+ {
4027
+ resourceUrl: details.resource?.url || url,
4028
+ resourceDescription: details.resource?.description || opts.description,
4029
+ maxTimeoutSeconds: Math.max(
4030
+ details.maxTimeoutSeconds || 0,
4031
+ opts.maxTimeoutSeconds || 300
4032
+ ),
4033
+ extra: details.extra
4034
+ }
4035
+ );
4036
+ }
4037
+ /** Accumulates the most-recent pending cost; settled by recordSpending. */
4038
+ pendingCostUsd = 0;
4039
+ recordSpending() {
4040
+ if (this.pendingCostUsd > 0) {
4041
+ this.sessionCalls += 1;
4042
+ this.sessionTotalUsd += this.pendingCostUsd;
4043
+ this.pendingCostUsd = 0;
4044
+ }
4045
+ }
4046
+ async throwApiError(resp, prefix) {
4047
+ let errorBody;
4048
+ try {
4049
+ errorBody = await resp.json();
4050
+ } catch {
4051
+ errorBody = { error: "Request failed" };
4052
+ }
4053
+ throw new APIError(
4054
+ `${prefix}: HTTP ${resp.status}`,
4055
+ resp.status,
4056
+ sanitizeErrorResponse(errorBody)
4057
+ );
4058
+ }
4059
+ async fetchWithTimeout(url, init) {
4060
+ const controller = new AbortController();
4061
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
4062
+ try {
4063
+ return await fetch(url, { ...init, signal: controller.signal });
4064
+ } finally {
4065
+ clearTimeout(timeoutId);
4066
+ }
4067
+ }
4068
+ // --------------------------------------------------------------------
4069
+ // Public surface: wallet + spending
4070
+ // --------------------------------------------------------------------
4071
+ getWalletAddress() {
4072
+ return this.account.address;
4073
+ }
4074
+ getSpending() {
4075
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
4076
+ }
4077
+ };
4078
+ function sleep(ms) {
4079
+ return new Promise((r) => setTimeout(r, ms));
4080
+ }
4081
+
3471
4082
  // src/wallet.ts
3472
- var import_accounts11 = require("viem/accounts");
4083
+ var import_accounts13 = require("viem/accounts");
3473
4084
  var fs2 = __toESM(require("fs"), 1);
3474
4085
  var path2 = __toESM(require("path"), 1);
3475
4086
  var os2 = __toESM(require("os"), 1);
@@ -3478,8 +4089,8 @@ var BASE_CHAIN_ID2 = "8453";
3478
4089
  var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
3479
4090
  var WALLET_FILE = path2.join(WALLET_DIR, ".session");
3480
4091
  function createWallet() {
3481
- const privateKey = (0, import_accounts11.generatePrivateKey)();
3482
- const account = (0, import_accounts11.privateKeyToAccount)(privateKey);
4092
+ const privateKey = (0, import_accounts13.generatePrivateKey)();
4093
+ const account = (0, import_accounts13.privateKeyToAccount)(privateKey);
3483
4094
  return {
3484
4095
  address: account.address,
3485
4096
  privateKey
@@ -3535,12 +4146,12 @@ function loadWallet() {
3535
4146
  function getOrCreateWallet() {
3536
4147
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3537
4148
  if (envKey) {
3538
- const account = (0, import_accounts11.privateKeyToAccount)(envKey);
4149
+ const account = (0, import_accounts13.privateKeyToAccount)(envKey);
3539
4150
  return { address: account.address, privateKey: envKey, isNew: false };
3540
4151
  }
3541
4152
  const fileKey = loadWallet();
3542
4153
  if (fileKey) {
3543
- const account = (0, import_accounts11.privateKeyToAccount)(fileKey);
4154
+ const account = (0, import_accounts13.privateKeyToAccount)(fileKey);
3544
4155
  return { address: account.address, privateKey: fileKey, isNew: false };
3545
4156
  }
3546
4157
  const { address, privateKey } = createWallet();
@@ -3550,11 +4161,11 @@ function getOrCreateWallet() {
3550
4161
  function getWalletAddress() {
3551
4162
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3552
4163
  if (envKey) {
3553
- return (0, import_accounts11.privateKeyToAccount)(envKey).address;
4164
+ return (0, import_accounts13.privateKeyToAccount)(envKey).address;
3554
4165
  }
3555
4166
  const fileKey = loadWallet();
3556
4167
  if (fileKey) {
3557
- return (0, import_accounts11.privateKeyToAccount)(fileKey).address;
4168
+ return (0, import_accounts13.privateKeyToAccount)(fileKey).address;
3558
4169
  }
3559
4170
  return null;
3560
4171
  }
@@ -3734,14 +4345,37 @@ async function getOrCreateSolanaWallet() {
3734
4345
  // src/solana-client.ts
3735
4346
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
3736
4347
  var DEFAULT_MAX_TOKENS2 = 1024;
3737
- var DEFAULT_TIMEOUT10 = 6e4;
4348
+ var DEFAULT_TIMEOUT12 = 6e4;
3738
4349
  var SDK_VERSION2 = "0.3.0";
3739
4350
  var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
4351
+ var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
4352
+ function resolveRpcConfig(rpcUrl, rpcHeaders) {
4353
+ const env = typeof process !== "undefined" && process.env ? process.env : {};
4354
+ const url = rpcUrl || env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC_URL;
4355
+ let headers;
4356
+ if (rpcHeaders) {
4357
+ headers = { ...rpcHeaders };
4358
+ } else if (env.SOLANA_RPC_HEADERS) {
4359
+ try {
4360
+ const parsed = JSON.parse(env.SOLANA_RPC_HEADERS);
4361
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4362
+ headers = Object.fromEntries(
4363
+ Object.entries(parsed).map(([k, v]) => [String(k), String(v)])
4364
+ );
4365
+ }
4366
+ } catch {
4367
+ }
4368
+ } else if (env.SOLANA_RPC_API_KEY) {
4369
+ headers = { "x-api-key": env.SOLANA_RPC_API_KEY };
4370
+ }
4371
+ return headers ? { url, headers } : { url };
4372
+ }
3740
4373
  var SolanaLLMClient = class {
3741
4374
  static SOLANA_API_URL = SOLANA_API_URL;
3742
4375
  privateKey;
3743
4376
  apiUrl;
3744
4377
  rpcUrl;
4378
+ rpcHeaders;
3745
4379
  timeout;
3746
4380
  sessionTotalUsd = 0;
3747
4381
  sessionCalls = 0;
@@ -3758,8 +4392,10 @@ var SolanaLLMClient = class {
3758
4392
  const apiUrl = options.apiUrl || SOLANA_API_URL;
3759
4393
  validateApiUrl(apiUrl);
3760
4394
  this.apiUrl = apiUrl.replace(/\/$/, "");
3761
- this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
3762
- this.timeout = options.timeout || DEFAULT_TIMEOUT10;
4395
+ const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
4396
+ this.rpcUrl = rpc.url;
4397
+ this.rpcHeaders = rpc.headers;
4398
+ this.timeout = options.timeout || DEFAULT_TIMEOUT12;
3763
4399
  }
3764
4400
  /** Get Solana wallet address (public key in base58). */
3765
4401
  async getWalletAddress() {
@@ -3817,7 +4453,7 @@ var SolanaLLMClient = class {
3817
4453
  try {
3818
4454
  const response = await fetch(this.rpcUrl, {
3819
4455
  method: "POST",
3820
- headers: { "Content-Type": "application/json" },
4456
+ headers: { "Content-Type": "application/json", ...this.rpcHeaders ?? {} },
3821
4457
  body: JSON.stringify({
3822
4458
  jsonrpc: "2.0",
3823
4459
  id: 1,
@@ -4068,7 +4704,8 @@ var SolanaLLMClient = class {
4068
4704
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
4069
4705
  extra: details.extra,
4070
4706
  extensions,
4071
- rpcUrl: this.rpcUrl
4707
+ rpcUrl: this.rpcUrl,
4708
+ rpcHeaders: this.rpcHeaders
4072
4709
  }
4073
4710
  );
4074
4711
  const retryResponse = await this.fetchWithTimeout(url, {
@@ -4159,7 +4796,8 @@ var SolanaLLMClient = class {
4159
4796
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
4160
4797
  extra: details.extra,
4161
4798
  extensions,
4162
- rpcUrl: this.rpcUrl
4799
+ rpcUrl: this.rpcUrl,
4800
+ rpcHeaders: this.rpcHeaders
4163
4801
  }
4164
4802
  );
4165
4803
  const retryResponse = await this.fetchWithTimeout(url, {
@@ -4250,7 +4888,8 @@ var SolanaLLMClient = class {
4250
4888
  maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
4251
4889
  extra: details.extra,
4252
4890
  extensions,
4253
- rpcUrl: this.rpcUrl
4891
+ rpcUrl: this.rpcUrl,
4892
+ rpcHeaders: this.rpcHeaders
4254
4893
  }
4255
4894
  );
4256
4895
  const query = params ? "?" + new URLSearchParams(params).toString() : "";
@@ -4666,7 +5305,7 @@ var OpenAI = class {
4666
5305
  };
4667
5306
 
4668
5307
  // src/anthropic-compat.ts
4669
- var import_accounts12 = require("viem/accounts");
5308
+ var import_accounts14 = require("viem/accounts");
4670
5309
  var AnthropicClient = class {
4671
5310
  _client = null;
4672
5311
  _clientPromise = null;
@@ -4679,7 +5318,7 @@ var AnthropicClient = class {
4679
5318
  const key = options.privateKey ?? wallet.privateKey;
4680
5319
  validatePrivateKey(key);
4681
5320
  this._privateKey = key;
4682
- this._account = (0, import_accounts12.privateKeyToAccount)(this._privateKey);
5321
+ this._account = (0, import_accounts14.privateKeyToAccount)(this._privateKey);
4683
5322
  const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
4684
5323
  validateApiUrl(apiUrl);
4685
5324
  this._apiUrl = apiUrl.replace(/\/$/, "");
@@ -4778,13 +5417,16 @@ var AnthropicClient = class {
4778
5417
  APIError,
4779
5418
  AnthropicClient,
4780
5419
  BASE_CHAIN_ID,
5420
+ BlockrunClient,
4781
5421
  BlockrunError,
4782
5422
  ImageClient,
4783
5423
  KNOWN_PROVIDERS,
4784
5424
  LLMClient,
4785
5425
  MusicClient,
4786
5426
  OpenAI,
5427
+ PHONE_PRICES,
4787
5428
  PaymentError,
5429
+ PhoneClient,
4788
5430
  PriceClient,
4789
5431
  SOLANA_NETWORK,
4790
5432
  SOLANA_WALLET_FILE_PATH,