@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/dist/cli.js CHANGED
@@ -40713,6 +40713,18 @@ function isBalanceError(error) {
40713
40713
  function isRpcError(error) {
40714
40714
  return error instanceof Error && error.code === "RPC_ERROR";
40715
40715
  }
40716
+ function describeFetchError(error) {
40717
+ const message = error instanceof Error ? error.message : String(error);
40718
+ const cause = error?.cause;
40719
+ if (!(cause instanceof Error)) return message;
40720
+ const detail = cause;
40721
+ const host = detail.hostname || detail.address;
40722
+ const where = host && detail.port ? `${host}:${detail.port}` : host;
40723
+ const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
40724
+ (part) => typeof part === "string" && part !== ""
40725
+ );
40726
+ return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
40727
+ }
40716
40728
  var InsufficientFundsError2, EmptyWalletError, RpcError2;
40717
40729
  var init_errors5 = __esm({
40718
40730
  "src/errors.ts"() {
@@ -55984,7 +55996,7 @@ var require_websocket = __commonJS({
55984
55996
  var net3 = __require("net");
55985
55997
  var tls2 = __require("tls");
55986
55998
  var { randomBytes: randomBytes9, createHash: createHash4 } = __require("crypto");
55987
- var { Duplex, Readable: Readable2 } = __require("stream");
55999
+ var { Duplex, Readable: Readable3 } = __require("stream");
55988
56000
  var { URL: URL3 } = __require("url");
55989
56001
  var PerMessageDeflate2 = require_permessage_deflate();
55990
56002
  var Receiver2 = require_receiver();
@@ -60107,16 +60119,20 @@ function maskApiKey(key2) {
60107
60119
  async function readOptional2(path5) {
60108
60120
  try {
60109
60121
  return (await readTextFile(path5)).trim() || void 0;
60110
- } catch {
60111
- return void 0;
60122
+ } catch (error) {
60123
+ if (error.code === "ENOENT") return void 0;
60124
+ throw new Error(
60125
+ `Cannot read BlockRun API key file ${path5}; refusing to fall back to another account or wallet.`,
60126
+ { cause: error }
60127
+ );
60112
60128
  }
60113
60129
  }
60114
60130
  async function resolveApiKey() {
60115
60131
  const envKey = process["env"].BLOCKRUN_API_KEY?.trim();
60116
60132
  if (envKey) {
60117
60133
  if (isValidApiKey(envKey)) return { key: envKey, source: "env" };
60118
- console.warn(
60119
- `[ClawRouter] \u26A0 BLOCKRUN_API_KEY is set but does not look like a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
60134
+ throw new Error(
60135
+ `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.`
60120
60136
  );
60121
60137
  }
60122
60138
  for (const [path5, source] of [
@@ -60126,8 +60142,8 @@ async function resolveApiKey() {
60126
60142
  const stored = await readOptional2(path5);
60127
60143
  if (stored === void 0) continue;
60128
60144
  if (isValidApiKey(stored)) return { key: stored, source };
60129
- console.warn(
60130
- `[ClawRouter] \u26A0 ${path5} does not contain a BlockRun key (expected brk_\u2026) \u2014 ignoring.`
60145
+ throw new Error(
60146
+ `${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.`
60131
60147
  );
60132
60148
  }
60133
60149
  return void 0;
@@ -60152,16 +60168,129 @@ async function clearApiKey() {
60152
60168
  }
60153
60169
  return { removed, envStillSet: Boolean(process["env"].BLOCKRUN_API_KEY?.trim()) };
60154
60170
  }
60155
- function createApiKeyFetch(apiKey, baseFetch = fetch) {
60171
+ async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
60172
+ try {
60173
+ const controller = new AbortController();
60174
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
60175
+ const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
60176
+ headers: { authorization: `Bearer ${apiKey}` },
60177
+ signal: controller.signal
60178
+ });
60179
+ clearTimeout(timer2);
60180
+ if (!res.ok) return void 0;
60181
+ const b = await res.json();
60182
+ const num2 = (v) => typeof v === "number" ? v : null;
60183
+ return {
60184
+ accountId: typeof b.account_id === "string" ? b.account_id : void 0,
60185
+ billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
60186
+ currency: typeof b.currency === "string" ? b.currency : "USD",
60187
+ grantedUsd: num2(b.granted_usd),
60188
+ spentUsd: num2(b.spent_usd),
60189
+ remainingUsd: num2(b.remaining_usd),
60190
+ blocked: b.blocked === true,
60191
+ blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
60192
+ };
60193
+ } catch {
60194
+ return void 0;
60195
+ }
60196
+ }
60197
+ function formatCreditBalance(b) {
60198
+ const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
60199
+ const remaining = money(b.remainingUsd);
60200
+ const spent = money(b.spentUsd);
60201
+ if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
60202
+ const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
60203
+ return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
60204
+ }
60205
+ async function fetchUsagePage(apiKey, opts = {}) {
60206
+ const params = new URLSearchParams();
60207
+ if (opts.from) params.set("from", opts.from);
60208
+ if (opts.to) params.set("to", opts.to);
60209
+ if (opts.limit) params.set("limit", String(opts.limit));
60210
+ if (opts.cursor) params.set("cursor", opts.cursor);
60211
+ const query = params.toString();
60212
+ try {
60213
+ const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/usage${query ? `?${query}` : ""}`, {
60214
+ headers: { authorization: `Bearer ${apiKey}` },
60215
+ signal: AbortSignal.timeout(3e4)
60216
+ });
60217
+ if (!res.ok) return void 0;
60218
+ const body = await res.json();
60219
+ return {
60220
+ rows: (body.data ?? []).map((r2) => ({
60221
+ requestId: String(r2.request_id ?? ""),
60222
+ timestamp: String(r2.timestamp ?? ""),
60223
+ endpoint: String(r2.endpoint ?? ""),
60224
+ model: typeof r2.model === "string" ? r2.model : null,
60225
+ kind: String(r2.kind ?? ""),
60226
+ inputTokens: typeof r2.input_tokens === "number" ? r2.input_tokens : 0,
60227
+ outputTokens: typeof r2.output_tokens === "number" ? r2.output_tokens : 0,
60228
+ costUsd: typeof r2.cost_usd === "number" ? r2.cost_usd : null,
60229
+ costState: String(r2.cost_state ?? ""),
60230
+ status: typeof r2.status === "number" ? r2.status : 0
60231
+ })),
60232
+ nextCursor: body.next_cursor ?? null,
60233
+ unavailableDays: body.unavailable_days ?? []
60234
+ };
60235
+ } catch {
60236
+ return void 0;
60237
+ }
60238
+ }
60239
+ function normalizeApiKeyBase(raw) {
60240
+ const base4 = raw.replace(/\/+$/, "").replace(/\/v1$/, "");
60241
+ const url2 = new URL(base4);
60242
+ if (url2.username || url2.password || url2.search || url2.hash || url2.protocol !== "https:" && !(url2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url2.hostname))) {
60243
+ throw new Error(
60244
+ "BlockRun account API URL requires HTTPS (except localhost) and no credentials, query or fragment."
60245
+ );
60246
+ }
60247
+ return base4;
60248
+ }
60249
+ function createApiKeyFetch(apiKey, baseFetch = fetch, apiBase = BLOCKRUN_API_KEY_API) {
60250
+ const base4 = new URL(normalizeApiKeyBase(apiBase));
60156
60251
  return async (input, init2) => {
60157
- const headers = new Headers(init2?.headers);
60252
+ const request2 = input instanceof Request ? input : void 0;
60253
+ const url2 = new URL(request2?.url ?? String(input), `${base4}/`);
60254
+ if (url2.origin !== base4.origin || url2.username || url2.password) {
60255
+ throw new Error("Refusing to forward a BlockRun account key to another origin.");
60256
+ }
60257
+ if (base4.pathname === "/" && url2.pathname.startsWith("/api/v1/")) {
60258
+ url2.pathname = url2.pathname.slice(4);
60259
+ }
60260
+ const headers = new Headers(init2?.headers ?? request2?.headers);
60261
+ for (const name of [...headers.keys()]) {
60262
+ if (/payment/i.test(name) || name.toLowerCase() === "x-api-key") headers.delete(name);
60263
+ }
60158
60264
  headers.set("authorization", `Bearer ${apiKey}`);
60159
- headers.delete("x-payment");
60160
- headers.delete("x-api-key");
60161
- const response = await baseFetch(input, { ...init2, headers });
60265
+ const response = await baseFetch(request2 ? new Request(url2, request2) : url2.href, {
60266
+ ...init2,
60267
+ headers,
60268
+ redirect: "error"
60269
+ });
60162
60270
  return response.ok ? response : explainApiKeyFailure(response);
60163
60271
  };
60164
60272
  }
60273
+ async function pollApiKeyJob(response, payFetch, apiBase, signal, intervalMs = 2e3) {
60274
+ if (response.status !== 202) return response;
60275
+ const initial = await response.clone().json();
60276
+ if (!initial.poll_url) throw new Error("Async account response missing poll_url");
60277
+ const pollUrl = new URL(initial.poll_url, `${normalizeApiKeyBase(apiBase)}/`).href;
60278
+ const abort = AbortSignal.any([signal, AbortSignal.timeout(15 * 6e4)]);
60279
+ while (!abort.aborted) {
60280
+ const polled = await payFetch(pollUrl, { signal: abort });
60281
+ if ([502, 503, 504, 522, 524].includes(polled.status)) {
60282
+ await polled.body?.cancel();
60283
+ } else {
60284
+ if (!polled.ok) return polled;
60285
+ const data = await polled.clone().json();
60286
+ if (data.status === "completed") return polled;
60287
+ if (data.status === "failed") return polled;
60288
+ await polled.body?.cancel();
60289
+ }
60290
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
60291
+ }
60292
+ throw new Error("Async account job did not complete before the client aborted or timed out.");
60293
+ }
60165
60294
  async function explainApiKeyFailure(response) {
60166
60295
  const hint = HINTS[response.status];
60167
60296
  if (!hint) return response;
@@ -60195,7 +60324,7 @@ var init_api_key = __esm({
60195
60324
  PORTAL_URL = "https://user.blockrun.ai";
60196
60325
  PORTAL_KEYS_URL = `${PORTAL_URL}/dashboard/keys`;
60197
60326
  PORTAL_CREDITS_URL = `${PORTAL_URL}/dashboard/credits`;
60198
- BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "") || "https://api.blockrun.ai";
60327
+ BLOCKRUN_API_KEY_API = process["env"].BLOCKRUN_API_BASE_URL?.replace(/\/+$/, "").replace(/\/v1$/, "") || "https://api.blockrun.ai";
60199
60328
  HINTS = {
60200
60329
  401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
60201
60330
  402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
@@ -69341,7 +69470,7 @@ var require_client_h2 = __commonJS({
69341
69470
  "node_modules/undici/lib/dispatcher/client-h2.js"(exports, module) {
69342
69471
  "use strict";
69343
69472
  var assert10 = __require("assert");
69344
- var { pipeline } = __require("stream");
69473
+ var { pipeline: pipeline2 } = __require("stream");
69345
69474
  var util5 = require_util();
69346
69475
  var {
69347
69476
  RequestContentLengthMismatchError,
@@ -70488,7 +70617,7 @@ var require_client_h2 = __commonJS({
70488
70617
  }
70489
70618
  function writeStream(abort, socket, expectsPayload, h2stream, body, client, request2, contentLength) {
70490
70619
  assert10(contentLength !== 0 || client[kRunning] === 0, "stream body cannot be pipelined");
70491
- const pipe2 = pipeline(
70620
+ const pipe2 = pipeline2(
70492
70621
  body,
70493
70622
  h2stream,
70494
70623
  (err) => {
@@ -73643,7 +73772,7 @@ var require_readable = __commonJS({
73643
73772
  "use strict";
73644
73773
  var assert10 = __require("assert");
73645
73774
  var { addAbortListener } = __require("events");
73646
- var { Readable: Readable2 } = __require("stream");
73775
+ var { Readable: Readable3 } = __require("stream");
73647
73776
  var { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = require_errors();
73648
73777
  var util5 = require_util();
73649
73778
  var { ReadableStreamFrom } = require_util();
@@ -73657,7 +73786,7 @@ var require_readable = __commonJS({
73657
73786
  var kBytesRead = /* @__PURE__ */ Symbol("kBytesRead");
73658
73787
  var noop2 = () => {
73659
73788
  };
73660
- var BodyReadable = class extends Readable2 {
73789
+ var BodyReadable = class extends Readable3 {
73661
73790
  /**
73662
73791
  * @param {object} opts
73663
73792
  * @param {(this: Readable, size: number) => void} opts.resume
@@ -74056,7 +74185,7 @@ var require_api_request = __commonJS({
74056
74185
  "use strict";
74057
74186
  var assert10 = __require("assert");
74058
74187
  var { AsyncResource } = __require("async_hooks");
74059
- var { Readable: Readable2 } = require_readable();
74188
+ var { Readable: Readable3 } = require_readable();
74060
74189
  var { InvalidArgumentError, RequestAbortedError } = require_errors();
74061
74190
  var util5 = require_util();
74062
74191
  function noop2() {
@@ -74142,7 +74271,7 @@ var require_api_request = __commonJS({
74142
74271
  const parsedHeaders = headers;
74143
74272
  const contentType = parsedHeaders?.["content-type"];
74144
74273
  const contentLength = parsedHeaders?.["content-length"];
74145
- const res = new Readable2({
74274
+ const res = new Readable3({
74146
74275
  resume: () => controller.resume(),
74147
74276
  abort: (reason) => controller.abort(reason),
74148
74277
  contentType,
@@ -74518,7 +74647,7 @@ var require_api_pipeline = __commonJS({
74518
74647
  "node_modules/undici/lib/api/api-pipeline.js"(exports, module) {
74519
74648
  "use strict";
74520
74649
  var {
74521
- Readable: Readable2,
74650
+ Readable: Readable3,
74522
74651
  Duplex,
74523
74652
  PassThrough
74524
74653
  } = __require("stream");
@@ -74535,7 +74664,7 @@ var require_api_pipeline = __commonJS({
74535
74664
  function noop2() {
74536
74665
  }
74537
74666
  var kResume = /* @__PURE__ */ Symbol("resume");
74538
- var PipelineRequest = class extends Readable2 {
74667
+ var PipelineRequest = class extends Readable3 {
74539
74668
  constructor() {
74540
74669
  super({ autoDestroy: true });
74541
74670
  this[kResume] = null;
@@ -74553,7 +74682,7 @@ var require_api_pipeline = __commonJS({
74553
74682
  callback(err);
74554
74683
  }
74555
74684
  };
74556
- var PipelineResponse = class extends Readable2 {
74685
+ var PipelineResponse = class extends Readable3 {
74557
74686
  constructor(resume2) {
74558
74687
  super({ autoDestroy: true });
74559
74688
  this[kResume] = resume2;
@@ -74707,7 +74836,7 @@ var require_api_pipeline = __commonJS({
74707
74836
  util5.destroy(ret, err);
74708
74837
  }
74709
74838
  };
74710
- function pipeline(opts, handler) {
74839
+ function pipeline2(opts, handler) {
74711
74840
  try {
74712
74841
  const pipelineHandler = new PipelineHandler(opts, handler);
74713
74842
  this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler);
@@ -74716,7 +74845,7 @@ var require_api_pipeline = __commonJS({
74716
74845
  return new PassThrough().destroy(err);
74717
74846
  }
74718
74847
  }
74719
- module.exports = pipeline;
74848
+ module.exports = pipeline2;
74720
74849
  }
74721
74850
  });
74722
74851
 
@@ -76352,7 +76481,7 @@ var require_snapshot_utils = __commonJS({
76352
76481
  var require_snapshot_recorder = __commonJS({
76353
76482
  "node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
76354
76483
  "use strict";
76355
- var { writeFile: writeFile4, readFile: readFile3, mkdir: mkdir6 } = __require("fs/promises");
76484
+ var { writeFile: writeFile4, readFile: readFile4, mkdir: mkdir6 } = __require("fs/promises");
76356
76485
  var { dirname: dirname4, resolve } = __require("path");
76357
76486
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
76358
76487
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -76569,7 +76698,7 @@ var require_snapshot_recorder = __commonJS({
76569
76698
  throw new InvalidArgumentError("Snapshot path is required");
76570
76699
  }
76571
76700
  try {
76572
- const data = await readFile3(resolve(path5), "utf8");
76701
+ const data = await readFile4(resolve(path5), "utf8");
76573
76702
  const parsed = JSON.parse(data);
76574
76703
  if (Array.isArray(parsed)) {
76575
76704
  this.#snapshots.clear();
@@ -79834,7 +79963,7 @@ var require_cache2 = __commonJS({
79834
79963
  "node_modules/undici/lib/interceptor/cache.js"(exports, module) {
79835
79964
  "use strict";
79836
79965
  var assert10 = __require("assert");
79837
- var { Readable: Readable2 } = __require("stream");
79966
+ var { Readable: Readable3 } = __require("stream");
79838
79967
  var util5 = require_util();
79839
79968
  var CacheHandler = require_cache_handler();
79840
79969
  var MemoryCacheStore = require_memory_cache_store();
@@ -80010,7 +80139,7 @@ var require_cache2 = __commonJS({
80010
80139
  return dispatch(opts, new CacheHandler(globalOpts, cacheKey2, handler));
80011
80140
  }
80012
80141
  function sendCachedValue(handler, opts, result, age, context, isStale2) {
80013
- const stream4 = util5.isStream(result.body) ? result.body : Readable2.from(result.body ?? []);
80142
+ const stream4 = util5.isStream(result.body) ? result.body : Readable3.from(result.body ?? []);
80014
80143
  assert10(!stream4.destroyed, "stream should not be destroyed");
80015
80144
  assert10(!stream4.readableDidRead, "stream should not be readableDidRead");
80016
80145
  const controller = {
@@ -80255,7 +80384,7 @@ var require_decompress = __commonJS({
80255
80384
  "node_modules/undici/lib/interceptor/decompress.js"(exports, module) {
80256
80385
  "use strict";
80257
80386
  var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("zlib");
80258
- var { pipeline } = __require("stream");
80387
+ var { pipeline: pipeline2 } = __require("stream");
80259
80388
  var DecoratorHandler = require_decorator_handler();
80260
80389
  var supportedEncodings = {
80261
80390
  gzip: createGunzip,
@@ -80365,7 +80494,7 @@ var require_decompress = __commonJS({
80365
80494
  #setupMultipleDecompressors(controller) {
80366
80495
  const lastDecompressor = this.#decompressors[this.#decompressors.length - 1];
80367
80496
  this.#setupDecompressorEvents(lastDecompressor, controller);
80368
- pipeline(this.#decompressors, (err) => {
80497
+ pipeline2(this.#decompressors, (err) => {
80369
80498
  if (err) {
80370
80499
  super.onResponseError(controller, err);
80371
80500
  return;
@@ -83159,7 +83288,7 @@ var require_fetch = __commonJS({
83159
83288
  subresourceSet
83160
83289
  } = require_constants4();
83161
83290
  var EE = __require("events");
83162
- var { Readable: Readable2, pipeline, finished: finished2, isErrored, isReadable } = __require("stream");
83291
+ var { Readable: Readable3, pipeline: pipeline2, finished: finished2, isErrored, isReadable } = __require("stream");
83163
83292
  var { addAbortListener, bufferToLowerCasedHeaderName } = require_util();
83164
83293
  var { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require_data_url();
83165
83294
  var { getGlobalDispatcher } = require_global2();
@@ -84135,7 +84264,7 @@ var require_fetch = __commonJS({
84135
84264
  const headersList = new HeadersList();
84136
84265
  appendHeadersListFromResponseHeaders(headersList, headers, rawHeaders);
84137
84266
  const location = headersList.get("location", true);
84138
- this.body = new Readable2({ read: () => controller.resume() });
84267
+ this.body = new Readable3({ read: () => controller.resume() });
84139
84268
  const willFollow = location && request2.redirect === "follow" && redirectStatusSet.has(status);
84140
84269
  const decoders = [];
84141
84270
  if (request2.method !== "HEAD" && request2.method !== "CONNECT" && !nullBodyStatus.includes(status) && !willFollow) {
@@ -84183,7 +84312,7 @@ var require_fetch = __commonJS({
84183
84312
  status,
84184
84313
  statusText,
84185
84314
  headersList,
84186
- body: decoders.length ? pipeline(this.body, ...decoders, (err) => {
84315
+ body: decoders.length ? pipeline2(this.body, ...decoders, (err) => {
84187
84316
  if (err) {
84188
84317
  this.onResponseError(controller, err);
84189
84318
  }
@@ -87995,7 +88124,7 @@ ${value}`;
87995
88124
  var require_eventsource = __commonJS({
87996
88125
  "node_modules/undici/lib/web/eventsource/eventsource.js"(exports, module) {
87997
88126
  "use strict";
87998
- var { pipeline } = __require("stream");
88127
+ var { pipeline: pipeline2 } = __require("stream");
87999
88128
  var { fetching } = require_fetch();
88000
88129
  var { webidl } = require_webidl();
88001
88130
  var { EventSourceStream } = require_eventsource_stream();
@@ -88145,7 +88274,7 @@ var require_eventsource = __commonJS({
88145
88274
  ));
88146
88275
  }
88147
88276
  });
88148
- pipeline(
88277
+ pipeline2(
88149
88278
  response.body.stream,
88150
88279
  eventSourceStream,
88151
88280
  (error) => {
@@ -90673,11 +90802,30 @@ var init_client3 = __esm({
90673
90802
  }
90674
90803
  });
90675
90804
 
90805
+ // node_modules/@x402/svm/dist/esm/v1/index.mjs
90806
+ var v1_exports = {};
90807
+ __export(v1_exports, {
90808
+ ExactSvmSchemeV1: () => ExactSvmSchemeV1,
90809
+ NETWORKS: () => NETWORKS2
90810
+ });
90811
+ var init_v1 = __esm({
90812
+ "node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
90813
+ "use strict";
90814
+ init_chunk_WWACQNRQ();
90815
+ init_chunk_MNNIECS4();
90816
+ init_chunk_WIPN332D();
90817
+ init_chunk_FVTMOTG6();
90818
+ init_chunk_FJ6REVHH();
90819
+ init_chunk_QEBTC3LJ();
90820
+ }
90821
+ });
90822
+
90676
90823
  // src/proxy.ts
90677
90824
  import { AsyncLocalStorage } from "async_hooks";
90678
90825
  import { createHmac } from "crypto";
90679
90826
  import { createServer } from "http";
90680
- import { finished } from "stream";
90827
+ import { finished, Readable } from "stream";
90828
+ import { pipeline } from "stream/promises";
90681
90829
  import { homedir as homedir8 } from "os";
90682
90830
  import { join as join11 } from "path";
90683
90831
  import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile2, stat as fsStat } from "fs/promises";
@@ -90686,10 +90834,10 @@ async function loadGatewayCatalog(apiBase, apiKey) {
90686
90834
  try {
90687
90835
  const controller = new AbortController();
90688
90836
  const timer2 = setTimeout(() => controller.abort(), GATEWAY_CATALOG_TIMEOUT_MS);
90689
- const res = await fetch(`${apiBase}/v1/models`, {
90690
- signal: controller.signal,
90691
- ...apiKey ? { headers: { authorization: `Bearer ${apiKey}` } } : {}
90692
- });
90837
+ const res = await (apiKey ? createApiKeyFetch(apiKey, fetch, apiBase) : fetch)(
90838
+ `${apiBase}/v1/models`,
90839
+ { signal: controller.signal }
90840
+ );
90693
90841
  clearTimeout(timer2);
90694
90842
  if (!res.ok) return;
90695
90843
  const body = await res.json();
@@ -91427,7 +91575,57 @@ function estimateImageCost(model, size5, n = 1) {
91427
91575
  const pricePerImage = sizePrice ?? pricing.default;
91428
91576
  return pricePerImage * n * 1.05;
91429
91577
  }
91430
- async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
91578
+ function blockrunRequestId(response) {
91579
+ if (!response) return void 0;
91580
+ for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
91581
+ const value = response.headers.get(name)?.trim();
91582
+ if (value) return value;
91583
+ }
91584
+ return void 0;
91585
+ }
91586
+ function gatewayRemainingCreditUsd(response) {
91587
+ const raw = response?.headers.get("x-blockrun-credit-remaining-usd");
91588
+ if (raw === null || raw === void 0) return void 0;
91589
+ const trimmed = raw.trim();
91590
+ if (trimmed === "") return void 0;
91591
+ const value = Number(trimmed);
91592
+ if (!Number.isFinite(value) || value < 0) return void 0;
91593
+ return value;
91594
+ }
91595
+ function settledMediaCostUsd(upstream, body, x402AmountUsd, estimate) {
91596
+ const header = gatewaySettledCostUsd(upstream);
91597
+ if (header !== void 0) return header;
91598
+ const price = body?.price?.amount;
91599
+ if (typeof price === "string" || typeof price === "number") {
91600
+ const parsed = Number(price);
91601
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
91602
+ }
91603
+ if (typeof x402AmountUsd === "number" && x402AmountUsd > 0) return x402AmountUsd;
91604
+ return estimate;
91605
+ }
91606
+ function noteRemainingCredit(response) {
91607
+ const remaining = gatewayRemainingCreditUsd(response);
91608
+ if (remaining === void 0) return;
91609
+ if (remaining > LOW_CREDIT_USD) {
91610
+ lowCreditWarned = false;
91611
+ return;
91612
+ }
91613
+ if (lowCreditWarned) return;
91614
+ lowCreditWarned = true;
91615
+ console.warn(
91616
+ 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}`
91617
+ );
91618
+ }
91619
+ function gatewaySettledCostUsd(response) {
91620
+ const raw = response?.headers.get("x-blockrun-cost-usd");
91621
+ if (raw === null || raw === void 0) return void 0;
91622
+ const trimmed = raw.trim();
91623
+ if (trimmed === "") return void 0;
91624
+ const value = Number(trimmed);
91625
+ if (!Number.isFinite(value) || value < 0) return void 0;
91626
+ return value;
91627
+ }
91628
+ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd, accountPassthrough = false, authMode = "wallet") {
91431
91629
  const startTime = Date.now();
91432
91630
  const upstreamUrl = `${apiBase}${req.url}`;
91433
91631
  const isBlockrunExa = req.url?.startsWith("/v1/exa/") ?? false;
@@ -91466,18 +91664,35 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91466
91664
  return;
91467
91665
  responseHeaders[key2] = value;
91468
91666
  });
91667
+ if (accountPassthrough || authMode === "api-key") {
91668
+ responseHeaders["cache-control"] = "no-store";
91669
+ }
91469
91670
  res.writeHead(upstream.status, responseHeaders);
91470
- if (upstream.body) {
91471
- const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91472
- for (const chunk of chunks) {
91473
- safeWrite(res, Buffer.from(chunk));
91671
+ if (accountPassthrough) {
91672
+ if (upstream.body) {
91673
+ await pipeline(
91674
+ Readable.fromWeb(upstream.body),
91675
+ res,
91676
+ { signal: clientAbort.signal }
91677
+ );
91678
+ } else {
91679
+ res.end();
91474
91680
  }
91681
+ } else {
91682
+ if (upstream.body) {
91683
+ const chunks = await readBodyWithTimeout(upstream.body, ERROR_BODY_READ_TIMEOUT_MS);
91684
+ for (const chunk of chunks) {
91685
+ safeWrite(res, Buffer.from(chunk));
91686
+ }
91687
+ }
91688
+ res.end();
91475
91689
  }
91476
- res.end();
91477
91690
  const latencyMs = Date.now() - startTime;
91478
91691
  console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
91479
- const paidAmount = getActualPaymentUsd();
91480
- const requestCost = resolvePhoneTelemetryCost({
91692
+ noteRemainingCredit(upstream);
91693
+ const settledCostUsd = gatewaySettledCostUsd(upstream);
91694
+ const paidAmount = settledCostUsd ?? getActualPaymentUsd();
91695
+ const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
91481
91696
  paidAmount,
91482
91697
  isPhone,
91483
91698
  upstreamStatus: upstream.status,
@@ -91486,6 +91701,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91486
91701
  });
91487
91702
  logUsage({
91488
91703
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91704
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
91489
91705
  model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
91490
91706
  tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
91491
91707
  cost: requestCost,
@@ -91577,7 +91793,8 @@ async function startProxy(options) {
91577
91793
  const walletKey = options.wallet === void 0 ? void 0 : typeof options.wallet === "string" ? options.wallet : options.wallet.key;
91578
91794
  const solanaPrivateKeyBytes = options.wallet === void 0 || typeof options.wallet === "string" ? void 0 : options.wallet.solanaPrivateKeyBytes;
91579
91795
  const paymentChain = options.paymentChain ?? await resolvePaymentChain();
91580
- const apiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91796
+ const requestedApiBase = options.apiBase ?? (authMode === "api-key" ? BLOCKRUN_API_KEY_API : paymentChain === "solana" && solanaPrivateKeyBytes ? BLOCKRUN_SOLANA_API : BLOCKRUN_API);
91797
+ const apiBase = authMode === "api-key" ? normalizeApiKeyBase(requestedApiBase) : requestedApiBase;
91581
91798
  if (authMode === "api-key") {
91582
91799
  console.log(`[ClawRouter] Auth: BlockRun API key ${maskApiKey(apiKey)} (${apiBase})`);
91583
91800
  console.log(`[ClawRouter] Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
@@ -91677,8 +91894,20 @@ async function startProxy(options) {
91677
91894
  const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
91678
91895
  const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
91679
91896
  solanaAddress = solanaSigner.address;
91897
+ const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
91680
91898
  registerExactSvmScheme2(x402, { signer: solanaSigner });
91899
+ if (solanaRpcUrl) {
91900
+ const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
91901
+ const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
91902
+ x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
91903
+ for (const network of SVM_V1_NETWORKS) {
91904
+ x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
91905
+ }
91906
+ }
91681
91907
  console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
91908
+ if (solanaRpcUrl) {
91909
+ console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
91910
+ }
91682
91911
  }
91683
91912
  x402?.onAfterPaymentCreation(async (context) => {
91684
91913
  if (!context.selectedRequirements.network.startsWith("eip155")) return;
@@ -91694,7 +91923,7 @@ async function startProxy(options) {
91694
91923
  if (store) store.amountUsd = amountUsd;
91695
91924
  console.log(`[ClawRouter] Payment signed on ${chain3} (${network}) \u2014 $${amountUsd.toFixed(6)}`);
91696
91925
  });
91697
- const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91926
+ const payFetch = authMode === "api-key" ? createApiKeyFetch(apiKey, fetch, apiBase) : createPayFetchWithPreAuth(fetch, x402, void 0, {
91698
91927
  skipPreAuth: paymentChain === "solana",
91699
91928
  // Per-request cost estimate so pre-auth is only reused when the cached
91700
91929
  // payment still covers the (possibly larger) request — BlockRun prices per
@@ -91778,7 +92007,15 @@ async function startProxy(options) {
91778
92007
  )
91779
92008
  ]);
91780
92009
  if (authMode === "api-key") {
91781
- response.balance = null;
92010
+ const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
92011
+ response.balance = credit?.remainingUsd ?? null;
92012
+ response.creditSpent = credit?.spentUsd ?? null;
92013
+ response.creditGranted = credit?.grantedUsd ?? null;
92014
+ response.billingMode = credit?.billingMode ?? null;
92015
+ response.creditSummary = credit ? formatCreditBalance(credit) : null;
92016
+ response.blocked = credit?.blocked ?? false;
92017
+ if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
92018
+ response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
91782
92019
  response.billing = "BlockRun account credit";
91783
92020
  response.topUpUrl = PORTAL_CREDITS_URL;
91784
92021
  } else {
@@ -92190,9 +92427,15 @@ async function startProxy(options) {
92190
92427
  }
92191
92428
  }
92192
92429
  }
92193
- const imgActualCost = paymentStore.getStore()?.amountUsd ?? imgCost;
92430
+ const imgActualCost = settledMediaCostUsd(
92431
+ upstream,
92432
+ result,
92433
+ paymentStore.getStore()?.amountUsd,
92434
+ imgCost
92435
+ );
92194
92436
  logUsage({
92195
92437
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92438
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92196
92439
  model: imgModel,
92197
92440
  tier: "IMAGE",
92198
92441
  cost: imgActualCost,
@@ -92261,12 +92504,15 @@ async function startProxy(options) {
92261
92504
  return;
92262
92505
  }
92263
92506
  try {
92264
- const upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92507
+ let upstream = await payFetch(`${apiBase}/v1/images/image2image`, {
92265
92508
  method: "POST",
92266
92509
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92267
92510
  body: reqBody,
92268
92511
  signal: clientAbort.signal
92269
92512
  });
92513
+ if (authMode === "api-key") {
92514
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92515
+ }
92270
92516
  const text = await upstream.text();
92271
92517
  if (!upstream.ok) {
92272
92518
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92313,9 +92559,15 @@ async function startProxy(options) {
92313
92559
  }
92314
92560
  }
92315
92561
  }
92316
- const img2imgActualCost = paymentStore.getStore()?.amountUsd ?? img2imgCost;
92562
+ const img2imgActualCost = settledMediaCostUsd(
92563
+ upstream,
92564
+ result,
92565
+ paymentStore.getStore()?.amountUsd,
92566
+ img2imgCost
92567
+ );
92317
92568
  logUsage({
92318
92569
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92570
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92319
92571
  model: img2imgModel,
92320
92572
  tier: "IMAGE",
92321
92573
  cost: img2imgActualCost,
@@ -92355,12 +92607,15 @@ async function startProxy(options) {
92355
92607
  } catch {
92356
92608
  }
92357
92609
  try {
92358
- const upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92610
+ let upstream = await payFetch(`${apiBase}/v1/audio/generations`, {
92359
92611
  method: "POST",
92360
92612
  headers: { "content-type": "application/json", "user-agent": USER_AGENT },
92361
92613
  body: reqBody,
92362
92614
  signal: clientAbort.signal
92363
92615
  });
92616
+ if (authMode === "api-key") {
92617
+ upstream = await pollApiKeyJob(upstream, payFetch, apiBase, clientAbort.signal);
92618
+ }
92364
92619
  const text = await upstream.text();
92365
92620
  if (!upstream.ok) {
92366
92621
  res.writeHead(upstream.status, { "Content-Type": "application/json" });
@@ -92399,9 +92654,15 @@ async function startProxy(options) {
92399
92654
  }
92400
92655
  }
92401
92656
  }
92402
- const audioActualCost = paymentStore.getStore()?.amountUsd ?? 0.15;
92657
+ const audioActualCost = settledMediaCostUsd(
92658
+ upstream,
92659
+ result,
92660
+ paymentStore.getStore()?.amountUsd,
92661
+ 0.15
92662
+ );
92403
92663
  logUsage({
92404
92664
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92665
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
92405
92666
  model: audioModel,
92406
92667
  tier: "AUDIO",
92407
92668
  cost: audioActualCost,
@@ -92559,7 +92820,12 @@ async function startProxy(options) {
92559
92820
  }
92560
92821
  }
92561
92822
  }
92562
- const videoActualCost = paymentStore.getStore()?.amountUsd ?? estimateVideoCost(videoModel, videoDuration, videoHasImageInput);
92823
+ const videoActualCost = settledMediaCostUsd(
92824
+ void 0,
92825
+ submitResult,
92826
+ paymentStore.getStore()?.amountUsd,
92827
+ estimateVideoCost(videoModel, videoDuration, videoHasImageInput)
92828
+ );
92563
92829
  logUsage({
92564
92830
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
92565
92831
  model: videoModel,
@@ -92593,7 +92859,9 @@ async function startProxy(options) {
92593
92859
  res,
92594
92860
  apiBase,
92595
92861
  payFetch,
92596
- () => paymentStore.getStore()?.amountUsd ?? 0
92862
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92863
+ false,
92864
+ authMode
92597
92865
  );
92598
92866
  } catch (err) {
92599
92867
  const error = err instanceof Error ? err : new Error(String(err));
@@ -92616,6 +92884,33 @@ async function startProxy(options) {
92616
92884
  res.end(JSON.stringify({ error: "Not found" }));
92617
92885
  return;
92618
92886
  }
92887
+ if (authMode === "api-key" && /^\/v1\//.test(req.url ?? "") && !/^\/v1\/(?:chat\/completions|messages)(?:\?|$)/.test(req.url ?? "")) {
92888
+ try {
92889
+ await proxyPaidApiRequest(
92890
+ req,
92891
+ res,
92892
+ apiBase,
92893
+ payFetch,
92894
+ () => paymentStore.getStore()?.amountUsd ?? 0,
92895
+ true,
92896
+ authMode
92897
+ );
92898
+ } catch (err) {
92899
+ const error = err instanceof Error ? err : new Error(String(err));
92900
+ options.onError?.(error);
92901
+ if (!res.headersSent) {
92902
+ res.writeHead(502, { "Content-Type": "application/json" });
92903
+ res.end(
92904
+ JSON.stringify({
92905
+ error: { message: `Account API error: ${error.message}`, type: "account_error" }
92906
+ })
92907
+ );
92908
+ } else if (!res.writableEnded) {
92909
+ res.end();
92910
+ }
92911
+ }
92912
+ return;
92913
+ }
92619
92914
  if (!req.url.includes("/chat/completions")) {
92620
92915
  res.writeHead(404, { "Content-Type": "application/json" });
92621
92916
  res.end(
@@ -92883,7 +93178,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
92883
93178
  errorBody,
92884
93179
  errorStatus: response.status,
92885
93180
  isProviderError: category !== null,
92886
- errorCategory: category ?? void 0
93181
+ errorCategory: category ?? void 0,
93182
+ upstreamRequestId: blockrunRequestId(response)
92887
93183
  };
92888
93184
  }
92889
93185
  const contentType = response.headers.get("content-type") || "";
@@ -92920,9 +93216,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
92920
93216
  errorCategory: "payment_error"
92921
93217
  };
92922
93218
  }
93219
+ const networkMsg = describeFetchError(err);
93220
+ 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" : "";
93221
+ console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
92923
93222
  return {
92924
93223
  success: false,
92925
- errorBody: errorMsg,
93224
+ errorBody: `${networkMsg}${solanaSigningHint}`,
92926
93225
  errorStatus: 500,
92927
93226
  isProviderError: true
92928
93227
  // Network errors are retryable
@@ -92955,6 +93254,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
92955
93254
  let accumulatedContent = "";
92956
93255
  let responseInputTokens;
92957
93256
  let responseOutputTokens;
93257
+ let upstreamRequestId;
92958
93258
  let requestHadError = false;
92959
93259
  let requestSummaryForStore = "";
92960
93260
  const isChatCompletion = req.url?.includes("/chat/completions");
@@ -93294,9 +93594,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93294
93594
  responseText = lines.join("\n");
93295
93595
  }
93296
93596
  console.log(`[ClawRouter] /imagegen success: ${images.length} image(s) generated`);
93297
- const imagegenActualCost = paymentStore.getStore()?.amountUsd ?? estimateImageCost(imageModel, imageSize, 1);
93597
+ const imagegenActualCost = settledMediaCostUsd(
93598
+ imageResponse,
93599
+ imageResult,
93600
+ paymentStore.getStore()?.amountUsd,
93601
+ estimateImageCost(imageModel, imageSize, 1)
93602
+ );
93298
93603
  logUsage({
93299
93604
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93605
+ ...blockrunRequestId(imageResponse) ? { requestId: blockrunRequestId(imageResponse) } : {},
93300
93606
  model: imageModel,
93301
93607
  tier: "IMAGE",
93302
93608
  cost: imagegenActualCost,
@@ -93520,9 +93826,15 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
93520
93826
  responseText = lines.join("\n");
93521
93827
  }
93522
93828
  console.log(`[ClawRouter] /img2img success: ${images.length} image(s)`);
93523
- const img2imgActualCost2 = paymentStore.getStore()?.amountUsd ?? estimateImageCost(img2imgModel, img2imgSize, 1);
93829
+ const img2imgActualCost2 = settledMediaCostUsd(
93830
+ img2imgResponse,
93831
+ img2imgResult,
93832
+ paymentStore.getStore()?.amountUsd,
93833
+ estimateImageCost(img2imgModel, img2imgSize, 1)
93834
+ );
93524
93835
  logUsage({
93525
93836
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
93837
+ ...blockrunRequestId(img2imgResponse) ? { requestId: blockrunRequestId(img2imgResponse) } : {},
93526
93838
  model: img2imgModel,
93527
93839
  tier: "IMAGE",
93528
93840
  cost: img2imgActualCost2,
@@ -94361,7 +94673,8 @@ data: [DONE]
94361
94673
  }
94362
94674
  lastError = {
94363
94675
  body: result.errorBody || "Unknown error",
94364
- status: result.errorStatus || 500
94676
+ status: result.errorStatus || 500,
94677
+ requestId: result.upstreamRequestId
94365
94678
  };
94366
94679
  failedAttempts.push({
94367
94680
  model: tryModel,
@@ -94433,7 +94746,8 @@ data: [DONE]
94433
94746
  }
94434
94747
  lastError = {
94435
94748
  body: retryResult.errorBody || lastError?.body || "Unknown error",
94436
- status: retryResult.errorStatus || lastError?.status || 500
94749
+ status: retryResult.errorStatus || lastError?.status || 500,
94750
+ requestId: retryResult.upstreamRequestId ?? lastError?.requestId
94437
94751
  };
94438
94752
  failedAttempts.push({
94439
94753
  model: tryModel,
@@ -94575,10 +94889,14 @@ data: [DONE]
94575
94889
  }
94576
94890
  }
94577
94891
  }
94892
+ upstreamRequestId = blockrunRequestId(upstream);
94893
+ noteRemainingCredit(upstream);
94578
94894
  if (!upstream) {
94579
94895
  const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
94580
94896
  const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
94581
- console.log(`[ClawRouter] ${structuredMessage}`);
94897
+ console.log(
94898
+ `[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
94899
+ );
94582
94900
  const rawErrBody = lastError?.body || structuredMessage;
94583
94901
  const errStatus = lastError?.status || 502;
94584
94902
  const transformedErr = transformPaymentError(rawErrBody);
@@ -94616,7 +94934,8 @@ data: [DONE]
94616
94934
  res.writeHead(errStatus, {
94617
94935
  "Content-Type": "application/json",
94618
94936
  "x-context-used-kb": String(originalContextSizeKB),
94619
- "x-context-limit-kb": String(CONTEXT_LIMIT_KB)
94937
+ "x-context-limit-kb": String(CONTEXT_LIMIT_KB),
94938
+ ...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
94620
94939
  });
94621
94940
  res.end(transformedErr);
94622
94941
  deduplicator.complete(dedupKey, {
@@ -95080,12 +95399,24 @@ data: [DONE]
95080
95399
  maxTokens,
95081
95400
  routingProfile ?? void 0
95082
95401
  );
95083
- logCost = FREE_MODELS.has(logModel) ? 0 : costs.costEstimate;
95402
+ const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
95403
+ if (FREE_MODELS.has(logModel)) {
95404
+ logCost = 0;
95405
+ logSavings = 1;
95406
+ } else if (apiKeyPricing) {
95407
+ const inTokens = responseInputTokens ?? chargedInputTokens;
95408
+ const outTokens = responseOutputTokens ?? 0;
95409
+ logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
95410
+ logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
95411
+ } else {
95412
+ logCost = costs.costEstimate;
95413
+ logSavings = costs.savings;
95414
+ }
95084
95415
  logBaseline = costs.baselineCost;
95085
- logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
95086
95416
  }
95087
95417
  const entry = {
95088
95418
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
95419
+ ...upstreamRequestId ? { requestId: upstreamRequestId } : {},
95089
95420
  model: logModel,
95090
95421
  tier: routingDecision?.tier ?? "DIRECT",
95091
95422
  cost: logCost,
@@ -95100,7 +95431,7 @@ data: [DONE]
95100
95431
  });
95101
95432
  }
95102
95433
  }
95103
- 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;
95434
+ 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;
95104
95435
  var init_proxy = __esm({
95105
95436
  "src/proxy.ts"() {
95106
95437
  "use strict";
@@ -95125,6 +95456,7 @@ var init_proxy = __esm({
95125
95456
  init_api_key();
95126
95457
  init_spend_control();
95127
95458
  init_compression();
95459
+ init_errors5();
95128
95460
  init_version4();
95129
95461
  init_session();
95130
95462
  init_updater();
@@ -95361,6 +95693,8 @@ var init_proxy = __esm({
95361
95693
  "phone/numbers/release": 0,
95362
95694
  "voice/call": 0.54
95363
95695
  };
95696
+ LOW_CREDIT_USD = 1;
95697
+ lowCreditWarned = false;
95364
95698
  }
95365
95699
  });
95366
95700
 
@@ -96401,13 +96735,13 @@ ${USAGE}`
96401
96735
  ${USAGE}`);
96402
96736
  }
96403
96737
  if (raw === "clear") return { kind: "limit", window: target, usd: void 0 };
96404
- const usd = Number(raw);
96405
- if (!USD.test(raw) || !Number.isFinite(usd) || usd <= 0) {
96738
+ const usd2 = Number(raw);
96739
+ if (!USD.test(raw) || !Number.isFinite(usd2) || usd2 <= 0) {
96406
96740
  return fail(
96407
96741
  `Rejected amount "${raw}": must be a positive decimal USD value such as 0.10 or 5`
96408
96742
  );
96409
96743
  }
96410
- return { kind: "limit", window: target, usd };
96744
+ return { kind: "limit", window: target, usd: usd2 };
96411
96745
  }
96412
96746
  if (!isListAction(action)) return fail(`Unknown subcommand "${sub}"
96413
96747
  ${USAGE}`);
@@ -96552,6 +96886,194 @@ var init_policy = __esm({
96552
96886
  }
96553
96887
  });
96554
96888
 
96889
+ // src/reconcile.ts
96890
+ var reconcile_exports = {};
96891
+ __export(reconcile_exports, {
96892
+ formatReconcile: () => formatReconcile,
96893
+ joinRows: () => joinRows,
96894
+ loadGatewayRows: () => loadGatewayRows,
96895
+ loadLocalRows: () => loadLocalRows,
96896
+ reconcile: () => reconcile
96897
+ });
96898
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
96899
+ import { join as join12 } from "path";
96900
+ import { homedir as homedir9 } from "os";
96901
+ async function loadLocalRows(since) {
96902
+ let files;
96903
+ try {
96904
+ files = (await readdir3(LOG_DIR3)).filter((f) => f.endsWith(".jsonl"));
96905
+ } catch {
96906
+ return { rows: [], unkeyed: 0 };
96907
+ }
96908
+ const rows = [];
96909
+ let unkeyed = 0;
96910
+ for (const file of files) {
96911
+ let text;
96912
+ try {
96913
+ text = await readFile3(join12(LOG_DIR3, file), "utf8");
96914
+ } catch {
96915
+ continue;
96916
+ }
96917
+ for (const line of text.split("\n")) {
96918
+ if (!line.trim()) continue;
96919
+ let entry;
96920
+ try {
96921
+ entry = JSON.parse(line);
96922
+ } catch {
96923
+ continue;
96924
+ }
96925
+ if (!entry.timestamp || new Date(entry.timestamp) < since) continue;
96926
+ if (!entry.requestId) {
96927
+ unkeyed++;
96928
+ continue;
96929
+ }
96930
+ rows.push({
96931
+ requestId: entry.requestId,
96932
+ model: entry.model,
96933
+ cost: typeof entry.cost === "number" ? entry.cost : 0,
96934
+ timestamp: entry.timestamp
96935
+ });
96936
+ }
96937
+ }
96938
+ return { rows, unkeyed };
96939
+ }
96940
+ async function loadGatewayRows(apiKey, from15, maxPages = 40) {
96941
+ const rows = [];
96942
+ const unavailableDays = /* @__PURE__ */ new Set();
96943
+ let cursor;
96944
+ for (let page = 0; page < maxPages; page++) {
96945
+ const result = await fetchUsagePage(apiKey, { from: from15, limit: 500, cursor });
96946
+ if (!result) return page === 0 ? void 0 : { rows, unavailableDays: [...unavailableDays] };
96947
+ rows.push(...result.rows);
96948
+ result.unavailableDays.forEach((d) => unavailableDays.add(d));
96949
+ if (!result.nextCursor) break;
96950
+ cursor = result.nextCursor;
96951
+ }
96952
+ return { rows, unavailableDays: [...unavailableDays] };
96953
+ }
96954
+ function joinRows(localRows, gatewayRows, unkeyed = 0, unavailableDays = []) {
96955
+ const localById = new Map(localRows.map((r2) => [r2.requestId, r2]));
96956
+ const seen = /* @__PURE__ */ new Set();
96957
+ const matched = [];
96958
+ const chargedNotRecorded = [];
96959
+ const pending = [];
96960
+ let gatewayTotalUsd = 0;
96961
+ for (const remote of gatewayRows) {
96962
+ if (remote.costState === "pending") {
96963
+ pending.push(remote);
96964
+ continue;
96965
+ }
96966
+ gatewayTotalUsd += remote.costUsd ?? 0;
96967
+ const local = localById.get(remote.requestId);
96968
+ if (!local) {
96969
+ chargedNotRecorded.push(remote);
96970
+ continue;
96971
+ }
96972
+ seen.add(remote.requestId);
96973
+ matched.push({ local, remote, deltaUsd: local.cost - (remote.costUsd ?? 0) });
96974
+ }
96975
+ const recordedNotCharged = localRows.filter((r2) => !seen.has(r2.requestId));
96976
+ const localTotalUsd = localRows.reduce((sum, r2) => sum + r2.cost, 0);
96977
+ return {
96978
+ matched,
96979
+ chargedNotRecorded,
96980
+ recordedNotCharged,
96981
+ pending,
96982
+ localTotalUsd,
96983
+ gatewayTotalUsd,
96984
+ unavailableDays,
96985
+ unkeyedLocalCount: unkeyed
96986
+ };
96987
+ }
96988
+ async function reconcile(apiKey, days) {
96989
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
96990
+ const [{ rows: localRows, unkeyed }, gateway] = await Promise.all([
96991
+ loadLocalRows(since),
96992
+ loadGatewayRows(apiKey, since.toISOString())
96993
+ ]);
96994
+ if (!gateway) throw new Error("Could not read the BlockRun usage ledger (GET /v1/usage).");
96995
+ return joinRows(localRows, gateway.rows, unkeyed, gateway.unavailableDays);
96996
+ }
96997
+ function formatReconcile(r2, days) {
96998
+ const out = [];
96999
+ const mismatched = r2.matched.filter((m) => Math.abs(m.deltaUsd) > CENT_EPSILON);
97000
+ out.push(`
97001
+ BlockRun reconciliation \u2014 last ${days} day${days === 1 ? "" : "s"}
97002
+ `);
97003
+ out.push(
97004
+ ` Gateway charged: ${usd(r2.gatewayTotalUsd)} (${r2.matched.length + r2.chargedNotRecorded.length} settled calls)`
97005
+ );
97006
+ out.push(` Journal recorded: ${usd(r2.localTotalUsd)}`);
97007
+ out.push(
97008
+ ` Matched: ${r2.matched.length}, of which ${mismatched.length} disagree on amount`
97009
+ );
97010
+ if (mismatched.length > 0) {
97011
+ out.push(`
97012
+ Amount mismatches (journal vs gateway):`);
97013
+ for (const m of mismatched.slice(0, 10)) {
97014
+ out.push(
97015
+ ` ${m.remote.requestId.slice(0, 8)} ${(m.local.model || m.remote.endpoint).padEnd(28)} journal ${usd(m.local.cost)} gateway ${usd(m.remote.costUsd ?? 0)}`
97016
+ );
97017
+ }
97018
+ if (mismatched.length > 10) out.push(` \u2026and ${mismatched.length - 10} more`);
97019
+ }
97020
+ if (r2.chargedNotRecorded.length > 0) {
97021
+ const total = r2.chargedNotRecorded.reduce((s3, x) => s3 + (x.costUsd ?? 0), 0);
97022
+ out.push(
97023
+ `
97024
+ \u26A0 Charged but NOT in this machine's journal: ${r2.chargedNotRecorded.length} call(s), ${usd(total)}`
97025
+ );
97026
+ out.push(
97027
+ ` Expected if the same key is used elsewhere \u2014 another machine, another BlockRun product,`
97028
+ );
97029
+ out.push(` or a call made before this proxy started recording request ids.`);
97030
+ for (const x of r2.chargedNotRecorded.slice(0, 10)) {
97031
+ out.push(
97032
+ ` ${x.requestId.slice(0, 8)} ${x.timestamp.slice(0, 19)} ${x.endpoint.padEnd(26)} ${usd(x.costUsd ?? 0)}`
97033
+ );
97034
+ }
97035
+ if (r2.chargedNotRecorded.length > 10) {
97036
+ out.push(` \u2026and ${r2.chargedNotRecorded.length - 10} more`);
97037
+ }
97038
+ }
97039
+ if (r2.recordedNotCharged.length > 0) {
97040
+ out.push(
97041
+ `
97042
+ Recorded locally with no settled ledger row: ${r2.recordedNotCharged.length} call(s)`
97043
+ );
97044
+ out.push(` Usually benign \u2014 free models, cache hits, and calls still pending pricing.`);
97045
+ }
97046
+ if (r2.pending.length > 0) {
97047
+ out.push(`
97048
+ Pending pricing (excluded from totals): ${r2.pending.length} call(s)`);
97049
+ out.push(` Usage recorded, charge not final. These can still be repriced.`);
97050
+ }
97051
+ if (r2.unkeyedLocalCount > 0) {
97052
+ out.push(`
97053
+ Journal entries with no request id: ${r2.unkeyedLocalCount} (not reconcilable)`);
97054
+ }
97055
+ if (r2.unavailableDays.length > 0) {
97056
+ out.push(`
97057
+ \u26A0 Gateway could not list these days: ${r2.unavailableDays.join(", ")}`);
97058
+ out.push(` Totals above are incomplete for that period.`);
97059
+ }
97060
+ out.push(`
97061
+ Note: the top-up fee (5.5% + $0.30) is charged at purchase, not per call,`);
97062
+ out.push(` so the sum above is less than your card was charged by exactly those fees.
97063
+ `);
97064
+ return out.join("\n");
97065
+ }
97066
+ var LOG_DIR3, CENT_EPSILON, usd;
97067
+ var init_reconcile = __esm({
97068
+ "src/reconcile.ts"() {
97069
+ "use strict";
97070
+ init_api_key();
97071
+ LOG_DIR3 = join12(homedir9(), ".openclaw", "blockrun", "logs");
97072
+ CENT_EPSILON = 1e-9;
97073
+ usd = (n) => Math.abs(n) < 0.01 ? `$${n.toFixed(6)}` : `$${n.toFixed(2)}`;
97074
+ }
97075
+ });
97076
+
96555
97077
  // src/provider.ts
96556
97078
  function setActiveProxy(proxy) {
96557
97079
  activeProxy = proxy;
@@ -112394,7 +112916,7 @@ var init_readBlob = __esm({
112394
112916
 
112395
112917
  // node_modules/axios/lib/helpers/formDataToStream.js
112396
112918
  import util2 from "util";
112397
- import { Readable } from "stream";
112919
+ import { Readable as Readable2 } from "stream";
112398
112920
  var BOUNDARY_ALPHABET, textEncoder, CRLF, CRLF_BYTES, CRLF_BYTES_COUNT, FormDataPart, formDataToStream, formDataToStream_default;
112399
112921
  var init_formDataToStream = __esm({
112400
112922
  "node_modules/axios/lib/helpers/formDataToStream.js"() {
@@ -112474,7 +112996,7 @@ var init_formDataToStream = __esm({
112474
112996
  computedHeaders["Content-Length"] = contentLength;
112475
112997
  }
112476
112998
  headersHandler && headersHandler(computedHeaders);
112477
- return Readable.from(
112999
+ return Readable2.from(
112478
113000
  (async function* () {
112479
113001
  for (const part of parts) {
112480
113002
  yield boundaryBytes;
@@ -117538,8 +118060,8 @@ var init_constants3 = __esm({
117538
118060
 
117539
118061
  // src/polymarket/wallet-adapter.ts
117540
118062
  import fs2 from "fs";
117541
- import { homedir as homedir9 } from "os";
117542
- import { join as join12 } from "path";
118063
+ import { homedir as homedir10 } from "os";
118064
+ import { join as join13 } from "path";
117543
118065
  function getOrCreateWalletKey() {
117544
118066
  const envKey = process.env.BLOCKRUN_WALLET_KEY?.trim();
117545
118067
  if (envKey && /^0x[0-9a-fA-F]{64}$/.test(envKey)) return envKey;
@@ -117587,7 +118109,7 @@ var init_wallet_adapter = __esm({
117587
118109
  init_esm();
117588
118110
  init_chains();
117589
118111
  init_constants3();
117590
- WALLET_FILE2 = join12(homedir9(), ".openclaw", "blockrun", "wallet.key");
118112
+ WALLET_FILE2 = join13(homedir10(), ".openclaw", "blockrun", "wallet.key");
117591
118113
  BASE_RPC_URLS = [
117592
118114
  "https://mainnet.base.org",
117593
118115
  "https://base.llamarpc.com",
@@ -117991,8 +118513,8 @@ var init_client5 = __esm({
117991
118513
  function resolveSpendControl(deps) {
117992
118514
  return deps?.spendControl ?? getSharedSpendControl();
117993
118515
  }
117994
- function usdToMicros(usd) {
117995
- return String(Math.ceil(usd * 1e6));
118516
+ function usdToMicros(usd2) {
118517
+ return String(Math.ceil(usd2 * 1e6));
117996
118518
  }
117997
118519
  async function signUnderSpendPolicy(deps, quote, action, sign4) {
117998
118520
  const control = resolveSpendControl(deps);
@@ -118021,13 +118543,13 @@ function getSessionLedger() {
118021
118543
  perAgent: Object.fromEntries(ledger.perAgent)
118022
118544
  };
118023
118545
  }
118024
- function reserveBet(usd, agentId) {
118025
- ledger.totalUsd += usd;
118026
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd);
118546
+ function reserveBet(usd2, agentId) {
118547
+ ledger.totalUsd += usd2;
118548
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) + usd2);
118027
118549
  }
118028
- function releaseBet(usd, agentId) {
118029
- ledger.totalUsd -= usd;
118030
- if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd);
118550
+ function releaseBet(usd2, agentId) {
118551
+ ledger.totalUsd -= usd2;
118552
+ if (agentId) ledger.perAgent.set(agentId, (ledger.perAgent.get(agentId) ?? 0) - usd2);
118031
118553
  }
118032
118554
  function commitBet() {
118033
118555
  ledger.count += 1;
@@ -227989,9 +228511,9 @@ import {
227989
228511
  unlinkSync
227990
228512
  } from "fs";
227991
228513
  import { readFile as readFileAsync } from "fs/promises";
227992
- import { homedir as homedir14 } from "os";
228514
+ import { homedir as homedir15 } from "os";
227993
228515
  import { randomUUID } from "crypto";
227994
- import { join as join17 } from "path";
228516
+ import { join as join18 } from "path";
227995
228517
  async function waitForProxyHealth(port, timeoutMs = 3e3) {
227996
228518
  const start = Date.now();
227997
228519
  while (Date.now() - start < timeoutMs) {
@@ -228033,8 +228555,8 @@ function isBlockrunWebSearchDisabled(config) {
228033
228555
  return cfg.tools?.web?.search?.enabled === false;
228034
228556
  }
228035
228557
  function injectModelsConfig(logger48, options = {}) {
228036
- const configDir = join17(homedir14(), ".openclaw");
228037
- const configPath = join17(configDir, "openclaw.json");
228558
+ const configDir = join18(homedir15(), ".openclaw");
228559
+ const configPath = join18(configDir, "openclaw.json");
228038
228560
  let config = {};
228039
228561
  let needsWrite = false;
228040
228562
  if (!existsSync4(configDir)) {
@@ -228255,7 +228777,7 @@ function injectModelsConfig(logger48, options = {}) {
228255
228777
  }
228256
228778
  function syncAgentModelCache(logger48, options = {}) {
228257
228779
  if (!isGatewayMode() && !options.forceWrite) return;
228258
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228780
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228259
228781
  if (!existsSync4(agentsDir)) return;
228260
228782
  let agentDirs;
228261
228783
  try {
@@ -228265,7 +228787,7 @@ function syncAgentModelCache(logger48, options = {}) {
228265
228787
  }
228266
228788
  const expectedIds = VISIBLE_OPENCLAW_MODELS.map((m) => m.id);
228267
228789
  for (const agent of agentDirs) {
228268
- const cachePath = join17(agentsDir, agent, "agent", "models.json");
228790
+ const cachePath = join18(agentsDir, agent, "agent", "models.json");
228269
228791
  if (!existsSync4(cachePath)) continue;
228270
228792
  try {
228271
228793
  const raw = readTextFileSync(cachePath).trim();
@@ -228294,7 +228816,7 @@ function syncAgentModelCache(logger48, options = {}) {
228294
228816
  }
228295
228817
  }
228296
228818
  function injectAuthProfile(logger48) {
228297
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
228819
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
228298
228820
  if (!existsSync4(agentsDir)) {
228299
228821
  try {
228300
228822
  mkdirSync3(agentsDir, { recursive: true });
@@ -228311,9 +228833,9 @@ function injectAuthProfile(logger48) {
228311
228833
  agents = ["main", ...agents];
228312
228834
  }
228313
228835
  for (const agentId of agents) {
228314
- const authDir = join17(agentsDir, agentId, "agent");
228315
- const authPath = join17(authDir, "auth-profiles.json");
228316
- const sqlitePath = join17(authDir, "openclaw-agent.sqlite");
228836
+ const authDir = join18(agentsDir, agentId, "agent");
228837
+ const authPath = join18(authDir, "auth-profiles.json");
228838
+ const sqlitePath = join18(authDir, "openclaw-agent.sqlite");
228317
228839
  if (existsSync4(sqlitePath)) {
228318
228840
  removeInjectedAuthPlaceholder(authPath, logger48, agentId);
228319
228841
  continue;
@@ -228461,12 +228983,12 @@ async function startProxyInBackground(api, startupGeneration) {
228461
228983
  proc.__clawrouterStartupPhase = "starting";
228462
228984
  }
228463
228985
  const pluginApiKey = api.pluginConfig?.apiKey;
228464
- const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228465
- if (typeof pluginApiKey === "string" && !isValidApiKey(pluginApiKey)) {
228466
- api.logger.warn(
228467
- `pluginConfig.apiKey is set but invalid (expected brk_...) \u2014 ignoring it and looking elsewhere`
228986
+ if (typeof pluginApiKey === "string" && pluginApiKey.trim() && !isValidApiKey(pluginApiKey)) {
228987
+ throw new Error(
228988
+ `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.`
228468
228989
  );
228469
228990
  }
228991
+ const resolvedApiKey = typeof pluginApiKey === "string" && isValidApiKey(pluginApiKey) ? { key: pluginApiKey.trim(), source: "config" } : await resolveApiKey();
228470
228992
  const apiKey = resolvedApiKey?.key;
228471
228993
  const configKey = api.pluginConfig?.walletKey;
228472
228994
  let wallet;
@@ -228816,7 +229338,7 @@ function buildImageGenerationProvider() {
228816
229338
  (result.data ?? []).map(async (img) => {
228817
229339
  const filename = img.url?.split("/images/").pop();
228818
229340
  if (!filename) throw new Error(`Unexpected image URL format: ${img.url}`);
228819
- const filePath = join17(IMAGE_DIR2, filename);
229341
+ const filePath = join18(IMAGE_DIR2, filename);
228820
229342
  const buffer2 = await readFileAsync(filePath);
228821
229343
  const ext = filename.split(".").pop()?.toLowerCase() ?? "png";
228822
229344
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
@@ -228868,7 +229390,7 @@ function buildMusicGenerationProvider() {
228868
229390
  (result.data ?? []).map(async (track) => {
228869
229391
  const filename = track.url?.split("/audio/").pop();
228870
229392
  if (!filename) throw new Error(`Unexpected audio URL format: ${track.url}`);
228871
- const filePath = join17(AUDIO_DIR2, filename);
229393
+ const filePath = join18(AUDIO_DIR2, filename);
228872
229394
  const buffer2 = await readFileAsync(filePath);
228873
229395
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp3";
228874
229396
  const mimeType = ext === "wav" ? "audio/wav" : "audio/mpeg";
@@ -228944,7 +229466,7 @@ function buildVideoGenerationProvider() {
228944
229466
  (result.data ?? []).map(async (clip) => {
228945
229467
  const filename = clip.url?.split("/videos/").pop();
228946
229468
  if (!filename) throw new Error(`Unexpected video URL format: ${clip.url}`);
228947
- const filePath = join17(VIDEO_DIR2, filename);
229469
+ const filePath = join18(VIDEO_DIR2, filename);
228948
229470
  const buffer2 = await readFileAsync(filePath);
228949
229471
  const ext = filename.split(".").pop()?.toLowerCase() ?? "mp4";
228950
229472
  const mimeType = ext === "webm" ? "video/webm" : ext === "mov" ? "video/quicktime" : "video/mp4";
@@ -229309,9 +229831,9 @@ var init_index = __esm({
229309
229831
  init_partners();
229310
229832
  activeProxyHandle = null;
229311
229833
  pendingConfiguredStartupApi = null;
229312
- IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
229313
- AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
229314
- VIDEO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "videos");
229834
+ IMAGE_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "images");
229835
+ AUDIO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "audio");
229836
+ VIDEO_DIR2 = join18(homedir15(), ".openclaw", "blockrun", "videos");
229315
229837
  plugin = {
229316
229838
  // NOT "clawrouter". OpenClaw bundles its own plugin under that id since the
229317
229839
  // 2026.7.1 line (vendored at dist/extensions/clawrouter, provider `clawrouter/*`,
@@ -229653,7 +230175,18 @@ ${errText}`
229653
230175
  if (!isGatewayMode()) {
229654
230176
  if (shouldLogRegistration) {
229655
230177
  void (async () => {
229656
- const keyResolution = await resolveApiKey().catch(() => void 0);
230178
+ let keyResolution;
230179
+ try {
230180
+ keyResolution = await resolveApiKey();
230181
+ } catch (error) {
230182
+ api.logger.error(
230183
+ `BlockRun API key is configured but unusable: ${error instanceof Error ? error.message : String(error)}`
230184
+ );
230185
+ api.logger.error(
230186
+ `Not falling back to a wallet \u2014 fix the key, or run "clawrouter logout" to pay from the wallet deliberately.`
230187
+ );
230188
+ return;
230189
+ }
229657
230190
  if (keyResolution) {
229658
230191
  api.logger.info(
229659
230192
  `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
@@ -229736,7 +230269,7 @@ ${errText}`
229736
230269
  }
229737
230270
  resetProxyStartupState();
229738
230271
  try {
229739
- const configPath = join17(homedir14(), ".openclaw", "openclaw.json");
230272
+ const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
229740
230273
  if (existsSync4(configPath)) {
229741
230274
  const config = JSON.parse(readTextFileSync(configPath));
229742
230275
  if (config.models?.providers?.blockrun) {
@@ -229778,11 +230311,11 @@ ${errText}`
229778
230311
  api.logger.warn(`Config cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
229779
230312
  }
229780
230313
  try {
229781
- const agentsDir = join17(homedir14(), ".openclaw", "agents");
230314
+ const agentsDir = join18(homedir15(), ".openclaw", "agents");
229782
230315
  if (existsSync4(agentsDir)) {
229783
230316
  for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
229784
230317
  if (!entry.isDirectory()) continue;
229785
- const authPath = join17(agentsDir, entry.name, "agent", "auth-profiles.json");
230318
+ const authPath = join18(agentsDir, entry.name, "agent", "auth-profiles.json");
229786
230319
  if (!existsSync4(authPath)) continue;
229787
230320
  try {
229788
230321
  const store = JSON.parse(readTextFileSync(authPath));
@@ -230598,6 +231131,27 @@ Use: clawrouter share <id> --as=<feishu|slack|discord|telegram|whatsapp|plain>
230598
231131
  process.exit(1);
230599
231132
  }
230600
231133
  }
231134
+ async function cmdReconcile(days) {
231135
+ const resolved = await resolveApiKey().catch((error) => {
231136
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231137
+ process.exit(1);
231138
+ });
231139
+ if (!resolved) {
231140
+ console.error(`\u2717 Reconciliation needs a BlockRun API key \u2014 it compares against your account`);
231141
+ console.error(` ledger. Wallet spend settles on-chain instead; see "clawrouter stats".`);
231142
+ console.error(` Sign in at ${PORTAL_URL} and run: clawrouter login brk_live_...`);
231143
+ process.exit(1);
231144
+ }
231145
+ try {
231146
+ const { reconcile: reconcile2, formatReconcile: formatReconcile2 } = await Promise.resolve().then(() => (init_reconcile(), reconcile_exports));
231147
+ const result = await reconcile2(resolved.key, days);
231148
+ console.log(formatReconcile2(result, days));
231149
+ process.exitCode = result.chargedNotRecorded.length > 0 ? 2 : 0;
231150
+ } catch (error) {
231151
+ console.error(`\u2717 ${error instanceof Error ? error.message : String(error)}`);
231152
+ process.exit(1);
231153
+ }
231154
+ }
230601
231155
  async function cmdStatus(port) {
230602
231156
  try {
230603
231157
  const data = await queryProxy("/health?full=true", port);
@@ -230609,6 +231163,12 @@ ClawRouter Status (port ${port})
230609
231163
  console.log(` Auth: BlockRun API key ${data.apiKey}`);
230610
231164
  console.log(` Gateway: ${data.gateway}`);
230611
231165
  console.log(` Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
231166
+ if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
231167
+ if (data.blocked) {
231168
+ console.log(` \u26A0 Account is blocked${data.blockedReason ? `: ${data.blockedReason}` : ""}`);
231169
+ } else if (data.isEmpty) {
231170
+ console.log(` \u26A0 Credit exhausted \u2014 top up at ${PORTAL_CREDITS_URL}`);
231171
+ }
230612
231172
  } else {
230613
231173
  console.log(` Auth: wallet (x402)`);
230614
231174
  console.log(` Payment Chain: ${data.paymentChain}`);
@@ -230636,6 +231196,7 @@ ClawRouter Account
230636
231196
  console.log(` Auth: BlockRun API key ${data.apiKey}`);
230637
231197
  console.log(` Gateway: ${data.gateway}`);
230638
231198
  console.log(` Billing: account credit (no wallet, no chain)`);
231199
+ if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
230639
231200
  console.log(` Top up: ${PORTAL_CREDITS_URL}`);
230640
231201
  console.log(` Usage: ${PORTAL_URL}/dashboard`);
230641
231202
  console.log();
@@ -230868,8 +231429,8 @@ async function cmdSetup() {
230868
231429
  unlinkSync: unlinkSync2,
230869
231430
  writeFileSync: writeFileSync4
230870
231431
  } = await import("fs");
230871
- const { dirname: dirname4, join: join18 } = await import("path");
230872
- const { homedir: homedir15 } = await import("os");
231432
+ const { dirname: dirname4, join: join19 } = await import("path");
231433
+ const { homedir: homedir16 } = await import("os");
230873
231434
  const { injectModelsConfig: injectModelsConfig2, injectAuthProfile: injectAuthProfile2, syncAgentModelCache: syncAgentModelCache2, VISIBLE_OPENCLAW_MODELS: VISIBLE_OPENCLAW_MODELS2 } = await Promise.resolve().then(() => (init_index(), index_exports));
230874
231435
  const { BLOCKRUN_PLUGIN_ID: BLOCKRUN_PLUGIN_ID2, prepareBlockRunPluginConfig: prepareBlockRunPluginConfig2 } = await Promise.resolve().then(() => (init_openclaw_plugin_config(), openclaw_plugin_config_exports));
230875
231436
  console.log("\u{1F99E} ClawRouter setup\n");
@@ -230884,19 +231445,19 @@ async function cmdSetup() {
230884
231445
  } catch {
230885
231446
  }
230886
231447
  if (process.env.npm_config_prefix) {
230887
- candidates.push(join18(process.env.npm_config_prefix, "bin", "openclaw"));
231448
+ candidates.push(join19(process.env.npm_config_prefix, "bin", "openclaw"));
230888
231449
  }
230889
231450
  try {
230890
231451
  const npmRoot = execSync("npm root -g", {
230891
231452
  encoding: "utf8",
230892
231453
  stdio: ["ignore", "pipe", "ignore"]
230893
231454
  }).trim();
230894
- if (npmRoot) candidates.push(join18(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
231455
+ if (npmRoot) candidates.push(join19(dirname4(dirname4(npmRoot)), "bin", "openclaw"));
230895
231456
  } catch {
230896
231457
  }
230897
231458
  candidates.push(
230898
- join18(homedir15(), ".npm-global", "bin", "openclaw"),
230899
- join18(homedir15(), ".local", "bin", "openclaw"),
231459
+ join19(homedir16(), ".npm-global", "bin", "openclaw"),
231460
+ join19(homedir16(), ".local", "bin", "openclaw"),
230900
231461
  "/usr/local/bin/openclaw"
230901
231462
  );
230902
231463
  return candidates.find((candidate) => existsSync5(candidate)) ?? "";
@@ -230908,7 +231469,7 @@ async function cmdSetup() {
230908
231469
  process.exit(1);
230909
231470
  }
230910
231471
  console.log(` \u2713 Found openclaw at ${openclawPath}`);
230911
- const configPath = join18(homedir15(), ".openclaw", "openclaw.json");
231472
+ const configPath = join19(homedir16(), ".openclaw", "openclaw.json");
230912
231473
  const configInitiallyExisted = existsSync5(configPath);
230913
231474
  const configRollbackBackup = configInitiallyExisted ? `${configPath}.before-${BLOCKRUN_PLUGIN_ID2}.${Date.now()}` : void 0;
230914
231475
  if (configRollbackBackup) copyFileSync2(configPath, configRollbackBackup);
@@ -230916,23 +231477,23 @@ async function cmdSetup() {
230916
231477
  return configRollbackBackup;
230917
231478
  };
230918
231479
  let installAttempted = false;
230919
- const openclawRoot = join18(homedir15(), ".openclaw");
231480
+ const openclawRoot = join19(homedir16(), ".openclaw");
230920
231481
  const findManagedBlockRunInstalls = () => {
230921
231482
  const candidates = [
230922
- join18(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
230923
- join18(openclawRoot, "extensions", "clawrouter"),
230924
- join18(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
231483
+ join19(openclawRoot, "extensions", BLOCKRUN_PLUGIN_ID2),
231484
+ join19(openclawRoot, "extensions", "clawrouter"),
231485
+ join19(openclawRoot, "npm", "node_modules", "@blockrun", "clawrouter")
230925
231486
  ];
230926
- const projectsDir = join18(openclawRoot, "npm", "projects");
231487
+ const projectsDir = join19(openclawRoot, "npm", "projects");
230927
231488
  try {
230928
231489
  for (const project of readdirSync2(projectsDir)) {
230929
- candidates.push(join18(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
231490
+ candidates.push(join19(projectsDir, project, "node_modules", "@blockrun", "clawrouter"));
230930
231491
  }
230931
231492
  } catch {
230932
231493
  }
230933
231494
  return [...new Set(candidates)].filter((candidate) => {
230934
231495
  try {
230935
- const metadata = JSON.parse(readFileSync4(join18(candidate, "package.json"), "utf8"));
231496
+ const metadata = JSON.parse(readFileSync4(join19(candidate, "package.json"), "utf8"));
230936
231497
  return metadata.name === "@blockrun/clawrouter";
230937
231498
  } catch {
230938
231499
  return false;
@@ -230940,7 +231501,7 @@ async function cmdSetup() {
230940
231501
  });
230941
231502
  };
230942
231503
  const initialPluginDirs = findManagedBlockRunInstalls();
230943
- const setupRollbackRoot = join18(
231504
+ const setupRollbackRoot = join19(
230944
231505
  openclawRoot,
230945
231506
  "blockrun",
230946
231507
  "setup-rollbacks",
@@ -230950,7 +231511,7 @@ async function cmdSetup() {
230950
231511
  if (initialPluginDirs.length > 0) {
230951
231512
  mkdirSync4(setupRollbackRoot, { recursive: true, mode: 448 });
230952
231513
  for (const [index2, pluginDir] of initialPluginDirs.entries()) {
230953
- const backup = join18(setupRollbackRoot, String(index2));
231514
+ const backup = join19(setupRollbackRoot, String(index2));
230954
231515
  cpSync(pluginDir, backup, { recursive: true });
230955
231516
  pluginRollbackBackups.set(pluginDir, backup);
230956
231517
  }
@@ -230995,8 +231556,8 @@ async function cmdSetup() {
230995
231556
  if (existsSync5(configPath)) {
230996
231557
  try {
230997
231558
  const config = JSON.parse(readFileSync4(configPath, "utf8"));
230998
- const legacyPackagePath = join18(
230999
- homedir15(),
231559
+ const legacyPackagePath = join19(
231560
+ homedir16(),
231000
231561
  ".openclaw",
231001
231562
  "extensions",
231002
231563
  "clawrouter",
@@ -231167,6 +231728,8 @@ function parseArgs(args) {
231167
231728
  queryModels: false,
231168
231729
  queryStats: false,
231169
231730
  queryStatsDays: 7,
231731
+ reconcile: false,
231732
+ reconcileDays: 7,
231170
231733
  queryCache: false,
231171
231734
  setup: false,
231172
231735
  share: false,
@@ -231201,6 +231764,12 @@ function parseArgs(args) {
231201
231764
  result.queryStatsDays = Math.min(parseInt(args[i + 2], 10) || 7, 30);
231202
231765
  i += 2;
231203
231766
  }
231767
+ } else if (arg === "reconcile") {
231768
+ result.reconcile = true;
231769
+ if (args[i + 1] === "--days" && args[i + 2]) {
231770
+ result.reconcileDays = Math.min(parseInt(args[i + 2], 10) || 7, 90);
231771
+ i += 2;
231772
+ }
231204
231773
  } else if (arg === "cache") {
231205
231774
  result.queryCache = true;
231206
231775
  } else if (arg === "doctor" || arg === "--doctor") {
@@ -231426,6 +231995,10 @@ async function main() {
231426
231995
  await cmdStats(queryPort, args.queryStatsDays);
231427
231996
  process.exit(0);
231428
231997
  }
231998
+ if (args.reconcile) {
231999
+ await cmdReconcile(args.reconcileDays);
232000
+ process.exit(0);
232001
+ }
231429
232002
  if (args.queryCache) {
231430
232003
  await cmdCache(queryPort);
231431
232004
  process.exit(0);