@blockrun/llm 3.16.0 → 3.17.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 +3 -3
- package/dist/index.cjs +100 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +100 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### Cut your LLM bill by <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%. One line of TypeScript.
|
|
6
6
|
|
|
7
|
-
The smart-routing SDK for <!-- br:models.chatVisible -->
|
|
7
|
+
The smart-routing SDK for <!-- br:models.chatVisible -->79<!-- /br:models.chatVisible --> models — every request goes to the cheapest model that can handle it,
|
|
8
8
|
paid with an API key or per-request USDC on Solana or Base. No vendor lock-in.
|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/@blockrun/llm)
|
|
@@ -56,7 +56,7 @@ console.log(r.response); // the proof
|
|
|
56
56
|
| | OpenAI SDK | OpenRouter | LiteLLM | **@blockrun/llm** |
|
|
57
57
|
| ------------------ | -------------- | ----------------- | ---------------- | ----------------------------------------------------------------------- |
|
|
58
58
|
| **Cost routing** | ✗ one vendor | Manual selection | Manual selection | **Automatic — <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper** |
|
|
59
|
-
| **Models** | GPT only | 200+ | 100+ (BYO keys) | **<!-- br:models.chatVisible -->
|
|
59
|
+
| **Models** | GPT only | 200+ | 100+ (BYO keys) | **<!-- br:models.chatVisible -->79<!-- /br:models.chatVisible -->, one credential** |
|
|
60
60
|
| **Free tier** | ✗ | Rate-limited | ✗ | **<!-- br:models.free -->6<!-- /br:models.free --> models, no signup** |
|
|
61
61
|
| **Auth** | API key | Account + API key | Your API keys | **API key *or* wallet signature** |
|
|
62
62
|
| **Payment** | Card + invoice | Credit card | BYO keys | **Account credit or USDC per-request** |
|
|
@@ -1774,7 +1774,7 @@ The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch
|
|
|
1774
1774
|
## Frequently Asked Questions
|
|
1775
1775
|
|
|
1776
1776
|
### What is @blockrun/llm?
|
|
1777
|
-
@blockrun/llm is a TypeScript SDK that cuts LLM costs by up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% with built-in smart routing: every request is routed to the cheapest of <!-- br:models.chatVisible -->
|
|
1777
|
+
@blockrun/llm is a TypeScript SDK that cuts LLM costs by up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% with built-in smart routing: every request is routed to the cheapest of <!-- br:models.chatVisible -->79<!-- /br:models.chatVisible --> models (OpenAI, Anthropic, Google, xAI, DeepSeek, Moonshot, and more) that can handle it, then paid per-request in USDC via the x402 protocol — with API key account billing or x402 wallet payments on Solana or Base.
|
|
1778
1778
|
|
|
1779
1779
|
### How does payment work?
|
|
1780
1780
|
When you make an API call, the SDK automatically handles x402 payment. It signs a USDC transaction locally using your wallet private key (which never leaves your machine), and includes the payment proof in the request header. Settlement is non-custodial and instant on Base or Solana.
|
package/dist/index.cjs
CHANGED
|
@@ -8715,6 +8715,15 @@ var BlockrunClient = class {
|
|
|
8715
8715
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
8716
8716
|
* completed — upstream failure or caller giving up = no charge.
|
|
8717
8717
|
*
|
|
8718
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
8719
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
8720
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
8721
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
8722
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
8723
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
8724
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
8725
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
8726
|
+
*
|
|
8718
8727
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
8719
8728
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8720
8729
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -9949,6 +9958,18 @@ var SolanaLLMClient = class {
|
|
|
9949
9958
|
}
|
|
9950
9959
|
}
|
|
9951
9960
|
/** Edit an image using img2img (Solana payment). */
|
|
9961
|
+
/**
|
|
9962
|
+
* Generate an image on Solana.
|
|
9963
|
+
*
|
|
9964
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
9965
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
9966
|
+
* models answer 200 inline and settle there.
|
|
9967
|
+
*/
|
|
9968
|
+
async image(prompt, options = {}) {
|
|
9969
|
+
const body = { prompt, ...options };
|
|
9970
|
+
const data = await this.requestWithPaymentRaw("/v1/images/generations", body);
|
|
9971
|
+
return data;
|
|
9972
|
+
}
|
|
9952
9973
|
async imageEdit(prompt, image, options) {
|
|
9953
9974
|
const body = {
|
|
9954
9975
|
model: options?.model || "openai/gpt-image-2",
|
|
@@ -10196,6 +10217,81 @@ var SolanaLLMClient = class {
|
|
|
10196
10217
|
* @returns the header value to replay with, and what it will settle for.
|
|
10197
10218
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
10198
10219
|
*/
|
|
10220
|
+
/**
|
|
10221
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
10222
|
+
*
|
|
10223
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
10224
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
10225
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
10226
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
10227
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
10228
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
10229
|
+
* so signing per poll costs signatures, never money.
|
|
10230
|
+
*/
|
|
10231
|
+
async followSolanaJob(submitBody, budgetMs, intervalMs = 2e3) {
|
|
10232
|
+
const id = submitBody.id;
|
|
10233
|
+
const pollPath = submitBody.poll_url;
|
|
10234
|
+
if (!id || !pollPath) return submitBody;
|
|
10235
|
+
const pollUrl = pollPath.startsWith("http") ? pollPath : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
10236
|
+
const deadline = Date.now() + budgetMs;
|
|
10237
|
+
let lastStatus = submitBody.status || "queued";
|
|
10238
|
+
while (Date.now() < deadline) {
|
|
10239
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
10240
|
+
const challenge = await this.fetchWithTimeout(pollUrl, {
|
|
10241
|
+
method: "GET",
|
|
10242
|
+
headers: { "User-Agent": USER_AGENT }
|
|
10243
|
+
});
|
|
10244
|
+
if (challenge.status === 200) {
|
|
10245
|
+
const done = await challenge.json();
|
|
10246
|
+
return done;
|
|
10247
|
+
}
|
|
10248
|
+
if (challenge.status !== 402) {
|
|
10249
|
+
let errorBody;
|
|
10250
|
+
try {
|
|
10251
|
+
errorBody = await challenge.json();
|
|
10252
|
+
} catch {
|
|
10253
|
+
errorBody = { error: "Poll failed" };
|
|
10254
|
+
}
|
|
10255
|
+
throw new APIError(`Poll failed: ${challenge.status}`, challenge.status, sanitizeErrorResponse(errorBody));
|
|
10256
|
+
}
|
|
10257
|
+
const { paymentPayload, costUsd } = await this.signPaymentFrom402(
|
|
10258
|
+
pollUrl,
|
|
10259
|
+
challenge,
|
|
10260
|
+
true,
|
|
10261
|
+
// always a fresh blockhash; the last one is stale by now
|
|
10262
|
+
pollUrl
|
|
10263
|
+
);
|
|
10264
|
+
const paid = await this.fetchWithTimeout(pollUrl, {
|
|
10265
|
+
method: "GET",
|
|
10266
|
+
headers: { "User-Agent": USER_AGENT, "PAYMENT-SIGNATURE": paymentPayload }
|
|
10267
|
+
});
|
|
10268
|
+
let data = {};
|
|
10269
|
+
try {
|
|
10270
|
+
data = await paid.json();
|
|
10271
|
+
} catch {
|
|
10272
|
+
}
|
|
10273
|
+
lastStatus = data.status || lastStatus;
|
|
10274
|
+
if (lastStatus === "failed") {
|
|
10275
|
+
throw new APIError(
|
|
10276
|
+
`Upstream job failed: ${data.error || "unknown"}`,
|
|
10277
|
+
paid.status,
|
|
10278
|
+
sanitizeErrorResponse(data)
|
|
10279
|
+
);
|
|
10280
|
+
}
|
|
10281
|
+
if (paid.status === 200 && lastStatus === "completed") {
|
|
10282
|
+
this.recordSettlement(costUsd);
|
|
10283
|
+
return data;
|
|
10284
|
+
}
|
|
10285
|
+
if (paid.status !== 200 && paid.status !== 202 && paid.status !== 504) {
|
|
10286
|
+
throw new APIError(`Poll failed: ${paid.status}`, paid.status, sanitizeErrorResponse(data));
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
throw new APIError(
|
|
10290
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
10291
|
+
504,
|
|
10292
|
+
{ id, last_status: lastStatus }
|
|
10293
|
+
);
|
|
10294
|
+
}
|
|
10199
10295
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10200
10296
|
let paymentHeader = response.headers.get("payment-required");
|
|
10201
10297
|
if (!paymentHeader) {
|
|
@@ -10306,6 +10402,10 @@ var SolanaLLMClient = class {
|
|
|
10306
10402
|
body: JSON.stringify(body)
|
|
10307
10403
|
});
|
|
10308
10404
|
await this.assertPaid(retryResponse);
|
|
10405
|
+
if (retryResponse.status === 202) {
|
|
10406
|
+
const submitted = await retryResponse.json();
|
|
10407
|
+
return this.followSolanaJob(submitted, this.timeout);
|
|
10408
|
+
}
|
|
10309
10409
|
this.recordSettlement(costUsd);
|
|
10310
10410
|
return retryResponse.json();
|
|
10311
10411
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -2264,6 +2264,15 @@ declare class BlockrunClient {
|
|
|
2264
2264
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
2265
2265
|
* completed — upstream failure or caller giving up = no charge.
|
|
2266
2266
|
*
|
|
2267
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
2268
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
2269
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
2270
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
2271
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
2272
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
2273
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
2274
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
2275
|
+
*
|
|
2267
2276
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
2268
2277
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
2269
2278
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -2768,6 +2777,18 @@ declare class SolanaLLMClient {
|
|
|
2768
2777
|
*/
|
|
2769
2778
|
getBalance(): Promise<number>;
|
|
2770
2779
|
/** Edit an image using img2img (Solana payment). */
|
|
2780
|
+
/**
|
|
2781
|
+
* Generate an image on Solana.
|
|
2782
|
+
*
|
|
2783
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
2784
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
2785
|
+
* models answer 200 inline and settle there.
|
|
2786
|
+
*/
|
|
2787
|
+
image(prompt: string, options?: {
|
|
2788
|
+
model?: string;
|
|
2789
|
+
size?: string;
|
|
2790
|
+
n?: number;
|
|
2791
|
+
} & Record<string, unknown>): Promise<ImageResponse>;
|
|
2771
2792
|
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
|
|
2772
2793
|
/** Standalone search (Solana payment). */
|
|
2773
2794
|
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
@@ -2897,6 +2918,18 @@ declare class SolanaLLMClient {
|
|
|
2897
2918
|
* @returns the header value to replay with, and what it will settle for.
|
|
2898
2919
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
2899
2920
|
*/
|
|
2921
|
+
/**
|
|
2922
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
2923
|
+
*
|
|
2924
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
2925
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
2926
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
2927
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
2928
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
2929
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
2930
|
+
* so signing per poll costs signatures, never money.
|
|
2931
|
+
*/
|
|
2932
|
+
private followSolanaJob;
|
|
2900
2933
|
private signPaymentFrom402;
|
|
2901
2934
|
private handlePaymentAndRetry;
|
|
2902
2935
|
private requestWithPaymentRaw;
|
package/dist/index.d.ts
CHANGED
|
@@ -2264,6 +2264,15 @@ declare class BlockrunClient {
|
|
|
2264
2264
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
2265
2265
|
* completed — upstream failure or caller giving up = no charge.
|
|
2266
2266
|
*
|
|
2267
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
2268
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
2269
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
2270
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
2271
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
2272
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
2273
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
2274
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
2275
|
+
*
|
|
2267
2276
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
2268
2277
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
2269
2278
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -2768,6 +2777,18 @@ declare class SolanaLLMClient {
|
|
|
2768
2777
|
*/
|
|
2769
2778
|
getBalance(): Promise<number>;
|
|
2770
2779
|
/** Edit an image using img2img (Solana payment). */
|
|
2780
|
+
/**
|
|
2781
|
+
* Generate an image on Solana.
|
|
2782
|
+
*
|
|
2783
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
2784
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
2785
|
+
* models answer 200 inline and settle there.
|
|
2786
|
+
*/
|
|
2787
|
+
image(prompt: string, options?: {
|
|
2788
|
+
model?: string;
|
|
2789
|
+
size?: string;
|
|
2790
|
+
n?: number;
|
|
2791
|
+
} & Record<string, unknown>): Promise<ImageResponse>;
|
|
2771
2792
|
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
|
|
2772
2793
|
/** Standalone search (Solana payment). */
|
|
2773
2794
|
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
@@ -2897,6 +2918,18 @@ declare class SolanaLLMClient {
|
|
|
2897
2918
|
* @returns the header value to replay with, and what it will settle for.
|
|
2898
2919
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
2899
2920
|
*/
|
|
2921
|
+
/**
|
|
2922
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
2923
|
+
*
|
|
2924
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
2925
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
2926
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
2927
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
2928
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
2929
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
2930
|
+
* so signing per poll costs signatures, never money.
|
|
2931
|
+
*/
|
|
2932
|
+
private followSolanaJob;
|
|
2900
2933
|
private signPaymentFrom402;
|
|
2901
2934
|
private handlePaymentAndRetry;
|
|
2902
2935
|
private requestWithPaymentRaw;
|
package/dist/index.js
CHANGED
|
@@ -8594,6 +8594,15 @@ var BlockrunClient = class {
|
|
|
8594
8594
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
8595
8595
|
* completed — upstream failure or caller giving up = no charge.
|
|
8596
8596
|
*
|
|
8597
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
8598
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
8599
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
8600
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
8601
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
8602
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
8603
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
8604
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
8605
|
+
*
|
|
8597
8606
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
8598
8607
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8599
8608
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -9828,6 +9837,18 @@ var SolanaLLMClient = class {
|
|
|
9828
9837
|
}
|
|
9829
9838
|
}
|
|
9830
9839
|
/** Edit an image using img2img (Solana payment). */
|
|
9840
|
+
/**
|
|
9841
|
+
* Generate an image on Solana.
|
|
9842
|
+
*
|
|
9843
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
9844
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
9845
|
+
* models answer 200 inline and settle there.
|
|
9846
|
+
*/
|
|
9847
|
+
async image(prompt, options = {}) {
|
|
9848
|
+
const body = { prompt, ...options };
|
|
9849
|
+
const data = await this.requestWithPaymentRaw("/v1/images/generations", body);
|
|
9850
|
+
return data;
|
|
9851
|
+
}
|
|
9831
9852
|
async imageEdit(prompt, image, options) {
|
|
9832
9853
|
const body = {
|
|
9833
9854
|
model: options?.model || "openai/gpt-image-2",
|
|
@@ -10075,6 +10096,81 @@ var SolanaLLMClient = class {
|
|
|
10075
10096
|
* @returns the header value to replay with, and what it will settle for.
|
|
10076
10097
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
10077
10098
|
*/
|
|
10099
|
+
/**
|
|
10100
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
10101
|
+
*
|
|
10102
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
10103
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
10104
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
10105
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
10106
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
10107
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
10108
|
+
* so signing per poll costs signatures, never money.
|
|
10109
|
+
*/
|
|
10110
|
+
async followSolanaJob(submitBody, budgetMs, intervalMs = 2e3) {
|
|
10111
|
+
const id = submitBody.id;
|
|
10112
|
+
const pollPath = submitBody.poll_url;
|
|
10113
|
+
if (!id || !pollPath) return submitBody;
|
|
10114
|
+
const pollUrl = pollPath.startsWith("http") ? pollPath : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
10115
|
+
const deadline = Date.now() + budgetMs;
|
|
10116
|
+
let lastStatus = submitBody.status || "queued";
|
|
10117
|
+
while (Date.now() < deadline) {
|
|
10118
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
10119
|
+
const challenge = await this.fetchWithTimeout(pollUrl, {
|
|
10120
|
+
method: "GET",
|
|
10121
|
+
headers: { "User-Agent": USER_AGENT }
|
|
10122
|
+
});
|
|
10123
|
+
if (challenge.status === 200) {
|
|
10124
|
+
const done = await challenge.json();
|
|
10125
|
+
return done;
|
|
10126
|
+
}
|
|
10127
|
+
if (challenge.status !== 402) {
|
|
10128
|
+
let errorBody;
|
|
10129
|
+
try {
|
|
10130
|
+
errorBody = await challenge.json();
|
|
10131
|
+
} catch {
|
|
10132
|
+
errorBody = { error: "Poll failed" };
|
|
10133
|
+
}
|
|
10134
|
+
throw new APIError(`Poll failed: ${challenge.status}`, challenge.status, sanitizeErrorResponse(errorBody));
|
|
10135
|
+
}
|
|
10136
|
+
const { paymentPayload, costUsd } = await this.signPaymentFrom402(
|
|
10137
|
+
pollUrl,
|
|
10138
|
+
challenge,
|
|
10139
|
+
true,
|
|
10140
|
+
// always a fresh blockhash; the last one is stale by now
|
|
10141
|
+
pollUrl
|
|
10142
|
+
);
|
|
10143
|
+
const paid = await this.fetchWithTimeout(pollUrl, {
|
|
10144
|
+
method: "GET",
|
|
10145
|
+
headers: { "User-Agent": USER_AGENT, "PAYMENT-SIGNATURE": paymentPayload }
|
|
10146
|
+
});
|
|
10147
|
+
let data = {};
|
|
10148
|
+
try {
|
|
10149
|
+
data = await paid.json();
|
|
10150
|
+
} catch {
|
|
10151
|
+
}
|
|
10152
|
+
lastStatus = data.status || lastStatus;
|
|
10153
|
+
if (lastStatus === "failed") {
|
|
10154
|
+
throw new APIError(
|
|
10155
|
+
`Upstream job failed: ${data.error || "unknown"}`,
|
|
10156
|
+
paid.status,
|
|
10157
|
+
sanitizeErrorResponse(data)
|
|
10158
|
+
);
|
|
10159
|
+
}
|
|
10160
|
+
if (paid.status === 200 && lastStatus === "completed") {
|
|
10161
|
+
this.recordSettlement(costUsd);
|
|
10162
|
+
return data;
|
|
10163
|
+
}
|
|
10164
|
+
if (paid.status !== 200 && paid.status !== 202 && paid.status !== 504) {
|
|
10165
|
+
throw new APIError(`Poll failed: ${paid.status}`, paid.status, sanitizeErrorResponse(data));
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
throw new APIError(
|
|
10169
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
10170
|
+
504,
|
|
10171
|
+
{ id, last_status: lastStatus }
|
|
10172
|
+
);
|
|
10173
|
+
}
|
|
10078
10174
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10079
10175
|
let paymentHeader = response.headers.get("payment-required");
|
|
10080
10176
|
if (!paymentHeader) {
|
|
@@ -10185,6 +10281,10 @@ var SolanaLLMClient = class {
|
|
|
10185
10281
|
body: JSON.stringify(body)
|
|
10186
10282
|
});
|
|
10187
10283
|
await this.assertPaid(retryResponse);
|
|
10284
|
+
if (retryResponse.status === 202) {
|
|
10285
|
+
const submitted = await retryResponse.json();
|
|
10286
|
+
return this.followSolanaJob(submitted, this.timeout);
|
|
10287
|
+
}
|
|
10188
10288
|
this.recordSettlement(costUsd);
|
|
10189
10289
|
return retryResponse.json();
|
|
10190
10290
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "TypeScript SDK for BlockRun - every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. No rate limits.",
|
|
6
6
|
"main": "dist/index.cjs",
|