@blockrun/llm 2.1.0 → 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 +29 -0
- package/dist/index.cjs +427 -29
- package/dist/index.d.cts +214 -6
- package/dist/index.d.ts +214 -6
- package/dist/index.js +424 -28
- package/package.json +13 -13
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
|
@@ -48,6 +48,8 @@ __export(index_exports, {
|
|
|
48
48
|
USDC_BASE: () => USDC_BASE,
|
|
49
49
|
USDC_BASE_CONTRACT: () => USDC_BASE_CONTRACT,
|
|
50
50
|
USDC_SOLANA: () => USDC_SOLANA,
|
|
51
|
+
VideoClient: () => VideoClient,
|
|
52
|
+
VoiceClient: () => VoiceClient,
|
|
51
53
|
WALLET_DIR_PATH: () => WALLET_DIR_PATH,
|
|
52
54
|
WALLET_FILE_PATH: () => WALLET_FILE_PATH,
|
|
53
55
|
XClient: () => XClient,
|
|
@@ -619,9 +621,6 @@ var LLMClient = class _LLMClient {
|
|
|
619
621
|
*
|
|
620
622
|
* @example With routing profile
|
|
621
623
|
* ```ts
|
|
622
|
-
* // Free tier only (zero cost)
|
|
623
|
-
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
624
|
-
*
|
|
625
624
|
* // Eco mode (budget optimized)
|
|
626
625
|
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
627
626
|
*
|
|
@@ -2366,15 +2365,21 @@ var MusicClient = class {
|
|
|
2366
2365
|
}
|
|
2367
2366
|
};
|
|
2368
2367
|
|
|
2369
|
-
// src/
|
|
2368
|
+
// src/video.ts
|
|
2370
2369
|
var import_accounts5 = require("viem/accounts");
|
|
2371
2370
|
var DEFAULT_API_URL4 = "https://blockrun.ai/api";
|
|
2372
|
-
var
|
|
2373
|
-
var
|
|
2371
|
+
var DEFAULT_MODEL3 = "xai/grok-imagine-video";
|
|
2372
|
+
var DEFAULT_TIMEOUT4 = 12e4;
|
|
2373
|
+
var POLL_INTERVAL_MS2 = 5e3;
|
|
2374
|
+
var DEFAULT_GENERATE_BUDGET_MS = 3e5;
|
|
2375
|
+
var MAX_TIMEOUT_SECONDS = 600;
|
|
2376
|
+
var VideoClient = class {
|
|
2374
2377
|
account;
|
|
2375
2378
|
privateKey;
|
|
2376
2379
|
apiUrl;
|
|
2377
2380
|
timeout;
|
|
2381
|
+
sessionTotalUsd = 0;
|
|
2382
|
+
sessionCalls = 0;
|
|
2378
2383
|
constructor(options = {}) {
|
|
2379
2384
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2380
2385
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2391,6 +2396,394 @@ var SearchClient = class {
|
|
|
2391
2396
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2392
2397
|
this.timeout = options.timeout || DEFAULT_TIMEOUT4;
|
|
2393
2398
|
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Generate a short video clip from a text prompt (or text + image).
|
|
2401
|
+
*
|
|
2402
|
+
* Submits an async job, then polls until the video is ready. Typical total
|
|
2403
|
+
* wall-time is 60-180s. If upstream runs past the budget (default 5min),
|
|
2404
|
+
* throws without charging.
|
|
2405
|
+
*
|
|
2406
|
+
* @param prompt - Text description of the video
|
|
2407
|
+
* @param options - Optional generation parameters
|
|
2408
|
+
*/
|
|
2409
|
+
async generate(prompt, options) {
|
|
2410
|
+
const body = {
|
|
2411
|
+
model: options?.model || DEFAULT_MODEL3,
|
|
2412
|
+
prompt
|
|
2413
|
+
};
|
|
2414
|
+
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2415
|
+
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2416
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_GENERATE_BUDGET_MS;
|
|
2417
|
+
return this.submitAndPoll(body, budgetMs);
|
|
2418
|
+
}
|
|
2419
|
+
// --------------------------------------------------------------------
|
|
2420
|
+
// Internal: async submit + poll
|
|
2421
|
+
// --------------------------------------------------------------------
|
|
2422
|
+
async submitAndPoll(body, budgetMs) {
|
|
2423
|
+
const submitUrl = `${this.apiUrl}/v1/videos/generations`;
|
|
2424
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
2425
|
+
method: "POST",
|
|
2426
|
+
headers: { "Content-Type": "application/json" },
|
|
2427
|
+
body: JSON.stringify(body)
|
|
2428
|
+
});
|
|
2429
|
+
if (resp402.status !== 402) {
|
|
2430
|
+
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2431
|
+
}
|
|
2432
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2433
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2434
|
+
const paymentPayload = await createPaymentPayload(
|
|
2435
|
+
this.privateKey,
|
|
2436
|
+
this.account.address,
|
|
2437
|
+
details.recipient,
|
|
2438
|
+
details.amount,
|
|
2439
|
+
details.network || "eip155:8453",
|
|
2440
|
+
{
|
|
2441
|
+
resourceUrl: details.resource?.url || submitUrl,
|
|
2442
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2443
|
+
// Ensure signed auth covers the entire polling window.
|
|
2444
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2445
|
+
extra: details.extra
|
|
2446
|
+
}
|
|
2447
|
+
);
|
|
2448
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2449
|
+
method: "POST",
|
|
2450
|
+
headers: {
|
|
2451
|
+
"Content-Type": "application/json",
|
|
2452
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2453
|
+
},
|
|
2454
|
+
body: JSON.stringify(body)
|
|
2455
|
+
});
|
|
2456
|
+
if (submitResp.status === 402) {
|
|
2457
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2458
|
+
}
|
|
2459
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
2460
|
+
await this.throwApiError(submitResp, "Submit failed");
|
|
2461
|
+
}
|
|
2462
|
+
const submitData = await submitResp.json();
|
|
2463
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
2464
|
+
throw new APIError(
|
|
2465
|
+
"Submit response missing id/poll_url",
|
|
2466
|
+
submitResp.status,
|
|
2467
|
+
{ response: submitData }
|
|
2468
|
+
);
|
|
2469
|
+
}
|
|
2470
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
2471
|
+
const deadline = Date.now() + budgetMs;
|
|
2472
|
+
let lastStatus = submitData.status || "queued";
|
|
2473
|
+
while (Date.now() < deadline) {
|
|
2474
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2475
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
2476
|
+
method: "GET",
|
|
2477
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
2478
|
+
});
|
|
2479
|
+
let pollData = {};
|
|
2480
|
+
try {
|
|
2481
|
+
pollData = await pollResp.json();
|
|
2482
|
+
} catch {
|
|
2483
|
+
}
|
|
2484
|
+
lastStatus = pollData.status || lastStatus;
|
|
2485
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
if (lastStatus === "failed") {
|
|
2489
|
+
throw new APIError(
|
|
2490
|
+
`Upstream generation failed: ${pollData.error || "unknown"}`,
|
|
2491
|
+
pollResp.status,
|
|
2492
|
+
sanitizeErrorResponse(pollData)
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
2496
|
+
const data = pollData;
|
|
2497
|
+
const billedSeconds = typeof body.duration_seconds === "number" ? body.duration_seconds : 8;
|
|
2498
|
+
this.sessionCalls++;
|
|
2499
|
+
this.sessionTotalUsd += 0.05 * billedSeconds * 1.05;
|
|
2500
|
+
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
2501
|
+
if (txHash) data.txHash = txHash;
|
|
2502
|
+
return data;
|
|
2503
|
+
}
|
|
2504
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2505
|
+
await this.throwApiError(pollResp, "Poll failed");
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
throw new APIError(
|
|
2509
|
+
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2510
|
+
504,
|
|
2511
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
2512
|
+
);
|
|
2513
|
+
}
|
|
2514
|
+
absolute(url) {
|
|
2515
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
2516
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
2517
|
+
return `${base}${url}`;
|
|
2518
|
+
}
|
|
2519
|
+
async extractPaymentRequired(resp) {
|
|
2520
|
+
const header = resp.headers.get("payment-required");
|
|
2521
|
+
if (header) return parsePaymentRequired(header);
|
|
2522
|
+
try {
|
|
2523
|
+
const body = await resp.json();
|
|
2524
|
+
if (body && (body.x402Version !== void 0 || body.accepts !== void 0)) {
|
|
2525
|
+
return body;
|
|
2526
|
+
}
|
|
2527
|
+
} catch {
|
|
2528
|
+
}
|
|
2529
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2530
|
+
}
|
|
2531
|
+
async throwApiError(resp, prefix) {
|
|
2532
|
+
let errorBody;
|
|
2533
|
+
try {
|
|
2534
|
+
errorBody = await resp.json();
|
|
2535
|
+
} catch {
|
|
2536
|
+
errorBody = { error: "Request failed" };
|
|
2537
|
+
}
|
|
2538
|
+
throw new APIError(
|
|
2539
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
2540
|
+
resp.status,
|
|
2541
|
+
sanitizeErrorResponse(errorBody)
|
|
2542
|
+
);
|
|
2543
|
+
}
|
|
2544
|
+
async fetchWithTimeout(url, options) {
|
|
2545
|
+
const controller = new AbortController();
|
|
2546
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2547
|
+
try {
|
|
2548
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2549
|
+
} finally {
|
|
2550
|
+
clearTimeout(timeoutId);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
getWalletAddress() {
|
|
2554
|
+
return this.account.address;
|
|
2555
|
+
}
|
|
2556
|
+
getSpending() {
|
|
2557
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2558
|
+
}
|
|
2559
|
+
};
|
|
2560
|
+
|
|
2561
|
+
// src/voice.ts
|
|
2562
|
+
var import_accounts6 = require("viem/accounts");
|
|
2563
|
+
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2564
|
+
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2565
|
+
var CALL_PRICE_USD = 0.54;
|
|
2566
|
+
var VALID_MODELS = /* @__PURE__ */ new Set(["base", "enhanced", "turbo"]);
|
|
2567
|
+
var VoiceClient = class {
|
|
2568
|
+
account;
|
|
2569
|
+
privateKey;
|
|
2570
|
+
apiUrl;
|
|
2571
|
+
timeout;
|
|
2572
|
+
sessionTotalUsd = 0;
|
|
2573
|
+
sessionCalls = 0;
|
|
2574
|
+
constructor(options = {}) {
|
|
2575
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2576
|
+
const privateKey = options.privateKey || envKey;
|
|
2577
|
+
if (!privateKey) {
|
|
2578
|
+
throw new Error(
|
|
2579
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
validatePrivateKey(privateKey);
|
|
2583
|
+
this.privateKey = privateKey;
|
|
2584
|
+
this.account = (0, import_accounts6.privateKeyToAccount)(privateKey);
|
|
2585
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL5;
|
|
2586
|
+
validateApiUrl(apiUrl);
|
|
2587
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2588
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
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
|
+
}
|
|
2394
2787
|
async search(query, options) {
|
|
2395
2788
|
if (!query || query.length > 1e3) {
|
|
2396
2789
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2503,9 +2896,9 @@ var SearchClient = class {
|
|
|
2503
2896
|
};
|
|
2504
2897
|
|
|
2505
2898
|
// src/x-client.ts
|
|
2506
|
-
var
|
|
2507
|
-
var
|
|
2508
|
-
var
|
|
2899
|
+
var import_accounts8 = require("viem/accounts");
|
|
2900
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2901
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2509
2902
|
var XClient = class _XClient {
|
|
2510
2903
|
account;
|
|
2511
2904
|
privateKey;
|
|
@@ -2528,11 +2921,11 @@ var XClient = class _XClient {
|
|
|
2528
2921
|
}
|
|
2529
2922
|
validatePrivateKey(privateKey);
|
|
2530
2923
|
this.privateKey = privateKey;
|
|
2531
|
-
this.account = (0,
|
|
2532
|
-
const apiUrl = options.apiUrl ||
|
|
2924
|
+
this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
2925
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2533
2926
|
validateApiUrl(apiUrl);
|
|
2534
2927
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2535
|
-
this.timeout = options.timeout ||
|
|
2928
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2536
2929
|
}
|
|
2537
2930
|
// ───────── User endpoints ─────────
|
|
2538
2931
|
userLookup(usernames) {
|
|
@@ -2710,9 +3103,9 @@ var XClient = class _XClient {
|
|
|
2710
3103
|
};
|
|
2711
3104
|
|
|
2712
3105
|
// src/price.ts
|
|
2713
|
-
var
|
|
2714
|
-
var
|
|
2715
|
-
var
|
|
3106
|
+
var import_accounts9 = require("viem/accounts");
|
|
3107
|
+
var DEFAULT_API_URL8 = "https://blockrun.ai/api";
|
|
3108
|
+
var DEFAULT_TIMEOUT8 = 3e4;
|
|
2716
3109
|
var PriceClient = class {
|
|
2717
3110
|
account = null;
|
|
2718
3111
|
privateKey = null;
|
|
@@ -2730,12 +3123,12 @@ var PriceClient = class {
|
|
|
2730
3123
|
if (privateKey) {
|
|
2731
3124
|
validatePrivateKey(privateKey);
|
|
2732
3125
|
this.privateKey = privateKey;
|
|
2733
|
-
this.account = (0,
|
|
3126
|
+
this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
|
|
2734
3127
|
}
|
|
2735
|
-
const apiUrl = options.apiUrl ||
|
|
3128
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL8;
|
|
2736
3129
|
validateApiUrl(apiUrl);
|
|
2737
3130
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2738
|
-
this.timeout = options.timeout ||
|
|
3131
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
2739
3132
|
}
|
|
2740
3133
|
async price(category, symbol, options) {
|
|
2741
3134
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -2914,7 +3307,7 @@ function buildUrl(base, query) {
|
|
|
2914
3307
|
}
|
|
2915
3308
|
|
|
2916
3309
|
// src/wallet.ts
|
|
2917
|
-
var
|
|
3310
|
+
var import_accounts10 = require("viem/accounts");
|
|
2918
3311
|
var fs2 = __toESM(require("fs"), 1);
|
|
2919
3312
|
var path2 = __toESM(require("path"), 1);
|
|
2920
3313
|
var os2 = __toESM(require("os"), 1);
|
|
@@ -2923,8 +3316,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
2923
3316
|
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2924
3317
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2925
3318
|
function createWallet() {
|
|
2926
|
-
const privateKey = (0,
|
|
2927
|
-
const account = (0,
|
|
3319
|
+
const privateKey = (0, import_accounts10.generatePrivateKey)();
|
|
3320
|
+
const account = (0, import_accounts10.privateKeyToAccount)(privateKey);
|
|
2928
3321
|
return {
|
|
2929
3322
|
address: account.address,
|
|
2930
3323
|
privateKey
|
|
@@ -2980,12 +3373,12 @@ function loadWallet() {
|
|
|
2980
3373
|
function getOrCreateWallet() {
|
|
2981
3374
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2982
3375
|
if (envKey) {
|
|
2983
|
-
const account = (0,
|
|
3376
|
+
const account = (0, import_accounts10.privateKeyToAccount)(envKey);
|
|
2984
3377
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
2985
3378
|
}
|
|
2986
3379
|
const fileKey = loadWallet();
|
|
2987
3380
|
if (fileKey) {
|
|
2988
|
-
const account = (0,
|
|
3381
|
+
const account = (0, import_accounts10.privateKeyToAccount)(fileKey);
|
|
2989
3382
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
2990
3383
|
}
|
|
2991
3384
|
const { address, privateKey } = createWallet();
|
|
@@ -2995,11 +3388,11 @@ function getOrCreateWallet() {
|
|
|
2995
3388
|
function getWalletAddress() {
|
|
2996
3389
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2997
3390
|
if (envKey) {
|
|
2998
|
-
return (0,
|
|
3391
|
+
return (0, import_accounts10.privateKeyToAccount)(envKey).address;
|
|
2999
3392
|
}
|
|
3000
3393
|
const fileKey = loadWallet();
|
|
3001
3394
|
if (fileKey) {
|
|
3002
|
-
return (0,
|
|
3395
|
+
return (0, import_accounts10.privateKeyToAccount)(fileKey).address;
|
|
3003
3396
|
}
|
|
3004
3397
|
return null;
|
|
3005
3398
|
}
|
|
@@ -3179,7 +3572,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3179
3572
|
// src/solana-client.ts
|
|
3180
3573
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3181
3574
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3182
|
-
var
|
|
3575
|
+
var DEFAULT_TIMEOUT9 = 6e4;
|
|
3183
3576
|
var SDK_VERSION2 = "0.3.0";
|
|
3184
3577
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3185
3578
|
var SolanaLLMClient = class {
|
|
@@ -3204,7 +3597,7 @@ var SolanaLLMClient = class {
|
|
|
3204
3597
|
validateApiUrl(apiUrl);
|
|
3205
3598
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3206
3599
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3207
|
-
this.timeout = options.timeout ||
|
|
3600
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT9;
|
|
3208
3601
|
}
|
|
3209
3602
|
/** Get Solana wallet address (public key in base58). */
|
|
3210
3603
|
async getWalletAddress() {
|
|
@@ -4029,6 +4422,9 @@ var ChatCompletions = class {
|
|
|
4029
4422
|
this.apiUrl = apiUrl;
|
|
4030
4423
|
this.timeout = timeout;
|
|
4031
4424
|
}
|
|
4425
|
+
client;
|
|
4426
|
+
apiUrl;
|
|
4427
|
+
timeout;
|
|
4032
4428
|
async create(params) {
|
|
4033
4429
|
if (params.stream) {
|
|
4034
4430
|
return this.createStream(params);
|
|
@@ -4108,7 +4504,7 @@ var OpenAI = class {
|
|
|
4108
4504
|
};
|
|
4109
4505
|
|
|
4110
4506
|
// src/anthropic-compat.ts
|
|
4111
|
-
var
|
|
4507
|
+
var import_accounts11 = require("viem/accounts");
|
|
4112
4508
|
var AnthropicClient = class {
|
|
4113
4509
|
_client = null;
|
|
4114
4510
|
_clientPromise = null;
|
|
@@ -4121,7 +4517,7 @@ var AnthropicClient = class {
|
|
|
4121
4517
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4122
4518
|
validatePrivateKey(key);
|
|
4123
4519
|
this._privateKey = key;
|
|
4124
|
-
this._account = (0,
|
|
4520
|
+
this._account = (0, import_accounts11.privateKeyToAccount)(this._privateKey);
|
|
4125
4521
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4126
4522
|
validateApiUrl(apiUrl);
|
|
4127
4523
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4235,6 +4631,8 @@ var AnthropicClient = class {
|
|
|
4235
4631
|
USDC_BASE,
|
|
4236
4632
|
USDC_BASE_CONTRACT,
|
|
4237
4633
|
USDC_SOLANA,
|
|
4634
|
+
VideoClient,
|
|
4635
|
+
VoiceClient,
|
|
4238
4636
|
WALLET_DIR_PATH,
|
|
4239
4637
|
WALLET_FILE_PATH,
|
|
4240
4638
|
XClient,
|