@blockrun/llm 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +250 -33
- package/dist/index.d.cts +99 -6
- package/dist/index.d.ts +99 -6
- package/dist/index.js +248 -32
- package/package.json +12 -12
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,7 @@ __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,
|
|
51
52
|
WALLET_DIR_PATH: () => WALLET_DIR_PATH,
|
|
52
53
|
WALLET_FILE_PATH: () => WALLET_FILE_PATH,
|
|
53
54
|
XClient: () => XClient,
|
|
@@ -619,9 +620,6 @@ var LLMClient = class _LLMClient {
|
|
|
619
620
|
*
|
|
620
621
|
* @example With routing profile
|
|
621
622
|
* ```ts
|
|
622
|
-
* // Free tier only (zero cost)
|
|
623
|
-
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
624
|
-
*
|
|
625
623
|
* // Eco mode (budget optimized)
|
|
626
624
|
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
627
625
|
*
|
|
@@ -781,6 +779,28 @@ var LLMClient = class _LLMClient {
|
|
|
781
779
|
} catch {
|
|
782
780
|
}
|
|
783
781
|
}
|
|
782
|
+
/**
|
|
783
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
784
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
785
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
786
|
+
* on the response when it served a paid request from a free model
|
|
787
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
788
|
+
* to the caller, the user gets a different model than requested with
|
|
789
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
790
|
+
* balance didn't change.
|
|
791
|
+
*/
|
|
792
|
+
async parseChatResponse(response) {
|
|
793
|
+
const body = await response.json();
|
|
794
|
+
const used = response.headers.get("X-Fallback-Used") === "true";
|
|
795
|
+
if (used) {
|
|
796
|
+
body.fallback = {
|
|
797
|
+
used: true,
|
|
798
|
+
model: response.headers.get("X-Fallback-Model") || void 0,
|
|
799
|
+
settlementSkipped: response.headers.get("X-Settlement-Skipped") === "free-fallback"
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
return body;
|
|
803
|
+
}
|
|
784
804
|
/**
|
|
785
805
|
* Make a request with automatic x402 payment handling.
|
|
786
806
|
*/
|
|
@@ -809,7 +829,7 @@ var LLMClient = class _LLMClient {
|
|
|
809
829
|
}
|
|
810
830
|
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
811
831
|
}
|
|
812
|
-
return
|
|
832
|
+
return this.parseChatResponse(retryResp);
|
|
813
833
|
}
|
|
814
834
|
}
|
|
815
835
|
if (response.status === 402) {
|
|
@@ -828,7 +848,7 @@ var LLMClient = class _LLMClient {
|
|
|
828
848
|
sanitizeErrorResponse(errorBody)
|
|
829
849
|
);
|
|
830
850
|
}
|
|
831
|
-
return
|
|
851
|
+
return this.parseChatResponse(response);
|
|
832
852
|
}
|
|
833
853
|
/**
|
|
834
854
|
* Handle 402 response: parse requirements, sign payment, retry.
|
|
@@ -903,7 +923,7 @@ var LLMClient = class _LLMClient {
|
|
|
903
923
|
this.sessionCalls += 1;
|
|
904
924
|
this.sessionTotalUsd += costUsd2;
|
|
905
925
|
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
906
|
-
return
|
|
926
|
+
return this.parseChatResponse(retryResp2);
|
|
907
927
|
}
|
|
908
928
|
}
|
|
909
929
|
if (retryResponse.status === 402) {
|
|
@@ -926,7 +946,7 @@ var LLMClient = class _LLMClient {
|
|
|
926
946
|
this.sessionCalls += 1;
|
|
927
947
|
this.sessionTotalUsd += costUsd;
|
|
928
948
|
this.recordCost(url, costUsd, { body, network: details.network });
|
|
929
|
-
return
|
|
949
|
+
return this.parseChatResponse(retryResponse);
|
|
930
950
|
}
|
|
931
951
|
/**
|
|
932
952
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
@@ -2344,15 +2364,21 @@ var MusicClient = class {
|
|
|
2344
2364
|
}
|
|
2345
2365
|
};
|
|
2346
2366
|
|
|
2347
|
-
// src/
|
|
2367
|
+
// src/video.ts
|
|
2348
2368
|
var import_accounts5 = require("viem/accounts");
|
|
2349
2369
|
var DEFAULT_API_URL4 = "https://blockrun.ai/api";
|
|
2350
|
-
var
|
|
2351
|
-
var
|
|
2370
|
+
var DEFAULT_MODEL3 = "xai/grok-imagine-video";
|
|
2371
|
+
var DEFAULT_TIMEOUT4 = 12e4;
|
|
2372
|
+
var POLL_INTERVAL_MS2 = 5e3;
|
|
2373
|
+
var DEFAULT_GENERATE_BUDGET_MS = 3e5;
|
|
2374
|
+
var MAX_TIMEOUT_SECONDS = 600;
|
|
2375
|
+
var VideoClient = class {
|
|
2352
2376
|
account;
|
|
2353
2377
|
privateKey;
|
|
2354
2378
|
apiUrl;
|
|
2355
2379
|
timeout;
|
|
2380
|
+
sessionTotalUsd = 0;
|
|
2381
|
+
sessionCalls = 0;
|
|
2356
2382
|
constructor(options = {}) {
|
|
2357
2383
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2358
2384
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2369,6 +2395,193 @@ var SearchClient = class {
|
|
|
2369
2395
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2370
2396
|
this.timeout = options.timeout || DEFAULT_TIMEOUT4;
|
|
2371
2397
|
}
|
|
2398
|
+
/**
|
|
2399
|
+
* Generate a short video clip from a text prompt (or text + image).
|
|
2400
|
+
*
|
|
2401
|
+
* Submits an async job, then polls until the video is ready. Typical total
|
|
2402
|
+
* wall-time is 60-180s. If upstream runs past the budget (default 5min),
|
|
2403
|
+
* throws without charging.
|
|
2404
|
+
*
|
|
2405
|
+
* @param prompt - Text description of the video
|
|
2406
|
+
* @param options - Optional generation parameters
|
|
2407
|
+
*/
|
|
2408
|
+
async generate(prompt, options) {
|
|
2409
|
+
const body = {
|
|
2410
|
+
model: options?.model || DEFAULT_MODEL3,
|
|
2411
|
+
prompt
|
|
2412
|
+
};
|
|
2413
|
+
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2414
|
+
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2415
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_GENERATE_BUDGET_MS;
|
|
2416
|
+
return this.submitAndPoll(body, budgetMs);
|
|
2417
|
+
}
|
|
2418
|
+
// --------------------------------------------------------------------
|
|
2419
|
+
// Internal: async submit + poll
|
|
2420
|
+
// --------------------------------------------------------------------
|
|
2421
|
+
async submitAndPoll(body, budgetMs) {
|
|
2422
|
+
const submitUrl = `${this.apiUrl}/v1/videos/generations`;
|
|
2423
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
2424
|
+
method: "POST",
|
|
2425
|
+
headers: { "Content-Type": "application/json" },
|
|
2426
|
+
body: JSON.stringify(body)
|
|
2427
|
+
});
|
|
2428
|
+
if (resp402.status !== 402) {
|
|
2429
|
+
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2430
|
+
}
|
|
2431
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2432
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2433
|
+
const paymentPayload = await createPaymentPayload(
|
|
2434
|
+
this.privateKey,
|
|
2435
|
+
this.account.address,
|
|
2436
|
+
details.recipient,
|
|
2437
|
+
details.amount,
|
|
2438
|
+
details.network || "eip155:8453",
|
|
2439
|
+
{
|
|
2440
|
+
resourceUrl: details.resource?.url || submitUrl,
|
|
2441
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2442
|
+
// Ensure signed auth covers the entire polling window.
|
|
2443
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2444
|
+
extra: details.extra
|
|
2445
|
+
}
|
|
2446
|
+
);
|
|
2447
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2448
|
+
method: "POST",
|
|
2449
|
+
headers: {
|
|
2450
|
+
"Content-Type": "application/json",
|
|
2451
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2452
|
+
},
|
|
2453
|
+
body: JSON.stringify(body)
|
|
2454
|
+
});
|
|
2455
|
+
if (submitResp.status === 402) {
|
|
2456
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2457
|
+
}
|
|
2458
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
2459
|
+
await this.throwApiError(submitResp, "Submit failed");
|
|
2460
|
+
}
|
|
2461
|
+
const submitData = await submitResp.json();
|
|
2462
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
2463
|
+
throw new APIError(
|
|
2464
|
+
"Submit response missing id/poll_url",
|
|
2465
|
+
submitResp.status,
|
|
2466
|
+
{ response: submitData }
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
2470
|
+
const deadline = Date.now() + budgetMs;
|
|
2471
|
+
let lastStatus = submitData.status || "queued";
|
|
2472
|
+
while (Date.now() < deadline) {
|
|
2473
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2474
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
2475
|
+
method: "GET",
|
|
2476
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
2477
|
+
});
|
|
2478
|
+
let pollData = {};
|
|
2479
|
+
try {
|
|
2480
|
+
pollData = await pollResp.json();
|
|
2481
|
+
} catch {
|
|
2482
|
+
}
|
|
2483
|
+
lastStatus = pollData.status || lastStatus;
|
|
2484
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
if (lastStatus === "failed") {
|
|
2488
|
+
throw new APIError(
|
|
2489
|
+
`Upstream generation failed: ${pollData.error || "unknown"}`,
|
|
2490
|
+
pollResp.status,
|
|
2491
|
+
sanitizeErrorResponse(pollData)
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
2495
|
+
const data = pollData;
|
|
2496
|
+
const billedSeconds = typeof body.duration_seconds === "number" ? body.duration_seconds : 8;
|
|
2497
|
+
this.sessionCalls++;
|
|
2498
|
+
this.sessionTotalUsd += 0.05 * billedSeconds * 1.05;
|
|
2499
|
+
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
2500
|
+
if (txHash) data.txHash = txHash;
|
|
2501
|
+
return data;
|
|
2502
|
+
}
|
|
2503
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2504
|
+
await this.throwApiError(pollResp, "Poll failed");
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
throw new APIError(
|
|
2508
|
+
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2509
|
+
504,
|
|
2510
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
2511
|
+
);
|
|
2512
|
+
}
|
|
2513
|
+
absolute(url) {
|
|
2514
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
2515
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
2516
|
+
return `${base}${url}`;
|
|
2517
|
+
}
|
|
2518
|
+
async extractPaymentRequired(resp) {
|
|
2519
|
+
const header = resp.headers.get("payment-required");
|
|
2520
|
+
if (header) return parsePaymentRequired(header);
|
|
2521
|
+
try {
|
|
2522
|
+
const body = await resp.json();
|
|
2523
|
+
if (body && (body.x402Version !== void 0 || body.accepts !== void 0)) {
|
|
2524
|
+
return body;
|
|
2525
|
+
}
|
|
2526
|
+
} catch {
|
|
2527
|
+
}
|
|
2528
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2529
|
+
}
|
|
2530
|
+
async throwApiError(resp, prefix) {
|
|
2531
|
+
let errorBody;
|
|
2532
|
+
try {
|
|
2533
|
+
errorBody = await resp.json();
|
|
2534
|
+
} catch {
|
|
2535
|
+
errorBody = { error: "Request failed" };
|
|
2536
|
+
}
|
|
2537
|
+
throw new APIError(
|
|
2538
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
2539
|
+
resp.status,
|
|
2540
|
+
sanitizeErrorResponse(errorBody)
|
|
2541
|
+
);
|
|
2542
|
+
}
|
|
2543
|
+
async fetchWithTimeout(url, options) {
|
|
2544
|
+
const controller = new AbortController();
|
|
2545
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2546
|
+
try {
|
|
2547
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2548
|
+
} finally {
|
|
2549
|
+
clearTimeout(timeoutId);
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
getWalletAddress() {
|
|
2553
|
+
return this.account.address;
|
|
2554
|
+
}
|
|
2555
|
+
getSpending() {
|
|
2556
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2557
|
+
}
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2560
|
+
// src/search.ts
|
|
2561
|
+
var import_accounts6 = require("viem/accounts");
|
|
2562
|
+
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2563
|
+
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2564
|
+
var SearchClient = class {
|
|
2565
|
+
account;
|
|
2566
|
+
privateKey;
|
|
2567
|
+
apiUrl;
|
|
2568
|
+
timeout;
|
|
2569
|
+
constructor(options = {}) {
|
|
2570
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2571
|
+
const privateKey = options.privateKey || envKey;
|
|
2572
|
+
if (!privateKey) {
|
|
2573
|
+
throw new Error(
|
|
2574
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
validatePrivateKey(privateKey);
|
|
2578
|
+
this.privateKey = privateKey;
|
|
2579
|
+
this.account = (0, import_accounts6.privateKeyToAccount)(privateKey);
|
|
2580
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL5;
|
|
2581
|
+
validateApiUrl(apiUrl);
|
|
2582
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2583
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2584
|
+
}
|
|
2372
2585
|
async search(query, options) {
|
|
2373
2586
|
if (!query || query.length > 1e3) {
|
|
2374
2587
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2481,9 +2694,9 @@ var SearchClient = class {
|
|
|
2481
2694
|
};
|
|
2482
2695
|
|
|
2483
2696
|
// src/x-client.ts
|
|
2484
|
-
var
|
|
2485
|
-
var
|
|
2486
|
-
var
|
|
2697
|
+
var import_accounts7 = require("viem/accounts");
|
|
2698
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2699
|
+
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2487
2700
|
var XClient = class _XClient {
|
|
2488
2701
|
account;
|
|
2489
2702
|
privateKey;
|
|
@@ -2506,11 +2719,11 @@ var XClient = class _XClient {
|
|
|
2506
2719
|
}
|
|
2507
2720
|
validatePrivateKey(privateKey);
|
|
2508
2721
|
this.privateKey = privateKey;
|
|
2509
|
-
this.account = (0,
|
|
2510
|
-
const apiUrl = options.apiUrl ||
|
|
2722
|
+
this.account = (0, import_accounts7.privateKeyToAccount)(privateKey);
|
|
2723
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2511
2724
|
validateApiUrl(apiUrl);
|
|
2512
2725
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2513
|
-
this.timeout = options.timeout ||
|
|
2726
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2514
2727
|
}
|
|
2515
2728
|
// ───────── User endpoints ─────────
|
|
2516
2729
|
userLookup(usernames) {
|
|
@@ -2688,9 +2901,9 @@ var XClient = class _XClient {
|
|
|
2688
2901
|
};
|
|
2689
2902
|
|
|
2690
2903
|
// src/price.ts
|
|
2691
|
-
var
|
|
2692
|
-
var
|
|
2693
|
-
var
|
|
2904
|
+
var import_accounts8 = require("viem/accounts");
|
|
2905
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2906
|
+
var DEFAULT_TIMEOUT7 = 3e4;
|
|
2694
2907
|
var PriceClient = class {
|
|
2695
2908
|
account = null;
|
|
2696
2909
|
privateKey = null;
|
|
@@ -2708,12 +2921,12 @@ var PriceClient = class {
|
|
|
2708
2921
|
if (privateKey) {
|
|
2709
2922
|
validatePrivateKey(privateKey);
|
|
2710
2923
|
this.privateKey = privateKey;
|
|
2711
|
-
this.account = (0,
|
|
2924
|
+
this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
2712
2925
|
}
|
|
2713
|
-
const apiUrl = options.apiUrl ||
|
|
2926
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2714
2927
|
validateApiUrl(apiUrl);
|
|
2715
2928
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2716
|
-
this.timeout = options.timeout ||
|
|
2929
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2717
2930
|
}
|
|
2718
2931
|
async price(category, symbol, options) {
|
|
2719
2932
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -2892,7 +3105,7 @@ function buildUrl(base, query) {
|
|
|
2892
3105
|
}
|
|
2893
3106
|
|
|
2894
3107
|
// src/wallet.ts
|
|
2895
|
-
var
|
|
3108
|
+
var import_accounts9 = require("viem/accounts");
|
|
2896
3109
|
var fs2 = __toESM(require("fs"), 1);
|
|
2897
3110
|
var path2 = __toESM(require("path"), 1);
|
|
2898
3111
|
var os2 = __toESM(require("os"), 1);
|
|
@@ -2901,8 +3114,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
2901
3114
|
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
2902
3115
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2903
3116
|
function createWallet() {
|
|
2904
|
-
const privateKey = (0,
|
|
2905
|
-
const account = (0,
|
|
3117
|
+
const privateKey = (0, import_accounts9.generatePrivateKey)();
|
|
3118
|
+
const account = (0, import_accounts9.privateKeyToAccount)(privateKey);
|
|
2906
3119
|
return {
|
|
2907
3120
|
address: account.address,
|
|
2908
3121
|
privateKey
|
|
@@ -2958,12 +3171,12 @@ function loadWallet() {
|
|
|
2958
3171
|
function getOrCreateWallet() {
|
|
2959
3172
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2960
3173
|
if (envKey) {
|
|
2961
|
-
const account = (0,
|
|
3174
|
+
const account = (0, import_accounts9.privateKeyToAccount)(envKey);
|
|
2962
3175
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
2963
3176
|
}
|
|
2964
3177
|
const fileKey = loadWallet();
|
|
2965
3178
|
if (fileKey) {
|
|
2966
|
-
const account = (0,
|
|
3179
|
+
const account = (0, import_accounts9.privateKeyToAccount)(fileKey);
|
|
2967
3180
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
2968
3181
|
}
|
|
2969
3182
|
const { address, privateKey } = createWallet();
|
|
@@ -2973,11 +3186,11 @@ function getOrCreateWallet() {
|
|
|
2973
3186
|
function getWalletAddress() {
|
|
2974
3187
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2975
3188
|
if (envKey) {
|
|
2976
|
-
return (0,
|
|
3189
|
+
return (0, import_accounts9.privateKeyToAccount)(envKey).address;
|
|
2977
3190
|
}
|
|
2978
3191
|
const fileKey = loadWallet();
|
|
2979
3192
|
if (fileKey) {
|
|
2980
|
-
return (0,
|
|
3193
|
+
return (0, import_accounts9.privateKeyToAccount)(fileKey).address;
|
|
2981
3194
|
}
|
|
2982
3195
|
return null;
|
|
2983
3196
|
}
|
|
@@ -3157,7 +3370,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3157
3370
|
// src/solana-client.ts
|
|
3158
3371
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3159
3372
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3160
|
-
var
|
|
3373
|
+
var DEFAULT_TIMEOUT8 = 6e4;
|
|
3161
3374
|
var SDK_VERSION2 = "0.3.0";
|
|
3162
3375
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3163
3376
|
var SolanaLLMClient = class {
|
|
@@ -3182,7 +3395,7 @@ var SolanaLLMClient = class {
|
|
|
3182
3395
|
validateApiUrl(apiUrl);
|
|
3183
3396
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3184
3397
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3185
|
-
this.timeout = options.timeout ||
|
|
3398
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
3186
3399
|
}
|
|
3187
3400
|
/** Get Solana wallet address (public key in base58). */
|
|
3188
3401
|
async getWalletAddress() {
|
|
@@ -4007,6 +4220,9 @@ var ChatCompletions = class {
|
|
|
4007
4220
|
this.apiUrl = apiUrl;
|
|
4008
4221
|
this.timeout = timeout;
|
|
4009
4222
|
}
|
|
4223
|
+
client;
|
|
4224
|
+
apiUrl;
|
|
4225
|
+
timeout;
|
|
4010
4226
|
async create(params) {
|
|
4011
4227
|
if (params.stream) {
|
|
4012
4228
|
return this.createStream(params);
|
|
@@ -4086,7 +4302,7 @@ var OpenAI = class {
|
|
|
4086
4302
|
};
|
|
4087
4303
|
|
|
4088
4304
|
// src/anthropic-compat.ts
|
|
4089
|
-
var
|
|
4305
|
+
var import_accounts10 = require("viem/accounts");
|
|
4090
4306
|
var AnthropicClient = class {
|
|
4091
4307
|
_client = null;
|
|
4092
4308
|
_clientPromise = null;
|
|
@@ -4099,7 +4315,7 @@ var AnthropicClient = class {
|
|
|
4099
4315
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4100
4316
|
validatePrivateKey(key);
|
|
4101
4317
|
this._privateKey = key;
|
|
4102
|
-
this._account = (0,
|
|
4318
|
+
this._account = (0, import_accounts10.privateKeyToAccount)(this._privateKey);
|
|
4103
4319
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4104
4320
|
validateApiUrl(apiUrl);
|
|
4105
4321
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4213,6 +4429,7 @@ var AnthropicClient = class {
|
|
|
4213
4429
|
USDC_BASE,
|
|
4214
4430
|
USDC_BASE_CONTRACT,
|
|
4215
4431
|
USDC_SOLANA,
|
|
4432
|
+
VideoClient,
|
|
4216
4433
|
WALLET_DIR_PATH,
|
|
4217
4434
|
WALLET_FILE_PATH,
|
|
4218
4435
|
XClient,
|
package/dist/index.d.cts
CHANGED
|
@@ -58,6 +58,25 @@ interface ChatResponse {
|
|
|
58
58
|
choices: ChatChoice[];
|
|
59
59
|
usage?: ChatUsage;
|
|
60
60
|
citations?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Populated when the gateway transparently substituted a different
|
|
63
|
+
* model for the one the caller asked for — typically because the
|
|
64
|
+
* requested model errored and the gateway routed to a free fallback
|
|
65
|
+
* to fulfil the request. When `used` is true:
|
|
66
|
+
* - `model` is the model that actually answered (vs `ChatResponse.model`
|
|
67
|
+
* which historically reflected the requested model id).
|
|
68
|
+
* - `settlementSkipped` is `true` when the gateway also skipped the
|
|
69
|
+
* on-chain settle — i.e. the user was not charged for this call
|
|
70
|
+
* because a free fallback served it.
|
|
71
|
+
* Surfaced from the gateway's `X-Fallback-Used / X-Fallback-Model /
|
|
72
|
+
* X-Settlement-Skipped` response headers. Absent when the headers
|
|
73
|
+
* aren't present (most calls).
|
|
74
|
+
*/
|
|
75
|
+
fallback?: {
|
|
76
|
+
used: true;
|
|
77
|
+
model?: string;
|
|
78
|
+
settlementSkipped?: boolean;
|
|
79
|
+
};
|
|
61
80
|
}
|
|
62
81
|
interface Model {
|
|
63
82
|
id: string;
|
|
@@ -272,7 +291,7 @@ interface ChatCompletionOptions {
|
|
|
272
291
|
*/
|
|
273
292
|
fallbackModels?: string[];
|
|
274
293
|
}
|
|
275
|
-
type RoutingProfile = "
|
|
294
|
+
type RoutingProfile = "eco" | "auto" | "premium";
|
|
276
295
|
type RoutingTier = "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING";
|
|
277
296
|
interface RoutingDecision {
|
|
278
297
|
model: string;
|
|
@@ -283,6 +302,10 @@ interface RoutingDecision {
|
|
|
283
302
|
costEstimate: number;
|
|
284
303
|
baselineCost: number;
|
|
285
304
|
savings: number;
|
|
305
|
+
/** Routing profile applied by clawrouter (may include "agentic" on gateway responses). */
|
|
306
|
+
profile?: RoutingProfile | "agentic";
|
|
307
|
+
/** Score used when agentic routing is active. */
|
|
308
|
+
agenticScore?: number;
|
|
286
309
|
/**
|
|
287
310
|
* Remaining tier models with known pricing, in fallback order. `chat()`
|
|
288
311
|
* walks this list when the primary model hits a transient error
|
|
@@ -291,7 +314,7 @@ interface RoutingDecision {
|
|
|
291
314
|
fallbacks?: string[];
|
|
292
315
|
}
|
|
293
316
|
interface SmartChatOptions extends ChatOptions {
|
|
294
|
-
/** Routing profile:
|
|
317
|
+
/** Routing profile: eco (budget), auto (balanced), premium (best quality) */
|
|
295
318
|
routingProfile?: RoutingProfile;
|
|
296
319
|
/** Maximum output tokens (used for cost estimation) */
|
|
297
320
|
maxOutputTokens?: number;
|
|
@@ -762,9 +785,6 @@ declare class LLMClient {
|
|
|
762
785
|
*
|
|
763
786
|
* @example With routing profile
|
|
764
787
|
* ```ts
|
|
765
|
-
* // Free tier only (zero cost)
|
|
766
|
-
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
767
|
-
*
|
|
768
788
|
* // Eco mode (budget optimized)
|
|
769
789
|
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
770
790
|
*
|
|
@@ -811,6 +831,17 @@ declare class LLMClient {
|
|
|
811
831
|
* cost_log.jsonl is a single source of truth regardless of caller.
|
|
812
832
|
*/
|
|
813
833
|
private recordCost;
|
|
834
|
+
/**
|
|
835
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
836
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
837
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
838
|
+
* on the response when it served a paid request from a free model
|
|
839
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
840
|
+
* to the caller, the user gets a different model than requested with
|
|
841
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
842
|
+
* balance didn't change.
|
|
843
|
+
*/
|
|
844
|
+
private parseChatResponse;
|
|
814
845
|
/**
|
|
815
846
|
* Make a request with automatic x402 payment handling.
|
|
816
847
|
*/
|
|
@@ -1323,6 +1354,68 @@ declare class MusicClient {
|
|
|
1323
1354
|
getSpending(): Spending;
|
|
1324
1355
|
}
|
|
1325
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* BlockRun Video Client - Generate short AI videos via x402 micropayments.
|
|
1359
|
+
*
|
|
1360
|
+
* SECURITY NOTE - Private Key Handling:
|
|
1361
|
+
* Your private key NEVER leaves your machine. Here's what happens:
|
|
1362
|
+
* 1. Key stays local - only used to sign an EIP-712 typed data message
|
|
1363
|
+
* 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
|
|
1364
|
+
* 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
|
|
1365
|
+
*
|
|
1366
|
+
* Async flow (client-polled):
|
|
1367
|
+
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1368
|
+
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1369
|
+
*
|
|
1370
|
+
* The client signs ONCE and replays the same PAYMENT-SIGNATURE on every poll.
|
|
1371
|
+
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1372
|
+
* the caller giving up = zero charge.
|
|
1373
|
+
*
|
|
1374
|
+
* Usage:
|
|
1375
|
+
* import { VideoClient } from '@blockrun/llm';
|
|
1376
|
+
*
|
|
1377
|
+
* const client = new VideoClient({ privateKey: '0x...' });
|
|
1378
|
+
* const result = await client.generate('a red apple slowly spinning on a wooden table');
|
|
1379
|
+
* console.log(result.data[0].url); // permanent MP4 URL
|
|
1380
|
+
* console.log(result.data[0].duration_seconds);
|
|
1381
|
+
*/
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* BlockRun Video Generation Client.
|
|
1385
|
+
*
|
|
1386
|
+
* Supports xAI Grok Imagine Video and ByteDance Seedance (1.5 Pro /
|
|
1387
|
+
* 2.0 Fast / 2.0 Pro) with automatic x402 micropayments on Base.
|
|
1388
|
+
*/
|
|
1389
|
+
declare class VideoClient {
|
|
1390
|
+
private account;
|
|
1391
|
+
private privateKey;
|
|
1392
|
+
private apiUrl;
|
|
1393
|
+
private timeout;
|
|
1394
|
+
private sessionTotalUsd;
|
|
1395
|
+
private sessionCalls;
|
|
1396
|
+
constructor(options?: VideoClientOptions);
|
|
1397
|
+
/**
|
|
1398
|
+
* Generate a short video clip from a text prompt (or text + image).
|
|
1399
|
+
*
|
|
1400
|
+
* Submits an async job, then polls until the video is ready. Typical total
|
|
1401
|
+
* wall-time is 60-180s. If upstream runs past the budget (default 5min),
|
|
1402
|
+
* throws without charging.
|
|
1403
|
+
*
|
|
1404
|
+
* @param prompt - Text description of the video
|
|
1405
|
+
* @param options - Optional generation parameters
|
|
1406
|
+
*/
|
|
1407
|
+
generate(prompt: string, options?: VideoGenerateOptions & {
|
|
1408
|
+
budgetMs?: number;
|
|
1409
|
+
}): Promise<VideoResponse>;
|
|
1410
|
+
private submitAndPoll;
|
|
1411
|
+
private absolute;
|
|
1412
|
+
private extractPaymentRequired;
|
|
1413
|
+
private throwApiError;
|
|
1414
|
+
private fetchWithTimeout;
|
|
1415
|
+
getWalletAddress(): string;
|
|
1416
|
+
getSpending(): Spending;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1326
1419
|
/**
|
|
1327
1420
|
* BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
|
|
1328
1421
|
*
|
|
@@ -2100,4 +2193,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
2100
2193
|
*/
|
|
2101
2194
|
declare function validateTopP(topP?: number): void;
|
|
2102
2195
|
|
|
2103
|
-
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, 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 };
|
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,25 @@ interface ChatResponse {
|
|
|
58
58
|
choices: ChatChoice[];
|
|
59
59
|
usage?: ChatUsage;
|
|
60
60
|
citations?: string[];
|
|
61
|
+
/**
|
|
62
|
+
* Populated when the gateway transparently substituted a different
|
|
63
|
+
* model for the one the caller asked for — typically because the
|
|
64
|
+
* requested model errored and the gateway routed to a free fallback
|
|
65
|
+
* to fulfil the request. When `used` is true:
|
|
66
|
+
* - `model` is the model that actually answered (vs `ChatResponse.model`
|
|
67
|
+
* which historically reflected the requested model id).
|
|
68
|
+
* - `settlementSkipped` is `true` when the gateway also skipped the
|
|
69
|
+
* on-chain settle — i.e. the user was not charged for this call
|
|
70
|
+
* because a free fallback served it.
|
|
71
|
+
* Surfaced from the gateway's `X-Fallback-Used / X-Fallback-Model /
|
|
72
|
+
* X-Settlement-Skipped` response headers. Absent when the headers
|
|
73
|
+
* aren't present (most calls).
|
|
74
|
+
*/
|
|
75
|
+
fallback?: {
|
|
76
|
+
used: true;
|
|
77
|
+
model?: string;
|
|
78
|
+
settlementSkipped?: boolean;
|
|
79
|
+
};
|
|
61
80
|
}
|
|
62
81
|
interface Model {
|
|
63
82
|
id: string;
|
|
@@ -272,7 +291,7 @@ interface ChatCompletionOptions {
|
|
|
272
291
|
*/
|
|
273
292
|
fallbackModels?: string[];
|
|
274
293
|
}
|
|
275
|
-
type RoutingProfile = "
|
|
294
|
+
type RoutingProfile = "eco" | "auto" | "premium";
|
|
276
295
|
type RoutingTier = "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING";
|
|
277
296
|
interface RoutingDecision {
|
|
278
297
|
model: string;
|
|
@@ -283,6 +302,10 @@ interface RoutingDecision {
|
|
|
283
302
|
costEstimate: number;
|
|
284
303
|
baselineCost: number;
|
|
285
304
|
savings: number;
|
|
305
|
+
/** Routing profile applied by clawrouter (may include "agentic" on gateway responses). */
|
|
306
|
+
profile?: RoutingProfile | "agentic";
|
|
307
|
+
/** Score used when agentic routing is active. */
|
|
308
|
+
agenticScore?: number;
|
|
286
309
|
/**
|
|
287
310
|
* Remaining tier models with known pricing, in fallback order. `chat()`
|
|
288
311
|
* walks this list when the primary model hits a transient error
|
|
@@ -291,7 +314,7 @@ interface RoutingDecision {
|
|
|
291
314
|
fallbacks?: string[];
|
|
292
315
|
}
|
|
293
316
|
interface SmartChatOptions extends ChatOptions {
|
|
294
|
-
/** Routing profile:
|
|
317
|
+
/** Routing profile: eco (budget), auto (balanced), premium (best quality) */
|
|
295
318
|
routingProfile?: RoutingProfile;
|
|
296
319
|
/** Maximum output tokens (used for cost estimation) */
|
|
297
320
|
maxOutputTokens?: number;
|
|
@@ -762,9 +785,6 @@ declare class LLMClient {
|
|
|
762
785
|
*
|
|
763
786
|
* @example With routing profile
|
|
764
787
|
* ```ts
|
|
765
|
-
* // Free tier only (zero cost)
|
|
766
|
-
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
767
|
-
*
|
|
768
788
|
* // Eco mode (budget optimized)
|
|
769
789
|
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
770
790
|
*
|
|
@@ -811,6 +831,17 @@ declare class LLMClient {
|
|
|
811
831
|
* cost_log.jsonl is a single source of truth regardless of caller.
|
|
812
832
|
*/
|
|
813
833
|
private recordCost;
|
|
834
|
+
/**
|
|
835
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
836
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
837
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
838
|
+
* on the response when it served a paid request from a free model
|
|
839
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
840
|
+
* to the caller, the user gets a different model than requested with
|
|
841
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
842
|
+
* balance didn't change.
|
|
843
|
+
*/
|
|
844
|
+
private parseChatResponse;
|
|
814
845
|
/**
|
|
815
846
|
* Make a request with automatic x402 payment handling.
|
|
816
847
|
*/
|
|
@@ -1323,6 +1354,68 @@ declare class MusicClient {
|
|
|
1323
1354
|
getSpending(): Spending;
|
|
1324
1355
|
}
|
|
1325
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* BlockRun Video Client - Generate short AI videos via x402 micropayments.
|
|
1359
|
+
*
|
|
1360
|
+
* SECURITY NOTE - Private Key Handling:
|
|
1361
|
+
* Your private key NEVER leaves your machine. Here's what happens:
|
|
1362
|
+
* 1. Key stays local - only used to sign an EIP-712 typed data message
|
|
1363
|
+
* 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
|
|
1364
|
+
* 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
|
|
1365
|
+
*
|
|
1366
|
+
* Async flow (client-polled):
|
|
1367
|
+
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1368
|
+
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1369
|
+
*
|
|
1370
|
+
* The client signs ONCE and replays the same PAYMENT-SIGNATURE on every poll.
|
|
1371
|
+
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1372
|
+
* the caller giving up = zero charge.
|
|
1373
|
+
*
|
|
1374
|
+
* Usage:
|
|
1375
|
+
* import { VideoClient } from '@blockrun/llm';
|
|
1376
|
+
*
|
|
1377
|
+
* const client = new VideoClient({ privateKey: '0x...' });
|
|
1378
|
+
* const result = await client.generate('a red apple slowly spinning on a wooden table');
|
|
1379
|
+
* console.log(result.data[0].url); // permanent MP4 URL
|
|
1380
|
+
* console.log(result.data[0].duration_seconds);
|
|
1381
|
+
*/
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* BlockRun Video Generation Client.
|
|
1385
|
+
*
|
|
1386
|
+
* Supports xAI Grok Imagine Video and ByteDance Seedance (1.5 Pro /
|
|
1387
|
+
* 2.0 Fast / 2.0 Pro) with automatic x402 micropayments on Base.
|
|
1388
|
+
*/
|
|
1389
|
+
declare class VideoClient {
|
|
1390
|
+
private account;
|
|
1391
|
+
private privateKey;
|
|
1392
|
+
private apiUrl;
|
|
1393
|
+
private timeout;
|
|
1394
|
+
private sessionTotalUsd;
|
|
1395
|
+
private sessionCalls;
|
|
1396
|
+
constructor(options?: VideoClientOptions);
|
|
1397
|
+
/**
|
|
1398
|
+
* Generate a short video clip from a text prompt (or text + image).
|
|
1399
|
+
*
|
|
1400
|
+
* Submits an async job, then polls until the video is ready. Typical total
|
|
1401
|
+
* wall-time is 60-180s. If upstream runs past the budget (default 5min),
|
|
1402
|
+
* throws without charging.
|
|
1403
|
+
*
|
|
1404
|
+
* @param prompt - Text description of the video
|
|
1405
|
+
* @param options - Optional generation parameters
|
|
1406
|
+
*/
|
|
1407
|
+
generate(prompt: string, options?: VideoGenerateOptions & {
|
|
1408
|
+
budgetMs?: number;
|
|
1409
|
+
}): Promise<VideoResponse>;
|
|
1410
|
+
private submitAndPoll;
|
|
1411
|
+
private absolute;
|
|
1412
|
+
private extractPaymentRequired;
|
|
1413
|
+
private throwApiError;
|
|
1414
|
+
private fetchWithTimeout;
|
|
1415
|
+
getWalletAddress(): string;
|
|
1416
|
+
getSpending(): Spending;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1326
1419
|
/**
|
|
1327
1420
|
* BlockRun Search Client - Standalone Grok Live Search via x402 micropayments.
|
|
1328
1421
|
*
|
|
@@ -2100,4 +2193,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
2100
2193
|
*/
|
|
2101
2194
|
declare function validateTopP(topP?: number): void;
|
|
2102
2195
|
|
|
2103
|
-
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, 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 };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -524,9 +524,6 @@ var LLMClient = class _LLMClient {
|
|
|
524
524
|
*
|
|
525
525
|
* @example With routing profile
|
|
526
526
|
* ```ts
|
|
527
|
-
* // Free tier only (zero cost)
|
|
528
|
-
* const result = await client.smartChat('Hello!', { routingProfile: 'free' });
|
|
529
|
-
*
|
|
530
527
|
* // Eco mode (budget optimized)
|
|
531
528
|
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
|
|
532
529
|
*
|
|
@@ -686,6 +683,28 @@ var LLMClient = class _LLMClient {
|
|
|
686
683
|
} catch {
|
|
687
684
|
}
|
|
688
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Parse the chat response JSON and attach `fallback` metadata when the
|
|
688
|
+
* gateway signalled a transparent free-fallback substitution. The
|
|
689
|
+
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
|
|
690
|
+
* on the response when it served a paid request from a free model
|
|
691
|
+
* (route.ts createPaymentResponseHeader path). Without surfacing these
|
|
692
|
+
* to the caller, the user gets a different model than requested with
|
|
693
|
+
* no visibility — silent quality drop and no clue why the on-chain
|
|
694
|
+
* balance didn't change.
|
|
695
|
+
*/
|
|
696
|
+
async parseChatResponse(response) {
|
|
697
|
+
const body = await response.json();
|
|
698
|
+
const used = response.headers.get("X-Fallback-Used") === "true";
|
|
699
|
+
if (used) {
|
|
700
|
+
body.fallback = {
|
|
701
|
+
used: true,
|
|
702
|
+
model: response.headers.get("X-Fallback-Model") || void 0,
|
|
703
|
+
settlementSkipped: response.headers.get("X-Settlement-Skipped") === "free-fallback"
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
return body;
|
|
707
|
+
}
|
|
689
708
|
/**
|
|
690
709
|
* Make a request with automatic x402 payment handling.
|
|
691
710
|
*/
|
|
@@ -714,7 +733,7 @@ var LLMClient = class _LLMClient {
|
|
|
714
733
|
}
|
|
715
734
|
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
716
735
|
}
|
|
717
|
-
return
|
|
736
|
+
return this.parseChatResponse(retryResp);
|
|
718
737
|
}
|
|
719
738
|
}
|
|
720
739
|
if (response.status === 402) {
|
|
@@ -733,7 +752,7 @@ var LLMClient = class _LLMClient {
|
|
|
733
752
|
sanitizeErrorResponse(errorBody)
|
|
734
753
|
);
|
|
735
754
|
}
|
|
736
|
-
return
|
|
755
|
+
return this.parseChatResponse(response);
|
|
737
756
|
}
|
|
738
757
|
/**
|
|
739
758
|
* Handle 402 response: parse requirements, sign payment, retry.
|
|
@@ -808,7 +827,7 @@ var LLMClient = class _LLMClient {
|
|
|
808
827
|
this.sessionCalls += 1;
|
|
809
828
|
this.sessionTotalUsd += costUsd2;
|
|
810
829
|
this.recordCost(url, costUsd2, { body, network: details.network });
|
|
811
|
-
return
|
|
830
|
+
return this.parseChatResponse(retryResp2);
|
|
812
831
|
}
|
|
813
832
|
}
|
|
814
833
|
if (retryResponse.status === 402) {
|
|
@@ -831,7 +850,7 @@ var LLMClient = class _LLMClient {
|
|
|
831
850
|
this.sessionCalls += 1;
|
|
832
851
|
this.sessionTotalUsd += costUsd;
|
|
833
852
|
this.recordCost(url, costUsd, { body, network: details.network });
|
|
834
|
-
return
|
|
853
|
+
return this.parseChatResponse(retryResponse);
|
|
835
854
|
}
|
|
836
855
|
/**
|
|
837
856
|
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
@@ -2249,15 +2268,21 @@ var MusicClient = class {
|
|
|
2249
2268
|
}
|
|
2250
2269
|
};
|
|
2251
2270
|
|
|
2252
|
-
// src/
|
|
2271
|
+
// src/video.ts
|
|
2253
2272
|
import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
|
|
2254
2273
|
var DEFAULT_API_URL4 = "https://blockrun.ai/api";
|
|
2255
|
-
var
|
|
2256
|
-
var
|
|
2274
|
+
var DEFAULT_MODEL3 = "xai/grok-imagine-video";
|
|
2275
|
+
var DEFAULT_TIMEOUT4 = 12e4;
|
|
2276
|
+
var POLL_INTERVAL_MS2 = 5e3;
|
|
2277
|
+
var DEFAULT_GENERATE_BUDGET_MS = 3e5;
|
|
2278
|
+
var MAX_TIMEOUT_SECONDS = 600;
|
|
2279
|
+
var VideoClient = class {
|
|
2257
2280
|
account;
|
|
2258
2281
|
privateKey;
|
|
2259
2282
|
apiUrl;
|
|
2260
2283
|
timeout;
|
|
2284
|
+
sessionTotalUsd = 0;
|
|
2285
|
+
sessionCalls = 0;
|
|
2261
2286
|
constructor(options = {}) {
|
|
2262
2287
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2263
2288
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2274,6 +2299,193 @@ var SearchClient = class {
|
|
|
2274
2299
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2275
2300
|
this.timeout = options.timeout || DEFAULT_TIMEOUT4;
|
|
2276
2301
|
}
|
|
2302
|
+
/**
|
|
2303
|
+
* Generate a short video clip from a text prompt (or text + image).
|
|
2304
|
+
*
|
|
2305
|
+
* Submits an async job, then polls until the video is ready. Typical total
|
|
2306
|
+
* wall-time is 60-180s. If upstream runs past the budget (default 5min),
|
|
2307
|
+
* throws without charging.
|
|
2308
|
+
*
|
|
2309
|
+
* @param prompt - Text description of the video
|
|
2310
|
+
* @param options - Optional generation parameters
|
|
2311
|
+
*/
|
|
2312
|
+
async generate(prompt, options) {
|
|
2313
|
+
const body = {
|
|
2314
|
+
model: options?.model || DEFAULT_MODEL3,
|
|
2315
|
+
prompt
|
|
2316
|
+
};
|
|
2317
|
+
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2318
|
+
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2319
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_GENERATE_BUDGET_MS;
|
|
2320
|
+
return this.submitAndPoll(body, budgetMs);
|
|
2321
|
+
}
|
|
2322
|
+
// --------------------------------------------------------------------
|
|
2323
|
+
// Internal: async submit + poll
|
|
2324
|
+
// --------------------------------------------------------------------
|
|
2325
|
+
async submitAndPoll(body, budgetMs) {
|
|
2326
|
+
const submitUrl = `${this.apiUrl}/v1/videos/generations`;
|
|
2327
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
2328
|
+
method: "POST",
|
|
2329
|
+
headers: { "Content-Type": "application/json" },
|
|
2330
|
+
body: JSON.stringify(body)
|
|
2331
|
+
});
|
|
2332
|
+
if (resp402.status !== 402) {
|
|
2333
|
+
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2334
|
+
}
|
|
2335
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2336
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2337
|
+
const paymentPayload = await createPaymentPayload(
|
|
2338
|
+
this.privateKey,
|
|
2339
|
+
this.account.address,
|
|
2340
|
+
details.recipient,
|
|
2341
|
+
details.amount,
|
|
2342
|
+
details.network || "eip155:8453",
|
|
2343
|
+
{
|
|
2344
|
+
resourceUrl: details.resource?.url || submitUrl,
|
|
2345
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2346
|
+
// Ensure signed auth covers the entire polling window.
|
|
2347
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2348
|
+
extra: details.extra
|
|
2349
|
+
}
|
|
2350
|
+
);
|
|
2351
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers: {
|
|
2354
|
+
"Content-Type": "application/json",
|
|
2355
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2356
|
+
},
|
|
2357
|
+
body: JSON.stringify(body)
|
|
2358
|
+
});
|
|
2359
|
+
if (submitResp.status === 402) {
|
|
2360
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2361
|
+
}
|
|
2362
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
2363
|
+
await this.throwApiError(submitResp, "Submit failed");
|
|
2364
|
+
}
|
|
2365
|
+
const submitData = await submitResp.json();
|
|
2366
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
2367
|
+
throw new APIError(
|
|
2368
|
+
"Submit response missing id/poll_url",
|
|
2369
|
+
submitResp.status,
|
|
2370
|
+
{ response: submitData }
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
2374
|
+
const deadline = Date.now() + budgetMs;
|
|
2375
|
+
let lastStatus = submitData.status || "queued";
|
|
2376
|
+
while (Date.now() < deadline) {
|
|
2377
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2378
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
2379
|
+
method: "GET",
|
|
2380
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
2381
|
+
});
|
|
2382
|
+
let pollData = {};
|
|
2383
|
+
try {
|
|
2384
|
+
pollData = await pollResp.json();
|
|
2385
|
+
} catch {
|
|
2386
|
+
}
|
|
2387
|
+
lastStatus = pollData.status || lastStatus;
|
|
2388
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
if (lastStatus === "failed") {
|
|
2392
|
+
throw new APIError(
|
|
2393
|
+
`Upstream generation failed: ${pollData.error || "unknown"}`,
|
|
2394
|
+
pollResp.status,
|
|
2395
|
+
sanitizeErrorResponse(pollData)
|
|
2396
|
+
);
|
|
2397
|
+
}
|
|
2398
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
2399
|
+
const data = pollData;
|
|
2400
|
+
const billedSeconds = typeof body.duration_seconds === "number" ? body.duration_seconds : 8;
|
|
2401
|
+
this.sessionCalls++;
|
|
2402
|
+
this.sessionTotalUsd += 0.05 * billedSeconds * 1.05;
|
|
2403
|
+
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
2404
|
+
if (txHash) data.txHash = txHash;
|
|
2405
|
+
return data;
|
|
2406
|
+
}
|
|
2407
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2408
|
+
await this.throwApiError(pollResp, "Poll failed");
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
throw new APIError(
|
|
2412
|
+
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2413
|
+
504,
|
|
2414
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
2415
|
+
);
|
|
2416
|
+
}
|
|
2417
|
+
absolute(url) {
|
|
2418
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
2419
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
2420
|
+
return `${base}${url}`;
|
|
2421
|
+
}
|
|
2422
|
+
async extractPaymentRequired(resp) {
|
|
2423
|
+
const header = resp.headers.get("payment-required");
|
|
2424
|
+
if (header) return parsePaymentRequired(header);
|
|
2425
|
+
try {
|
|
2426
|
+
const body = await resp.json();
|
|
2427
|
+
if (body && (body.x402Version !== void 0 || body.accepts !== void 0)) {
|
|
2428
|
+
return body;
|
|
2429
|
+
}
|
|
2430
|
+
} catch {
|
|
2431
|
+
}
|
|
2432
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2433
|
+
}
|
|
2434
|
+
async throwApiError(resp, prefix) {
|
|
2435
|
+
let errorBody;
|
|
2436
|
+
try {
|
|
2437
|
+
errorBody = await resp.json();
|
|
2438
|
+
} catch {
|
|
2439
|
+
errorBody = { error: "Request failed" };
|
|
2440
|
+
}
|
|
2441
|
+
throw new APIError(
|
|
2442
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
2443
|
+
resp.status,
|
|
2444
|
+
sanitizeErrorResponse(errorBody)
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
async fetchWithTimeout(url, options) {
|
|
2448
|
+
const controller = new AbortController();
|
|
2449
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2450
|
+
try {
|
|
2451
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
2452
|
+
} finally {
|
|
2453
|
+
clearTimeout(timeoutId);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
getWalletAddress() {
|
|
2457
|
+
return this.account.address;
|
|
2458
|
+
}
|
|
2459
|
+
getSpending() {
|
|
2460
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
|
|
2464
|
+
// src/search.ts
|
|
2465
|
+
import { privateKeyToAccount as privateKeyToAccount5 } from "viem/accounts";
|
|
2466
|
+
var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
2467
|
+
var DEFAULT_TIMEOUT5 = 6e4;
|
|
2468
|
+
var SearchClient = class {
|
|
2469
|
+
account;
|
|
2470
|
+
privateKey;
|
|
2471
|
+
apiUrl;
|
|
2472
|
+
timeout;
|
|
2473
|
+
constructor(options = {}) {
|
|
2474
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2475
|
+
const privateKey = options.privateKey || envKey;
|
|
2476
|
+
if (!privateKey) {
|
|
2477
|
+
throw new Error(
|
|
2478
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
validatePrivateKey(privateKey);
|
|
2482
|
+
this.privateKey = privateKey;
|
|
2483
|
+
this.account = privateKeyToAccount5(privateKey);
|
|
2484
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL5;
|
|
2485
|
+
validateApiUrl(apiUrl);
|
|
2486
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2487
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT5;
|
|
2488
|
+
}
|
|
2277
2489
|
async search(query, options) {
|
|
2278
2490
|
if (!query || query.length > 1e3) {
|
|
2279
2491
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2386,9 +2598,9 @@ var SearchClient = class {
|
|
|
2386
2598
|
};
|
|
2387
2599
|
|
|
2388
2600
|
// src/x-client.ts
|
|
2389
|
-
import { privateKeyToAccount as
|
|
2390
|
-
var
|
|
2391
|
-
var
|
|
2601
|
+
import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
|
|
2602
|
+
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2603
|
+
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2392
2604
|
var XClient = class _XClient {
|
|
2393
2605
|
account;
|
|
2394
2606
|
privateKey;
|
|
@@ -2411,11 +2623,11 @@ var XClient = class _XClient {
|
|
|
2411
2623
|
}
|
|
2412
2624
|
validatePrivateKey(privateKey);
|
|
2413
2625
|
this.privateKey = privateKey;
|
|
2414
|
-
this.account =
|
|
2415
|
-
const apiUrl = options.apiUrl ||
|
|
2626
|
+
this.account = privateKeyToAccount6(privateKey);
|
|
2627
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL6;
|
|
2416
2628
|
validateApiUrl(apiUrl);
|
|
2417
2629
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2418
|
-
this.timeout = options.timeout ||
|
|
2630
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2419
2631
|
}
|
|
2420
2632
|
// ───────── User endpoints ─────────
|
|
2421
2633
|
userLookup(usernames) {
|
|
@@ -2593,9 +2805,9 @@ var XClient = class _XClient {
|
|
|
2593
2805
|
};
|
|
2594
2806
|
|
|
2595
2807
|
// src/price.ts
|
|
2596
|
-
import { privateKeyToAccount as
|
|
2597
|
-
var
|
|
2598
|
-
var
|
|
2808
|
+
import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
|
|
2809
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2810
|
+
var DEFAULT_TIMEOUT7 = 3e4;
|
|
2599
2811
|
var PriceClient = class {
|
|
2600
2812
|
account = null;
|
|
2601
2813
|
privateKey = null;
|
|
@@ -2613,12 +2825,12 @@ var PriceClient = class {
|
|
|
2613
2825
|
if (privateKey) {
|
|
2614
2826
|
validatePrivateKey(privateKey);
|
|
2615
2827
|
this.privateKey = privateKey;
|
|
2616
|
-
this.account =
|
|
2828
|
+
this.account = privateKeyToAccount7(privateKey);
|
|
2617
2829
|
}
|
|
2618
|
-
const apiUrl = options.apiUrl ||
|
|
2830
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2619
2831
|
validateApiUrl(apiUrl);
|
|
2620
2832
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2621
|
-
this.timeout = options.timeout ||
|
|
2833
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2622
2834
|
}
|
|
2623
2835
|
async price(category, symbol, options) {
|
|
2624
2836
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -2797,7 +3009,7 @@ function buildUrl(base, query) {
|
|
|
2797
3009
|
}
|
|
2798
3010
|
|
|
2799
3011
|
// src/wallet.ts
|
|
2800
|
-
import { privateKeyToAccount as
|
|
3012
|
+
import { privateKeyToAccount as privateKeyToAccount8, generatePrivateKey } from "viem/accounts";
|
|
2801
3013
|
import * as fs2 from "fs";
|
|
2802
3014
|
import * as path2 from "path";
|
|
2803
3015
|
import * as os2 from "os";
|
|
@@ -2807,7 +3019,7 @@ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
|
2807
3019
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
2808
3020
|
function createWallet() {
|
|
2809
3021
|
const privateKey = generatePrivateKey();
|
|
2810
|
-
const account =
|
|
3022
|
+
const account = privateKeyToAccount8(privateKey);
|
|
2811
3023
|
return {
|
|
2812
3024
|
address: account.address,
|
|
2813
3025
|
privateKey
|
|
@@ -2863,12 +3075,12 @@ function loadWallet() {
|
|
|
2863
3075
|
function getOrCreateWallet() {
|
|
2864
3076
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2865
3077
|
if (envKey) {
|
|
2866
|
-
const account =
|
|
3078
|
+
const account = privateKeyToAccount8(envKey);
|
|
2867
3079
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
2868
3080
|
}
|
|
2869
3081
|
const fileKey = loadWallet();
|
|
2870
3082
|
if (fileKey) {
|
|
2871
|
-
const account =
|
|
3083
|
+
const account = privateKeyToAccount8(fileKey);
|
|
2872
3084
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
2873
3085
|
}
|
|
2874
3086
|
const { address, privateKey } = createWallet();
|
|
@@ -2878,11 +3090,11 @@ function getOrCreateWallet() {
|
|
|
2878
3090
|
function getWalletAddress() {
|
|
2879
3091
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2880
3092
|
if (envKey) {
|
|
2881
|
-
return
|
|
3093
|
+
return privateKeyToAccount8(envKey).address;
|
|
2882
3094
|
}
|
|
2883
3095
|
const fileKey = loadWallet();
|
|
2884
3096
|
if (fileKey) {
|
|
2885
|
-
return
|
|
3097
|
+
return privateKeyToAccount8(fileKey).address;
|
|
2886
3098
|
}
|
|
2887
3099
|
return null;
|
|
2888
3100
|
}
|
|
@@ -3062,7 +3274,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3062
3274
|
// src/solana-client.ts
|
|
3063
3275
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3064
3276
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3065
|
-
var
|
|
3277
|
+
var DEFAULT_TIMEOUT8 = 6e4;
|
|
3066
3278
|
var SDK_VERSION2 = "0.3.0";
|
|
3067
3279
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3068
3280
|
var SolanaLLMClient = class {
|
|
@@ -3087,7 +3299,7 @@ var SolanaLLMClient = class {
|
|
|
3087
3299
|
validateApiUrl(apiUrl);
|
|
3088
3300
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3089
3301
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3090
|
-
this.timeout = options.timeout ||
|
|
3302
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
3091
3303
|
}
|
|
3092
3304
|
/** Get Solana wallet address (public key in base58). */
|
|
3093
3305
|
async getWalletAddress() {
|
|
@@ -3912,6 +4124,9 @@ var ChatCompletions = class {
|
|
|
3912
4124
|
this.apiUrl = apiUrl;
|
|
3913
4125
|
this.timeout = timeout;
|
|
3914
4126
|
}
|
|
4127
|
+
client;
|
|
4128
|
+
apiUrl;
|
|
4129
|
+
timeout;
|
|
3915
4130
|
async create(params) {
|
|
3916
4131
|
if (params.stream) {
|
|
3917
4132
|
return this.createStream(params);
|
|
@@ -3991,7 +4206,7 @@ var OpenAI = class {
|
|
|
3991
4206
|
};
|
|
3992
4207
|
|
|
3993
4208
|
// src/anthropic-compat.ts
|
|
3994
|
-
import { privateKeyToAccount as
|
|
4209
|
+
import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
|
|
3995
4210
|
var AnthropicClient = class {
|
|
3996
4211
|
_client = null;
|
|
3997
4212
|
_clientPromise = null;
|
|
@@ -4004,7 +4219,7 @@ var AnthropicClient = class {
|
|
|
4004
4219
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4005
4220
|
validatePrivateKey(key);
|
|
4006
4221
|
this._privateKey = key;
|
|
4007
|
-
this._account =
|
|
4222
|
+
this._account = privateKeyToAccount9(this._privateKey);
|
|
4008
4223
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4009
4224
|
validateApiUrl(apiUrl);
|
|
4010
4225
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4117,6 +4332,7 @@ export {
|
|
|
4117
4332
|
USDC_BASE,
|
|
4118
4333
|
USDC_BASE_CONTRACT,
|
|
4119
4334
|
USDC_SOLANA,
|
|
4335
|
+
VideoClient,
|
|
4120
4336
|
WALLET_DIR_PATH,
|
|
4121
4337
|
WALLET_FILE_PATH,
|
|
4122
4338
|
XClient,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "BlockRun SDK - Pay-per-request AI (LLM, Image, Video, Music) via x402 on Base and Solana",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -48,23 +48,23 @@
|
|
|
48
48
|
"url": "https://github.com/BlockRunAI/blockrun-llm-ts/issues"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@blockrun/clawrouter": "^0.12.
|
|
51
|
+
"@blockrun/clawrouter": "^0.12.190",
|
|
52
52
|
"bs58": "^6.0.0",
|
|
53
|
-
"viem": "^2.
|
|
53
|
+
"viem": "^2.49.0"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
56
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
57
|
-
"@solana/spl-token": "^0.4.
|
|
58
|
-
"@solana/web3.js": "^1.98.
|
|
57
|
+
"@solana/spl-token": "^0.4.14",
|
|
58
|
+
"@solana/web3.js": "^1.98.4"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@eslint/js": "^9.
|
|
62
|
-
"@types/node": "^20.
|
|
63
|
-
"eslint": "^9.
|
|
64
|
-
"tsup": "^8.
|
|
65
|
-
"typescript": "^5.
|
|
66
|
-
"typescript-eslint": "^8.
|
|
67
|
-
"vitest": "^1.
|
|
61
|
+
"@eslint/js": "^9.39.4",
|
|
62
|
+
"@types/node": "^20.19.41",
|
|
63
|
+
"eslint": "^9.39.4",
|
|
64
|
+
"tsup": "^8.5.1",
|
|
65
|
+
"typescript": "^5.9.3",
|
|
66
|
+
"typescript-eslint": "^8.59.3",
|
|
67
|
+
"vitest": "^1.6.1"
|
|
68
68
|
},
|
|
69
69
|
"engines": {
|
|
70
70
|
"node": ">=20"
|