routstrd 0.3.2 → 0.3.3
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/bun.lock +2 -2
- package/dist/daemon/index.js +787 -135
- package/dist/index.js +61 -275
- package/package.json +2 -2
- package/src/daemon/http/usage-summary.test.ts +259 -0
- package/src/daemon/http/usage-summary.ts +361 -0
- package/src/daemon/request-response-log-sink.ts +269 -0
- package/src/tui/usage/app.ts +2 -2
- package/src/tui/usage/data.ts +12 -181
- package/src/tui/usage/render.ts +57 -107
- package/src/tui/usage/types.ts +4 -45
- package/src/utils/config.ts +8 -0
package/dist/daemon/index.js
CHANGED
|
@@ -21081,12 +21081,12 @@ function toBytes7(data) {
|
|
|
21081
21081
|
}
|
|
21082
21082
|
function concatBytes8(...arrays) {
|
|
21083
21083
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
21084
|
-
let
|
|
21084
|
+
let pad3 = 0;
|
|
21085
21085
|
arrays.forEach((a) => {
|
|
21086
21086
|
if (!u8a7(a))
|
|
21087
21087
|
throw new Error("Uint8Array expected");
|
|
21088
|
-
r.set(a,
|
|
21089
|
-
|
|
21088
|
+
r.set(a, pad3);
|
|
21089
|
+
pad3 += a.length;
|
|
21090
21090
|
});
|
|
21091
21091
|
return r;
|
|
21092
21092
|
}
|
|
@@ -21474,12 +21474,12 @@ function ensureBytes3(title, hex4, expectedLength) {
|
|
|
21474
21474
|
}
|
|
21475
21475
|
function concatBytes9(...arrays) {
|
|
21476
21476
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
21477
|
-
let
|
|
21477
|
+
let pad3 = 0;
|
|
21478
21478
|
arrays.forEach((a) => {
|
|
21479
21479
|
if (!u8a8(a))
|
|
21480
21480
|
throw new Error("Uint8Array expected");
|
|
21481
|
-
r.set(a,
|
|
21482
|
-
|
|
21481
|
+
r.set(a, pad3);
|
|
21482
|
+
pad3 += a.length;
|
|
21483
21483
|
});
|
|
21484
21484
|
return r;
|
|
21485
21485
|
}
|
|
@@ -22715,16 +22715,16 @@ var init_hmac3 = __esm(() => {
|
|
|
22715
22715
|
this.blockLen = this.iHash.blockLen;
|
|
22716
22716
|
this.outputLen = this.iHash.outputLen;
|
|
22717
22717
|
const blockLen = this.blockLen;
|
|
22718
|
-
const
|
|
22719
|
-
|
|
22720
|
-
for (let i4 = 0;i4 <
|
|
22721
|
-
|
|
22722
|
-
this.iHash.update(
|
|
22718
|
+
const pad3 = new Uint8Array(blockLen);
|
|
22719
|
+
pad3.set(key.length > blockLen ? hash6.create().update(key).digest() : key);
|
|
22720
|
+
for (let i4 = 0;i4 < pad3.length; i4++)
|
|
22721
|
+
pad3[i4] ^= 54;
|
|
22722
|
+
this.iHash.update(pad3);
|
|
22723
22723
|
this.oHash = hash6.create();
|
|
22724
|
-
for (let i4 = 0;i4 <
|
|
22725
|
-
|
|
22726
|
-
this.oHash.update(
|
|
22727
|
-
|
|
22724
|
+
for (let i4 = 0;i4 < pad3.length; i4++)
|
|
22725
|
+
pad3[i4] ^= 54 ^ 92;
|
|
22726
|
+
this.oHash.update(pad3);
|
|
22727
|
+
pad3.fill(0);
|
|
22728
22728
|
}
|
|
22729
22729
|
update(buf) {
|
|
22730
22730
|
exists6(this);
|
|
@@ -22995,12 +22995,12 @@ function toBytes8(data) {
|
|
|
22995
22995
|
}
|
|
22996
22996
|
function concatBytes10(...arrays) {
|
|
22997
22997
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
22998
|
-
let
|
|
22998
|
+
let pad3 = 0;
|
|
22999
22999
|
arrays.forEach((a) => {
|
|
23000
23000
|
if (!u8a9(a))
|
|
23001
23001
|
throw new Error("Uint8Array expected");
|
|
23002
|
-
r.set(a,
|
|
23003
|
-
|
|
23002
|
+
r.set(a, pad3);
|
|
23003
|
+
pad3 += a.length;
|
|
23004
23004
|
});
|
|
23005
23005
|
return r;
|
|
23006
23006
|
}
|
|
@@ -24706,7 +24706,7 @@ class Poly13052 {
|
|
|
24706
24706
|
h2[9] = d9;
|
|
24707
24707
|
}
|
|
24708
24708
|
finalize() {
|
|
24709
|
-
const { h: h2, pad:
|
|
24709
|
+
const { h: h2, pad: pad3 } = this;
|
|
24710
24710
|
const g2 = new Uint16Array(10);
|
|
24711
24711
|
let c = h2[1] >>> 13;
|
|
24712
24712
|
h2[1] &= 8191;
|
|
@@ -24745,10 +24745,10 @@ class Poly13052 {
|
|
|
24745
24745
|
h2[5] = (h2[6] >>> 2 | h2[7] << 11) & 65535;
|
|
24746
24746
|
h2[6] = (h2[7] >>> 5 | h2[8] << 8) & 65535;
|
|
24747
24747
|
h2[7] = (h2[8] >>> 8 | h2[9] << 5) & 65535;
|
|
24748
|
-
let f = h2[0] +
|
|
24748
|
+
let f = h2[0] + pad3[0];
|
|
24749
24749
|
h2[0] = f & 65535;
|
|
24750
24750
|
for (let i4 = 1;i4 < 8; i4++) {
|
|
24751
|
-
f = (h2[i4] +
|
|
24751
|
+
f = (h2[i4] + pad3[i4] | 0) + (f >>> 16) | 0;
|
|
24752
24752
|
h2[i4] = f & 65535;
|
|
24753
24753
|
}
|
|
24754
24754
|
}
|
|
@@ -25200,16 +25200,16 @@ var init_hmac4 = __esm(() => {
|
|
|
25200
25200
|
this.blockLen = this.iHash.blockLen;
|
|
25201
25201
|
this.outputLen = this.iHash.outputLen;
|
|
25202
25202
|
const blockLen = this.blockLen;
|
|
25203
|
-
const
|
|
25204
|
-
|
|
25205
|
-
for (let i4 = 0;i4 <
|
|
25206
|
-
|
|
25207
|
-
this.iHash.update(
|
|
25203
|
+
const pad3 = new Uint8Array(blockLen);
|
|
25204
|
+
pad3.set(key.length > blockLen ? hash7.create().update(key).digest() : key);
|
|
25205
|
+
for (let i4 = 0;i4 < pad3.length; i4++)
|
|
25206
|
+
pad3[i4] ^= 54;
|
|
25207
|
+
this.iHash.update(pad3);
|
|
25208
25208
|
this.oHash = hash7.create();
|
|
25209
|
-
for (let i4 = 0;i4 <
|
|
25210
|
-
|
|
25211
|
-
this.oHash.update(
|
|
25212
|
-
|
|
25209
|
+
for (let i4 = 0;i4 < pad3.length; i4++)
|
|
25210
|
+
pad3[i4] ^= 54 ^ 92;
|
|
25211
|
+
this.oHash.update(pad3);
|
|
25212
|
+
pad3.fill(0);
|
|
25213
25213
|
}
|
|
25214
25214
|
update(buf) {
|
|
25215
25215
|
_assert_default3.exists(this);
|
|
@@ -25916,7 +25916,7 @@ function writeU16BE2(num) {
|
|
|
25916
25916
|
new DataView(arr.buffer).setUint16(0, num, false);
|
|
25917
25917
|
return arr;
|
|
25918
25918
|
}
|
|
25919
|
-
function
|
|
25919
|
+
function pad3(plaintext) {
|
|
25920
25920
|
const unpadded = utf8Encoder5.encode(plaintext);
|
|
25921
25921
|
const unpaddedLen = unpadded.length;
|
|
25922
25922
|
const prefix = writeU16BE2(unpaddedLen);
|
|
@@ -25964,7 +25964,7 @@ function decodePayload2(payload) {
|
|
|
25964
25964
|
}
|
|
25965
25965
|
function encrypt23(plaintext, conversationKey, nonce = randomBytes7(32)) {
|
|
25966
25966
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys2(conversationKey, nonce);
|
|
25967
|
-
const padded =
|
|
25967
|
+
const padded = pad3(plaintext);
|
|
25968
25968
|
const ciphertext = chacha202(chacha_key, chacha_nonce, padded);
|
|
25969
25969
|
const mac = hmacAad2(hmac_key, ciphertext, nonce);
|
|
25970
25970
|
return base644.encode(concatBytes10(new Uint8Array([2]), nonce, ciphertext, mac));
|
|
@@ -41756,6 +41756,11 @@ var NoProvidersAvailableError = class extends Error {
|
|
|
41756
41756
|
this.name = "NoProvidersAvailableError";
|
|
41757
41757
|
}
|
|
41758
41758
|
};
|
|
41759
|
+
var DEFAULT_NOSTR_RELAYS = [
|
|
41760
|
+
"wss://relay.damus.io",
|
|
41761
|
+
"wss://nos.lol",
|
|
41762
|
+
"wss://relay.routstr.com"
|
|
41763
|
+
];
|
|
41759
41764
|
var ModelManager = class _ModelManager {
|
|
41760
41765
|
constructor(adapter, config = {}) {
|
|
41761
41766
|
this.adapter = adapter;
|
|
@@ -41899,15 +41904,11 @@ var ModelManager = class _ModelManager {
|
|
|
41899
41904
|
}
|
|
41900
41905
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
41901
41906
|
}
|
|
41902
|
-
getNostrRelays(
|
|
41903
|
-
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays :
|
|
41907
|
+
getNostrRelays() {
|
|
41908
|
+
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : DEFAULT_NOSTR_RELAYS;
|
|
41904
41909
|
}
|
|
41905
41910
|
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
41906
|
-
const relays2 = this.getNostrRelays(
|
|
41907
|
-
"wss://relay.primal.net",
|
|
41908
|
-
"wss://nos.lol",
|
|
41909
|
-
"wss://relay.damus.io"
|
|
41910
|
-
]);
|
|
41911
|
+
const relays2 = this.getNostrRelays();
|
|
41911
41912
|
const cached = await this.getCachedNostrEvents({ kinds: [kind] }, this.cacheTTL, forceRefresh);
|
|
41912
41913
|
let sessionEvents = cached;
|
|
41913
41914
|
if (cached.length === 0) {
|
|
@@ -42038,12 +42039,7 @@ var ModelManager = class _ModelManager {
|
|
|
42038
42039
|
const cached = await this.getCachedNostrEvents({ kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] }, this.cacheTTL, forceRefresh);
|
|
42039
42040
|
let sessionEvents = cached;
|
|
42040
42041
|
if (cached.length === 0) {
|
|
42041
|
-
const lgtmRelays = this.getNostrRelays(
|
|
42042
|
-
"wss://relay.primal.net",
|
|
42043
|
-
"wss://nos.lol",
|
|
42044
|
-
"wss://relay.damus.io",
|
|
42045
|
-
"wss://relay.routstr.com"
|
|
42046
|
-
]);
|
|
42042
|
+
const lgtmRelays = this.getNostrRelays();
|
|
42047
42043
|
const pool3 = new RelayPool;
|
|
42048
42044
|
const timeoutMs = 5000;
|
|
42049
42045
|
await new Promise((resolve) => {
|
|
@@ -42241,11 +42237,7 @@ var ModelManager = class _ModelManager {
|
|
|
42241
42237
|
return cachedModels;
|
|
42242
42238
|
}
|
|
42243
42239
|
}
|
|
42244
|
-
const relays2 = this.getNostrRelays(
|
|
42245
|
-
"wss://relay.damus.io",
|
|
42246
|
-
"wss://nos.lol",
|
|
42247
|
-
"wss://relay.routstr.com"
|
|
42248
|
-
]);
|
|
42240
|
+
const relays2 = this.getNostrRelays();
|
|
42249
42241
|
const cached = await this.getCachedNostrEvents({ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] }, this.cacheTTL, forceRefresh);
|
|
42250
42242
|
let sessionEvents = cached;
|
|
42251
42243
|
if (cached.length === 0) {
|
|
@@ -43109,8 +43101,8 @@ var BalanceManager = class _BalanceManager {
|
|
|
43109
43101
|
const totalMintBalance = Object.values(balanceState.mintBalances).reduce((sum, value) => sum + value, 0);
|
|
43110
43102
|
const targetProviderBalance = balanceState.providerBalances[baseUrl] || 0;
|
|
43111
43103
|
const refundableProviderBalance = Object.entries(balanceState.providerBalances).filter(([providerBaseUrl]) => providerBaseUrl !== baseUrl).reduce((sum, [, value]) => sum + value, 0);
|
|
43112
|
-
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount <
|
|
43113
|
-
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount);
|
|
43104
|
+
if (totalMintBalance + targetProviderBalance < adjustedAmount && totalMintBalance + targetProviderBalance + refundableProviderBalance >= adjustedAmount && retryCount < 3) {
|
|
43105
|
+
await this._refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, adjustedAmount);
|
|
43114
43106
|
return this.createProviderToken({
|
|
43115
43107
|
...options,
|
|
43116
43108
|
retryCount: retryCount + 1
|
|
@@ -43229,27 +43221,42 @@ var BalanceManager = class _BalanceManager {
|
|
|
43229
43221
|
}
|
|
43230
43222
|
return candidates;
|
|
43231
43223
|
}
|
|
43232
|
-
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount) {
|
|
43224
|
+
async _refundOtherProvidersForTopUp(baseUrl, mintUrl, retryCount, requiredAmount) {
|
|
43233
43225
|
const apiKeyDistribution = this.storageAdapter.getApiKeyDistribution();
|
|
43234
43226
|
const forceRefund = retryCount >= 2;
|
|
43235
|
-
const
|
|
43236
|
-
|
|
43237
|
-
|
|
43238
|
-
|
|
43239
|
-
|
|
43227
|
+
const candidates = apiKeyDistribution.filter((apiKey) => apiKey.baseUrl !== baseUrl && apiKey.amount > 0).map((apiKey) => {
|
|
43228
|
+
const full = this.storageAdapter.getApiKey(apiKey.baseUrl);
|
|
43229
|
+
return {
|
|
43230
|
+
baseUrl: apiKey.baseUrl,
|
|
43231
|
+
amount: apiKey.amount,
|
|
43232
|
+
lastUsed: full?.lastUsed ?? 0,
|
|
43233
|
+
key: full?.key
|
|
43234
|
+
};
|
|
43235
|
+
}).filter((c) => c.key != null).sort((a, b) => a.lastUsed - b.lastUsed);
|
|
43236
|
+
if (candidates.length === 0)
|
|
43237
|
+
return;
|
|
43238
|
+
if (forceRefund) {
|
|
43239
|
+
for (const candidate of candidates) {
|
|
43240
|
+
await this.refundApiKey({
|
|
43241
|
+
mintUrl,
|
|
43242
|
+
baseUrl: candidate.baseUrl,
|
|
43243
|
+
apiKey: candidate.key,
|
|
43244
|
+
forceRefund: true
|
|
43245
|
+
});
|
|
43246
|
+
const newState = await this.getBalanceState();
|
|
43247
|
+
const newAvailable = (newState.mintBalances[mintUrl] || 0) + (newState.providerBalances[baseUrl] || 0);
|
|
43248
|
+
if (newAvailable >= requiredAmount) {
|
|
43249
|
+
this.logger.log(`_refundOtherProvidersForTopUp: freed enough balance (${newAvailable} >= ${requiredAmount}), stopping early`);
|
|
43250
|
+
return;
|
|
43251
|
+
}
|
|
43240
43252
|
}
|
|
43241
|
-
|
|
43253
|
+
} else {
|
|
43254
|
+
await Promise.allSettled(candidates.map((candidate) => this.refundApiKey({
|
|
43242
43255
|
mintUrl,
|
|
43243
|
-
baseUrl:
|
|
43244
|
-
apiKey:
|
|
43245
|
-
forceRefund
|
|
43246
|
-
});
|
|
43247
|
-
return { baseUrl: apiKeyEntry.baseUrl, success: result.success };
|
|
43248
|
-
}));
|
|
43249
|
-
for (const result of apiKeyRefundResults) {
|
|
43250
|
-
if (result.status === "fulfilled" && result.value.success) {
|
|
43251
|
-
this.storageAdapter.updateApiKeyBalance(result.value.baseUrl, 0);
|
|
43252
|
-
}
|
|
43256
|
+
baseUrl: candidate.baseUrl,
|
|
43257
|
+
apiKey: candidate.key,
|
|
43258
|
+
forceRefund: false
|
|
43259
|
+
})));
|
|
43253
43260
|
}
|
|
43254
43261
|
}
|
|
43255
43262
|
async _postTopUp(baseUrl, storedToken, cashuToken) {
|
|
@@ -43975,6 +43982,90 @@ var SDK_STORAGE_KEYS = {
|
|
|
43975
43982
|
LAST_FAILED: "last_failed",
|
|
43976
43983
|
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
43977
43984
|
};
|
|
43985
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
43986
|
+
var jsGroupKey = (entry, groupBy, tzOffsetMinutes) => {
|
|
43987
|
+
switch (groupBy) {
|
|
43988
|
+
case "modelId":
|
|
43989
|
+
return entry.modelId ?? null;
|
|
43990
|
+
case "baseUrl":
|
|
43991
|
+
return entry.baseUrl ?? null;
|
|
43992
|
+
case "client":
|
|
43993
|
+
return entry.client ?? null;
|
|
43994
|
+
case "sessionId":
|
|
43995
|
+
return entry.sessionId ?? null;
|
|
43996
|
+
case "provider":
|
|
43997
|
+
return entry.provider ?? null;
|
|
43998
|
+
case "day": {
|
|
43999
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 60000);
|
|
44000
|
+
return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
|
|
44001
|
+
}
|
|
44002
|
+
case "hour": {
|
|
44003
|
+
const d = new Date(entry.timestamp - tzOffsetMinutes * 60000);
|
|
44004
|
+
return pad2(d.getUTCHours());
|
|
44005
|
+
}
|
|
44006
|
+
}
|
|
44007
|
+
};
|
|
44008
|
+
var reduceAggregate = (entries, options = {}) => {
|
|
44009
|
+
const emptyRow = (group2) => ({
|
|
44010
|
+
group: group2,
|
|
44011
|
+
requests: 0,
|
|
44012
|
+
promptTokens: 0,
|
|
44013
|
+
completionTokens: 0,
|
|
44014
|
+
totalTokens: 0,
|
|
44015
|
+
cost: 0,
|
|
44016
|
+
satsCost: 0,
|
|
44017
|
+
baseMsats: 0,
|
|
44018
|
+
inputMsats: 0,
|
|
44019
|
+
outputMsats: 0,
|
|
44020
|
+
totalMsats: 0,
|
|
44021
|
+
totalUsd: 0,
|
|
44022
|
+
cacheReadInputTokens: 0,
|
|
44023
|
+
cacheCreationInputTokens: 0,
|
|
44024
|
+
cacheReadMsats: 0,
|
|
44025
|
+
cacheCreationMsats: 0
|
|
44026
|
+
});
|
|
44027
|
+
const accumulate = (row, entry) => {
|
|
44028
|
+
row.requests += 1;
|
|
44029
|
+
row.promptTokens += entry.promptTokens;
|
|
44030
|
+
row.completionTokens += entry.completionTokens;
|
|
44031
|
+
row.totalTokens += entry.totalTokens;
|
|
44032
|
+
row.cost += entry.cost;
|
|
44033
|
+
row.satsCost += entry.satsCost;
|
|
44034
|
+
row.baseMsats += entry.baseMsats ?? 0;
|
|
44035
|
+
row.inputMsats += entry.inputMsats ?? 0;
|
|
44036
|
+
row.outputMsats += entry.outputMsats ?? 0;
|
|
44037
|
+
row.totalMsats += entry.totalMsats ?? 0;
|
|
44038
|
+
row.totalUsd += entry.totalUsd ?? 0;
|
|
44039
|
+
row.cacheReadInputTokens += entry.cacheReadInputTokens ?? 0;
|
|
44040
|
+
row.cacheCreationInputTokens += entry.cacheCreationInputTokens ?? 0;
|
|
44041
|
+
row.cacheReadMsats += entry.cacheReadMsats ?? 0;
|
|
44042
|
+
row.cacheCreationMsats += entry.cacheCreationMsats ?? 0;
|
|
44043
|
+
};
|
|
44044
|
+
if (!options.groupBy) {
|
|
44045
|
+
const total = emptyRow(null);
|
|
44046
|
+
for (const entry of entries)
|
|
44047
|
+
accumulate(total, entry);
|
|
44048
|
+
return [total];
|
|
44049
|
+
}
|
|
44050
|
+
const tz = options.tzOffsetMinutes ?? 0;
|
|
44051
|
+
const groups = /* @__PURE__ */ new Map;
|
|
44052
|
+
for (const entry of entries) {
|
|
44053
|
+
const key = jsGroupKey(entry, options.groupBy, tz);
|
|
44054
|
+
let row = groups.get(key);
|
|
44055
|
+
if (!row) {
|
|
44056
|
+
row = emptyRow(key);
|
|
44057
|
+
groups.set(key, row);
|
|
44058
|
+
}
|
|
44059
|
+
accumulate(row, entry);
|
|
44060
|
+
}
|
|
44061
|
+
const rows = [...groups.values()];
|
|
44062
|
+
if (options.groupBy === "day" || options.groupBy === "hour") {
|
|
44063
|
+
rows.sort((a, b) => (a.group ?? "").localeCompare(b.group ?? ""));
|
|
44064
|
+
} else {
|
|
44065
|
+
rows.sort((a, b) => b.satsCost - a.satsCost);
|
|
44066
|
+
}
|
|
44067
|
+
return rows;
|
|
44068
|
+
};
|
|
43978
44069
|
var DEFAULT_DB_NAME = "routstr-sdk";
|
|
43979
44070
|
var DEFAULT_STORE_NAME = "usage_tracking";
|
|
43980
44071
|
var MIGRATION_MARKER_KEY = "usage_tracking_migration_v1";
|
|
@@ -44034,6 +44125,9 @@ var matchesFilters = (entry, options = {}) => {
|
|
|
44034
44125
|
if (options.client && entry.client !== options.client) {
|
|
44035
44126
|
return false;
|
|
44036
44127
|
}
|
|
44128
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
44129
|
+
return false;
|
|
44130
|
+
}
|
|
44037
44131
|
if (options.provider && entry.provider !== options.provider) {
|
|
44038
44132
|
return false;
|
|
44039
44133
|
}
|
|
@@ -44129,6 +44223,10 @@ var createIndexedDBUsageTrackingDriver = (options = {}) => {
|
|
|
44129
44223
|
const results = await this.list(options2);
|
|
44130
44224
|
return results.length;
|
|
44131
44225
|
},
|
|
44226
|
+
async aggregate(options2 = {}) {
|
|
44227
|
+
const entries = await this.list(options2);
|
|
44228
|
+
return reduceAggregate(entries, options2);
|
|
44229
|
+
},
|
|
44132
44230
|
async deleteOlderThan(timestamp) {
|
|
44133
44231
|
await ensureMigrated();
|
|
44134
44232
|
const db = await getDb();
|
|
@@ -44184,6 +44282,9 @@ var matchesFilters2 = (entry, options = {}) => {
|
|
|
44184
44282
|
if (options.client && entry.client !== options.client) {
|
|
44185
44283
|
return false;
|
|
44186
44284
|
}
|
|
44285
|
+
if (options.clients && options.clients.length > 0 && (entry.client == null || !options.clients.includes(entry.client))) {
|
|
44286
|
+
return false;
|
|
44287
|
+
}
|
|
44187
44288
|
if (options.provider && entry.provider !== options.provider) {
|
|
44188
44289
|
return false;
|
|
44189
44290
|
}
|
|
@@ -44216,6 +44317,10 @@ var createMemoryUsageTrackingDriver = (seed = []) => {
|
|
|
44216
44317
|
async count(options = {}) {
|
|
44217
44318
|
return (await this.list(options)).length;
|
|
44218
44319
|
},
|
|
44320
|
+
async aggregate(options = {}) {
|
|
44321
|
+
const entries = [...store.values()].filter((entry) => matchesFilters2(entry, options));
|
|
44322
|
+
return reduceAggregate(entries, options);
|
|
44323
|
+
},
|
|
44219
44324
|
async deleteOlderThan(timestamp) {
|
|
44220
44325
|
let deleted = 0;
|
|
44221
44326
|
for (const [id, entry] of store.entries()) {
|
|
@@ -44947,13 +45052,14 @@ function hasUsageChanged(previous, next) {
|
|
|
44947
45052
|
function isInspectionComplete(responseIdCaptured, usage) {
|
|
44948
45053
|
return responseIdCaptured && !!usage && usage.totalTokens > 0 && typeof usage.totalMsats === "number" && !!usage.provider;
|
|
44949
45054
|
}
|
|
44950
|
-
async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
45055
|
+
async function inspectSSEWebStream(stream, onUsage, onResponseId, options) {
|
|
44951
45056
|
const reader = stream.getReader();
|
|
44952
45057
|
const decoder = new TextDecoder("utf-8");
|
|
44953
45058
|
let buffer = "";
|
|
44954
45059
|
let capturedUsage = null;
|
|
44955
45060
|
let capturedResponseId;
|
|
44956
45061
|
let responseIdCaptured = false;
|
|
45062
|
+
let rawChunkSequence = 0;
|
|
44957
45063
|
const inspectDataPayload = (jsonText) => {
|
|
44958
45064
|
const trimmed = jsonText.trim();
|
|
44959
45065
|
if (!trimmed || trimmed === "[DONE]") {
|
|
@@ -45031,7 +45137,9 @@ async function inspectSSEWebStream(stream, onUsage, onResponseId) {
|
|
|
45031
45137
|
if (done)
|
|
45032
45138
|
break;
|
|
45033
45139
|
if (value && value.byteLength > 0) {
|
|
45034
|
-
|
|
45140
|
+
const text = decoder.decode(value, { stream: true });
|
|
45141
|
+
options?.onRawChunk?.(value, rawChunkSequence++, text);
|
|
45142
|
+
buffer += text;
|
|
45035
45143
|
drainBufferedEvents();
|
|
45036
45144
|
}
|
|
45037
45145
|
}
|
|
@@ -45066,6 +45174,7 @@ var RoutstrClient = class {
|
|
|
45066
45174
|
this.mode = mode;
|
|
45067
45175
|
this.usageTrackingDriver = options.usageTrackingDriver;
|
|
45068
45176
|
this.sdkStore = options.sdkStore;
|
|
45177
|
+
this.requestResponseLogSink = options.requestResponseLogSink;
|
|
45069
45178
|
this.providerManager = options.providerManager ?? new ProviderManager(providerRegistry, this.sdkStore, this.logger);
|
|
45070
45179
|
}
|
|
45071
45180
|
walletAdapter;
|
|
@@ -45080,6 +45189,7 @@ var RoutstrClient = class {
|
|
|
45080
45189
|
usageTrackingDriver;
|
|
45081
45190
|
sdkStore;
|
|
45082
45191
|
logger;
|
|
45192
|
+
requestResponseLogSink;
|
|
45083
45193
|
getMode() {
|
|
45084
45194
|
return this.mode;
|
|
45085
45195
|
}
|
|
@@ -45232,6 +45342,7 @@ var RoutstrClient = class {
|
|
|
45232
45342
|
let usagePromise = Promise.resolve({});
|
|
45233
45343
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
45234
45344
|
const [clientStream, inspectStream] = response.body.tee();
|
|
45345
|
+
const requestResponseLogId = response.requestResponseLogId;
|
|
45235
45346
|
processedResponse = new Response(clientStream, {
|
|
45236
45347
|
status: response.status,
|
|
45237
45348
|
statusText: response.statusText,
|
|
@@ -45239,12 +45350,23 @@ var RoutstrClient = class {
|
|
|
45239
45350
|
});
|
|
45240
45351
|
processedResponse.baseUrl = response.baseUrl;
|
|
45241
45352
|
processedResponse.token = response.token;
|
|
45353
|
+
processedResponse.requestResponseLogId = requestResponseLogId;
|
|
45242
45354
|
usagePromise = inspectSSEWebStream(inspectStream, (usage) => {
|
|
45243
45355
|
capturedUsage = usage;
|
|
45244
45356
|
processedResponse.usage = usage;
|
|
45245
45357
|
}, (responseId) => {
|
|
45246
45358
|
capturedResponseId = responseId;
|
|
45247
45359
|
processedResponse.requestId = responseId;
|
|
45360
|
+
}, {
|
|
45361
|
+
onRawChunk: (_chunk, sequence, text) => {
|
|
45362
|
+
this.requestResponseLogSink?.logResponseChunk?.(requestResponseLogId, sequence, text);
|
|
45363
|
+
}
|
|
45364
|
+
}).then(async (result) => {
|
|
45365
|
+
await this.requestResponseLogSink?.logResponseEnd?.(requestResponseLogId);
|
|
45366
|
+
return result;
|
|
45367
|
+
}).catch(async (error) => {
|
|
45368
|
+
await this.requestResponseLogSink?.logResponseError?.(requestResponseLogId, error);
|
|
45369
|
+
throw error;
|
|
45248
45370
|
});
|
|
45249
45371
|
processedResponse.usagePromise = usagePromise;
|
|
45250
45372
|
}
|
|
@@ -45273,18 +45395,32 @@ var RoutstrClient = class {
|
|
|
45273
45395
|
const { path, method, body, baseUrl, token, headers } = params;
|
|
45274
45396
|
try {
|
|
45275
45397
|
const url2 = `${baseUrl.replace(/\/$/, "")}${path}`;
|
|
45398
|
+
const requestBodyText = body === undefined || method === "GET" ? undefined : JSON.stringify(body);
|
|
45399
|
+
const requestLogId = await this.requestResponseLogSink?.logRequest?.({
|
|
45400
|
+
method,
|
|
45401
|
+
url: url2,
|
|
45402
|
+
path,
|
|
45403
|
+
baseUrl,
|
|
45404
|
+
headers,
|
|
45405
|
+
body,
|
|
45406
|
+
rawBody: requestBodyText
|
|
45407
|
+
});
|
|
45276
45408
|
if (this.mode === "xcashu")
|
|
45277
45409
|
this._log("DEBUG", "HEADERS,", headers);
|
|
45278
45410
|
const response = await fetch(url2, {
|
|
45279
45411
|
method,
|
|
45280
45412
|
headers,
|
|
45281
|
-
body:
|
|
45413
|
+
body: requestBodyText
|
|
45282
45414
|
});
|
|
45283
45415
|
if (this.mode === "xcashu")
|
|
45284
45416
|
this._log("DEBUG", "response,", response);
|
|
45285
45417
|
response.baseUrl = baseUrl;
|
|
45286
45418
|
response.token = token;
|
|
45419
|
+
response.requestResponseLogId = requestLogId;
|
|
45420
|
+
await this.requestResponseLogSink?.logResponseStart?.(requestLogId, response);
|
|
45421
|
+
const contentType = response.headers.get("content-type") || "";
|
|
45287
45422
|
if (!response.ok) {
|
|
45423
|
+
this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
45288
45424
|
const requestId = response.headers.get("x-routstr-request-id") || undefined;
|
|
45289
45425
|
let bodyText;
|
|
45290
45426
|
try {
|
|
@@ -45294,6 +45430,9 @@ var RoutstrClient = class {
|
|
|
45294
45430
|
}
|
|
45295
45431
|
return await this._handleErrorResponse(params, token, response.status, requestId, this.mode === "xcashu" ? response.headers.get("x-cashu") ?? undefined : undefined, bodyText, params.retryCount ?? 0);
|
|
45296
45432
|
}
|
|
45433
|
+
if (!contentType.includes("text/event-stream")) {
|
|
45434
|
+
this.requestResponseLogSink?.logResponseBody?.(requestLogId, response.clone());
|
|
45435
|
+
}
|
|
45297
45436
|
return response;
|
|
45298
45437
|
} catch (error) {
|
|
45299
45438
|
if (isNetworkErrorMessage(error?.message || "")) {
|
|
@@ -45735,7 +45874,8 @@ async function resolveRouteRequestContext(options) {
|
|
|
45735
45874
|
usageTrackingDriver,
|
|
45736
45875
|
sdkStore,
|
|
45737
45876
|
providerManager: providedProviderManager,
|
|
45738
|
-
logger: logger3
|
|
45877
|
+
logger: logger3,
|
|
45878
|
+
requestResponseLogSink
|
|
45739
45879
|
} = options;
|
|
45740
45880
|
let modelManager;
|
|
45741
45881
|
let providers;
|
|
@@ -45782,17 +45922,12 @@ async function resolveRouteRequestContext(options) {
|
|
|
45782
45922
|
baseUrl = cheapest.baseUrl;
|
|
45783
45923
|
selectedModel = cheapest.model;
|
|
45784
45924
|
}
|
|
45785
|
-
const balances = await walletAdapter.getBalances();
|
|
45786
|
-
const totalBalance = Object.values(balances).reduce((sum, v3) => sum + v3, 0);
|
|
45787
|
-
if (totalBalance <= 0) {
|
|
45788
|
-
throw new Error("Wallet balance is empty. Add a mint and fund it before making requests.");
|
|
45789
|
-
}
|
|
45790
45925
|
const providerMints = providerRegistry.getProviderMints(baseUrl);
|
|
45791
|
-
const mintUrl = walletAdapter.getActiveMintUrl() || providerMints[0] || Object.keys(
|
|
45926
|
+
const mintUrl = walletAdapter.getActiveMintUrl() || providerMints[0] || Object.keys(await walletAdapter.getBalances())[0];
|
|
45792
45927
|
if (!mintUrl) {
|
|
45793
45928
|
throw new Error("No mint configured in wallet");
|
|
45794
45929
|
}
|
|
45795
|
-
const client2 = options.client ?? new RoutstrClient(walletAdapter, storageAdapter, providerRegistry, "min", mode, { usageTrackingDriver, sdkStore, providerManager, logger: logger3 });
|
|
45930
|
+
const client2 = options.client ?? new RoutstrClient(walletAdapter, storageAdapter, providerRegistry, "min", mode, { usageTrackingDriver, sdkStore, providerManager, logger: logger3, requestResponseLogSink });
|
|
45796
45931
|
const maxTokens = extractMaxTokens(requestBody);
|
|
45797
45932
|
const stream = extractStream(requestBody);
|
|
45798
45933
|
const proxiedBody = requestBody && typeof requestBody === "object" ? { ...requestBody } : {};
|
|
@@ -45881,6 +46016,11 @@ var NoProvidersAvailableError2 = class extends Error {
|
|
|
45881
46016
|
this.name = "NoProvidersAvailableError";
|
|
45882
46017
|
}
|
|
45883
46018
|
};
|
|
46019
|
+
var DEFAULT_NOSTR_RELAYS2 = [
|
|
46020
|
+
"wss://relay.damus.io",
|
|
46021
|
+
"wss://nos.lol",
|
|
46022
|
+
"wss://relay.routstr.com"
|
|
46023
|
+
];
|
|
45884
46024
|
var ModelManager2 = class _ModelManager2 {
|
|
45885
46025
|
constructor(adapter, config = {}) {
|
|
45886
46026
|
this.adapter = adapter;
|
|
@@ -46024,15 +46164,11 @@ var ModelManager2 = class _ModelManager2 {
|
|
|
46024
46164
|
}
|
|
46025
46165
|
return this.bootstrapFromHttp(torMode, forceRefresh);
|
|
46026
46166
|
}
|
|
46027
|
-
getNostrRelays(
|
|
46028
|
-
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays :
|
|
46167
|
+
getNostrRelays() {
|
|
46168
|
+
return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : DEFAULT_NOSTR_RELAYS2;
|
|
46029
46169
|
}
|
|
46030
46170
|
async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
|
|
46031
|
-
const relays3 = this.getNostrRelays(
|
|
46032
|
-
"wss://relay.primal.net",
|
|
46033
|
-
"wss://nos.lol",
|
|
46034
|
-
"wss://relay.damus.io"
|
|
46035
|
-
]);
|
|
46171
|
+
const relays3 = this.getNostrRelays();
|
|
46036
46172
|
const cached = await this.getCachedNostrEvents({ kinds: [kind] }, this.cacheTTL, forceRefresh);
|
|
46037
46173
|
let sessionEvents = cached;
|
|
46038
46174
|
if (cached.length === 0) {
|
|
@@ -46163,12 +46299,7 @@ var ModelManager2 = class _ModelManager2 {
|
|
|
46163
46299
|
const cached = await this.getCachedNostrEvents({ kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] }, this.cacheTTL, forceRefresh);
|
|
46164
46300
|
let sessionEvents = cached;
|
|
46165
46301
|
if (cached.length === 0) {
|
|
46166
|
-
const lgtmRelays = this.getNostrRelays(
|
|
46167
|
-
"wss://relay.primal.net",
|
|
46168
|
-
"wss://nos.lol",
|
|
46169
|
-
"wss://relay.damus.io",
|
|
46170
|
-
"wss://relay.routstr.com"
|
|
46171
|
-
]);
|
|
46302
|
+
const lgtmRelays = this.getNostrRelays();
|
|
46172
46303
|
const pool3 = new RelayPool;
|
|
46173
46304
|
const timeoutMs = 5000;
|
|
46174
46305
|
await new Promise((resolve) => {
|
|
@@ -46366,11 +46497,7 @@ var ModelManager2 = class _ModelManager2 {
|
|
|
46366
46497
|
return cachedModels;
|
|
46367
46498
|
}
|
|
46368
46499
|
}
|
|
46369
|
-
const relays3 = this.getNostrRelays(
|
|
46370
|
-
"wss://relay.damus.io",
|
|
46371
|
-
"wss://nos.lol",
|
|
46372
|
-
"wss://relay.routstr.com"
|
|
46373
|
-
]);
|
|
46500
|
+
const relays3 = this.getNostrRelays();
|
|
46374
46501
|
const cached = await this.getCachedNostrEvents({ kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] }, this.cacheTTL, forceRefresh);
|
|
46375
46502
|
let sessionEvents = cached;
|
|
46376
46503
|
if (cached.length === 0) {
|
|
@@ -46478,6 +46605,7 @@ var PID_FILE = process.env.ROUTSTRD_PID || `${CONFIG_DIR}/routstrd.pid`;
|
|
|
46478
46605
|
var DB_PATH = `${CONFIG_DIR}/routstr.db`;
|
|
46479
46606
|
var CONFIG_FILE = `${CONFIG_DIR}/config.json`;
|
|
46480
46607
|
var LOGS_DIR = `${CONFIG_DIR}/logs`;
|
|
46608
|
+
var REQUEST_RESPONSE_LOGS_DIR = `${CONFIG_DIR}/request-response-logs`;
|
|
46481
46609
|
var DEFAULT_CONFIG = {
|
|
46482
46610
|
port: 8008,
|
|
46483
46611
|
provider: null,
|
|
@@ -46615,6 +46743,64 @@ var SDK_STORAGE_KEYS2 = {
|
|
|
46615
46743
|
LAST_FAILED: "last_failed",
|
|
46616
46744
|
PROVIDERS_ON_COOLDOWN: "providers_on_cooldown"
|
|
46617
46745
|
};
|
|
46746
|
+
var aggregateColumns = "COUNT(*) AS requests, COALESCE(SUM(prompt_tokens), 0) AS promptTokens, COALESCE(SUM(completion_tokens), 0) AS completionTokens, COALESCE(SUM(total_tokens), 0) AS totalTokens, COALESCE(SUM(cost), 0) AS cost, COALESCE(SUM(sats_cost), 0) AS satsCost, COALESCE(SUM(base_msats), 0) AS baseMsats, COALESCE(SUM(input_msats), 0) AS inputMsats, COALESCE(SUM(output_msats), 0) AS outputMsats, COALESCE(SUM(total_msats), 0) AS totalMsats, COALESCE(SUM(total_usd), 0) AS totalUsd, COALESCE(SUM(cache_read_input_tokens), 0) AS cacheReadInputTokens, COALESCE(SUM(cache_creation_input_tokens), 0) AS cacheCreationInputTokens, COALESCE(SUM(cache_read_msats), 0) AS cacheReadMsats, COALESCE(SUM(cache_creation_msats), 0) AS cacheCreationMsats";
|
|
46747
|
+
var sqlGroupExpr = (groupBy) => {
|
|
46748
|
+
switch (groupBy) {
|
|
46749
|
+
case "modelId":
|
|
46750
|
+
return { expr: "model_id", usesTz: false };
|
|
46751
|
+
case "baseUrl":
|
|
46752
|
+
return { expr: "base_url", usesTz: false };
|
|
46753
|
+
case "client":
|
|
46754
|
+
return { expr: "client", usesTz: false };
|
|
46755
|
+
case "sessionId":
|
|
46756
|
+
return { expr: "session_id", usesTz: false };
|
|
46757
|
+
case "provider":
|
|
46758
|
+
return { expr: "provider", usesTz: false };
|
|
46759
|
+
case "day":
|
|
46760
|
+
return {
|
|
46761
|
+
expr: "strftime('%Y-%m-%d', (timestamp - ? * 60000) / 1000, 'unixepoch')",
|
|
46762
|
+
usesTz: true
|
|
46763
|
+
};
|
|
46764
|
+
case "hour":
|
|
46765
|
+
return {
|
|
46766
|
+
expr: "strftime('%H', (timestamp - ? * 60000) / 1000, 'unixepoch')",
|
|
46767
|
+
usesTz: true
|
|
46768
|
+
};
|
|
46769
|
+
}
|
|
46770
|
+
};
|
|
46771
|
+
var buildAggregateSql = (tableName, where, options = {}) => {
|
|
46772
|
+
if (!options.groupBy) {
|
|
46773
|
+
return {
|
|
46774
|
+
sql: `SELECT NULL AS grp, ${aggregateColumns} FROM ${tableName} ${where.sql}`,
|
|
46775
|
+
params: where.params
|
|
46776
|
+
};
|
|
46777
|
+
}
|
|
46778
|
+
const { expr, usesTz } = sqlGroupExpr(options.groupBy);
|
|
46779
|
+
const tzParams = usesTz ? [options.tzOffsetMinutes ?? 0] : [];
|
|
46780
|
+
const orderBy = options.groupBy === "day" || options.groupBy === "hour" ? "ORDER BY grp ASC" : "ORDER BY satsCost DESC";
|
|
46781
|
+
return {
|
|
46782
|
+
sql: `SELECT ${expr} AS grp, ${aggregateColumns} FROM ${tableName} ${where.sql} GROUP BY grp ${orderBy}`,
|
|
46783
|
+
params: [...tzParams, ...where.params]
|
|
46784
|
+
};
|
|
46785
|
+
};
|
|
46786
|
+
var mapAggregateRow = (row) => ({
|
|
46787
|
+
group: row.grp == null ? null : String(row.grp),
|
|
46788
|
+
requests: Number(row.requests ?? 0),
|
|
46789
|
+
promptTokens: Number(row.promptTokens ?? 0),
|
|
46790
|
+
completionTokens: Number(row.completionTokens ?? 0),
|
|
46791
|
+
totalTokens: Number(row.totalTokens ?? 0),
|
|
46792
|
+
cost: Number(row.cost ?? 0),
|
|
46793
|
+
satsCost: Number(row.satsCost ?? 0),
|
|
46794
|
+
baseMsats: Number(row.baseMsats ?? 0),
|
|
46795
|
+
inputMsats: Number(row.inputMsats ?? 0),
|
|
46796
|
+
outputMsats: Number(row.outputMsats ?? 0),
|
|
46797
|
+
totalMsats: Number(row.totalMsats ?? 0),
|
|
46798
|
+
totalUsd: Number(row.totalUsd ?? 0),
|
|
46799
|
+
cacheReadInputTokens: Number(row.cacheReadInputTokens ?? 0),
|
|
46800
|
+
cacheCreationInputTokens: Number(row.cacheCreationInputTokens ?? 0),
|
|
46801
|
+
cacheReadMsats: Number(row.cacheReadMsats ?? 0),
|
|
46802
|
+
cacheCreationMsats: Number(row.cacheCreationMsats ?? 0)
|
|
46803
|
+
});
|
|
46618
46804
|
function makeConsoleLogger3(prefix) {
|
|
46619
46805
|
const fmt = (args) => prefix ? [prefix, ...args] : args;
|
|
46620
46806
|
return {
|
|
@@ -46944,6 +47130,11 @@ var buildWhereClause = (options = {}) => {
|
|
|
46944
47130
|
clauses.push("client = ?");
|
|
46945
47131
|
params.push(options.client);
|
|
46946
47132
|
}
|
|
47133
|
+
if (options.clients && options.clients.length > 0) {
|
|
47134
|
+
const placeholders = options.clients.map(() => "?").join(", ");
|
|
47135
|
+
clauses.push(`client IN (${placeholders})`);
|
|
47136
|
+
params.push(...options.clients);
|
|
47137
|
+
}
|
|
46947
47138
|
if (options.provider) {
|
|
46948
47139
|
clauses.push("provider = ?");
|
|
46949
47140
|
params.push(options.provider);
|
|
@@ -47084,6 +47275,13 @@ var createBunSqliteUsageTrackingDriver = (options = {}) => {
|
|
|
47084
47275
|
const row = db.query(query).get(...params);
|
|
47085
47276
|
return Number(row?.count ?? 0);
|
|
47086
47277
|
},
|
|
47278
|
+
async aggregate(options2 = {}) {
|
|
47279
|
+
await ensureMigrated();
|
|
47280
|
+
const where = buildWhereClause(options2);
|
|
47281
|
+
const { sql, params } = buildAggregateSql(tableName, where, options2);
|
|
47282
|
+
const rows = db.query(sql).all(...params);
|
|
47283
|
+
return rows.map(mapAggregateRow);
|
|
47284
|
+
},
|
|
47087
47285
|
async deleteOlderThan(timestamp) {
|
|
47088
47286
|
await ensureMigrated();
|
|
47089
47287
|
const before = timestamp;
|
|
@@ -50418,12 +50616,12 @@ function toBytes10(data) {
|
|
|
50418
50616
|
}
|
|
50419
50617
|
function concatBytes11(...arrays) {
|
|
50420
50618
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
50421
|
-
let
|
|
50619
|
+
let pad4 = 0;
|
|
50422
50620
|
arrays.forEach((a) => {
|
|
50423
50621
|
if (!u8a10(a))
|
|
50424
50622
|
throw new Error("Uint8Array expected");
|
|
50425
|
-
r.set(a,
|
|
50426
|
-
|
|
50623
|
+
r.set(a, pad4);
|
|
50624
|
+
pad4 += a.length;
|
|
50427
50625
|
});
|
|
50428
50626
|
return r;
|
|
50429
50627
|
}
|
|
@@ -50803,12 +51001,12 @@ function ensureBytes4(title, hex5, expectedLength) {
|
|
|
50803
51001
|
}
|
|
50804
51002
|
function concatBytes12(...arrays) {
|
|
50805
51003
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
50806
|
-
let
|
|
51004
|
+
let pad4 = 0;
|
|
50807
51005
|
arrays.forEach((a) => {
|
|
50808
51006
|
if (!u8a11(a))
|
|
50809
51007
|
throw new Error("Uint8Array expected");
|
|
50810
|
-
r.set(a,
|
|
50811
|
-
|
|
51008
|
+
r.set(a, pad4);
|
|
51009
|
+
pad4 += a.length;
|
|
50812
51010
|
});
|
|
50813
51011
|
return r;
|
|
50814
51012
|
}
|
|
@@ -52020,16 +52218,16 @@ class HMAC6 extends Hash8 {
|
|
|
52020
52218
|
this.blockLen = this.iHash.blockLen;
|
|
52021
52219
|
this.outputLen = this.iHash.outputLen;
|
|
52022
52220
|
const blockLen = this.blockLen;
|
|
52023
|
-
const
|
|
52024
|
-
|
|
52025
|
-
for (let i6 = 0;i6 <
|
|
52026
|
-
|
|
52027
|
-
this.iHash.update(
|
|
52221
|
+
const pad4 = new Uint8Array(blockLen);
|
|
52222
|
+
pad4.set(key.length > blockLen ? hash8.create().update(key).digest() : key);
|
|
52223
|
+
for (let i6 = 0;i6 < pad4.length; i6++)
|
|
52224
|
+
pad4[i6] ^= 54;
|
|
52225
|
+
this.iHash.update(pad4);
|
|
52028
52226
|
this.oHash = hash8.create();
|
|
52029
|
-
for (let i6 = 0;i6 <
|
|
52030
|
-
|
|
52031
|
-
this.oHash.update(
|
|
52032
|
-
|
|
52227
|
+
for (let i6 = 0;i6 < pad4.length; i6++)
|
|
52228
|
+
pad4[i6] ^= 54 ^ 92;
|
|
52229
|
+
this.oHash.update(pad4);
|
|
52230
|
+
pad4.fill(0);
|
|
52033
52231
|
}
|
|
52034
52232
|
update(buf) {
|
|
52035
52233
|
exists9(this);
|
|
@@ -52300,12 +52498,12 @@ function toBytes11(data) {
|
|
|
52300
52498
|
}
|
|
52301
52499
|
function concatBytes13(...arrays) {
|
|
52302
52500
|
const r = new Uint8Array(arrays.reduce((sum, a) => sum + a.length, 0));
|
|
52303
|
-
let
|
|
52501
|
+
let pad4 = 0;
|
|
52304
52502
|
arrays.forEach((a) => {
|
|
52305
52503
|
if (!u8a12(a))
|
|
52306
52504
|
throw new Error("Uint8Array expected");
|
|
52307
|
-
r.set(a,
|
|
52308
|
-
|
|
52505
|
+
r.set(a, pad4);
|
|
52506
|
+
pad4 += a.length;
|
|
52309
52507
|
});
|
|
52310
52508
|
return r;
|
|
52311
52509
|
}
|
|
@@ -53996,7 +54194,7 @@ class Poly13053 {
|
|
|
53996
54194
|
h2[9] = d9;
|
|
53997
54195
|
}
|
|
53998
54196
|
finalize() {
|
|
53999
|
-
const { h: h2, pad:
|
|
54197
|
+
const { h: h2, pad: pad4 } = this;
|
|
54000
54198
|
const g2 = new Uint16Array(10);
|
|
54001
54199
|
let c = h2[1] >>> 13;
|
|
54002
54200
|
h2[1] &= 8191;
|
|
@@ -54035,10 +54233,10 @@ class Poly13053 {
|
|
|
54035
54233
|
h2[5] = (h2[6] >>> 2 | h2[7] << 11) & 65535;
|
|
54036
54234
|
h2[6] = (h2[7] >>> 5 | h2[8] << 8) & 65535;
|
|
54037
54235
|
h2[7] = (h2[8] >>> 8 | h2[9] << 5) & 65535;
|
|
54038
|
-
let f = h2[0] +
|
|
54236
|
+
let f = h2[0] + pad4[0];
|
|
54039
54237
|
h2[0] = f & 65535;
|
|
54040
54238
|
for (let i6 = 1;i6 < 8; i6++) {
|
|
54041
|
-
f = (h2[i6] +
|
|
54239
|
+
f = (h2[i6] + pad4[i6] | 0) + (f >>> 16) | 0;
|
|
54042
54240
|
h2[i6] = f & 65535;
|
|
54043
54241
|
}
|
|
54044
54242
|
}
|
|
@@ -54474,16 +54672,16 @@ class HMAC7 extends Hash9 {
|
|
|
54474
54672
|
this.blockLen = this.iHash.blockLen;
|
|
54475
54673
|
this.outputLen = this.iHash.outputLen;
|
|
54476
54674
|
const blockLen = this.blockLen;
|
|
54477
|
-
const
|
|
54478
|
-
|
|
54479
|
-
for (let i6 = 0;i6 <
|
|
54480
|
-
|
|
54481
|
-
this.iHash.update(
|
|
54675
|
+
const pad4 = new Uint8Array(blockLen);
|
|
54676
|
+
pad4.set(key.length > blockLen ? hash9.create().update(key).digest() : key);
|
|
54677
|
+
for (let i6 = 0;i6 < pad4.length; i6++)
|
|
54678
|
+
pad4[i6] ^= 54;
|
|
54679
|
+
this.iHash.update(pad4);
|
|
54482
54680
|
this.oHash = hash9.create();
|
|
54483
|
-
for (let i6 = 0;i6 <
|
|
54484
|
-
|
|
54485
|
-
this.oHash.update(
|
|
54486
|
-
|
|
54681
|
+
for (let i6 = 0;i6 < pad4.length; i6++)
|
|
54682
|
+
pad4[i6] ^= 54 ^ 92;
|
|
54683
|
+
this.oHash.update(pad4);
|
|
54684
|
+
pad4.fill(0);
|
|
54487
54685
|
}
|
|
54488
54686
|
update(buf) {
|
|
54489
54687
|
_assert_default4.exists(this);
|
|
@@ -56035,7 +56233,7 @@ function writeU16BE3(num) {
|
|
|
56035
56233
|
new DataView(arr.buffer).setUint16(0, num, false);
|
|
56036
56234
|
return arr;
|
|
56037
56235
|
}
|
|
56038
|
-
function
|
|
56236
|
+
function pad5(plaintext) {
|
|
56039
56237
|
const unpadded = utf8Encoder9.encode(plaintext);
|
|
56040
56238
|
const unpaddedLen = unpadded.length;
|
|
56041
56239
|
const prefix = writeU16BE3(unpaddedLen);
|
|
@@ -56083,7 +56281,7 @@ function decodePayload3(payload) {
|
|
|
56083
56281
|
}
|
|
56084
56282
|
function encrypt24(plaintext, conversationKey, nonce = randomBytes9(32)) {
|
|
56085
56283
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys3(conversationKey, nonce);
|
|
56086
|
-
const padded =
|
|
56284
|
+
const padded = pad5(plaintext);
|
|
56087
56285
|
const ciphertext = chacha203(chacha_key, chacha_nonce, padded);
|
|
56088
56286
|
const mac = hmacAad3(hmac_key, ciphertext, nonce);
|
|
56089
56287
|
return base645.encode(concatBytes13(new Uint8Array([2]), nonce, ciphertext, mac));
|
|
@@ -67516,7 +67714,7 @@ function writeU32BE(num2) {
|
|
|
67516
67714
|
new DataView(arr.buffer).setUint32(0, num2, false);
|
|
67517
67715
|
return arr;
|
|
67518
67716
|
}
|
|
67519
|
-
function
|
|
67717
|
+
function pad6(plaintext) {
|
|
67520
67718
|
const unpadded = utf8Encoder13.encode(plaintext);
|
|
67521
67719
|
const unpaddedLen = unpadded.length;
|
|
67522
67720
|
if (unpaddedLen < minPlaintextSize4 || unpaddedLen > maxPlaintextSize4)
|
|
@@ -67578,7 +67776,7 @@ function decodePayload4(payload) {
|
|
|
67578
67776
|
}
|
|
67579
67777
|
function encrypt25(plaintext, conversationKey, nonce = randomBytes10(32)) {
|
|
67580
67778
|
const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys4(conversationKey, nonce);
|
|
67581
|
-
const padded =
|
|
67779
|
+
const padded = pad6(plaintext);
|
|
67582
67780
|
const ciphertext = chacha204(chacha_key, chacha_nonce, padded);
|
|
67583
67781
|
const mac = hmacAad4(hmac_key, ciphertext, nonce);
|
|
67584
67782
|
return base646.encode(concatBytes14(new Uint8Array([2]), nonce, ciphertext, mac));
|
|
@@ -67596,7 +67794,7 @@ var v24 = {
|
|
|
67596
67794
|
utils: {
|
|
67597
67795
|
getConversationKey: getConversationKey4,
|
|
67598
67796
|
calcPaddedLen: calcPaddedLen4,
|
|
67599
|
-
pad:
|
|
67797
|
+
pad: pad6,
|
|
67600
67798
|
unpad: unpad4
|
|
67601
67799
|
},
|
|
67602
67800
|
encrypt: encrypt25,
|
|
@@ -69523,6 +69721,221 @@ async function getClientsList() {
|
|
|
69523
69721
|
}));
|
|
69524
69722
|
}
|
|
69525
69723
|
|
|
69724
|
+
// src/daemon/http/usage-summary.ts
|
|
69725
|
+
function rowToStat(r) {
|
|
69726
|
+
return {
|
|
69727
|
+
requests: r.requests,
|
|
69728
|
+
promptTokens: r.promptTokens,
|
|
69729
|
+
completionTokens: r.completionTokens,
|
|
69730
|
+
totalTokens: r.totalTokens,
|
|
69731
|
+
cost: r.cost,
|
|
69732
|
+
satsCost: r.satsCost
|
|
69733
|
+
};
|
|
69734
|
+
}
|
|
69735
|
+
function rowToTopModel(r) {
|
|
69736
|
+
return {
|
|
69737
|
+
modelId: r.group ?? "unknown",
|
|
69738
|
+
requests: r.requests,
|
|
69739
|
+
satsCost: r.satsCost,
|
|
69740
|
+
totalTokens: r.totalTokens
|
|
69741
|
+
};
|
|
69742
|
+
}
|
|
69743
|
+
function emptyBucket() {
|
|
69744
|
+
return { count: 0, cost: 0 };
|
|
69745
|
+
}
|
|
69746
|
+
var SIZE_BUCKET_BOUNDS = {
|
|
69747
|
+
tiny: [0, 1000],
|
|
69748
|
+
small: [1000, 1e4],
|
|
69749
|
+
medium: [1e4, 50000],
|
|
69750
|
+
large: [50000, 1e5],
|
|
69751
|
+
huge: [1e5, Infinity]
|
|
69752
|
+
};
|
|
69753
|
+
function computeSizeBuckets(entries) {
|
|
69754
|
+
const buckets = {
|
|
69755
|
+
tiny: emptyBucket(),
|
|
69756
|
+
small: emptyBucket(),
|
|
69757
|
+
medium: emptyBucket(),
|
|
69758
|
+
large: emptyBucket(),
|
|
69759
|
+
huge: emptyBucket()
|
|
69760
|
+
};
|
|
69761
|
+
for (const entry of entries) {
|
|
69762
|
+
for (const [name, [min, max]] of Object.entries(SIZE_BUCKET_BOUNDS)) {
|
|
69763
|
+
if (entry.totalTokens >= min && entry.totalTokens < max) {
|
|
69764
|
+
const bucket = buckets[name];
|
|
69765
|
+
bucket.count++;
|
|
69766
|
+
bucket.cost += entry.satsCost;
|
|
69767
|
+
break;
|
|
69768
|
+
}
|
|
69769
|
+
}
|
|
69770
|
+
}
|
|
69771
|
+
return buckets;
|
|
69772
|
+
}
|
|
69773
|
+
function startOfLocalDayUtc(now5, tzOffsetMinutes) {
|
|
69774
|
+
return Math.floor((now5 - tzOffsetMinutes * 60000) / 86400000) * 86400000 + tzOffsetMinutes * 60000;
|
|
69775
|
+
}
|
|
69776
|
+
var _cache = null;
|
|
69777
|
+
var CACHE_TTL_MS = 60000;
|
|
69778
|
+
async function getUsageSummary(driver, clients, tzOffsetMinutes, clientFilter) {
|
|
69779
|
+
const count = await driver.count(clientFilter ? { clients: clientFilter } : {});
|
|
69780
|
+
const clientIdentity = clients.map((c) => `${c.clientId}:${c.ownerNpub ?? ""}`).join(",");
|
|
69781
|
+
const filterKey = clientFilter ? `:f:${clientFilter.sort().join(",")}` : "";
|
|
69782
|
+
const cacheKey = `${count}:${clientIdentity}:${tzOffsetMinutes}${filterKey}`;
|
|
69783
|
+
const now5 = Date.now();
|
|
69784
|
+
if (_cache !== null && _cache.key === cacheKey && now5 - _cache.summary.generatedAt <= CACHE_TTL_MS) {
|
|
69785
|
+
return _cache.summary;
|
|
69786
|
+
}
|
|
69787
|
+
const baseFilter = clientFilter ? { clients: clientFilter } : {};
|
|
69788
|
+
if (count === 0) {
|
|
69789
|
+
const zeroStat = {
|
|
69790
|
+
requests: 0,
|
|
69791
|
+
promptTokens: 0,
|
|
69792
|
+
completionTokens: 0,
|
|
69793
|
+
totalTokens: 0,
|
|
69794
|
+
cost: 0,
|
|
69795
|
+
satsCost: 0
|
|
69796
|
+
};
|
|
69797
|
+
const zeroSummary = {
|
|
69798
|
+
generatedAt: now5,
|
|
69799
|
+
totals: zeroStat,
|
|
69800
|
+
models: [],
|
|
69801
|
+
providers: [],
|
|
69802
|
+
clients: [],
|
|
69803
|
+
npubs: [],
|
|
69804
|
+
days: [],
|
|
69805
|
+
hoursToday: [],
|
|
69806
|
+
sizeBuckets: {
|
|
69807
|
+
tiny: emptyBucket(),
|
|
69808
|
+
small: emptyBucket(),
|
|
69809
|
+
medium: emptyBucket(),
|
|
69810
|
+
large: emptyBucket(),
|
|
69811
|
+
huge: emptyBucket()
|
|
69812
|
+
},
|
|
69813
|
+
recent: []
|
|
69814
|
+
};
|
|
69815
|
+
_cache = { key: cacheKey, summary: zeroSummary };
|
|
69816
|
+
return zeroSummary;
|
|
69817
|
+
}
|
|
69818
|
+
const [totalsRow] = await driver.aggregate({ ...baseFilter });
|
|
69819
|
+
const totals = totalsRow ? rowToStat(totalsRow) : {
|
|
69820
|
+
requests: 0,
|
|
69821
|
+
promptTokens: 0,
|
|
69822
|
+
completionTokens: 0,
|
|
69823
|
+
totalTokens: 0,
|
|
69824
|
+
cost: 0,
|
|
69825
|
+
satsCost: 0
|
|
69826
|
+
};
|
|
69827
|
+
const modelRows = await driver.aggregate({ ...baseFilter, groupBy: "modelId" });
|
|
69828
|
+
const models = modelRows.map((r) => ({
|
|
69829
|
+
modelId: r.group ?? "unknown",
|
|
69830
|
+
...rowToStat(r)
|
|
69831
|
+
}));
|
|
69832
|
+
const providerRows = await driver.aggregate({ ...baseFilter, groupBy: "baseUrl" });
|
|
69833
|
+
const providers = providerRows.map((r) => ({
|
|
69834
|
+
baseUrl: r.group ?? "unknown",
|
|
69835
|
+
...rowToStat(r)
|
|
69836
|
+
}));
|
|
69837
|
+
const clientRows = await driver.aggregate({ ...baseFilter, groupBy: "client" });
|
|
69838
|
+
const clientSummaries = clientRows.map((r) => ({
|
|
69839
|
+
client: r.group ?? "unknown",
|
|
69840
|
+
...rowToStat(r),
|
|
69841
|
+
topModels: []
|
|
69842
|
+
}));
|
|
69843
|
+
const topClientRows = clientRows.filter((r) => r.group !== null).slice(0, 3);
|
|
69844
|
+
for (let i9 = 0;i9 < topClientRows.length; i9++) {
|
|
69845
|
+
const clientId = topClientRows[i9].group;
|
|
69846
|
+
const topModelRows = await driver.aggregate({
|
|
69847
|
+
...baseFilter,
|
|
69848
|
+
groupBy: "modelId",
|
|
69849
|
+
client: clientId
|
|
69850
|
+
});
|
|
69851
|
+
const summary2 = clientSummaries.find((c) => c.client === clientId);
|
|
69852
|
+
if (summary2) {
|
|
69853
|
+
summary2.topModels = topModelRows.slice(0, 5).map(rowToTopModel);
|
|
69854
|
+
}
|
|
69855
|
+
}
|
|
69856
|
+
const clientToNpub = new Map;
|
|
69857
|
+
for (const c of clients) {
|
|
69858
|
+
if (c.ownerNpub) {
|
|
69859
|
+
clientToNpub.set(c.clientId, c.ownerNpub);
|
|
69860
|
+
}
|
|
69861
|
+
}
|
|
69862
|
+
const npubStats = new Map;
|
|
69863
|
+
const npubClientIds = new Map;
|
|
69864
|
+
for (const r of clientRows) {
|
|
69865
|
+
if (r.group === null)
|
|
69866
|
+
continue;
|
|
69867
|
+
const npub = clientToNpub.get(r.group);
|
|
69868
|
+
if (!npub)
|
|
69869
|
+
continue;
|
|
69870
|
+
const existing = npubStats.get(npub);
|
|
69871
|
+
if (existing) {
|
|
69872
|
+
existing.requests += r.requests;
|
|
69873
|
+
existing.promptTokens += r.promptTokens;
|
|
69874
|
+
existing.completionTokens += r.completionTokens;
|
|
69875
|
+
existing.totalTokens += r.totalTokens;
|
|
69876
|
+
existing.cost += r.cost;
|
|
69877
|
+
existing.satsCost += r.satsCost;
|
|
69878
|
+
} else {
|
|
69879
|
+
npubStats.set(npub, { ...rowToStat(r) });
|
|
69880
|
+
}
|
|
69881
|
+
const ids = npubClientIds.get(npub) ?? [];
|
|
69882
|
+
ids.push(r.group);
|
|
69883
|
+
npubClientIds.set(npub, ids);
|
|
69884
|
+
}
|
|
69885
|
+
const sortedNpubs = [...npubStats.entries()].sort((a, b2) => b2[1].satsCost - a[1].satsCost);
|
|
69886
|
+
const npubs = sortedNpubs.map(([npub, stat2]) => ({
|
|
69887
|
+
npub,
|
|
69888
|
+
...stat2,
|
|
69889
|
+
topModels: []
|
|
69890
|
+
}));
|
|
69891
|
+
for (let i9 = 0;i9 < Math.min(5, npubs.length); i9++) {
|
|
69892
|
+
const npubSummary = npubs[i9];
|
|
69893
|
+
const ids = npubClientIds.get(npubSummary.npub) ?? [];
|
|
69894
|
+
if (ids.length > 0) {
|
|
69895
|
+
const topModelRows = await driver.aggregate({
|
|
69896
|
+
groupBy: "modelId",
|
|
69897
|
+
clients: ids
|
|
69898
|
+
});
|
|
69899
|
+
npubSummary.topModels = topModelRows.slice(0, 5).map(rowToTopModel);
|
|
69900
|
+
}
|
|
69901
|
+
}
|
|
69902
|
+
const dayRows = await driver.aggregate({
|
|
69903
|
+
...baseFilter,
|
|
69904
|
+
groupBy: "day",
|
|
69905
|
+
tzOffsetMinutes,
|
|
69906
|
+
after: now5 - 30 * 86400000
|
|
69907
|
+
});
|
|
69908
|
+
const days = dayRows.map((r) => ({ date: r.group, ...rowToStat(r) })).reverse();
|
|
69909
|
+
const todayStartUtc = startOfLocalDayUtc(now5, tzOffsetMinutes);
|
|
69910
|
+
const hourRows = await driver.aggregate({
|
|
69911
|
+
...baseFilter,
|
|
69912
|
+
groupBy: "hour",
|
|
69913
|
+
tzOffsetMinutes,
|
|
69914
|
+
after: todayStartUtc - 1
|
|
69915
|
+
});
|
|
69916
|
+
const hoursToday = hourRows.map((r) => ({
|
|
69917
|
+
hour: Number(r.group),
|
|
69918
|
+
...rowToStat(r)
|
|
69919
|
+
}));
|
|
69920
|
+
const allEntries = await driver.list(baseFilter);
|
|
69921
|
+
const sizeBuckets = computeSizeBuckets(allEntries);
|
|
69922
|
+
const recent = await driver.list({ ...baseFilter, limit: 50 });
|
|
69923
|
+
const summary = {
|
|
69924
|
+
generatedAt: now5,
|
|
69925
|
+
totals,
|
|
69926
|
+
models,
|
|
69927
|
+
providers,
|
|
69928
|
+
clients: clientSummaries,
|
|
69929
|
+
npubs,
|
|
69930
|
+
days,
|
|
69931
|
+
hoursToday,
|
|
69932
|
+
sizeBuckets,
|
|
69933
|
+
recent
|
|
69934
|
+
};
|
|
69935
|
+
_cache = { key: cacheKey, summary };
|
|
69936
|
+
return summary;
|
|
69937
|
+
}
|
|
69938
|
+
|
|
69526
69939
|
// src/daemon/http/index.ts
|
|
69527
69940
|
function generateApiKey() {
|
|
69528
69941
|
const bytes12 = randomBytes12(24);
|
|
@@ -70310,8 +70723,12 @@ function createDaemonRequestHandler(deps) {
|
|
|
70310
70723
|
}
|
|
70311
70724
|
if (req.method === "GET" && url4.pathname === "/usage") {
|
|
70312
70725
|
try {
|
|
70726
|
+
const npubFilter = url4.searchParams.get("npub")?.trim();
|
|
70727
|
+
const clients = npubFilter ? getClientsFromStore(deps.store) : undefined;
|
|
70728
|
+
const clientFilter = npubFilter ? clients.filter((c) => c.ownerNpub === npubFilter).map((c) => c.clientId) : undefined;
|
|
70313
70729
|
const output12 = await deps.usageTrackingDriver.list({
|
|
70314
|
-
limit: parseLimit(url4.searchParams.get("limit"))
|
|
70730
|
+
limit: parseLimit(url4.searchParams.get("limit")),
|
|
70731
|
+
...clientFilter ? { clients: clientFilter } : {}
|
|
70315
70732
|
});
|
|
70316
70733
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
70317
70734
|
res.end(JSON.stringify({ output: output12 }));
|
|
@@ -70320,6 +70737,19 @@ function createDaemonRequestHandler(deps) {
|
|
|
70320
70737
|
}
|
|
70321
70738
|
return;
|
|
70322
70739
|
}
|
|
70740
|
+
if (req.method === "GET" && url4.pathname === "/usage/summary") {
|
|
70741
|
+
try {
|
|
70742
|
+
const tz = Number.parseInt(url4.searchParams.get("tz") || "0", 10) || 0;
|
|
70743
|
+
const npubFilter = url4.searchParams.get("npub")?.trim();
|
|
70744
|
+
const clients = getClientsFromStore(deps.store);
|
|
70745
|
+
const clientFilter = npubFilter ? clients.filter((c) => c.ownerNpub === npubFilter).map((c) => c.clientId) : undefined;
|
|
70746
|
+
const summary = await getUsageSummary(deps.usageTrackingDriver, clients, tz, clientFilter);
|
|
70747
|
+
sendJson(res, 200, { output: summary });
|
|
70748
|
+
} catch (error) {
|
|
70749
|
+
sendJson(res, 500, { error: toErrorMessage(error) });
|
|
70750
|
+
}
|
|
70751
|
+
return;
|
|
70752
|
+
}
|
|
70323
70753
|
if (req.method === "GET" && url4.pathname === "/usagePi") {
|
|
70324
70754
|
try {
|
|
70325
70755
|
const timestamp = (url4.searchParams.get("timestamp") || "").trim();
|
|
@@ -70406,6 +70836,7 @@ function createDaemonRequestHandler(deps) {
|
|
|
70406
70836
|
sdkStore: deps.store,
|
|
70407
70837
|
providerManager: deps.providerManager,
|
|
70408
70838
|
logger: reqLogger,
|
|
70839
|
+
...deps.requestResponseLogSink ? { requestResponseLogSink: deps.requestResponseLogSink } : {},
|
|
70409
70840
|
...deps.routstrPubkey ? { routstrPubkey: deps.routstrPubkey } : {}
|
|
70410
70841
|
});
|
|
70411
70842
|
res.statusCode = response3.status;
|
|
@@ -70475,6 +70906,218 @@ function createDaemonRequestHandler(deps) {
|
|
|
70475
70906
|
};
|
|
70476
70907
|
}
|
|
70477
70908
|
|
|
70909
|
+
// src/daemon/request-response-log-sink.ts
|
|
70910
|
+
import { createWriteStream, mkdirSync as mkdirSync6 } from "fs";
|
|
70911
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
70912
|
+
import { join as join9 } from "path";
|
|
70913
|
+
var SENSITIVE_HEADER_NAMES = new Set([
|
|
70914
|
+
"authorization",
|
|
70915
|
+
"x-cashu",
|
|
70916
|
+
"cookie",
|
|
70917
|
+
"set-cookie",
|
|
70918
|
+
"proxy-authorization"
|
|
70919
|
+
]);
|
|
70920
|
+
var SENSITIVE_BODY_FIELD_NAMES = new Set([
|
|
70921
|
+
"authorization",
|
|
70922
|
+
"api_key",
|
|
70923
|
+
"apikey",
|
|
70924
|
+
"apiKey",
|
|
70925
|
+
"access_token",
|
|
70926
|
+
"accessToken",
|
|
70927
|
+
"bearer",
|
|
70928
|
+
"cashu",
|
|
70929
|
+
"cookie",
|
|
70930
|
+
"key",
|
|
70931
|
+
"password",
|
|
70932
|
+
"secret",
|
|
70933
|
+
"token",
|
|
70934
|
+
"x-cashu"
|
|
70935
|
+
]);
|
|
70936
|
+
var REDACTED = "[REDACTED]";
|
|
70937
|
+
var sanitizeForFilename = (value) => value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
70938
|
+
var makeId = () => {
|
|
70939
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
70940
|
+
const random = crypto.randomUUID().slice(0, 8);
|
|
70941
|
+
return `${timestamp}-${random}`;
|
|
70942
|
+
};
|
|
70943
|
+
var headersToObject = (headers) => {
|
|
70944
|
+
const out = {};
|
|
70945
|
+
headers.forEach((value, key) => {
|
|
70946
|
+
out[key] = value;
|
|
70947
|
+
});
|
|
70948
|
+
return out;
|
|
70949
|
+
};
|
|
70950
|
+
var redactHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([key, value]) => [
|
|
70951
|
+
key,
|
|
70952
|
+
SENSITIVE_HEADER_NAMES.has(key.toLowerCase()) ? REDACTED : value
|
|
70953
|
+
]));
|
|
70954
|
+
var redactBody = (value) => {
|
|
70955
|
+
if (!value || typeof value !== "object")
|
|
70956
|
+
return value;
|
|
70957
|
+
if (Array.isArray(value))
|
|
70958
|
+
return value.map(redactBody);
|
|
70959
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
70960
|
+
key,
|
|
70961
|
+
SENSITIVE_BODY_FIELD_NAMES.has(key) || SENSITIVE_BODY_FIELD_NAMES.has(key.toLowerCase()) ? REDACTED : redactBody(entry)
|
|
70962
|
+
]));
|
|
70963
|
+
};
|
|
70964
|
+
var redactRawBody = (rawBody) => {
|
|
70965
|
+
if (!rawBody)
|
|
70966
|
+
return;
|
|
70967
|
+
try {
|
|
70968
|
+
return JSON.stringify(redactBody(JSON.parse(rawBody)));
|
|
70969
|
+
} catch {
|
|
70970
|
+
return rawBody;
|
|
70971
|
+
}
|
|
70972
|
+
};
|
|
70973
|
+
|
|
70974
|
+
class FileRequestResponseLogSink {
|
|
70975
|
+
options;
|
|
70976
|
+
requestsDir;
|
|
70977
|
+
responsesDir;
|
|
70978
|
+
activeResponses = new Map;
|
|
70979
|
+
constructor(options) {
|
|
70980
|
+
this.options = options;
|
|
70981
|
+
this.requestsDir = join9(options.dir, "requests");
|
|
70982
|
+
this.responsesDir = join9(options.dir, "responses");
|
|
70983
|
+
mkdirSync6(this.requestsDir, { recursive: true });
|
|
70984
|
+
mkdirSync6(this.responsesDir, { recursive: true });
|
|
70985
|
+
}
|
|
70986
|
+
async logRequest(input) {
|
|
70987
|
+
try {
|
|
70988
|
+
const id = makeId();
|
|
70989
|
+
const filePath = join9(this.requestsDir, `${sanitizeForFilename(id)}.json`);
|
|
70990
|
+
await writeFile7(filePath, JSON.stringify({
|
|
70991
|
+
id,
|
|
70992
|
+
timestamp: new Date().toISOString(),
|
|
70993
|
+
method: input.method,
|
|
70994
|
+
url: input.url,
|
|
70995
|
+
path: input.path,
|
|
70996
|
+
baseUrl: input.baseUrl,
|
|
70997
|
+
headers: redactHeaders(input.headers),
|
|
70998
|
+
body: redactBody(input.body),
|
|
70999
|
+
rawBody: redactRawBody(input.rawBody)
|
|
71000
|
+
}, null, 2));
|
|
71001
|
+
return id;
|
|
71002
|
+
} catch (error) {
|
|
71003
|
+
this.options.logger?.error?.("[request-response-log] failed to log request:", error);
|
|
71004
|
+
return;
|
|
71005
|
+
}
|
|
71006
|
+
}
|
|
71007
|
+
async logResponseStart(id, response3) {
|
|
71008
|
+
if (!id)
|
|
71009
|
+
return;
|
|
71010
|
+
await this.append(id, {
|
|
71011
|
+
type: "response_start",
|
|
71012
|
+
status: response3.status,
|
|
71013
|
+
statusText: response3.statusText,
|
|
71014
|
+
headers: redactHeaders(headersToObject(response3.headers))
|
|
71015
|
+
});
|
|
71016
|
+
}
|
|
71017
|
+
logResponseChunk(id, sequence, text) {
|
|
71018
|
+
if (!id)
|
|
71019
|
+
return;
|
|
71020
|
+
this.append(id, {
|
|
71021
|
+
type: "chunk",
|
|
71022
|
+
sequence,
|
|
71023
|
+
text
|
|
71024
|
+
});
|
|
71025
|
+
}
|
|
71026
|
+
async logResponseEnd(id) {
|
|
71027
|
+
if (!id)
|
|
71028
|
+
return;
|
|
71029
|
+
await this.append(id, { type: "end" });
|
|
71030
|
+
await this.close(id);
|
|
71031
|
+
}
|
|
71032
|
+
async logResponseError(id, error) {
|
|
71033
|
+
if (!id)
|
|
71034
|
+
return;
|
|
71035
|
+
await this.append(id, {
|
|
71036
|
+
type: "error",
|
|
71037
|
+
error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
|
|
71038
|
+
});
|
|
71039
|
+
await this.close(id);
|
|
71040
|
+
}
|
|
71041
|
+
async logResponseBody(id, response3) {
|
|
71042
|
+
if (!id)
|
|
71043
|
+
return;
|
|
71044
|
+
try {
|
|
71045
|
+
if (!response3.body) {
|
|
71046
|
+
await this.logResponseEnd(id);
|
|
71047
|
+
return;
|
|
71048
|
+
}
|
|
71049
|
+
const reader = response3.body.getReader();
|
|
71050
|
+
const decoder = new TextDecoder("utf-8");
|
|
71051
|
+
let sequence = 0;
|
|
71052
|
+
while (true) {
|
|
71053
|
+
const { done, value } = await reader.read();
|
|
71054
|
+
if (done)
|
|
71055
|
+
break;
|
|
71056
|
+
if (value && value.byteLength > 0) {
|
|
71057
|
+
await this.append(id, {
|
|
71058
|
+
type: "chunk",
|
|
71059
|
+
sequence: sequence++,
|
|
71060
|
+
text: decoder.decode(value, { stream: true })
|
|
71061
|
+
});
|
|
71062
|
+
}
|
|
71063
|
+
}
|
|
71064
|
+
const tail = decoder.decode();
|
|
71065
|
+
if (tail) {
|
|
71066
|
+
await this.append(id, {
|
|
71067
|
+
type: "chunk",
|
|
71068
|
+
sequence: sequence++,
|
|
71069
|
+
text: tail
|
|
71070
|
+
});
|
|
71071
|
+
}
|
|
71072
|
+
await this.logResponseEnd(id);
|
|
71073
|
+
} catch (error) {
|
|
71074
|
+
await this.logResponseError(id, error);
|
|
71075
|
+
}
|
|
71076
|
+
}
|
|
71077
|
+
getOrCreate(id) {
|
|
71078
|
+
const existing = this.activeResponses.get(id);
|
|
71079
|
+
if (existing)
|
|
71080
|
+
return existing;
|
|
71081
|
+
const filePath = join9(this.responsesDir, `${sanitizeForFilename(id)}.jsonl`);
|
|
71082
|
+
const stream = createWriteStream(filePath, { flags: "a" });
|
|
71083
|
+
const active = {
|
|
71084
|
+
stream,
|
|
71085
|
+
pending: Promise.resolve()
|
|
71086
|
+
};
|
|
71087
|
+
stream.on("error", (error) => {
|
|
71088
|
+
this.options.logger?.error?.("[request-response-log] response log stream error:", error);
|
|
71089
|
+
});
|
|
71090
|
+
this.activeResponses.set(id, active);
|
|
71091
|
+
return active;
|
|
71092
|
+
}
|
|
71093
|
+
async append(id, event11) {
|
|
71094
|
+
try {
|
|
71095
|
+
const active = this.getOrCreate(id);
|
|
71096
|
+
const line = JSON.stringify({ requestLogId: id, timestamp: new Date().toISOString(), ...event11 }) + `
|
|
71097
|
+
`;
|
|
71098
|
+
active.pending = active.pending.then(() => new Promise((resolve, reject) => {
|
|
71099
|
+
active.stream.write(line, (error) => {
|
|
71100
|
+
if (error)
|
|
71101
|
+
reject(error);
|
|
71102
|
+
else
|
|
71103
|
+
resolve();
|
|
71104
|
+
});
|
|
71105
|
+
}));
|
|
71106
|
+
await active.pending;
|
|
71107
|
+
} catch (error) {
|
|
71108
|
+
this.options.logger?.error?.("[request-response-log] failed to append response event:", error);
|
|
71109
|
+
}
|
|
71110
|
+
}
|
|
71111
|
+
async close(id) {
|
|
71112
|
+
const active = this.activeResponses.get(id);
|
|
71113
|
+
if (!active)
|
|
71114
|
+
return;
|
|
71115
|
+
this.activeResponses.delete(id);
|
|
71116
|
+
await active.pending;
|
|
71117
|
+
await new Promise((resolve) => active.stream.end(resolve));
|
|
71118
|
+
}
|
|
71119
|
+
}
|
|
71120
|
+
|
|
70478
71121
|
// src/integrations/index.ts
|
|
70479
71122
|
async function refreshModelsAndIntegrations(getRoutstr21Models, config, label = "Scheduled") {
|
|
70480
71123
|
await getRoutstr21Models(true);
|
|
@@ -70505,6 +71148,11 @@ async function main() {
|
|
|
70505
71148
|
const config = await loadDaemonConfig();
|
|
70506
71149
|
const port = args.port;
|
|
70507
71150
|
const provider = args.provider || config.provider;
|
|
71151
|
+
const requestResponseLogDir = process.env.ROUTSTRD_REQUEST_RESPONSE_LOG_DIR || (config.requestResponseLogging?.enabled ? config.requestResponseLogging.dir || REQUEST_RESPONSE_LOGS_DIR : undefined);
|
|
71152
|
+
const requestResponseLogSink = requestResponseLogDir ? new FileRequestResponseLogSink({
|
|
71153
|
+
dir: requestResponseLogDir,
|
|
71154
|
+
logger: daemonSdkLogger.child("request-response-log")
|
|
71155
|
+
}) : undefined;
|
|
70508
71156
|
await ensureDirs();
|
|
70509
71157
|
const updatedConfig = { ...config, port, provider };
|
|
70510
71158
|
saveDaemonConfig(updatedConfig);
|
|
@@ -70564,7 +71212,8 @@ async function main() {
|
|
|
70564
71212
|
routstrPubkey: config.routstrPubkey,
|
|
70565
71213
|
usageTrackingDriver,
|
|
70566
71214
|
providerManager,
|
|
70567
|
-
refundClient
|
|
71215
|
+
refundClient,
|
|
71216
|
+
requestResponseLogSink
|
|
70568
71217
|
}));
|
|
70569
71218
|
Bun.write(PID_FILE, String(process.pid));
|
|
70570
71219
|
try {
|
|
@@ -70639,6 +71288,9 @@ async function main() {
|
|
|
70639
71288
|
});
|
|
70640
71289
|
server.listen(port, async () => {
|
|
70641
71290
|
logger5.log(`Routstr daemon listening on http://localhost:${port}/v1`);
|
|
71291
|
+
if (requestResponseLogDir) {
|
|
71292
|
+
logger5.log(`Raw request/response logs: ${requestResponseLogDir}`);
|
|
71293
|
+
}
|
|
70642
71294
|
ensureProvidersBootstrapped().then(async () => {
|
|
70643
71295
|
startModelRefreshJob();
|
|
70644
71296
|
startRefundJob();
|