@blockrun/llm 3.2.3 → 3.5.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 +53 -8
- package/dist/index.cjs +98 -23
- package/dist/index.d.cts +38 -6
- package/dist/index.d.ts +38 -6
- package/dist/index.js +98 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,15 +165,59 @@ const tweet = await client.chat('xai/grok-3-mini', 'What is trending on X?', { s
|
|
|
165
165
|
**Supported endpoint:** `https://sol.blockrun.ai/api`
|
|
166
166
|
**Payment:** Solana USDC (SPL, mainnet)
|
|
167
167
|
|
|
168
|
-
## How
|
|
168
|
+
## How Payment Works
|
|
169
169
|
|
|
170
|
-
|
|
171
|
-
2. The API returns a 402 Payment Required with the price
|
|
172
|
-
3. The SDK automatically signs a USDC payment on Base
|
|
173
|
-
4. The request is retried with the payment proof
|
|
174
|
-
5. You receive the AI response
|
|
170
|
+
No API keys, no subscription. You hold USDC in your own wallet, and **every request pays for itself** with an on-chain micropayment. Two phases:
|
|
175
171
|
|
|
176
|
-
|
|
172
|
+
### Phase 1 — Fund your wallet once
|
|
173
|
+
|
|
174
|
+
You only do this when your balance runs low. Three ways to get USDC into your wallet:
|
|
175
|
+
|
|
176
|
+
- **(a) Buy with a card (Base USDC).** Call the new `onramp()` method to mint a one-time Coinbase Onramp link, then open the returned `pay.coinbase.com` URL — pay by card/bank in 60+ fiat currencies and the USDC lands in your wallet. The call itself is **free**. Onramp is **Base-only** (buying USDC with a card always lands Base USDC), and the funding address must equal your signing wallet:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const { url } = await client.onramp(client.getWalletAddress());
|
|
180
|
+
console.log(`Fund your wallet: ${url}`); // single-use, expires ~5 min — mint at click time
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
- **(b) Transfer existing USDC.** Send USDC you already hold to your wallet address (`client.getWalletAddress()`). On Base, send Base USDC; on Solana (`SolanaLLMClient`), send Solana SPL USDC.
|
|
184
|
+
|
|
185
|
+
- **(c) Skip funding entirely.** Use the free NVIDIA models (e.g. `nvidia/deepseek-v4-flash`) — every call is **$0**, no balance required.
|
|
186
|
+
|
|
187
|
+
$5 of USDC covers thousands of paid requests. Check your balance any time:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const balance = await client.getBalance(); // USDC on Base
|
|
191
|
+
console.log(`Balance: $${balance.toFixed(2)} USDC`);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Phase 2 — Every request pays itself (automatic x402)
|
|
195
|
+
|
|
196
|
+
You just call e.g. `client.chat(...)` — the payment is invisible:
|
|
197
|
+
|
|
198
|
+
1. You send a request to BlockRun's API.
|
|
199
|
+
2. The gateway returns **402 Payment Required** with the price.
|
|
200
|
+
3. The SDK signs a USDC payment **locally** (EIP-712) — on **Base** for `LLMClient`, on **Solana** for `SolanaLLMClient` — using your wallet key.
|
|
201
|
+
4. The request is retried automatically with the payment proof.
|
|
202
|
+
5. The gateway settles on-chain and returns the AI response.
|
|
203
|
+
|
|
204
|
+
One call, no separate pay step. Free NVIDIA models settle at **$0** (no payment signed).
|
|
205
|
+
|
|
206
|
+
### Track spend and verify settlements
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { getCostSummary } from '@blockrun/llm';
|
|
210
|
+
|
|
211
|
+
const spent = client.getSpending(); // this session
|
|
212
|
+
console.log(`Spent $${spent.totalUsd.toFixed(4)} across ${spent.calls} calls`);
|
|
213
|
+
|
|
214
|
+
const summary = getCostSummary(); // across sessions (~/.blockrun/data/costs.jsonl)
|
|
215
|
+
console.log(`Lifetime: $${summary.totalUsd.toFixed(2)} over ${summary.calls} calls`);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Every paid request is a real on-chain USDC transfer — look up your wallet address on [Basescan](https://basescan.org) (or a Solana explorer) to verify each settlement independently.
|
|
219
|
+
|
|
220
|
+
**Non-custodial by design: your private key never leaves your machine** — it is only used for local signing, and no funds are ever held by BlockRun.
|
|
177
221
|
|
|
178
222
|
## Smart Routing (ClawRouter)
|
|
179
223
|
|
|
@@ -298,6 +342,7 @@ Released 2026-04-23 — first fully retrained base since GPT-4.5. 1M context, 12
|
|
|
298
342
|
### Anthropic Claude
|
|
299
343
|
| Model | Input Price | Output Price | Context | Notes |
|
|
300
344
|
|-------|-------------|--------------|---------|-------|
|
|
345
|
+
| `anthropic/claude-fable-5` | $10.00/M | $50.00/M | **1M** | Mythos-class flagship above Opus — always-on thinking, 128K output, fallback `claude-opus-4.8`. Alias: `claude-fable-5` |
|
|
301
346
|
| `anthropic/claude-opus-4.8` | $5.00/M | $25.00/M | **1M** | Flagship — agentic coding + adaptive thinking, 128K output |
|
|
302
347
|
| `anthropic/claude-opus-4.7` | $5.00/M | $25.00/M | **1M** | Agentic coding + adaptive thinking, 128K output |
|
|
303
348
|
| `anthropic/claude-opus-4.6` | $5.00/M | $25.00/M | 200K | Hidden but still callable — kept as in-family hot-swap fallback |
|
|
@@ -1320,7 +1365,7 @@ const gptResponse = await client.messages.create({
|
|
|
1320
1365
|
});
|
|
1321
1366
|
```
|
|
1322
1367
|
|
|
1323
|
-
The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch that handles x402 payment automatically. Your private key never leaves your machine.
|
|
1368
|
+
The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch that handles x402 payment automatically. Your private key never leaves your machine. The Mythos-class `claude-fable-5` alias is available here too (1M context, always-on thinking).
|
|
1324
1369
|
|
|
1325
1370
|
## Links
|
|
1326
1371
|
|
package/dist/index.cjs
CHANGED
|
@@ -544,7 +544,17 @@ function mapRawToImageModel(m) {
|
|
|
544
544
|
}
|
|
545
545
|
var DEFAULT_API_URL = "https://blockrun.ai/api";
|
|
546
546
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
547
|
-
|
|
547
|
+
function resolveDefaultTimeout() {
|
|
548
|
+
const envVal = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_CHAT_TIMEOUT : void 0;
|
|
549
|
+
if (envVal !== void 0) {
|
|
550
|
+
const seconds = Number(envVal);
|
|
551
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
552
|
+
return seconds * 1e3;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return 6e5;
|
|
556
|
+
}
|
|
557
|
+
var DEFAULT_TIMEOUT = resolveDefaultTimeout();
|
|
548
558
|
var SDK_VERSION = "1.5.0";
|
|
549
559
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
550
560
|
var LLMClient = class _LLMClient {
|
|
@@ -1581,6 +1591,40 @@ var LLMClient = class _LLMClient {
|
|
|
1581
1591
|
const data = await this.requestWithPaymentRaw("/v1/exa/find-similar", body);
|
|
1582
1592
|
return data.data;
|
|
1583
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Mint a one-time Coinbase Onramp link to fund this wallet with USDC on Base.
|
|
1596
|
+
*
|
|
1597
|
+
* FREE — the x402 signature only authenticates the wallet (no payment is
|
|
1598
|
+
* charged). Because the funding address must equal the signing wallet, this
|
|
1599
|
+
* mints a `pay.coinbase.com` link prefilled for `address` so a card/bank can
|
|
1600
|
+
* top it up (60+ fiat currencies → Base USDC). Base / USDC only.
|
|
1601
|
+
*
|
|
1602
|
+
* The returned URL is single-use and expires in ~5 minutes — mint it at click
|
|
1603
|
+
* time and never cache it. The funding `address` must match the signing wallet.
|
|
1604
|
+
*
|
|
1605
|
+
* @param address - Destination Base wallet address (0x + 40 hex chars). Must
|
|
1606
|
+
* equal the signing wallet that funds it.
|
|
1607
|
+
* @returns `{ url }` — a one-time https://pay.coinbase.com/... link.
|
|
1608
|
+
*
|
|
1609
|
+
* @example
|
|
1610
|
+
* const { url } = await client.onramp(client.getWalletAddress());
|
|
1611
|
+
* console.log(`Fund your wallet: ${url}`);
|
|
1612
|
+
*/
|
|
1613
|
+
async onramp(address) {
|
|
1614
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
1615
|
+
throw new Error("address must be a 0x-prefixed 40-hex-character Base address");
|
|
1616
|
+
}
|
|
1617
|
+
const data = await this.requestWithPaymentRaw("/v1/onramp/token", {
|
|
1618
|
+
address,
|
|
1619
|
+
network: "base",
|
|
1620
|
+
asset: "USDC"
|
|
1621
|
+
});
|
|
1622
|
+
const url = typeof data.url === "string" ? data.url : "";
|
|
1623
|
+
if (!url.startsWith("https://pay.coinbase.com/")) {
|
|
1624
|
+
throw new APIError("gateway returned no onramp url", 502, { message: "gateway returned no onramp url" });
|
|
1625
|
+
}
|
|
1626
|
+
return { url };
|
|
1627
|
+
}
|
|
1584
1628
|
/**
|
|
1585
1629
|
* Get USDC balance on Base mainnet.
|
|
1586
1630
|
*
|
|
@@ -2522,8 +2566,9 @@ var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
|
2522
2566
|
var DEFAULT_MODEL4 = "xai/grok-imagine-video";
|
|
2523
2567
|
var DEFAULT_TIMEOUT5 = 12e4;
|
|
2524
2568
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
2525
|
-
var DEFAULT_GENERATE_BUDGET_MS =
|
|
2569
|
+
var DEFAULT_GENERATE_BUDGET_MS = 9e5;
|
|
2526
2570
|
var MAX_TIMEOUT_SECONDS = 600;
|
|
2571
|
+
var MAX_POLL_RESIGNS = 2;
|
|
2527
2572
|
var VideoClient = class {
|
|
2528
2573
|
account;
|
|
2529
2574
|
privateKey;
|
|
@@ -2551,8 +2596,9 @@ var VideoClient = class {
|
|
|
2551
2596
|
* Generate a short video clip from a text prompt (or text + image).
|
|
2552
2597
|
*
|
|
2553
2598
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
2554
|
-
* wall-time is 60-180s
|
|
2555
|
-
*
|
|
2599
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
2600
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
2601
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
2556
2602
|
*
|
|
2557
2603
|
* @param prompt - Text description of the video
|
|
2558
2604
|
* @param options - Optional generation parameters
|
|
@@ -2670,21 +2716,9 @@ var VideoClient = class {
|
|
|
2670
2716
|
if (resp402.status !== 402) {
|
|
2671
2717
|
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2672
2718
|
}
|
|
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
|
-
}
|
|
2719
|
+
let { payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2720
|
+
resp402,
|
|
2721
|
+
submitUrl
|
|
2688
2722
|
);
|
|
2689
2723
|
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2690
2724
|
method: "POST",
|
|
@@ -2711,6 +2745,7 @@ var VideoClient = class {
|
|
|
2711
2745
|
const pollUrl = this.absolute(submitData.poll_url);
|
|
2712
2746
|
const deadline = Date.now() + budgetMs;
|
|
2713
2747
|
let lastStatus = submitData.status || "queued";
|
|
2748
|
+
let resignsLeft = MAX_POLL_RESIGNS;
|
|
2714
2749
|
while (Date.now() < deadline) {
|
|
2715
2750
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2716
2751
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
@@ -2741,15 +2776,55 @@ var VideoClient = class {
|
|
|
2741
2776
|
if (txHash) data.txHash = txHash;
|
|
2742
2777
|
return data;
|
|
2743
2778
|
}
|
|
2779
|
+
if (pollResp.status === 402) {
|
|
2780
|
+
if (resignsLeft > 0) {
|
|
2781
|
+
resignsLeft--;
|
|
2782
|
+
const challenge = await this.fetchWithTimeout(pollUrl, { method: "GET" });
|
|
2783
|
+
if (challenge.status === 402) {
|
|
2784
|
+
({ payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2785
|
+
challenge,
|
|
2786
|
+
pollUrl
|
|
2787
|
+
));
|
|
2788
|
+
continue;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
throw new PaymentError(
|
|
2792
|
+
"Payment verification failed mid-poll (not a signature-expiry). Check the wallet balance and that you poll from the wallet that submitted the job."
|
|
2793
|
+
);
|
|
2794
|
+
}
|
|
2744
2795
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2745
2796
|
await this.throwApiError(pollResp, "Poll failed");
|
|
2746
2797
|
}
|
|
2747
2798
|
}
|
|
2748
2799
|
throw new APIError(
|
|
2749
|
-
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2800
|
+
`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.`,
|
|
2750
2801
|
504,
|
|
2751
|
-
{ id: submitData.id, last_status: lastStatus }
|
|
2802
|
+
{ id: submitData.id, last_status: lastStatus, poll_url: pollUrl }
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
2807
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
2808
|
+
* authorization window lapses on long polls.
|
|
2809
|
+
*/
|
|
2810
|
+
async signFromChallenge(resp402, fallbackUrl) {
|
|
2811
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2812
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2813
|
+
const payload = await createPaymentPayload(
|
|
2814
|
+
this.privateKey,
|
|
2815
|
+
this.account.address,
|
|
2816
|
+
details.recipient,
|
|
2817
|
+
details.amount,
|
|
2818
|
+
details.network || "eip155:8453",
|
|
2819
|
+
{
|
|
2820
|
+
resourceUrl: details.resource?.url || fallbackUrl,
|
|
2821
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2822
|
+
// Ensure signed auth covers as much of the polling window as allowed.
|
|
2823
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2824
|
+
extra: details.extra
|
|
2825
|
+
}
|
|
2752
2826
|
);
|
|
2827
|
+
return { payload, details };
|
|
2753
2828
|
}
|
|
2754
2829
|
absolute(url) {
|
|
2755
2830
|
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
@@ -5739,7 +5814,7 @@ var OpenAI = class {
|
|
|
5739
5814
|
constructor(options = {}) {
|
|
5740
5815
|
const privateKey = options.walletKey || options.privateKey;
|
|
5741
5816
|
const apiUrl = options.baseURL || "https://blockrun.ai/api";
|
|
5742
|
-
const timeout = options.timeout
|
|
5817
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
5743
5818
|
this.client = new LLMClient({
|
|
5744
5819
|
privateKey,
|
|
5745
5820
|
apiUrl,
|
|
@@ -5773,7 +5848,7 @@ var AnthropicClient = class {
|
|
|
5773
5848
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
5774
5849
|
validateApiUrl(apiUrl);
|
|
5775
5850
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
5776
|
-
this._timeout = options.timeout ??
|
|
5851
|
+
this._timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
5777
5852
|
}
|
|
5778
5853
|
async _getClient() {
|
|
5779
5854
|
if (this._client) return this._client;
|
package/dist/index.d.cts
CHANGED
|
@@ -245,7 +245,7 @@ interface LLMClientOptions {
|
|
|
245
245
|
privateKey?: `0x${string}` | string;
|
|
246
246
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
247
247
|
apiUrl?: string;
|
|
248
|
-
/** Request timeout in milliseconds (default:
|
|
248
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
249
249
|
timeout?: number;
|
|
250
250
|
}
|
|
251
251
|
/**
|
|
@@ -727,6 +727,10 @@ interface ExaFindSimilarOptions {
|
|
|
727
727
|
/** Exclude pages from the same domain as the reference URL */
|
|
728
728
|
excludeSourceDomain?: boolean;
|
|
729
729
|
}
|
|
730
|
+
interface OnrampResult {
|
|
731
|
+
/** One-time https://pay.coinbase.com/... URL prefilled for this wallet. */
|
|
732
|
+
url: string;
|
|
733
|
+
}
|
|
730
734
|
type PriceCategory = "crypto" | "fx" | "commodity" | "usstock" | "stocks";
|
|
731
735
|
type StockMarket = "us" | "hk" | "jp" | "kr" | "gb" | "de" | "fr" | "nl" | "ie" | "lu" | "cn" | "ca";
|
|
732
736
|
type BarResolution = "1" | "5" | "15" | "60" | "240" | "D" | "W" | "M";
|
|
@@ -806,7 +810,7 @@ interface PhoneClientOptions {
|
|
|
806
810
|
privateKey?: `0x${string}` | string;
|
|
807
811
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
808
812
|
apiUrl?: string;
|
|
809
|
-
/** Request timeout in milliseconds (default:
|
|
813
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
810
814
|
timeout?: number;
|
|
811
815
|
}
|
|
812
816
|
/**
|
|
@@ -875,7 +879,7 @@ interface PortraitClientOptions {
|
|
|
875
879
|
privateKey?: `0x${string}` | string;
|
|
876
880
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
877
881
|
apiUrl?: string;
|
|
878
|
-
/** Request timeout in milliseconds (default:
|
|
882
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
879
883
|
timeout?: number;
|
|
880
884
|
}
|
|
881
885
|
interface PortraitEnrollOptions {
|
|
@@ -1220,6 +1224,26 @@ declare class LLMClient {
|
|
|
1220
1224
|
* @param options - Optional filters (numResults, excludeSourceDomain)
|
|
1221
1225
|
*/
|
|
1222
1226
|
exaFindSimilar(url: string, options?: ExaFindSimilarOptions): Promise<ExaSearchResponse>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Mint a one-time Coinbase Onramp link to fund this wallet with USDC on Base.
|
|
1229
|
+
*
|
|
1230
|
+
* FREE — the x402 signature only authenticates the wallet (no payment is
|
|
1231
|
+
* charged). Because the funding address must equal the signing wallet, this
|
|
1232
|
+
* mints a `pay.coinbase.com` link prefilled for `address` so a card/bank can
|
|
1233
|
+
* top it up (60+ fiat currencies → Base USDC). Base / USDC only.
|
|
1234
|
+
*
|
|
1235
|
+
* The returned URL is single-use and expires in ~5 minutes — mint it at click
|
|
1236
|
+
* time and never cache it. The funding `address` must match the signing wallet.
|
|
1237
|
+
*
|
|
1238
|
+
* @param address - Destination Base wallet address (0x + 40 hex chars). Must
|
|
1239
|
+
* equal the signing wallet that funds it.
|
|
1240
|
+
* @returns `{ url }` — a one-time https://pay.coinbase.com/... link.
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* const { url } = await client.onramp(client.getWalletAddress());
|
|
1244
|
+
* console.log(`Fund your wallet: ${url}`);
|
|
1245
|
+
*/
|
|
1246
|
+
onramp(address: string): Promise<OnrampResult>;
|
|
1223
1247
|
/**
|
|
1224
1248
|
* Get USDC balance on Base mainnet.
|
|
1225
1249
|
*
|
|
@@ -1658,7 +1682,8 @@ declare class SpeechClient {
|
|
|
1658
1682
|
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1659
1683
|
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1660
1684
|
*
|
|
1661
|
-
* The client signs
|
|
1685
|
+
* The client signs once and replays the same PAYMENT-SIGNATURE on every poll,
|
|
1686
|
+
* re-signing automatically if the 600s authorization window lapses mid-poll.
|
|
1662
1687
|
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1663
1688
|
* the caller giving up = zero charge.
|
|
1664
1689
|
*
|
|
@@ -1689,8 +1714,9 @@ declare class VideoClient {
|
|
|
1689
1714
|
* Generate a short video clip from a text prompt (or text + image).
|
|
1690
1715
|
*
|
|
1691
1716
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
1692
|
-
* wall-time is 60-180s
|
|
1693
|
-
*
|
|
1717
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
1718
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
1719
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
1694
1720
|
*
|
|
1695
1721
|
* @param prompt - Text description of the video
|
|
1696
1722
|
* @param options - Optional generation parameters
|
|
@@ -1734,6 +1760,12 @@ declare class VideoClient {
|
|
|
1734
1760
|
returnLastFrame?: boolean;
|
|
1735
1761
|
} & Record<string, unknown>): Promise<VideoResponse>;
|
|
1736
1762
|
private submitAndPoll;
|
|
1763
|
+
/**
|
|
1764
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
1765
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
1766
|
+
* authorization window lapses on long polls.
|
|
1767
|
+
*/
|
|
1768
|
+
private signFromChallenge;
|
|
1737
1769
|
private absolute;
|
|
1738
1770
|
private extractPaymentRequired;
|
|
1739
1771
|
private throwApiError;
|
package/dist/index.d.ts
CHANGED
|
@@ -245,7 +245,7 @@ interface LLMClientOptions {
|
|
|
245
245
|
privateKey?: `0x${string}` | string;
|
|
246
246
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
247
247
|
apiUrl?: string;
|
|
248
|
-
/** Request timeout in milliseconds (default:
|
|
248
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
249
249
|
timeout?: number;
|
|
250
250
|
}
|
|
251
251
|
/**
|
|
@@ -727,6 +727,10 @@ interface ExaFindSimilarOptions {
|
|
|
727
727
|
/** Exclude pages from the same domain as the reference URL */
|
|
728
728
|
excludeSourceDomain?: boolean;
|
|
729
729
|
}
|
|
730
|
+
interface OnrampResult {
|
|
731
|
+
/** One-time https://pay.coinbase.com/... URL prefilled for this wallet. */
|
|
732
|
+
url: string;
|
|
733
|
+
}
|
|
730
734
|
type PriceCategory = "crypto" | "fx" | "commodity" | "usstock" | "stocks";
|
|
731
735
|
type StockMarket = "us" | "hk" | "jp" | "kr" | "gb" | "de" | "fr" | "nl" | "ie" | "lu" | "cn" | "ca";
|
|
732
736
|
type BarResolution = "1" | "5" | "15" | "60" | "240" | "D" | "W" | "M";
|
|
@@ -806,7 +810,7 @@ interface PhoneClientOptions {
|
|
|
806
810
|
privateKey?: `0x${string}` | string;
|
|
807
811
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
808
812
|
apiUrl?: string;
|
|
809
|
-
/** Request timeout in milliseconds (default:
|
|
813
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
810
814
|
timeout?: number;
|
|
811
815
|
}
|
|
812
816
|
/**
|
|
@@ -875,7 +879,7 @@ interface PortraitClientOptions {
|
|
|
875
879
|
privateKey?: `0x${string}` | string;
|
|
876
880
|
/** API endpoint URL (default: https://blockrun.ai/api) */
|
|
877
881
|
apiUrl?: string;
|
|
878
|
-
/** Request timeout in milliseconds (default:
|
|
882
|
+
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
|
|
879
883
|
timeout?: number;
|
|
880
884
|
}
|
|
881
885
|
interface PortraitEnrollOptions {
|
|
@@ -1220,6 +1224,26 @@ declare class LLMClient {
|
|
|
1220
1224
|
* @param options - Optional filters (numResults, excludeSourceDomain)
|
|
1221
1225
|
*/
|
|
1222
1226
|
exaFindSimilar(url: string, options?: ExaFindSimilarOptions): Promise<ExaSearchResponse>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Mint a one-time Coinbase Onramp link to fund this wallet with USDC on Base.
|
|
1229
|
+
*
|
|
1230
|
+
* FREE — the x402 signature only authenticates the wallet (no payment is
|
|
1231
|
+
* charged). Because the funding address must equal the signing wallet, this
|
|
1232
|
+
* mints a `pay.coinbase.com` link prefilled for `address` so a card/bank can
|
|
1233
|
+
* top it up (60+ fiat currencies → Base USDC). Base / USDC only.
|
|
1234
|
+
*
|
|
1235
|
+
* The returned URL is single-use and expires in ~5 minutes — mint it at click
|
|
1236
|
+
* time and never cache it. The funding `address` must match the signing wallet.
|
|
1237
|
+
*
|
|
1238
|
+
* @param address - Destination Base wallet address (0x + 40 hex chars). Must
|
|
1239
|
+
* equal the signing wallet that funds it.
|
|
1240
|
+
* @returns `{ url }` — a one-time https://pay.coinbase.com/... link.
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* const { url } = await client.onramp(client.getWalletAddress());
|
|
1244
|
+
* console.log(`Fund your wallet: ${url}`);
|
|
1245
|
+
*/
|
|
1246
|
+
onramp(address: string): Promise<OnrampResult>;
|
|
1223
1247
|
/**
|
|
1224
1248
|
* Get USDC balance on Base mainnet.
|
|
1225
1249
|
*
|
|
@@ -1658,7 +1682,8 @@ declare class SpeechClient {
|
|
|
1658
1682
|
* POST /v1/videos/generations -> 402 -> sign -> 202 { id, poll_url }
|
|
1659
1683
|
* GET /v1/videos/generations/{id} -> loop until status=completed
|
|
1660
1684
|
*
|
|
1661
|
-
* The client signs
|
|
1685
|
+
* The client signs once and replays the same PAYMENT-SIGNATURE on every poll,
|
|
1686
|
+
* re-signing automatically if the 600s authorization window lapses mid-poll.
|
|
1662
1687
|
* Settlement happens only on the first completed poll, so upstream failure or
|
|
1663
1688
|
* the caller giving up = zero charge.
|
|
1664
1689
|
*
|
|
@@ -1689,8 +1714,9 @@ declare class VideoClient {
|
|
|
1689
1714
|
* Generate a short video clip from a text prompt (or text + image).
|
|
1690
1715
|
*
|
|
1691
1716
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
1692
|
-
* wall-time is 60-180s
|
|
1693
|
-
*
|
|
1717
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
1718
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
1719
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
1694
1720
|
*
|
|
1695
1721
|
* @param prompt - Text description of the video
|
|
1696
1722
|
* @param options - Optional generation parameters
|
|
@@ -1734,6 +1760,12 @@ declare class VideoClient {
|
|
|
1734
1760
|
returnLastFrame?: boolean;
|
|
1735
1761
|
} & Record<string, unknown>): Promise<VideoResponse>;
|
|
1736
1762
|
private submitAndPoll;
|
|
1763
|
+
/**
|
|
1764
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
1765
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
1766
|
+
* authorization window lapses on long polls.
|
|
1767
|
+
*/
|
|
1768
|
+
private signFromChallenge;
|
|
1737
1769
|
private absolute;
|
|
1738
1770
|
private extractPaymentRequired;
|
|
1739
1771
|
private throwApiError;
|
package/dist/index.js
CHANGED
|
@@ -437,7 +437,17 @@ function mapRawToImageModel(m) {
|
|
|
437
437
|
}
|
|
438
438
|
var DEFAULT_API_URL = "https://blockrun.ai/api";
|
|
439
439
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
440
|
-
|
|
440
|
+
function resolveDefaultTimeout() {
|
|
441
|
+
const envVal = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_CHAT_TIMEOUT : void 0;
|
|
442
|
+
if (envVal !== void 0) {
|
|
443
|
+
const seconds = Number(envVal);
|
|
444
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
445
|
+
return seconds * 1e3;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return 6e5;
|
|
449
|
+
}
|
|
450
|
+
var DEFAULT_TIMEOUT = resolveDefaultTimeout();
|
|
441
451
|
var SDK_VERSION = "1.5.0";
|
|
442
452
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
443
453
|
var LLMClient = class _LLMClient {
|
|
@@ -1474,6 +1484,40 @@ var LLMClient = class _LLMClient {
|
|
|
1474
1484
|
const data = await this.requestWithPaymentRaw("/v1/exa/find-similar", body);
|
|
1475
1485
|
return data.data;
|
|
1476
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Mint a one-time Coinbase Onramp link to fund this wallet with USDC on Base.
|
|
1489
|
+
*
|
|
1490
|
+
* FREE — the x402 signature only authenticates the wallet (no payment is
|
|
1491
|
+
* charged). Because the funding address must equal the signing wallet, this
|
|
1492
|
+
* mints a `pay.coinbase.com` link prefilled for `address` so a card/bank can
|
|
1493
|
+
* top it up (60+ fiat currencies → Base USDC). Base / USDC only.
|
|
1494
|
+
*
|
|
1495
|
+
* The returned URL is single-use and expires in ~5 minutes — mint it at click
|
|
1496
|
+
* time and never cache it. The funding `address` must match the signing wallet.
|
|
1497
|
+
*
|
|
1498
|
+
* @param address - Destination Base wallet address (0x + 40 hex chars). Must
|
|
1499
|
+
* equal the signing wallet that funds it.
|
|
1500
|
+
* @returns `{ url }` — a one-time https://pay.coinbase.com/... link.
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* const { url } = await client.onramp(client.getWalletAddress());
|
|
1504
|
+
* console.log(`Fund your wallet: ${url}`);
|
|
1505
|
+
*/
|
|
1506
|
+
async onramp(address) {
|
|
1507
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address)) {
|
|
1508
|
+
throw new Error("address must be a 0x-prefixed 40-hex-character Base address");
|
|
1509
|
+
}
|
|
1510
|
+
const data = await this.requestWithPaymentRaw("/v1/onramp/token", {
|
|
1511
|
+
address,
|
|
1512
|
+
network: "base",
|
|
1513
|
+
asset: "USDC"
|
|
1514
|
+
});
|
|
1515
|
+
const url = typeof data.url === "string" ? data.url : "";
|
|
1516
|
+
if (!url.startsWith("https://pay.coinbase.com/")) {
|
|
1517
|
+
throw new APIError("gateway returned no onramp url", 502, { message: "gateway returned no onramp url" });
|
|
1518
|
+
}
|
|
1519
|
+
return { url };
|
|
1520
|
+
}
|
|
1477
1521
|
/**
|
|
1478
1522
|
* Get USDC balance on Base mainnet.
|
|
1479
1523
|
*
|
|
@@ -2415,8 +2459,9 @@ var DEFAULT_API_URL5 = "https://blockrun.ai/api";
|
|
|
2415
2459
|
var DEFAULT_MODEL4 = "xai/grok-imagine-video";
|
|
2416
2460
|
var DEFAULT_TIMEOUT5 = 12e4;
|
|
2417
2461
|
var POLL_INTERVAL_MS2 = 5e3;
|
|
2418
|
-
var DEFAULT_GENERATE_BUDGET_MS =
|
|
2462
|
+
var DEFAULT_GENERATE_BUDGET_MS = 9e5;
|
|
2419
2463
|
var MAX_TIMEOUT_SECONDS = 600;
|
|
2464
|
+
var MAX_POLL_RESIGNS = 2;
|
|
2420
2465
|
var VideoClient = class {
|
|
2421
2466
|
account;
|
|
2422
2467
|
privateKey;
|
|
@@ -2444,8 +2489,9 @@ var VideoClient = class {
|
|
|
2444
2489
|
* Generate a short video clip from a text prompt (or text + image).
|
|
2445
2490
|
*
|
|
2446
2491
|
* Submits an async job, then polls until the video is ready. Typical total
|
|
2447
|
-
* wall-time is 60-180s
|
|
2448
|
-
*
|
|
2492
|
+
* wall-time is 60-180s, but upstream status can lag several minutes behind
|
|
2493
|
+
* actual completion. If upstream runs past the budget (default 15min),
|
|
2494
|
+
* throws without charging — the job stays claimable ~48h via poll_url.
|
|
2449
2495
|
*
|
|
2450
2496
|
* @param prompt - Text description of the video
|
|
2451
2497
|
* @param options - Optional generation parameters
|
|
@@ -2563,21 +2609,9 @@ var VideoClient = class {
|
|
|
2563
2609
|
if (resp402.status !== 402) {
|
|
2564
2610
|
await this.throwApiError(resp402, "Expected 402 on first POST");
|
|
2565
2611
|
}
|
|
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
|
-
}
|
|
2612
|
+
let { payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2613
|
+
resp402,
|
|
2614
|
+
submitUrl
|
|
2581
2615
|
);
|
|
2582
2616
|
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
2583
2617
|
method: "POST",
|
|
@@ -2604,6 +2638,7 @@ var VideoClient = class {
|
|
|
2604
2638
|
const pollUrl = this.absolute(submitData.poll_url);
|
|
2605
2639
|
const deadline = Date.now() + budgetMs;
|
|
2606
2640
|
let lastStatus = submitData.status || "queued";
|
|
2641
|
+
let resignsLeft = MAX_POLL_RESIGNS;
|
|
2607
2642
|
while (Date.now() < deadline) {
|
|
2608
2643
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
2609
2644
|
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
@@ -2634,15 +2669,55 @@ var VideoClient = class {
|
|
|
2634
2669
|
if (txHash) data.txHash = txHash;
|
|
2635
2670
|
return data;
|
|
2636
2671
|
}
|
|
2672
|
+
if (pollResp.status === 402) {
|
|
2673
|
+
if (resignsLeft > 0) {
|
|
2674
|
+
resignsLeft--;
|
|
2675
|
+
const challenge = await this.fetchWithTimeout(pollUrl, { method: "GET" });
|
|
2676
|
+
if (challenge.status === 402) {
|
|
2677
|
+
({ payload: paymentPayload, details } = await this.signFromChallenge(
|
|
2678
|
+
challenge,
|
|
2679
|
+
pollUrl
|
|
2680
|
+
));
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
throw new PaymentError(
|
|
2685
|
+
"Payment verification failed mid-poll (not a signature-expiry). Check the wallet balance and that you poll from the wallet that submitted the job."
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2637
2688
|
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
2638
2689
|
await this.throwApiError(pollResp, "Poll failed");
|
|
2639
2690
|
}
|
|
2640
2691
|
}
|
|
2641
2692
|
throw new APIError(
|
|
2642
|
-
`Video generation did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
2693
|
+
`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.`,
|
|
2643
2694
|
504,
|
|
2644
|
-
{ id: submitData.id, last_status: lastStatus }
|
|
2695
|
+
{ id: submitData.id, last_status: lastStatus, poll_url: pollUrl }
|
|
2696
|
+
);
|
|
2697
|
+
}
|
|
2698
|
+
/**
|
|
2699
|
+
* Parse an x402 challenge response and sign a payment payload for it.
|
|
2700
|
+
* Used for the initial submit AND for mid-poll re-signing after the 600s
|
|
2701
|
+
* authorization window lapses on long polls.
|
|
2702
|
+
*/
|
|
2703
|
+
async signFromChallenge(resp402, fallbackUrl) {
|
|
2704
|
+
const paymentRequired = await this.extractPaymentRequired(resp402);
|
|
2705
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2706
|
+
const payload = await createPaymentPayload(
|
|
2707
|
+
this.privateKey,
|
|
2708
|
+
this.account.address,
|
|
2709
|
+
details.recipient,
|
|
2710
|
+
details.amount,
|
|
2711
|
+
details.network || "eip155:8453",
|
|
2712
|
+
{
|
|
2713
|
+
resourceUrl: details.resource?.url || fallbackUrl,
|
|
2714
|
+
resourceDescription: details.resource?.description || "BlockRun Video Generation",
|
|
2715
|
+
// Ensure signed auth covers as much of the polling window as allowed.
|
|
2716
|
+
maxTimeoutSeconds: Math.max(details.maxTimeoutSeconds || 0, MAX_TIMEOUT_SECONDS),
|
|
2717
|
+
extra: details.extra
|
|
2718
|
+
}
|
|
2645
2719
|
);
|
|
2720
|
+
return { payload, details };
|
|
2646
2721
|
}
|
|
2647
2722
|
absolute(url) {
|
|
2648
2723
|
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
@@ -5632,7 +5707,7 @@ var OpenAI = class {
|
|
|
5632
5707
|
constructor(options = {}) {
|
|
5633
5708
|
const privateKey = options.walletKey || options.privateKey;
|
|
5634
5709
|
const apiUrl = options.baseURL || "https://blockrun.ai/api";
|
|
5635
|
-
const timeout = options.timeout
|
|
5710
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
5636
5711
|
this.client = new LLMClient({
|
|
5637
5712
|
privateKey,
|
|
5638
5713
|
apiUrl,
|
|
@@ -5666,7 +5741,7 @@ var AnthropicClient = class {
|
|
|
5666
5741
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
5667
5742
|
validateApiUrl(apiUrl);
|
|
5668
5743
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
5669
|
-
this._timeout = options.timeout ??
|
|
5744
|
+
this._timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
5670
5745
|
}
|
|
5671
5746
|
async _getClient() {
|
|
5672
5747
|
if (this._client) return this._client;
|