@blockrun/llm 2.1.1 → 2.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/README.md CHANGED
@@ -383,6 +383,35 @@ const r2 = await client.generate('the subject turns and smiles', {
383
383
  });
384
384
  ```
385
385
 
386
+ ### Voice Calls
387
+
388
+ `VoiceClient` wraps `POST /v1/voice/call` (paid, $0.54/call) and
389
+ `GET /v1/voice/call/{callId}` (free polling) — AI-powered outbound phone
390
+ calls powered by Bland.ai. The agent dials the recipient and runs a real-time
391
+ conversation based on your `task` instructions. US + Canada destinations.
392
+
393
+ ```ts
394
+ import { VoiceClient } from '@blockrun/llm';
395
+
396
+ const client = new VoiceClient();
397
+
398
+ // Initiate (paid $0.54)
399
+ const result = await client.call({
400
+ to: '+14155552671',
401
+ task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
402
+ voice: 'maya', // 'nat' | 'josh' | 'maya' | 'june' | 'paige' | 'derek' | 'florian'
403
+ max_duration: 5, // minutes (1–30)
404
+ });
405
+ console.log(result.call_id);
406
+
407
+ // Poll for transcript + recording (free)
408
+ const status = await client.getStatus(result.call_id);
409
+ console.log(status.status, status.recording_url);
410
+ ```
411
+
412
+ Bring your own caller-ID: pass `from: '+14155552671'` (must be a BlockRun
413
+ phone number you own; buy via `/v1/phone/numbers/buy`).
414
+
386
415
  ### Standalone Search
387
416
 
388
417
  `SearchClient` wraps `POST /v1/search` — standalone Grok Live Search.
package/dist/index.cjs CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  USDC_BASE_CONTRACT: () => USDC_BASE_CONTRACT,
50
50
  USDC_SOLANA: () => USDC_SOLANA,
51
51
  VideoClient: () => VideoClient,
52
+ VoiceClient: () => VoiceClient,
52
53
  WALLET_DIR_PATH: () => WALLET_DIR_PATH,
53
54
  WALLET_FILE_PATH: () => WALLET_FILE_PATH,
54
55
  XClient: () => XClient,
@@ -2557,15 +2558,19 @@ var VideoClient = class {
2557
2558
  }
2558
2559
  };
2559
2560
 
2560
- // src/search.ts
2561
+ // src/voice.ts
2561
2562
  var import_accounts6 = require("viem/accounts");
2562
2563
  var DEFAULT_API_URL5 = "https://blockrun.ai/api";
2563
2564
  var DEFAULT_TIMEOUT5 = 6e4;
2564
- var SearchClient = class {
2565
+ var CALL_PRICE_USD = 0.54;
2566
+ var VALID_MODELS = /* @__PURE__ */ new Set(["base", "enhanced", "turbo"]);
2567
+ var VoiceClient = class {
2565
2568
  account;
2566
2569
  privateKey;
2567
2570
  apiUrl;
2568
2571
  timeout;
2572
+ sessionTotalUsd = 0;
2573
+ sessionCalls = 0;
2569
2574
  constructor(options = {}) {
2570
2575
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2571
2576
  const privateKey = options.privateKey || envKey;
@@ -2582,6 +2587,203 @@ var SearchClient = class {
2582
2587
  this.apiUrl = apiUrl.replace(/\/$/, "");
2583
2588
  this.timeout = options.timeout || DEFAULT_TIMEOUT5;
2584
2589
  }
2590
+ /**
2591
+ * Initiate an AI-powered outbound phone call.
2592
+ *
2593
+ * Pricing: $0.54 per call. Returns immediately once the call is queued —
2594
+ * poll getStatus() for transcript and recording.
2595
+ *
2596
+ * @example
2597
+ * const r = await client.call({
2598
+ * to: '+14155552671',
2599
+ * task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
2600
+ * voice: 'maya',
2601
+ * max_duration: 3,
2602
+ * });
2603
+ */
2604
+ async call(options) {
2605
+ if (!options.to || !options.to.trim()) {
2606
+ throw new Error("'to' phone number is required (E.164 format)");
2607
+ }
2608
+ const task = options.task?.trim() ?? "";
2609
+ if (task.length < 10) {
2610
+ throw new Error("'task' must be at least 10 characters");
2611
+ }
2612
+ if (task.length > 4e3) {
2613
+ throw new Error("'task' must be at most 4000 characters");
2614
+ }
2615
+ const maxDuration = options.max_duration ?? 5;
2616
+ if (maxDuration < 1 || maxDuration > 30) {
2617
+ throw new Error("max_duration must be between 1 and 30 minutes");
2618
+ }
2619
+ if (options.model && !VALID_MODELS.has(options.model)) {
2620
+ throw new Error("model must be 'base' | 'enhanced' | 'turbo'");
2621
+ }
2622
+ if (options.interruption_threshold !== void 0 && (options.interruption_threshold < 50 || options.interruption_threshold > 500)) {
2623
+ throw new Error("interruption_threshold must be between 50 and 500");
2624
+ }
2625
+ const body = {
2626
+ to: options.to.trim(),
2627
+ task,
2628
+ max_duration: maxDuration,
2629
+ language: options.language ?? "en-US"
2630
+ };
2631
+ if (options.from) body.from = options.from.trim();
2632
+ if (options.voice) body.voice = options.voice;
2633
+ if (options.first_sentence) body.first_sentence = options.first_sentence.trim();
2634
+ if (options.wait_for_greeting !== void 0) body.wait_for_greeting = options.wait_for_greeting;
2635
+ if (options.interruption_threshold !== void 0) body.interruption_threshold = options.interruption_threshold;
2636
+ if (options.model) body.model = options.model;
2637
+ return this.requestWithPayment("/v1/voice/call", body);
2638
+ }
2639
+ /**
2640
+ * Poll the status of an in-progress or completed call. Free — no payment.
2641
+ *
2642
+ * Returns Bland.ai's full call record: status, transcript, recording URL, etc.
2643
+ * Most fields populate only once the call ends.
2644
+ */
2645
+ async getStatus(callId) {
2646
+ if (!callId || !callId.trim()) {
2647
+ throw new Error("callId is required");
2648
+ }
2649
+ const url = `${this.apiUrl}/v1/voice/call/${encodeURIComponent(callId.trim())}`;
2650
+ const response = await this.fetchWithTimeout(url, {
2651
+ method: "GET",
2652
+ headers: { Accept: "application/json" }
2653
+ });
2654
+ if (response.status === 404) {
2655
+ throw new APIError(`Call not found: ${callId}`, 404, { call_id: callId });
2656
+ }
2657
+ if (!response.ok) {
2658
+ let errorBody;
2659
+ try {
2660
+ errorBody = await response.json();
2661
+ } catch {
2662
+ errorBody = { error: "Request failed" };
2663
+ }
2664
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2665
+ }
2666
+ return response.json();
2667
+ }
2668
+ async requestWithPayment(endpoint, body) {
2669
+ const url = `${this.apiUrl}${endpoint}`;
2670
+ const response = await this.fetchWithTimeout(url, {
2671
+ method: "POST",
2672
+ headers: { "Content-Type": "application/json" },
2673
+ body: JSON.stringify(body)
2674
+ });
2675
+ if (response.status === 402) {
2676
+ return this.handlePaymentAndRetry(url, body, response);
2677
+ }
2678
+ if (!response.ok) {
2679
+ let errorBody;
2680
+ try {
2681
+ errorBody = await response.json();
2682
+ } catch {
2683
+ errorBody = { error: "Request failed" };
2684
+ }
2685
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2686
+ }
2687
+ return response.json();
2688
+ }
2689
+ async handlePaymentAndRetry(url, body, response) {
2690
+ let paymentHeader = response.headers.get("payment-required");
2691
+ if (!paymentHeader) {
2692
+ try {
2693
+ const respBody = await response.json();
2694
+ if (respBody.x402 || respBody.accepts) {
2695
+ paymentHeader = btoa(JSON.stringify(respBody));
2696
+ }
2697
+ } catch {
2698
+ }
2699
+ }
2700
+ if (!paymentHeader) {
2701
+ throw new PaymentError("402 response but no payment requirements found");
2702
+ }
2703
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2704
+ const details = extractPaymentDetails(paymentRequired);
2705
+ const paymentPayload = await createPaymentPayload(
2706
+ this.privateKey,
2707
+ this.account.address,
2708
+ details.recipient,
2709
+ details.amount,
2710
+ details.network || "eip155:8453",
2711
+ {
2712
+ resourceUrl: details.resource?.url || `${this.apiUrl}/v1/voice/call`,
2713
+ resourceDescription: details.resource?.description || "BlockRun Voice Call",
2714
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2715
+ extra: details.extra
2716
+ }
2717
+ );
2718
+ const retryResponse = await this.fetchWithTimeout(url, {
2719
+ method: "POST",
2720
+ headers: {
2721
+ "Content-Type": "application/json",
2722
+ "PAYMENT-SIGNATURE": paymentPayload
2723
+ },
2724
+ body: JSON.stringify(body)
2725
+ });
2726
+ if (retryResponse.status === 402) {
2727
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
2728
+ }
2729
+ if (!retryResponse.ok) {
2730
+ let errorBody;
2731
+ try {
2732
+ errorBody = await retryResponse.json();
2733
+ } catch {
2734
+ errorBody = { error: "Request failed" };
2735
+ }
2736
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2737
+ }
2738
+ const data = await retryResponse.json();
2739
+ this.sessionCalls++;
2740
+ this.sessionTotalUsd += CALL_PRICE_USD;
2741
+ const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
2742
+ if (txHash) data.txHash = txHash;
2743
+ return data;
2744
+ }
2745
+ async fetchWithTimeout(url, options) {
2746
+ const controller = new AbortController();
2747
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2748
+ try {
2749
+ return await fetch(url, { ...options, signal: controller.signal });
2750
+ } finally {
2751
+ clearTimeout(timeoutId);
2752
+ }
2753
+ }
2754
+ getWalletAddress() {
2755
+ return this.account.address;
2756
+ }
2757
+ getSpending() {
2758
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
2759
+ }
2760
+ };
2761
+
2762
+ // src/search.ts
2763
+ var import_accounts7 = require("viem/accounts");
2764
+ var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2765
+ var DEFAULT_TIMEOUT6 = 6e4;
2766
+ var SearchClient = class {
2767
+ account;
2768
+ privateKey;
2769
+ apiUrl;
2770
+ timeout;
2771
+ constructor(options = {}) {
2772
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2773
+ const privateKey = options.privateKey || envKey;
2774
+ if (!privateKey) {
2775
+ throw new Error(
2776
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
2777
+ );
2778
+ }
2779
+ validatePrivateKey(privateKey);
2780
+ this.privateKey = privateKey;
2781
+ this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
2782
+ const apiUrl = options.apiUrl || DEFAULT_API_URL6;
2783
+ validateApiUrl(apiUrl);
2784
+ this.apiUrl = apiUrl.replace(/\/$/, "");
2785
+ this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2786
+ }
2585
2787
  async search(query, options) {
2586
2788
  if (!query || query.length > 1e3) {
2587
2789
  throw new Error("query must be 1-1000 characters");
@@ -2694,9 +2896,9 @@ var SearchClient = class {
2694
2896
  };
2695
2897
 
2696
2898
  // src/x-client.ts
2697
- var import_accounts7 = require("viem/accounts");
2698
- var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2699
- var DEFAULT_TIMEOUT6 = 6e4;
2899
+ var import_accounts8 = require("viem/accounts");
2900
+ var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2901
+ var DEFAULT_TIMEOUT7 = 6e4;
2700
2902
  var XClient = class _XClient {
2701
2903
  account;
2702
2904
  privateKey;
@@ -2719,11 +2921,11 @@ var XClient = class _XClient {
2719
2921
  }
2720
2922
  validatePrivateKey(privateKey);
2721
2923
  this.privateKey = privateKey;
2722
- this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
2723
- const apiUrl = options.apiUrl || DEFAULT_API_URL6;
2924
+ this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
2925
+ const apiUrl = options.apiUrl || DEFAULT_API_URL7;
2724
2926
  validateApiUrl(apiUrl);
2725
2927
  this.apiUrl = apiUrl.replace(/\/$/, "");
2726
- this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2928
+ this.timeout = options.timeout || DEFAULT_TIMEOUT7;
2727
2929
  }
2728
2930
  // ───────── User endpoints ─────────
2729
2931
  userLookup(usernames) {
@@ -2901,9 +3103,9 @@ var XClient = class _XClient {
2901
3103
  };
2902
3104
 
2903
3105
  // src/price.ts
2904
- var import_accounts8 = require("viem/accounts");
2905
- var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2906
- var DEFAULT_TIMEOUT7 = 3e4;
3106
+ var import_accounts9 = require("viem/accounts");
3107
+ var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3108
+ var DEFAULT_TIMEOUT8 = 3e4;
2907
3109
  var PriceClient = class {
2908
3110
  account = null;
2909
3111
  privateKey = null;
@@ -2921,12 +3123,12 @@ var PriceClient = class {
2921
3123
  if (privateKey) {
2922
3124
  validatePrivateKey(privateKey);
2923
3125
  this.privateKey = privateKey;
2924
- this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
3126
+ this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
2925
3127
  }
2926
- const apiUrl = options.apiUrl || DEFAULT_API_URL7;
3128
+ const apiUrl = options.apiUrl || DEFAULT_API_URL8;
2927
3129
  validateApiUrl(apiUrl);
2928
3130
  this.apiUrl = apiUrl.replace(/\/$/, "");
2929
- this.timeout = options.timeout || DEFAULT_TIMEOUT7;
3131
+ this.timeout = options.timeout || DEFAULT_TIMEOUT8;
2930
3132
  }
2931
3133
  async price(category, symbol, options) {
2932
3134
  if (!symbol) throw new Error("symbol is required");
@@ -3105,7 +3307,7 @@ function buildUrl(base, query) {
3105
3307
  }
3106
3308
 
3107
3309
  // src/wallet.ts
3108
- var import_accounts9 = require("viem/accounts");
3310
+ var import_accounts10 = require("viem/accounts");
3109
3311
  var fs2 = __toESM(require("fs"), 1);
3110
3312
  var path2 = __toESM(require("path"), 1);
3111
3313
  var os2 = __toESM(require("os"), 1);
@@ -3114,8 +3316,8 @@ var BASE_CHAIN_ID2 = "8453";
3114
3316
  var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
3115
3317
  var WALLET_FILE = path2.join(WALLET_DIR, ".session");
3116
3318
  function createWallet() {
3117
- const privateKey = (0, import_accounts9.generatePrivateKey)();
3118
- const account = (0, import_accounts9.privateKeyToAccount)(privateKey);
3319
+ const privateKey = (0, import_accounts10.generatePrivateKey)();
3320
+ const account = (0, import_accounts10.privateKeyToAccount)(privateKey);
3119
3321
  return {
3120
3322
  address: account.address,
3121
3323
  privateKey
@@ -3171,12 +3373,12 @@ function loadWallet() {
3171
3373
  function getOrCreateWallet() {
3172
3374
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3173
3375
  if (envKey) {
3174
- const account = (0, import_accounts9.privateKeyToAccount)(envKey);
3376
+ const account = (0, import_accounts10.privateKeyToAccount)(envKey);
3175
3377
  return { address: account.address, privateKey: envKey, isNew: false };
3176
3378
  }
3177
3379
  const fileKey = loadWallet();
3178
3380
  if (fileKey) {
3179
- const account = (0, import_accounts9.privateKeyToAccount)(fileKey);
3381
+ const account = (0, import_accounts10.privateKeyToAccount)(fileKey);
3180
3382
  return { address: account.address, privateKey: fileKey, isNew: false };
3181
3383
  }
3182
3384
  const { address, privateKey } = createWallet();
@@ -3186,11 +3388,11 @@ function getOrCreateWallet() {
3186
3388
  function getWalletAddress() {
3187
3389
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3188
3390
  if (envKey) {
3189
- return (0, import_accounts9.privateKeyToAccount)(envKey).address;
3391
+ return (0, import_accounts10.privateKeyToAccount)(envKey).address;
3190
3392
  }
3191
3393
  const fileKey = loadWallet();
3192
3394
  if (fileKey) {
3193
- return (0, import_accounts9.privateKeyToAccount)(fileKey).address;
3395
+ return (0, import_accounts10.privateKeyToAccount)(fileKey).address;
3194
3396
  }
3195
3397
  return null;
3196
3398
  }
@@ -3370,7 +3572,7 @@ async function getOrCreateSolanaWallet() {
3370
3572
  // src/solana-client.ts
3371
3573
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
3372
3574
  var DEFAULT_MAX_TOKENS2 = 1024;
3373
- var DEFAULT_TIMEOUT8 = 6e4;
3575
+ var DEFAULT_TIMEOUT9 = 6e4;
3374
3576
  var SDK_VERSION2 = "0.3.0";
3375
3577
  var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
3376
3578
  var SolanaLLMClient = class {
@@ -3395,7 +3597,7 @@ var SolanaLLMClient = class {
3395
3597
  validateApiUrl(apiUrl);
3396
3598
  this.apiUrl = apiUrl.replace(/\/$/, "");
3397
3599
  this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
3398
- this.timeout = options.timeout || DEFAULT_TIMEOUT8;
3600
+ this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3399
3601
  }
3400
3602
  /** Get Solana wallet address (public key in base58). */
3401
3603
  async getWalletAddress() {
@@ -4302,7 +4504,7 @@ var OpenAI = class {
4302
4504
  };
4303
4505
 
4304
4506
  // src/anthropic-compat.ts
4305
- var import_accounts10 = require("viem/accounts");
4507
+ var import_accounts11 = require("viem/accounts");
4306
4508
  var AnthropicClient = class {
4307
4509
  _client = null;
4308
4510
  _clientPromise = null;
@@ -4315,7 +4517,7 @@ var AnthropicClient = class {
4315
4517
  const key = options.privateKey ?? wallet.privateKey;
4316
4518
  validatePrivateKey(key);
4317
4519
  this._privateKey = key;
4318
- this._account = (0, import_accounts10.privateKeyToAccount)(this._privateKey);
4520
+ this._account = (0, import_accounts11.privateKeyToAccount)(this._privateKey);
4319
4521
  const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
4320
4522
  validateApiUrl(apiUrl);
4321
4523
  this._apiUrl = apiUrl.replace(/\/$/, "");
@@ -4430,6 +4632,7 @@ var AnthropicClient = class {
4430
4632
  USDC_BASE_CONTRACT,
4431
4633
  USDC_SOLANA,
4432
4634
  VideoClient,
4635
+ VoiceClient,
4433
4636
  WALLET_DIR_PATH,
4434
4637
  WALLET_FILE_PATH,
4435
4638
  XClient,
package/dist/index.d.cts CHANGED
@@ -383,6 +383,74 @@ interface MusicGenerateOptions {
383
383
  /** Custom lyrics — cannot be used with instrumental: true */
384
384
  lyrics?: string;
385
385
  }
386
+ /** Built-in Bland.ai voice presets. Any string is accepted (custom voice IDs work too). */
387
+ type VoicePreset = "nat" | "josh" | "maya" | "june" | "paige" | "derek" | "florian";
388
+ /** Bland.ai conversation model tier. */
389
+ type CallModel = "base" | "enhanced" | "turbo";
390
+ interface VoiceClientOptions {
391
+ /** EVM wallet private key (hex string starting with 0x) */
392
+ privateKey?: `0x${string}` | string;
393
+ /** API endpoint URL (default: https://blockrun.ai/api) */
394
+ apiUrl?: string;
395
+ /** Request timeout in milliseconds (default: 60000 — initiation only) */
396
+ timeout?: number;
397
+ }
398
+ interface CallOptions {
399
+ /** Destination phone number in E.164 format (e.g. "+14155552671"). US + Canada. */
400
+ to: string;
401
+ /** What the AI agent should do on the call. 10–4000 chars. */
402
+ task: string;
403
+ /** Your provisioned BlockRun caller-ID number (E.164). Must be wallet-owned. */
404
+ from?: string;
405
+ /** Voice preset or any Bland.ai voice ID. */
406
+ voice?: VoicePreset | string;
407
+ /** Maximum call length in minutes (1–30, default 5). */
408
+ max_duration?: number;
409
+ /** BCP-47 language code for STT/TTS (default "en-US"). */
410
+ language?: string;
411
+ /** Optional opening line spoken by the agent. */
412
+ first_sentence?: string;
413
+ /** If true, wait for the recipient to speak first. */
414
+ wait_for_greeting?: boolean;
415
+ /** Sensitivity for detecting recipient interruption (50–500 ms). */
416
+ interruption_threshold?: number;
417
+ /** Conversation model. */
418
+ model?: CallModel;
419
+ }
420
+ interface CallInitiatedResponse {
421
+ call_id: string;
422
+ status: string;
423
+ poll_url: string;
424
+ message?: string;
425
+ /** On-chain payment receipt (Base tx hash). */
426
+ txHash?: string;
427
+ }
428
+ /**
429
+ * Bland.ai call status payload. Returned from getStatus.
430
+ * Most fields are populated only after the call ends.
431
+ */
432
+ interface CallStatusResponse {
433
+ call_id?: string;
434
+ status?: string;
435
+ to?: string;
436
+ from?: string;
437
+ /** ISO timestamp call started, or null while queued. */
438
+ started_at?: string | null;
439
+ /** ISO timestamp call ended, or null while in progress. */
440
+ ended_at?: string | null;
441
+ /** Total call length in seconds, once completed. */
442
+ call_length?: number;
443
+ /** URL to the call recording (mp3/wav). */
444
+ recording_url?: string | null;
445
+ /** Full text transcript of the call. */
446
+ concatenated_transcript?: string | null;
447
+ /** Per-turn transcript array. Shape comes from Bland.ai. */
448
+ transcripts?: Array<Record<string, unknown>>;
449
+ /** Why the call ended ("user_hangup", "agent_hangup", "timeout", …). */
450
+ ended_reason?: string | null;
451
+ /** Pass-through for anything Bland.ai adds. */
452
+ [key: string]: unknown;
453
+ }
386
454
  interface VideoClip {
387
455
  /** Permanent blockrun-hosted URL (falls back to upstream if backup fails) */
388
456
  url: string;
@@ -1416,6 +1484,83 @@ declare class VideoClient {
1416
1484
  getSpending(): Spending;
1417
1485
  }
1418
1486
 
1487
+ /**
1488
+ * BlockRun Voice Call Client - AI-powered outbound phone calls via x402 micropayments.
1489
+ *
1490
+ * The AI agent calls a phone number (E.164) and conducts a real-time conversation
1491
+ * based on your 'task' instructions. STT, LLM, and TTS are handled upstream by
1492
+ * Bland.ai; BlockRun handles billing through x402.
1493
+ *
1494
+ * SECURITY NOTE - Private Key Handling:
1495
+ * Your private key NEVER leaves your machine. Here's what happens:
1496
+ * 1. Key stays local - only used to sign an EIP-712 typed data message
1497
+ * 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
1498
+ * 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
1499
+ *
1500
+ * Usage:
1501
+ * import { VoiceClient } from '@blockrun/llm';
1502
+ *
1503
+ * const client = new VoiceClient({ privateKey: '0x...' });
1504
+ *
1505
+ * // Initiate a call (paid, $0.54)
1506
+ * const result = await client.call({
1507
+ * to: '+14155552671',
1508
+ * task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
1509
+ * max_duration: 5,
1510
+ * });
1511
+ * console.log(result.call_id);
1512
+ *
1513
+ * // Poll status, transcript, recording (free)
1514
+ * const status = await client.getStatus(result.call_id);
1515
+ * console.log(status);
1516
+ */
1517
+
1518
+ /**
1519
+ * BlockRun Voice Call Client.
1520
+ *
1521
+ * Initiates AI-powered outbound phone calls with automatic x402 micropayments
1522
+ * on Base chain.
1523
+ *
1524
+ * Pricing: $0.54 per call (regardless of duration up to max_duration).
1525
+ * Status polling is free.
1526
+ */
1527
+ declare class VoiceClient {
1528
+ private account;
1529
+ private privateKey;
1530
+ private apiUrl;
1531
+ private timeout;
1532
+ private sessionTotalUsd;
1533
+ private sessionCalls;
1534
+ constructor(options?: VoiceClientOptions);
1535
+ /**
1536
+ * Initiate an AI-powered outbound phone call.
1537
+ *
1538
+ * Pricing: $0.54 per call. Returns immediately once the call is queued —
1539
+ * poll getStatus() for transcript and recording.
1540
+ *
1541
+ * @example
1542
+ * const r = await client.call({
1543
+ * to: '+14155552671',
1544
+ * task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
1545
+ * voice: 'maya',
1546
+ * max_duration: 3,
1547
+ * });
1548
+ */
1549
+ call(options: CallOptions): Promise<CallInitiatedResponse>;
1550
+ /**
1551
+ * Poll the status of an in-progress or completed call. Free — no payment.
1552
+ *
1553
+ * Returns Bland.ai's full call record: status, transcript, recording URL, etc.
1554
+ * Most fields populate only once the call ends.
1555
+ */
1556
+ getStatus(callId: string): Promise<CallStatusResponse>;
1557
+ private requestWithPayment;
1558
+ private handlePaymentAndRetry;
1559
+ private fetchWithTimeout;
1560
+ getWalletAddress(): string;
1561
+ getSpending(): Spending;
1562
+ }
1563
+
1419
1564
  /**
1420
1565
  * BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
1421
1566
  *
@@ -2193,4 +2338,4 @@ declare function validateTemperature(temperature?: number): void;
2193
2338
  */
2194
2339
  declare function validateTopP(topP?: number): void;
2195
2340
 
2196
- export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type StockMarket, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, XClient, type XClientOptions, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsOptions, type XMentionsResponse, type XSearchOptions, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesOptions, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XUserTweetsOptions, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
2341
+ export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, type BlockRunAnthropicOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type StockMarket, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, XClient, type XClientOptions, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsOptions, type XMentionsResponse, type XSearchOptions, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesOptions, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XUserTweetsOptions, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.d.ts CHANGED
@@ -383,6 +383,74 @@ interface MusicGenerateOptions {
383
383
  /** Custom lyrics — cannot be used with instrumental: true */
384
384
  lyrics?: string;
385
385
  }
386
+ /** Built-in Bland.ai voice presets. Any string is accepted (custom voice IDs work too). */
387
+ type VoicePreset = "nat" | "josh" | "maya" | "june" | "paige" | "derek" | "florian";
388
+ /** Bland.ai conversation model tier. */
389
+ type CallModel = "base" | "enhanced" | "turbo";
390
+ interface VoiceClientOptions {
391
+ /** EVM wallet private key (hex string starting with 0x) */
392
+ privateKey?: `0x${string}` | string;
393
+ /** API endpoint URL (default: https://blockrun.ai/api) */
394
+ apiUrl?: string;
395
+ /** Request timeout in milliseconds (default: 60000 — initiation only) */
396
+ timeout?: number;
397
+ }
398
+ interface CallOptions {
399
+ /** Destination phone number in E.164 format (e.g. "+14155552671"). US + Canada. */
400
+ to: string;
401
+ /** What the AI agent should do on the call. 10–4000 chars. */
402
+ task: string;
403
+ /** Your provisioned BlockRun caller-ID number (E.164). Must be wallet-owned. */
404
+ from?: string;
405
+ /** Voice preset or any Bland.ai voice ID. */
406
+ voice?: VoicePreset | string;
407
+ /** Maximum call length in minutes (1–30, default 5). */
408
+ max_duration?: number;
409
+ /** BCP-47 language code for STT/TTS (default "en-US"). */
410
+ language?: string;
411
+ /** Optional opening line spoken by the agent. */
412
+ first_sentence?: string;
413
+ /** If true, wait for the recipient to speak first. */
414
+ wait_for_greeting?: boolean;
415
+ /** Sensitivity for detecting recipient interruption (50–500 ms). */
416
+ interruption_threshold?: number;
417
+ /** Conversation model. */
418
+ model?: CallModel;
419
+ }
420
+ interface CallInitiatedResponse {
421
+ call_id: string;
422
+ status: string;
423
+ poll_url: string;
424
+ message?: string;
425
+ /** On-chain payment receipt (Base tx hash). */
426
+ txHash?: string;
427
+ }
428
+ /**
429
+ * Bland.ai call status payload. Returned from getStatus.
430
+ * Most fields are populated only after the call ends.
431
+ */
432
+ interface CallStatusResponse {
433
+ call_id?: string;
434
+ status?: string;
435
+ to?: string;
436
+ from?: string;
437
+ /** ISO timestamp call started, or null while queued. */
438
+ started_at?: string | null;
439
+ /** ISO timestamp call ended, or null while in progress. */
440
+ ended_at?: string | null;
441
+ /** Total call length in seconds, once completed. */
442
+ call_length?: number;
443
+ /** URL to the call recording (mp3/wav). */
444
+ recording_url?: string | null;
445
+ /** Full text transcript of the call. */
446
+ concatenated_transcript?: string | null;
447
+ /** Per-turn transcript array. Shape comes from Bland.ai. */
448
+ transcripts?: Array<Record<string, unknown>>;
449
+ /** Why the call ended ("user_hangup", "agent_hangup", "timeout", …). */
450
+ ended_reason?: string | null;
451
+ /** Pass-through for anything Bland.ai adds. */
452
+ [key: string]: unknown;
453
+ }
386
454
  interface VideoClip {
387
455
  /** Permanent blockrun-hosted URL (falls back to upstream if backup fails) */
388
456
  url: string;
@@ -1416,6 +1484,83 @@ declare class VideoClient {
1416
1484
  getSpending(): Spending;
1417
1485
  }
1418
1486
 
1487
+ /**
1488
+ * BlockRun Voice Call Client - AI-powered outbound phone calls via x402 micropayments.
1489
+ *
1490
+ * The AI agent calls a phone number (E.164) and conducts a real-time conversation
1491
+ * based on your 'task' instructions. STT, LLM, and TTS are handled upstream by
1492
+ * Bland.ai; BlockRun handles billing through x402.
1493
+ *
1494
+ * SECURITY NOTE - Private Key Handling:
1495
+ * Your private key NEVER leaves your machine. Here's what happens:
1496
+ * 1. Key stays local - only used to sign an EIP-712 typed data message
1497
+ * 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
1498
+ * 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
1499
+ *
1500
+ * Usage:
1501
+ * import { VoiceClient } from '@blockrun/llm';
1502
+ *
1503
+ * const client = new VoiceClient({ privateKey: '0x...' });
1504
+ *
1505
+ * // Initiate a call (paid, $0.54)
1506
+ * const result = await client.call({
1507
+ * to: '+14155552671',
1508
+ * task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
1509
+ * max_duration: 5,
1510
+ * });
1511
+ * console.log(result.call_id);
1512
+ *
1513
+ * // Poll status, transcript, recording (free)
1514
+ * const status = await client.getStatus(result.call_id);
1515
+ * console.log(status);
1516
+ */
1517
+
1518
+ /**
1519
+ * BlockRun Voice Call Client.
1520
+ *
1521
+ * Initiates AI-powered outbound phone calls with automatic x402 micropayments
1522
+ * on Base chain.
1523
+ *
1524
+ * Pricing: $0.54 per call (regardless of duration up to max_duration).
1525
+ * Status polling is free.
1526
+ */
1527
+ declare class VoiceClient {
1528
+ private account;
1529
+ private privateKey;
1530
+ private apiUrl;
1531
+ private timeout;
1532
+ private sessionTotalUsd;
1533
+ private sessionCalls;
1534
+ constructor(options?: VoiceClientOptions);
1535
+ /**
1536
+ * Initiate an AI-powered outbound phone call.
1537
+ *
1538
+ * Pricing: $0.54 per call. Returns immediately once the call is queued —
1539
+ * poll getStatus() for transcript and recording.
1540
+ *
1541
+ * @example
1542
+ * const r = await client.call({
1543
+ * to: '+14155552671',
1544
+ * task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
1545
+ * voice: 'maya',
1546
+ * max_duration: 3,
1547
+ * });
1548
+ */
1549
+ call(options: CallOptions): Promise<CallInitiatedResponse>;
1550
+ /**
1551
+ * Poll the status of an in-progress or completed call. Free — no payment.
1552
+ *
1553
+ * Returns Bland.ai's full call record: status, transcript, recording URL, etc.
1554
+ * Most fields populate only once the call ends.
1555
+ */
1556
+ getStatus(callId: string): Promise<CallStatusResponse>;
1557
+ private requestWithPayment;
1558
+ private handlePaymentAndRetry;
1559
+ private fetchWithTimeout;
1560
+ getWalletAddress(): string;
1561
+ getSpending(): Spending;
1562
+ }
1563
+
1419
1564
  /**
1420
1565
  * BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
1421
1566
  *
@@ -2193,4 +2338,4 @@ declare function validateTemperature(temperature?: number): void;
2193
2338
  */
2194
2339
  declare function validateTopP(topP?: number): void;
2195
2340
 
2196
- export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, type BlockRunAnthropicOptions, BlockrunError, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type StockMarket, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, XClient, type XClientOptions, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsOptions, type XMentionsResponse, type XSearchOptions, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesOptions, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XUserTweetsOptions, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
2341
+ export { APIError, AnthropicClient, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, type BlockRunAnthropicOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PaymentError, type PaymentLinks, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, type RoutingDecision, type RoutingProfile, type RoutingTier, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type SmartChatOptions, type SmartChatResponse, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type Spending, type SpendingReport, type StockMarket, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type XArticlesRisingResponse, type XAuthorAnalyticsResponse, XClient, type XClientOptions, type XCompareAuthorsResponse, type XFollower, type XFollowersResponse, type XFollowingsResponse, type XMentionsOptions, type XMentionsResponse, type XSearchOptions, type XSearchResponse, type XSearchSource, type XTrendingResponse, type XTweet, type XTweetLookupResponse, type XTweetRepliesOptions, type XTweetRepliesResponse, type XTweetThreadResponse, type XTweetsResponse, type XUser, type XUserInfoResponse, type XUserLookupResponse, type XUserTweetsOptions, type XVerifiedFollowersResponse, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatWalletCreatedMessage, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
package/dist/index.js CHANGED
@@ -2461,15 +2461,19 @@ var VideoClient = class {
2461
2461
  }
2462
2462
  };
2463
2463
 
2464
- // src/search.ts
2464
+ // src/voice.ts
2465
2465
  import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
2466
2466
  var DEFAULT_API_URL5 = "https://blockrun.ai/api";
2467
2467
  var DEFAULT_TIMEOUT5 = 6e4;
2468
- var SearchClient = class {
2468
+ var CALL_PRICE_USD = 0.54;
2469
+ var VALID_MODELS = /* @__PURE__ */ new Set(["base", "enhanced", "turbo"]);
2470
+ var VoiceClient = class {
2469
2471
  account;
2470
2472
  privateKey;
2471
2473
  apiUrl;
2472
2474
  timeout;
2475
+ sessionTotalUsd = 0;
2476
+ sessionCalls = 0;
2473
2477
  constructor(options = {}) {
2474
2478
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2475
2479
  const privateKey = options.privateKey || envKey;
@@ -2486,6 +2490,203 @@ var SearchClient = class {
2486
2490
  this.apiUrl = apiUrl.replace(/\/$/, "");
2487
2491
  this.timeout = options.timeout || DEFAULT_TIMEOUT5;
2488
2492
  }
2493
+ /**
2494
+ * Initiate an AI-powered outbound phone call.
2495
+ *
2496
+ * Pricing: $0.54 per call. Returns immediately once the call is queued —
2497
+ * poll getStatus() for transcript and recording.
2498
+ *
2499
+ * @example
2500
+ * const r = await client.call({
2501
+ * to: '+14155552671',
2502
+ * task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
2503
+ * voice: 'maya',
2504
+ * max_duration: 3,
2505
+ * });
2506
+ */
2507
+ async call(options) {
2508
+ if (!options.to || !options.to.trim()) {
2509
+ throw new Error("'to' phone number is required (E.164 format)");
2510
+ }
2511
+ const task = options.task?.trim() ?? "";
2512
+ if (task.length < 10) {
2513
+ throw new Error("'task' must be at least 10 characters");
2514
+ }
2515
+ if (task.length > 4e3) {
2516
+ throw new Error("'task' must be at most 4000 characters");
2517
+ }
2518
+ const maxDuration = options.max_duration ?? 5;
2519
+ if (maxDuration < 1 || maxDuration > 30) {
2520
+ throw new Error("max_duration must be between 1 and 30 minutes");
2521
+ }
2522
+ if (options.model && !VALID_MODELS.has(options.model)) {
2523
+ throw new Error("model must be 'base' | 'enhanced' | 'turbo'");
2524
+ }
2525
+ if (options.interruption_threshold !== void 0 && (options.interruption_threshold < 50 || options.interruption_threshold > 500)) {
2526
+ throw new Error("interruption_threshold must be between 50 and 500");
2527
+ }
2528
+ const body = {
2529
+ to: options.to.trim(),
2530
+ task,
2531
+ max_duration: maxDuration,
2532
+ language: options.language ?? "en-US"
2533
+ };
2534
+ if (options.from) body.from = options.from.trim();
2535
+ if (options.voice) body.voice = options.voice;
2536
+ if (options.first_sentence) body.first_sentence = options.first_sentence.trim();
2537
+ if (options.wait_for_greeting !== void 0) body.wait_for_greeting = options.wait_for_greeting;
2538
+ if (options.interruption_threshold !== void 0) body.interruption_threshold = options.interruption_threshold;
2539
+ if (options.model) body.model = options.model;
2540
+ return this.requestWithPayment("/v1/voice/call", body);
2541
+ }
2542
+ /**
2543
+ * Poll the status of an in-progress or completed call. Free — no payment.
2544
+ *
2545
+ * Returns Bland.ai's full call record: status, transcript, recording URL, etc.
2546
+ * Most fields populate only once the call ends.
2547
+ */
2548
+ async getStatus(callId) {
2549
+ if (!callId || !callId.trim()) {
2550
+ throw new Error("callId is required");
2551
+ }
2552
+ const url = `${this.apiUrl}/v1/voice/call/${encodeURIComponent(callId.trim())}`;
2553
+ const response = await this.fetchWithTimeout(url, {
2554
+ method: "GET",
2555
+ headers: { Accept: "application/json" }
2556
+ });
2557
+ if (response.status === 404) {
2558
+ throw new APIError(`Call not found: ${callId}`, 404, { call_id: callId });
2559
+ }
2560
+ if (!response.ok) {
2561
+ let errorBody;
2562
+ try {
2563
+ errorBody = await response.json();
2564
+ } catch {
2565
+ errorBody = { error: "Request failed" };
2566
+ }
2567
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2568
+ }
2569
+ return response.json();
2570
+ }
2571
+ async requestWithPayment(endpoint, body) {
2572
+ const url = `${this.apiUrl}${endpoint}`;
2573
+ const response = await this.fetchWithTimeout(url, {
2574
+ method: "POST",
2575
+ headers: { "Content-Type": "application/json" },
2576
+ body: JSON.stringify(body)
2577
+ });
2578
+ if (response.status === 402) {
2579
+ return this.handlePaymentAndRetry(url, body, response);
2580
+ }
2581
+ if (!response.ok) {
2582
+ let errorBody;
2583
+ try {
2584
+ errorBody = await response.json();
2585
+ } catch {
2586
+ errorBody = { error: "Request failed" };
2587
+ }
2588
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2589
+ }
2590
+ return response.json();
2591
+ }
2592
+ async handlePaymentAndRetry(url, body, response) {
2593
+ let paymentHeader = response.headers.get("payment-required");
2594
+ if (!paymentHeader) {
2595
+ try {
2596
+ const respBody = await response.json();
2597
+ if (respBody.x402 || respBody.accepts) {
2598
+ paymentHeader = btoa(JSON.stringify(respBody));
2599
+ }
2600
+ } catch {
2601
+ }
2602
+ }
2603
+ if (!paymentHeader) {
2604
+ throw new PaymentError("402 response but no payment requirements found");
2605
+ }
2606
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2607
+ const details = extractPaymentDetails(paymentRequired);
2608
+ const paymentPayload = await createPaymentPayload(
2609
+ this.privateKey,
2610
+ this.account.address,
2611
+ details.recipient,
2612
+ details.amount,
2613
+ details.network || "eip155:8453",
2614
+ {
2615
+ resourceUrl: details.resource?.url || `${this.apiUrl}/v1/voice/call`,
2616
+ resourceDescription: details.resource?.description || "BlockRun Voice Call",
2617
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2618
+ extra: details.extra
2619
+ }
2620
+ );
2621
+ const retryResponse = await this.fetchWithTimeout(url, {
2622
+ method: "POST",
2623
+ headers: {
2624
+ "Content-Type": "application/json",
2625
+ "PAYMENT-SIGNATURE": paymentPayload
2626
+ },
2627
+ body: JSON.stringify(body)
2628
+ });
2629
+ if (retryResponse.status === 402) {
2630
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
2631
+ }
2632
+ if (!retryResponse.ok) {
2633
+ let errorBody;
2634
+ try {
2635
+ errorBody = await retryResponse.json();
2636
+ } catch {
2637
+ errorBody = { error: "Request failed" };
2638
+ }
2639
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2640
+ }
2641
+ const data = await retryResponse.json();
2642
+ this.sessionCalls++;
2643
+ this.sessionTotalUsd += CALL_PRICE_USD;
2644
+ const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
2645
+ if (txHash) data.txHash = txHash;
2646
+ return data;
2647
+ }
2648
+ async fetchWithTimeout(url, options) {
2649
+ const controller = new AbortController();
2650
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2651
+ try {
2652
+ return await fetch(url, { ...options, signal: controller.signal });
2653
+ } finally {
2654
+ clearTimeout(timeoutId);
2655
+ }
2656
+ }
2657
+ getWalletAddress() {
2658
+ return this.account.address;
2659
+ }
2660
+ getSpending() {
2661
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
2662
+ }
2663
+ };
2664
+
2665
+ // src/search.ts
2666
+ import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
2667
+ var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2668
+ var DEFAULT_TIMEOUT6 = 6e4;
2669
+ var SearchClient = class {
2670
+ account;
2671
+ privateKey;
2672
+ apiUrl;
2673
+ timeout;
2674
+ constructor(options = {}) {
2675
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
2676
+ const privateKey = options.privateKey || envKey;
2677
+ if (!privateKey) {
2678
+ throw new Error(
2679
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
2680
+ );
2681
+ }
2682
+ validatePrivateKey(privateKey);
2683
+ this.privateKey = privateKey;
2684
+ this.account = privateKeyToAccount6(privateKey);
2685
+ const apiUrl = options.apiUrl || DEFAULT_API_URL6;
2686
+ validateApiUrl(apiUrl);
2687
+ this.apiUrl = apiUrl.replace(/\/$/, "");
2688
+ this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2689
+ }
2489
2690
  async search(query, options) {
2490
2691
  if (!query || query.length > 1e3) {
2491
2692
  throw new Error("query must be 1-1000 characters");
@@ -2598,9 +2799,9 @@ var SearchClient = class {
2598
2799
  };
2599
2800
 
2600
2801
  // src/x-client.ts
2601
- import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
2602
- var DEFAULT_API_URL6 = "https://blockrun.ai/api";
2603
- var DEFAULT_TIMEOUT6 = 6e4;
2802
+ import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
2803
+ var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2804
+ var DEFAULT_TIMEOUT7 = 6e4;
2604
2805
  var XClient = class _XClient {
2605
2806
  account;
2606
2807
  privateKey;
@@ -2623,11 +2824,11 @@ var XClient = class _XClient {
2623
2824
  }
2624
2825
  validatePrivateKey(privateKey);
2625
2826
  this.privateKey = privateKey;
2626
- this.account = privateKeyToAccount6(privateKey);
2627
- const apiUrl = options.apiUrl || DEFAULT_API_URL6;
2827
+ this.account = privateKeyToAccount7(privateKey);
2828
+ const apiUrl = options.apiUrl || DEFAULT_API_URL7;
2628
2829
  validateApiUrl(apiUrl);
2629
2830
  this.apiUrl = apiUrl.replace(/\/$/, "");
2630
- this.timeout = options.timeout || DEFAULT_TIMEOUT6;
2831
+ this.timeout = options.timeout || DEFAULT_TIMEOUT7;
2631
2832
  }
2632
2833
  // ───────── User endpoints ─────────
2633
2834
  userLookup(usernames) {
@@ -2805,9 +3006,9 @@ var XClient = class _XClient {
2805
3006
  };
2806
3007
 
2807
3008
  // src/price.ts
2808
- import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
2809
- var DEFAULT_API_URL7 = "https://blockrun.ai/api";
2810
- var DEFAULT_TIMEOUT7 = 3e4;
3009
+ import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
3010
+ var DEFAULT_API_URL8 = "https://blockrun.ai/api";
3011
+ var DEFAULT_TIMEOUT8 = 3e4;
2811
3012
  var PriceClient = class {
2812
3013
  account = null;
2813
3014
  privateKey = null;
@@ -2825,12 +3026,12 @@ var PriceClient = class {
2825
3026
  if (privateKey) {
2826
3027
  validatePrivateKey(privateKey);
2827
3028
  this.privateKey = privateKey;
2828
- this.account = privateKeyToAccount7(privateKey);
3029
+ this.account = privateKeyToAccount8(privateKey);
2829
3030
  }
2830
- const apiUrl = options.apiUrl || DEFAULT_API_URL7;
3031
+ const apiUrl = options.apiUrl || DEFAULT_API_URL8;
2831
3032
  validateApiUrl(apiUrl);
2832
3033
  this.apiUrl = apiUrl.replace(/\/$/, "");
2833
- this.timeout = options.timeout || DEFAULT_TIMEOUT7;
3034
+ this.timeout = options.timeout || DEFAULT_TIMEOUT8;
2834
3035
  }
2835
3036
  async price(category, symbol, options) {
2836
3037
  if (!symbol) throw new Error("symbol is required");
@@ -3009,7 +3210,7 @@ function buildUrl(base, query) {
3009
3210
  }
3010
3211
 
3011
3212
  // src/wallet.ts
3012
- import { privateKeyToAccount as privateKeyToAccount8, generatePrivateKey } from "viem/accounts";
3213
+ import { privateKeyToAccount as privateKeyToAccount9, generatePrivateKey } from "viem/accounts";
3013
3214
  import * as fs2 from "fs";
3014
3215
  import * as path2 from "path";
3015
3216
  import * as os2 from "os";
@@ -3019,7 +3220,7 @@ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
3019
3220
  var WALLET_FILE = path2.join(WALLET_DIR, ".session");
3020
3221
  function createWallet() {
3021
3222
  const privateKey = generatePrivateKey();
3022
- const account = privateKeyToAccount8(privateKey);
3223
+ const account = privateKeyToAccount9(privateKey);
3023
3224
  return {
3024
3225
  address: account.address,
3025
3226
  privateKey
@@ -3075,12 +3276,12 @@ function loadWallet() {
3075
3276
  function getOrCreateWallet() {
3076
3277
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3077
3278
  if (envKey) {
3078
- const account = privateKeyToAccount8(envKey);
3279
+ const account = privateKeyToAccount9(envKey);
3079
3280
  return { address: account.address, privateKey: envKey, isNew: false };
3080
3281
  }
3081
3282
  const fileKey = loadWallet();
3082
3283
  if (fileKey) {
3083
- const account = privateKeyToAccount8(fileKey);
3284
+ const account = privateKeyToAccount9(fileKey);
3084
3285
  return { address: account.address, privateKey: fileKey, isNew: false };
3085
3286
  }
3086
3287
  const { address, privateKey } = createWallet();
@@ -3090,11 +3291,11 @@ function getOrCreateWallet() {
3090
3291
  function getWalletAddress() {
3091
3292
  const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
3092
3293
  if (envKey) {
3093
- return privateKeyToAccount8(envKey).address;
3294
+ return privateKeyToAccount9(envKey).address;
3094
3295
  }
3095
3296
  const fileKey = loadWallet();
3096
3297
  if (fileKey) {
3097
- return privateKeyToAccount8(fileKey).address;
3298
+ return privateKeyToAccount9(fileKey).address;
3098
3299
  }
3099
3300
  return null;
3100
3301
  }
@@ -3274,7 +3475,7 @@ async function getOrCreateSolanaWallet() {
3274
3475
  // src/solana-client.ts
3275
3476
  var SOLANA_API_URL = "https://sol.blockrun.ai/api";
3276
3477
  var DEFAULT_MAX_TOKENS2 = 1024;
3277
- var DEFAULT_TIMEOUT8 = 6e4;
3478
+ var DEFAULT_TIMEOUT9 = 6e4;
3278
3479
  var SDK_VERSION2 = "0.3.0";
3279
3480
  var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
3280
3481
  var SolanaLLMClient = class {
@@ -3299,7 +3500,7 @@ var SolanaLLMClient = class {
3299
3500
  validateApiUrl(apiUrl);
3300
3501
  this.apiUrl = apiUrl.replace(/\/$/, "");
3301
3502
  this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
3302
- this.timeout = options.timeout || DEFAULT_TIMEOUT8;
3503
+ this.timeout = options.timeout || DEFAULT_TIMEOUT9;
3303
3504
  }
3304
3505
  /** Get Solana wallet address (public key in base58). */
3305
3506
  async getWalletAddress() {
@@ -4206,7 +4407,7 @@ var OpenAI = class {
4206
4407
  };
4207
4408
 
4208
4409
  // src/anthropic-compat.ts
4209
- import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
4410
+ import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
4210
4411
  var AnthropicClient = class {
4211
4412
  _client = null;
4212
4413
  _clientPromise = null;
@@ -4219,7 +4420,7 @@ var AnthropicClient = class {
4219
4420
  const key = options.privateKey ?? wallet.privateKey;
4220
4421
  validatePrivateKey(key);
4221
4422
  this._privateKey = key;
4222
- this._account = privateKeyToAccount9(this._privateKey);
4423
+ this._account = privateKeyToAccount10(this._privateKey);
4223
4424
  const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
4224
4425
  validateApiUrl(apiUrl);
4225
4426
  this._apiUrl = apiUrl.replace(/\/$/, "");
@@ -4333,6 +4534,7 @@ export {
4333
4534
  USDC_BASE_CONTRACT,
4334
4535
  USDC_SOLANA,
4335
4536
  VideoClient,
4537
+ VoiceClient,
4336
4538
  WALLET_DIR_PATH,
4337
4539
  WALLET_FILE_PATH,
4338
4540
  XClient,
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@blockrun/llm",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
- "description": "BlockRun SDK - Pay-per-request AI (LLM, Image, Video, Music) via x402 on Base and Solana",
5
+ "description": "BlockRun SDK - Pay-per-request AI (LLM, Image, Video, Music, Voice) via x402 on Base and Solana",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",