@blockrun/llm 2.1.1 → 2.3.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 +40 -0
- package/dist/index.cjs +234 -25
- package/dist/index.d.cts +164 -1
- package/dist/index.d.ts +164 -1
- package/dist/index.js +232 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -381,8 +381,48 @@ console.log(result.data[0].duration_seconds); // 8
|
|
|
381
381
|
const r2 = await client.generate('the subject turns and smiles', {
|
|
382
382
|
imageUrl: 'https://example.com/portrait.jpg',
|
|
383
383
|
});
|
|
384
|
+
|
|
385
|
+
// Token360 / Seedance options (silently ignored by xAI Grok video)
|
|
386
|
+
const r3 = await client.generate('aerial drone shot over a snowy mountain', {
|
|
387
|
+
model: 'bytedance/seedance-2.0-fast',
|
|
388
|
+
aspectRatio: '21:9',
|
|
389
|
+
resolution: '1080p',
|
|
390
|
+
generateAudio: true, // omit to use the model's default
|
|
391
|
+
seed: 42,
|
|
392
|
+
watermark: false,
|
|
393
|
+
returnLastFrame: true, // useful for clip chaining
|
|
394
|
+
});
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Voice Calls
|
|
398
|
+
|
|
399
|
+
`VoiceClient` wraps `POST /v1/voice/call` (paid, $0.54/call) and
|
|
400
|
+
`GET /v1/voice/call/{callId}` (free polling) — AI-powered outbound phone
|
|
401
|
+
calls powered by Bland.ai. The agent dials the recipient and runs a real-time
|
|
402
|
+
conversation based on your `task` instructions. US + Canada destinations.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { VoiceClient } from '@blockrun/llm';
|
|
406
|
+
|
|
407
|
+
const client = new VoiceClient();
|
|
408
|
+
|
|
409
|
+
// Initiate (paid $0.54)
|
|
410
|
+
const result = await client.call({
|
|
411
|
+
to: '+14155552671',
|
|
412
|
+
task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
|
|
413
|
+
voice: 'maya', // 'nat' | 'josh' | 'maya' | 'june' | 'paige' | 'derek' | 'florian'
|
|
414
|
+
max_duration: 5, // minutes (1–30)
|
|
415
|
+
});
|
|
416
|
+
console.log(result.call_id);
|
|
417
|
+
|
|
418
|
+
// Poll for transcript + recording (free)
|
|
419
|
+
const status = await client.getStatus(result.call_id);
|
|
420
|
+
console.log(status.status, status.recording_url);
|
|
384
421
|
```
|
|
385
422
|
|
|
423
|
+
Bring your own caller-ID: pass `from: '+14155552671'` (must be a BlockRun
|
|
424
|
+
phone number you own; buy via `/v1/phone/numbers/buy`).
|
|
425
|
+
|
|
386
426
|
### Standalone Search
|
|
387
427
|
|
|
388
428
|
`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,
|
|
@@ -2412,6 +2413,12 @@ var VideoClient = class {
|
|
|
2412
2413
|
};
|
|
2413
2414
|
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2414
2415
|
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2416
|
+
if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
2417
|
+
if (options?.resolution) body.resolution = options.resolution;
|
|
2418
|
+
if (options?.generateAudio !== void 0) body.generate_audio = options.generateAudio;
|
|
2419
|
+
if (options?.seed !== void 0) body.seed = options.seed;
|
|
2420
|
+
if (options?.watermark !== void 0) body.watermark = options.watermark;
|
|
2421
|
+
if (options?.returnLastFrame) body.return_last_frame = true;
|
|
2415
2422
|
const budgetMs = options?.budgetMs ?? DEFAULT_GENERATE_BUDGET_MS;
|
|
2416
2423
|
return this.submitAndPoll(body, budgetMs);
|
|
2417
2424
|
}
|
|
@@ -2557,15 +2564,19 @@ var VideoClient = class {
|
|
|
2557
2564
|
}
|
|
2558
2565
|
};
|
|
2559
2566
|
|
|
2560
|
-
// src/
|
|
2567
|
+
// src/voice.ts
|
|
2561
2568
|
var import_accounts6 = require("viem/accounts");
|
|
2562
2569
|
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2563
2570
|
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2564
|
-
var
|
|
2571
|
+
var CALL_PRICE_USD = 0.54;
|
|
2572
|
+
var VALID_MODELS = /* @__PURE__ */ new Set(["base", "enhanced", "turbo"]);
|
|
2573
|
+
var VoiceClient = class {
|
|
2565
2574
|
account;
|
|
2566
2575
|
privateKey;
|
|
2567
2576
|
apiUrl;
|
|
2568
2577
|
timeout;
|
|
2578
|
+
sessionTotalUsd = 0;
|
|
2579
|
+
sessionCalls = 0;
|
|
2569
2580
|
constructor(options = {}) {
|
|
2570
2581
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2571
2582
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2582,6 +2593,203 @@ var SearchClient = class {
|
|
|
2582
2593
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2583
2594
|
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2584
2595
|
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Initiate an AI-powered outbound phone call.
|
|
2598
|
+
*
|
|
2599
|
+
* Pricing: $0.54 per call. Returns immediately once the call is queued —
|
|
2600
|
+
* poll getStatus() for transcript and recording.
|
|
2601
|
+
*
|
|
2602
|
+
* @example
|
|
2603
|
+
* const r = await client.call({
|
|
2604
|
+
* to: '+14155552671',
|
|
2605
|
+
* task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
|
|
2606
|
+
* voice: 'maya',
|
|
2607
|
+
* max_duration: 3,
|
|
2608
|
+
* });
|
|
2609
|
+
*/
|
|
2610
|
+
async call(options) {
|
|
2611
|
+
if (!options.to || !options.to.trim()) {
|
|
2612
|
+
throw new Error("'to' phone number is required (E.164 format)");
|
|
2613
|
+
}
|
|
2614
|
+
const task = options.task?.trim() ?? "";
|
|
2615
|
+
if (task.length < 10) {
|
|
2616
|
+
throw new Error("'task' must be at least 10 characters");
|
|
2617
|
+
}
|
|
2618
|
+
if (task.length > 4e3) {
|
|
2619
|
+
throw new Error("'task' must be at most 4000 characters");
|
|
2620
|
+
}
|
|
2621
|
+
const maxDuration = options.max_duration ?? 5;
|
|
2622
|
+
if (maxDuration < 1 || maxDuration > 30) {
|
|
2623
|
+
throw new Error("max_duration must be between 1 and 30 minutes");
|
|
2624
|
+
}
|
|
2625
|
+
if (options.model && !VALID_MODELS.has(options.model)) {
|
|
2626
|
+
throw new Error("model must be 'base' | 'enhanced' | 'turbo'");
|
|
2627
|
+
}
|
|
2628
|
+
if (options.interruption_threshold !== void 0 && (options.interruption_threshold < 50 || options.interruption_threshold > 500)) {
|
|
2629
|
+
throw new Error("interruption_threshold must be between 50 and 500");
|
|
2630
|
+
}
|
|
2631
|
+
const body = {
|
|
2632
|
+
to: options.to.trim(),
|
|
2633
|
+
task,
|
|
2634
|
+
max_duration: maxDuration,
|
|
2635
|
+
language: options.language ?? "en-US"
|
|
2636
|
+
};
|
|
2637
|
+
if (options.from) body.from = options.from.trim();
|
|
2638
|
+
if (options.voice) body.voice = options.voice;
|
|
2639
|
+
if (options.first_sentence) body.first_sentence = options.first_sentence.trim();
|
|
2640
|
+
if (options.wait_for_greeting !== void 0) body.wait_for_greeting = options.wait_for_greeting;
|
|
2641
|
+
if (options.interruption_threshold !== void 0) body.interruption_threshold = options.interruption_threshold;
|
|
2642
|
+
if (options.model) body.model = options.model;
|
|
2643
|
+
return this.requestWithPayment("/v1/voice/call", body);
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Poll the status of an in-progress or completed call. Free — no payment.
|
|
2647
|
+
*
|
|
2648
|
+
* Returns Bland.ai's full call record: status, transcript, recording URL, etc.
|
|
2649
|
+
* Most fields populate only once the call ends.
|
|
2650
|
+
*/
|
|
2651
|
+
async getStatus(callId) {
|
|
2652
|
+
if (!callId || !callId.trim()) {
|
|
2653
|
+
throw new Error("callId is required");
|
|
2654
|
+
}
|
|
2655
|
+
const url = `${this.apiUrl}/v1/voice/call/${encodeURIComponent(callId.trim())}`;
|
|
2656
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2657
|
+
method: "GET",
|
|
2658
|
+
headers: { Accept: "application/json" }
|
|
2659
|
+
});
|
|
2660
|
+
if (response.status === 404) {
|
|
2661
|
+
throw new APIError(`Call not found: ${callId}`, 404, { call_id: callId });
|
|
2662
|
+
}
|
|
2663
|
+
if (!response.ok) {
|
|
2664
|
+
let errorBody;
|
|
2665
|
+
try {
|
|
2666
|
+
errorBody = await response.json();
|
|
2667
|
+
} catch {
|
|
2668
|
+
errorBody = { error: "Request failed" };
|
|
2669
|
+
}
|
|
2670
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
2671
|
+
}
|
|
2672
|
+
return response.json();
|
|
2673
|
+
}
|
|
2674
|
+
async requestWithPayment(endpoint, body) {
|
|
2675
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2676
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2677
|
+
method: "POST",
|
|
2678
|
+
headers: { "Content-Type": "application/json" },
|
|
2679
|
+
body: JSON.stringify(body)
|
|
2680
|
+
});
|
|
2681
|
+
if (response.status === 402) {
|
|
2682
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2683
|
+
}
|
|
2684
|
+
if (!response.ok) {
|
|
2685
|
+
let errorBody;
|
|
2686
|
+
try {
|
|
2687
|
+
errorBody = await response.json();
|
|
2688
|
+
} catch {
|
|
2689
|
+
errorBody = { error: "Request failed" };
|
|
2690
|
+
}
|
|
2691
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
2692
|
+
}
|
|
2693
|
+
return response.json();
|
|
2694
|
+
}
|
|
2695
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2696
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2697
|
+
if (!paymentHeader) {
|
|
2698
|
+
try {
|
|
2699
|
+
const respBody = await response.json();
|
|
2700
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2701
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2702
|
+
}
|
|
2703
|
+
} catch {
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
if (!paymentHeader) {
|
|
2707
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2708
|
+
}
|
|
2709
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2710
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2711
|
+
const paymentPayload = await createPaymentPayload(
|
|
2712
|
+
this.privateKey,
|
|
2713
|
+
this.account.address,
|
|
2714
|
+
details.recipient,
|
|
2715
|
+
details.amount,
|
|
2716
|
+
details.network || "eip155:8453",
|
|
2717
|
+
{
|
|
2718
|
+
resourceUrl: details.resource?.url || `${this.apiUrl}/v1/voice/call`,
|
|
2719
|
+
resourceDescription: details.resource?.description || "BlockRun Voice Call",
|
|
2720
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2721
|
+
extra: details.extra
|
|
2722
|
+
}
|
|
2723
|
+
);
|
|
2724
|
+
const retryResponse = await this.fetchWithTimeout(url, {
|
|
2725
|
+
method: "POST",
|
|
2726
|
+
headers: {
|
|
2727
|
+
"Content-Type": "application/json",
|
|
2728
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2729
|
+
},
|
|
2730
|
+
body: JSON.stringify(body)
|
|
2731
|
+
});
|
|
2732
|
+
if (retryResponse.status === 402) {
|
|
2733
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2734
|
+
}
|
|
2735
|
+
if (!retryResponse.ok) {
|
|
2736
|
+
let errorBody;
|
|
2737
|
+
try {
|
|
2738
|
+
errorBody = await retryResponse.json();
|
|
2739
|
+
} catch {
|
|
2740
|
+
errorBody = { error: "Request failed" };
|
|
2741
|
+
}
|
|
2742
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
2743
|
+
}
|
|
2744
|
+
const data = await retryResponse.json();
|
|
2745
|
+
this.sessionCalls++;
|
|
2746
|
+
this.sessionTotalUsd += CALL_PRICE_USD;
|
|
2747
|
+
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
2748
|
+
if (txHash) data.txHash = txHash;
|
|
2749
|
+
return data;
|
|
2750
|
+
}
|
|
2751
|
+
async fetchWithTimeout(url, options) {
|
|
2752
|
+
const controller = new AbortController();
|
|
2753
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2754
|
+
try {
|
|
2755
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2756
|
+
} finally {
|
|
2757
|
+
clearTimeout(timeoutId);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
getWalletAddress() {
|
|
2761
|
+
return this.account.address;
|
|
2762
|
+
}
|
|
2763
|
+
getSpending() {
|
|
2764
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
2767
|
+
|
|
2768
|
+
// src/search.ts
|
|
2769
|
+
var import_accounts7 = require("viem/accounts");
|
|
2770
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2771
|
+
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2772
|
+
var SearchClient = class {
|
|
2773
|
+
account;
|
|
2774
|
+
privateKey;
|
|
2775
|
+
apiUrl;
|
|
2776
|
+
timeout;
|
|
2777
|
+
constructor(options = {}) {
|
|
2778
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2779
|
+
const privateKey = options.privateKey || envKey;
|
|
2780
|
+
if (!privateKey) {
|
|
2781
|
+
throw new Error(
|
|
2782
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2783
|
+
);
|
|
2784
|
+
}
|
|
2785
|
+
validatePrivateKey(privateKey);
|
|
2786
|
+
this.privateKey = privateKey;
|
|
2787
|
+
this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
|
|
2788
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2789
|
+
validateApiUrl(apiUrl);
|
|
2790
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2791
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2792
|
+
}
|
|
2585
2793
|
async search(query, options) {
|
|
2586
2794
|
if (!query || query.length > 1e3) {
|
|
2587
2795
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2694,9 +2902,9 @@ var SearchClient = class {
|
|
|
2694
2902
|
};
|
|
2695
2903
|
|
|
2696
2904
|
// src/x-client.ts
|
|
2697
|
-
var
|
|
2698
|
-
var
|
|
2699
|
-
var
|
|
2905
|
+
var import_accounts8 = require("viem/accounts");
|
|
2906
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2907
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2700
2908
|
var XClient = class _XClient {
|
|
2701
2909
|
account;
|
|
2702
2910
|
privateKey;
|
|
@@ -2719,11 +2927,11 @@ var XClient = class _XClient {
|
|
|
2719
2927
|
}
|
|
2720
2928
|
validatePrivateKey(privateKey);
|
|
2721
2929
|
this.privateKey = privateKey;
|
|
2722
|
-
this.account = (0,
|
|
2723
|
-
const apiUrl = options.apiUrl ||
|
|
2930
|
+
this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
2931
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2724
2932
|
validateApiUrl(apiUrl);
|
|
2725
2933
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2726
|
-
this.timeout = options.timeout ||
|
|
2934
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2727
2935
|
}
|
|
2728
2936
|
// ───────── User endpoints ─────────
|
|
2729
2937
|
userLookup(usernames) {
|
|
@@ -2901,9 +3109,9 @@ var XClient = class _XClient {
|
|
|
2901
3109
|
};
|
|
2902
3110
|
|
|
2903
3111
|
// src/price.ts
|
|
2904
|
-
var
|
|
2905
|
-
var
|
|
2906
|
-
var
|
|
3112
|
+
var import_accounts9 = require("viem/accounts");
|
|
3113
|
+
var DEFAULT_API_URL8 = "https://blockrun.ai/api";
|
|
3114
|
+
var DEFAULT_TIMEOUT8 = 3e4;
|
|
2907
3115
|
var PriceClient = class {
|
|
2908
3116
|
account = null;
|
|
2909
3117
|
privateKey = null;
|
|
@@ -2921,12 +3129,12 @@ var PriceClient = class {
|
|
|
2921
3129
|
if (privateKey) {
|
|
2922
3130
|
validatePrivateKey(privateKey);
|
|
2923
3131
|
this.privateKey = privateKey;
|
|
2924
|
-
this.account = (0,
|
|
3132
|
+
this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
|
|
2925
3133
|
}
|
|
2926
|
-
const apiUrl = options.apiUrl ||
|
|
3134
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL8;
|
|
2927
3135
|
validateApiUrl(apiUrl);
|
|
2928
3136
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2929
|
-
this.timeout = options.timeout ||
|
|
3137
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
2930
3138
|
}
|
|
2931
3139
|
async price(category, symbol, options) {
|
|
2932
3140
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -3105,7 +3313,7 @@ function buildUrl(base, query) {
|
|
|
3105
3313
|
}
|
|
3106
3314
|
|
|
3107
3315
|
// src/wallet.ts
|
|
3108
|
-
var
|
|
3316
|
+
var import_accounts10 = require("viem/accounts");
|
|
3109
3317
|
var fs2 = __toESM(require("fs"), 1);
|
|
3110
3318
|
var path2 = __toESM(require("path"), 1);
|
|
3111
3319
|
var os2 = __toESM(require("os"), 1);
|
|
@@ -3114,8 +3322,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
3114
3322
|
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
3115
3323
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
3116
3324
|
function createWallet() {
|
|
3117
|
-
const privateKey = (0,
|
|
3118
|
-
const account = (0,
|
|
3325
|
+
const privateKey = (0, import_accounts10.generatePrivateKey)();
|
|
3326
|
+
const account = (0, import_accounts10.privateKeyToAccount)(privateKey);
|
|
3119
3327
|
return {
|
|
3120
3328
|
address: account.address,
|
|
3121
3329
|
privateKey
|
|
@@ -3171,12 +3379,12 @@ function loadWallet() {
|
|
|
3171
3379
|
function getOrCreateWallet() {
|
|
3172
3380
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3173
3381
|
if (envKey) {
|
|
3174
|
-
const account = (0,
|
|
3382
|
+
const account = (0, import_accounts10.privateKeyToAccount)(envKey);
|
|
3175
3383
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
3176
3384
|
}
|
|
3177
3385
|
const fileKey = loadWallet();
|
|
3178
3386
|
if (fileKey) {
|
|
3179
|
-
const account = (0,
|
|
3387
|
+
const account = (0, import_accounts10.privateKeyToAccount)(fileKey);
|
|
3180
3388
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
3181
3389
|
}
|
|
3182
3390
|
const { address, privateKey } = createWallet();
|
|
@@ -3186,11 +3394,11 @@ function getOrCreateWallet() {
|
|
|
3186
3394
|
function getWalletAddress() {
|
|
3187
3395
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3188
3396
|
if (envKey) {
|
|
3189
|
-
return (0,
|
|
3397
|
+
return (0, import_accounts10.privateKeyToAccount)(envKey).address;
|
|
3190
3398
|
}
|
|
3191
3399
|
const fileKey = loadWallet();
|
|
3192
3400
|
if (fileKey) {
|
|
3193
|
-
return (0,
|
|
3401
|
+
return (0, import_accounts10.privateKeyToAccount)(fileKey).address;
|
|
3194
3402
|
}
|
|
3195
3403
|
return null;
|
|
3196
3404
|
}
|
|
@@ -3370,7 +3578,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3370
3578
|
// src/solana-client.ts
|
|
3371
3579
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3372
3580
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3373
|
-
var
|
|
3581
|
+
var DEFAULT_TIMEOUT9 = 6e4;
|
|
3374
3582
|
var SDK_VERSION2 = "0.3.0";
|
|
3375
3583
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3376
3584
|
var SolanaLLMClient = class {
|
|
@@ -3395,7 +3603,7 @@ var SolanaLLMClient = class {
|
|
|
3395
3603
|
validateApiUrl(apiUrl);
|
|
3396
3604
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3397
3605
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3398
|
-
this.timeout = options.timeout ||
|
|
3606
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT9;
|
|
3399
3607
|
}
|
|
3400
3608
|
/** Get Solana wallet address (public key in base58). */
|
|
3401
3609
|
async getWalletAddress() {
|
|
@@ -4302,7 +4510,7 @@ var OpenAI = class {
|
|
|
4302
4510
|
};
|
|
4303
4511
|
|
|
4304
4512
|
// src/anthropic-compat.ts
|
|
4305
|
-
var
|
|
4513
|
+
var import_accounts11 = require("viem/accounts");
|
|
4306
4514
|
var AnthropicClient = class {
|
|
4307
4515
|
_client = null;
|
|
4308
4516
|
_clientPromise = null;
|
|
@@ -4315,7 +4523,7 @@ var AnthropicClient = class {
|
|
|
4315
4523
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4316
4524
|
validatePrivateKey(key);
|
|
4317
4525
|
this._privateKey = key;
|
|
4318
|
-
this._account = (0,
|
|
4526
|
+
this._account = (0, import_accounts11.privateKeyToAccount)(this._privateKey);
|
|
4319
4527
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4320
4528
|
validateApiUrl(apiUrl);
|
|
4321
4529
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4430,6 +4638,7 @@ var AnthropicClient = class {
|
|
|
4430
4638
|
USDC_BASE_CONTRACT,
|
|
4431
4639
|
USDC_SOLANA,
|
|
4432
4640
|
VideoClient,
|
|
4641
|
+
VoiceClient,
|
|
4433
4642
|
WALLET_DIR_PATH,
|
|
4434
4643
|
WALLET_FILE_PATH,
|
|
4435
4644
|
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;
|
|
@@ -428,6 +496,24 @@ interface VideoGenerateOptions {
|
|
|
428
496
|
imageUrl?: string;
|
|
429
497
|
/** Duration to bill for (defaults to model's default duration) */
|
|
430
498
|
durationSeconds?: number;
|
|
499
|
+
/** Output aspect ratio. Token360 / Seedance only — silently ignored by xAI Grok. */
|
|
500
|
+
aspectRatio?: "adaptive" | "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | "9:21";
|
|
501
|
+
/** Output resolution. Token360 / Seedance only. */
|
|
502
|
+
resolution?: "360p" | "480p" | "540p" | "720p" | "1080p" | "1K" | "2K" | "4K";
|
|
503
|
+
/**
|
|
504
|
+
* Whether the model should produce an audio track. Token360 / Seedance only.
|
|
505
|
+
* When omitted, the gateway does not send the flag — Token360 then applies
|
|
506
|
+
* its own upstream default (typically off, occasionally on depending on
|
|
507
|
+
* model variant / prompt). Audio generation is usually a paid surcharge,
|
|
508
|
+
* so callers should expose this as a visible toggle to their users.
|
|
509
|
+
*/
|
|
510
|
+
generateAudio?: boolean;
|
|
511
|
+
/** Reproducibility seed when supported by the model. */
|
|
512
|
+
seed?: number;
|
|
513
|
+
/** Embed the upstream watermark on the output. Defaults to false at the gateway. */
|
|
514
|
+
watermark?: boolean;
|
|
515
|
+
/** Return the last frame as an image alongside the clip — useful for chaining. */
|
|
516
|
+
returnLastFrame?: boolean;
|
|
431
517
|
}
|
|
432
518
|
interface SearchOptions {
|
|
433
519
|
/** Source types to search (e.g. ["web", "x", "news"]) */
|
|
@@ -1416,6 +1502,83 @@ declare class VideoClient {
|
|
|
1416
1502
|
getSpending(): Spending;
|
|
1417
1503
|
}
|
|
1418
1504
|
|
|
1505
|
+
/**
|
|
1506
|
+
* BlockRun Voice Call Client - AI-powered outbound phone calls via x402 micropayments.
|
|
1507
|
+
*
|
|
1508
|
+
* The AI agent calls a phone number (E.164) and conducts a real-time conversation
|
|
1509
|
+
* based on your 'task' instructions. STT, LLM, and TTS are handled upstream by
|
|
1510
|
+
* Bland.ai; BlockRun handles billing through x402.
|
|
1511
|
+
*
|
|
1512
|
+
* SECURITY NOTE - Private Key Handling:
|
|
1513
|
+
* Your private key NEVER leaves your machine. Here's what happens:
|
|
1514
|
+
* 1. Key stays local - only used to sign an EIP-712 typed data message
|
|
1515
|
+
* 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
|
|
1516
|
+
* 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
|
|
1517
|
+
*
|
|
1518
|
+
* Usage:
|
|
1519
|
+
* import { VoiceClient } from '@blockrun/llm';
|
|
1520
|
+
*
|
|
1521
|
+
* const client = new VoiceClient({ privateKey: '0x...' });
|
|
1522
|
+
*
|
|
1523
|
+
* // Initiate a call (paid, $0.54)
|
|
1524
|
+
* const result = await client.call({
|
|
1525
|
+
* to: '+14155552671',
|
|
1526
|
+
* task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
|
|
1527
|
+
* max_duration: 5,
|
|
1528
|
+
* });
|
|
1529
|
+
* console.log(result.call_id);
|
|
1530
|
+
*
|
|
1531
|
+
* // Poll status, transcript, recording (free)
|
|
1532
|
+
* const status = await client.getStatus(result.call_id);
|
|
1533
|
+
* console.log(status);
|
|
1534
|
+
*/
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* BlockRun Voice Call Client.
|
|
1538
|
+
*
|
|
1539
|
+
* Initiates AI-powered outbound phone calls with automatic x402 micropayments
|
|
1540
|
+
* on Base chain.
|
|
1541
|
+
*
|
|
1542
|
+
* Pricing: $0.54 per call (regardless of duration up to max_duration).
|
|
1543
|
+
* Status polling is free.
|
|
1544
|
+
*/
|
|
1545
|
+
declare class VoiceClient {
|
|
1546
|
+
private account;
|
|
1547
|
+
private privateKey;
|
|
1548
|
+
private apiUrl;
|
|
1549
|
+
private timeout;
|
|
1550
|
+
private sessionTotalUsd;
|
|
1551
|
+
private sessionCalls;
|
|
1552
|
+
constructor(options?: VoiceClientOptions);
|
|
1553
|
+
/**
|
|
1554
|
+
* Initiate an AI-powered outbound phone call.
|
|
1555
|
+
*
|
|
1556
|
+
* Pricing: $0.54 per call. Returns immediately once the call is queued —
|
|
1557
|
+
* poll getStatus() for transcript and recording.
|
|
1558
|
+
*
|
|
1559
|
+
* @example
|
|
1560
|
+
* const r = await client.call({
|
|
1561
|
+
* to: '+14155552671',
|
|
1562
|
+
* task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
|
|
1563
|
+
* voice: 'maya',
|
|
1564
|
+
* max_duration: 3,
|
|
1565
|
+
* });
|
|
1566
|
+
*/
|
|
1567
|
+
call(options: CallOptions): Promise<CallInitiatedResponse>;
|
|
1568
|
+
/**
|
|
1569
|
+
* Poll the status of an in-progress or completed call. Free — no payment.
|
|
1570
|
+
*
|
|
1571
|
+
* Returns Bland.ai's full call record: status, transcript, recording URL, etc.
|
|
1572
|
+
* Most fields populate only once the call ends.
|
|
1573
|
+
*/
|
|
1574
|
+
getStatus(callId: string): Promise<CallStatusResponse>;
|
|
1575
|
+
private requestWithPayment;
|
|
1576
|
+
private handlePaymentAndRetry;
|
|
1577
|
+
private fetchWithTimeout;
|
|
1578
|
+
getWalletAddress(): string;
|
|
1579
|
+
getSpending(): Spending;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1419
1582
|
/**
|
|
1420
1583
|
* BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
|
|
1421
1584
|
*
|
|
@@ -2193,4 +2356,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
2193
2356
|
*/
|
|
2194
2357
|
declare function validateTopP(topP?: number): void;
|
|
2195
2358
|
|
|
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 };
|
|
2359
|
+
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;
|
|
@@ -428,6 +496,24 @@ interface VideoGenerateOptions {
|
|
|
428
496
|
imageUrl?: string;
|
|
429
497
|
/** Duration to bill for (defaults to model's default duration) */
|
|
430
498
|
durationSeconds?: number;
|
|
499
|
+
/** Output aspect ratio. Token360 / Seedance only — silently ignored by xAI Grok. */
|
|
500
|
+
aspectRatio?: "adaptive" | "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | "9:21";
|
|
501
|
+
/** Output resolution. Token360 / Seedance only. */
|
|
502
|
+
resolution?: "360p" | "480p" | "540p" | "720p" | "1080p" | "1K" | "2K" | "4K";
|
|
503
|
+
/**
|
|
504
|
+
* Whether the model should produce an audio track. Token360 / Seedance only.
|
|
505
|
+
* When omitted, the gateway does not send the flag — Token360 then applies
|
|
506
|
+
* its own upstream default (typically off, occasionally on depending on
|
|
507
|
+
* model variant / prompt). Audio generation is usually a paid surcharge,
|
|
508
|
+
* so callers should expose this as a visible toggle to their users.
|
|
509
|
+
*/
|
|
510
|
+
generateAudio?: boolean;
|
|
511
|
+
/** Reproducibility seed when supported by the model. */
|
|
512
|
+
seed?: number;
|
|
513
|
+
/** Embed the upstream watermark on the output. Defaults to false at the gateway. */
|
|
514
|
+
watermark?: boolean;
|
|
515
|
+
/** Return the last frame as an image alongside the clip — useful for chaining. */
|
|
516
|
+
returnLastFrame?: boolean;
|
|
431
517
|
}
|
|
432
518
|
interface SearchOptions {
|
|
433
519
|
/** Source types to search (e.g. ["web", "x", "news"]) */
|
|
@@ -1416,6 +1502,83 @@ declare class VideoClient {
|
|
|
1416
1502
|
getSpending(): Spending;
|
|
1417
1503
|
}
|
|
1418
1504
|
|
|
1505
|
+
/**
|
|
1506
|
+
* BlockRun Voice Call Client - AI-powered outbound phone calls via x402 micropayments.
|
|
1507
|
+
*
|
|
1508
|
+
* The AI agent calls a phone number (E.164) and conducts a real-time conversation
|
|
1509
|
+
* based on your 'task' instructions. STT, LLM, and TTS are handled upstream by
|
|
1510
|
+
* Bland.ai; BlockRun handles billing through x402.
|
|
1511
|
+
*
|
|
1512
|
+
* SECURITY NOTE - Private Key Handling:
|
|
1513
|
+
* Your private key NEVER leaves your machine. Here's what happens:
|
|
1514
|
+
* 1. Key stays local - only used to sign an EIP-712 typed data message
|
|
1515
|
+
* 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
|
|
1516
|
+
* 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
|
|
1517
|
+
*
|
|
1518
|
+
* Usage:
|
|
1519
|
+
* import { VoiceClient } from '@blockrun/llm';
|
|
1520
|
+
*
|
|
1521
|
+
* const client = new VoiceClient({ privateKey: '0x...' });
|
|
1522
|
+
*
|
|
1523
|
+
* // Initiate a call (paid, $0.54)
|
|
1524
|
+
* const result = await client.call({
|
|
1525
|
+
* to: '+14155552671',
|
|
1526
|
+
* task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
|
|
1527
|
+
* max_duration: 5,
|
|
1528
|
+
* });
|
|
1529
|
+
* console.log(result.call_id);
|
|
1530
|
+
*
|
|
1531
|
+
* // Poll status, transcript, recording (free)
|
|
1532
|
+
* const status = await client.getStatus(result.call_id);
|
|
1533
|
+
* console.log(status);
|
|
1534
|
+
*/
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* BlockRun Voice Call Client.
|
|
1538
|
+
*
|
|
1539
|
+
* Initiates AI-powered outbound phone calls with automatic x402 micropayments
|
|
1540
|
+
* on Base chain.
|
|
1541
|
+
*
|
|
1542
|
+
* Pricing: $0.54 per call (regardless of duration up to max_duration).
|
|
1543
|
+
* Status polling is free.
|
|
1544
|
+
*/
|
|
1545
|
+
declare class VoiceClient {
|
|
1546
|
+
private account;
|
|
1547
|
+
private privateKey;
|
|
1548
|
+
private apiUrl;
|
|
1549
|
+
private timeout;
|
|
1550
|
+
private sessionTotalUsd;
|
|
1551
|
+
private sessionCalls;
|
|
1552
|
+
constructor(options?: VoiceClientOptions);
|
|
1553
|
+
/**
|
|
1554
|
+
* Initiate an AI-powered outbound phone call.
|
|
1555
|
+
*
|
|
1556
|
+
* Pricing: $0.54 per call. Returns immediately once the call is queued —
|
|
1557
|
+
* poll getStatus() for transcript and recording.
|
|
1558
|
+
*
|
|
1559
|
+
* @example
|
|
1560
|
+
* const r = await client.call({
|
|
1561
|
+
* to: '+14155552671',
|
|
1562
|
+
* task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
|
|
1563
|
+
* voice: 'maya',
|
|
1564
|
+
* max_duration: 3,
|
|
1565
|
+
* });
|
|
1566
|
+
*/
|
|
1567
|
+
call(options: CallOptions): Promise<CallInitiatedResponse>;
|
|
1568
|
+
/**
|
|
1569
|
+
* Poll the status of an in-progress or completed call. Free — no payment.
|
|
1570
|
+
*
|
|
1571
|
+
* Returns Bland.ai's full call record: status, transcript, recording URL, etc.
|
|
1572
|
+
* Most fields populate only once the call ends.
|
|
1573
|
+
*/
|
|
1574
|
+
getStatus(callId: string): Promise<CallStatusResponse>;
|
|
1575
|
+
private requestWithPayment;
|
|
1576
|
+
private handlePaymentAndRetry;
|
|
1577
|
+
private fetchWithTimeout;
|
|
1578
|
+
getWalletAddress(): string;
|
|
1579
|
+
getSpending(): Spending;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1419
1582
|
/**
|
|
1420
1583
|
* BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
|
|
1421
1584
|
*
|
|
@@ -2193,4 +2356,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
2193
2356
|
*/
|
|
2194
2357
|
declare function validateTopP(topP?: number): void;
|
|
2195
2358
|
|
|
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 };
|
|
2359
|
+
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
|
@@ -2316,6 +2316,12 @@ var VideoClient = class {
|
|
|
2316
2316
|
};
|
|
2317
2317
|
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2318
2318
|
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2319
|
+
if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
2320
|
+
if (options?.resolution) body.resolution = options.resolution;
|
|
2321
|
+
if (options?.generateAudio !== void 0) body.generate_audio = options.generateAudio;
|
|
2322
|
+
if (options?.seed !== void 0) body.seed = options.seed;
|
|
2323
|
+
if (options?.watermark !== void 0) body.watermark = options.watermark;
|
|
2324
|
+
if (options?.returnLastFrame) body.return_last_frame = true;
|
|
2319
2325
|
const budgetMs = options?.budgetMs ?? DEFAULT_GENERATE_BUDGET_MS;
|
|
2320
2326
|
return this.submitAndPoll(body, budgetMs);
|
|
2321
2327
|
}
|
|
@@ -2461,15 +2467,19 @@ var VideoClient = class {
|
|
|
2461
2467
|
}
|
|
2462
2468
|
};
|
|
2463
2469
|
|
|
2464
|
-
// src/
|
|
2470
|
+
// src/voice.ts
|
|
2465
2471
|
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
2466
2472
|
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2467
2473
|
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2468
|
-
var
|
|
2474
|
+
var CALL_PRICE_USD = 0.54;
|
|
2475
|
+
var VALID_MODELS = /* @__PURE__ */ new Set(["base", "enhanced", "turbo"]);
|
|
2476
|
+
var VoiceClient = class {
|
|
2469
2477
|
account;
|
|
2470
2478
|
privateKey;
|
|
2471
2479
|
apiUrl;
|
|
2472
2480
|
timeout;
|
|
2481
|
+
sessionTotalUsd = 0;
|
|
2482
|
+
sessionCalls = 0;
|
|
2473
2483
|
constructor(options = {}) {
|
|
2474
2484
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2475
2485
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2486,6 +2496,203 @@ var SearchClient = class {
|
|
|
2486
2496
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2487
2497
|
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2488
2498
|
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Initiate an AI-powered outbound phone call.
|
|
2501
|
+
*
|
|
2502
|
+
* Pricing: $0.54 per call. Returns immediately once the call is queued —
|
|
2503
|
+
* poll getStatus() for transcript and recording.
|
|
2504
|
+
*
|
|
2505
|
+
* @example
|
|
2506
|
+
* const r = await client.call({
|
|
2507
|
+
* to: '+14155552671',
|
|
2508
|
+
* task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
|
|
2509
|
+
* voice: 'maya',
|
|
2510
|
+
* max_duration: 3,
|
|
2511
|
+
* });
|
|
2512
|
+
*/
|
|
2513
|
+
async call(options) {
|
|
2514
|
+
if (!options.to || !options.to.trim()) {
|
|
2515
|
+
throw new Error("'to' phone number is required (E.164 format)");
|
|
2516
|
+
}
|
|
2517
|
+
const task = options.task?.trim() ?? "";
|
|
2518
|
+
if (task.length < 10) {
|
|
2519
|
+
throw new Error("'task' must be at least 10 characters");
|
|
2520
|
+
}
|
|
2521
|
+
if (task.length > 4e3) {
|
|
2522
|
+
throw new Error("'task' must be at most 4000 characters");
|
|
2523
|
+
}
|
|
2524
|
+
const maxDuration = options.max_duration ?? 5;
|
|
2525
|
+
if (maxDuration < 1 || maxDuration > 30) {
|
|
2526
|
+
throw new Error("max_duration must be between 1 and 30 minutes");
|
|
2527
|
+
}
|
|
2528
|
+
if (options.model && !VALID_MODELS.has(options.model)) {
|
|
2529
|
+
throw new Error("model must be 'base' | 'enhanced' | 'turbo'");
|
|
2530
|
+
}
|
|
2531
|
+
if (options.interruption_threshold !== void 0 && (options.interruption_threshold < 50 || options.interruption_threshold > 500)) {
|
|
2532
|
+
throw new Error("interruption_threshold must be between 50 and 500");
|
|
2533
|
+
}
|
|
2534
|
+
const body = {
|
|
2535
|
+
to: options.to.trim(),
|
|
2536
|
+
task,
|
|
2537
|
+
max_duration: maxDuration,
|
|
2538
|
+
language: options.language ?? "en-US"
|
|
2539
|
+
};
|
|
2540
|
+
if (options.from) body.from = options.from.trim();
|
|
2541
|
+
if (options.voice) body.voice = options.voice;
|
|
2542
|
+
if (options.first_sentence) body.first_sentence = options.first_sentence.trim();
|
|
2543
|
+
if (options.wait_for_greeting !== void 0) body.wait_for_greeting = options.wait_for_greeting;
|
|
2544
|
+
if (options.interruption_threshold !== void 0) body.interruption_threshold = options.interruption_threshold;
|
|
2545
|
+
if (options.model) body.model = options.model;
|
|
2546
|
+
return this.requestWithPayment("/v1/voice/call", body);
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Poll the status of an in-progress or completed call. Free — no payment.
|
|
2550
|
+
*
|
|
2551
|
+
* Returns Bland.ai's full call record: status, transcript, recording URL, etc.
|
|
2552
|
+
* Most fields populate only once the call ends.
|
|
2553
|
+
*/
|
|
2554
|
+
async getStatus(callId) {
|
|
2555
|
+
if (!callId || !callId.trim()) {
|
|
2556
|
+
throw new Error("callId is required");
|
|
2557
|
+
}
|
|
2558
|
+
const url = `${this.apiUrl}/v1/voice/call/${encodeURIComponent(callId.trim())}`;
|
|
2559
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2560
|
+
method: "GET",
|
|
2561
|
+
headers: { Accept: "application/json" }
|
|
2562
|
+
});
|
|
2563
|
+
if (response.status === 404) {
|
|
2564
|
+
throw new APIError(`Call not found: ${callId}`, 404, { call_id: callId });
|
|
2565
|
+
}
|
|
2566
|
+
if (!response.ok) {
|
|
2567
|
+
let errorBody;
|
|
2568
|
+
try {
|
|
2569
|
+
errorBody = await response.json();
|
|
2570
|
+
} catch {
|
|
2571
|
+
errorBody = { error: "Request failed" };
|
|
2572
|
+
}
|
|
2573
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
2574
|
+
}
|
|
2575
|
+
return response.json();
|
|
2576
|
+
}
|
|
2577
|
+
async requestWithPayment(endpoint, body) {
|
|
2578
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
2579
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2580
|
+
method: "POST",
|
|
2581
|
+
headers: { "Content-Type": "application/json" },
|
|
2582
|
+
body: JSON.stringify(body)
|
|
2583
|
+
});
|
|
2584
|
+
if (response.status === 402) {
|
|
2585
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2586
|
+
}
|
|
2587
|
+
if (!response.ok) {
|
|
2588
|
+
let errorBody;
|
|
2589
|
+
try {
|
|
2590
|
+
errorBody = await response.json();
|
|
2591
|
+
} catch {
|
|
2592
|
+
errorBody = { error: "Request failed" };
|
|
2593
|
+
}
|
|
2594
|
+
throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
|
|
2595
|
+
}
|
|
2596
|
+
return response.json();
|
|
2597
|
+
}
|
|
2598
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2599
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2600
|
+
if (!paymentHeader) {
|
|
2601
|
+
try {
|
|
2602
|
+
const respBody = await response.json();
|
|
2603
|
+
if (respBody.x402 || respBody.accepts) {
|
|
2604
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
2605
|
+
}
|
|
2606
|
+
} catch {
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
if (!paymentHeader) {
|
|
2610
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2611
|
+
}
|
|
2612
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2613
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2614
|
+
const paymentPayload = await createPaymentPayload(
|
|
2615
|
+
this.privateKey,
|
|
2616
|
+
this.account.address,
|
|
2617
|
+
details.recipient,
|
|
2618
|
+
details.amount,
|
|
2619
|
+
details.network || "eip155:8453",
|
|
2620
|
+
{
|
|
2621
|
+
resourceUrl: details.resource?.url || `${this.apiUrl}/v1/voice/call`,
|
|
2622
|
+
resourceDescription: details.resource?.description || "BlockRun Voice Call",
|
|
2623
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2624
|
+
extra: details.extra
|
|
2625
|
+
}
|
|
2626
|
+
);
|
|
2627
|
+
const retryResponse = await this.fetchWithTimeout(url, {
|
|
2628
|
+
method: "POST",
|
|
2629
|
+
headers: {
|
|
2630
|
+
"Content-Type": "application/json",
|
|
2631
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2632
|
+
},
|
|
2633
|
+
body: JSON.stringify(body)
|
|
2634
|
+
});
|
|
2635
|
+
if (retryResponse.status === 402) {
|
|
2636
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2637
|
+
}
|
|
2638
|
+
if (!retryResponse.ok) {
|
|
2639
|
+
let errorBody;
|
|
2640
|
+
try {
|
|
2641
|
+
errorBody = await retryResponse.json();
|
|
2642
|
+
} catch {
|
|
2643
|
+
errorBody = { error: "Request failed" };
|
|
2644
|
+
}
|
|
2645
|
+
throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
|
|
2646
|
+
}
|
|
2647
|
+
const data = await retryResponse.json();
|
|
2648
|
+
this.sessionCalls++;
|
|
2649
|
+
this.sessionTotalUsd += CALL_PRICE_USD;
|
|
2650
|
+
const txHash = retryResponse.headers.get("x-payment-receipt") || retryResponse.headers.get("X-Payment-Receipt");
|
|
2651
|
+
if (txHash) data.txHash = txHash;
|
|
2652
|
+
return data;
|
|
2653
|
+
}
|
|
2654
|
+
async fetchWithTimeout(url, options) {
|
|
2655
|
+
const controller = new AbortController();
|
|
2656
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2657
|
+
try {
|
|
2658
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2659
|
+
} finally {
|
|
2660
|
+
clearTimeout(timeoutId);
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
getWalletAddress() {
|
|
2664
|
+
return this.account.address;
|
|
2665
|
+
}
|
|
2666
|
+
getSpending() {
|
|
2667
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2668
|
+
}
|
|
2669
|
+
};
|
|
2670
|
+
|
|
2671
|
+
// src/search.ts
|
|
2672
|
+
import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
|
|
2673
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2674
|
+
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2675
|
+
var SearchClient = class {
|
|
2676
|
+
account;
|
|
2677
|
+
privateKey;
|
|
2678
|
+
apiUrl;
|
|
2679
|
+
timeout;
|
|
2680
|
+
constructor(options = {}) {
|
|
2681
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2682
|
+
const privateKey = options.privateKey || envKey;
|
|
2683
|
+
if (!privateKey) {
|
|
2684
|
+
throw new Error(
|
|
2685
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
validatePrivateKey(privateKey);
|
|
2689
|
+
this.privateKey = privateKey;
|
|
2690
|
+
this.account = privateKeyToAccount6(privateKey);
|
|
2691
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2692
|
+
validateApiUrl(apiUrl);
|
|
2693
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2694
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2695
|
+
}
|
|
2489
2696
|
async search(query, options) {
|
|
2490
2697
|
if (!query || query.length > 1e3) {
|
|
2491
2698
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2598,9 +2805,9 @@ var SearchClient = class {
|
|
|
2598
2805
|
};
|
|
2599
2806
|
|
|
2600
2807
|
// src/x-client.ts
|
|
2601
|
-
import { privateKeyToAccount as
|
|
2602
|
-
var
|
|
2603
|
-
var
|
|
2808
|
+
import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
|
|
2809
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2810
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2604
2811
|
var XClient = class _XClient {
|
|
2605
2812
|
account;
|
|
2606
2813
|
privateKey;
|
|
@@ -2623,11 +2830,11 @@ var XClient = class _XClient {
|
|
|
2623
2830
|
}
|
|
2624
2831
|
validatePrivateKey(privateKey);
|
|
2625
2832
|
this.privateKey = privateKey;
|
|
2626
|
-
this.account =
|
|
2627
|
-
const apiUrl = options.apiUrl ||
|
|
2833
|
+
this.account = privateKeyToAccount7(privateKey);
|
|
2834
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2628
2835
|
validateApiUrl(apiUrl);
|
|
2629
2836
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2630
|
-
this.timeout = options.timeout ||
|
|
2837
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2631
2838
|
}
|
|
2632
2839
|
// ───────── User endpoints ─────────
|
|
2633
2840
|
userLookup(usernames) {
|
|
@@ -2805,9 +3012,9 @@ var XClient = class _XClient {
|
|
|
2805
3012
|
};
|
|
2806
3013
|
|
|
2807
3014
|
// src/price.ts
|
|
2808
|
-
import { privateKeyToAccount as
|
|
2809
|
-
var
|
|
2810
|
-
var
|
|
3015
|
+
import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
|
|
3016
|
+
var DEFAULT_API_URL8 = "https://blockrun.ai/api";
|
|
3017
|
+
var DEFAULT_TIMEOUT8 = 3e4;
|
|
2811
3018
|
var PriceClient = class {
|
|
2812
3019
|
account = null;
|
|
2813
3020
|
privateKey = null;
|
|
@@ -2825,12 +3032,12 @@ var PriceClient = class {
|
|
|
2825
3032
|
if (privateKey) {
|
|
2826
3033
|
validatePrivateKey(privateKey);
|
|
2827
3034
|
this.privateKey = privateKey;
|
|
2828
|
-
this.account =
|
|
3035
|
+
this.account = privateKeyToAccount8(privateKey);
|
|
2829
3036
|
}
|
|
2830
|
-
const apiUrl = options.apiUrl ||
|
|
3037
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL8;
|
|
2831
3038
|
validateApiUrl(apiUrl);
|
|
2832
3039
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2833
|
-
this.timeout = options.timeout ||
|
|
3040
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
2834
3041
|
}
|
|
2835
3042
|
async price(category, symbol, options) {
|
|
2836
3043
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -3009,7 +3216,7 @@ function buildUrl(base, query) {
|
|
|
3009
3216
|
}
|
|
3010
3217
|
|
|
3011
3218
|
// src/wallet.ts
|
|
3012
|
-
import { privateKeyToAccount as
|
|
3219
|
+
import { privateKeyToAccount as privateKeyToAccount9, generatePrivateKey } from "viem/accounts";
|
|
3013
3220
|
import * as fs2 from "fs";
|
|
3014
3221
|
import * as path2 from "path";
|
|
3015
3222
|
import * as os2 from "os";
|
|
@@ -3019,7 +3226,7 @@ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
|
3019
3226
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
3020
3227
|
function createWallet() {
|
|
3021
3228
|
const privateKey = generatePrivateKey();
|
|
3022
|
-
const account =
|
|
3229
|
+
const account = privateKeyToAccount9(privateKey);
|
|
3023
3230
|
return {
|
|
3024
3231
|
address: account.address,
|
|
3025
3232
|
privateKey
|
|
@@ -3075,12 +3282,12 @@ function loadWallet() {
|
|
|
3075
3282
|
function getOrCreateWallet() {
|
|
3076
3283
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3077
3284
|
if (envKey) {
|
|
3078
|
-
const account =
|
|
3285
|
+
const account = privateKeyToAccount9(envKey);
|
|
3079
3286
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
3080
3287
|
}
|
|
3081
3288
|
const fileKey = loadWallet();
|
|
3082
3289
|
if (fileKey) {
|
|
3083
|
-
const account =
|
|
3290
|
+
const account = privateKeyToAccount9(fileKey);
|
|
3084
3291
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
3085
3292
|
}
|
|
3086
3293
|
const { address, privateKey } = createWallet();
|
|
@@ -3090,11 +3297,11 @@ function getOrCreateWallet() {
|
|
|
3090
3297
|
function getWalletAddress() {
|
|
3091
3298
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3092
3299
|
if (envKey) {
|
|
3093
|
-
return
|
|
3300
|
+
return privateKeyToAccount9(envKey).address;
|
|
3094
3301
|
}
|
|
3095
3302
|
const fileKey = loadWallet();
|
|
3096
3303
|
if (fileKey) {
|
|
3097
|
-
return
|
|
3304
|
+
return privateKeyToAccount9(fileKey).address;
|
|
3098
3305
|
}
|
|
3099
3306
|
return null;
|
|
3100
3307
|
}
|
|
@@ -3274,7 +3481,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3274
3481
|
// src/solana-client.ts
|
|
3275
3482
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3276
3483
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3277
|
-
var
|
|
3484
|
+
var DEFAULT_TIMEOUT9 = 6e4;
|
|
3278
3485
|
var SDK_VERSION2 = "0.3.0";
|
|
3279
3486
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3280
3487
|
var SolanaLLMClient = class {
|
|
@@ -3299,7 +3506,7 @@ var SolanaLLMClient = class {
|
|
|
3299
3506
|
validateApiUrl(apiUrl);
|
|
3300
3507
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3301
3508
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3302
|
-
this.timeout = options.timeout ||
|
|
3509
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT9;
|
|
3303
3510
|
}
|
|
3304
3511
|
/** Get Solana wallet address (public key in base58). */
|
|
3305
3512
|
async getWalletAddress() {
|
|
@@ -4206,7 +4413,7 @@ var OpenAI = class {
|
|
|
4206
4413
|
};
|
|
4207
4414
|
|
|
4208
4415
|
// src/anthropic-compat.ts
|
|
4209
|
-
import { privateKeyToAccount as
|
|
4416
|
+
import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
|
|
4210
4417
|
var AnthropicClient = class {
|
|
4211
4418
|
_client = null;
|
|
4212
4419
|
_clientPromise = null;
|
|
@@ -4219,7 +4426,7 @@ var AnthropicClient = class {
|
|
|
4219
4426
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4220
4427
|
validatePrivateKey(key);
|
|
4221
4428
|
this._privateKey = key;
|
|
4222
|
-
this._account =
|
|
4429
|
+
this._account = privateKeyToAccount10(this._privateKey);
|
|
4223
4430
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4224
4431
|
validateApiUrl(apiUrl);
|
|
4225
4432
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4333,6 +4540,7 @@ export {
|
|
|
4333
4540
|
USDC_BASE_CONTRACT,
|
|
4334
4541
|
USDC_SOLANA,
|
|
4335
4542
|
VideoClient,
|
|
4543
|
+
VoiceClient,
|
|
4336
4544
|
WALLET_DIR_PATH,
|
|
4337
4545
|
WALLET_FILE_PATH,
|
|
4338
4546
|
XClient,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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",
|