@blockrun/llm 3.2.2 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +53 -23
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +53 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2522,8 +2522,9 @@ var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
|
2522
2522
|
var DEFAULT_MODEL4 = "xai/grok-imagine-video";
|
|
2523
2523
|
var DEFAULT_TIMEOUT5 = 12e4;
|
|
2524
2524
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
2525
|
-
var DEFAULT_GENERATE_BUDGET_MS =
|
|
2525
|
+
var DEFAULT_GENERATE_BUDGET_MS = 9e5;
|
|
2526
2526
|
var MAX_TIMEOUT_SECONDS = 600;
|
|
2527
|
+
var MAX_POLL_RESIGNS = 2;
|
|
2527
2528
|
var VideoClient = class {
|
|
2528
2529
|
account;
|
|
2529
2530
|
privateKey;
|
|
@@ -2551,8 +2552,9 @@ var VideoClient = class {
|
|
|
2551
2552
|
* Generate a short video clip from a text prompt (or text + image).
|
|
2552
2553
|
*
|
|
2553
2554
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
2554
|
-
* wall-time is 60-180s
|
|
2555
|
-
*
|
|
2555
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
2556
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
2557
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
2556
2558
|
*
|
|
2557
2559
|
* @param prompt - Text description of the video
|
|
2558
2560
|
* @param options - Optional generation parameters
|
|
@@ -2670,21 +2672,9 @@ var VideoClient = class {
|
|
|
2670
2672
|
if (resp402.status !== 402) {
|
|
2671
2673
|
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2672
2674
|
}
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
this.privateKey,
|
|
2677
|
-
this.account.address,
|
|
2678
|
-
details.recipient,
|
|
2679
|
-
details.amount,
|
|
2680
|
-
details.network || "eip155:8453",
|
|
2681
|
-
{
|
|
2682
|
-
resourceUrl: details.resource?.url || submitUrl,
|
|
2683
|
-
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2684
|
-
// Ensure signed auth covers the entire polling window.
|
|
2685
|
-
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2686
|
-
extra: details.extra
|
|
2687
|
-
}
|
|
2675
|
+
let { payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2676
|
+
resp402,
|
|
2677
|
+
submitUrl
|
|
2688
2678
|
);
|
|
2689
2679
|
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2690
2680
|
method: "POST",
|
|
@@ -2711,6 +2701,7 @@ var VideoClient = class {
|
|
|
2711
2701
|
const pollUrl = this.absolute(submitData.poll_url);
|
|
2712
2702
|
const deadline = Date.now() + budgetMs;
|
|
2713
2703
|
let lastStatus = submitData.status || "queued";
|
|
2704
|
+
let resignsLeft = MAX_POLL_RESIGNS;
|
|
2714
2705
|
while (Date.now() < deadline) {
|
|
2715
2706
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2716
2707
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
@@ -2733,24 +2724,63 @@ var VideoClient = class {
|
|
|
2733
2724
|
sanitizeErrorResponse(pollData)
|
|
2734
2725
|
);
|
|
2735
2726
|
}
|
|
2736
|
-
if (
|
|
2727
|
+
if (lastStatus === "completed") {
|
|
2737
2728
|
const data = pollData;
|
|
2738
|
-
const billedSeconds = typeof body.duration_seconds === "number" ? body.duration_seconds : 8;
|
|
2739
2729
|
this.sessionCalls++;
|
|
2740
|
-
this.sessionTotalUsd +=
|
|
2730
|
+
this.sessionTotalUsd += Number(details.amount) / 1e6;
|
|
2741
2731
|
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
2742
2732
|
if (txHash) data.txHash = txHash;
|
|
2743
2733
|
return data;
|
|
2744
2734
|
}
|
|
2735
|
+
if (pollResp.status === 402) {
|
|
2736
|
+
if (resignsLeft > 0) {
|
|
2737
|
+
resignsLeft--;
|
|
2738
|
+
const challenge = await this.fetchWithTimeout(pollUrl, { method: "GET" });
|
|
2739
|
+
if (challenge.status === 402) {
|
|
2740
|
+
({ payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2741
|
+
challenge,
|
|
2742
|
+
pollUrl
|
|
2743
|
+
));
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
throw new PaymentError(
|
|
2748
|
+
"Payment verification failed mid-poll (not a signature-expiry). Check the wallet balance and that you poll from the wallet that submitted the job."
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2745
2751
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2746
2752
|
await this.throwApiError(pollResp, "Poll failed");
|
|
2747
2753
|
}
|
|
2748
2754
|
}
|
|
2749
2755
|
throw new APIError(
|
|
2750
|
-
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2756
|
+
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken. The job is NOT lost: it stays claimable for ~48h \u2014 re-GET poll_url with a fresh signature from the same wallet to fetch (and settle) the finished video.`,
|
|
2751
2757
|
504,
|
|
2752
|
-
{ id: submitData.id, last_status: lastStatus }
|
|
2758
|
+
{ id: submitData.id, last_status: lastStatus, poll_url: pollUrl }
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
2763
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
2764
|
+
* authorization window lapses on long polls.
|
|
2765
|
+
*/
|
|
2766
|
+
async signFromChallenge(resp402, fallbackUrl) {
|
|
2767
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2768
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2769
|
+
const payload = await createPaymentPayload(
|
|
2770
|
+
this.privateKey,
|
|
2771
|
+
this.account.address,
|
|
2772
|
+
details.recipient,
|
|
2773
|
+
details.amount,
|
|
2774
|
+
details.network || "eip155:8453",
|
|
2775
|
+
{
|
|
2776
|
+
resourceUrl: details.resource?.url || fallbackUrl,
|
|
2777
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2778
|
+
// Ensure signed auth covers as much of the polling window as allowed.
|
|
2779
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2780
|
+
extra: details.extra
|
|
2781
|
+
}
|
|
2753
2782
|
);
|
|
2783
|
+
return { payload, details };
|
|
2754
2784
|
}
|
|
2755
2785
|
absolute(url) {
|
|
2756
2786
|
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
package/dist/index.d.cts
CHANGED
|
@@ -1658,7 +1658,8 @@ declare class SpeechClient {
|
|
|
1658
1658
|
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1659
1659
|
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1660
1660
|
*
|
|
1661
|
-
* The client signs
|
|
1661
|
+
* The client signs once and replays the same PAYMENT-SIGNATURE on every poll,
|
|
1662
|
+
* re-signing automatically if the 600s authorization window lapses mid-poll.
|
|
1662
1663
|
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1663
1664
|
* the caller giving up = zero charge.
|
|
1664
1665
|
*
|
|
@@ -1689,8 +1690,9 @@ declare class VideoClient {
|
|
|
1689
1690
|
* Generate a short video clip from a text prompt (or text + image).
|
|
1690
1691
|
*
|
|
1691
1692
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
1692
|
-
* wall-time is 60-180s
|
|
1693
|
-
*
|
|
1693
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
1694
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
1695
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
1694
1696
|
*
|
|
1695
1697
|
* @param prompt - Text description of the video
|
|
1696
1698
|
* @param options - Optional generation parameters
|
|
@@ -1734,6 +1736,12 @@ declare class VideoClient {
|
|
|
1734
1736
|
returnLastFrame?: boolean;
|
|
1735
1737
|
} & Record<string, unknown>): Promise<VideoResponse>;
|
|
1736
1738
|
private submitAndPoll;
|
|
1739
|
+
/**
|
|
1740
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
1741
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
1742
|
+
* authorization window lapses on long polls.
|
|
1743
|
+
*/
|
|
1744
|
+
private signFromChallenge;
|
|
1737
1745
|
private absolute;
|
|
1738
1746
|
private extractPaymentRequired;
|
|
1739
1747
|
private throwApiError;
|
package/dist/index.d.ts
CHANGED
|
@@ -1658,7 +1658,8 @@ declare class SpeechClient {
|
|
|
1658
1658
|
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1659
1659
|
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1660
1660
|
*
|
|
1661
|
-
* The client signs
|
|
1661
|
+
* The client signs once and replays the same PAYMENT-SIGNATURE on every poll,
|
|
1662
|
+
* re-signing automatically if the 600s authorization window lapses mid-poll.
|
|
1662
1663
|
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1663
1664
|
* the caller giving up = zero charge.
|
|
1664
1665
|
*
|
|
@@ -1689,8 +1690,9 @@ declare class VideoClient {
|
|
|
1689
1690
|
* Generate a short video clip from a text prompt (or text + image).
|
|
1690
1691
|
*
|
|
1691
1692
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
1692
|
-
* wall-time is 60-180s
|
|
1693
|
-
*
|
|
1693
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
1694
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
1695
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
1694
1696
|
*
|
|
1695
1697
|
* @param prompt - Text description of the video
|
|
1696
1698
|
* @param options - Optional generation parameters
|
|
@@ -1734,6 +1736,12 @@ declare class VideoClient {
|
|
|
1734
1736
|
returnLastFrame?: boolean;
|
|
1735
1737
|
} & Record<string, unknown>): Promise<VideoResponse>;
|
|
1736
1738
|
private submitAndPoll;
|
|
1739
|
+
/**
|
|
1740
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
1741
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
1742
|
+
* authorization window lapses on long polls.
|
|
1743
|
+
*/
|
|
1744
|
+
private signFromChallenge;
|
|
1737
1745
|
private absolute;
|
|
1738
1746
|
private extractPaymentRequired;
|
|
1739
1747
|
private throwApiError;
|
package/dist/index.js
CHANGED
|
@@ -2415,8 +2415,9 @@ var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
|
2415
2415
|
var DEFAULT_MODEL4 = "xai/grok-imagine-video";
|
|
2416
2416
|
var DEFAULT_TIMEOUT5 = 12e4;
|
|
2417
2417
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
2418
|
-
var DEFAULT_GENERATE_BUDGET_MS =
|
|
2418
|
+
var DEFAULT_GENERATE_BUDGET_MS = 9e5;
|
|
2419
2419
|
var MAX_TIMEOUT_SECONDS = 600;
|
|
2420
|
+
var MAX_POLL_RESIGNS = 2;
|
|
2420
2421
|
var VideoClient = class {
|
|
2421
2422
|
account;
|
|
2422
2423
|
privateKey;
|
|
@@ -2444,8 +2445,9 @@ var VideoClient = class {
|
|
|
2444
2445
|
* Generate a short video clip from a text prompt (or text + image).
|
|
2445
2446
|
*
|
|
2446
2447
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
2447
|
-
* wall-time is 60-180s
|
|
2448
|
-
*
|
|
2448
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
2449
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
2450
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
2449
2451
|
*
|
|
2450
2452
|
* @param prompt - Text description of the video
|
|
2451
2453
|
* @param options - Optional generation parameters
|
|
@@ -2563,21 +2565,9 @@ var VideoClient = class {
|
|
|
2563
2565
|
if (resp402.status !== 402) {
|
|
2564
2566
|
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2565
2567
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
this.privateKey,
|
|
2570
|
-
this.account.address,
|
|
2571
|
-
details.recipient,
|
|
2572
|
-
details.amount,
|
|
2573
|
-
details.network || "eip155:8453",
|
|
2574
|
-
{
|
|
2575
|
-
resourceUrl: details.resource?.url || submitUrl,
|
|
2576
|
-
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2577
|
-
// Ensure signed auth covers the entire polling window.
|
|
2578
|
-
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2579
|
-
extra: details.extra
|
|
2580
|
-
}
|
|
2568
|
+
let { payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2569
|
+
resp402,
|
|
2570
|
+
submitUrl
|
|
2581
2571
|
);
|
|
2582
2572
|
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2583
2573
|
method: "POST",
|
|
@@ -2604,6 +2594,7 @@ var VideoClient = class {
|
|
|
2604
2594
|
const pollUrl = this.absolute(submitData.poll_url);
|
|
2605
2595
|
const deadline = Date.now() + budgetMs;
|
|
2606
2596
|
let lastStatus = submitData.status || "queued";
|
|
2597
|
+
let resignsLeft = MAX_POLL_RESIGNS;
|
|
2607
2598
|
while (Date.now() < deadline) {
|
|
2608
2599
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2609
2600
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
@@ -2626,24 +2617,63 @@ var VideoClient = class {
|
|
|
2626
2617
|
sanitizeErrorResponse(pollData)
|
|
2627
2618
|
);
|
|
2628
2619
|
}
|
|
2629
|
-
if (
|
|
2620
|
+
if (lastStatus === "completed") {
|
|
2630
2621
|
const data = pollData;
|
|
2631
|
-
const billedSeconds = typeof body.duration_seconds === "number" ? body.duration_seconds : 8;
|
|
2632
2622
|
this.sessionCalls++;
|
|
2633
|
-
this.sessionTotalUsd +=
|
|
2623
|
+
this.sessionTotalUsd += Number(details.amount) / 1e6;
|
|
2634
2624
|
const txHash = pollResp.headers.get("x-payment-receipt") || pollResp.headers.get("X-Payment-Receipt");
|
|
2635
2625
|
if (txHash) data.txHash = txHash;
|
|
2636
2626
|
return data;
|
|
2637
2627
|
}
|
|
2628
|
+
if (pollResp.status === 402) {
|
|
2629
|
+
if (resignsLeft > 0) {
|
|
2630
|
+
resignsLeft--;
|
|
2631
|
+
const challenge = await this.fetchWithTimeout(pollUrl, { method: "GET" });
|
|
2632
|
+
if (challenge.status === 402) {
|
|
2633
|
+
({ payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2634
|
+
challenge,
|
|
2635
|
+
pollUrl
|
|
2636
|
+
));
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
throw new PaymentError(
|
|
2641
|
+
"Payment verification failed mid-poll (not a signature-expiry). Check the wallet balance and that you poll from the wallet that submitted the job."
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2638
2644
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2639
2645
|
await this.throwApiError(pollResp, "Poll failed");
|
|
2640
2646
|
}
|
|
2641
2647
|
}
|
|
2642
2648
|
throw new APIError(
|
|
2643
|
-
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2649
|
+
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken. The job is NOT lost: it stays claimable for ~48h \u2014 re-GET poll_url with a fresh signature from the same wallet to fetch (and settle) the finished video.`,
|
|
2644
2650
|
504,
|
|
2645
|
-
{ id: submitData.id, last_status: lastStatus }
|
|
2651
|
+
{ id: submitData.id, last_status: lastStatus, poll_url: pollUrl }
|
|
2652
|
+
);
|
|
2653
|
+
}
|
|
2654
|
+
/**
|
|
2655
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
2656
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
2657
|
+
* authorization window lapses on long polls.
|
|
2658
|
+
*/
|
|
2659
|
+
async signFromChallenge(resp402, fallbackUrl) {
|
|
2660
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2661
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2662
|
+
const payload = await createPaymentPayload(
|
|
2663
|
+
this.privateKey,
|
|
2664
|
+
this.account.address,
|
|
2665
|
+
details.recipient,
|
|
2666
|
+
details.amount,
|
|
2667
|
+
details.network || "eip155:8453",
|
|
2668
|
+
{
|
|
2669
|
+
resourceUrl: details.resource?.url || fallbackUrl,
|
|
2670
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2671
|
+
// Ensure signed auth covers as much of the polling window as allowed.
|
|
2672
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2673
|
+
extra: details.extra
|
|
2674
|
+
}
|
|
2646
2675
|
);
|
|
2676
|
+
return { payload, details };
|
|
2647
2677
|
}
|
|
2648
2678
|
absolute(url) {
|
|
2649
2679
|
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|