@blockrun/clawrouter 0.12.270 → 0.12.272
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 +43 -7
- package/dist/cli.js +700 -127
- package/dist/index.d.ts +17 -0
- package/dist/index.js +385 -74
- package/docs/configuration.md +17 -1
- package/package.json +1 -1
- package/skills/surf/SKILL.md +32 -5
package/dist/index.js
CHANGED
|
@@ -40581,6 +40581,18 @@ function isBalanceError(error) {
|
|
|
40581
40581
|
function isRpcError(error) {
|
|
40582
40582
|
return error instanceof Error && error.code === "RPC_ERROR";
|
|
40583
40583
|
}
|
|
40584
|
+
function describeFetchError(error) {
|
|
40585
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40586
|
+
const cause = error?.cause;
|
|
40587
|
+
if (!(cause instanceof Error)) return message;
|
|
40588
|
+
const detail = cause;
|
|
40589
|
+
const host = detail.hostname || detail.address;
|
|
40590
|
+
const where = host && detail.port ? `${host}:${detail.port}` : host;
|
|
40591
|
+
const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
|
|
40592
|
+
(part) => typeof part === "string" && part !== ""
|
|
40593
|
+
);
|
|
40594
|
+
return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
|
|
40595
|
+
}
|
|
40584
40596
|
var InsufficientFundsError2, EmptyWalletError, RpcError2;
|
|
40585
40597
|
var init_errors5 = __esm({
|
|
40586
40598
|
"src/errors.ts"() {
|
|
@@ -55852,7 +55864,7 @@ var require_websocket = __commonJS({
|
|
|
55852
55864
|
var net3 = __require("net");
|
|
55853
55865
|
var tls2 = __require("tls");
|
|
55854
55866
|
var { randomBytes: randomBytes9, createHash: createHash4 } = __require("crypto");
|
|
55855
|
-
var { Duplex, Readable:
|
|
55867
|
+
var { Duplex, Readable: Readable3 } = __require("stream");
|
|
55856
55868
|
var { URL: URL3 } = __require("url");
|
|
55857
55869
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
55858
55870
|
var Receiver2 = require_receiver();
|
|
@@ -59914,16 +59926,20 @@ function maskApiKey(key2) {
|
|
|
59914
59926
|
async function readOptional2(path5) {
|
|
59915
59927
|
try {
|
|
59916
59928
|
return (await readTextFile(path5)).trim() || void 0;
|
|
59917
|
-
} catch {
|
|
59918
|
-
return void 0;
|
|
59929
|
+
} catch (error) {
|
|
59930
|
+
if (error.code === "ENOENT") return void 0;
|
|
59931
|
+
throw new Error(
|
|
59932
|
+
`Cannot read BlockRun API key file ${path5}; refusing to fall back to another account or wallet.`,
|
|
59933
|
+
{ cause: error }
|
|
59934
|
+
);
|
|
59919
59935
|
}
|
|
59920
59936
|
}
|
|
59921
59937
|
async function resolveApiKey() {
|
|
59922
59938
|
const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
|
|
59923
59939
|
if (envKey) {
|
|
59924
59940
|
if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
|
|
59925
|
-
|
|
59926
|
-
`
|
|
59941
|
+
throw new Error(
|
|
59942
|
+
`BLOCKRUN_API_KEY is malformed (expected brk_\u2026). Mint one at ${PORTAL_KEYS_URL}, or unset it to pay from the wallet. Refusing to fall back silently.`
|
|
59927
59943
|
);
|
|
59928
59944
|
}
|
|
59929
59945
|
for (const [path5, source] of [
|
|
@@ -59933,22 +59949,101 @@ async function resolveApiKey() {
|
|
|
59933
59949
|
const stored = await readOptional2(path5);
|
|
59934
59950
|
if (stored === void 0) continue;
|
|
59935
59951
|
if (isValidApiKey(stored)) return { key: stored, source };
|
|
59936
|
-
|
|
59937
|
-
|
|
59952
|
+
throw new Error(
|
|
59953
|
+
`${path5} does not contain a BlockRun key (expected brk_\u2026). Run "clawrouter login" with a valid key, or "clawrouter logout" to return to wallet billing. Refusing to fall back silently: the next source could bill a different account or spend from a wallet.`
|
|
59938
59954
|
);
|
|
59939
59955
|
}
|
|
59940
59956
|
return void 0;
|
|
59941
59957
|
}
|
|
59942
|
-
function
|
|
59958
|
+
async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
|
|
59959
|
+
try {
|
|
59960
|
+
const controller = new AbortController();
|
|
59961
|
+
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
59962
|
+
const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
|
|
59963
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
59964
|
+
signal: controller.signal
|
|
59965
|
+
});
|
|
59966
|
+
clearTimeout(timer2);
|
|
59967
|
+
if (!res.ok) return void 0;
|
|
59968
|
+
const b = await res.json();
|
|
59969
|
+
const num2 = (v) => typeof v === "number" ? v : null;
|
|
59970
|
+
return {
|
|
59971
|
+
accountId: typeof b.account_id === "string" ? b.account_id : void 0,
|
|
59972
|
+
billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
|
|
59973
|
+
currency: typeof b.currency === "string" ? b.currency : "USD",
|
|
59974
|
+
grantedUsd: num2(b.granted_usd),
|
|
59975
|
+
spentUsd: num2(b.spent_usd),
|
|
59976
|
+
remainingUsd: num2(b.remaining_usd),
|
|
59977
|
+
blocked: b.blocked === true,
|
|
59978
|
+
blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
|
|
59979
|
+
};
|
|
59980
|
+
} catch {
|
|
59981
|
+
return void 0;
|
|
59982
|
+
}
|
|
59983
|
+
}
|
|
59984
|
+
function formatCreditBalance(b) {
|
|
59985
|
+
const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
|
|
59986
|
+
const remaining = money(b.remainingUsd);
|
|
59987
|
+
const spent = money(b.spentUsd);
|
|
59988
|
+
if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
|
|
59989
|
+
const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
|
|
59990
|
+
return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
|
|
59991
|
+
}
|
|
59992
|
+
function normalizeApiKeyBase(raw) {
|
|
59993
|
+
const base4 = raw.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
59994
|
+
const url2 = new URL(base4);
|
|
59995
|
+
if (url2.username || url2.password || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) {
|
|
59996
|
+
throw new Error(
|
|
59997
|
+
"BlockRun account API URL requires HTTPS (except localhost) and no credentials, query or fragment."
|
|
59998
|
+
);
|
|
59999
|
+
}
|
|
60000
|
+
return base4;
|
|
60001
|
+
}
|
|
60002
|
+
function createApiKeyFetch(apiKey, baseFetch = fetch, apiBase = BLOCKRUN_API_KEY_API) {
|
|
60003
|
+
const base4 = new URL(normalizeApiKeyBase(apiBase));
|
|
59943
60004
|
return async (input, init2) => {
|
|
59944
|
-
const
|
|
60005
|
+
const request2 = input instanceof Request ? input : void 0;
|
|
60006
|
+
const url2 = new URL(request2?.url ?? String(input), `${base4}/`);
|
|
60007
|
+
if (url2.origin !== base4.origin || url2.username || url2.password) {
|
|
60008
|
+
throw new Error("Refusing to forward a BlockRun account key to another origin.");
|
|
60009
|
+
}
|
|
60010
|
+
if (base4.pathname === "/" && url2.pathname.startsWith("/api/v1/")) {
|
|
60011
|
+
url2.pathname = url2.pathname.slice(4);
|
|
60012
|
+
}
|
|
60013
|
+
const headers = new Headers(init2?.headers ?? request2?.headers);
|
|
60014
|
+
for (const name of [...headers.keys()]) {
|
|
60015
|
+
if (/payment/i.test(name) || name.toLowerCase() === "x-api-key") headers.delete(name);
|
|
60016
|
+
}
|
|
59945
60017
|
headers.set("authorization", `Bearer ${apiKey}`);
|
|
59946
|
-
|
|
59947
|
-
|
|
59948
|
-
|
|
60018
|
+
const response = await baseFetch(request2 ? new Request(url2, request2) : url2.href, {
|
|
60019
|
+
...init2,
|
|
60020
|
+
headers,
|
|
60021
|
+
redirect: "error"
|
|
60022
|
+
});
|
|
59949
60023
|
return response.ok ? response : explainApiKeyFailure(response);
|
|
59950
60024
|
};
|
|
59951
60025
|
}
|
|
60026
|
+
async function pollApiKeyJob(response, payFetch, apiBase, signal, intervalMs = 2e3) {
|
|
60027
|
+
if (response.status !== 202) return response;
|
|
60028
|
+
const initial = await response.clone().json();
|
|
60029
|
+
if (!initial.poll_url) throw new Error("Async account response missing poll_url");
|
|
60030
|
+
const pollUrl = new URL(initial.poll_url, `${normalizeApiKeyBase(apiBase)}/`).href;
|
|
60031
|
+
const abort = AbortSignal.any([signal, AbortSignal.timeout(15 * 6e4)]);
|
|
60032
|
+
while (!abort.aborted) {
|
|
60033
|
+
const polled = await payFetch(pollUrl, { signal: abort });
|
|
60034
|
+
if ([502, 503, 504, 522, 524].includes(polled.status)) {
|
|
60035
|
+
await polled.body?.cancel();
|
|
60036
|
+
} else {
|
|
60037
|
+
if (!polled.ok) return polled;
|
|
60038
|
+
const data = await polled.clone().json();
|
|
60039
|
+
if (data.status === "completed") return polled;
|
|
60040
|
+
if (data.status === "failed") return polled;
|
|
60041
|
+
await polled.body?.cancel();
|
|
60042
|
+
}
|
|
60043
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
60044
|
+
}
|
|
60045
|
+
throw new Error("Async account job did not complete before the client aborted or timed out.");
|
|
60046
|
+
}
|
|
59952
60047
|
async function explainApiKeyFailure(response) {
|
|
59953
60048
|
const hint = HINTS[response.status];
|
|
59954
60049
|
if (!hint) return response;
|
|
@@ -59982,7 +60077,7 @@ var init_api_key = __esm({
|
|
|
59982
60077
|
PORTAL_URL = "https://user.blockrun.ai";
|
|
59983
60078
|
PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
|
|
59984
60079
|
PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
|
|
59985
|
-
BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
|
|
60080
|
+
BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "").replace(/\/v1$/, "") || "https://api.blockrun.ai";
|
|
59986
60081
|
HINTS = {
|
|
59987
60082
|
401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
|
|
59988
60083
|
402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
|
|
@@ -69128,7 +69223,7 @@ var require_client_h2 = __commonJS({
|
|
|
69128
69223
|
"node_modules/undici/lib/dispatcher/client-h2.js"(exports, module) {
|
|
69129
69224
|
"use strict";
|
|
69130
69225
|
var assert10 = __require("assert");
|
|
69131
|
-
var { pipeline } = __require("stream");
|
|
69226
|
+
var { pipeline: pipeline2 } = __require("stream");
|
|
69132
69227
|
var util5 = require_util();
|
|
69133
69228
|
var {
|
|
69134
69229
|
RequestContentLengthMismatchError,
|
|
@@ -70275,7 +70370,7 @@ var require_client_h2 = __commonJS({
|
|
|
70275
70370
|
}
|
|
70276
70371
|
function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) {
|
|
70277
70372
|
assert10(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined");
|
|
70278
|
-
const pipe2 =
|
|
70373
|
+
const pipe2 = pipeline2(
|
|
70279
70374
|
body,
|
|
70280
70375
|
h2stream,
|
|
70281
70376
|
(err) => {
|
|
@@ -73430,7 +73525,7 @@ var require_readable = __commonJS({
|
|
|
73430
73525
|
"use strict";
|
|
73431
73526
|
var assert10 = __require("assert");
|
|
73432
73527
|
var { addAbortListener } = __require("events");
|
|
73433
|
-
var { Readable:
|
|
73528
|
+
var { Readable: Readable3 } = __require("stream");
|
|
73434
73529
|
var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors();
|
|
73435
73530
|
var util5 = require_util();
|
|
73436
73531
|
var { ReadableStreamFrom } = require_util();
|
|
@@ -73444,7 +73539,7 @@ var require_readable = __commonJS({
|
|
|
73444
73539
|
var kBytesRead = /* @__PURE__ */ Symbol("kBytesRead");
|
|
73445
73540
|
var noop2 = () => {
|
|
73446
73541
|
};
|
|
73447
|
-
var BodyReadable = class extends
|
|
73542
|
+
var BodyReadable = class extends Readable3 {
|
|
73448
73543
|
/**
|
|
73449
73544
|
* @param {object} opts
|
|
73450
73545
|
* @param {(this: Readable, size: number) => void} opts.resume
|
|
@@ -73843,7 +73938,7 @@ var require_api_request = __commonJS({
|
|
|
73843
73938
|
"use strict";
|
|
73844
73939
|
var assert10 = __require("assert");
|
|
73845
73940
|
var { AsyncResource } = __require("async_hooks");
|
|
73846
|
-
var { Readable:
|
|
73941
|
+
var { Readable: Readable3 } = require_readable();
|
|
73847
73942
|
var { InvalidArgumentError, RequestAbortedError } = require_errors();
|
|
73848
73943
|
var util5 = require_util();
|
|
73849
73944
|
function noop2() {
|
|
@@ -73929,7 +74024,7 @@ var require_api_request = __commonJS({
|
|
|
73929
74024
|
const parsedHeaders = headers;
|
|
73930
74025
|
const contentType = parsedHeaders?.["content-type"];
|
|
73931
74026
|
const contentLength = parsedHeaders?.["content-length"];
|
|
73932
|
-
const res = new
|
|
74027
|
+
const res = new Readable3({
|
|
73933
74028
|
resume: () => controller.resume(),
|
|
73934
74029
|
abort: (reason) => controller.abort(reason),
|
|
73935
74030
|
contentType,
|
|
@@ -74305,7 +74400,7 @@ var require_api_pipeline = __commonJS({
|
|
|
74305
74400
|
"node_modules/undici/lib/api/api-pipeline.js"(exports, module) {
|
|
74306
74401
|
"use strict";
|
|
74307
74402
|
var {
|
|
74308
|
-
Readable:
|
|
74403
|
+
Readable: Readable3,
|
|
74309
74404
|
Duplex,
|
|
74310
74405
|
PassThrough
|
|
74311
74406
|
} = __require("stream");
|
|
@@ -74322,7 +74417,7 @@ var require_api_pipeline = __commonJS({
|
|
|
74322
74417
|
function noop2() {
|
|
74323
74418
|
}
|
|
74324
74419
|
var kResume = /* @__PURE__ */ Symbol("resume");
|
|
74325
|
-
var PipelineRequest = class extends
|
|
74420
|
+
var PipelineRequest = class extends Readable3 {
|
|
74326
74421
|
constructor() {
|
|
74327
74422
|
super({ autoDestroy: true });
|
|
74328
74423
|
this[kResume] = null;
|
|
@@ -74340,7 +74435,7 @@ var require_api_pipeline = __commonJS({
|
|
|
74340
74435
|
callback(err);
|
|
74341
74436
|
}
|
|
74342
74437
|
};
|
|
74343
|
-
var PipelineResponse = class extends
|
|
74438
|
+
var PipelineResponse = class extends Readable3 {
|
|
74344
74439
|
constructor(resume2) {
|
|
74345
74440
|
super({ autoDestroy: true });
|
|
74346
74441
|
this[kResume] = resume2;
|
|
@@ -74494,7 +74589,7 @@ var require_api_pipeline = __commonJS({
|
|
|
74494
74589
|
util5.destroy(ret, err);
|
|
74495
74590
|
}
|
|
74496
74591
|
};
|
|
74497
|
-
function
|
|
74592
|
+
function pipeline2(opts, handler) {
|
|
74498
74593
|
try {
|
|
74499
74594
|
const pipelineHandler = new PipelineHandler(opts, handler);
|
|
74500
74595
|
this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
|
|
@@ -74503,7 +74598,7 @@ var require_api_pipeline = __commonJS({
|
|
|
74503
74598
|
return new PassThrough().destroy(err);
|
|
74504
74599
|
}
|
|
74505
74600
|
}
|
|
74506
|
-
module.exports =
|
|
74601
|
+
module.exports = pipeline2;
|
|
74507
74602
|
}
|
|
74508
74603
|
});
|
|
74509
74604
|
|
|
@@ -79621,7 +79716,7 @@ var require_cache2 = __commonJS({
|
|
|
79621
79716
|
"node_modules/undici/lib/interceptor/cache.js"(exports, module) {
|
|
79622
79717
|
"use strict";
|
|
79623
79718
|
var assert10 = __require("assert");
|
|
79624
|
-
var { Readable:
|
|
79719
|
+
var { Readable: Readable3 } = __require("stream");
|
|
79625
79720
|
var util5 = require_util();
|
|
79626
79721
|
var CacheHandler = require_cache_handler();
|
|
79627
79722
|
var MemoryCacheStore = require_memory_cache_store();
|
|
@@ -79797,7 +79892,7 @@ var require_cache2 = __commonJS({
|
|
|
79797
79892
|
return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler));
|
|
79798
79893
|
}
|
|
79799
79894
|
function sendCachedValue(handler, opts, result, age, context, isStale2) {
|
|
79800
|
-
const stream4 = util5.isStream(result.body) ? result.body :
|
|
79895
|
+
const stream4 = util5.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
|
|
79801
79896
|
assert10(!stream4.destroyed, "stream should not be destroyed");
|
|
79802
79897
|
assert10(!stream4.readableDidRead, "stream should not be readableDidRead");
|
|
79803
79898
|
const controller = {
|
|
@@ -80042,7 +80137,7 @@ var require_decompress = __commonJS({
|
|
|
80042
80137
|
"node_modules/undici/lib/interceptor/decompress.js"(exports, module) {
|
|
80043
80138
|
"use strict";
|
|
80044
80139
|
var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("zlib");
|
|
80045
|
-
var { pipeline } = __require("stream");
|
|
80140
|
+
var { pipeline: pipeline2 } = __require("stream");
|
|
80046
80141
|
var DecoratorHandler = require_decorator_handler();
|
|
80047
80142
|
var supportedEncodings = {
|
|
80048
80143
|
gzip: createGunzip,
|
|
@@ -80152,7 +80247,7 @@ var require_decompress = __commonJS({
|
|
|
80152
80247
|
#setupMultipleDecompressors(controller) {
|
|
80153
80248
|
const lastDecompressor = this.#decompressors[this.#decompressors.length - 1];
|
|
80154
80249
|
this.#setupDecompressorEvents(lastDecompressor, controller);
|
|
80155
|
-
|
|
80250
|
+
pipeline2(this.#decompressors, (err) => {
|
|
80156
80251
|
if (err) {
|
|
80157
80252
|
super.onResponseError(controller, err);
|
|
80158
80253
|
return;
|
|
@@ -82946,7 +83041,7 @@ var require_fetch = __commonJS({
|
|
|
82946
83041
|
subresourceSet
|
|
82947
83042
|
} = require_constants4();
|
|
82948
83043
|
var EE = __require("events");
|
|
82949
|
-
var { Readable:
|
|
83044
|
+
var { Readable: Readable3, pipeline: pipeline2, finished: finished2, isErrored, isReadable } = __require("stream");
|
|
82950
83045
|
var { addAbortListener, bufferToLowerCasedHeaderName } = require_util();
|
|
82951
83046
|
var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
|
|
82952
83047
|
var { getGlobalDispatcher } = require_global2();
|
|
@@ -83922,7 +84017,7 @@ var require_fetch = __commonJS({
|
|
|
83922
84017
|
const headersList = new HeadersList();
|
|
83923
84018
|
appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders);
|
|
83924
84019
|
const location = headersList.get("location", true);
|
|
83925
|
-
this.body = new
|
|
84020
|
+
this.body = new Readable3({ read: () => controller.resume() });
|
|
83926
84021
|
const willFollow = location && request2.redirect === "follow" && redirectStatusSet.has(status);
|
|
83927
84022
|
const decoders = [];
|
|
83928
84023
|
if (request2.method !== "HEAD" && request2.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
|
|
@@ -83970,7 +84065,7 @@ var require_fetch = __commonJS({
|
|
|
83970
84065
|
status,
|
|
83971
84066
|
statusText,
|
|
83972
84067
|
headersList,
|
|
83973
|
-
body: decoders.length ?
|
|
84068
|
+
body: decoders.length ? pipeline2(this.body, ...decoders, (err) => {
|
|
83974
84069
|
if (err) {
|
|
83975
84070
|
this.onResponseError(controller, err);
|
|
83976
84071
|
}
|
|
@@ -87782,7 +87877,7 @@ ${value}`;
|
|
|
87782
87877
|
var require_eventsource = __commonJS({
|
|
87783
87878
|
"node_modules/undici/lib/web/eventsource/eventsource.js"(exports, module) {
|
|
87784
87879
|
"use strict";
|
|
87785
|
-
var { pipeline } = __require("stream");
|
|
87880
|
+
var { pipeline: pipeline2 } = __require("stream");
|
|
87786
87881
|
var { fetching } = require_fetch();
|
|
87787
87882
|
var { webidl } = require_webidl();
|
|
87788
87883
|
var { EventSourceStream } = require_eventsource_stream();
|
|
@@ -87932,7 +88027,7 @@ var require_eventsource = __commonJS({
|
|
|
87932
88027
|
));
|
|
87933
88028
|
}
|
|
87934
88029
|
});
|
|
87935
|
-
|
|
88030
|
+
pipeline2(
|
|
87936
88031
|
response.body.stream,
|
|
87937
88032
|
eventSourceStream,
|
|
87938
88033
|
(error) => {
|
|
@@ -90460,11 +90555,30 @@ var init_client3 = __esm({
|
|
|
90460
90555
|
}
|
|
90461
90556
|
});
|
|
90462
90557
|
|
|
90558
|
+
// node_modules/@x402/svm/dist/esm/v1/index.mjs
|
|
90559
|
+
var v1_exports = {};
|
|
90560
|
+
__export(v1_exports, {
|
|
90561
|
+
ExactSvmSchemeV1: () => ExactSvmSchemeV1,
|
|
90562
|
+
NETWORKS: () => NETWORKS2
|
|
90563
|
+
});
|
|
90564
|
+
var init_v1 = __esm({
|
|
90565
|
+
"node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
|
|
90566
|
+
"use strict";
|
|
90567
|
+
init_chunk_WWACQNRQ();
|
|
90568
|
+
init_chunk_MNNIECS4();
|
|
90569
|
+
init_chunk_WIPN332D();
|
|
90570
|
+
init_chunk_FVTMOTG6();
|
|
90571
|
+
init_chunk_FJ6REVHH();
|
|
90572
|
+
init_chunk_QEBTC3LJ();
|
|
90573
|
+
}
|
|
90574
|
+
});
|
|
90575
|
+
|
|
90463
90576
|
// src/proxy.ts
|
|
90464
90577
|
import { AsyncLocalStorage } from "async_hooks";
|
|
90465
90578
|
import { createHmac } from "crypto";
|
|
90466
90579
|
import { createServer } from "http";
|
|
90467
|
-
import { finished } from "stream";
|
|
90580
|
+
import { finished, Readable } from "stream";
|
|
90581
|
+
import { pipeline } from "stream/promises";
|
|
90468
90582
|
import { homedir as homedir8 } from "os";
|
|
90469
90583
|
import { join as join11 } from "path";
|
|
90470
90584
|
import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
|
|
@@ -90473,10 +90587,10 @@ async function loadGatewayCatalog(apiBase, apiKey) {
|
|
|
90473
90587
|
try {
|
|
90474
90588
|
const controller = new AbortController();
|
|
90475
90589
|
const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
|
|
90476
|
-
const res = await fetch
|
|
90477
|
-
|
|
90478
|
-
|
|
90479
|
-
|
|
90590
|
+
const res = await (apiKey ? createApiKeyFetch(apiKey, fetch, apiBase) : fetch)(
|
|
90591
|
+
`${apiBase}/v1/models`,
|
|
90592
|
+
{ signal: controller.signal }
|
|
90593
|
+
);
|
|
90480
90594
|
clearTimeout(timer2);
|
|
90481
90595
|
if (!res.ok) return;
|
|
90482
90596
|
const body = await res.json();
|
|
@@ -91214,7 +91328,57 @@ function estimateImageCost(model, size5, n = 1) {
|
|
|
91214
91328
|
const pricePerImage = sizePrice ?? pricing.default;
|
|
91215
91329
|
return pricePerImage * n * 1.05;
|
|
91216
91330
|
}
|
|
91217
|
-
|
|
91331
|
+
function blockrunRequestId(response) {
|
|
91332
|
+
if (!response) return void 0;
|
|
91333
|
+
for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
|
|
91334
|
+
const value = response.headers.get(name)?.trim();
|
|
91335
|
+
if (value) return value;
|
|
91336
|
+
}
|
|
91337
|
+
return void 0;
|
|
91338
|
+
}
|
|
91339
|
+
function gatewayRemainingCreditUsd(response) {
|
|
91340
|
+
const raw = response?.headers.get("x-blockrun-credit-remaining-usd");
|
|
91341
|
+
if (raw === null || raw === void 0) return void 0;
|
|
91342
|
+
const trimmed = raw.trim();
|
|
91343
|
+
if (trimmed === "") return void 0;
|
|
91344
|
+
const value = Number(trimmed);
|
|
91345
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
91346
|
+
return value;
|
|
91347
|
+
}
|
|
91348
|
+
function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
|
|
91349
|
+
const header = gatewaySettledCostUsd(upstream);
|
|
91350
|
+
if (header !== void 0) return header;
|
|
91351
|
+
const price = body?.price?.amount;
|
|
91352
|
+
if (typeof price === "string" || typeof price === "number") {
|
|
91353
|
+
const parsed = Number(price);
|
|
91354
|
+
if (Number.isFinite(parsed) && parsed >= 0) return parsed;
|
|
91355
|
+
}
|
|
91356
|
+
if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
|
|
91357
|
+
return estimate;
|
|
91358
|
+
}
|
|
91359
|
+
function noteRemainingCredit(response) {
|
|
91360
|
+
const remaining = gatewayRemainingCreditUsd(response);
|
|
91361
|
+
if (remaining === void 0) return;
|
|
91362
|
+
if (remaining > LOW_CREDIT_USD) {
|
|
91363
|
+
lowCreditWarned = false;
|
|
91364
|
+
return;
|
|
91365
|
+
}
|
|
91366
|
+
if (lowCreditWarned) return;
|
|
91367
|
+
lowCreditWarned = true;
|
|
91368
|
+
console.warn(
|
|
91369
|
+
remaining <= 0 ? `[ClawRouter] \u26A0 BlockRun account credit is exhausted \u2014 calls will fail with 402. Top up: ${PORTAL_CREDITS_URL}` : `[ClawRouter] \u26A0 BlockRun account credit low: $${remaining.toFixed(2)} remaining. Top up: ${PORTAL_CREDITS_URL}`
|
|
91370
|
+
);
|
|
91371
|
+
}
|
|
91372
|
+
function gatewaySettledCostUsd(response) {
|
|
91373
|
+
const raw = response?.headers.get("x-blockrun-cost-usd");
|
|
91374
|
+
if (raw === null || raw === void 0) return void 0;
|
|
91375
|
+
const trimmed = raw.trim();
|
|
91376
|
+
if (trimmed === "") return void 0;
|
|
91377
|
+
const value = Number(trimmed);
|
|
91378
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
91379
|
+
return value;
|
|
91380
|
+
}
|
|
91381
|
+
async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd, accountPassthrough = false, authMode = "wallet") {
|
|
91218
91382
|
const startTime = Date.now();
|
|
91219
91383
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
91220
91384
|
const isBlockrunExa = req.url?.startsWith("/v1/exa/") ?? false;
|
|
@@ -91253,18 +91417,35 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91253
91417
|
return;
|
|
91254
91418
|
responseHeaders[key2] = value;
|
|
91255
91419
|
});
|
|
91420
|
+
if (accountPassthrough || authMode === "api-key") {
|
|
91421
|
+
responseHeaders["cache-control"] = "no-store";
|
|
91422
|
+
}
|
|
91256
91423
|
res.writeHead(upstream.status, responseHeaders);
|
|
91257
|
-
if (
|
|
91258
|
-
|
|
91259
|
-
|
|
91260
|
-
|
|
91424
|
+
if (accountPassthrough) {
|
|
91425
|
+
if (upstream.body) {
|
|
91426
|
+
await pipeline(
|
|
91427
|
+
Readable.fromWeb(upstream.body),
|
|
91428
|
+
res,
|
|
91429
|
+
{ signal: clientAbort.signal }
|
|
91430
|
+
);
|
|
91431
|
+
} else {
|
|
91432
|
+
res.end();
|
|
91433
|
+
}
|
|
91434
|
+
} else {
|
|
91435
|
+
if (upstream.body) {
|
|
91436
|
+
const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
|
|
91437
|
+
for (const chunk of chunks) {
|
|
91438
|
+
safeWrite(res, Buffer.from(chunk));
|
|
91439
|
+
}
|
|
91261
91440
|
}
|
|
91441
|
+
res.end();
|
|
91262
91442
|
}
|
|
91263
|
-
res.end();
|
|
91264
91443
|
const latencyMs = Date.now() - startTime;
|
|
91265
91444
|
console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
|
|
91266
|
-
|
|
91267
|
-
const
|
|
91445
|
+
noteRemainingCredit(upstream);
|
|
91446
|
+
const settledCostUsd = gatewaySettledCostUsd(upstream);
|
|
91447
|
+
const paidAmount = settledCostUsd ?? getActualPaymentUsd();
|
|
91448
|
+
const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
|
|
91268
91449
|
paidAmount,
|
|
91269
91450
|
isPhone,
|
|
91270
91451
|
upstreamStatus: upstream.status,
|
|
@@ -91273,6 +91454,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91273
91454
|
});
|
|
91274
91455
|
logUsage({
|
|
91275
91456
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91457
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91276
91458
|
model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
|
|
91277
91459
|
tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
|
|
91278
91460
|
cost: requestCost,
|
|
@@ -91364,7 +91546,8 @@ async function startProxy(options) {
|
|
|
91364
91546
|
const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
|
|
91365
91547
|
const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
|
|
91366
91548
|
const paymentChain = options.paymentChain ?? await resolvePaymentChain();
|
|
91367
|
-
const
|
|
91549
|
+
const requestedApiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
|
|
91550
|
+
const apiBase = authMode === "api-key" ? normalizeApiKeyBase(requestedApiBase) : requestedApiBase;
|
|
91368
91551
|
if (authMode === "api-key") {
|
|
91369
91552
|
console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
|
|
91370
91553
|
console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
@@ -91464,8 +91647,20 @@ async function startProxy(options) {
|
|
|
91464
91647
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91465
91648
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
91466
91649
|
solanaAddress = solanaSigner.address;
|
|
91650
|
+
const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
|
|
91467
91651
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91652
|
+
if (solanaRpcUrl) {
|
|
91653
|
+
const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91654
|
+
const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
|
|
91655
|
+
x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91656
|
+
for (const network of SVM_V1_NETWORKS) {
|
|
91657
|
+
x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91658
|
+
}
|
|
91659
|
+
}
|
|
91468
91660
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91661
|
+
if (solanaRpcUrl) {
|
|
91662
|
+
console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
|
|
91663
|
+
}
|
|
91469
91664
|
}
|
|
91470
91665
|
x402?.onAfterPaymentCreation(async (context) => {
|
|
91471
91666
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
@@ -91481,7 +91676,7 @@ async function startProxy(options) {
|
|
|
91481
91676
|
if (store) store.amountUsd = amountUsd;
|
|
91482
91677
|
console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
|
|
91483
91678
|
});
|
|
91484
|
-
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91679
|
+
const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey, fetch, apiBase) : createPayFetchWithPreAuth(fetch, x402, void 0, {
|
|
91485
91680
|
skipPreAuth: paymentChain === "solana",
|
|
91486
91681
|
// Per-request cost estimate so pre-auth is only reused when the cached
|
|
91487
91682
|
// payment still covers the (possibly larger) request — BlockRun prices per
|
|
@@ -91565,7 +91760,15 @@ async function startProxy(options) {
|
|
|
91565
91760
|
)
|
|
91566
91761
|
]);
|
|
91567
91762
|
if (authMode === "api-key") {
|
|
91568
|
-
|
|
91763
|
+
const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
|
|
91764
|
+
response.balance = credit?.remainingUsd ?? null;
|
|
91765
|
+
response.creditSpent = credit?.spentUsd ?? null;
|
|
91766
|
+
response.creditGranted = credit?.grantedUsd ?? null;
|
|
91767
|
+
response.billingMode = credit?.billingMode ?? null;
|
|
91768
|
+
response.creditSummary = credit ? formatCreditBalance(credit) : null;
|
|
91769
|
+
response.blocked = credit?.blocked ?? false;
|
|
91770
|
+
if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
|
|
91771
|
+
response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
|
|
91569
91772
|
response.billing = "BlockRun account credit";
|
|
91570
91773
|
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91571
91774
|
} else {
|
|
@@ -91977,9 +92180,15 @@ async function startProxy(options) {
|
|
|
91977
92180
|
}
|
|
91978
92181
|
}
|
|
91979
92182
|
}
|
|
91980
|
-
const imgActualCost =
|
|
92183
|
+
const imgActualCost = settledMediaCostUsd(
|
|
92184
|
+
upstream,
|
|
92185
|
+
result,
|
|
92186
|
+
paymentStore.getStore()?.amountUsd,
|
|
92187
|
+
imgCost
|
|
92188
|
+
);
|
|
91981
92189
|
logUsage({
|
|
91982
92190
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92191
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91983
92192
|
model: imgModel,
|
|
91984
92193
|
tier: "IMAGE",
|
|
91985
92194
|
cost: imgActualCost,
|
|
@@ -92048,12 +92257,15 @@ async function startProxy(options) {
|
|
|
92048
92257
|
return;
|
|
92049
92258
|
}
|
|
92050
92259
|
try {
|
|
92051
|
-
|
|
92260
|
+
let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
|
|
92052
92261
|
method: "POST",
|
|
92053
92262
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
92054
92263
|
body: reqBody,
|
|
92055
92264
|
signal: clientAbort.signal
|
|
92056
92265
|
});
|
|
92266
|
+
if (authMode === "api-key") {
|
|
92267
|
+
upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
|
|
92268
|
+
}
|
|
92057
92269
|
const text = await upstream.text();
|
|
92058
92270
|
if (!upstream.ok) {
|
|
92059
92271
|
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
@@ -92100,9 +92312,15 @@ async function startProxy(options) {
|
|
|
92100
92312
|
}
|
|
92101
92313
|
}
|
|
92102
92314
|
}
|
|
92103
|
-
const img2imgActualCost =
|
|
92315
|
+
const img2imgActualCost = settledMediaCostUsd(
|
|
92316
|
+
upstream,
|
|
92317
|
+
result,
|
|
92318
|
+
paymentStore.getStore()?.amountUsd,
|
|
92319
|
+
img2imgCost
|
|
92320
|
+
);
|
|
92104
92321
|
logUsage({
|
|
92105
92322
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92323
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
92106
92324
|
model: img2imgModel,
|
|
92107
92325
|
tier: "IMAGE",
|
|
92108
92326
|
cost: img2imgActualCost,
|
|
@@ -92142,12 +92360,15 @@ async function startProxy(options) {
|
|
|
92142
92360
|
} catch {
|
|
92143
92361
|
}
|
|
92144
92362
|
try {
|
|
92145
|
-
|
|
92363
|
+
let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
|
|
92146
92364
|
method: "POST",
|
|
92147
92365
|
headers: { "content-type": "application/json", "user-agent": USER_AGENT },
|
|
92148
92366
|
body: reqBody,
|
|
92149
92367
|
signal: clientAbort.signal
|
|
92150
92368
|
});
|
|
92369
|
+
if (authMode === "api-key") {
|
|
92370
|
+
upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
|
|
92371
|
+
}
|
|
92151
92372
|
const text = await upstream.text();
|
|
92152
92373
|
if (!upstream.ok) {
|
|
92153
92374
|
res.writeHead(upstream.status, { "Content-Type": "application/json" });
|
|
@@ -92186,9 +92407,15 @@ async function startProxy(options) {
|
|
|
92186
92407
|
}
|
|
92187
92408
|
}
|
|
92188
92409
|
}
|
|
92189
|
-
const audioActualCost =
|
|
92410
|
+
const audioActualCost = settledMediaCostUsd(
|
|
92411
|
+
upstream,
|
|
92412
|
+
result,
|
|
92413
|
+
paymentStore.getStore()?.amountUsd,
|
|
92414
|
+
0.15
|
|
92415
|
+
);
|
|
92190
92416
|
logUsage({
|
|
92191
92417
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92418
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
92192
92419
|
model: audioModel,
|
|
92193
92420
|
tier: "AUDIO",
|
|
92194
92421
|
cost: audioActualCost,
|
|
@@ -92346,7 +92573,12 @@ async function startProxy(options) {
|
|
|
92346
92573
|
}
|
|
92347
92574
|
}
|
|
92348
92575
|
}
|
|
92349
|
-
const videoActualCost =
|
|
92576
|
+
const videoActualCost = settledMediaCostUsd(
|
|
92577
|
+
void 0,
|
|
92578
|
+
submitResult,
|
|
92579
|
+
paymentStore.getStore()?.amountUsd,
|
|
92580
|
+
estimateVideoCost(videoModel, videoDuration, videoHasImageInput)
|
|
92581
|
+
);
|
|
92350
92582
|
logUsage({
|
|
92351
92583
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
92352
92584
|
model: videoModel,
|
|
@@ -92380,7 +92612,9 @@ async function startProxy(options) {
|
|
|
92380
92612
|
res,
|
|
92381
92613
|
apiBase,
|
|
92382
92614
|
payFetch,
|
|
92383
|
-
() => paymentStore.getStore()?.amountUsd ?? 0
|
|
92615
|
+
() => paymentStore.getStore()?.amountUsd ?? 0,
|
|
92616
|
+
false,
|
|
92617
|
+
authMode
|
|
92384
92618
|
);
|
|
92385
92619
|
} catch (err) {
|
|
92386
92620
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -92403,6 +92637,33 @@ async function startProxy(options) {
|
|
|
92403
92637
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
92404
92638
|
return;
|
|
92405
92639
|
}
|
|
92640
|
+
if (authMode === "api-key" && /^\/v1\//.test(req.url ?? "") && !/^\/v1\/(?:chat\/completions|messages)(?:\?|$)/.test(req.url ?? "")) {
|
|
92641
|
+
try {
|
|
92642
|
+
await proxyPaidApiRequest(
|
|
92643
|
+
req,
|
|
92644
|
+
res,
|
|
92645
|
+
apiBase,
|
|
92646
|
+
payFetch,
|
|
92647
|
+
() => paymentStore.getStore()?.amountUsd ?? 0,
|
|
92648
|
+
true,
|
|
92649
|
+
authMode
|
|
92650
|
+
);
|
|
92651
|
+
} catch (err) {
|
|
92652
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
92653
|
+
options.onError?.(error);
|
|
92654
|
+
if (!res.headersSent) {
|
|
92655
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
92656
|
+
res.end(
|
|
92657
|
+
JSON.stringify({
|
|
92658
|
+
error: { message: `Account API error: ${error.message}`, type: "account_error" }
|
|
92659
|
+
})
|
|
92660
|
+
);
|
|
92661
|
+
} else if (!res.writableEnded) {
|
|
92662
|
+
res.end();
|
|
92663
|
+
}
|
|
92664
|
+
}
|
|
92665
|
+
return;
|
|
92666
|
+
}
|
|
92406
92667
|
if (!req.url.includes("/chat/completions")) {
|
|
92407
92668
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
92408
92669
|
res.end(
|
|
@@ -92670,7 +92931,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92670
92931
|
errorBody,
|
|
92671
92932
|
errorStatus: response.status,
|
|
92672
92933
|
isProviderError: category !== null,
|
|
92673
|
-
errorCategory: category ?? void 0
|
|
92934
|
+
errorCategory: category ?? void 0,
|
|
92935
|
+
upstreamRequestId: blockrunRequestId(response)
|
|
92674
92936
|
};
|
|
92675
92937
|
}
|
|
92676
92938
|
const contentType = response.headers.get("content-type") || "";
|
|
@@ -92707,9 +92969,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92707
92969
|
errorCategory: "payment_error"
|
|
92708
92970
|
};
|
|
92709
92971
|
}
|
|
92972
|
+
const networkMsg = describeFetchError(err);
|
|
92973
|
+
const solanaSigningHint = upstreamUrl.startsWith(BLOCKRUN_SOLANA_API) && networkMsg.startsWith("fetch failed") ? " \u2014 on the Solana rail this may be the RPC that signs the payment rather than the gateway; if api.mainnet-beta.solana.com is unreachable from this host, set CLAWROUTER_SOLANA_RPC_URL to your own endpoint" : "";
|
|
92974
|
+
console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
|
|
92710
92975
|
return {
|
|
92711
92976
|
success: false,
|
|
92712
|
-
errorBody:
|
|
92977
|
+
errorBody: `${networkMsg}${solanaSigningHint}`,
|
|
92713
92978
|
errorStatus: 500,
|
|
92714
92979
|
isProviderError: true
|
|
92715
92980
|
// Network errors are retryable
|
|
@@ -92742,6 +93007,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
92742
93007
|
let accumulatedContent = "";
|
|
92743
93008
|
let responseInputTokens;
|
|
92744
93009
|
let responseOutputTokens;
|
|
93010
|
+
let upstreamRequestId;
|
|
92745
93011
|
let requestHadError = false;
|
|
92746
93012
|
let requestSummaryForStore = "";
|
|
92747
93013
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
@@ -93081,9 +93347,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93081
93347
|
responseText = lines.join("\n");
|
|
93082
93348
|
}
|
|
93083
93349
|
console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
|
|
93084
|
-
const imagegenActualCost =
|
|
93350
|
+
const imagegenActualCost = settledMediaCostUsd(
|
|
93351
|
+
imageResponse,
|
|
93352
|
+
imageResult,
|
|
93353
|
+
paymentStore.getStore()?.amountUsd,
|
|
93354
|
+
estimateImageCost(imageModel, imageSize, 1)
|
|
93355
|
+
);
|
|
93085
93356
|
logUsage({
|
|
93086
93357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93358
|
+
...blockrunRequestId(imageResponse) ? { requestId: blockrunRequestId(imageResponse) } : {},
|
|
93087
93359
|
model: imageModel,
|
|
93088
93360
|
tier: "IMAGE",
|
|
93089
93361
|
cost: imagegenActualCost,
|
|
@@ -93307,9 +93579,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
93307
93579
|
responseText = lines.join("\n");
|
|
93308
93580
|
}
|
|
93309
93581
|
console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
|
|
93310
|
-
const img2imgActualCost2 =
|
|
93582
|
+
const img2imgActualCost2 = settledMediaCostUsd(
|
|
93583
|
+
img2imgResponse,
|
|
93584
|
+
img2imgResult,
|
|
93585
|
+
paymentStore.getStore()?.amountUsd,
|
|
93586
|
+
estimateImageCost(img2imgModel, img2imgSize, 1)
|
|
93587
|
+
);
|
|
93311
93588
|
logUsage({
|
|
93312
93589
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
93590
|
+
...blockrunRequestId(img2imgResponse) ? { requestId: blockrunRequestId(img2imgResponse) } : {},
|
|
93313
93591
|
model: img2imgModel,
|
|
93314
93592
|
tier: "IMAGE",
|
|
93315
93593
|
cost: img2imgActualCost2,
|
|
@@ -94148,7 +94426,8 @@ data: [DONE]
|
|
|
94148
94426
|
}
|
|
94149
94427
|
lastError = {
|
|
94150
94428
|
body: result.errorBody || "Unknown error",
|
|
94151
|
-
status: result.errorStatus || 500
|
|
94429
|
+
status: result.errorStatus || 500,
|
|
94430
|
+
requestId: result.upstreamRequestId
|
|
94152
94431
|
};
|
|
94153
94432
|
failedAttempts.push({
|
|
94154
94433
|
model: tryModel,
|
|
@@ -94220,7 +94499,8 @@ data: [DONE]
|
|
|
94220
94499
|
}
|
|
94221
94500
|
lastError = {
|
|
94222
94501
|
body: retryResult.errorBody || lastError?.body || "Unknown error",
|
|
94223
|
-
status: retryResult.errorStatus || lastError?.status || 500
|
|
94502
|
+
status: retryResult.errorStatus || lastError?.status || 500,
|
|
94503
|
+
requestId: retryResult.upstreamRequestId ?? lastError?.requestId
|
|
94224
94504
|
};
|
|
94225
94505
|
failedAttempts.push({
|
|
94226
94506
|
model: tryModel,
|
|
@@ -94362,10 +94642,14 @@ data: [DONE]
|
|
|
94362
94642
|
}
|
|
94363
94643
|
}
|
|
94364
94644
|
}
|
|
94645
|
+
upstreamRequestId = blockrunRequestId(upstream);
|
|
94646
|
+
noteRemainingCredit(upstream);
|
|
94365
94647
|
if (!upstream) {
|
|
94366
94648
|
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
94367
94649
|
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
94368
|
-
console.log(
|
|
94650
|
+
console.log(
|
|
94651
|
+
`[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
|
|
94652
|
+
);
|
|
94369
94653
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
94370
94654
|
const errStatus = lastError?.status || 502;
|
|
94371
94655
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
@@ -94403,7 +94687,8 @@ data: [DONE]
|
|
|
94403
94687
|
res.writeHead(errStatus, {
|
|
94404
94688
|
"Content-Type": "application/json",
|
|
94405
94689
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
94406
|
-
"x-context-limit-kb": String(CONTEXT_LIMIT_KB)
|
|
94690
|
+
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
94691
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
94407
94692
|
});
|
|
94408
94693
|
res.end(transformedErr);
|
|
94409
94694
|
deduplicator.complete(dedupKey, {
|
|
@@ -94867,12 +95152,24 @@ data: [DONE]
|
|
|
94867
95152
|
maxTokens,
|
|
94868
95153
|
routingProfile ?? void 0
|
|
94869
95154
|
);
|
|
94870
|
-
|
|
95155
|
+
const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
|
|
95156
|
+
if (FREE_MODELS.has(logModel)) {
|
|
95157
|
+
logCost = 0;
|
|
95158
|
+
logSavings = 1;
|
|
95159
|
+
} else if (apiKeyPricing) {
|
|
95160
|
+
const inTokens = responseInputTokens ?? chargedInputTokens;
|
|
95161
|
+
const outTokens = responseOutputTokens ?? 0;
|
|
95162
|
+
logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
|
|
95163
|
+
logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
|
|
95164
|
+
} else {
|
|
95165
|
+
logCost = costs.costEstimate;
|
|
95166
|
+
logSavings = costs.savings;
|
|
95167
|
+
}
|
|
94871
95168
|
logBaseline = costs.baselineCost;
|
|
94872
|
-
logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
|
|
94873
95169
|
}
|
|
94874
95170
|
const entry = {
|
|
94875
95171
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95172
|
+
...upstreamRequestId ? { requestId: upstreamRequestId } : {},
|
|
94876
95173
|
model: logModel,
|
|
94877
95174
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
94878
95175
|
cost: logCost,
|
|
@@ -94887,7 +95184,7 @@ data: [DONE]
|
|
|
94887
95184
|
});
|
|
94888
95185
|
}
|
|
94889
95186
|
}
|
|
94890
|
-
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, DESKTOP_SERVICE_TOKEN_FILE, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, gatewayModelIds, FREE_MODEL, FREE_UPSTREAM_OVERRIDES, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, GATEWAY_CATALOG_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, ClientDisconnectedError, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING;
|
|
95187
|
+
var paymentStore, BLOCKRUN_API, BLOCKRUN_SOLANA_API, DESKTOP_SERVICE_TOKEN_FILE, IMAGE_DIR, AUDIO_DIR, VIDEO_DIR, AUTO_MODEL, ROUTING_PROFILES, FREE_MODELS, gatewayModelIds, FREE_MODEL, FREE_UPSTREAM_OVERRIDES, MAX_MESSAGES, CONTEXT_LIMIT_KB, HEARTBEAT_INTERVAL_MS, BALANCE_CHECK_TIMEOUT_MS, GATEWAY_CATALOG_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, PER_MODEL_TIMEOUT_MS, REASONING_MODEL_TIMEOUT_MS, REASONING_MODEL_IDS, MAX_FALLBACK_ATTEMPTS, HEALTH_CHECK_TIMEOUT_MS, RATE_LIMIT_COOLDOWN_MS, OVERLOAD_COOLDOWN_MS, PORT_RETRY_ATTEMPTS, PORT_RETRY_DELAY_MS, MODEL_BODY_READ_TIMEOUT_MS, ERROR_BODY_READ_TIMEOUT_MS, ClientDisconnectedError, rateLimitedModels, overloadedModels, perProviderErrors, BALANCE_CHECK_BUFFER, BALANCE_PREFLIGHT_OUTPUT_TOKEN_CAP, PROVIDER_ERROR_PATTERNS, DEGRADED_RESPONSE_PATTERNS, DEGRADED_LOOP_PATTERNS, VALID_ROLES, ROLE_MAPPINGS, VALID_TOOL_ID_PATTERN, KIMI_BLOCK_RE, KIMI_TOKEN_RE, THINKING_TAG_RE, THINKING_BLOCK_RE, BLOCKRUN_MODEL_BY_ID, IMAGE_PRICING, IMAGE_MODEL_IDS, IMAGE_MODEL_SIZES, IMAGE_MODEL_ALIASES, VIDEO_PRICING, PHONE_PRICING, LOW_CREDIT_USD, lowCreditWarned;
|
|
94891
95188
|
var init_proxy = __esm({
|
|
94892
95189
|
"src/proxy.ts"() {
|
|
94893
95190
|
"use strict";
|
|
@@ -94912,6 +95209,7 @@ var init_proxy = __esm({
|
|
|
94912
95209
|
init_api_key();
|
|
94913
95210
|
init_spend_control();
|
|
94914
95211
|
init_compression();
|
|
95212
|
+
init_errors5();
|
|
94915
95213
|
init_version4();
|
|
94916
95214
|
init_session();
|
|
94917
95215
|
init_updater();
|
|
@@ -95148,6 +95446,8 @@ var init_proxy = __esm({
|
|
|
95148
95446
|
"phone/numbers/release": 0,
|
|
95149
95447
|
"voice/call": 0.54
|
|
95150
95448
|
};
|
|
95449
|
+
LOW_CREDIT_USD = 1;
|
|
95450
|
+
lowCreditWarned = false;
|
|
95151
95451
|
}
|
|
95152
95452
|
});
|
|
95153
95453
|
|
|
@@ -111977,7 +112277,7 @@ var init_readBlob = __esm({
|
|
|
111977
112277
|
|
|
111978
112278
|
// node_modules/axios/lib/helpers/formDataToStream.js
|
|
111979
112279
|
import util2 from "util";
|
|
111980
|
-
import { Readable } from "stream";
|
|
112280
|
+
import { Readable as Readable2 } from "stream";
|
|
111981
112281
|
var BOUNDARY_ALPHABET, textEncoder, CRLF, CRLF_BYTES, CRLF_BYTES_COUNT, FormDataPart, formDataToStream, formDataToStream_default;
|
|
111982
112282
|
var init_formDataToStream = __esm({
|
|
111983
112283
|
"node_modules/axios/lib/helpers/formDataToStream.js"() {
|
|
@@ -112057,7 +112357,7 @@ var init_formDataToStream = __esm({
|
|
|
112057
112357
|
computedHeaders["Content-Length"] = contentLength;
|
|
112058
112358
|
}
|
|
112059
112359
|
headersHandler && headersHandler(computedHeaders);
|
|
112060
|
-
return
|
|
112360
|
+
return Readable2.from(
|
|
112061
112361
|
(async function* () {
|
|
112062
112362
|
for (const part of parts) {
|
|
112063
112363
|
yield boundaryBytes;
|
|
@@ -228167,12 +228467,12 @@ async function startProxyInBackground(api, startupGeneration) {
|
|
|
228167
228467
|
proc.__clawrouterStartupPhase = "starting";
|
|
228168
228468
|
}
|
|
228169
228469
|
const pluginApiKey = api.pluginConfig?.apiKey;
|
|
228170
|
-
|
|
228171
|
-
|
|
228172
|
-
|
|
228173
|
-
`pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
|
|
228470
|
+
if (typeof pluginApiKey === "string" && pluginApiKey.trim() && !isValidApiKey(pluginApiKey)) {
|
|
228471
|
+
throw new Error(
|
|
228472
|
+
`pluginConfig.apiKey is set but is not a BlockRun key (expected brk_\u2026). Fix it or remove it \u2014 refusing to fall back to another credential.`
|
|
228174
228473
|
);
|
|
228175
228474
|
}
|
|
228475
|
+
const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
|
|
228176
228476
|
const apiKey = resolvedApiKey?.key;
|
|
228177
228477
|
const configKey = api.pluginConfig?.walletKey;
|
|
228178
228478
|
let wallet;
|
|
@@ -229358,7 +229658,18 @@ ${errText}`
|
|
|
229358
229658
|
if (!isGatewayMode()) {
|
|
229359
229659
|
if (shouldLogRegistration) {
|
|
229360
229660
|
void (async () => {
|
|
229361
|
-
|
|
229661
|
+
let keyResolution;
|
|
229662
|
+
try {
|
|
229663
|
+
keyResolution = await resolveApiKey();
|
|
229664
|
+
} catch (error) {
|
|
229665
|
+
api.logger.error(
|
|
229666
|
+
`BlockRun API key is configured but unusable: ${error instanceof Error ? error.message : String(error)}`
|
|
229667
|
+
);
|
|
229668
|
+
api.logger.error(
|
|
229669
|
+
`Not falling back to a wallet \u2014 fix the key, or run "clawrouter logout" to pay from the wallet deliberately.`
|
|
229670
|
+
);
|
|
229671
|
+
return;
|
|
229672
|
+
}
|
|
229362
229673
|
if (keyResolution) {
|
|
229363
229674
|
api.logger.info(
|
|
229364
229675
|
`Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
|