@blockrun/llm 2.4.0 → 2.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 +47 -0
- package/dist/index.cjs +391 -11
- package/dist/index.d.cts +141 -4
- package/dist/index.d.ts +141 -4
- package/dist/index.js +389 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,53 @@ const response = await client.chat('openai/gpt-4o', 'Hello!');
|
|
|
41
41
|
|
|
42
42
|
That's it. The SDK handles x402 payment automatically.
|
|
43
43
|
|
|
44
|
+
## `BlockrunClient` — the universal primitive (recommended for new code)
|
|
45
|
+
|
|
46
|
+
Starting in `2.5.0`, the SDK ships a single `BlockrunClient` that speaks to
|
|
47
|
+
**every** BlockRun endpoint over x402. New API surfaces are intended to be
|
|
48
|
+
distributed as [Claude Code skills](https://github.com/anthropics/skills)
|
|
49
|
+
that drive this primitive — no SDK release required to add an endpoint.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { BlockrunClient } from '@blockrun/llm';
|
|
53
|
+
|
|
54
|
+
const br = new BlockrunClient();
|
|
55
|
+
|
|
56
|
+
// Sync GET — Surf market price (Tier 1, $0.001)
|
|
57
|
+
const btc = await br.get('/v1/surf/market/price', { symbol: 'BTC' });
|
|
58
|
+
|
|
59
|
+
// Sync POST — raw on-chain SQL (Tier 3, $0.020)
|
|
60
|
+
const rows = await br.post('/v1/surf/onchain/sql', {
|
|
61
|
+
query: 'SELECT block_number FROM ethereum.blocks ORDER BY block_number DESC LIMIT 1',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Submit + poll — long-running video gen (settled only on completion)
|
|
65
|
+
const video = await br.poll('/v1/videos/generations', {
|
|
66
|
+
model: 'xai/grok-imagine-video',
|
|
67
|
+
prompt: 'a red apple spinning',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Streaming SSE — chat completions
|
|
71
|
+
for await (const chunk of br.stream('/v1/chat/completions', {
|
|
72
|
+
model: 'anthropic/claude-sonnet-4-6',
|
|
73
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
74
|
+
stream: true,
|
|
75
|
+
})) {
|
|
76
|
+
process.stdout.write(chunk?.choices?.[0]?.delta?.content ?? '');
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Four call shapes cover every endpoint type:
|
|
81
|
+
- `get<T>(path, params?)` — synchronous GET (price, ranking, list, news)
|
|
82
|
+
- `post<T>(path, body?)` — synchronous POST (on-chain SQL, search)
|
|
83
|
+
- `poll<T>(path, body?, { budgetMs, intervalMs })` — submit + poll (image, video, music, voice)
|
|
84
|
+
- `stream<T>(path, body?)` — async iterator over SSE chunks (chat)
|
|
85
|
+
|
|
86
|
+
The per-API client classes (`LLMClient`, `ImageClient`, `VideoClient`,
|
|
87
|
+
`VoiceClient`, `MusicClient`, `SearchClient`, `XClient`, `PriceClient`,
|
|
88
|
+
`SurfClient`) all remain — they will be soft-deprecated in 2.6 (rewritten as
|
|
89
|
+
shims over `BlockrunClient`) and removed in 3.0.
|
|
90
|
+
|
|
44
91
|
### Try It Free (No USDC Required)
|
|
45
92
|
|
|
46
93
|
Want to kick the tires before funding a wallet? Route to BlockRun's free NVIDIA tier:
|
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
APIError: () => APIError,
|
|
34
34
|
AnthropicClient: () => AnthropicClient,
|
|
35
35
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
36
|
+
BlockrunClient: () => BlockrunClient,
|
|
36
37
|
BlockrunError: () => BlockrunError,
|
|
37
38
|
ImageClient: () => ImageClient,
|
|
38
39
|
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
@@ -3468,8 +3469,386 @@ var SurfClient = class {
|
|
|
3468
3469
|
}
|
|
3469
3470
|
};
|
|
3470
3471
|
|
|
3471
|
-
// src/
|
|
3472
|
+
// src/blockrun.ts
|
|
3472
3473
|
var import_accounts11 = require("viem/accounts");
|
|
3474
|
+
var DEFAULT_API_URL10 = "https://blockrun.ai/api";
|
|
3475
|
+
var DEFAULT_TIMEOUT10 = 6e4;
|
|
3476
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
3477
|
+
var DEFAULT_POLL_BUDGET_MS = 3e5;
|
|
3478
|
+
var MAX_SIGNED_AUTH_SECONDS = 600;
|
|
3479
|
+
var BlockrunClient = class {
|
|
3480
|
+
account;
|
|
3481
|
+
privateKey;
|
|
3482
|
+
apiUrl;
|
|
3483
|
+
timeout;
|
|
3484
|
+
sessionTotalUsd = 0;
|
|
3485
|
+
sessionCalls = 0;
|
|
3486
|
+
constructor(options = {}) {
|
|
3487
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3488
|
+
const privateKey = options.privateKey || envKey;
|
|
3489
|
+
if (!privateKey) {
|
|
3490
|
+
throw new Error(
|
|
3491
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
validatePrivateKey(privateKey);
|
|
3495
|
+
this.privateKey = privateKey;
|
|
3496
|
+
this.account = (0, import_accounts11.privateKeyToAccount)(privateKey);
|
|
3497
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL10;
|
|
3498
|
+
validateApiUrl(apiUrl);
|
|
3499
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3500
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT10;
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* GET a BlockRun endpoint. `path` is everything after `/api` (a leading
|
|
3504
|
+
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
|
|
3505
|
+
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
|
|
3506
|
+
*/
|
|
3507
|
+
async get(path5, params) {
|
|
3508
|
+
const url = this.buildUrl(path5, params);
|
|
3509
|
+
return this.requestWithPayment(url, "GET");
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* POST a BlockRun endpoint with a JSON body.
|
|
3513
|
+
*/
|
|
3514
|
+
async post(path5, body) {
|
|
3515
|
+
const url = this.buildUrl(path5);
|
|
3516
|
+
return this.requestWithPayment(url, "POST", body);
|
|
3517
|
+
}
|
|
3518
|
+
/**
|
|
3519
|
+
* Submit a long-running job and poll until it completes.
|
|
3520
|
+
*
|
|
3521
|
+
* Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
|
|
3522
|
+
* the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
|
|
3523
|
+
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
3524
|
+
* completed — upstream failure or caller giving up = no charge.
|
|
3525
|
+
*
|
|
3526
|
+
* If the gateway returns 200 directly on submit (no async surface), this
|
|
3527
|
+
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
3528
|
+
* video, music, voice) return 202 with a poll_url.
|
|
3529
|
+
*/
|
|
3530
|
+
async poll(path5, body, options) {
|
|
3531
|
+
const submitUrl = this.buildUrl(path5);
|
|
3532
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
|
|
3533
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3534
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
3535
|
+
method: "POST",
|
|
3536
|
+
headers: { "Content-Type": "application/json" },
|
|
3537
|
+
body: JSON.stringify(body ?? {})
|
|
3538
|
+
});
|
|
3539
|
+
if (resp402.status === 200) {
|
|
3540
|
+
return resp402.json();
|
|
3541
|
+
}
|
|
3542
|
+
if (resp402.status !== 402) {
|
|
3543
|
+
await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
|
|
3544
|
+
}
|
|
3545
|
+
const paymentPayload = await this.signFrom402(resp402, submitUrl, {
|
|
3546
|
+
description: "BlockRun async job",
|
|
3547
|
+
maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
|
|
3548
|
+
});
|
|
3549
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
3550
|
+
method: "POST",
|
|
3551
|
+
headers: {
|
|
3552
|
+
"Content-Type": "application/json",
|
|
3553
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3554
|
+
},
|
|
3555
|
+
body: JSON.stringify(body ?? {})
|
|
3556
|
+
});
|
|
3557
|
+
if (submitResp.status === 402) {
|
|
3558
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
3559
|
+
}
|
|
3560
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
3561
|
+
await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
|
|
3562
|
+
}
|
|
3563
|
+
if (submitResp.status === 200) {
|
|
3564
|
+
this.recordSpending();
|
|
3565
|
+
return submitResp.json();
|
|
3566
|
+
}
|
|
3567
|
+
const submitData = await submitResp.json();
|
|
3568
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
3569
|
+
throw new APIError(
|
|
3570
|
+
"Async submit response missing id/poll_url",
|
|
3571
|
+
submitResp.status,
|
|
3572
|
+
{ response: submitData }
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
3576
|
+
const deadline = Date.now() + budgetMs;
|
|
3577
|
+
let lastStatus = submitData.status || "queued";
|
|
3578
|
+
while (Date.now() < deadline) {
|
|
3579
|
+
await sleep(intervalMs);
|
|
3580
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
3581
|
+
method: "GET",
|
|
3582
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3583
|
+
});
|
|
3584
|
+
let pollData = {};
|
|
3585
|
+
try {
|
|
3586
|
+
pollData = await pollResp.json();
|
|
3587
|
+
} catch {
|
|
3588
|
+
}
|
|
3589
|
+
lastStatus = pollData.status || lastStatus;
|
|
3590
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
3591
|
+
continue;
|
|
3592
|
+
}
|
|
3593
|
+
if (lastStatus === "failed") {
|
|
3594
|
+
throw new APIError(
|
|
3595
|
+
`Upstream job failed: ${pollData.error || "unknown"}`,
|
|
3596
|
+
pollResp.status,
|
|
3597
|
+
sanitizeErrorResponse(pollData)
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
3601
|
+
this.recordSpending();
|
|
3602
|
+
return pollData;
|
|
3603
|
+
}
|
|
3604
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
3605
|
+
await this.throwApiError(pollResp, `poll failed (${pollUrl})`);
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
throw new APIError(
|
|
3609
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
3610
|
+
504,
|
|
3611
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
/**
|
|
3615
|
+
* Stream a Server-Sent Events endpoint.
|
|
3616
|
+
*
|
|
3617
|
+
* Yields each `data: …` line parsed as JSON. Stops when the upstream emits
|
|
3618
|
+
* `data: [DONE]` or closes the connection. Caller is responsible for typing
|
|
3619
|
+
* the chunk shape; pass a generic for typed yields.
|
|
3620
|
+
*
|
|
3621
|
+
* Example — streaming chat:
|
|
3622
|
+
* for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
|
|
3623
|
+
* model: "anthropic/claude-sonnet-4-6",
|
|
3624
|
+
* messages: [{ role: "user", content: "Hi" }],
|
|
3625
|
+
* stream: true,
|
|
3626
|
+
* })) {
|
|
3627
|
+
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
3628
|
+
* }
|
|
3629
|
+
*/
|
|
3630
|
+
async *stream(path5, body) {
|
|
3631
|
+
const url = this.buildUrl(path5);
|
|
3632
|
+
const requestBody = JSON.stringify(body ?? {});
|
|
3633
|
+
const resp402 = await this.fetchWithTimeout(url, {
|
|
3634
|
+
method: "POST",
|
|
3635
|
+
headers: { "Content-Type": "application/json" },
|
|
3636
|
+
body: requestBody
|
|
3637
|
+
});
|
|
3638
|
+
let streamResp;
|
|
3639
|
+
if (resp402.status === 200) {
|
|
3640
|
+
streamResp = resp402;
|
|
3641
|
+
} else if (resp402.status === 402) {
|
|
3642
|
+
const paymentPayload = await this.signFrom402(resp402, url, {
|
|
3643
|
+
description: "BlockRun stream"
|
|
3644
|
+
});
|
|
3645
|
+
streamResp = await this.fetchWithTimeout(url, {
|
|
3646
|
+
method: "POST",
|
|
3647
|
+
headers: {
|
|
3648
|
+
"Content-Type": "application/json",
|
|
3649
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3650
|
+
},
|
|
3651
|
+
body: requestBody
|
|
3652
|
+
});
|
|
3653
|
+
if (streamResp.status === 402) {
|
|
3654
|
+
throw new PaymentError(
|
|
3655
|
+
"Payment was rejected. Check your wallet balance."
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
if (!streamResp.ok) {
|
|
3659
|
+
await this.throwApiError(streamResp, `stream failed after payment (${url})`);
|
|
3660
|
+
}
|
|
3661
|
+
this.recordSpending();
|
|
3662
|
+
} else {
|
|
3663
|
+
await this.throwApiError(resp402, `stream failed (${url})`);
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
if (!streamResp.body) {
|
|
3667
|
+
throw new APIError("Stream response has no body", streamResp.status, {});
|
|
3668
|
+
}
|
|
3669
|
+
const reader = streamResp.body.getReader();
|
|
3670
|
+
const decoder = new TextDecoder();
|
|
3671
|
+
let buffer = "";
|
|
3672
|
+
try {
|
|
3673
|
+
while (true) {
|
|
3674
|
+
const { done, value } = await reader.read();
|
|
3675
|
+
if (done) break;
|
|
3676
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3677
|
+
const lines = buffer.split("\n");
|
|
3678
|
+
buffer = lines.pop() || "";
|
|
3679
|
+
for (const line of lines) {
|
|
3680
|
+
const trimmed = line.trim();
|
|
3681
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
3682
|
+
const data = trimmed.slice(6);
|
|
3683
|
+
if (data === "[DONE]") return;
|
|
3684
|
+
try {
|
|
3685
|
+
yield JSON.parse(data);
|
|
3686
|
+
} catch {
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
} finally {
|
|
3691
|
+
reader.releaseLock();
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
// --------------------------------------------------------------------
|
|
3695
|
+
// Internal: shared infrastructure
|
|
3696
|
+
// --------------------------------------------------------------------
|
|
3697
|
+
buildUrl(path5, params) {
|
|
3698
|
+
let normalized = path5.startsWith("/") ? path5 : `/${path5}`;
|
|
3699
|
+
if (normalized.startsWith("/api/")) {
|
|
3700
|
+
normalized = normalized.slice(4);
|
|
3701
|
+
}
|
|
3702
|
+
const base = `${this.apiUrl}${normalized}`;
|
|
3703
|
+
if (!params) return base;
|
|
3704
|
+
const qs = new URLSearchParams();
|
|
3705
|
+
for (const [key, value] of Object.entries(params)) {
|
|
3706
|
+
if (value === void 0 || value === null) continue;
|
|
3707
|
+
if (Array.isArray(value)) {
|
|
3708
|
+
for (const v of value) {
|
|
3709
|
+
if (v === void 0 || v === null) continue;
|
|
3710
|
+
qs.append(key, String(v));
|
|
3711
|
+
}
|
|
3712
|
+
} else {
|
|
3713
|
+
qs.append(key, String(value));
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
const query = qs.toString();
|
|
3717
|
+
return query ? `${base}?${query}` : base;
|
|
3718
|
+
}
|
|
3719
|
+
absolute(url) {
|
|
3720
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
3721
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
3722
|
+
return `${base}${url}`;
|
|
3723
|
+
}
|
|
3724
|
+
async requestWithPayment(url, method, body) {
|
|
3725
|
+
const init = { method };
|
|
3726
|
+
if (method === "POST") {
|
|
3727
|
+
init.headers = { "Content-Type": "application/json" };
|
|
3728
|
+
init.body = JSON.stringify(body ?? {});
|
|
3729
|
+
}
|
|
3730
|
+
const response = await this.fetchWithTimeout(url, init);
|
|
3731
|
+
if (response.status === 402) {
|
|
3732
|
+
return this.handlePaymentAndRetry(url, method, body, response);
|
|
3733
|
+
}
|
|
3734
|
+
if (!response.ok) {
|
|
3735
|
+
await this.throwApiError(response, `${method} ${url} failed`);
|
|
3736
|
+
}
|
|
3737
|
+
return response.json();
|
|
3738
|
+
}
|
|
3739
|
+
async handlePaymentAndRetry(url, method, body, response) {
|
|
3740
|
+
const paymentPayload = await this.signFrom402(response, url, {
|
|
3741
|
+
description: "BlockRun"
|
|
3742
|
+
});
|
|
3743
|
+
const retryInit = {
|
|
3744
|
+
method,
|
|
3745
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3746
|
+
};
|
|
3747
|
+
if (method === "POST") {
|
|
3748
|
+
retryInit.headers = {
|
|
3749
|
+
...retryInit.headers,
|
|
3750
|
+
"Content-Type": "application/json"
|
|
3751
|
+
};
|
|
3752
|
+
retryInit.body = JSON.stringify(body ?? {});
|
|
3753
|
+
}
|
|
3754
|
+
const retry = await this.fetchWithTimeout(url, retryInit);
|
|
3755
|
+
if (retry.status === 402) {
|
|
3756
|
+
throw new PaymentError(
|
|
3757
|
+
"Payment was rejected. Check your wallet balance."
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
if (!retry.ok) {
|
|
3761
|
+
await this.throwApiError(retry, `${method} ${url} failed after payment`);
|
|
3762
|
+
}
|
|
3763
|
+
this.recordSpending();
|
|
3764
|
+
return retry.json();
|
|
3765
|
+
}
|
|
3766
|
+
/**
|
|
3767
|
+
* Read a 402 response's payment requirements (header or body), then sign and
|
|
3768
|
+
* return the base64 PAYMENT-SIGNATURE payload. Also records the cost-to-be
|
|
3769
|
+
* onto the response context (settled on `recordSpending`).
|
|
3770
|
+
*/
|
|
3771
|
+
async signFrom402(response, url, opts) {
|
|
3772
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
3773
|
+
if (!paymentHeader) {
|
|
3774
|
+
try {
|
|
3775
|
+
const respBody = await response.json();
|
|
3776
|
+
if (respBody.x402Version !== void 0 || respBody.accepts !== void 0) {
|
|
3777
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
3778
|
+
}
|
|
3779
|
+
} catch {
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
if (!paymentHeader) {
|
|
3783
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
3784
|
+
}
|
|
3785
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
3786
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
3787
|
+
this.pendingCostUsd = parseFloat(details.amount) / 1e6;
|
|
3788
|
+
return createPaymentPayload(
|
|
3789
|
+
this.privateKey,
|
|
3790
|
+
this.account.address,
|
|
3791
|
+
details.recipient,
|
|
3792
|
+
details.amount,
|
|
3793
|
+
details.network || "eip155:8453",
|
|
3794
|
+
{
|
|
3795
|
+
resourceUrl: details.resource?.url || url,
|
|
3796
|
+
resourceDescription: details.resource?.description || opts.description,
|
|
3797
|
+
maxTimeoutSeconds: Math.max(
|
|
3798
|
+
details.maxTimeoutSeconds || 0,
|
|
3799
|
+
opts.maxTimeoutSeconds || 300
|
|
3800
|
+
),
|
|
3801
|
+
extra: details.extra
|
|
3802
|
+
}
|
|
3803
|
+
);
|
|
3804
|
+
}
|
|
3805
|
+
/** Accumulates the most-recent pending cost; settled by recordSpending. */
|
|
3806
|
+
pendingCostUsd = 0;
|
|
3807
|
+
recordSpending() {
|
|
3808
|
+
if (this.pendingCostUsd > 0) {
|
|
3809
|
+
this.sessionCalls += 1;
|
|
3810
|
+
this.sessionTotalUsd += this.pendingCostUsd;
|
|
3811
|
+
this.pendingCostUsd = 0;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
async throwApiError(resp, prefix) {
|
|
3815
|
+
let errorBody;
|
|
3816
|
+
try {
|
|
3817
|
+
errorBody = await resp.json();
|
|
3818
|
+
} catch {
|
|
3819
|
+
errorBody = { error: "Request failed" };
|
|
3820
|
+
}
|
|
3821
|
+
throw new APIError(
|
|
3822
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
3823
|
+
resp.status,
|
|
3824
|
+
sanitizeErrorResponse(errorBody)
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
async fetchWithTimeout(url, init) {
|
|
3828
|
+
const controller = new AbortController();
|
|
3829
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3830
|
+
try {
|
|
3831
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
3832
|
+
} finally {
|
|
3833
|
+
clearTimeout(timeoutId);
|
|
3834
|
+
}
|
|
3835
|
+
}
|
|
3836
|
+
// --------------------------------------------------------------------
|
|
3837
|
+
// Public surface: wallet + spending
|
|
3838
|
+
// --------------------------------------------------------------------
|
|
3839
|
+
getWalletAddress() {
|
|
3840
|
+
return this.account.address;
|
|
3841
|
+
}
|
|
3842
|
+
getSpending() {
|
|
3843
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
3844
|
+
}
|
|
3845
|
+
};
|
|
3846
|
+
function sleep(ms) {
|
|
3847
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// src/wallet.ts
|
|
3851
|
+
var import_accounts12 = require("viem/accounts");
|
|
3473
3852
|
var fs2 = __toESM(require("fs"), 1);
|
|
3474
3853
|
var path2 = __toESM(require("path"), 1);
|
|
3475
3854
|
var os2 = __toESM(require("os"), 1);
|
|
@@ -3478,8 +3857,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
3478
3857
|
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
3479
3858
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
3480
3859
|
function createWallet() {
|
|
3481
|
-
const privateKey = (0,
|
|
3482
|
-
const account = (0,
|
|
3860
|
+
const privateKey = (0, import_accounts12.generatePrivateKey)();
|
|
3861
|
+
const account = (0, import_accounts12.privateKeyToAccount)(privateKey);
|
|
3483
3862
|
return {
|
|
3484
3863
|
address: account.address,
|
|
3485
3864
|
privateKey
|
|
@@ -3535,12 +3914,12 @@ function loadWallet() {
|
|
|
3535
3914
|
function getOrCreateWallet() {
|
|
3536
3915
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3537
3916
|
if (envKey) {
|
|
3538
|
-
const account = (0,
|
|
3917
|
+
const account = (0, import_accounts12.privateKeyToAccount)(envKey);
|
|
3539
3918
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
3540
3919
|
}
|
|
3541
3920
|
const fileKey = loadWallet();
|
|
3542
3921
|
if (fileKey) {
|
|
3543
|
-
const account = (0,
|
|
3922
|
+
const account = (0, import_accounts12.privateKeyToAccount)(fileKey);
|
|
3544
3923
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
3545
3924
|
}
|
|
3546
3925
|
const { address, privateKey } = createWallet();
|
|
@@ -3550,11 +3929,11 @@ function getOrCreateWallet() {
|
|
|
3550
3929
|
function getWalletAddress() {
|
|
3551
3930
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3552
3931
|
if (envKey) {
|
|
3553
|
-
return (0,
|
|
3932
|
+
return (0, import_accounts12.privateKeyToAccount)(envKey).address;
|
|
3554
3933
|
}
|
|
3555
3934
|
const fileKey = loadWallet();
|
|
3556
3935
|
if (fileKey) {
|
|
3557
|
-
return (0,
|
|
3936
|
+
return (0, import_accounts12.privateKeyToAccount)(fileKey).address;
|
|
3558
3937
|
}
|
|
3559
3938
|
return null;
|
|
3560
3939
|
}
|
|
@@ -3734,7 +4113,7 @@ async function getOrCreateSolanaWallet() {
|
|
|
3734
4113
|
// src/solana-client.ts
|
|
3735
4114
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3736
4115
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3737
|
-
var
|
|
4116
|
+
var DEFAULT_TIMEOUT11 = 6e4;
|
|
3738
4117
|
var SDK_VERSION2 = "0.3.0";
|
|
3739
4118
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
3740
4119
|
var SolanaLLMClient = class {
|
|
@@ -3759,7 +4138,7 @@ var SolanaLLMClient = class {
|
|
|
3759
4138
|
validateApiUrl(apiUrl);
|
|
3760
4139
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3761
4140
|
this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
|
|
3762
|
-
this.timeout = options.timeout ||
|
|
4141
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT11;
|
|
3763
4142
|
}
|
|
3764
4143
|
/** Get Solana wallet address (public key in base58). */
|
|
3765
4144
|
async getWalletAddress() {
|
|
@@ -4666,7 +5045,7 @@ var OpenAI = class {
|
|
|
4666
5045
|
};
|
|
4667
5046
|
|
|
4668
5047
|
// src/anthropic-compat.ts
|
|
4669
|
-
var
|
|
5048
|
+
var import_accounts13 = require("viem/accounts");
|
|
4670
5049
|
var AnthropicClient = class {
|
|
4671
5050
|
_client = null;
|
|
4672
5051
|
_clientPromise = null;
|
|
@@ -4679,7 +5058,7 @@ var AnthropicClient = class {
|
|
|
4679
5058
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4680
5059
|
validatePrivateKey(key);
|
|
4681
5060
|
this._privateKey = key;
|
|
4682
|
-
this._account = (0,
|
|
5061
|
+
this._account = (0, import_accounts13.privateKeyToAccount)(this._privateKey);
|
|
4683
5062
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4684
5063
|
validateApiUrl(apiUrl);
|
|
4685
5064
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4778,6 +5157,7 @@ var AnthropicClient = class {
|
|
|
4778
5157
|
APIError,
|
|
4779
5158
|
AnthropicClient,
|
|
4780
5159
|
BASE_CHAIN_ID,
|
|
5160
|
+
BlockrunClient,
|
|
4781
5161
|
BlockrunError,
|
|
4782
5162
|
ImageClient,
|
|
4783
5163
|
KNOWN_PROVIDERS,
|