@blockrun/llm 1.4.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/index.cjs +562 -66
- package/dist/index.d.cts +256 -11
- package/dist/index.d.ts +256 -11
- package/dist/index.js +555 -65
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
36
36
|
BlockrunError: () => BlockrunError,
|
|
37
37
|
ImageClient: () => ImageClient,
|
|
38
|
+
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
38
39
|
LLMClient: () => LLMClient,
|
|
39
40
|
OpenAI: () => OpenAI,
|
|
40
41
|
PaymentError: () => PaymentError,
|
|
@@ -58,6 +59,7 @@ __export(index_exports, {
|
|
|
58
59
|
formatWalletCreatedMessage: () => formatWalletCreatedMessage,
|
|
59
60
|
getCached: () => getCached,
|
|
60
61
|
getCachedByRequest: () => getCachedByRequest,
|
|
62
|
+
getCostLogSummary: () => getCostLogSummary,
|
|
61
63
|
getCostSummary: () => getCostSummary,
|
|
62
64
|
getEip681Uri: () => getEip681Uri,
|
|
63
65
|
getOrCreateSolanaWallet: () => getOrCreateSolanaWallet,
|
|
@@ -80,7 +82,11 @@ __export(index_exports, {
|
|
|
80
82
|
solanaKeyToBytes: () => solanaKeyToBytes,
|
|
81
83
|
solanaPublicKey: () => solanaPublicKey,
|
|
82
84
|
status: () => status,
|
|
83
|
-
testnetClient: () => testnetClient
|
|
85
|
+
testnetClient: () => testnetClient,
|
|
86
|
+
validateMaxTokens: () => validateMaxTokens,
|
|
87
|
+
validateModel: () => validateModel,
|
|
88
|
+
validateTemperature: () => validateTemperature,
|
|
89
|
+
validateTopP: () => validateTopP
|
|
84
90
|
});
|
|
85
91
|
module.exports = __toCommonJS(index_exports);
|
|
86
92
|
|
|
@@ -302,6 +308,61 @@ function extractPaymentDetails(paymentRequired, preferredNetwork) {
|
|
|
302
308
|
|
|
303
309
|
// src/validation.ts
|
|
304
310
|
var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
|
|
311
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set([
|
|
312
|
+
"openai",
|
|
313
|
+
"anthropic",
|
|
314
|
+
"google",
|
|
315
|
+
"deepseek",
|
|
316
|
+
"mistralai",
|
|
317
|
+
"meta-llama",
|
|
318
|
+
"together",
|
|
319
|
+
"xai",
|
|
320
|
+
"moonshot",
|
|
321
|
+
"nvidia",
|
|
322
|
+
"minimax",
|
|
323
|
+
"zai"
|
|
324
|
+
]);
|
|
325
|
+
function validateModel(model) {
|
|
326
|
+
if (!model || typeof model !== "string") {
|
|
327
|
+
throw new Error("Model must be a non-empty string");
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function validateMaxTokens(maxTokens) {
|
|
331
|
+
if (maxTokens === void 0 || maxTokens === null) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens)) {
|
|
335
|
+
throw new Error("maxTokens must be an integer");
|
|
336
|
+
}
|
|
337
|
+
if (maxTokens < 1) {
|
|
338
|
+
throw new Error("maxTokens must be positive (minimum: 1)");
|
|
339
|
+
}
|
|
340
|
+
if (maxTokens > 1e5) {
|
|
341
|
+
throw new Error("maxTokens too large (maximum: 100000)");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function validateTemperature(temperature) {
|
|
345
|
+
if (temperature === void 0 || temperature === null) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (typeof temperature !== "number") {
|
|
349
|
+
throw new Error("temperature must be a number");
|
|
350
|
+
}
|
|
351
|
+
if (temperature < 0 || temperature > 2) {
|
|
352
|
+
throw new Error("temperature must be between 0 and 2");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function validateTopP(topP) {
|
|
356
|
+
if (topP === void 0 || topP === null) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (typeof topP !== "number") {
|
|
360
|
+
throw new Error("topP must be a number");
|
|
361
|
+
}
|
|
362
|
+
if (topP < 0 || topP > 1) {
|
|
363
|
+
throw new Error("topP must be between 0 and 1");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
305
366
|
function validatePrivateKey(key) {
|
|
306
367
|
if (typeof key !== "string") {
|
|
307
368
|
throw new Error("Private key must be a string");
|
|
@@ -377,9 +438,9 @@ var DEFAULT_API_URL = "https://blockrun.ai/api";
|
|
|
377
438
|
var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
|
|
378
439
|
var DEFAULT_MAX_TOKENS = 1024;
|
|
379
440
|
var DEFAULT_TIMEOUT = 6e4;
|
|
380
|
-
var SDK_VERSION = "
|
|
441
|
+
var SDK_VERSION = "1.5.0";
|
|
381
442
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
382
|
-
var LLMClient = class {
|
|
443
|
+
var LLMClient = class _LLMClient {
|
|
383
444
|
static DEFAULT_API_URL = DEFAULT_API_URL;
|
|
384
445
|
static TESTNET_API_URL = TESTNET_API_URL;
|
|
385
446
|
account;
|
|
@@ -390,6 +451,11 @@ var LLMClient = class {
|
|
|
390
451
|
sessionCalls = 0;
|
|
391
452
|
modelPricingCache = null;
|
|
392
453
|
modelPricingPromise = null;
|
|
454
|
+
// Pre-auth cache: avoids the 402 round-trip on repeat requests to the same model.
|
|
455
|
+
// Key = "endpoint:model", value = cached payment header + timestamp.
|
|
456
|
+
// TTL: 1 hour (mirrors ClawRouter's payment-preauth.ts approach).
|
|
457
|
+
preAuthCache = /* @__PURE__ */ new Map();
|
|
458
|
+
static PRE_AUTH_TTL_MS = 36e5;
|
|
393
459
|
/**
|
|
394
460
|
* Initialize the BlockRun LLM client.
|
|
395
461
|
*
|
|
@@ -414,13 +480,13 @@ var LLMClient = class {
|
|
|
414
480
|
/**
|
|
415
481
|
* Simple 1-line chat interface.
|
|
416
482
|
*
|
|
417
|
-
* @param model - Model ID (e.g., 'openai/gpt-
|
|
483
|
+
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
|
|
418
484
|
* @param prompt - User message
|
|
419
485
|
* @param options - Optional chat parameters
|
|
420
486
|
* @returns Assistant's response text
|
|
421
487
|
*
|
|
422
488
|
* @example
|
|
423
|
-
* const response = await client.chat('gpt-
|
|
489
|
+
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
|
|
424
490
|
* console.log(response); // 'The capital of France is Paris.'
|
|
425
491
|
*/
|
|
426
492
|
async chat(model, prompt, options) {
|
|
@@ -566,6 +632,27 @@ var LLMClient = class {
|
|
|
566
632
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
567
633
|
body: JSON.stringify(body)
|
|
568
634
|
});
|
|
635
|
+
if (response.status === 502 || response.status === 503) {
|
|
636
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
637
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
640
|
+
body: JSON.stringify(body)
|
|
641
|
+
});
|
|
642
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
643
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetry(url, body, retryResp);
|
|
644
|
+
if (!retryResp.ok) {
|
|
645
|
+
let errorBody;
|
|
646
|
+
try {
|
|
647
|
+
errorBody = await retryResp.json();
|
|
648
|
+
} catch {
|
|
649
|
+
errorBody = { error: "Request failed" };
|
|
650
|
+
}
|
|
651
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
652
|
+
}
|
|
653
|
+
return retryResp.json();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
569
656
|
if (response.status === 402) {
|
|
570
657
|
return this.handlePaymentAndRetry(url, body, response);
|
|
571
658
|
}
|
|
@@ -631,6 +718,34 @@ var LLMClient = class {
|
|
|
631
718
|
},
|
|
632
719
|
body: JSON.stringify(body)
|
|
633
720
|
});
|
|
721
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
722
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
723
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
724
|
+
method: "POST",
|
|
725
|
+
headers: {
|
|
726
|
+
"Content-Type": "application/json",
|
|
727
|
+
"User-Agent": USER_AGENT,
|
|
728
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
729
|
+
},
|
|
730
|
+
body: JSON.stringify(body)
|
|
731
|
+
});
|
|
732
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
733
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
734
|
+
if (!retryResp2.ok) {
|
|
735
|
+
let errorBody;
|
|
736
|
+
try {
|
|
737
|
+
errorBody = await retryResp2.json();
|
|
738
|
+
} catch {
|
|
739
|
+
errorBody = { error: "Request failed" };
|
|
740
|
+
}
|
|
741
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
742
|
+
}
|
|
743
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
744
|
+
this.sessionCalls += 1;
|
|
745
|
+
this.sessionTotalUsd += costUsd2;
|
|
746
|
+
return retryResp2.json();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
634
749
|
if (retryResponse.status === 402) {
|
|
635
750
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
636
751
|
}
|
|
@@ -652,6 +767,131 @@ var LLMClient = class {
|
|
|
652
767
|
this.sessionTotalUsd += costUsd;
|
|
653
768
|
return retryResponse.json();
|
|
654
769
|
}
|
|
770
|
+
/**
|
|
771
|
+
* Sign a payment header and return the PAYMENT-SIGNATURE value.
|
|
772
|
+
* Extracted to share logic between streaming and non-streaming flows.
|
|
773
|
+
*/
|
|
774
|
+
async signPayment(paymentHeader) {
|
|
775
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
776
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
777
|
+
const extensions = paymentRequired.extensions;
|
|
778
|
+
const paymentPayload = await createPaymentPayload(
|
|
779
|
+
this.privateKey,
|
|
780
|
+
this.account.address,
|
|
781
|
+
details.recipient,
|
|
782
|
+
details.amount,
|
|
783
|
+
details.network || "eip155:8453",
|
|
784
|
+
{
|
|
785
|
+
resourceUrl: validateResourceUrl(
|
|
786
|
+
details.resource?.url || `${this.apiUrl}/v1/chat/completions`,
|
|
787
|
+
this.apiUrl
|
|
788
|
+
),
|
|
789
|
+
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
790
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
791
|
+
extra: details.extra,
|
|
792
|
+
extensions
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
const costUsd = parseFloat(details.amount) / 1e6;
|
|
796
|
+
return { paymentPayload, costUsd };
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Streaming chat completion with automatic x402 payment.
|
|
800
|
+
*
|
|
801
|
+
* Uses a pre-auth cache so repeat calls to the same model skip the 402
|
|
802
|
+
* round-trip (~200ms savings). Falls back to the normal 402 flow on cache
|
|
803
|
+
* miss or if the pre-signed payment is rejected.
|
|
804
|
+
*
|
|
805
|
+
* @returns Raw fetch Response with a streaming SSE body.
|
|
806
|
+
*/
|
|
807
|
+
async chatCompletionStream(model, messages, options) {
|
|
808
|
+
const url = `${this.apiUrl}/v1/chat/completions`;
|
|
809
|
+
const body = {
|
|
810
|
+
model,
|
|
811
|
+
messages,
|
|
812
|
+
max_tokens: options?.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
813
|
+
stream: true
|
|
814
|
+
};
|
|
815
|
+
if (options?.temperature !== void 0) body.temperature = options.temperature;
|
|
816
|
+
if (options?.topP !== void 0) body.top_p = options.topP;
|
|
817
|
+
if (options?.tools !== void 0) body.tools = options.tools;
|
|
818
|
+
if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
|
|
819
|
+
const cacheKey2 = `/v1/chat/completions:${model}`;
|
|
820
|
+
const cached = this.preAuthCache.get(cacheKey2);
|
|
821
|
+
const now = Date.now();
|
|
822
|
+
if (cached && now - cached.cachedAt < _LLMClient.PRE_AUTH_TTL_MS) {
|
|
823
|
+
try {
|
|
824
|
+
const { paymentPayload: paymentPayload2, costUsd: costUsd2 } = await this.signPayment(cached.paymentHeader);
|
|
825
|
+
const preAuthResp = await this.fetchWithTimeout(url, {
|
|
826
|
+
method: "POST",
|
|
827
|
+
headers: {
|
|
828
|
+
"Content-Type": "application/json",
|
|
829
|
+
"User-Agent": USER_AGENT,
|
|
830
|
+
"PAYMENT-SIGNATURE": paymentPayload2
|
|
831
|
+
},
|
|
832
|
+
body: JSON.stringify(body)
|
|
833
|
+
});
|
|
834
|
+
if (preAuthResp.status !== 402 && preAuthResp.ok) {
|
|
835
|
+
this.sessionCalls += 1;
|
|
836
|
+
this.sessionTotalUsd += costUsd2;
|
|
837
|
+
return preAuthResp;
|
|
838
|
+
}
|
|
839
|
+
this.preAuthCache.delete(cacheKey2);
|
|
840
|
+
} catch {
|
|
841
|
+
this.preAuthCache.delete(cacheKey2);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const firstResp = await this.fetchWithTimeout(url, {
|
|
845
|
+
method: "POST",
|
|
846
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
847
|
+
body: JSON.stringify(body)
|
|
848
|
+
});
|
|
849
|
+
if (firstResp.status !== 402) {
|
|
850
|
+
if (!firstResp.ok) {
|
|
851
|
+
let errorBody;
|
|
852
|
+
try {
|
|
853
|
+
errorBody = await firstResp.json();
|
|
854
|
+
} catch {
|
|
855
|
+
errorBody = { error: "Request failed" };
|
|
856
|
+
}
|
|
857
|
+
throw new APIError(`API error: ${firstResp.status}`, firstResp.status, sanitizeErrorResponse(errorBody));
|
|
858
|
+
}
|
|
859
|
+
return firstResp;
|
|
860
|
+
}
|
|
861
|
+
let paymentHeader = firstResp.headers.get("payment-required");
|
|
862
|
+
if (!paymentHeader) {
|
|
863
|
+
try {
|
|
864
|
+
const rb = await firstResp.json();
|
|
865
|
+
if (rb.x402 || rb.accepts) paymentHeader = btoa(JSON.stringify(rb));
|
|
866
|
+
} catch {
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (!paymentHeader) throw new PaymentError("402 response but no payment requirements found");
|
|
870
|
+
this.preAuthCache.set(cacheKey2, { paymentHeader, cachedAt: now });
|
|
871
|
+
const { paymentPayload, costUsd } = await this.signPayment(paymentHeader);
|
|
872
|
+
const streamResp = await this.fetchWithTimeout(url, {
|
|
873
|
+
method: "POST",
|
|
874
|
+
headers: {
|
|
875
|
+
"Content-Type": "application/json",
|
|
876
|
+
"User-Agent": USER_AGENT,
|
|
877
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
878
|
+
},
|
|
879
|
+
body: JSON.stringify(body)
|
|
880
|
+
});
|
|
881
|
+
if (streamResp.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
882
|
+
if (!streamResp.ok) {
|
|
883
|
+
let errorBody;
|
|
884
|
+
try {
|
|
885
|
+
errorBody = await streamResp.json();
|
|
886
|
+
} catch {
|
|
887
|
+
errorBody = { error: "Request failed" };
|
|
888
|
+
}
|
|
889
|
+
throw new APIError(`API error after payment: ${streamResp.status}`, streamResp.status, sanitizeErrorResponse(errorBody));
|
|
890
|
+
}
|
|
891
|
+
this.sessionCalls += 1;
|
|
892
|
+
this.sessionTotalUsd += costUsd;
|
|
893
|
+
return streamResp;
|
|
894
|
+
}
|
|
655
895
|
/**
|
|
656
896
|
* Make a request with automatic x402 payment handling, returning raw JSON.
|
|
657
897
|
* Used for non-ChatResponse endpoints (X/Twitter, search, image edit, etc.).
|
|
@@ -663,6 +903,27 @@ var LLMClient = class {
|
|
|
663
903
|
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
664
904
|
body: JSON.stringify(body)
|
|
665
905
|
});
|
|
906
|
+
if (response.status === 502 || response.status === 503) {
|
|
907
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
908
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
909
|
+
method: "POST",
|
|
910
|
+
headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
|
|
911
|
+
body: JSON.stringify(body)
|
|
912
|
+
});
|
|
913
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
914
|
+
if (retryResp.status === 402) return this.handlePaymentAndRetryRaw(url, body, retryResp);
|
|
915
|
+
if (!retryResp.ok) {
|
|
916
|
+
let errorBody;
|
|
917
|
+
try {
|
|
918
|
+
errorBody = await retryResp.json();
|
|
919
|
+
} catch {
|
|
920
|
+
errorBody = { error: "Request failed" };
|
|
921
|
+
}
|
|
922
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
923
|
+
}
|
|
924
|
+
return retryResp.json();
|
|
925
|
+
}
|
|
926
|
+
}
|
|
666
927
|
if (response.status === 402) {
|
|
667
928
|
return this.handlePaymentAndRetryRaw(url, body, response);
|
|
668
929
|
}
|
|
@@ -728,6 +989,34 @@ var LLMClient = class {
|
|
|
728
989
|
},
|
|
729
990
|
body: JSON.stringify(body)
|
|
730
991
|
});
|
|
992
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
993
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
994
|
+
const retryResp2 = await this.fetchWithTimeout(url, {
|
|
995
|
+
method: "POST",
|
|
996
|
+
headers: {
|
|
997
|
+
"Content-Type": "application/json",
|
|
998
|
+
"User-Agent": USER_AGENT,
|
|
999
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1000
|
+
},
|
|
1001
|
+
body: JSON.stringify(body)
|
|
1002
|
+
});
|
|
1003
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
1004
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
1005
|
+
if (!retryResp2.ok) {
|
|
1006
|
+
let errorBody;
|
|
1007
|
+
try {
|
|
1008
|
+
errorBody = await retryResp2.json();
|
|
1009
|
+
} catch {
|
|
1010
|
+
errorBody = { error: "Request failed" };
|
|
1011
|
+
}
|
|
1012
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
1013
|
+
}
|
|
1014
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1015
|
+
this.sessionCalls += 1;
|
|
1016
|
+
this.sessionTotalUsd += costUsd2;
|
|
1017
|
+
return retryResp2.json();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
731
1020
|
if (retryResponse.status === 402) {
|
|
732
1021
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
733
1022
|
}
|
|
@@ -760,6 +1049,26 @@ var LLMClient = class {
|
|
|
760
1049
|
method: "GET",
|
|
761
1050
|
headers: { "User-Agent": USER_AGENT }
|
|
762
1051
|
});
|
|
1052
|
+
if (response.status === 502 || response.status === 503) {
|
|
1053
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1054
|
+
const retryResp = await this.fetchWithTimeout(url, {
|
|
1055
|
+
method: "GET",
|
|
1056
|
+
headers: { "User-Agent": USER_AGENT }
|
|
1057
|
+
});
|
|
1058
|
+
if (retryResp.status !== 502 && retryResp.status !== 503) {
|
|
1059
|
+
if (retryResp.status === 402) return this.handleGetPaymentAndRetryRaw(url, endpoint, params, retryResp);
|
|
1060
|
+
if (!retryResp.ok) {
|
|
1061
|
+
let errorBody;
|
|
1062
|
+
try {
|
|
1063
|
+
errorBody = await retryResp.json();
|
|
1064
|
+
} catch {
|
|
1065
|
+
errorBody = { error: "Request failed" };
|
|
1066
|
+
}
|
|
1067
|
+
throw new APIError(`API error: ${retryResp.status}`, retryResp.status, sanitizeErrorResponse(errorBody));
|
|
1068
|
+
}
|
|
1069
|
+
return retryResp.json();
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
763
1072
|
if (response.status === 402) {
|
|
764
1073
|
return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
|
|
765
1074
|
}
|
|
@@ -825,6 +1134,32 @@ var LLMClient = class {
|
|
|
825
1134
|
"PAYMENT-SIGNATURE": paymentPayload
|
|
826
1135
|
}
|
|
827
1136
|
});
|
|
1137
|
+
if (retryResponse.status === 502 || retryResponse.status === 503) {
|
|
1138
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1139
|
+
const retryResp2 = await this.fetchWithTimeout(retryUrl, {
|
|
1140
|
+
method: "GET",
|
|
1141
|
+
headers: {
|
|
1142
|
+
"User-Agent": USER_AGENT,
|
|
1143
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
if (retryResp2.status !== 502 && retryResp2.status !== 503) {
|
|
1147
|
+
if (retryResp2.status === 402) throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
1148
|
+
if (!retryResp2.ok) {
|
|
1149
|
+
let errorBody;
|
|
1150
|
+
try {
|
|
1151
|
+
errorBody = await retryResp2.json();
|
|
1152
|
+
} catch {
|
|
1153
|
+
errorBody = { error: "Request failed" };
|
|
1154
|
+
}
|
|
1155
|
+
throw new APIError(`API error after payment: ${retryResp2.status}`, retryResp2.status, sanitizeErrorResponse(errorBody));
|
|
1156
|
+
}
|
|
1157
|
+
const costUsd2 = parseFloat(details.amount) / 1e6;
|
|
1158
|
+
this.sessionCalls += 1;
|
|
1159
|
+
this.sessionTotalUsd += costUsd2;
|
|
1160
|
+
return retryResp2.json();
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
828
1163
|
if (retryResponse.status === 402) {
|
|
829
1164
|
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
830
1165
|
}
|
|
@@ -886,12 +1221,12 @@ var LLMClient = class {
|
|
|
886
1221
|
return (data.data || []).map((m) => ({
|
|
887
1222
|
id: m.id,
|
|
888
1223
|
name: m.name || m.id,
|
|
889
|
-
provider: m.owned_by || "",
|
|
1224
|
+
provider: m.provider || m.owned_by || "",
|
|
890
1225
|
description: m.description || "",
|
|
891
|
-
inputPrice: m.
|
|
892
|
-
outputPrice: m.pricing?.output ?? 0,
|
|
893
|
-
contextWindow: m.context_window
|
|
894
|
-
maxOutput: m.max_output
|
|
1226
|
+
inputPrice: m.inputPrice ?? m.input_price ?? m.pricing?.input ?? 0,
|
|
1227
|
+
outputPrice: m.outputPrice ?? m.output_price ?? m.pricing?.output ?? 0,
|
|
1228
|
+
contextWindow: m.contextWindow ?? m.context_window ?? 0,
|
|
1229
|
+
maxOutput: m.maxOutput ?? m.max_output ?? 0,
|
|
895
1230
|
categories: m.categories || [],
|
|
896
1231
|
available: true
|
|
897
1232
|
}));
|
|
@@ -979,6 +1314,58 @@ var LLMClient = class {
|
|
|
979
1314
|
const data = await this.requestWithPaymentRaw("/v1/search", body);
|
|
980
1315
|
return data;
|
|
981
1316
|
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
|
|
1319
|
+
* Understands meaning, not just keywords. $0.01/call.
|
|
1320
|
+
*
|
|
1321
|
+
* @param query - Natural language search query
|
|
1322
|
+
* @param options - Optional filters (numResults, category, date range, domains)
|
|
1323
|
+
*/
|
|
1324
|
+
async exaSearch(query, options) {
|
|
1325
|
+
const body = { query };
|
|
1326
|
+
if (options?.numResults !== void 0) body.numResults = options.numResults;
|
|
1327
|
+
if (options?.category !== void 0) body.category = options.category;
|
|
1328
|
+
if (options?.startPublishedDate !== void 0) body.startPublishedDate = options.startPublishedDate;
|
|
1329
|
+
if (options?.endPublishedDate !== void 0) body.endPublishedDate = options.endPublishedDate;
|
|
1330
|
+
if (options?.includeDomains !== void 0) body.includeDomains = options.includeDomains;
|
|
1331
|
+
if (options?.excludeDomains !== void 0) body.excludeDomains = options.excludeDomains;
|
|
1332
|
+
const data = await this.requestWithPaymentRaw("/v1/exa/search", body);
|
|
1333
|
+
return data.data;
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Ask a question and get a cited, synthesized answer grounded in real web sources.
|
|
1337
|
+
* No hallucinations — every claim is backed by a citation. $0.01/call.
|
|
1338
|
+
*
|
|
1339
|
+
* @param query - The question to answer
|
|
1340
|
+
*/
|
|
1341
|
+
async exaAnswer(query) {
|
|
1342
|
+
const data = await this.requestWithPaymentRaw("/v1/exa/answer", { query });
|
|
1343
|
+
return data.data;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Fetch full Markdown text content from a list of URLs. $0.002 per URL.
|
|
1347
|
+
* Returns clean text ready to feed into an LLM context window.
|
|
1348
|
+
*
|
|
1349
|
+
* @param urls - Array of URLs to fetch (up to 100)
|
|
1350
|
+
*/
|
|
1351
|
+
async exaContents(urls) {
|
|
1352
|
+
const data = await this.requestWithPaymentRaw("/v1/exa/contents", { urls });
|
|
1353
|
+
return data.data;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Find pages semantically similar to a given URL. $0.01/call.
|
|
1357
|
+
* Useful for discovering competitors, alternatives, and related resources.
|
|
1358
|
+
*
|
|
1359
|
+
* @param url - Reference URL
|
|
1360
|
+
* @param options - Optional filters (numResults, excludeSourceDomain)
|
|
1361
|
+
*/
|
|
1362
|
+
async exaFindSimilar(url, options) {
|
|
1363
|
+
const body = { url };
|
|
1364
|
+
if (options?.numResults !== void 0) body.numResults = options.numResults;
|
|
1365
|
+
if (options?.excludeSourceDomain !== void 0) body.excludeSourceDomain = options.excludeSourceDomain;
|
|
1366
|
+
const data = await this.requestWithPaymentRaw("/v1/exa/find-similar", body);
|
|
1367
|
+
return data.data;
|
|
1368
|
+
}
|
|
982
1369
|
/**
|
|
983
1370
|
* Get USDC balance on Base network.
|
|
984
1371
|
*
|
|
@@ -1992,6 +2379,55 @@ var SolanaLLMClient = class {
|
|
|
1992
2379
|
async pmQuery(path5, query) {
|
|
1993
2380
|
return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
|
|
1994
2381
|
}
|
|
2382
|
+
// ── Exa Web Search (Powered by Exa) ──────────────────────────────────────
|
|
2383
|
+
/**
|
|
2384
|
+
* Generic Exa endpoint proxy (POST, Solana payment). Powered by Exa.
|
|
2385
|
+
*
|
|
2386
|
+
* @param path - Exa endpoint: "search" | "find-similar" | "contents" | "answer"
|
|
2387
|
+
* @param body - Request body (see Exa API docs)
|
|
2388
|
+
*
|
|
2389
|
+
* @example
|
|
2390
|
+
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
|
|
2391
|
+
*/
|
|
2392
|
+
async exa(path5, body) {
|
|
2393
|
+
return this.requestWithPaymentRaw(`/v1/exa/${path5}`, body);
|
|
2394
|
+
}
|
|
2395
|
+
/**
|
|
2396
|
+
* Neural and keyword web search via Exa (Solana payment, $0.01/request).
|
|
2397
|
+
*
|
|
2398
|
+
* @example
|
|
2399
|
+
* const results = await client.exaSearch("latest AI papers", { numResults: 5 });
|
|
2400
|
+
*/
|
|
2401
|
+
async exaSearch(query, options) {
|
|
2402
|
+
return this.requestWithPaymentRaw("/v1/exa/search", { query, ...options });
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Find pages semantically similar to a given URL via Exa (Solana payment, $0.01/request).
|
|
2406
|
+
*
|
|
2407
|
+
* @example
|
|
2408
|
+
* const results = await client.exaFindSimilar("https://openai.com/research/gpt-4", { numResults: 5 });
|
|
2409
|
+
*/
|
|
2410
|
+
async exaFindSimilar(url, options) {
|
|
2411
|
+
return this.requestWithPaymentRaw("/v1/exa/find-similar", { url, ...options });
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* Extract full text content from URLs via Exa (Solana payment, $0.002/URL).
|
|
2415
|
+
*
|
|
2416
|
+
* @example
|
|
2417
|
+
* const data = await client.exaContents(["https://arxiv.org/abs/2303.08774"]);
|
|
2418
|
+
*/
|
|
2419
|
+
async exaContents(urls, options) {
|
|
2420
|
+
return this.requestWithPaymentRaw("/v1/exa/contents", { urls, ...options });
|
|
2421
|
+
}
|
|
2422
|
+
/**
|
|
2423
|
+
* AI-generated answer grounded in live web search via Exa (Solana payment, $0.01/request).
|
|
2424
|
+
*
|
|
2425
|
+
* @example
|
|
2426
|
+
* const answer = await client.exaAnswer("What is the current state of AI safety research?");
|
|
2427
|
+
*/
|
|
2428
|
+
async exaAnswer(query, options) {
|
|
2429
|
+
return this.requestWithPaymentRaw("/v1/exa/answer", { query, ...options });
|
|
2430
|
+
}
|
|
1995
2431
|
/** Get session spending. */
|
|
1996
2432
|
getSpending() {
|
|
1997
2433
|
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
@@ -2293,6 +2729,8 @@ var path3 = __toESM(require("path"), 1);
|
|
|
2293
2729
|
var os3 = __toESM(require("os"), 1);
|
|
2294
2730
|
var crypto2 = __toESM(require("crypto"), 1);
|
|
2295
2731
|
var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
|
|
2732
|
+
var DATA_DIR = path3.join(os3.homedir(), ".blockrun", "data");
|
|
2733
|
+
var COST_LOG_FILE = path3.join(os3.homedir(), ".blockrun", "cost_log.jsonl");
|
|
2296
2734
|
var DEFAULT_TTL = {
|
|
2297
2735
|
"/v1/x/": 3600 * 1e3,
|
|
2298
2736
|
"/v1/partner/": 3600 * 1e3,
|
|
@@ -2357,26 +2795,80 @@ function setCache(key, data, ttlMs) {
|
|
|
2357
2795
|
} catch {
|
|
2358
2796
|
}
|
|
2359
2797
|
}
|
|
2360
|
-
function
|
|
2361
|
-
const
|
|
2362
|
-
|
|
2798
|
+
function readableFilename(endpoint, body) {
|
|
2799
|
+
const now = /* @__PURE__ */ new Date();
|
|
2800
|
+
const ts = now.toISOString().slice(0, 10) + "_" + String(now.getHours()).padStart(2, "0") + String(now.getMinutes()).padStart(2, "0") + String(now.getSeconds()).padStart(2, "0");
|
|
2801
|
+
let ep = endpoint.replace(/\/+$/, "").split("/").pop() || "";
|
|
2802
|
+
if (endpoint.includes("/v1/chat/")) {
|
|
2803
|
+
ep = "chat";
|
|
2804
|
+
} else if (endpoint.includes("/v1/x/")) {
|
|
2805
|
+
ep = "x_" + ep;
|
|
2806
|
+
} else if (endpoint.includes("/v1/search")) {
|
|
2807
|
+
ep = "search";
|
|
2808
|
+
} else if (endpoint.includes("/v1/image")) {
|
|
2809
|
+
ep = "image";
|
|
2810
|
+
}
|
|
2811
|
+
let label = body.query || body.username || body.handle || body.model || (typeof body.prompt === "string" ? body.prompt.slice(0, 40) : "") || "";
|
|
2812
|
+
label = String(label).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40).replace(/^_+|_+$/g, "");
|
|
2813
|
+
return label ? `${ep}_${ts}_${label}.json` : `${ep}_${ts}.json`;
|
|
2814
|
+
}
|
|
2815
|
+
function saveReadable(endpoint, body, response, costUsd) {
|
|
2363
2816
|
try {
|
|
2364
|
-
fs3.mkdirSync(
|
|
2817
|
+
fs3.mkdirSync(DATA_DIR, { recursive: true });
|
|
2365
2818
|
} catch {
|
|
2366
2819
|
}
|
|
2367
|
-
const
|
|
2820
|
+
const filename = readableFilename(endpoint, body);
|
|
2368
2821
|
const entry = {
|
|
2369
|
-
|
|
2822
|
+
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2370
2823
|
endpoint,
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2824
|
+
cost_usd: costUsd,
|
|
2825
|
+
request: body,
|
|
2826
|
+
response
|
|
2374
2827
|
};
|
|
2375
2828
|
try {
|
|
2376
|
-
fs3.writeFileSync(
|
|
2829
|
+
fs3.writeFileSync(path3.join(DATA_DIR, filename), JSON.stringify(entry, null, 2));
|
|
2377
2830
|
} catch {
|
|
2378
2831
|
}
|
|
2379
2832
|
}
|
|
2833
|
+
function appendCostLog(endpoint, costUsd) {
|
|
2834
|
+
if (costUsd <= 0) return;
|
|
2835
|
+
try {
|
|
2836
|
+
fs3.mkdirSync(path3.dirname(COST_LOG_FILE), { recursive: true });
|
|
2837
|
+
} catch {
|
|
2838
|
+
}
|
|
2839
|
+
const entry = {
|
|
2840
|
+
ts: Date.now() / 1e3,
|
|
2841
|
+
endpoint,
|
|
2842
|
+
cost_usd: costUsd
|
|
2843
|
+
};
|
|
2844
|
+
try {
|
|
2845
|
+
fs3.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
|
|
2846
|
+
} catch {
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function saveToCache(endpoint, body, response, costUsd = 0) {
|
|
2850
|
+
const ttl = getTtl(endpoint);
|
|
2851
|
+
if (ttl > 0) {
|
|
2852
|
+
try {
|
|
2853
|
+
fs3.mkdirSync(CACHE_DIR, { recursive: true });
|
|
2854
|
+
} catch {
|
|
2855
|
+
}
|
|
2856
|
+
const key = cacheKey(endpoint, body);
|
|
2857
|
+
const entry = {
|
|
2858
|
+
cachedAt: Date.now(),
|
|
2859
|
+
endpoint,
|
|
2860
|
+
body,
|
|
2861
|
+
response,
|
|
2862
|
+
costUsd
|
|
2863
|
+
};
|
|
2864
|
+
try {
|
|
2865
|
+
fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
|
|
2866
|
+
} catch {
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
saveReadable(endpoint, body, response, costUsd);
|
|
2870
|
+
appendCostLog(endpoint, costUsd);
|
|
2871
|
+
}
|
|
2380
2872
|
function clearCache() {
|
|
2381
2873
|
if (!fs3.existsSync(CACHE_DIR)) return 0;
|
|
2382
2874
|
let count = 0;
|
|
@@ -2395,6 +2887,32 @@ function clearCache() {
|
|
|
2395
2887
|
}
|
|
2396
2888
|
return count;
|
|
2397
2889
|
}
|
|
2890
|
+
function getCostLogSummary() {
|
|
2891
|
+
if (!fs3.existsSync(COST_LOG_FILE)) {
|
|
2892
|
+
return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2893
|
+
}
|
|
2894
|
+
let totalUsd = 0;
|
|
2895
|
+
let calls = 0;
|
|
2896
|
+
const byEndpoint = {};
|
|
2897
|
+
try {
|
|
2898
|
+
const content = fs3.readFileSync(COST_LOG_FILE, "utf-8").trim();
|
|
2899
|
+
if (!content) return { totalUsd: 0, calls: 0, byEndpoint: {} };
|
|
2900
|
+
for (const line of content.split("\n")) {
|
|
2901
|
+
if (!line) continue;
|
|
2902
|
+
try {
|
|
2903
|
+
const entry = JSON.parse(line);
|
|
2904
|
+
const cost = entry.cost_usd ?? 0;
|
|
2905
|
+
const ep = entry.endpoint ?? "unknown";
|
|
2906
|
+
totalUsd += cost;
|
|
2907
|
+
calls += 1;
|
|
2908
|
+
byEndpoint[ep] = (byEndpoint[ep] || 0) + cost;
|
|
2909
|
+
} catch {
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
return { totalUsd, calls, byEndpoint };
|
|
2915
|
+
}
|
|
2398
2916
|
|
|
2399
2917
|
// src/setup.ts
|
|
2400
2918
|
function setupAgentWallet(options) {
|
|
@@ -2436,27 +2954,27 @@ async function status() {
|
|
|
2436
2954
|
var fs4 = __toESM(require("fs"), 1);
|
|
2437
2955
|
var path4 = __toESM(require("path"), 1);
|
|
2438
2956
|
var os4 = __toESM(require("os"), 1);
|
|
2439
|
-
var
|
|
2440
|
-
var
|
|
2957
|
+
var DATA_DIR2 = path4.join(os4.homedir(), ".blockrun", "data");
|
|
2958
|
+
var COST_LOG_FILE2 = path4.join(DATA_DIR2, "costs.jsonl");
|
|
2441
2959
|
function logCost(entry) {
|
|
2442
2960
|
try {
|
|
2443
|
-
fs4.mkdirSync(
|
|
2961
|
+
fs4.mkdirSync(DATA_DIR2, { recursive: true });
|
|
2444
2962
|
} catch {
|
|
2445
2963
|
}
|
|
2446
2964
|
try {
|
|
2447
|
-
fs4.appendFileSync(
|
|
2965
|
+
fs4.appendFileSync(COST_LOG_FILE2, JSON.stringify(entry) + "\n");
|
|
2448
2966
|
} catch {
|
|
2449
2967
|
}
|
|
2450
2968
|
}
|
|
2451
2969
|
function getCostSummary() {
|
|
2452
|
-
if (!fs4.existsSync(
|
|
2970
|
+
if (!fs4.existsSync(COST_LOG_FILE2)) {
|
|
2453
2971
|
return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2454
2972
|
}
|
|
2455
2973
|
let totalUsd = 0;
|
|
2456
2974
|
let calls = 0;
|
|
2457
2975
|
const byModel = {};
|
|
2458
2976
|
try {
|
|
2459
|
-
const content = fs4.readFileSync(
|
|
2977
|
+
const content = fs4.readFileSync(COST_LOG_FILE2, "utf-8").trim();
|
|
2460
2978
|
if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
|
|
2461
2979
|
for (const line of content.split("\n")) {
|
|
2462
2980
|
if (!line) continue;
|
|
@@ -2555,46 +3073,18 @@ var ChatCompletions = class {
|
|
|
2555
3073
|
return this.transformResponse(response);
|
|
2556
3074
|
}
|
|
2557
3075
|
async createStream(params) {
|
|
2558
|
-
const
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
if (params.tools) {
|
|
2568
|
-
body.tools = params.tools;
|
|
2569
|
-
}
|
|
2570
|
-
if (params.tool_choice) {
|
|
2571
|
-
body.tool_choice = params.tool_choice;
|
|
2572
|
-
}
|
|
2573
|
-
const controller = new AbortController();
|
|
2574
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2575
|
-
try {
|
|
2576
|
-
const response = await fetch(url, {
|
|
2577
|
-
method: "POST",
|
|
2578
|
-
headers: { "Content-Type": "application/json" },
|
|
2579
|
-
body: JSON.stringify(body),
|
|
2580
|
-
signal: controller.signal
|
|
2581
|
-
});
|
|
2582
|
-
if (response.status === 402) {
|
|
2583
|
-
const paymentHeader = response.headers.get("payment-required");
|
|
2584
|
-
if (!paymentHeader) {
|
|
2585
|
-
throw new Error("402 response but no payment requirements found");
|
|
2586
|
-
}
|
|
2587
|
-
throw new Error(
|
|
2588
|
-
"Streaming with automatic payment requires direct wallet access. Please use non-streaming mode or contact support for streaming setup."
|
|
2589
|
-
);
|
|
2590
|
-
}
|
|
2591
|
-
if (!response.ok) {
|
|
2592
|
-
throw new Error(`API error: ${response.status}`);
|
|
3076
|
+
const response = await this.client.chatCompletionStream(
|
|
3077
|
+
params.model,
|
|
3078
|
+
params.messages,
|
|
3079
|
+
{
|
|
3080
|
+
maxTokens: params.max_tokens,
|
|
3081
|
+
temperature: params.temperature,
|
|
3082
|
+
topP: params.top_p,
|
|
3083
|
+
tools: params.tools,
|
|
3084
|
+
toolChoice: params.tool_choice
|
|
2593
3085
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
clearTimeout(timeoutId);
|
|
2597
|
-
}
|
|
3086
|
+
);
|
|
3087
|
+
return new StreamingResponse(response, params.model);
|
|
2598
3088
|
}
|
|
2599
3089
|
transformResponse(response) {
|
|
2600
3090
|
return {
|
|
@@ -2758,6 +3248,7 @@ var AnthropicClient = class {
|
|
|
2758
3248
|
BASE_CHAIN_ID,
|
|
2759
3249
|
BlockrunError,
|
|
2760
3250
|
ImageClient,
|
|
3251
|
+
KNOWN_PROVIDERS,
|
|
2761
3252
|
LLMClient,
|
|
2762
3253
|
OpenAI,
|
|
2763
3254
|
PaymentError,
|
|
@@ -2780,6 +3271,7 @@ var AnthropicClient = class {
|
|
|
2780
3271
|
formatWalletCreatedMessage,
|
|
2781
3272
|
getCached,
|
|
2782
3273
|
getCachedByRequest,
|
|
3274
|
+
getCostLogSummary,
|
|
2783
3275
|
getCostSummary,
|
|
2784
3276
|
getEip681Uri,
|
|
2785
3277
|
getOrCreateSolanaWallet,
|
|
@@ -2802,5 +3294,9 @@ var AnthropicClient = class {
|
|
|
2802
3294
|
solanaKeyToBytes,
|
|
2803
3295
|
solanaPublicKey,
|
|
2804
3296
|
status,
|
|
2805
|
-
testnetClient
|
|
3297
|
+
testnetClient,
|
|
3298
|
+
validateMaxTokens,
|
|
3299
|
+
validateModel,
|
|
3300
|
+
validateTemperature,
|
|
3301
|
+
validateTopP
|
|
2806
3302
|
});
|