routstrd 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29366,12 +29366,12 @@ var require_lib = __commonJS((exports, module) => {
29366
29366
  var exports_dist = {};
29367
29367
  __export(exports_dist, {
29368
29368
  setDefaultUsageTrackingDriver: () => setDefaultUsageTrackingDriver,
29369
- routeRequestsToNodeResponse: () => routeRequestsToNodeResponse,
29370
29369
  routeRequests: () => routeRequests,
29371
29370
  normalizeProviderUrl: () => normalizeProviderUrl,
29372
29371
  localStorageDriver: () => localStorageDriver,
29373
29372
  isTorContext: () => isTorContext,
29374
29373
  isOnionUrl: () => isOnionUrl,
29374
+ inspectSSEWebStream: () => inspectSSEWebStream,
29375
29375
  getProviderEndpoints: () => getProviderEndpoints,
29376
29376
  getDefaultUsageTrackingDriver: () => getDefaultUsageTrackingDriver,
29377
29377
  getDefaultStorageAdapter: () => getDefaultStorageAdapter,
@@ -29412,10 +29412,8 @@ __export(exports_dist, {
29412
29412
  CashuSpender: () => CashuSpender,
29413
29413
  BalanceManager: () => BalanceManager
29414
29414
  });
29415
- import { Transform, Readable } from "stream";
29416
- import * as fs2 from "fs";
29417
- import * as path from "path";
29418
- import * as os2 from "os";
29415
+ import { Transform } from "stream";
29416
+ import { StringDecoder } from "string_decoder";
29419
29417
  function isNetworkErrorMessage(message) {
29420
29418
  return message.includes("NetworkError when attempting to fetch resource") || message.includes("Failed to fetch") || message.includes("Load failed") || message.includes("ERR_TLS_CERT_ALTNAME_INVALID") || message.includes("ERR_TLS_CERT_NOT_YET_VALID") || message.includes("ERR_TLS_CERT_EXPIRED") || message.includes("UNABLE_TO_VERIFY_LEAF_SIGNATURE") || message.includes("SELF_SIGNED_CERT_IN_CHAIN");
29421
29419
  }
@@ -29684,28 +29682,125 @@ async function createBunSqliteDriver(dbPath) {
29684
29682
  }
29685
29683
  };
29686
29684
  }
29687
- function createSSEParserTransform(onUsage, onResponseId) {
29685
+ function mergeUsage(previous, next) {
29686
+ if (!previous)
29687
+ return next;
29688
+ return {
29689
+ promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
29690
+ completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
29691
+ totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
29692
+ cost: next.cost > 0 ? next.cost : previous.cost,
29693
+ satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
29694
+ };
29695
+ }
29696
+ function hasUsageChanged(previous, next) {
29697
+ if (!previous)
29698
+ return true;
29699
+ return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
29700
+ }
29701
+ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
29702
+ const reader = stream.getReader();
29703
+ const decoder = new TextDecoder("utf-8");
29688
29704
  let buffer = "";
29689
29705
  let capturedUsage = null;
29706
+ let capturedResponseId;
29690
29707
  let responseIdCaptured = false;
29691
- const mergeUsage = (previous, next) => {
29692
- if (!previous)
29693
- return next;
29694
- return {
29695
- promptTokens: next.promptTokens > 0 ? next.promptTokens : previous.promptTokens,
29696
- completionTokens: next.completionTokens > 0 ? next.completionTokens : previous.completionTokens,
29697
- totalTokens: next.totalTokens > 0 ? next.totalTokens : previous.totalTokens,
29698
- cost: next.cost > 0 ? next.cost : previous.cost,
29699
- satsCost: next.satsCost > 0 ? next.satsCost : previous.satsCost
29700
- };
29708
+ const inspectDataPayload = (jsonText) => {
29709
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
29710
+ return;
29711
+ }
29712
+ const trimmed = jsonText.trim();
29713
+ if (!trimmed || trimmed === "[DONE]")
29714
+ return;
29715
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
29716
+ return;
29717
+ try {
29718
+ const data = JSON.parse(trimmed);
29719
+ if (!responseIdCaptured) {
29720
+ const responseId = data?.id;
29721
+ if (typeof responseId === "string" && responseId.trim().length > 0) {
29722
+ capturedResponseId = responseId.trim();
29723
+ onResponseId?.(capturedResponseId);
29724
+ responseIdCaptured = true;
29725
+ }
29726
+ }
29727
+ const usage = extractUsageFromSSEJson(data);
29728
+ if (usage) {
29729
+ const merged = mergeUsage(capturedUsage, usage);
29730
+ if (hasUsageChanged(capturedUsage, merged)) {
29731
+ capturedUsage = merged;
29732
+ onUsage(merged);
29733
+ }
29734
+ }
29735
+ } catch {}
29701
29736
  };
29702
- const hasUsageChanged = (previous, next) => {
29703
- if (!previous)
29704
- return true;
29705
- return previous.promptTokens !== next.promptTokens || previous.completionTokens !== next.completionTokens || previous.totalTokens !== next.totalTokens || previous.cost !== next.cost || previous.satsCost !== next.satsCost;
29737
+ const inspectEventBlock = (eventBlock) => {
29738
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
29739
+ return;
29740
+ }
29741
+ const lines = eventBlock.split(/\r?\n/);
29742
+ const dataParts = [];
29743
+ for (const line of lines) {
29744
+ if (!line || line.startsWith(":"))
29745
+ continue;
29746
+ if (line.startsWith("data:")) {
29747
+ const value = line.startsWith("data: ") ? line.slice(6) : line.slice(5);
29748
+ dataParts.push(value);
29749
+ }
29750
+ }
29751
+ if (dataParts.length === 0)
29752
+ return;
29753
+ inspectDataPayload(dataParts.join(`
29754
+ `));
29755
+ };
29756
+ const drainBufferedEvents = () => {
29757
+ const terminator = /\r?\n\r?\n/g;
29758
+ let lastIndex = 0;
29759
+ let match;
29760
+ while ((match = terminator.exec(buffer)) !== null) {
29761
+ const block = buffer.slice(lastIndex, match.index);
29762
+ lastIndex = match.index + match[0].length;
29763
+ if (block.length > 0)
29764
+ inspectEventBlock(block);
29765
+ }
29766
+ if (lastIndex > 0)
29767
+ buffer = buffer.slice(lastIndex);
29768
+ };
29769
+ try {
29770
+ while (true) {
29771
+ const { value, done } = await reader.read();
29772
+ if (done)
29773
+ break;
29774
+ if (value && value.byteLength > 0) {
29775
+ buffer += decoder.decode(value, { stream: true });
29776
+ drainBufferedEvents();
29777
+ }
29778
+ }
29779
+ buffer += decoder.decode();
29780
+ drainBufferedEvents();
29781
+ if (buffer.length > 0) {
29782
+ const tail = buffer.replace(/\r?\n+$/, "");
29783
+ if (tail.length > 0)
29784
+ inspectEventBlock(tail);
29785
+ buffer = "";
29786
+ }
29787
+ } catch {} finally {
29788
+ try {
29789
+ reader.releaseLock();
29790
+ } catch {}
29791
+ }
29792
+ return {
29793
+ capturedUsage: capturedUsage ?? undefined,
29794
+ capturedResponseId
29706
29795
  };
29796
+ }
29797
+ function createSSEParserTransform(onUsage, onResponseId) {
29798
+ let buffer = "";
29799
+ const decoder = new StringDecoder("utf8");
29800
+ let capturedUsage = null;
29801
+ let responseIdCaptured = false;
29707
29802
  const inspectDataPayload = (jsonText) => {
29708
- if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
29803
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
29709
29804
  return;
29710
29805
  }
29711
29806
  const trimmed = jsonText.trim();
@@ -29733,7 +29828,7 @@ function createSSEParserTransform(onUsage, onResponseId) {
29733
29828
  } catch {}
29734
29829
  };
29735
29830
  const inspectEventBlock = (eventBlock) => {
29736
- if (responseIdCaptured && capturedUsage?.satsCost && capturedUsage.totalTokens) {
29831
+ if (responseIdCaptured && capturedUsage && capturedUsage.totalTokens > 0) {
29737
29832
  return;
29738
29833
  }
29739
29834
  const lines = eventBlock.split(/\r?\n/);
@@ -29752,35 +29847,35 @@ function createSSEParserTransform(onUsage, onResponseId) {
29752
29847
  `);
29753
29848
  inspectDataPayload(payload);
29754
29849
  };
29755
- const emitEventBlock = (self, eventBlock) => {
29756
- if (eventBlock.length === 0)
29757
- return;
29758
- inspectEventBlock(eventBlock);
29759
- self.push(eventBlock + `
29760
-
29761
- `);
29850
+ const processBufferedEvents = () => {
29851
+ const terminator = /\r?\n\r?\n/g;
29852
+ let lastIndex = 0;
29853
+ let match;
29854
+ while ((match = terminator.exec(buffer)) !== null) {
29855
+ const block = buffer.slice(lastIndex, match.index);
29856
+ lastIndex = match.index + match[0].length;
29857
+ if (block.length > 0) {
29858
+ inspectEventBlock(block);
29859
+ }
29860
+ }
29861
+ if (lastIndex > 0) {
29862
+ buffer = buffer.slice(lastIndex);
29863
+ }
29762
29864
  };
29763
29865
  return new Transform({
29764
29866
  transform(chunk, _encoding, callback) {
29765
- buffer += chunk.toString();
29766
- const terminator = /\r?\n\r?\n/g;
29767
- let lastIndex = 0;
29768
- let match;
29769
- while ((match = terminator.exec(buffer)) !== null) {
29770
- const block = buffer.slice(lastIndex, match.index);
29771
- lastIndex = match.index + match[0].length;
29772
- emitEventBlock(this, block);
29773
- }
29774
- if (lastIndex > 0) {
29775
- buffer = buffer.slice(lastIndex);
29776
- }
29867
+ this.push(chunk);
29868
+ buffer += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
29869
+ processBufferedEvents();
29777
29870
  callback();
29778
29871
  },
29779
29872
  flush(callback) {
29873
+ buffer += decoder.end();
29874
+ processBufferedEvents();
29780
29875
  if (buffer.length > 0) {
29781
29876
  const tail = buffer.replace(/\r?\n+$/, "");
29782
29877
  if (tail.length > 0) {
29783
- emitEventBlock(this, tail);
29878
+ inspectEventBlock(tail);
29784
29879
  }
29785
29880
  buffer = "";
29786
29881
  }
@@ -29792,7 +29887,7 @@ async function resolveRouteRequestContext(options) {
29792
29887
  const {
29793
29888
  modelId,
29794
29889
  requestBody,
29795
- path: path2 = "/v1/chat/completions",
29890
+ path = "/v1/chat/completions",
29796
29891
  headers = {},
29797
29892
  forcedProvider,
29798
29893
  walletAdapter,
@@ -29880,17 +29975,17 @@ async function resolveRouteRequestContext(options) {
29880
29975
  client: client2,
29881
29976
  baseUrl,
29882
29977
  mintUrl,
29883
- path: path2,
29978
+ path,
29884
29979
  headers,
29885
29980
  modelId,
29886
29981
  proxiedBody
29887
29982
  };
29888
29983
  }
29889
29984
  async function routeRequests(options) {
29890
- const { client: client2, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
29985
+ const { client: client2, baseUrl, mintUrl, path, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
29891
29986
  try {
29892
29987
  const response = await client2.routeRequest({
29893
- path: path2,
29988
+ path,
29894
29989
  method: "POST",
29895
29990
  body: proxiedBody,
29896
29991
  headers,
@@ -29909,27 +30004,6 @@ async function routeRequests(options) {
29909
30004
  throw error;
29910
30005
  }
29911
30006
  }
29912
- async function routeRequestsToNodeResponse(options) {
29913
- const { res } = options;
29914
- const { client: client2, baseUrl, mintUrl, path: path2, headers, modelId, proxiedBody } = await resolveRouteRequestContext(options);
29915
- try {
29916
- await client2.routeRequestToNodeResponse({
29917
- path: path2,
29918
- method: "POST",
29919
- body: proxiedBody,
29920
- headers,
29921
- baseUrl,
29922
- mintUrl,
29923
- modelId,
29924
- res
29925
- });
29926
- } catch (error) {
29927
- if (error instanceof Error && (error.message.includes("401") || error.message.includes("402") || error.message.includes("403"))) {
29928
- throw new Error(`Authentication failed: ${error.message}`);
29929
- }
29930
- throw error;
29931
- }
29932
- }
29933
30007
  function extractMaxTokens(requestBody) {
29934
30008
  if (!requestBody || typeof requestBody !== "object") {
29935
30009
  return;
@@ -30428,10 +30502,10 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
30428
30502
  `;
30429
30503
  if (typeof window === "undefined") {
30430
30504
  try {
30431
- const fs22 = await import("fs");
30432
- const path2 = await import("path");
30433
- const logPath = path2.join(process.cwd(), "audit.log");
30434
- fs22.appendFileSync(logPath, logLine);
30505
+ const fs2 = await import("fs");
30506
+ const path = await import("path");
30507
+ const logPath = path.join(process.cwd(), "audit.log");
30508
+ fs2.appendFileSync(logPath, logLine);
30435
30509
  } catch (error) {
30436
30510
  console.error("[AuditLogger] Failed to write to file:", error);
30437
30511
  }
@@ -30870,7 +30944,7 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
30870
30944
  }
30871
30945
  return 0;
30872
30946
  }
30873
- }, BalanceManager = class {
30947
+ }, BalanceManager = class _BalanceManager {
30874
30948
  constructor(walletAdapter, storageAdapter, providerRegistry, cashuSpender) {
30875
30949
  this.walletAdapter = walletAdapter;
30876
30950
  this.storageAdapter = storageAdapter;
@@ -30882,6 +30956,41 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
30882
30956
  }
30883
30957
  }
30884
30958
  cashuSpender;
30959
+ providerWalletOps = /* @__PURE__ */ new Map;
30960
+ static PROVIDER_WALLET_COOLDOWN_MS = 1e4;
30961
+ _canRunProviderWalletOperation(baseUrl, type) {
30962
+ const existing = this.providerWalletOps.get(baseUrl);
30963
+ if (!existing) {
30964
+ return { allowed: true };
30965
+ }
30966
+ if (existing.type === type) {
30967
+ return { allowed: true };
30968
+ }
30969
+ if (!existing.endTime) {
30970
+ return {
30971
+ allowed: false,
30972
+ reason: `Provider wallet operation locked; ${existing.type} in progress`
30973
+ };
30974
+ }
30975
+ const elapsed = Date.now() - existing.endTime;
30976
+ if (elapsed < _BalanceManager.PROVIDER_WALLET_COOLDOWN_MS) {
30977
+ return {
30978
+ allowed: false,
30979
+ reason: `Provider wallet operation locked; recent ${existing.type} completed ${Math.round(elapsed / 1000)}s ago`
30980
+ };
30981
+ }
30982
+ this.providerWalletOps.delete(baseUrl);
30983
+ return { allowed: true };
30984
+ }
30985
+ _beginProviderWalletOperation(baseUrl, type) {
30986
+ this.providerWalletOps.set(baseUrl, { type, startTime: Date.now() });
30987
+ }
30988
+ _endProviderWalletOperation(baseUrl, type) {
30989
+ const existing = this.providerWalletOps.get(baseUrl);
30990
+ if (existing && existing.type === type) {
30991
+ existing.endTime = Date.now();
30992
+ }
30993
+ }
30885
30994
  async getBalanceState() {
30886
30995
  const mintBalances = await this.walletAdapter.getBalances();
30887
30996
  const units = this.walletAdapter.getMintUnits();
@@ -30911,6 +31020,20 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
30911
31020
  };
30912
31021
  }
30913
31022
  async refundApiKey(options) {
31023
+ const { mintUrl, baseUrl, apiKey, forceRefund } = options;
31024
+ const guard = this._canRunProviderWalletOperation(baseUrl, "refund");
31025
+ if (!guard.allowed) {
31026
+ console.log(`[BalanceManager] Skipping refund for ${baseUrl} - ${guard.reason}`);
31027
+ return { success: false, message: guard.reason };
31028
+ }
31029
+ this._beginProviderWalletOperation(baseUrl, "refund");
31030
+ try {
31031
+ return await this._refundApiKeyImpl({ mintUrl, baseUrl, apiKey, forceRefund });
31032
+ } finally {
31033
+ this._endProviderWalletOperation(baseUrl, "refund");
31034
+ }
31035
+ }
31036
+ async _refundApiKeyImpl(options) {
30914
31037
  const { mintUrl, baseUrl, apiKey, forceRefund } = options;
30915
31038
  if (!apiKey) {
30916
31039
  return { success: false, message: "No API key to refund" };
@@ -31029,6 +31152,20 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
31029
31152
  }
31030
31153
  }
31031
31154
  async topUp(options) {
31155
+ const { mintUrl, baseUrl, amount, token: providedToken } = options;
31156
+ const guard = this._canRunProviderWalletOperation(baseUrl, "topup");
31157
+ if (!guard.allowed) {
31158
+ console.log(`[BalanceManager] Skipping topup for ${baseUrl} - ${guard.reason}`);
31159
+ return { success: false, message: guard.reason };
31160
+ }
31161
+ this._beginProviderWalletOperation(baseUrl, "topup");
31162
+ try {
31163
+ return await this._topUpImpl({ mintUrl, baseUrl, amount, token: providedToken });
31164
+ } finally {
31165
+ this._endProviderWalletOperation(baseUrl, "topup");
31166
+ }
31167
+ }
31168
+ async _topUpImpl(options) {
31032
31169
  const { mintUrl, baseUrl, amount, token: providedToken } = options;
31033
31170
  if (!amount || amount <= 0) {
31034
31171
  return { success: false, message: "Invalid top up amount" };
@@ -31153,7 +31290,7 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
31153
31290
  try {
31154
31291
  console.log(`[BalanceManager.createProviderToken] Attempting mint: ${candidateMint}, amount: ${requiredAmount}`);
31155
31292
  const token = await this.walletAdapter.sendToken(candidateMint, requiredAmount, p2pkPubkey);
31156
- console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}`);
31293
+ console.log(`[BalanceManager.createProviderToken] SUCCESS: Token created from mint ${candidateMint}, all mint balances: ${JSON.stringify(Object.fromEntries(Object.entries(balances).map(([mint, balance]) => [mint, getBalanceInSats(balance, units[mint])])))}`);
31157
31294
  return {
31158
31295
  success: true,
31159
31296
  token,
@@ -31963,8 +32100,9 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
31963
32100
  const approximateTokens = apiMessagesNoImages ? Math.ceil(JSON.stringify(apiMessagesNoImages, null, 2).length / 2.84) : 1e4;
31964
32101
  const totalInputTokens = approximateTokens + imageTokens;
31965
32102
  const sp = model2?.sats_pricing;
31966
- if (!sp)
32103
+ if (!sp) {
31967
32104
  return 0;
32105
+ }
31968
32106
  if (!sp.max_completion_cost) {
31969
32107
  return sp.max_cost ?? 50;
31970
32108
  }
@@ -33424,31 +33562,12 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33424
33562
  }
33425
33563
  async routeRequest(params) {
33426
33564
  const prepared = await this._prepareRoutedRequest(params);
33427
- const satsSpent = await this._handlePostResponseBalanceUpdate({
33428
- token: prepared.tokenUsed,
33429
- baseUrl: prepared.baseUrlUsed,
33430
- mintUrl: params.mintUrl,
33431
- initialTokenBalance: prepared.tokenBalanceInSats,
33432
- response: prepared.response,
33433
- modelId: prepared.modelId,
33434
- usage: prepared.capturedUsage,
33435
- requestId: prepared.capturedResponseId,
33436
- clientApiKey: prepared.clientApiKey
33437
- });
33438
- prepared.response.satsSpent = satsSpent;
33439
- prepared.response.usage = prepared.capturedUsage;
33440
- prepared.response.requestId = prepared.capturedResponseId;
33441
- return prepared.response;
33442
- }
33443
- async routeRequestToNodeResponse(params) {
33444
- const { res } = params;
33445
- const prepared = await this._prepareRoutedRequest(params);
33446
- res.statusCode = prepared.response.status;
33447
- prepared.response.headers.forEach((value, key) => {
33448
- res.setHeader(key, value);
33449
- });
33450
- const body = prepared.response.body;
33451
- if (!body) {
33565
+ const contentType = prepared.response.headers.get("content-type") || "";
33566
+ const isSSE = contentType.includes("text/event-stream");
33567
+ const runFinalize = async () => {
33568
+ const { capturedUsage, capturedResponseId } = await prepared.usagePromise;
33569
+ const usage = capturedUsage ?? prepared.capturedUsage;
33570
+ const requestId = capturedResponseId ?? prepared.capturedResponseId;
33452
33571
  const satsSpent = await this._handlePostResponseBalanceUpdate({
33453
33572
  token: prepared.tokenUsed,
33454
33573
  baseUrl: prepared.baseUrlUsed,
@@ -33456,53 +33575,25 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33456
33575
  initialTokenBalance: prepared.tokenBalanceInSats,
33457
33576
  response: prepared.response,
33458
33577
  modelId: prepared.modelId,
33459
- usage: prepared.capturedUsage,
33460
- requestId: prepared.capturedResponseId,
33578
+ usage,
33579
+ requestId,
33461
33580
  clientApiKey: prepared.clientApiKey
33462
33581
  });
33463
33582
  prepared.response.satsSpent = satsSpent;
33464
- res.end();
33465
- return;
33583
+ prepared.response.usage = usage;
33584
+ prepared.response.requestId = requestId;
33585
+ return satsSpent;
33586
+ };
33587
+ if (isSSE) {
33588
+ const finalizePromise = runFinalize().catch((error) => {
33589
+ this._log("ERROR", "[RoutstrClient] SSE finalize failed:", error);
33590
+ return 0;
33591
+ });
33592
+ prepared.response.finalize = () => finalizePromise;
33593
+ return prepared.response;
33466
33594
  }
33467
- const nodeReadable = Readable.fromWeb(body);
33468
- await new Promise((resolve, reject) => {
33469
- let settled = false;
33470
- const finish = async () => {
33471
- if (settled)
33472
- return;
33473
- settled = true;
33474
- try {
33475
- const satsSpent = await this._handlePostResponseBalanceUpdate({
33476
- token: prepared.tokenUsed,
33477
- baseUrl: prepared.baseUrlUsed,
33478
- mintUrl: params.mintUrl,
33479
- initialTokenBalance: prepared.tokenBalanceInSats,
33480
- response: prepared.response,
33481
- modelId: prepared.modelId,
33482
- usage: prepared.capturedUsage,
33483
- requestId: prepared.capturedResponseId,
33484
- clientApiKey: prepared.clientApiKey
33485
- });
33486
- prepared.response.satsSpent = satsSpent;
33487
- prepared.response.usage = prepared.capturedUsage;
33488
- prepared.response.requestId = prepared.capturedResponseId;
33489
- resolve();
33490
- } catch (error) {
33491
- reject(error);
33492
- }
33493
- };
33494
- const fail = (error) => {
33495
- if (settled)
33496
- return;
33497
- settled = true;
33498
- reject(error);
33499
- };
33500
- res.once("finish", finish);
33501
- res.once("close", finish);
33502
- res.once("error", fail);
33503
- nodeReadable.once("error", fail);
33504
- nodeReadable.pipe(res);
33505
- });
33595
+ await runFinalize();
33596
+ return prepared.response;
33506
33597
  }
33507
33598
  async _prepareRoutedRequest(params) {
33508
33599
  const {
@@ -33523,7 +33614,14 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33523
33614
  const providerModel = await this.providerManager.getModelForProvider(baseUrl, modelId);
33524
33615
  selectedModel = providerModel ?? undefined;
33525
33616
  if (selectedModel) {
33526
- requiredSats = this.providerManager.getRequiredSatsForModel(selectedModel, []);
33617
+ const requestMessages = Array.isArray(body?.messages) ? body.messages : [];
33618
+ const requestMaxTokens = typeof body?.max_tokens === "number" ? body.max_tokens : undefined;
33619
+ this._log("DEBUG", "[RoutstrClient] generic request pricing input", {
33620
+ modelId: selectedModel.id,
33621
+ messageCount: requestMessages.length,
33622
+ maxTokens: requestMaxTokens
33623
+ });
33624
+ requiredSats = this.providerManager.getRequiredSatsForModel(selectedModel, requestMessages, requestMaxTokens);
33527
33625
  }
33528
33626
  }
33529
33627
  const { token, tokenBalance, tokenBalanceUnit } = await this._spendToken({
@@ -33559,38 +33657,24 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33559
33657
  let processedResponse = response;
33560
33658
  let capturedUsage;
33561
33659
  let capturedResponseId;
33660
+ let usagePromise = Promise.resolve({});
33562
33661
  if (contentType.includes("text/event-stream") && response.body) {
33563
- const logDir = path.join(os2.homedir(), ".routstrd", "stream-response");
33564
- if (!fs2.existsSync(logDir)) {
33565
- fs2.mkdirSync(logDir, { recursive: true });
33566
- }
33567
- const logFile = path.join(logDir, `${Date.now()}.jsonl`);
33568
- const logStream = fs2.createWriteStream(logFile);
33569
- const nodeReadable = Readable.fromWeb(response.body);
33570
- const loggingTransform = new Transform({
33571
- transform(chunk, encoding, callback) {
33572
- const raw = chunk.toString();
33573
- logStream.write(JSON.stringify({ raw, timestamp: Date.now() }) + `
33574
- `);
33575
- callback(null, chunk);
33576
- }
33662
+ const [clientStream, inspectStream] = response.body.tee();
33663
+ processedResponse = new Response(clientStream, {
33664
+ status: response.status,
33665
+ statusText: response.statusText,
33666
+ headers: response.headers
33577
33667
  });
33578
- const sseParser = createSSEParserTransform((usage) => {
33668
+ processedResponse.baseUrl = response.baseUrl;
33669
+ processedResponse.token = response.token;
33670
+ usagePromise = inspectSSEWebStream(inspectStream, (usage) => {
33579
33671
  capturedUsage = usage;
33580
33672
  processedResponse.usage = usage;
33581
33673
  }, (responseId) => {
33582
33674
  capturedResponseId = responseId;
33583
33675
  processedResponse.requestId = responseId;
33584
33676
  });
33585
- const transformed = nodeReadable.pipe(loggingTransform).pipe(sseParser, { end: true });
33586
- const webStream = Readable.toWeb(transformed);
33587
- processedResponse = new Response(webStream, {
33588
- status: response.status,
33589
- statusText: response.statusText,
33590
- headers: response.headers
33591
- });
33592
- processedResponse.baseUrl = response.baseUrl;
33593
- processedResponse.token = response.token;
33677
+ processedResponse.usagePromise = usagePromise;
33594
33678
  }
33595
33679
  return {
33596
33680
  response: processedResponse,
@@ -33600,7 +33684,8 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33600
33684
  modelId,
33601
33685
  capturedUsage,
33602
33686
  capturedResponseId,
33603
- clientApiKey
33687
+ clientApiKey,
33688
+ usagePromise
33604
33689
  };
33605
33690
  }
33606
33691
  _extractClientApiKey(headers) {
@@ -33726,9 +33811,9 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33726
33811
  }
33727
33812
  }
33728
33813
  async _makeRequest(params) {
33729
- const { path: path2, method, body, baseUrl, token, headers } = params;
33814
+ const { path, method, body, baseUrl, token, headers } = params;
33730
33815
  try {
33731
- const url2 = `${baseUrl.replace(/\/$/, "")}${path2}`;
33816
+ const url2 = `${baseUrl.replace(/\/$/, "")}${path}`;
33732
33817
  if (this.mode === "xcashu")
33733
33818
  this._log("DEBUG", "HEADERS,", headers);
33734
33819
  const response = await fetch(url2, {
@@ -33760,7 +33845,7 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33760
33845
  }
33761
33846
  async _handleErrorResponse(params, token, status, requestId, xCashuRefundToken, responseBody, retryCount = 0) {
33762
33847
  const MAX_RETRIES_PER_PROVIDER = 2;
33763
- const { path: path2, method, body, selectedModel, baseUrl, mintUrl } = params;
33848
+ const { path, method, body, selectedModel, baseUrl, mintUrl } = params;
33764
33849
  let tryNextProvider = false;
33765
33850
  const errorMessage = responseBody;
33766
33851
  this._log("DEBUG", `[RoutstrClient] _handleErrorResponse: status=${status}, baseUrl=${baseUrl}, mode=${this.mode}, token preview=${token}, requestId=${requestId}, errorMessage=${errorMessage}`);
@@ -33797,7 +33882,7 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33797
33882
  const currentBalanceInfo = await this.balanceManager.getTokenBalance(params.token, baseUrl);
33798
33883
  const currentBalance = currentBalanceInfo.unit === "msat" ? currentBalanceInfo.amount / 1000 : currentBalanceInfo.amount;
33799
33884
  const shortfall = Math.max(0, params.requiredSats - currentBalance);
33800
- topupAmount = shortfall > 0 ? shortfall : params.requiredSats;
33885
+ topupAmount = shortfall > 0.21 * params.requiredSats ? shortfall : 0.21 * params.requiredSats;
33801
33886
  this._log("DEBUG", `The shortfall is: ${shortfall}. requiredSats: ${params.requiredSats}. Current Balance: ${currentBalance} `);
33802
33887
  } catch (e) {
33803
33888
  this._log("WARN", "Could not get current token balance for topup calculation:", e);
@@ -33926,7 +34011,7 @@ var import_rxjs24, InsufficientBalanceError, ProviderError, MintUnreachableError
33926
34011
  });
33927
34012
  return this._makeRequest({
33928
34013
  ...params,
33929
- path: path2,
34014
+ path,
33930
34015
  method,
33931
34016
  body,
33932
34017
  baseUrl: nextProvider,
@@ -34400,7 +34485,7 @@ var init_dist3 = __esm(() => {
34400
34485
  // src/daemon/index.ts
34401
34486
  init_dist3();
34402
34487
  import { createServer } from "http";
34403
- import { existsSync as existsSync8 } from "fs";
34488
+ import { existsSync as existsSync7 } from "fs";
34404
34489
 
34405
34490
  // src/utils/config.ts
34406
34491
  var HOME = process.env.HOME || process.env.USERPROFILE || "";
@@ -34419,19 +34504,19 @@ var DEFAULT_CONFIG = {
34419
34504
 
34420
34505
  // src/utils/logger.ts
34421
34506
  import { appendFile, mkdir } from "fs/promises";
34422
- import { existsSync as existsSync2 } from "fs";
34423
- import { join as join4 } from "path";
34507
+ import { existsSync } from "fs";
34508
+ import { join as join3 } from "path";
34424
34509
  var HOME2 = process.env.HOME || process.env.USERPROFILE || "";
34425
34510
  var LOG_DIR = process.env.ROUTSTRD_DIR || `${HOME2}/.routstrd`;
34426
- var LOGS_DIR2 = join4(LOG_DIR, "logs");
34511
+ var LOGS_DIR2 = join3(LOG_DIR, "logs");
34427
34512
  function getLogFileForDate(date = new Date) {
34428
34513
  const year = date.getFullYear();
34429
34514
  const month = String(date.getMonth() + 1).padStart(2, "0");
34430
34515
  const day = String(date.getDate()).padStart(2, "0");
34431
- return join4(LOGS_DIR2, `${year}-${month}-${day}.log`);
34516
+ return join3(LOGS_DIR2, `${year}-${month}-${day}.log`);
34432
34517
  }
34433
34518
  async function ensureLogDir() {
34434
- if (!existsSync2(LOGS_DIR2)) {
34519
+ if (!existsSync(LOGS_DIR2)) {
34435
34520
  await mkdir(LOGS_DIR2, { recursive: true });
34436
34521
  }
34437
34522
  }
@@ -34491,7 +34576,7 @@ function parseArgs(argv) {
34491
34576
 
34492
34577
  // src/daemon/config-store.ts
34493
34578
  import { mkdir as mkdir2 } from "fs/promises";
34494
- import { existsSync as existsSync3 } from "fs";
34579
+ import { existsSync as existsSync2 } from "fs";
34495
34580
  var REQUESTS_DIR = `${CONFIG_DIR}/requests`;
34496
34581
  async function ensureDirs() {
34497
34582
  try {
@@ -34501,7 +34586,7 @@ async function ensureDirs() {
34501
34586
  }
34502
34587
  async function loadDaemonConfig() {
34503
34588
  try {
34504
- if (existsSync3(CONFIG_FILE)) {
34589
+ if (existsSync2(CONFIG_FILE)) {
34505
34590
  const content2 = await Bun.file(CONFIG_FILE).text();
34506
34591
  return { ...DEFAULT_CONFIG, ...JSON.parse(content2) };
34507
34592
  }
@@ -34813,7 +34898,7 @@ function alphabet3(letters) {
34813
34898
  }
34814
34899
  };
34815
34900
  }
34816
- function join5(separator = "") {
34901
+ function join4(separator = "") {
34817
34902
  astr2("join", separator);
34818
34903
  return {
34819
34904
  encode: (from7) => {
@@ -35016,12 +35101,12 @@ function checksum2(len, fn) {
35016
35101
  }
35017
35102
  };
35018
35103
  }
35019
- var base163 = chain3(radix23(4), alphabet3("0123456789ABCDEF"), join5(""));
35020
- var base323 = chain3(radix23(5), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), padding3(5), join5(""));
35021
- var base32nopad2 = chain3(radix23(5), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), join5(""));
35022
- var base32hex3 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHIJKLMNOPQRSTUV"), padding3(5), join5(""));
35023
- var base32hexnopad2 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHIJKLMNOPQRSTUV"), join5(""));
35024
- var base32crockford3 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), join5(""), normalize3((s) => s.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1")));
35104
+ var base163 = chain3(radix23(4), alphabet3("0123456789ABCDEF"), join4(""));
35105
+ var base323 = chain3(radix23(5), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), padding3(5), join4(""));
35106
+ var base32nopad2 = chain3(radix23(5), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"), join4(""));
35107
+ var base32hex3 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHIJKLMNOPQRSTUV"), padding3(5), join4(""));
35108
+ var base32hexnopad2 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHIJKLMNOPQRSTUV"), join4(""));
35109
+ var base32crockford3 = chain3(radix23(5), alphabet3("0123456789ABCDEFGHJKMNPQRSTVWXYZ"), join4(""), normalize3((s) => s.toUpperCase().replace(/O/g, "0").replace(/[IL]/g, "1")));
35025
35110
  var hasBase64Builtin2 = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toBase64 === "function" && typeof Uint8Array.fromBase64 === "function")();
35026
35111
  var decodeBase64Builtin2 = (s, isUrl) => {
35027
35112
  astr2("base64", s);
@@ -35039,8 +35124,8 @@ var base643 = hasBase64Builtin2 ? {
35039
35124
  decode(s) {
35040
35125
  return decodeBase64Builtin2(s, false);
35041
35126
  }
35042
- } : chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), padding3(6), join5(""));
35043
- var base64nopad2 = chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), join5(""));
35127
+ } : chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), padding3(6), join4(""));
35128
+ var base64nopad2 = chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), join4(""));
35044
35129
  var base64url3 = hasBase64Builtin2 ? {
35045
35130
  encode(b) {
35046
35131
  abytes4(b);
@@ -35049,14 +35134,14 @@ var base64url3 = hasBase64Builtin2 ? {
35049
35134
  decode(s) {
35050
35135
  return decodeBase64Builtin2(s, true);
35051
35136
  }
35052
- } : chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), padding3(6), join5(""));
35053
- var base64urlnopad2 = chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), join5(""));
35054
- var genBase583 = (abc) => chain3(radix5(58), alphabet3(abc), join5(""));
35137
+ } : chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), padding3(6), join4(""));
35138
+ var base64urlnopad2 = chain3(radix23(6), alphabet3("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"), join4(""));
35139
+ var genBase583 = (abc) => chain3(radix5(58), alphabet3(abc), join4(""));
35055
35140
  var base583 = genBase583("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
35056
35141
  var base58flickr3 = genBase583("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ");
35057
35142
  var base58xrp3 = genBase583("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");
35058
35143
  var createBase58check2 = (sha2565) => chain3(checksum2(4, (data) => sha2565(sha2565(data))), base583);
35059
- var BECH_ALPHABET3 = chain3(alphabet3("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join5(""));
35144
+ var BECH_ALPHABET3 = chain3(alphabet3("qpzry9x8gf2tvdw0s3jn54khce6mua7l"), join4(""));
35060
35145
  var POLYMOD_GENERATORS3 = [996825010, 642813549, 513874426, 1027748829, 705979059];
35061
35146
  function bech32Polymod3(pre) {
35062
35147
  const b = pre >> 25;
@@ -35160,7 +35245,7 @@ var hexBuiltin2 = {
35160
35245
  return Uint8Array.fromHex(s);
35161
35246
  }
35162
35247
  };
35163
- var hex3 = hasHexBuiltin3 ? hexBuiltin2 : chain3(radix23(4), alphabet3("0123456789abcdef"), join5(""), normalize3((s) => {
35248
+ var hex3 = hasHexBuiltin3 ? hexBuiltin2 : chain3(radix23(4), alphabet3("0123456789abcdef"), join4(""), normalize3((s) => {
35164
35249
  if (typeof s !== "string" || s.length % 2 !== 0)
35165
35250
  throw new TypeError(`hex.decode: expected string, got ${typeof s} with length ${s.length}`);
35166
35251
  return s.toLowerCase();
@@ -38016,14 +38101,14 @@ class HDKey2 {
38016
38101
  }
38017
38102
  this.pubHash = hash1602(this._publicKey);
38018
38103
  }
38019
- derive(path2) {
38020
- if (!/^[mM]'?/.test(path2)) {
38104
+ derive(path) {
38105
+ if (!/^[mM]'?/.test(path)) {
38021
38106
  throw new Error('Path must start with "m" or "M"');
38022
38107
  }
38023
- if (/^[mM]'?$/.test(path2)) {
38108
+ if (/^[mM]'?$/.test(path)) {
38024
38109
  return this;
38025
38110
  }
38026
- const parts = path2.replace(/^[mM]'?\//, "").split("/");
38111
+ const parts = path.replace(/^[mM]'?\//, "").split("/");
38027
38112
  let child = this;
38028
38113
  for (const c of parts) {
38029
38114
  const m2 = /^(\d+)('?)$/.exec(c);
@@ -38382,7 +38467,7 @@ function bt(s, t) {
38382
38467
  case 2:
38383
38468
  return is(s, t, r);
38384
38469
  case 3:
38385
- return os3(s, t, r);
38470
+ return os2(s, t, r);
38386
38471
  case 4:
38387
38472
  return as2(s, t, r);
38388
38473
  case 5:
@@ -38433,7 +38518,7 @@ function is(s, t, e) {
38433
38518
  throw new Error("Byte string length exceeds data length");
38434
38519
  return { value: new Uint8Array(s.buffer, s.byteOffset + r, n), offset: r + n };
38435
38520
  }
38436
- function os3(s, t, e) {
38521
+ function os2(s, t, e) {
38437
38522
  const { value: n, offset: r } = ot2(s, t, e);
38438
38523
  if (r + n > s.byteLength)
38439
38524
  throw new Error("String length exceeds data length");
@@ -40203,25 +40288,25 @@ function createCocodClient(options = {}) {
40203
40288
  return proc;
40204
40289
  });
40205
40290
  let startPromise = null;
40206
- async function fetchJson(path2, init = {}) {
40291
+ async function fetchJson(path, init = {}) {
40207
40292
  const method = init.method || "GET";
40208
40293
  const requestInit = {
40209
40294
  ...init,
40210
40295
  unix: socketPath
40211
40296
  };
40212
- const response = await fetchImpl(`http://localhost${path2}`, requestInit);
40297
+ const response = await fetchImpl(`http://localhost${path}`, requestInit);
40213
40298
  const rawText = await response.text();
40214
40299
  if (!rawText.trim()) {
40215
- throw new CocodHttpError(response.ok ? 502 : response.status, `Empty response from cocod for ${method} ${path2}`);
40300
+ throw new CocodHttpError(response.ok ? 502 : response.status, `Empty response from cocod for ${method} ${path}`);
40216
40301
  }
40217
40302
  let data;
40218
40303
  try {
40219
40304
  data = JSON.parse(rawText);
40220
40305
  } catch {
40221
- throw new CocodHttpError(response.ok ? 502 : response.status, `Invalid JSON response from cocod for ${method} ${path2}`);
40306
+ throw new CocodHttpError(response.ok ? 502 : response.status, `Invalid JSON response from cocod for ${method} ${path}`);
40222
40307
  }
40223
40308
  if (!data || typeof data !== "object") {
40224
- throw new CocodHttpError(response.ok ? 502 : response.status, `Unexpected response shape from cocod for ${method} ${path2}`);
40309
+ throw new CocodHttpError(response.ok ? 502 : response.status, `Unexpected response shape from cocod for ${method} ${path}`);
40225
40310
  }
40226
40311
  const errorMessage = toErrorText(data.error);
40227
40312
  if (errorMessage) {
@@ -40272,13 +40357,13 @@ function createCocodClient(options = {}) {
40272
40357
  }
40273
40358
  await startPromise;
40274
40359
  }
40275
- async function callDaemon(path2, init = {}) {
40360
+ async function callDaemon(path, init = {}) {
40276
40361
  await ensureDaemonRunning();
40277
- const response = await fetchJson(path2, init);
40362
+ const response = await fetchJson(path, init);
40278
40363
  return response.output;
40279
40364
  }
40280
- function post(path2, body) {
40281
- return callDaemon(path2, {
40365
+ function post(path, body) {
40366
+ return callDaemon(path, {
40282
40367
  method: "POST",
40283
40368
  headers: { "Content-Type": "application/json" },
40284
40369
  body: JSON.stringify(body)
@@ -40489,6 +40574,7 @@ function createModelService(modelManager) {
40489
40574
  // src/daemon/http/index.ts
40490
40575
  init_dist3();
40491
40576
  import { randomBytes as randomBytes5 } from "crypto";
40577
+ import { Readable } from "stream";
40492
40578
  function generateApiKey() {
40493
40579
  const bytes4 = randomBytes5(24);
40494
40580
  return `sk-${bytes4.toString("hex")}`;
@@ -41154,7 +41240,7 @@ function createDaemonRequestHandler(deps) {
41154
41240
  try {
41155
41241
  await deps.ensureProvidersBootstrapped();
41156
41242
  logger3.log("Routing request with path: ", url2.pathname);
41157
- await routeRequestsToNodeResponse({
41243
+ const response = await routeRequests({
41158
41244
  modelId,
41159
41245
  requestBody,
41160
41246
  path: url2.pathname,
@@ -41169,9 +41255,52 @@ function createDaemonRequestHandler(deps) {
41169
41255
  mode: deps.mode,
41170
41256
  usageTrackingDriver: deps.usageTrackingDriver,
41171
41257
  sdkStore: deps.store,
41172
- providerManager: deps.providerManager,
41173
- res
41258
+ providerManager: deps.providerManager
41259
+ });
41260
+ res.statusCode = response.status;
41261
+ response.headers.forEach((value, key) => {
41262
+ res.setHeader(key, value);
41174
41263
  });
41264
+ const finalize6 = response.finalize;
41265
+ if (!response.body) {
41266
+ res.end();
41267
+ if (finalize6) {
41268
+ try {
41269
+ await finalize6();
41270
+ } catch (err) {
41271
+ logger3.error(`[daemon] finalize error: ${toErrorMessage(err)}`);
41272
+ }
41273
+ }
41274
+ return;
41275
+ }
41276
+ const nodeReadable = Readable.fromWeb(response.body);
41277
+ await new Promise((resolve, reject) => {
41278
+ let settled = false;
41279
+ const finish = () => {
41280
+ if (settled)
41281
+ return;
41282
+ settled = true;
41283
+ resolve();
41284
+ };
41285
+ const fail = (err) => {
41286
+ if (settled)
41287
+ return;
41288
+ settled = true;
41289
+ reject(err);
41290
+ };
41291
+ res.once("finish", finish);
41292
+ res.once("close", finish);
41293
+ res.once("error", fail);
41294
+ nodeReadable.once("error", fail);
41295
+ nodeReadable.pipe(res);
41296
+ });
41297
+ if (finalize6) {
41298
+ try {
41299
+ await finalize6();
41300
+ } catch (err) {
41301
+ logger3.error(`[daemon] finalize error: ${toErrorMessage(err)}`);
41302
+ }
41303
+ }
41175
41304
  return;
41176
41305
  } catch (error) {
41177
41306
  const message = error instanceof Error ? error.message : String(error);
@@ -41196,16 +41325,16 @@ function createDaemonRequestHandler(deps) {
41196
41325
  }
41197
41326
 
41198
41327
  // src/integrations/opencode.ts
41199
- import { existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
41328
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
41200
41329
  import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
41201
41330
  import { dirname as dirname4 } from "path";
41202
41331
 
41203
41332
  // src/integrations/registry.ts
41204
41333
  import { randomBytes as randomBytes6 } from "crypto";
41205
- import { join as join6 } from "path";
41334
+ import { join as join5 } from "path";
41206
41335
 
41207
41336
  // src/integrations/pi.ts
41208
- import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
41337
+ import { existsSync as existsSync3, mkdirSync } from "fs";
41209
41338
  import { readFile, writeFile } from "fs/promises";
41210
41339
  import { dirname } from "path";
41211
41340
  async function installPiIntegration(config, store, integrationConfig) {
@@ -41235,7 +41364,7 @@ Installing routstr models in pi models.json...`);
41235
41364
  }
41236
41365
  let piConfig = {};
41237
41366
  try {
41238
- if (existsSync4(configPath)) {
41367
+ if (existsSync3(configPath)) {
41239
41368
  const content2 = await readFile(configPath, "utf-8");
41240
41369
  piConfig = JSON.parse(content2);
41241
41370
  }
@@ -41246,7 +41375,7 @@ Installing routstr models in pi models.json...`);
41246
41375
  piConfig.providers = {};
41247
41376
  }
41248
41377
  try {
41249
- mkdirSync2(dirname(configPath), { recursive: true });
41378
+ mkdirSync(dirname(configPath), { recursive: true });
41250
41379
  const response = await fetch(`http://localhost:${port}/models`);
41251
41380
  const data = await response.json();
41252
41381
  const models = data.output?.models || [];
@@ -41271,7 +41400,7 @@ Installing routstr models in pi models.json...`);
41271
41400
  }
41272
41401
 
41273
41402
  // src/integrations/openclaw.ts
41274
- import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
41403
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
41275
41404
  import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
41276
41405
  import { dirname as dirname2 } from "path";
41277
41406
  var OPENCLAW_PROVIDER_ID = "routstr";
@@ -41303,7 +41432,7 @@ Installing routstr models in openclaw.json...`);
41303
41432
  }
41304
41433
  let openclawConfig = {};
41305
41434
  try {
41306
- if (existsSync5(configPath)) {
41435
+ if (existsSync4(configPath)) {
41307
41436
  const content2 = await readFile2(configPath, "utf-8");
41308
41437
  openclawConfig = JSON.parse(content2);
41309
41438
  }
@@ -41323,7 +41452,7 @@ Installing routstr models in openclaw.json...`);
41323
41452
  openclawConfig.agents.defaults = {};
41324
41453
  }
41325
41454
  try {
41326
- mkdirSync3(dirname2(configPath), { recursive: true });
41455
+ mkdirSync2(dirname2(configPath), { recursive: true });
41327
41456
  const response = await fetch(`http://localhost:${port}/models`);
41328
41457
  const data = await response.json();
41329
41458
  const models = data.output?.models || [];
@@ -41364,7 +41493,7 @@ Installing routstr models in openclaw.json...`);
41364
41493
  }
41365
41494
 
41366
41495
  // src/integrations/claudecode.ts
41367
- import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
41496
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
41368
41497
  import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
41369
41498
  import { dirname as dirname3 } from "path";
41370
41499
  async function installClaudeCodeIntegration(config, store, integrationConfig) {
@@ -41393,7 +41522,7 @@ Installing routstr configuration in ${configPath}...`);
41393
41522
  }
41394
41523
  let settings = {};
41395
41524
  try {
41396
- if (existsSync6(configPath)) {
41525
+ if (existsSync5(configPath)) {
41397
41526
  const content2 = await readFile3(configPath, "utf-8");
41398
41527
  settings = JSON.parse(content2);
41399
41528
  }
@@ -41409,7 +41538,7 @@ Installing routstr configuration in ${configPath}...`);
41409
41538
  settings.env["ANTHROPIC_DEFAULT_OPUS_MODEL"] = "claude-opus-4.7";
41410
41539
  settings.env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = "minimax-m2.7";
41411
41540
  try {
41412
- mkdirSync4(dirname3(configPath), { recursive: true });
41541
+ mkdirSync3(dirname3(configPath), { recursive: true });
41413
41542
  await writeFile3(configPath, JSON.stringify(settings, null, 2));
41414
41543
  logger3.log(`Successfully updated ${configPath} with routstr settings.`);
41415
41544
  } catch (error) {
@@ -41426,22 +41555,22 @@ var CLIENT_CONFIGS = {
41426
41555
  opencode: {
41427
41556
  clientId: "opencode",
41428
41557
  name: "OpenCode",
41429
- configPath: join6(process.env.HOME || "", ".config/opencode/opencode.json")
41558
+ configPath: join5(process.env.HOME || "", ".config/opencode/opencode.json")
41430
41559
  },
41431
41560
  "pi-agent": {
41432
41561
  clientId: "pi-agent",
41433
41562
  name: "Pi Agent",
41434
- configPath: join6(process.env.HOME || "", ".pi/agent/models.json")
41563
+ configPath: join5(process.env.HOME || "", ".pi/agent/models.json")
41435
41564
  },
41436
41565
  openclaw: {
41437
41566
  clientId: "openclaw",
41438
41567
  name: "OpenClaw",
41439
- configPath: join6(process.env.HOME || "", ".openclaw/openclaw.json")
41568
+ configPath: join5(process.env.HOME || "", ".openclaw/openclaw.json")
41440
41569
  },
41441
41570
  "claude-code": {
41442
41571
  clientId: "claude-code",
41443
41572
  name: "Claude Code",
41444
- configPath: join6(process.env.HOME || "", ".claude/settings.json")
41573
+ configPath: join5(process.env.HOME || "", ".claude/settings.json")
41445
41574
  }
41446
41575
  };
41447
41576
  var CLIENT_INTEGRATIONS = {
@@ -41492,7 +41621,7 @@ Installing routstr models in opencode.json...`);
41492
41621
  }
41493
41622
  let opencodeConfig;
41494
41623
  try {
41495
- if (existsSync7(configPath)) {
41624
+ if (existsSync6(configPath)) {
41496
41625
  const content2 = await readFile4(configPath, "utf-8");
41497
41626
  opencodeConfig = JSON.parse(content2);
41498
41627
  } else {
@@ -41505,7 +41634,7 @@ Installing routstr models in opencode.json...`);
41505
41634
  opencodeConfig.provider = {};
41506
41635
  }
41507
41636
  try {
41508
- mkdirSync5(dirname4(configPath), { recursive: true });
41637
+ mkdirSync4(dirname4(configPath), { recursive: true });
41509
41638
  const response = await fetch(`http://localhost:${port}/models`);
41510
41639
  const data = await response.json();
41511
41640
  const models = data.output?.models || [];
@@ -41583,7 +41712,7 @@ async function main() {
41583
41712
  }));
41584
41713
  Bun.write(PID_FILE, String(process.pid));
41585
41714
  try {
41586
- if (existsSync8(SOCKET_PATH)) {
41715
+ if (existsSync7(SOCKET_PATH)) {
41587
41716
  Bun.spawn(["rm", SOCKET_PATH]);
41588
41717
  }
41589
41718
  } catch {}