@blockrun/clawrouter 0.12.271 → 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/dist/index.js CHANGED
@@ -55864,7 +55864,7 @@ var require_websocket = __commonJS({
55864
55864
  var net3 = __require("net");
55865
55865
  var tls2 = __require("tls");
55866
55866
  var { randomBytes: randomBytes9, createHash: createHash4 } = __require("crypto");
55867
- var { Duplex, Readable: Readable2 } = __require("stream");
55867
+ var { Duplex, Readable: Readable3 } = __require("stream");
55868
55868
  var { URL: URL3 } = __require("url");
55869
55869
  var PerMessageDeflate2 = require_permessage_deflate();
55870
55870
  var Receiver2 = require_receiver();
@@ -59926,16 +59926,20 @@ function maskApiKey(key2) {
59926
59926
  async function readOptional2(path5) {
59927
59927
  try {
59928
59928
  return (await readTextFile(path5)).trim() || void 0;
59929
- } catch {
59930
- 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
+ );
59931
59935
  }
59932
59936
  }
59933
59937
  async function resolveApiKey() {
59934
59938
  const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
59935
59939
  if (envKey) {
59936
59940
  if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
59937
- console.warn(
59938
- `[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
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.`
59939
59943
  );
59940
59944
  }
59941
59945
  for (const [path5, source] of [
@@ -59945,8 +59949,8 @@ async function resolveApiKey() {
59945
59949
  const stored = await readOptional2(path5);
59946
59950
  if (stored === void 0) continue;
59947
59951
  if (isValidApiKey(stored)) return { key: stored, source };
59948
- console.warn(
59949
- `[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
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.`
59950
59954
  );
59951
59955
  }
59952
59956
  return void 0;
@@ -59985,16 +59989,61 @@ function formatCreditBalance(b) {
59985
59989
  const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
59986
59990
  return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
59987
59991
  }
59988
- function createApiKeyFetch(apiKey, baseFetch = fetch) {
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));
59989
60004
  return async (input, init2) => {
59990
- const headers = new Headers(init2?.headers);
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
+ }
59991
60017
  headers.set("authorization", `Bearer ${apiKey}`);
59992
- headers.delete("x-payment");
59993
- headers.delete("x-api-key");
59994
- const response = await baseFetch(input, { ...init2, headers });
60018
+ const response = await baseFetch(request2 ? new Request(url2, request2) : url2.href, {
60019
+ ...init2,
60020
+ headers,
60021
+ redirect: "error"
60022
+ });
59995
60023
  return response.ok ? response : explainApiKeyFailure(response);
59996
60024
  };
59997
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
+ }
59998
60047
  async function explainApiKeyFailure(response) {
59999
60048
  const hint = HINTS[response.status];
60000
60049
  if (!hint) return response;
@@ -60028,7 +60077,7 @@ var init_api_key = __esm({
60028
60077
  PORTAL_URL = "https://user.blockrun.ai";
60029
60078
  PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
60030
60079
  PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
60031
- 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";
60032
60081
  HINTS = {
60033
60082
  401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
60034
60083
  402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
@@ -69174,7 +69223,7 @@ var require_client_h2 = __commonJS({
69174
69223
  "node_modules/undici/lib/dispatcher/client-h2.js"(exports, module) {
69175
69224
  "use strict";
69176
69225
  var assert10 = __require("assert");
69177
- var { pipeline } = __require("stream");
69226
+ var { pipeline: pipeline2 } = __require("stream");
69178
69227
  var util5 = require_util();
69179
69228
  var {
69180
69229
  RequestContentLengthMismatchError,
@@ -70321,7 +70370,7 @@ var require_client_h2 = __commonJS({
70321
70370
  }
70322
70371
  function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) {
70323
70372
  assert10(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined");
70324
- const pipe2 = pipeline(
70373
+ const pipe2 = pipeline2(
70325
70374
  body,
70326
70375
  h2stream,
70327
70376
  (err) => {
@@ -73476,7 +73525,7 @@ var require_readable = __commonJS({
73476
73525
  "use strict";
73477
73526
  var assert10 = __require("assert");
73478
73527
  var { addAbortListener } = __require("events");
73479
- var { Readable: Readable2 } = __require("stream");
73528
+ var { Readable: Readable3 } = __require("stream");
73480
73529
  var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors();
73481
73530
  var util5 = require_util();
73482
73531
  var { ReadableStreamFrom } = require_util();
@@ -73490,7 +73539,7 @@ var require_readable = __commonJS({
73490
73539
  var kBytesRead = /* @__PURE__ */ Symbol("kBytesRead");
73491
73540
  var noop2 = () => {
73492
73541
  };
73493
- var BodyReadable = class extends Readable2 {
73542
+ var BodyReadable = class extends Readable3 {
73494
73543
  /**
73495
73544
  * @param {object} opts
73496
73545
  * @param {(this: Readable, size: number) => void} opts.resume
@@ -73889,7 +73938,7 @@ var require_api_request = __commonJS({
73889
73938
  "use strict";
73890
73939
  var assert10 = __require("assert");
73891
73940
  var { AsyncResource } = __require("async_hooks");
73892
- var { Readable: Readable2 } = require_readable();
73941
+ var { Readable: Readable3 } = require_readable();
73893
73942
  var { InvalidArgumentError, RequestAbortedError } = require_errors();
73894
73943
  var util5 = require_util();
73895
73944
  function noop2() {
@@ -73975,7 +74024,7 @@ var require_api_request = __commonJS({
73975
74024
  const parsedHeaders = headers;
73976
74025
  const contentType = parsedHeaders?.["content-type"];
73977
74026
  const contentLength = parsedHeaders?.["content-length"];
73978
- const res = new Readable2({
74027
+ const res = new Readable3({
73979
74028
  resume: () => controller.resume(),
73980
74029
  abort: (reason) => controller.abort(reason),
73981
74030
  contentType,
@@ -74351,7 +74400,7 @@ var require_api_pipeline = __commonJS({
74351
74400
  "node_modules/undici/lib/api/api-pipeline.js"(exports, module) {
74352
74401
  "use strict";
74353
74402
  var {
74354
- Readable: Readable2,
74403
+ Readable: Readable3,
74355
74404
  Duplex,
74356
74405
  PassThrough
74357
74406
  } = __require("stream");
@@ -74368,7 +74417,7 @@ var require_api_pipeline = __commonJS({
74368
74417
  function noop2() {
74369
74418
  }
74370
74419
  var kResume = /* @__PURE__ */ Symbol("resume");
74371
- var PipelineRequest = class extends Readable2 {
74420
+ var PipelineRequest = class extends Readable3 {
74372
74421
  constructor() {
74373
74422
  super({ autoDestroy: true });
74374
74423
  this[kResume] = null;
@@ -74386,7 +74435,7 @@ var require_api_pipeline = __commonJS({
74386
74435
  callback(err);
74387
74436
  }
74388
74437
  };
74389
- var PipelineResponse = class extends Readable2 {
74438
+ var PipelineResponse = class extends Readable3 {
74390
74439
  constructor(resume2) {
74391
74440
  super({ autoDestroy: true });
74392
74441
  this[kResume] = resume2;
@@ -74540,7 +74589,7 @@ var require_api_pipeline = __commonJS({
74540
74589
  util5.destroy(ret, err);
74541
74590
  }
74542
74591
  };
74543
- function pipeline(opts, handler) {
74592
+ function pipeline2(opts, handler) {
74544
74593
  try {
74545
74594
  const pipelineHandler = new PipelineHandler(opts, handler);
74546
74595
  this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
@@ -74549,7 +74598,7 @@ var require_api_pipeline = __commonJS({
74549
74598
  return new PassThrough().destroy(err);
74550
74599
  }
74551
74600
  }
74552
- module.exports = pipeline;
74601
+ module.exports = pipeline2;
74553
74602
  }
74554
74603
  });
74555
74604
 
@@ -79667,7 +79716,7 @@ var require_cache2 = __commonJS({
79667
79716
  "node_modules/undici/lib/interceptor/cache.js"(exports, module) {
79668
79717
  "use strict";
79669
79718
  var assert10 = __require("assert");
79670
- var { Readable: Readable2 } = __require("stream");
79719
+ var { Readable: Readable3 } = __require("stream");
79671
79720
  var util5 = require_util();
79672
79721
  var CacheHandler = require_cache_handler();
79673
79722
  var MemoryCacheStore = require_memory_cache_store();
@@ -79843,7 +79892,7 @@ var require_cache2 = __commonJS({
79843
79892
  return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler));
79844
79893
  }
79845
79894
  function sendCachedValue(handler, opts, result, age, context, isStale2) {
79846
- const stream4 = util5.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
79895
+ const stream4 = util5.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
79847
79896
  assert10(!stream4.destroyed, "stream should not be destroyed");
79848
79897
  assert10(!stream4.readableDidRead, "stream should not be readableDidRead");
79849
79898
  const controller = {
@@ -80088,7 +80137,7 @@ var require_decompress = __commonJS({
80088
80137
  "node_modules/undici/lib/interceptor/decompress.js"(exports, module) {
80089
80138
  "use strict";
80090
80139
  var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("zlib");
80091
- var { pipeline } = __require("stream");
80140
+ var { pipeline: pipeline2 } = __require("stream");
80092
80141
  var DecoratorHandler = require_decorator_handler();
80093
80142
  var supportedEncodings = {
80094
80143
  gzip: createGunzip,
@@ -80198,7 +80247,7 @@ var require_decompress = __commonJS({
80198
80247
  #setupMultipleDecompressors(controller) {
80199
80248
  const lastDecompressor = this.#decompressors[this.#decompressors.length - 1];
80200
80249
  this.#setupDecompressorEvents(lastDecompressor, controller);
80201
- pipeline(this.#decompressors, (err) => {
80250
+ pipeline2(this.#decompressors, (err) => {
80202
80251
  if (err) {
80203
80252
  super.onResponseError(controller, err);
80204
80253
  return;
@@ -82992,7 +83041,7 @@ var require_fetch = __commonJS({
82992
83041
  subresourceSet
82993
83042
  } = require_constants4();
82994
83043
  var EE = __require("events");
82995
- var { Readable: Readable2, pipeline, finished: finished2, isErrored, isReadable } = __require("stream");
83044
+ var { Readable: Readable3, pipeline: pipeline2, finished: finished2, isErrored, isReadable } = __require("stream");
82996
83045
  var { addAbortListener, bufferToLowerCasedHeaderName } = require_util();
82997
83046
  var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
82998
83047
  var { getGlobalDispatcher } = require_global2();
@@ -83968,7 +84017,7 @@ var require_fetch = __commonJS({
83968
84017
  const headersList = new HeadersList();
83969
84018
  appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders);
83970
84019
  const location = headersList.get("location", true);
83971
- this.body = new Readable2({ read: () => controller.resume() });
84020
+ this.body = new Readable3({ read: () => controller.resume() });
83972
84021
  const willFollow = location && request2.redirect === "follow" && redirectStatusSet.has(status);
83973
84022
  const decoders = [];
83974
84023
  if (request2.method !== "HEAD" && request2.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
@@ -84016,7 +84065,7 @@ var require_fetch = __commonJS({
84016
84065
  status,
84017
84066
  statusText,
84018
84067
  headersList,
84019
- body: decoders.length ? pipeline(this.body, ...decoders, (err) => {
84068
+ body: decoders.length ? pipeline2(this.body, ...decoders, (err) => {
84020
84069
  if (err) {
84021
84070
  this.onResponseError(controller, err);
84022
84071
  }
@@ -87828,7 +87877,7 @@ ${value}`;
87828
87877
  var require_eventsource = __commonJS({
87829
87878
  "node_modules/undici/lib/web/eventsource/eventsource.js"(exports, module) {
87830
87879
  "use strict";
87831
- var { pipeline } = __require("stream");
87880
+ var { pipeline: pipeline2 } = __require("stream");
87832
87881
  var { fetching } = require_fetch();
87833
87882
  var { webidl } = require_webidl();
87834
87883
  var { EventSourceStream } = require_eventsource_stream();
@@ -87978,7 +88027,7 @@ var require_eventsource = __commonJS({
87978
88027
  ));
87979
88028
  }
87980
88029
  });
87981
- pipeline(
88030
+ pipeline2(
87982
88031
  response.body.stream,
87983
88032
  eventSourceStream,
87984
88033
  (error) => {
@@ -90528,7 +90577,8 @@ var init_v1 = __esm({
90528
90577
  import { AsyncLocalStorage } from "async_hooks";
90529
90578
  import { createHmac } from "crypto";
90530
90579
  import { createServer } from "http";
90531
- import { finished } from "stream";
90580
+ import { finished, Readable } from "stream";
90581
+ import { pipeline } from "stream/promises";
90532
90582
  import { homedir as homedir8 } from "os";
90533
90583
  import { join as join11 } from "path";
90534
90584
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
@@ -90537,10 +90587,10 @@ async function loadGatewayCatalog(apiBase, apiKey) {
90537
90587
  try {
90538
90588
  const controller = new AbortController();
90539
90589
  const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
90540
- const res = await fetch(`${apiBase}/v1/models`, {
90541
- signal: controller.signal,
90542
- ...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
90543
- });
90590
+ const res = await (apiKey ? createApiKeyFetch(apiKey, fetch, apiBase) : fetch)(
90591
+ `${apiBase}/v1/models`,
90592
+ { signal: controller.signal }
90593
+ );
90544
90594
  clearTimeout(timer2);
90545
90595
  if (!res.ok) return;
90546
90596
  const body = await res.json();
@@ -91286,6 +91336,39 @@ function blockrunRequestId(response) {
91286
91336
  }
91287
91337
  return void 0;
91288
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
+ }
91289
91372
  function gatewaySettledCostUsd(response) {
91290
91373
  const raw = response?.headers.get("x-blockrun-cost-usd");
91291
91374
  if (raw === null || raw === void 0) return void 0;
@@ -91295,7 +91378,7 @@ function gatewaySettledCostUsd(response) {
91295
91378
  if (!Number.isFinite(value) || value < 0) return void 0;
91296
91379
  return value;
91297
91380
  }
91298
- async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
91381
+ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd, accountPassthrough = false, authMode = "wallet") {
91299
91382
  const startTime = Date.now();
91300
91383
  const upstreamUrl = `${apiBase}${req.url}`;
91301
91384
  const isBlockrunExa = req.url?.startsWith("/v1/exa/") ?? false;
@@ -91334,16 +91417,32 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91334
91417
  return;
91335
91418
  responseHeaders[key2] = value;
91336
91419
  });
91420
+ if (accountPassthrough || authMode === "api-key") {
91421
+ responseHeaders["cache-control"] = "no-store";
91422
+ }
91337
91423
  res.writeHead(upstream.status, responseHeaders);
91338
- if (upstream.body) {
91339
- const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91340
- for (const chunk of chunks) {
91341
- safeWrite(res, Buffer.from(chunk));
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
+ }
91342
91440
  }
91441
+ res.end();
91343
91442
  }
91344
- res.end();
91345
91443
  const latencyMs = Date.now() - startTime;
91346
91444
  console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
91445
+ noteRemainingCredit(upstream);
91347
91446
  const settledCostUsd = gatewaySettledCostUsd(upstream);
91348
91447
  const paidAmount = settledCostUsd ?? getActualPaymentUsd();
91349
91448
  const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
@@ -91447,7 +91546,8 @@ async function startProxy(options) {
91447
91546
  const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91448
91547
  const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91449
91548
  const paymentChain = options.paymentChain ?? await resolvePaymentChain();
91450
- const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
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;
91451
91551
  if (authMode === "api-key") {
91452
91552
  console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
91453
91553
  console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
@@ -91576,7 +91676,7 @@ async function startProxy(options) {
91576
91676
  if (store) store.amountUsd = amountUsd;
91577
91677
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
91578
91678
  });
91579
- 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, {
91580
91680
  skipPreAuth: paymentChain === "solana",
91581
91681
  // Per-request cost estimate so pre-auth is only reused when the cached
91582
91682
  // payment still covers the (possibly larger) request — BlockRun prices per
@@ -92080,9 +92180,15 @@ async function startProxy(options) {
92080
92180
  }
92081
92181
  }
92082
92182
  }
92083
- const imgActualCost = paymentStore.getStore()?.amountUsd ?? imgCost;
92183
+ const imgActualCost = settledMediaCostUsd(
92184
+ upstream,
92185
+ result,
92186
+ paymentStore.getStore()?.amountUsd,
92187
+ imgCost
92188
+ );
92084
92189
  logUsage({
92085
92190
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92191
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92086
92192
  model: imgModel,
92087
92193
  tier: "IMAGE",
92088
92194
  cost: imgActualCost,
@@ -92151,12 +92257,15 @@ async function startProxy(options) {
92151
92257
  return;
92152
92258
  }
92153
92259
  try {
92154
- const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92260
+ let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92155
92261
  method: "POST",
92156
92262
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92157
92263
  body: reqBody,
92158
92264
  signal: clientAbort.signal
92159
92265
  });
92266
+ if (authMode === "api-key") {
92267
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92268
+ }
92160
92269
  const text = await upstream.text();
92161
92270
  if (!upstream.ok) {
92162
92271
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92203,9 +92312,15 @@ async function startProxy(options) {
92203
92312
  }
92204
92313
  }
92205
92314
  }
92206
- const img2imgActualCost = paymentStore.getStore()?.amountUsd ?? img2imgCost;
92315
+ const img2imgActualCost = settledMediaCostUsd(
92316
+ upstream,
92317
+ result,
92318
+ paymentStore.getStore()?.amountUsd,
92319
+ img2imgCost
92320
+ );
92207
92321
  logUsage({
92208
92322
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92323
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92209
92324
  model: img2imgModel,
92210
92325
  tier: "IMAGE",
92211
92326
  cost: img2imgActualCost,
@@ -92245,12 +92360,15 @@ async function startProxy(options) {
92245
92360
  } catch {
92246
92361
  }
92247
92362
  try {
92248
- const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92363
+ let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92249
92364
  method: "POST",
92250
92365
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92251
92366
  body: reqBody,
92252
92367
  signal: clientAbort.signal
92253
92368
  });
92369
+ if (authMode === "api-key") {
92370
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92371
+ }
92254
92372
  const text = await upstream.text();
92255
92373
  if (!upstream.ok) {
92256
92374
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92289,9 +92407,15 @@ async function startProxy(options) {
92289
92407
  }
92290
92408
  }
92291
92409
  }
92292
- const audioActualCost = paymentStore.getStore()?.amountUsd ?? 0.15;
92410
+ const audioActualCost = settledMediaCostUsd(
92411
+ upstream,
92412
+ result,
92413
+ paymentStore.getStore()?.amountUsd,
92414
+ 0.15
92415
+ );
92293
92416
  logUsage({
92294
92417
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92418
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92295
92419
  model: audioModel,
92296
92420
  tier: "AUDIO",
92297
92421
  cost: audioActualCost,
@@ -92449,7 +92573,12 @@ async function startProxy(options) {
92449
92573
  }
92450
92574
  }
92451
92575
  }
92452
- const videoActualCost = paymentStore.getStore()?.amountUsd ?? estimateVideoCost(videoModel, videoDuration, videoHasImageInput);
92576
+ const videoActualCost = settledMediaCostUsd(
92577
+ void 0,
92578
+ submitResult,
92579
+ paymentStore.getStore()?.amountUsd,
92580
+ estimateVideoCost(videoModel, videoDuration, videoHasImageInput)
92581
+ );
92453
92582
  logUsage({
92454
92583
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92455
92584
  model: videoModel,
@@ -92483,7 +92612,9 @@ async function startProxy(options) {
92483
92612
  res,
92484
92613
  apiBase,
92485
92614
  payFetch,
92486
- () => paymentStore.getStore()?.amountUsd ?? 0
92615
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92616
+ false,
92617
+ authMode
92487
92618
  );
92488
92619
  } catch (err) {
92489
92620
  const error = err instanceof Error ? err : new Error(String(err));
@@ -92506,6 +92637,33 @@ async function startProxy(options) {
92506
92637
  res.end(JSON.stringify({ error: "Not found" }));
92507
92638
  return;
92508
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
+ }
92509
92667
  if (!req.url.includes("/chat/completions")) {
92510
92668
  res.writeHead(404, { "Content-Type": "application/json" });
92511
92669
  res.end(
@@ -93189,9 +93347,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93189
93347
  responseText = lines.join("\n");
93190
93348
  }
93191
93349
  console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
93192
- const imagegenActualCost = paymentStore.getStore()?.amountUsd ?? estimateImageCost(imageModel, imageSize, 1);
93350
+ const imagegenActualCost = settledMediaCostUsd(
93351
+ imageResponse,
93352
+ imageResult,
93353
+ paymentStore.getStore()?.amountUsd,
93354
+ estimateImageCost(imageModel, imageSize, 1)
93355
+ );
93193
93356
  logUsage({
93194
93357
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93358
+ ...blockrunRequestId(imageResponse) ? { requestId: blockrunRequestId(imageResponse) } : {},
93195
93359
  model: imageModel,
93196
93360
  tier: "IMAGE",
93197
93361
  cost: imagegenActualCost,
@@ -93415,9 +93579,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93415
93579
  responseText = lines.join("\n");
93416
93580
  }
93417
93581
  console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
93418
- const img2imgActualCost2 = paymentStore.getStore()?.amountUsd ?? estimateImageCost(img2imgModel, img2imgSize, 1);
93582
+ const img2imgActualCost2 = settledMediaCostUsd(
93583
+ img2imgResponse,
93584
+ img2imgResult,
93585
+ paymentStore.getStore()?.amountUsd,
93586
+ estimateImageCost(img2imgModel, img2imgSize, 1)
93587
+ );
93419
93588
  logUsage({
93420
93589
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93590
+ ...blockrunRequestId(img2imgResponse) ? { requestId: blockrunRequestId(img2imgResponse) } : {},
93421
93591
  model: img2imgModel,
93422
93592
  tier: "IMAGE",
93423
93593
  cost: img2imgActualCost2,
@@ -94473,6 +94643,7 @@ data: [DONE]
94473
94643
  }
94474
94644
  }
94475
94645
  upstreamRequestId = blockrunRequestId(upstream);
94646
+ noteRemainingCredit(upstream);
94476
94647
  if (!upstream) {
94477
94648
  const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
94478
94649
  const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
@@ -95013,7 +95184,7 @@ data: [DONE]
95013
95184
  });
95014
95185
  }
95015
95186
  }
95016
- 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;
95017
95188
  var init_proxy = __esm({
95018
95189
  "src/proxy.ts"() {
95019
95190
  "use strict";
@@ -95275,6 +95446,8 @@ var init_proxy = __esm({
95275
95446
  "phone/numbers/release": 0,
95276
95447
  "voice/call": 0.54
95277
95448
  };
95449
+ LOW_CREDIT_USD = 1;
95450
+ lowCreditWarned = false;
95278
95451
  }
95279
95452
  });
95280
95453
 
@@ -112104,7 +112277,7 @@ var init_readBlob = __esm({
112104
112277
 
112105
112278
  // node_modules/axios/lib/helpers/formDataToStream.js
112106
112279
  import util2 from "util";
112107
- import { Readable } from "stream";
112280
+ import { Readable as Readable2 } from "stream";
112108
112281
  var BOUNDARY_ALPHABET, textEncoder, CRLF, CRLF_BYTES, CRLF_BYTES_COUNT, FormDataPart, formDataToStream, formDataToStream_default;
112109
112282
  var init_formDataToStream = __esm({
112110
112283
  "node_modules/axios/lib/helpers/formDataToStream.js"() {
@@ -112184,7 +112357,7 @@ var init_formDataToStream = __esm({
112184
112357
  computedHeaders["Content-Length"] = contentLength;
112185
112358
  }
112186
112359
  headersHandler && headersHandler(computedHeaders);
112187
- return Readable.from(
112360
+ return Readable2.from(
112188
112361
  (async function* () {
112189
112362
  for (const part of parts) {
112190
112363
  yield boundaryBytes;
@@ -228294,12 +228467,12 @@ async function startProxyInBackground(api, startupGeneration) {
228294
228467
  proc.__clawrouterStartupPhase = "starting";
228295
228468
  }
228296
228469
  const pluginApiKey = api.pluginConfig?.apiKey;
228297
- const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228298
- if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
228299
- api.logger.warn(
228300
- `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.`
228301
228473
  );
228302
228474
  }
228475
+ const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228303
228476
  const apiKey = resolvedApiKey?.key;
228304
228477
  const configKey = api.pluginConfig?.walletKey;
228305
228478
  let wallet;
@@ -229485,7 +229658,18 @@ ${errText}`
229485
229658
  if (!isGatewayMode()) {
229486
229659
  if (shouldLogRegistration) {
229487
229660
  void (async () => {
229488
- const keyResolution = await resolveApiKey().catch(() => void 0);
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
+ }
229489
229673
  if (keyResolution) {
229490
229674
  api.logger.info(
229491
229675
  `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`