@lakphy/local-router 0.5.5 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -52922,513 +52922,134 @@ var init_crypto = __esm(() => {
52922
52922
  ECDH_PARAMS = { name: "ECDH", namedCurve: "P-256" };
52923
52923
  });
52924
52924
 
52925
- // src/log-metrics.ts
52926
- import { createReadStream, existsSync as existsSync4 } from "fs";
52927
- import { join as join6 } from "path";
52928
- import { createInterface } from "readline";
52929
- function isLogMetricsWindow(value) {
52930
- return value === "1h" || value === "6h" || value === "24h";
52931
- }
52932
- function toPercent(numerator, denominator) {
52933
- if (denominator <= 0)
52934
- return 0;
52935
- return Number((numerator / denominator * 100).toFixed(2));
52925
+ // src/token-usage.ts
52926
+ import { existsSync as existsSync4, readFileSync as readFileSync6, statSync } from "fs";
52927
+ import { resolve as resolve5 } from "path";
52928
+ function asRecord(value) {
52929
+ if (!value || typeof value !== "object" || Array.isArray(value))
52930
+ return null;
52931
+ return value;
52936
52932
  }
52937
- function toDayStart(ms) {
52938
- const date5 = new Date(ms);
52939
- return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
52933
+ function numeric(value) {
52934
+ if (typeof value === "number" && Number.isFinite(value))
52935
+ return value;
52936
+ if (typeof value === "string" && value.trim()) {
52937
+ const parsed = Number(value);
52938
+ if (Number.isFinite(parsed))
52939
+ return parsed;
52940
+ }
52941
+ return null;
52940
52942
  }
52941
- function listDateStrings(fromMs, toMs) {
52942
- const result = [];
52943
- for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
52944
- result.push(new Date(day).toISOString().slice(0, 10));
52943
+ function numberAt(value, path) {
52944
+ let current = value;
52945
+ for (const key of path) {
52946
+ const record2 = asRecord(current);
52947
+ if (!record2 || !(key in record2))
52948
+ return null;
52949
+ current = record2[key];
52945
52950
  }
52946
- return result;
52951
+ return numeric(current);
52947
52952
  }
52948
- function getStatusClass(event) {
52949
- if (event.error_type)
52950
- return "network_error";
52951
- const status = event.upstream_status ?? 0;
52952
- if (status >= 200 && status < 300)
52953
- return "2xx";
52954
- if (status >= 400 && status < 500)
52955
- return "4xx";
52956
- if (status >= 500)
52957
- return "5xx";
52958
- return "network_error";
52953
+ function firstNumber(value, paths) {
52954
+ for (const path of paths) {
52955
+ const found = numberAt(value, path);
52956
+ if (found !== null)
52957
+ return found;
52958
+ }
52959
+ return null;
52959
52960
  }
52960
- function isErrorEvent(event) {
52961
- if (event.error_type)
52962
- return true;
52963
- const status = event.upstream_status ?? 0;
52964
- return status < 200 || status >= 400;
52961
+ function maxNullable(...values) {
52962
+ const numbers = values.filter((value) => value !== null && value !== undefined);
52963
+ if (numbers.length === 0)
52964
+ return null;
52965
+ return Math.max(...numbers);
52965
52966
  }
52966
- function percentile(sortedNumbers, ratio) {
52967
- if (sortedNumbers.length === 0)
52968
- return 0;
52969
- const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
52970
- return Math.round(sortedNumbers[index]);
52967
+ function sumNullable(...values) {
52968
+ let total = 0;
52969
+ let hasValue = false;
52970
+ for (const value of values) {
52971
+ if (value === null || value === undefined)
52972
+ continue;
52973
+ total += value;
52974
+ hasValue = true;
52975
+ }
52976
+ return hasValue ? total : null;
52971
52977
  }
52972
- function createEmptyMetrics(window2, nowMs, source, warnings = []) {
52973
- const fromMs = nowMs - WINDOW_MS[window2];
52974
- const bucketMs = BUCKET_MS[window2];
52975
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
52976
- const series = Array.from({ length: bucketCount }, (_, i) => ({
52977
- ts: new Date(fromMs + i * bucketMs).toISOString(),
52978
- requests: 0,
52979
- errors: 0,
52980
- avgLatencyMs: 0
52981
- }));
52982
- return {
52983
- window: window2,
52984
- from: new Date(fromMs).toISOString(),
52985
- to: new Date(nowMs).toISOString(),
52986
- generatedAt: new Date(nowMs).toISOString(),
52987
- source,
52988
- summary: {
52989
- totalRequests: 0,
52990
- successRequests: 0,
52991
- errorRequests: 0,
52992
- successRate: 0,
52993
- avgLatencyMs: 0,
52994
- p95LatencyMs: 0,
52995
- totalRequestBytes: 0,
52996
- totalResponseBytes: 0
52997
- },
52998
- series,
52999
- topProviders: [],
53000
- topRouteTypes: [],
53001
- statusClasses: {
53002
- "2xx": 0,
53003
- "4xx": 0,
53004
- "5xx": 0,
53005
- network_error: 0
53006
- },
53007
- warnings
53008
- };
52978
+ function roundPercent(numerator, denominator) {
52979
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
52980
+ return null;
52981
+ return Number((numerator / denominator * 100).toFixed(2));
53009
52982
  }
53010
- async function getLogMetrics(options) {
53011
- const window2 = options.window ?? "24h";
53012
- const refresh = options.refresh === true;
53013
- const nowMs = options.nowMs ?? Date.now();
53014
- const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
53015
- if (!logEnabled) {
53016
- return createEmptyMetrics(window2, nowMs, {
53017
- logEnabled: false,
53018
- baseDir: null,
53019
- filesScanned: 0,
53020
- linesScanned: 0,
53021
- partial: false
53022
- }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
52983
+ function inferProviderStyle(usage, providerHint) {
52984
+ const hint = providerHint?.toLowerCase() ?? "";
52985
+ if (hint.includes("anthropic") || hint.includes("claude"))
52986
+ return "anthropic";
52987
+ if (hint.includes("gemini") || hint.includes("google"))
52988
+ return "gemini";
52989
+ if (hint.includes("deepseek"))
52990
+ return "deepseek";
52991
+ if (hint.includes("cohere"))
52992
+ return "cohere";
52993
+ if (hint.includes("mistral"))
52994
+ return "mistral";
52995
+ if (hint.includes("openrouter"))
52996
+ return "openrouter";
52997
+ if (hint.includes("openai") || hint.includes("gpt-"))
52998
+ return "openai";
52999
+ if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
53000
+ return "anthropic";
53023
53001
  }
53024
- const baseDir = resolveLogBaseDir(options.logConfig);
53025
- const cacheKey = `${baseDir}:${window2}`;
53026
- const cached2 = metricsCache.get(cacheKey);
53027
- if (!refresh && cached2 && cached2.expiresAt > nowMs) {
53028
- return cached2.value;
53002
+ if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
53003
+ return "deepseek";
53029
53004
  }
53030
- const eventsDir = join6(baseDir, "events");
53031
- if (!existsSync4(eventsDir)) {
53032
- const empty = createEmptyMetrics(window2, nowMs, {
53033
- logEnabled: true,
53034
- baseDir,
53035
- filesScanned: 0,
53036
- linesScanned: 0,
53037
- partial: false
53038
- }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
53039
- metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
53040
- return empty;
53005
+ if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
53006
+ return "gemini";
53041
53007
  }
53042
- const fromMs = nowMs - WINDOW_MS[window2];
53043
- const bucketMs = BUCKET_MS[window2];
53044
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53045
- const buckets = Array.from({ length: bucketCount }, () => ({
53046
- requests: 0,
53047
- errors: 0,
53048
- latencySum: 0,
53049
- latencyCount: 0
53050
- }));
53051
- const providerAgg = new Map;
53052
- const routeTypeAgg = new Map;
53053
- const latencies = [];
53054
- const statusClasses = {
53055
- "2xx": 0,
53056
- "4xx": 0,
53057
- "5xx": 0,
53058
- network_error: 0
53059
- };
53060
- let filesScanned = 0;
53061
- let linesScanned = 0;
53062
- let parseErrors = 0;
53063
- let partial2 = false;
53064
- let totalRequests = 0;
53065
- let successRequests = 0;
53066
- let errorRequests = 0;
53067
- let totalLatency = 0;
53068
- let totalRequestBytes = 0;
53069
- let totalResponseBytes = 0;
53070
- const warnings = [];
53071
- const dateStrings = listDateStrings(fromMs, nowMs);
53072
- for (const dateStr of dateStrings) {
53073
- if (linesScanned >= MAX_LINES_SCANNED) {
53074
- partial2 = true;
53075
- break;
53076
- }
53077
- const filePath = join6(eventsDir, `${dateStr}.jsonl`);
53078
- if (!existsSync4(filePath))
53079
- continue;
53080
- filesScanned += 1;
53081
- try {
53082
- const stream = createReadStream(filePath, { encoding: "utf-8" });
53083
- const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
53084
- for await (const line of rl) {
53085
- if (linesScanned >= MAX_LINES_SCANNED) {
53086
- partial2 = true;
53087
- rl.close();
53088
- stream.destroy();
53089
- break;
53090
- }
53091
- linesScanned += 1;
53092
- if (!line.trim())
53093
- continue;
53094
- let event;
53095
- try {
53096
- event = JSON.parse(line);
53097
- } catch {
53098
- parseErrors += 1;
53099
- continue;
53100
- }
53101
- if (!event.ts_start)
53102
- continue;
53103
- const ts = Date.parse(event.ts_start);
53104
- if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
53105
- continue;
53106
- totalRequests += 1;
53107
- const isError = isErrorEvent(event);
53108
- if (isError) {
53109
- errorRequests += 1;
53110
- } else {
53111
- successRequests += 1;
53112
- }
53113
- const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
53114
- totalLatency += latency;
53115
- latencies.push(latency);
53116
- totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
53117
- totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
53118
- const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
53119
- const bucket = buckets[bucketIndex];
53120
- bucket.requests += 1;
53121
- bucket.latencySum += latency;
53122
- bucket.latencyCount += 1;
53123
- if (isError)
53124
- bucket.errors += 1;
53125
- const providerKey = event.provider || "unknown";
53126
- const providerRow = providerAgg.get(providerKey) ?? {
53127
- requests: 0,
53128
- errors: 0,
53129
- latencySum: 0
53130
- };
53131
- providerRow.requests += 1;
53132
- providerRow.latencySum += latency;
53133
- if (isError)
53134
- providerRow.errors += 1;
53135
- providerAgg.set(providerKey, providerRow);
53136
- const routeTypeKey = event.route_type || "unknown";
53137
- const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
53138
- requests: 0,
53139
- errors: 0,
53140
- latencySum: 0
53141
- };
53142
- routeTypeRow.requests += 1;
53143
- routeTypeRow.latencySum += latency;
53144
- if (isError)
53145
- routeTypeRow.errors += 1;
53146
- routeTypeAgg.set(routeTypeKey, routeTypeRow);
53147
- statusClasses[getStatusClass(event)] += 1;
53148
- }
53149
- } catch (err) {
53150
- warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
53151
- partial2 = true;
53152
- }
53153
- }
53154
- if (parseErrors > 0) {
53155
- warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
53156
- }
53157
- if (partial2) {
53158
- warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
53159
- }
53160
- latencies.sort((a, b) => a - b);
53161
- const series = buckets.map((bucket, index) => ({
53162
- ts: new Date(fromMs + index * bucketMs).toISOString(),
53163
- requests: bucket.requests,
53164
- errors: bucket.errors,
53165
- avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
53166
- }));
53167
- const topProviders = Array.from(providerAgg.entries()).map(([key, row]) => ({
53168
- key,
53169
- requests: row.requests,
53170
- errorRate: toPercent(row.errors, row.requests),
53171
- avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
53172
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53173
- const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key, row]) => ({
53174
- key,
53175
- requests: row.requests,
53176
- errorRate: toPercent(row.errors, row.requests)
53177
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53178
- const response = {
53179
- window: window2,
53180
- from: new Date(fromMs).toISOString(),
53181
- to: new Date(nowMs).toISOString(),
53182
- generatedAt: new Date(nowMs).toISOString(),
53183
- source: {
53184
- logEnabled: true,
53185
- baseDir,
53186
- filesScanned,
53187
- linesScanned,
53188
- partial: partial2
53189
- },
53190
- summary: {
53191
- totalRequests,
53192
- successRequests,
53193
- errorRequests,
53194
- successRate: toPercent(successRequests, totalRequests),
53195
- avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
53196
- p95LatencyMs: percentile(latencies, 0.95),
53197
- totalRequestBytes,
53198
- totalResponseBytes
53199
- },
53200
- series,
53201
- topProviders,
53202
- topRouteTypes,
53203
- statusClasses,
53204
- warnings
53205
- };
53206
- metricsCache.set(cacheKey, {
53207
- expiresAt: nowMs + CACHE_TTL_MS,
53208
- value: response
53209
- });
53210
- return response;
53211
- }
53212
- var WINDOW_MS, BUCKET_MS, TOP_LIMIT = 5, MAX_LINES_SCANNED = 250000, CACHE_TTL_MS = 15000, metricsCache;
53213
- var init_log_metrics = __esm(() => {
53214
- init_config();
53215
- WINDOW_MS = {
53216
- "1h": 60 * 60 * 1000,
53217
- "6h": 6 * 60 * 60 * 1000,
53218
- "24h": 24 * 60 * 60 * 1000
53219
- };
53220
- BUCKET_MS = {
53221
- "1h": 5 * 60 * 1000,
53222
- "6h": 15 * 60 * 1000,
53223
- "24h": 30 * 60 * 1000
53224
- };
53225
- metricsCache = new Map;
53226
- });
53227
-
53228
- // src/log-session-identity.ts
53229
- function toRecord(value) {
53230
- if (!value || typeof value !== "object" || Array.isArray(value))
53231
- return null;
53232
- return value;
53233
- }
53234
- function extractUserIdRawFromRequestBody(requestBody) {
53235
- const requestBodyRecord = toRecord(requestBody);
53236
- const metadata = toRecord(requestBodyRecord?.metadata);
53237
- if (!metadata) {
53238
- return {
53239
- hasMetadata: false,
53240
- userIdRaw: null
53241
- };
53242
- }
53243
- const userId = metadata.user_id;
53244
- if (typeof userId !== "string" || userId.trim() === "") {
53245
- return {
53246
- hasMetadata: true,
53247
- userIdRaw: null
53248
- };
53249
- }
53250
- return {
53251
- hasMetadata: true,
53252
- userIdRaw: userId
53253
- };
53254
- }
53255
- function parseUserSessionFromJsonFormat(userIdRaw) {
53256
- let parsed;
53257
- try {
53258
- parsed = JSON.parse(userIdRaw);
53259
- } catch {
53260
- return null;
53261
- }
53262
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
53263
- return null;
53264
- const obj = parsed;
53265
- const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
53266
- if (!sessionId)
53267
- return null;
53268
- const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
53269
- return { userKey: userKey || sessionId, sessionId };
53270
- }
53271
- function parseUserSessionFromUserIdRaw(userIdRaw) {
53272
- if (userIdRaw.trimStart().startsWith("{")) {
53273
- return parseUserSessionFromJsonFormat(userIdRaw);
53274
- }
53275
- const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
53276
- if (index <= 0)
53277
- return null;
53278
- const userKey = userIdRaw.slice(0, index).trim();
53279
- const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
53280
- if (!userKey || !sessionId)
53281
- return null;
53282
- return { userKey, sessionId };
53283
- }
53284
- function resolveLogSessionIdentity(requestBody) {
53285
- const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
53286
- if (!userIdRaw) {
53287
- return {
53288
- hasMetadata,
53289
- userIdRaw: null,
53290
- userKey: null,
53291
- sessionId: null
53292
- };
53293
- }
53294
- const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
53295
- return {
53296
- hasMetadata,
53297
- userIdRaw,
53298
- userKey: parsed?.userKey ?? null,
53299
- sessionId: parsed?.sessionId ?? null
53300
- };
53301
- }
53302
- var USER_SESSION_DELIMITER = "_account__session_";
53303
-
53304
- // src/token-usage.ts
53305
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync } from "fs";
53306
- import { resolve as resolve5 } from "path";
53307
- function asRecord(value) {
53308
- if (!value || typeof value !== "object" || Array.isArray(value))
53309
- return null;
53310
- return value;
53311
- }
53312
- function numeric(value) {
53313
- if (typeof value === "number" && Number.isFinite(value))
53314
- return value;
53315
- if (typeof value === "string" && value.trim()) {
53316
- const parsed = Number(value);
53317
- if (Number.isFinite(parsed))
53318
- return parsed;
53319
- }
53320
- return null;
53321
- }
53322
- function numberAt(value, path) {
53323
- let current = value;
53324
- for (const key of path) {
53325
- const record2 = asRecord(current);
53326
- if (!record2 || !(key in record2))
53327
- return null;
53328
- current = record2[key];
53329
- }
53330
- return numeric(current);
53331
- }
53332
- function firstNumber(value, paths) {
53333
- for (const path of paths) {
53334
- const found = numberAt(value, path);
53335
- if (found !== null)
53336
- return found;
53337
- }
53338
- return null;
53339
- }
53340
- function maxNullable(...values) {
53341
- const numbers = values.filter((value) => value !== null && value !== undefined);
53342
- if (numbers.length === 0)
53343
- return null;
53344
- return Math.max(...numbers);
53345
- }
53346
- function sumNullable(...values) {
53347
- let total = 0;
53348
- let hasValue = false;
53349
- for (const value of values) {
53350
- if (value === null || value === undefined)
53351
- continue;
53352
- total += value;
53353
- hasValue = true;
53354
- }
53355
- return hasValue ? total : null;
53356
- }
53357
- function roundPercent(numerator, denominator) {
53358
- if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
53359
- return null;
53360
- return Number((numerator / denominator * 100).toFixed(2));
53361
- }
53362
- function inferProviderStyle(usage, providerHint) {
53363
- const hint = providerHint?.toLowerCase() ?? "";
53364
- if (hint.includes("anthropic") || hint.includes("claude"))
53365
- return "anthropic";
53366
- if (hint.includes("gemini") || hint.includes("google"))
53367
- return "gemini";
53368
- if (hint.includes("deepseek"))
53369
- return "deepseek";
53370
- if (hint.includes("cohere"))
53371
- return "cohere";
53372
- if (hint.includes("mistral"))
53373
- return "mistral";
53374
- if (hint.includes("openrouter"))
53375
- return "openrouter";
53376
- if (hint.includes("openai") || hint.includes("gpt-"))
53377
- return "openai";
53378
- if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
53379
- return "anthropic";
53380
- }
53381
- if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
53382
- return "deepseek";
53383
- }
53384
- if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
53385
- return "gemini";
53386
- }
53387
- if ("billed_units" in usage || "tokens" in usage) {
53388
- return "cohere";
53389
- }
53390
- if ("prompt_tokens" in usage || "completion_tokens" in usage) {
53391
- return "openai";
53392
- }
53393
- if ("input_tokens" in usage || "output_tokens" in usage) {
53394
- return "openai";
53395
- }
53396
- return "unknown";
53397
- }
53398
- function createEmptyMetrics2(input) {
53399
- return {
53400
- schemaVersion: 1,
53401
- source: input.source,
53402
- providerStyle: input.providerStyle,
53403
- inputTokens: null,
53404
- outputTokens: null,
53405
- totalTokens: null,
53406
- cachedInputTokens: null,
53407
- cacheHitInputTokens: null,
53408
- cacheHitRate: null,
53409
- cacheHitRateDenominatorTokens: null,
53410
- cacheHitRateFormula: null,
53411
- cacheReadInputTokens: null,
53412
- cacheCreationInputTokens: null,
53413
- cacheCreationInputTokens5m: null,
53414
- cacheCreationInputTokens1h: null,
53415
- cacheWriteInputTokens: null,
53416
- cacheMissInputTokens: null,
53417
- reasoningTokens: null,
53418
- audioInputTokens: null,
53419
- audioOutputTokens: null,
53420
- textInputTokens: null,
53421
- textOutputTokens: null,
53422
- acceptedPredictionTokens: null,
53423
- rejectedPredictionTokens: null,
53424
- toolUsePromptTokens: null,
53425
- billableInputTokens: null,
53426
- billableOutputTokens: null,
53427
- creditUsage: null,
53428
- cost: null,
53429
- rawUsage: input.rawUsage,
53430
- rawUsagePath: input.rawUsagePath,
53431
- warnings: []
53008
+ if ("billed_units" in usage || "tokens" in usage) {
53009
+ return "cohere";
53010
+ }
53011
+ if ("prompt_tokens" in usage || "completion_tokens" in usage) {
53012
+ return "openai";
53013
+ }
53014
+ if ("input_tokens" in usage || "output_tokens" in usage) {
53015
+ return "openai";
53016
+ }
53017
+ return "unknown";
53018
+ }
53019
+ function createEmptyMetrics(input) {
53020
+ return {
53021
+ schemaVersion: 1,
53022
+ source: input.source,
53023
+ providerStyle: input.providerStyle,
53024
+ inputTokens: null,
53025
+ outputTokens: null,
53026
+ totalTokens: null,
53027
+ cachedInputTokens: null,
53028
+ cacheHitInputTokens: null,
53029
+ cacheHitRate: null,
53030
+ cacheHitRateDenominatorTokens: null,
53031
+ cacheHitRateFormula: null,
53032
+ cacheReadInputTokens: null,
53033
+ cacheCreationInputTokens: null,
53034
+ cacheCreationInputTokens5m: null,
53035
+ cacheCreationInputTokens1h: null,
53036
+ cacheWriteInputTokens: null,
53037
+ cacheMissInputTokens: null,
53038
+ reasoningTokens: null,
53039
+ audioInputTokens: null,
53040
+ audioOutputTokens: null,
53041
+ textInputTokens: null,
53042
+ textOutputTokens: null,
53043
+ acceptedPredictionTokens: null,
53044
+ rejectedPredictionTokens: null,
53045
+ toolUsePromptTokens: null,
53046
+ billableInputTokens: null,
53047
+ billableOutputTokens: null,
53048
+ creditUsage: null,
53049
+ cost: null,
53050
+ rawUsage: input.rawUsage,
53051
+ rawUsagePath: input.rawUsagePath,
53052
+ warnings: []
53432
53053
  };
53433
53054
  }
53434
53055
  function hasAnyTokenSignal(metrics) {
@@ -53451,7 +53072,7 @@ function hasAnyTokenSignal(metrics) {
53451
53072
  function normalizeUsageObject(input) {
53452
53073
  const { usage, source, rawUsagePath, providerHint } = input;
53453
53074
  const providerStyle = inferProviderStyle(usage, providerHint);
53454
- const metrics = createEmptyMetrics2({
53075
+ const metrics = createEmptyMetrics({
53455
53076
  source,
53456
53077
  providerStyle,
53457
53078
  rawUsage: usage,
@@ -53724,165 +53345,594 @@ function extractTokenUsageFromJson(value, options) {
53724
53345
  }
53725
53346
  return merged;
53726
53347
  }
53727
- function extractTokenUsageFromResponseText(text2, source = "response_body", providerHint) {
53728
- if (!text2?.trim())
53729
- return null;
53730
- try {
53731
- return extractTokenUsageFromJson(JSON.parse(text2), { source, providerHint });
53732
- } catch {
53733
- return null;
53348
+ function extractTokenUsageFromResponseText(text2, source = "response_body", providerHint) {
53349
+ if (!text2?.trim())
53350
+ return null;
53351
+ try {
53352
+ return extractTokenUsageFromJson(JSON.parse(text2), { source, providerHint });
53353
+ } catch {
53354
+ return null;
53355
+ }
53356
+ }
53357
+ function processSseMessage(dataLines, source, providerHint) {
53358
+ if (dataLines.length === 0)
53359
+ return null;
53360
+ const data = dataLines.join(`
53361
+ `).trim();
53362
+ if (!data || data === "[DONE]")
53363
+ return null;
53364
+ try {
53365
+ return extractTokenUsageFromJson(JSON.parse(data), {
53366
+ source,
53367
+ providerHint,
53368
+ rawUsagePathPrefix: source === "stream_file" ? "stream" : "stream"
53369
+ });
53370
+ } catch {
53371
+ return null;
53372
+ }
53373
+ }
53374
+ function extractTokenUsageFromSseText(text2, source = "stream_file", providerHint) {
53375
+ if (!text2?.trim())
53376
+ return null;
53377
+ let merged = null;
53378
+ let dataLines = [];
53379
+ const flush = () => {
53380
+ merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source, providerHint));
53381
+ dataLines = [];
53382
+ };
53383
+ for (const rawLine of text2.split(/\r?\n/)) {
53384
+ if (rawLine === "") {
53385
+ flush();
53386
+ continue;
53387
+ }
53388
+ if (rawLine.startsWith("data:")) {
53389
+ dataLines.push(rawLine.slice(5).trimStart());
53390
+ }
53391
+ }
53392
+ flush();
53393
+ return merged;
53394
+ }
53395
+ function createTokenUsageStreamCollector(providerHint) {
53396
+ const decoder = new TextDecoder;
53397
+ let buffer = "";
53398
+ let dataLines = [];
53399
+ let latest = null;
53400
+ const flushMessage = () => {
53401
+ latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
53402
+ dataLines = [];
53403
+ };
53404
+ const processLine = (rawLine) => {
53405
+ const line = rawLine.replace(/\r$/, "");
53406
+ if (line === "") {
53407
+ flushMessage();
53408
+ return;
53409
+ }
53410
+ if (line.startsWith("data:")) {
53411
+ dataLines.push(line.slice(5).trimStart());
53412
+ }
53413
+ };
53414
+ return {
53415
+ addChunk(chunk) {
53416
+ buffer += decoder.decode(chunk, { stream: true });
53417
+ let newlineIndex = buffer.indexOf(`
53418
+ `);
53419
+ while (newlineIndex >= 0) {
53420
+ processLine(buffer.slice(0, newlineIndex));
53421
+ buffer = buffer.slice(newlineIndex + 1);
53422
+ newlineIndex = buffer.indexOf(`
53423
+ `);
53424
+ }
53425
+ },
53426
+ getUsage() {
53427
+ buffer += decoder.decode();
53428
+ if (buffer) {
53429
+ processLine(buffer);
53430
+ buffer = "";
53431
+ }
53432
+ flushMessage();
53433
+ return latest;
53434
+ }
53435
+ };
53436
+ }
53437
+ function safeReadStreamFile(streamFile, baseDir) {
53438
+ if (!streamFile)
53439
+ return { content: null, warning: null };
53440
+ try {
53441
+ const candidates = [streamFile];
53442
+ if (baseDir)
53443
+ candidates.push(resolve5(baseDir, streamFile));
53444
+ for (const candidate of candidates) {
53445
+ const resolved = resolve5(candidate);
53446
+ if (!resolved.endsWith(".sse.raw"))
53447
+ continue;
53448
+ if (!existsSync4(resolved))
53449
+ continue;
53450
+ const stats = statSync(resolved);
53451
+ if (stats.size > MAX_STREAM_USAGE_BYTES) {
53452
+ return {
53453
+ content: null,
53454
+ warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
53455
+ };
53456
+ }
53457
+ return { content: readFileSync6(resolved, "utf-8"), warning: null };
53458
+ }
53459
+ } catch (err) {
53460
+ return {
53461
+ content: null,
53462
+ warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
53463
+ };
53464
+ }
53465
+ return { content: null, warning: null };
53466
+ }
53467
+ function extractTokenUsageFromLogEvent(event, options = {}) {
53468
+ if (event.token_usage) {
53469
+ return {
53470
+ rawUsage: null,
53471
+ ...event.token_usage,
53472
+ source: event.token_usage.source ?? "explicit"
53473
+ };
53474
+ }
53475
+ const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
53476
+ const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
53477
+ if (responseBodyUsage)
53478
+ return responseBodyUsage;
53479
+ const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
53480
+ if (responseAfterPluginsUsage)
53481
+ return responseAfterPluginsUsage;
53482
+ const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
53483
+ if (responseBeforePluginsUsage)
53484
+ return responseBeforePluginsUsage;
53485
+ const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
53486
+ return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
53487
+ }
53488
+ function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
53489
+ const metrics = extractTokenUsageFromLogEvent(event, options);
53490
+ return metrics ? toTokenUsageSummary(metrics) : null;
53491
+ }
53492
+ function enrichLogEventTokenUsage(event, options = {}) {
53493
+ if (event.token_usage)
53494
+ return event;
53495
+ const tokenUsage = extractTokenUsageFromLogEvent(event, options);
53496
+ if (!tokenUsage)
53497
+ return event;
53498
+ return {
53499
+ ...event,
53500
+ token_usage: tokenUsage
53501
+ };
53502
+ }
53503
+ var MAX_STREAM_USAGE_BYTES;
53504
+ var init_token_usage = __esm(() => {
53505
+ MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
53506
+ });
53507
+
53508
+ // src/log-metrics.ts
53509
+ import { createReadStream, existsSync as existsSync5 } from "fs";
53510
+ import { join as join6 } from "path";
53511
+ import { createInterface } from "readline";
53512
+ function isLogMetricsWindow(value) {
53513
+ return value === "1h" || value === "6h" || value === "24h";
53514
+ }
53515
+ function toPercent(numerator, denominator) {
53516
+ if (denominator <= 0)
53517
+ return 0;
53518
+ return Number((numerator / denominator * 100).toFixed(2));
53519
+ }
53520
+ function toDayStart(ms) {
53521
+ const date5 = new Date(ms);
53522
+ return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
53523
+ }
53524
+ function listDateStrings(fromMs, toMs) {
53525
+ const result = [];
53526
+ for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
53527
+ result.push(new Date(day).toISOString().slice(0, 10));
53528
+ }
53529
+ return result;
53530
+ }
53531
+ function getStatusClass(event) {
53532
+ if (event.error_type)
53533
+ return "network_error";
53534
+ const status = event.upstream_status ?? 0;
53535
+ if (status >= 200 && status < 300)
53536
+ return "2xx";
53537
+ if (status >= 400 && status < 500)
53538
+ return "4xx";
53539
+ if (status >= 500)
53540
+ return "5xx";
53541
+ return "network_error";
53542
+ }
53543
+ function isErrorEvent(event) {
53544
+ if (event.error_type)
53545
+ return true;
53546
+ const status = event.upstream_status ?? 0;
53547
+ return status < 200 || status >= 400;
53548
+ }
53549
+ function percentile(sortedNumbers, ratio) {
53550
+ if (sortedNumbers.length === 0)
53551
+ return 0;
53552
+ const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
53553
+ return Math.round(sortedNumbers[index]);
53554
+ }
53555
+ function createEmptyMetrics2(window2, nowMs, source, warnings = []) {
53556
+ const fromMs = nowMs - WINDOW_MS[window2];
53557
+ const bucketMs = BUCKET_MS[window2];
53558
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53559
+ const series = Array.from({ length: bucketCount }, (_, i) => ({
53560
+ ts: new Date(fromMs + i * bucketMs).toISOString(),
53561
+ requests: 0,
53562
+ errors: 0,
53563
+ avgLatencyMs: 0
53564
+ }));
53565
+ return {
53566
+ window: window2,
53567
+ from: new Date(fromMs).toISOString(),
53568
+ to: new Date(nowMs).toISOString(),
53569
+ generatedAt: new Date(nowMs).toISOString(),
53570
+ source,
53571
+ summary: {
53572
+ totalRequests: 0,
53573
+ successRequests: 0,
53574
+ errorRequests: 0,
53575
+ successRate: 0,
53576
+ avgLatencyMs: 0,
53577
+ p95LatencyMs: 0,
53578
+ totalRequestBytes: 0,
53579
+ totalResponseBytes: 0
53580
+ },
53581
+ tokens: {
53582
+ usageCount: 0,
53583
+ inputTokens: 0,
53584
+ outputTokens: 0,
53585
+ totalTokens: 0,
53586
+ cachedInputTokens: 0,
53587
+ cacheHitInputTokens: 0,
53588
+ cacheHitRateDenominatorTokens: 0,
53589
+ cacheHitRate: 0,
53590
+ reasoningTokens: 0,
53591
+ cost: null
53592
+ },
53593
+ series,
53594
+ topProviders: [],
53595
+ topRouteTypes: [],
53596
+ statusClasses: {
53597
+ "2xx": 0,
53598
+ "4xx": 0,
53599
+ "5xx": 0,
53600
+ network_error: 0
53601
+ },
53602
+ warnings
53603
+ };
53604
+ }
53605
+ async function getLogMetrics(options) {
53606
+ const window2 = options.window ?? "24h";
53607
+ const refresh = options.refresh === true;
53608
+ const nowMs = options.nowMs ?? Date.now();
53609
+ const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
53610
+ if (!logEnabled) {
53611
+ return createEmptyMetrics2(window2, nowMs, {
53612
+ logEnabled: false,
53613
+ baseDir: null,
53614
+ filesScanned: 0,
53615
+ linesScanned: 0,
53616
+ partial: false
53617
+ }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
53734
53618
  }
53735
- }
53736
- function processSseMessage(dataLines, source, providerHint) {
53737
- if (dataLines.length === 0)
53738
- return null;
53739
- const data = dataLines.join(`
53740
- `).trim();
53741
- if (!data || data === "[DONE]")
53742
- return null;
53743
- try {
53744
- return extractTokenUsageFromJson(JSON.parse(data), {
53745
- source,
53746
- providerHint,
53747
- rawUsagePathPrefix: source === "stream_file" ? "stream" : "stream"
53748
- });
53749
- } catch {
53750
- return null;
53619
+ const baseDir = resolveLogBaseDir(options.logConfig);
53620
+ const cacheKey = `${baseDir}:${window2}`;
53621
+ const cached2 = metricsCache.get(cacheKey);
53622
+ if (!refresh && cached2 && cached2.expiresAt > nowMs) {
53623
+ return cached2.value;
53751
53624
  }
53752
- }
53753
- function extractTokenUsageFromSseText(text2, source = "stream_file", providerHint) {
53754
- if (!text2?.trim())
53755
- return null;
53756
- let merged = null;
53757
- let dataLines = [];
53758
- const flush = () => {
53759
- merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source, providerHint));
53760
- dataLines = [];
53761
- };
53762
- for (const rawLine of text2.split(/\r?\n/)) {
53763
- if (rawLine === "") {
53764
- flush();
53765
- continue;
53766
- }
53767
- if (rawLine.startsWith("data:")) {
53768
- dataLines.push(rawLine.slice(5).trimStart());
53769
- }
53625
+ const eventsDir = join6(baseDir, "events");
53626
+ if (!existsSync5(eventsDir)) {
53627
+ const empty = createEmptyMetrics2(window2, nowMs, {
53628
+ logEnabled: true,
53629
+ baseDir,
53630
+ filesScanned: 0,
53631
+ linesScanned: 0,
53632
+ partial: false
53633
+ }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
53634
+ metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
53635
+ return empty;
53770
53636
  }
53771
- flush();
53772
- return merged;
53773
- }
53774
- function createTokenUsageStreamCollector(providerHint) {
53775
- const decoder = new TextDecoder;
53776
- let buffer = "";
53777
- let dataLines = [];
53778
- let latest = null;
53779
- const flushMessage = () => {
53780
- latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
53781
- dataLines = [];
53637
+ const fromMs = nowMs - WINDOW_MS[window2];
53638
+ const bucketMs = BUCKET_MS[window2];
53639
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53640
+ const buckets = Array.from({ length: bucketCount }, () => ({
53641
+ requests: 0,
53642
+ errors: 0,
53643
+ latencySum: 0,
53644
+ latencyCount: 0
53645
+ }));
53646
+ const providerAgg = new Map;
53647
+ const routeTypeAgg = new Map;
53648
+ const latencies = [];
53649
+ const statusClasses = {
53650
+ "2xx": 0,
53651
+ "4xx": 0,
53652
+ "5xx": 0,
53653
+ network_error: 0
53782
53654
  };
53783
- const processLine = (rawLine) => {
53784
- const line = rawLine.replace(/\r$/, "");
53785
- if (line === "") {
53786
- flushMessage();
53787
- return;
53788
- }
53789
- if (line.startsWith("data:")) {
53790
- dataLines.push(line.slice(5).trimStart());
53655
+ let filesScanned = 0;
53656
+ let linesScanned = 0;
53657
+ let parseErrors = 0;
53658
+ let partial2 = false;
53659
+ let totalRequests = 0;
53660
+ let successRequests = 0;
53661
+ let errorRequests = 0;
53662
+ let totalLatency = 0;
53663
+ let totalRequestBytes = 0;
53664
+ let totalResponseBytes = 0;
53665
+ let tokenUsageCount = 0;
53666
+ let tokenInput = 0;
53667
+ let tokenOutput = 0;
53668
+ let tokenTotal = 0;
53669
+ let tokenCachedInput = 0;
53670
+ let tokenCacheHitInput = 0;
53671
+ let tokenCacheHitDenominator = 0;
53672
+ let tokenReasoning = 0;
53673
+ let tokenCost = 0;
53674
+ let tokenCostSeen = false;
53675
+ const warnings = [];
53676
+ const dateStrings = listDateStrings(fromMs, nowMs);
53677
+ for (const dateStr of dateStrings) {
53678
+ if (linesScanned >= MAX_LINES_SCANNED) {
53679
+ partial2 = true;
53680
+ break;
53791
53681
  }
53792
- };
53793
- return {
53794
- addChunk(chunk) {
53795
- buffer += decoder.decode(chunk, { stream: true });
53796
- let newlineIndex = buffer.indexOf(`
53797
- `);
53798
- while (newlineIndex >= 0) {
53799
- processLine(buffer.slice(0, newlineIndex));
53800
- buffer = buffer.slice(newlineIndex + 1);
53801
- newlineIndex = buffer.indexOf(`
53802
- `);
53682
+ const filePath = join6(eventsDir, `${dateStr}.jsonl`);
53683
+ if (!existsSync5(filePath))
53684
+ continue;
53685
+ filesScanned += 1;
53686
+ try {
53687
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
53688
+ const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
53689
+ for await (const line of rl) {
53690
+ if (linesScanned >= MAX_LINES_SCANNED) {
53691
+ partial2 = true;
53692
+ rl.close();
53693
+ stream.destroy();
53694
+ break;
53695
+ }
53696
+ linesScanned += 1;
53697
+ if (!line.trim())
53698
+ continue;
53699
+ let event;
53700
+ try {
53701
+ event = JSON.parse(line);
53702
+ } catch {
53703
+ parseErrors += 1;
53704
+ continue;
53705
+ }
53706
+ if (!event.ts_start)
53707
+ continue;
53708
+ const ts = Date.parse(event.ts_start);
53709
+ if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
53710
+ continue;
53711
+ totalRequests += 1;
53712
+ const isError = isErrorEvent(event);
53713
+ if (isError) {
53714
+ errorRequests += 1;
53715
+ } else {
53716
+ successRequests += 1;
53717
+ }
53718
+ const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
53719
+ totalLatency += latency;
53720
+ latencies.push(latency);
53721
+ totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
53722
+ totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
53723
+ const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
53724
+ const bucket = buckets[bucketIndex];
53725
+ bucket.requests += 1;
53726
+ bucket.latencySum += latency;
53727
+ bucket.latencyCount += 1;
53728
+ if (isError)
53729
+ bucket.errors += 1;
53730
+ const providerKey = event.provider || "unknown";
53731
+ const providerRow = providerAgg.get(providerKey) ?? {
53732
+ requests: 0,
53733
+ errors: 0,
53734
+ latencySum: 0
53735
+ };
53736
+ providerRow.requests += 1;
53737
+ providerRow.latencySum += latency;
53738
+ if (isError)
53739
+ providerRow.errors += 1;
53740
+ providerAgg.set(providerKey, providerRow);
53741
+ const routeTypeKey = event.route_type || "unknown";
53742
+ const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
53743
+ requests: 0,
53744
+ errors: 0,
53745
+ latencySum: 0
53746
+ };
53747
+ routeTypeRow.requests += 1;
53748
+ routeTypeRow.latencySum += latency;
53749
+ if (isError)
53750
+ routeTypeRow.errors += 1;
53751
+ routeTypeAgg.set(routeTypeKey, routeTypeRow);
53752
+ statusClasses[getStatusClass(event)] += 1;
53753
+ const usage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
53754
+ if (usage) {
53755
+ tokenUsageCount += 1;
53756
+ tokenInput += Math.max(0, usage.inputTokens ?? 0);
53757
+ tokenOutput += Math.max(0, usage.outputTokens ?? 0);
53758
+ tokenTotal += Math.max(0, usage.totalTokens ?? 0);
53759
+ tokenCachedInput += Math.max(0, usage.cachedInputTokens ?? 0);
53760
+ tokenCacheHitInput += Math.max(0, usage.cacheHitInputTokens ?? 0);
53761
+ tokenCacheHitDenominator += Math.max(0, usage.cacheHitRateDenominatorTokens ?? 0);
53762
+ tokenReasoning += Math.max(0, usage.reasoningTokens ?? 0);
53763
+ if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
53764
+ tokenCost += usage.cost;
53765
+ tokenCostSeen = true;
53766
+ }
53767
+ }
53803
53768
  }
53769
+ } catch (err) {
53770
+ warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
53771
+ partial2 = true;
53772
+ }
53773
+ }
53774
+ if (parseErrors > 0) {
53775
+ warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
53776
+ }
53777
+ if (partial2) {
53778
+ warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
53779
+ }
53780
+ latencies.sort((a, b) => a - b);
53781
+ const series = buckets.map((bucket, index) => ({
53782
+ ts: new Date(fromMs + index * bucketMs).toISOString(),
53783
+ requests: bucket.requests,
53784
+ errors: bucket.errors,
53785
+ avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
53786
+ }));
53787
+ const topProviders = Array.from(providerAgg.entries()).map(([key, row]) => ({
53788
+ key,
53789
+ requests: row.requests,
53790
+ errorRate: toPercent(row.errors, row.requests),
53791
+ avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
53792
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53793
+ const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key, row]) => ({
53794
+ key,
53795
+ requests: row.requests,
53796
+ errorRate: toPercent(row.errors, row.requests)
53797
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53798
+ const response = {
53799
+ window: window2,
53800
+ from: new Date(fromMs).toISOString(),
53801
+ to: new Date(nowMs).toISOString(),
53802
+ generatedAt: new Date(nowMs).toISOString(),
53803
+ source: {
53804
+ logEnabled: true,
53805
+ baseDir,
53806
+ filesScanned,
53807
+ linesScanned,
53808
+ partial: partial2
53809
+ },
53810
+ summary: {
53811
+ totalRequests,
53812
+ successRequests,
53813
+ errorRequests,
53814
+ successRate: toPercent(successRequests, totalRequests),
53815
+ avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
53816
+ p95LatencyMs: percentile(latencies, 0.95),
53817
+ totalRequestBytes,
53818
+ totalResponseBytes
53804
53819
  },
53805
- getUsage() {
53806
- buffer += decoder.decode();
53807
- if (buffer) {
53808
- processLine(buffer);
53809
- buffer = "";
53810
- }
53811
- flushMessage();
53812
- return latest;
53813
- }
53820
+ tokens: {
53821
+ usageCount: tokenUsageCount,
53822
+ inputTokens: tokenInput,
53823
+ outputTokens: tokenOutput,
53824
+ totalTokens: tokenTotal,
53825
+ cachedInputTokens: tokenCachedInput,
53826
+ cacheHitInputTokens: tokenCacheHitInput,
53827
+ cacheHitRateDenominatorTokens: tokenCacheHitDenominator,
53828
+ cacheHitRate: toPercent(tokenCacheHitInput, tokenCacheHitDenominator),
53829
+ reasoningTokens: tokenReasoning,
53830
+ cost: tokenCostSeen ? Number(tokenCost.toFixed(6)) : null
53831
+ },
53832
+ series,
53833
+ topProviders,
53834
+ topRouteTypes,
53835
+ statusClasses,
53836
+ warnings
53814
53837
  };
53838
+ metricsCache.set(cacheKey, {
53839
+ expiresAt: nowMs + CACHE_TTL_MS,
53840
+ value: response
53841
+ });
53842
+ return response;
53815
53843
  }
53816
- function safeReadStreamFile(streamFile, baseDir) {
53817
- if (!streamFile)
53818
- return { content: null, warning: null };
53819
- try {
53820
- const candidates = [streamFile];
53821
- if (baseDir)
53822
- candidates.push(resolve5(baseDir, streamFile));
53823
- for (const candidate of candidates) {
53824
- const resolved = resolve5(candidate);
53825
- if (!resolved.endsWith(".sse.raw"))
53826
- continue;
53827
- if (!existsSync5(resolved))
53828
- continue;
53829
- const stats = statSync(resolved);
53830
- if (stats.size > MAX_STREAM_USAGE_BYTES) {
53831
- return {
53832
- content: null,
53833
- warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
53834
- };
53835
- }
53836
- return { content: readFileSync6(resolved, "utf-8"), warning: null };
53837
- }
53838
- } catch (err) {
53844
+ var WINDOW_MS, BUCKET_MS, TOP_LIMIT = 5, MAX_LINES_SCANNED = 250000, CACHE_TTL_MS = 15000, metricsCache;
53845
+ var init_log_metrics = __esm(() => {
53846
+ init_config();
53847
+ init_token_usage();
53848
+ WINDOW_MS = {
53849
+ "1h": 60 * 60 * 1000,
53850
+ "6h": 6 * 60 * 60 * 1000,
53851
+ "24h": 24 * 60 * 60 * 1000
53852
+ };
53853
+ BUCKET_MS = {
53854
+ "1h": 5 * 60 * 1000,
53855
+ "6h": 15 * 60 * 1000,
53856
+ "24h": 30 * 60 * 1000
53857
+ };
53858
+ metricsCache = new Map;
53859
+ });
53860
+
53861
+ // src/log-session-identity.ts
53862
+ function toRecord(value) {
53863
+ if (!value || typeof value !== "object" || Array.isArray(value))
53864
+ return null;
53865
+ return value;
53866
+ }
53867
+ function extractUserIdRawFromRequestBody(requestBody) {
53868
+ const requestBodyRecord = toRecord(requestBody);
53869
+ const metadata = toRecord(requestBodyRecord?.metadata);
53870
+ if (!metadata) {
53839
53871
  return {
53840
- content: null,
53841
- warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
53872
+ hasMetadata: false,
53873
+ userIdRaw: null
53842
53874
  };
53843
53875
  }
53844
- return { content: null, warning: null };
53845
- }
53846
- function extractTokenUsageFromLogEvent(event, options = {}) {
53847
- if (event.token_usage) {
53876
+ const userId = metadata.user_id;
53877
+ if (typeof userId !== "string" || userId.trim() === "") {
53848
53878
  return {
53849
- rawUsage: null,
53850
- ...event.token_usage,
53851
- source: event.token_usage.source ?? "explicit"
53879
+ hasMetadata: true,
53880
+ userIdRaw: null
53852
53881
  };
53853
53882
  }
53854
- const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
53855
- const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
53856
- if (responseBodyUsage)
53857
- return responseBodyUsage;
53858
- const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
53859
- if (responseAfterPluginsUsage)
53860
- return responseAfterPluginsUsage;
53861
- const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
53862
- if (responseBeforePluginsUsage)
53863
- return responseBeforePluginsUsage;
53864
- const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
53865
- return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
53883
+ return {
53884
+ hasMetadata: true,
53885
+ userIdRaw: userId
53886
+ };
53866
53887
  }
53867
- function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
53868
- const metrics = extractTokenUsageFromLogEvent(event, options);
53869
- return metrics ? toTokenUsageSummary(metrics) : null;
53888
+ function parseUserSessionFromJsonFormat(userIdRaw) {
53889
+ let parsed;
53890
+ try {
53891
+ parsed = JSON.parse(userIdRaw);
53892
+ } catch {
53893
+ return null;
53894
+ }
53895
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
53896
+ return null;
53897
+ const obj = parsed;
53898
+ const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
53899
+ if (!sessionId)
53900
+ return null;
53901
+ const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
53902
+ return { userKey: userKey || sessionId, sessionId };
53870
53903
  }
53871
- function enrichLogEventTokenUsage(event, options = {}) {
53872
- if (event.token_usage)
53873
- return event;
53874
- const tokenUsage = extractTokenUsageFromLogEvent(event, options);
53875
- if (!tokenUsage)
53876
- return event;
53904
+ function parseUserSessionFromUserIdRaw(userIdRaw) {
53905
+ if (userIdRaw.trimStart().startsWith("{")) {
53906
+ return parseUserSessionFromJsonFormat(userIdRaw);
53907
+ }
53908
+ const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
53909
+ if (index <= 0)
53910
+ return null;
53911
+ const userKey = userIdRaw.slice(0, index).trim();
53912
+ const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
53913
+ if (!userKey || !sessionId)
53914
+ return null;
53915
+ return { userKey, sessionId };
53916
+ }
53917
+ function resolveLogSessionIdentity(requestBody) {
53918
+ const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
53919
+ if (!userIdRaw) {
53920
+ return {
53921
+ hasMetadata,
53922
+ userIdRaw: null,
53923
+ userKey: null,
53924
+ sessionId: null
53925
+ };
53926
+ }
53927
+ const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
53877
53928
  return {
53878
- ...event,
53879
- token_usage: tokenUsage
53929
+ hasMetadata,
53930
+ userIdRaw,
53931
+ userKey: parsed?.userKey ?? null,
53932
+ sessionId: parsed?.sessionId ?? null
53880
53933
  };
53881
53934
  }
53882
- var MAX_STREAM_USAGE_BYTES;
53883
- var init_token_usage = __esm(() => {
53884
- MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
53885
- });
53935
+ var USER_SESSION_DELIMITER = "_account__session_";
53886
53936
 
53887
53937
  // src/log-index.ts
53888
53938
  import { Database } from "bun:sqlite";
@@ -54275,6 +54325,31 @@ function buildWhereClause(query, options = {}) {
54275
54325
  usesFts
54276
54326
  };
54277
54327
  }
54328
+ function buildSessionsWhereClause(query) {
54329
+ const pseudo = {
54330
+ fromMs: query.fromMs,
54331
+ toMs: query.toMs,
54332
+ levels: [],
54333
+ providers: [],
54334
+ routeTypes: [],
54335
+ models: [],
54336
+ modelIns: [],
54337
+ modelOuts: [],
54338
+ users: query.users,
54339
+ sessions: query.sessions,
54340
+ statusClasses: [],
54341
+ hasError: null,
54342
+ q: query.q,
54343
+ sort: "time_desc",
54344
+ limit: 1,
54345
+ cursor: null
54346
+ };
54347
+ const { whereSql, params } = buildWhereClause(pseudo);
54348
+ return { whereSql, params };
54349
+ }
54350
+ function sortIndexedCountItems(map2) {
54351
+ return Array.from(map2.entries()).map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
54352
+ }
54278
54353
 
54279
54354
  class LogIndex {
54280
54355
  baseDir;
@@ -54482,6 +54557,163 @@ class LogIndex {
54482
54557
  }
54483
54558
  };
54484
54559
  }
54560
+ querySessions(query) {
54561
+ const startedAt = performance.now();
54562
+ const { whereSql, params } = buildSessionsWhereClause(query);
54563
+ const aggregatedWhere = `${whereSql} AND e.user_key IS NOT NULL AND e.session_id IS NOT NULL`;
54564
+ const summaryRow = this.db.query(`
54565
+ SELECT
54566
+ COUNT(*) AS totalRequests,
54567
+ COALESCE(SUM(has_metadata), 0) AS metadataRequests,
54568
+ COUNT(DISTINCT user_key) AS uniqueUsers,
54569
+ COUNT(DISTINCT CASE
54570
+ WHEN user_key IS NOT NULL AND session_id IS NOT NULL
54571
+ THEN user_key || ' ' || session_id
54572
+ END) AS uniqueSessions
54573
+ FROM log_events e
54574
+ ${whereSql}
54575
+ `).get(...params);
54576
+ const userRows = this.db.query(`
54577
+ SELECT
54578
+ user_key AS userKey,
54579
+ COUNT(*) AS requestCount,
54580
+ MIN(ts_ms) AS firstMs,
54581
+ MAX(ts_ms) AS lastMs,
54582
+ COUNT(DISTINCT session_id) AS sessionCount
54583
+ FROM log_events e
54584
+ ${aggregatedWhere}
54585
+ GROUP BY user_key
54586
+ `).all(...params);
54587
+ const sessionRows = this.db.query(`
54588
+ SELECT
54589
+ user_key AS userKey,
54590
+ session_id AS sessionId,
54591
+ COUNT(*) AS requestCount,
54592
+ MIN(ts_ms) AS firstMs,
54593
+ MAX(ts_ms) AS lastMs
54594
+ FROM log_events e
54595
+ ${aggregatedWhere}
54596
+ GROUP BY user_key, session_id
54597
+ `).all(...params);
54598
+ const userModelRows = this.db.query(`
54599
+ SELECT user_key AS userKey, model AS key, COUNT(*) AS count
54600
+ FROM log_events e
54601
+ ${aggregatedWhere}
54602
+ GROUP BY user_key, model
54603
+ `).all(...params);
54604
+ const userProviderRows = this.db.query(`
54605
+ SELECT user_key AS userKey, provider AS key, COUNT(*) AS count
54606
+ FROM log_events e
54607
+ ${aggregatedWhere}
54608
+ GROUP BY user_key, provider
54609
+ `).all(...params);
54610
+ const userRouteRows = this.db.query(`
54611
+ SELECT user_key AS userKey, route_type AS key, COUNT(*) AS count
54612
+ FROM log_events e
54613
+ ${aggregatedWhere}
54614
+ GROUP BY user_key, route_type
54615
+ `).all(...params);
54616
+ const sessionModelRows = this.db.query(`
54617
+ SELECT user_key AS userKey, session_id AS sessionId, model AS key, COUNT(*) AS count
54618
+ FROM log_events e
54619
+ ${aggregatedWhere}
54620
+ GROUP BY user_key, session_id, model
54621
+ `).all(...params);
54622
+ const latestRows = this.db.query(`
54623
+ SELECT userKey, sessionId, request_id AS latestRequestId
54624
+ FROM (
54625
+ SELECT
54626
+ user_key AS userKey,
54627
+ session_id AS sessionId,
54628
+ request_id,
54629
+ ROW_NUMBER() OVER (
54630
+ PARTITION BY user_key, session_id ORDER BY ts_ms DESC, id DESC
54631
+ ) AS rn
54632
+ FROM log_events e
54633
+ ${aggregatedWhere}
54634
+ )
54635
+ WHERE rn = 1
54636
+ `).all(...params);
54637
+ const userModels = new Map;
54638
+ const userProviders = new Map;
54639
+ const userRoutes = new Map;
54640
+ const sessionModels = new Map;
54641
+ const latestBySession = new Map;
54642
+ const addCount = (target, groupKey, key, count) => {
54643
+ if (!key)
54644
+ return;
54645
+ let inner = target.get(groupKey);
54646
+ if (!inner) {
54647
+ inner = new Map;
54648
+ target.set(groupKey, inner);
54649
+ }
54650
+ inner.set(key, count);
54651
+ };
54652
+ for (const row of userModelRows)
54653
+ addCount(userModels, row.userKey, row.key, row.count);
54654
+ for (const row of userProviderRows)
54655
+ addCount(userProviders, row.userKey, row.key, row.count);
54656
+ for (const row of userRouteRows)
54657
+ addCount(userRoutes, row.userKey, row.key, row.count);
54658
+ for (const row of sessionModelRows) {
54659
+ addCount(sessionModels, `${row.userKey}\x00${row.sessionId}`, row.key, row.count);
54660
+ }
54661
+ for (const row of latestRows) {
54662
+ latestBySession.set(`${row.userKey}\x00${row.sessionId}`, row.latestRequestId);
54663
+ }
54664
+ const sessionsByUser = new Map;
54665
+ for (const row of sessionRows) {
54666
+ const sessionKey = `${row.userKey}\x00${row.sessionId}`;
54667
+ const session = {
54668
+ sessionId: row.sessionId,
54669
+ requestCount: row.requestCount,
54670
+ firstSeenAt: new Date(row.firstMs).toISOString(),
54671
+ lastSeenAt: new Date(row.lastMs).toISOString(),
54672
+ models: sortIndexedCountItems(sessionModels.get(sessionKey) ?? new Map),
54673
+ latestRequestId: latestBySession.get(sessionKey) ?? ""
54674
+ };
54675
+ const list = sessionsByUser.get(row.userKey);
54676
+ if (list) {
54677
+ list.push(session);
54678
+ } else {
54679
+ sessionsByUser.set(row.userKey, [session]);
54680
+ }
54681
+ }
54682
+ const users = userRows.map((row) => {
54683
+ const sessions = (sessionsByUser.get(row.userKey) ?? []).sort((a, b) => {
54684
+ if (a.requestCount !== b.requestCount)
54685
+ return b.requestCount - a.requestCount;
54686
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
54687
+ });
54688
+ return {
54689
+ userKey: row.userKey,
54690
+ requestCount: row.requestCount,
54691
+ sessionCount: row.sessionCount,
54692
+ firstSeenAt: new Date(row.firstMs).toISOString(),
54693
+ lastSeenAt: new Date(row.lastMs).toISOString(),
54694
+ models: sortIndexedCountItems(userModels.get(row.userKey) ?? new Map),
54695
+ providers: sortIndexedCountItems(userProviders.get(row.userKey) ?? new Map),
54696
+ routeTypes: sortIndexedCountItems(userRoutes.get(row.userKey) ?? new Map),
54697
+ sessions
54698
+ };
54699
+ }).sort((a, b) => {
54700
+ if (a.requestCount !== b.requestCount)
54701
+ return b.requestCount - a.requestCount;
54702
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
54703
+ });
54704
+ return {
54705
+ from: new Date(query.fromMs).toISOString(),
54706
+ to: new Date(query.toMs).toISOString(),
54707
+ summary: {
54708
+ totalRequests: Number(summaryRow.totalRequests) || 0,
54709
+ metadataRequests: Number(summaryRow.metadataRequests) || 0,
54710
+ uniqueUsers: Number(summaryRow.uniqueUsers) || 0,
54711
+ uniqueSessions: Number(summaryRow.uniqueSessions) || 0
54712
+ },
54713
+ users,
54714
+ queryMs: Math.round((performance.now() - startedAt) * 100) / 100
54715
+ };
54716
+ }
54485
54717
  configure() {
54486
54718
  this.db.exec(`
54487
54719
  PRAGMA journal_mode = WAL;
@@ -54864,6 +55096,65 @@ async function queryIndexedLogEvents(logConfig, query) {
54864
55096
  };
54865
55097
  }
54866
55098
  }
55099
+ async function queryIndexedLogSessions(logConfig, query) {
55100
+ if (!logConfig || logConfig.enabled === false) {
55101
+ return {
55102
+ from: new Date(query.fromMs).toISOString(),
55103
+ to: new Date(query.toMs).toISOString(),
55104
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
55105
+ users: [],
55106
+ meta: {
55107
+ scannedFiles: 0,
55108
+ scannedLines: 0,
55109
+ parseErrors: 0,
55110
+ truncated: false,
55111
+ indexUsed: true,
55112
+ indexFresh: true,
55113
+ queryMs: 0
55114
+ }
55115
+ };
55116
+ }
55117
+ const baseDir = resolveLogBaseDir(logConfig);
55118
+ const index = getLogIndex(baseDir);
55119
+ if (!index)
55120
+ return null;
55121
+ try {
55122
+ const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
55123
+ const result = index.querySessions(query);
55124
+ return {
55125
+ from: result.from,
55126
+ to: result.to,
55127
+ summary: result.summary,
55128
+ users: result.users,
55129
+ meta: {
55130
+ scannedFiles: freshness.scannedFiles,
55131
+ scannedLines: freshness.scannedLines,
55132
+ parseErrors: freshness.parseErrors,
55133
+ truncated: false,
55134
+ indexUsed: true,
55135
+ indexFresh: true,
55136
+ queryMs: result.queryMs
55137
+ }
55138
+ };
55139
+ } catch (err) {
55140
+ return {
55141
+ from: new Date(query.fromMs).toISOString(),
55142
+ to: new Date(query.toMs).toISOString(),
55143
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
55144
+ users: [],
55145
+ meta: {
55146
+ scannedFiles: 0,
55147
+ scannedLines: 0,
55148
+ parseErrors: 0,
55149
+ truncated: false,
55150
+ indexUsed: false,
55151
+ indexFresh: false,
55152
+ queryMs: 0,
55153
+ fallbackReason: err instanceof Error ? err.message : String(err)
55154
+ }
55155
+ };
55156
+ }
55157
+ }
54867
55158
  function getIndexedLogEventDetail(logConfig, id) {
54868
55159
  if (!logConfig || logConfig.enabled === false)
54869
55160
  return null;
@@ -55543,7 +55834,7 @@ async function scanEvents(baseDir, query) {
55543
55834
  };
55544
55835
  }
55545
55836
  function isLogQueryWindow(value) {
55546
- return value === "1h" || value === "6h" || value === "24h";
55837
+ return value === "1h" || value === "6h" || value === "24h" || value === "7d" || value === "1mo" || value === "1y";
55547
55838
  }
55548
55839
  function getLogQueryWindowMs(window2) {
55549
55840
  return WINDOW_MS2[window2];
@@ -55860,7 +56151,10 @@ var init_log_query = __esm(() => {
55860
56151
  WINDOW_MS2 = {
55861
56152
  "1h": 60 * 60 * 1000,
55862
56153
  "6h": 6 * 60 * 60 * 1000,
55863
- "24h": 24 * 60 * 60 * 1000
56154
+ "24h": 24 * 60 * 60 * 1000,
56155
+ "7d": 7 * 24 * 60 * 60 * 1000,
56156
+ "1mo": 30 * 24 * 60 * 60 * 1000,
56157
+ "1y": 365 * 24 * 60 * 60 * 1000
55864
56158
  };
55865
56159
  });
55866
56160
 
@@ -56049,7 +56343,7 @@ function compileRealtimeQuery(input) {
56049
56343
  const nowMs = Date.now();
56050
56344
  const windowRaw = parseStringValue(query.window) ?? "24h";
56051
56345
  if (!isLogQueryWindow(windowRaw)) {
56052
- throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
56346
+ throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y");
56053
56347
  }
56054
56348
  const from = parseStringValue(query.from);
56055
56349
  const to = parseStringValue(query.to);
@@ -56639,6 +56933,23 @@ async function queryLogSessions(context2, input) {
56639
56933
  if (!logEnabled) {
56640
56934
  return createEmptyResult(normalized.fromMs, normalized.toMs);
56641
56935
  }
56936
+ const indexed = await queryIndexedLogSessions(context2.logConfig, {
56937
+ fromMs: normalized.fromMs,
56938
+ toMs: normalized.toMs,
56939
+ users: normalized.users,
56940
+ sessions: normalized.sessions,
56941
+ q: normalized.q
56942
+ });
56943
+ if (indexed?.meta.indexUsed) {
56944
+ return {
56945
+ from: indexed.from,
56946
+ to: indexed.to,
56947
+ summary: indexed.summary,
56948
+ users: indexed.users,
56949
+ meta: indexed.meta
56950
+ };
56951
+ }
56952
+ const fallbackReason = indexed?.meta.fallbackReason;
56642
56953
  const baseDir = resolveLogBaseDir(context2.logConfig);
56643
56954
  const eventsDir = join9(baseDir, "events");
56644
56955
  if (!existsSync8(eventsDir)) {
@@ -56762,13 +57073,16 @@ async function queryLogSessions(context2, input) {
56762
57073
  scannedFiles,
56763
57074
  scannedLines,
56764
57075
  parseErrors,
56765
- truncated
57076
+ truncated,
57077
+ indexUsed: false,
57078
+ ...fallbackReason ? { fallbackReason } : {}
56766
57079
  }
56767
57080
  };
56768
57081
  }
56769
57082
  var MAX_LINES_SCANNED3 = 250000, MAX_Q_LENGTH3 = 200;
56770
57083
  var init_log_sessions = __esm(() => {
56771
57084
  init_config();
57085
+ init_log_index();
56772
57086
  });
56773
57087
 
56774
57088
  // src/log-storage.ts
@@ -57299,7 +57613,7 @@ var init_openapi = __esm(() => {
57299
57613
  required: false,
57300
57614
  schema: {
57301
57615
  type: "string",
57302
- enum: ["1h", "6h", "24h"],
57616
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57303
57617
  default: "24h"
57304
57618
  },
57305
57619
  description: "\u65F6\u95F4\u7A97\u53E3\uFF08\u5F53\u672A\u63D0\u4F9B from/to \u65F6\u751F\u6548\uFF09"
@@ -57629,7 +57943,7 @@ var init_openapi = __esm(() => {
57629
57943
  required: false,
57630
57944
  schema: {
57631
57945
  type: "string",
57632
- enum: ["1h", "6h", "24h"],
57946
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57633
57947
  default: "24h"
57634
57948
  },
57635
57949
  description: "\u65F6\u95F4\u7A97\u53E3"
@@ -57750,7 +58064,7 @@ var init_openapi = __esm(() => {
57750
58064
  required: false,
57751
58065
  schema: {
57752
58066
  type: "string",
57753
- enum: ["1h", "6h", "24h"],
58067
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57754
58068
  default: "1h"
57755
58069
  }
57756
58070
  },
@@ -59260,7 +59574,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59260
59574
  try {
59261
59575
  const windowRaw = c.req.query("window") ?? "24h";
59262
59576
  if (!isLogQueryWindow(windowRaw)) {
59263
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59577
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59264
59578
  }
59265
59579
  const range = resolveLogQueryRange({
59266
59580
  window: windowRaw,
@@ -59311,7 +59625,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59311
59625
  try {
59312
59626
  const windowRaw = c.req.query("window") ?? "24h";
59313
59627
  if (!isLogQueryWindow(windowRaw)) {
59314
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59628
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59315
59629
  }
59316
59630
  const range = resolveLogQueryRange({
59317
59631
  window: windowRaw,
@@ -59350,7 +59664,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59350
59664
  try {
59351
59665
  const windowRaw = c.req.query("window") ?? "24h";
59352
59666
  if (!isLogQueryWindow(windowRaw)) {
59353
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59667
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59354
59668
  }
59355
59669
  const range = resolveLogQueryRange({
59356
59670
  window: windowRaw,
@@ -59400,7 +59714,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59400
59714
  const target = c.req.raw;
59401
59715
  const windowRaw = c.req.query("window") ?? "1h";
59402
59716
  if (!isLogQueryWindow(windowRaw)) {
59403
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59717
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59404
59718
  }
59405
59719
  const sortRaw = c.req.query("sort") ?? "time_desc";
59406
59720
  if (!validateSort(sortRaw)) {
@@ -64676,7 +64990,7 @@ defineSchemaCommand({
64676
64990
  {
64677
64991
  name: "window",
64678
64992
  type: "enum",
64679
- enum: ["1h", "6h", "24h"],
64993
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64680
64994
  default: "24h",
64681
64995
  description: "\u65F6\u95F4\u7A97\u53E3"
64682
64996
  },
@@ -64809,7 +65123,7 @@ defineSchemaCommand({
64809
65123
  {
64810
65124
  name: "window",
64811
65125
  type: "enum",
64812
- enum: ["1h", "6h", "24h"],
65126
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64813
65127
  default: "24h",
64814
65128
  description: "\u65F6\u95F4\u7A97\u53E3"
64815
65129
  }
@@ -64933,7 +65247,7 @@ defineSchemaCommand({
64933
65247
  {
64934
65248
  name: "window",
64935
65249
  type: "enum",
64936
- enum: ["1h", "6h", "24h"],
65250
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64937
65251
  default: "24h",
64938
65252
  description: "\u65F6\u95F4\u7A97\u53E3"
64939
65253
  },
@@ -65020,7 +65334,7 @@ defineSchemaCommand({
65020
65334
  {
65021
65335
  name: "window",
65022
65336
  type: "enum",
65023
- enum: ["1h", "6h", "24h"],
65337
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65024
65338
  default: "24h",
65025
65339
  description: "\u65F6\u95F4\u7A97\u53E3"
65026
65340
  },
@@ -65146,7 +65460,7 @@ defineSchemaCommand({
65146
65460
  {
65147
65461
  name: "window",
65148
65462
  type: "enum",
65149
- enum: ["1h", "6h", "24h"],
65463
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65150
65464
  default: "24h",
65151
65465
  description: "\u65F6\u95F4\u7A97\u53E3"
65152
65466
  }
@@ -65206,7 +65520,7 @@ defineSchemaCommand({
65206
65520
  {
65207
65521
  name: "window",
65208
65522
  type: "enum",
65209
- enum: ["1h", "6h", "24h"],
65523
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65210
65524
  default: "24h",
65211
65525
  description: "\u65F6\u95F4\u7A97\u53E3"
65212
65526
  },