@lakphy/local-router 0.5.3 → 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/entry.js CHANGED
@@ -4934,7 +4934,7 @@ var require_compile = __commonJS((exports) => {
4934
4934
  const schOrFunc = root2.refs[ref];
4935
4935
  if (schOrFunc)
4936
4936
  return schOrFunc;
4937
- let _sch = resolve4.call(this, root2, ref);
4937
+ let _sch = resolve5.call(this, root2, ref);
4938
4938
  if (_sch === undefined) {
4939
4939
  const schema = (_a21 = root2.localRefs) === null || _a21 === undefined ? undefined : _a21[ref];
4940
4940
  const { schemaId } = this.opts;
@@ -4961,7 +4961,7 @@ var require_compile = __commonJS((exports) => {
4961
4961
  function sameSchemaEnv(s1, s2) {
4962
4962
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
4963
4963
  }
4964
- function resolve4(root2, ref) {
4964
+ function resolve5(root2, ref) {
4965
4965
  let sch;
4966
4966
  while (typeof (sch = this.refs[ref]) == "string")
4967
4967
  ref = sch;
@@ -5491,7 +5491,7 @@ var require_fast_uri = __commonJS((exports, module) => {
5491
5491
  }
5492
5492
  return uri;
5493
5493
  }
5494
- function resolve4(baseURI, relativeURI, options) {
5494
+ function resolve5(baseURI, relativeURI, options) {
5495
5495
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
5496
5496
  const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
5497
5497
  schemelessOptions.skipEscape = true;
@@ -5719,7 +5719,7 @@ var require_fast_uri = __commonJS((exports, module) => {
5719
5719
  var fastUri = {
5720
5720
  SCHEMES,
5721
5721
  normalize,
5722
- resolve: resolve4,
5722
+ resolve: resolve5,
5723
5723
  resolveComponent,
5724
5724
  equal,
5725
5725
  serialize,
@@ -9284,8 +9284,218 @@ var require_dist2 = __commonJS((exports, module) => {
9284
9284
  });
9285
9285
 
9286
9286
  // src/index.ts
9287
- import { readFileSync as readFileSync5 } from "fs";
9288
- import { dirname as dirname2, resolve as resolve7 } from "path";
9287
+ import { readFileSync as readFileSync6 } from "fs";
9288
+ import { dirname as dirname3, resolve as resolve8 } from "path";
9289
+
9290
+ // src/cli/autostart.ts
9291
+ import { execSync } from "child_process";
9292
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
9293
+ import { homedir as homedir2, platform } from "os";
9294
+ import { dirname, join as join2 } from "path";
9295
+
9296
+ // src/cli/runtime.ts
9297
+ import { homedir } from "os";
9298
+ import { join, resolve } from "path";
9299
+ function getRuntimeDirs() {
9300
+ const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
9301
+ const root = override?.trim() ? override.trim() : join(homedir(), ".local-router");
9302
+ return {
9303
+ root,
9304
+ run: join(root, "run"),
9305
+ logs: join(root, "logs")
9306
+ };
9307
+ }
9308
+
9309
+ // src/cli/autostart.ts
9310
+ var LABEL = "com.lakphy.local-router";
9311
+ function getDaemonLogPath() {
9312
+ return getRuntimeDirs().logs + "/daemon.log";
9313
+ }
9314
+ function getLaunchAgentPath() {
9315
+ return join2(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
9316
+ }
9317
+ function buildPlist(opts) {
9318
+ const logPath = getDaemonLogPath();
9319
+ const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
9320
+ `);
9321
+ return `<?xml version="1.0" encoding="UTF-8"?>
9322
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9323
+ <plist version="1.0">
9324
+ <dict>
9325
+ <key>Label</key>
9326
+ <string>${escapeXml(opts.label)}</string>
9327
+ <key>ProgramArguments</key>
9328
+ <array>
9329
+ ${args}
9330
+ </array>
9331
+ <key>RunAtLoad</key>
9332
+ <true/>
9333
+ <key>KeepAlive</key>
9334
+ <false/>
9335
+ <key>StandardOutPath</key>
9336
+ <string>${escapeXml(logPath)}</string>
9337
+ <key>StandardErrorPath</key>
9338
+ <string>${escapeXml(logPath)}</string>
9339
+ </dict>
9340
+ </plist>
9341
+ `;
9342
+ }
9343
+ function escapeXml(s) {
9344
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9345
+ }
9346
+ function createMacosManager() {
9347
+ const plistPath = getLaunchAgentPath();
9348
+ return {
9349
+ platform: "macos",
9350
+ async isInstalled() {
9351
+ return existsSync(plistPath);
9352
+ },
9353
+ async install(opts) {
9354
+ const dir = dirname(plistPath);
9355
+ if (!existsSync(dir))
9356
+ mkdirSync(dir, { recursive: true });
9357
+ writeFileSync(plistPath, buildPlist(opts), "utf-8");
9358
+ try {
9359
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
9360
+ } catch {}
9361
+ execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9362
+ },
9363
+ async uninstall() {
9364
+ if (!existsSync(plistPath))
9365
+ return;
9366
+ try {
9367
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9368
+ } catch {}
9369
+ rmSync(plistPath, { force: true });
9370
+ },
9371
+ getServicePath() {
9372
+ return plistPath;
9373
+ }
9374
+ };
9375
+ }
9376
+ function getSystemdUnitPath() {
9377
+ return join2(homedir2(), ".config", "systemd", "user", "local-router.service");
9378
+ }
9379
+ function buildUnit(opts) {
9380
+ const logPath = getDaemonLogPath();
9381
+ const execStart = [opts.execPath, ...opts.args].join(" ");
9382
+ return `[Unit]
9383
+ Description=Local Router API Gateway
9384
+ After=network-online.target
9385
+
9386
+ [Service]
9387
+ Type=simple
9388
+ ExecStart=${execStart}
9389
+ Restart=on-failure
9390
+ RestartSec=5
9391
+ StandardOutput=append:${logPath}
9392
+ StandardError=append:${logPath}
9393
+
9394
+ [Install]
9395
+ WantedBy=default.target
9396
+ `;
9397
+ }
9398
+ function createLinuxManager() {
9399
+ const unitPath = getSystemdUnitPath();
9400
+ return {
9401
+ platform: "linux",
9402
+ async isInstalled() {
9403
+ if (!existsSync(unitPath))
9404
+ return false;
9405
+ try {
9406
+ const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
9407
+ encoding: "utf-8"
9408
+ }).trim();
9409
+ return out === "enabled";
9410
+ } catch {
9411
+ return false;
9412
+ }
9413
+ },
9414
+ async install(opts) {
9415
+ const dir = dirname(unitPath);
9416
+ if (!existsSync(dir))
9417
+ mkdirSync(dir, { recursive: true });
9418
+ writeFileSync(unitPath, buildUnit(opts), "utf-8");
9419
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9420
+ execSync("systemctl --user enable local-router", { stdio: "ignore" });
9421
+ },
9422
+ async uninstall() {
9423
+ try {
9424
+ execSync("systemctl --user disable local-router", { stdio: "ignore" });
9425
+ } catch {}
9426
+ rmSync(unitPath, { force: true });
9427
+ try {
9428
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9429
+ } catch {}
9430
+ },
9431
+ getServicePath() {
9432
+ return unitPath;
9433
+ }
9434
+ };
9435
+ }
9436
+ var WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
9437
+ var WIN_REG_VALUE = "LocalRouter";
9438
+ function createWindowsManager() {
9439
+ return {
9440
+ platform: "windows",
9441
+ async isInstalled() {
9442
+ try {
9443
+ execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
9444
+ return true;
9445
+ } catch {
9446
+ return false;
9447
+ }
9448
+ },
9449
+ async install(opts) {
9450
+ const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
9451
+ execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
9452
+ stdio: "ignore"
9453
+ });
9454
+ },
9455
+ async uninstall() {
9456
+ try {
9457
+ execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
9458
+ } catch {}
9459
+ },
9460
+ getServicePath() {
9461
+ return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
9462
+ }
9463
+ };
9464
+ }
9465
+ function createUnsupportedManager() {
9466
+ return {
9467
+ platform: "unsupported",
9468
+ async isInstalled() {
9469
+ return false;
9470
+ },
9471
+ async install() {
9472
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9473
+ },
9474
+ async uninstall() {
9475
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9476
+ },
9477
+ getServicePath() {
9478
+ return "";
9479
+ }
9480
+ };
9481
+ }
9482
+ function createAutostartManager() {
9483
+ const p = platform();
9484
+ if (p === "darwin")
9485
+ return createMacosManager();
9486
+ if (p === "linux")
9487
+ return createLinuxManager();
9488
+ if (p === "win32")
9489
+ return createWindowsManager();
9490
+ return createUnsupportedManager();
9491
+ }
9492
+ function getAutostartExecArgs() {
9493
+ const script = process.argv[1] ?? "dist/cli.js";
9494
+ return {
9495
+ execPath: process.execPath,
9496
+ args: [script, "__run-server", "--mode", "daemon"]
9497
+ };
9498
+ }
9289
9499
 
9290
9500
  // node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
9291
9501
  var marker = "vercel.ai.error";
@@ -29133,7 +29343,7 @@ function createProviderToolFactoryWithOutputSchema({
29133
29343
  supportsDeferredResults
29134
29344
  });
29135
29345
  }
29136
- async function resolve(value) {
29346
+ async function resolve2(value) {
29137
29347
  if (typeof value === "function") {
29138
29348
  value = value();
29139
29349
  }
@@ -32078,11 +32288,11 @@ var AnthropicMessagesLanguageModel = class {
32078
32288
  betas,
32079
32289
  headers
32080
32290
  }) {
32081
- return combineHeaders(await resolve(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
32291
+ return combineHeaders(await resolve2(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
32082
32292
  }
32083
32293
  async getBetasFromHeaders(requestHeaders) {
32084
32294
  var _a16, _b16;
32085
- const configHeaders = await resolve(this.config.headers);
32295
+ const configHeaders = await resolve2(this.config.headers);
32086
32296
  const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
32087
32297
  const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
32088
32298
  return new Set([
@@ -41267,7 +41477,7 @@ var GatewayFetchMetadata = class {
41267
41477
  try {
41268
41478
  const { value } = await getFromApi({
41269
41479
  url: `${this.config.baseURL}/config`,
41270
- headers: await resolve(this.config.headers()),
41480
+ headers: await resolve2(this.config.headers()),
41271
41481
  successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
41272
41482
  failedResponseHandler: createJsonErrorResponseHandler({
41273
41483
  errorSchema: exports_external.any(),
@@ -41285,7 +41495,7 @@ var GatewayFetchMetadata = class {
41285
41495
  const baseUrl = new URL(this.config.baseURL);
41286
41496
  const { value } = await getFromApi({
41287
41497
  url: `${baseUrl.origin}/v1/credits`,
41288
- headers: await resolve(this.config.headers()),
41498
+ headers: await resolve2(this.config.headers()),
41289
41499
  successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
41290
41500
  failedResponseHandler: createJsonErrorResponseHandler({
41291
41501
  errorSchema: exports_external.any(),
@@ -41350,7 +41560,7 @@ var GatewayLanguageModel = class {
41350
41560
  async doGenerate(options) {
41351
41561
  const { args, warnings } = await this.getArgs(options);
41352
41562
  const { abortSignal } = options;
41353
- const resolvedHeaders = await resolve(this.config.headers());
41563
+ const resolvedHeaders = await resolve2(this.config.headers());
41354
41564
  try {
41355
41565
  const {
41356
41566
  responseHeaders,
@@ -41358,7 +41568,7 @@ var GatewayLanguageModel = class {
41358
41568
  rawValue: rawResponse
41359
41569
  } = await postJsonToApi({
41360
41570
  url: this.getUrl(),
41361
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve(this.config.o11yHeaders)),
41571
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve2(this.config.o11yHeaders)),
41362
41572
  body: args,
41363
41573
  successfulResponseHandler: createJsonResponseHandler(exports_external.any()),
41364
41574
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -41381,11 +41591,11 @@ var GatewayLanguageModel = class {
41381
41591
  async doStream(options) {
41382
41592
  const { args, warnings } = await this.getArgs(options);
41383
41593
  const { abortSignal } = options;
41384
- const resolvedHeaders = await resolve(this.config.headers());
41594
+ const resolvedHeaders = await resolve2(this.config.headers());
41385
41595
  try {
41386
41596
  const { value: response, responseHeaders } = await postJsonToApi({
41387
41597
  url: this.getUrl(),
41388
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve(this.config.o11yHeaders)),
41598
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve2(this.config.o11yHeaders)),
41389
41599
  body: args,
41390
41600
  successfulResponseHandler: createEventSourceResponseHandler(exports_external.any()),
41391
41601
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -41471,7 +41681,7 @@ var GatewayEmbeddingModel = class {
41471
41681
  providerOptions
41472
41682
  }) {
41473
41683
  var _a92;
41474
- const resolvedHeaders = await resolve(this.config.headers());
41684
+ const resolvedHeaders = await resolve2(this.config.headers());
41475
41685
  try {
41476
41686
  const {
41477
41687
  responseHeaders,
@@ -41479,7 +41689,7 @@ var GatewayEmbeddingModel = class {
41479
41689
  rawValue
41480
41690
  } = await postJsonToApi({
41481
41691
  url: this.getUrl(),
41482
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
41692
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
41483
41693
  body: {
41484
41694
  values,
41485
41695
  ...providerOptions ? { providerOptions } : {}
@@ -41541,7 +41751,7 @@ var GatewayImageModel = class {
41541
41751
  abortSignal
41542
41752
  }) {
41543
41753
  var _a92, _b92, _c, _d;
41544
- const resolvedHeaders = await resolve(this.config.headers());
41754
+ const resolvedHeaders = await resolve2(this.config.headers());
41545
41755
  try {
41546
41756
  const {
41547
41757
  responseHeaders,
@@ -41549,7 +41759,7 @@ var GatewayImageModel = class {
41549
41759
  rawValue
41550
41760
  } = await postJsonToApi({
41551
41761
  url: this.getUrl(),
41552
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
41762
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
41553
41763
  body: {
41554
41764
  prompt,
41555
41765
  n,
@@ -41664,11 +41874,11 @@ var GatewayVideoModel = class {
41664
41874
  abortSignal
41665
41875
  }) {
41666
41876
  var _a92;
41667
- const resolvedHeaders = await resolve(this.config.headers());
41877
+ const resolvedHeaders = await resolve2(this.config.headers());
41668
41878
  try {
41669
41879
  const { responseHeaders, value: responseBody } = await postJsonToApi({
41670
41880
  url: this.getUrl(),
41671
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders), { accept: "text/event-stream" }),
41881
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders), { accept: "text/event-stream" }),
41672
41882
  body: {
41673
41883
  prompt,
41674
41884
  n,
@@ -44539,7 +44749,7 @@ var object2 = ({
44539
44749
  const schema = asSchema(inputSchema);
44540
44750
  return {
44541
44751
  name: "object",
44542
- responseFormat: resolve(schema.jsonSchema).then((jsonSchema2) => ({
44752
+ responseFormat: resolve2(schema.jsonSchema).then((jsonSchema2) => ({
44543
44753
  type: "json",
44544
44754
  schema: jsonSchema2,
44545
44755
  ...name21 != null && { name: name21 },
@@ -44601,7 +44811,7 @@ var array2 = ({
44601
44811
  const elementSchema = asSchema(inputElementSchema);
44602
44812
  return {
44603
44813
  name: "array",
44604
- responseFormat: resolve(elementSchema.jsonSchema).then((jsonSchema2) => {
44814
+ responseFormat: resolve2(elementSchema.jsonSchema).then((jsonSchema2) => {
44605
44815
  const { $schema, ...itemSchema } = jsonSchema2;
44606
44816
  return {
44607
44817
  type: "json",
@@ -49860,7 +50070,7 @@ var Hono2 = class extends Hono {
49860
50070
 
49861
50071
  // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/adapter/bun/serve-static.js
49862
50072
  import { stat } from "fs/promises";
49863
- import { join } from "path";
50073
+ import { join as join3 } from "path";
49864
50074
 
49865
50075
  // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/compress.js
49866
50076
  var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
@@ -49965,7 +50175,7 @@ var DEFAULT_DOCUMENT = "index.html";
49965
50175
  var serveStatic = (options) => {
49966
50176
  const root = options.root ?? "./";
49967
50177
  const optionPath = options.path;
49968
- const join = options.join ?? defaultJoin;
50178
+ const join3 = options.join ?? defaultJoin;
49969
50179
  return async (c, next) => {
49970
50180
  if (c.finalized) {
49971
50181
  return next();
@@ -49984,9 +50194,9 @@ var serveStatic = (options) => {
49984
50194
  return next();
49985
50195
  }
49986
50196
  }
49987
- let path = join(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
50197
+ let path = join3(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
49988
50198
  if (options.isDir && await options.isDir(path)) {
49989
- path = join(path, DEFAULT_DOCUMENT);
50199
+ path = join3(path, DEFAULT_DOCUMENT);
49990
50200
  }
49991
50201
  const getContent = options.getContent;
49992
50202
  let content = await getContent(path, c);
@@ -50038,7 +50248,7 @@ var serveStatic2 = (options) => {
50038
50248
  return serveStatic({
50039
50249
  ...options,
50040
50250
  getContent,
50041
- join,
50251
+ join: join3,
50042
50252
  isDir
50043
50253
  })(c, next);
50044
50254
  };
@@ -50130,9 +50340,9 @@ var upgradeWebSocket = defineWebSocketHelper((c, events) => {
50130
50340
  });
50131
50341
 
50132
50342
  // src/config.ts
50133
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
50134
- import { homedir } from "os";
50135
- import { join as join2, resolve as resolve2 } from "path";
50343
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
50344
+ import { homedir as homedir3 } from "os";
50345
+ import { join as join4, resolve as resolve3 } from "path";
50136
50346
 
50137
50347
  // node_modules/.bun/json5@2.2.3/node_modules/json5/dist/index.mjs
50138
50348
  var Space_Separator = /[\u1680\u2000-\u200A\u202F\u205F\u3000]/;
@@ -51300,8 +51510,8 @@ var DEFAULT_CONFIG = `{
51300
51510
  // },
51301
51511
  }`;
51302
51512
  function loadConfig(configPath) {
51303
- const absolutePath = resolve2(configPath);
51304
- const content = readFileSync(absolutePath, "utf-8");
51513
+ const absolutePath = resolve3(configPath);
51514
+ const content = readFileSync2(absolutePath, "utf-8");
51305
51515
  const config2 = dist_default.parse(content);
51306
51516
  for (const [routeType, modelMap] of Object.entries(config2.routes)) {
51307
51517
  if (!modelMap["*"]) {
@@ -51316,21 +51526,21 @@ function loadConfig(configPath) {
51316
51526
  return config2;
51317
51527
  }
51318
51528
  function createDefaultConfig(configPath) {
51319
- const configDir = resolve2(configPath, "..");
51320
- if (!existsSync(configDir)) {
51321
- mkdirSync(configDir, { recursive: true });
51529
+ const configDir = resolve3(configPath, "..");
51530
+ if (!existsSync2(configDir)) {
51531
+ mkdirSync2(configDir, { recursive: true });
51322
51532
  }
51323
- writeFileSync(configPath, DEFAULT_CONFIG, "utf-8");
51533
+ writeFileSync2(configPath, DEFAULT_CONFIG, "utf-8");
51324
51534
  console.log(`\u5DF2\u521B\u5EFA\u9ED8\u8BA4\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
51325
51535
  }
51326
51536
  function resolveDefaultConfigPath() {
51327
51537
  const localConfig = "config.json5";
51328
- if (existsSync(localConfig)) {
51538
+ if (existsSync2(localConfig)) {
51329
51539
  return localConfig;
51330
51540
  }
51331
- const globalConfigDir = join2(homedir(), ".local-router");
51332
- const globalConfig2 = join2(globalConfigDir, "config.json5");
51333
- if (!existsSync(globalConfig2)) {
51541
+ const globalConfigDir = join4(homedir3(), ".local-router");
51542
+ const globalConfig2 = join4(globalConfigDir, "config.json5");
51543
+ if (!existsSync2(globalConfig2)) {
51334
51544
  createDefaultConfig(globalConfig2);
51335
51545
  }
51336
51546
  return globalConfig2;
@@ -51349,19 +51559,19 @@ function parseConfigPath() {
51349
51559
  }
51350
51560
  function resolveLogBaseDir(logConfig) {
51351
51561
  if (logConfig?.baseDir) {
51352
- return resolve2(logConfig.baseDir);
51562
+ return resolve3(logConfig.baseDir);
51353
51563
  }
51354
- return join2(homedir(), ".local-router", "logs");
51564
+ return join4(homedir3(), ".local-router", "logs");
51355
51565
  }
51356
51566
 
51357
51567
  // src/config-store.ts
51358
- import { writeFileSync as writeFileSync2 } from "fs";
51359
- import { resolve as resolve3 } from "path";
51568
+ import { writeFileSync as writeFileSync3 } from "fs";
51569
+ import { resolve as resolve4 } from "path";
51360
51570
  class ConfigStore {
51361
51571
  config;
51362
51572
  absolutePath;
51363
51573
  constructor(configPath) {
51364
- this.absolutePath = resolve3(configPath);
51574
+ this.absolutePath = resolve4(configPath);
51365
51575
  this.config = loadConfig(this.absolutePath);
51366
51576
  }
51367
51577
  get() {
@@ -51376,7 +51586,7 @@ class ConfigStore {
51376
51586
  }
51377
51587
  save(newConfig) {
51378
51588
  const content = dist_default.stringify(newConfig, { space: 2, quote: '"' });
51379
- writeFileSync2(this.absolutePath, content, "utf-8");
51589
+ writeFileSync3(this.absolutePath, content, "utf-8");
51380
51590
  }
51381
51591
  validate(config2) {
51382
51592
  for (const [routeType, modelMap] of Object.entries(config2.routes)) {
@@ -51395,7 +51605,7 @@ class ConfigStore {
51395
51605
  // src/config-validate.ts
51396
51606
  var import__2020 = __toESM(require_2020(), 1);
51397
51607
  var import_ajv_formats = __toESM(require_dist2(), 1);
51398
- import { readFileSync as readFileSync2 } from "fs";
51608
+ import { readFileSync as readFileSync3 } from "fs";
51399
51609
 
51400
51610
  // src/runtime-assets.ts
51401
51611
  import { fileURLToPath } from "url";
@@ -51426,7 +51636,7 @@ function validateConfigOrThrow(config2) {
51426
51636
  validateBusinessRules(config2);
51427
51637
  const ajv = new import__2020.default({ allErrors: true, strict: false });
51428
51638
  import_ajv_formats.default(ajv);
51429
- const schemaJson = JSON.parse(readFileSync2(getBundledSchemaPath(), "utf-8"));
51639
+ const schemaJson = JSON.parse(readFileSync3(getBundledSchemaPath(), "utf-8"));
51430
51640
  const validateBySchema = ajv.compile(schemaJson);
51431
51641
  const valid = validateBySchema(config2);
51432
51642
  if (!valid) {
@@ -51487,404 +51697,13 @@ class CryptoSession {
51487
51697
  }
51488
51698
 
51489
51699
  // src/log-metrics.ts
51490
- import { createReadStream, existsSync as existsSync2 } from "fs";
51491
- import { join as join3 } from "path";
51700
+ import { createReadStream, existsSync as existsSync4 } from "fs";
51701
+ import { join as join5 } from "path";
51492
51702
  import { createInterface } from "readline";
51493
- var WINDOW_MS = {
51494
- "1h": 60 * 60 * 1000,
51495
- "6h": 6 * 60 * 60 * 1000,
51496
- "24h": 24 * 60 * 60 * 1000
51497
- };
51498
- var BUCKET_MS = {
51499
- "1h": 5 * 60 * 1000,
51500
- "6h": 15 * 60 * 1000,
51501
- "24h": 30 * 60 * 1000
51502
- };
51503
- var TOP_LIMIT = 5;
51504
- var MAX_LINES_SCANNED = 250000;
51505
- var CACHE_TTL_MS = 15000;
51506
- var metricsCache = new Map;
51507
- function isLogMetricsWindow(value) {
51508
- return value === "1h" || value === "6h" || value === "24h";
51509
- }
51510
- function toPercent(numerator, denominator) {
51511
- if (denominator <= 0)
51512
- return 0;
51513
- return Number((numerator / denominator * 100).toFixed(2));
51514
- }
51515
- function toDayStart(ms) {
51516
- const date5 = new Date(ms);
51517
- return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
51518
- }
51519
- function listDateStrings(fromMs, toMs) {
51520
- const result = [];
51521
- for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
51522
- result.push(new Date(day).toISOString().slice(0, 10));
51523
- }
51524
- return result;
51525
- }
51526
- function getStatusClass(event) {
51527
- if (event.error_type)
51528
- return "network_error";
51529
- const status = event.upstream_status ?? 0;
51530
- if (status >= 200 && status < 300)
51531
- return "2xx";
51532
- if (status >= 400 && status < 500)
51533
- return "4xx";
51534
- if (status >= 500)
51535
- return "5xx";
51536
- return "network_error";
51537
- }
51538
- function isErrorEvent(event) {
51539
- if (event.error_type)
51540
- return true;
51541
- const status = event.upstream_status ?? 0;
51542
- return status < 200 || status >= 400;
51543
- }
51544
- function percentile(sortedNumbers, ratio) {
51545
- if (sortedNumbers.length === 0)
51546
- return 0;
51547
- const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
51548
- return Math.round(sortedNumbers[index]);
51549
- }
51550
- function createEmptyMetrics(window2, nowMs, source2, warnings = []) {
51551
- const fromMs = nowMs - WINDOW_MS[window2];
51552
- const bucketMs = BUCKET_MS[window2];
51553
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
51554
- const series = Array.from({ length: bucketCount }, (_, i) => ({
51555
- ts: new Date(fromMs + i * bucketMs).toISOString(),
51556
- requests: 0,
51557
- errors: 0,
51558
- avgLatencyMs: 0
51559
- }));
51560
- return {
51561
- window: window2,
51562
- from: new Date(fromMs).toISOString(),
51563
- to: new Date(nowMs).toISOString(),
51564
- generatedAt: new Date(nowMs).toISOString(),
51565
- source: source2,
51566
- summary: {
51567
- totalRequests: 0,
51568
- successRequests: 0,
51569
- errorRequests: 0,
51570
- successRate: 0,
51571
- avgLatencyMs: 0,
51572
- p95LatencyMs: 0,
51573
- totalRequestBytes: 0,
51574
- totalResponseBytes: 0
51575
- },
51576
- series,
51577
- topProviders: [],
51578
- topRouteTypes: [],
51579
- statusClasses: {
51580
- "2xx": 0,
51581
- "4xx": 0,
51582
- "5xx": 0,
51583
- network_error: 0
51584
- },
51585
- warnings
51586
- };
51587
- }
51588
- async function getLogMetrics(options) {
51589
- const window2 = options.window ?? "24h";
51590
- const refresh = options.refresh === true;
51591
- const nowMs = options.nowMs ?? Date.now();
51592
- const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
51593
- if (!logEnabled) {
51594
- return createEmptyMetrics(window2, nowMs, {
51595
- logEnabled: false,
51596
- baseDir: null,
51597
- filesScanned: 0,
51598
- linesScanned: 0,
51599
- partial: false
51600
- }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
51601
- }
51602
- const baseDir = resolveLogBaseDir(options.logConfig);
51603
- const cacheKey = `${baseDir}:${window2}`;
51604
- const cached2 = metricsCache.get(cacheKey);
51605
- if (!refresh && cached2 && cached2.expiresAt > nowMs) {
51606
- return cached2.value;
51607
- }
51608
- const eventsDir = join3(baseDir, "events");
51609
- if (!existsSync2(eventsDir)) {
51610
- const empty = createEmptyMetrics(window2, nowMs, {
51611
- logEnabled: true,
51612
- baseDir,
51613
- filesScanned: 0,
51614
- linesScanned: 0,
51615
- partial: false
51616
- }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
51617
- metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
51618
- return empty;
51619
- }
51620
- const fromMs = nowMs - WINDOW_MS[window2];
51621
- const bucketMs = BUCKET_MS[window2];
51622
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
51623
- const buckets = Array.from({ length: bucketCount }, () => ({
51624
- requests: 0,
51625
- errors: 0,
51626
- latencySum: 0,
51627
- latencyCount: 0
51628
- }));
51629
- const providerAgg = new Map;
51630
- const routeTypeAgg = new Map;
51631
- const latencies = [];
51632
- const statusClasses = {
51633
- "2xx": 0,
51634
- "4xx": 0,
51635
- "5xx": 0,
51636
- network_error: 0
51637
- };
51638
- let filesScanned = 0;
51639
- let linesScanned = 0;
51640
- let parseErrors = 0;
51641
- let partial2 = false;
51642
- let totalRequests = 0;
51643
- let successRequests = 0;
51644
- let errorRequests = 0;
51645
- let totalLatency = 0;
51646
- let totalRequestBytes = 0;
51647
- let totalResponseBytes = 0;
51648
- const warnings = [];
51649
- const dateStrings = listDateStrings(fromMs, nowMs);
51650
- for (const dateStr of dateStrings) {
51651
- if (linesScanned >= MAX_LINES_SCANNED) {
51652
- partial2 = true;
51653
- break;
51654
- }
51655
- const filePath = join3(eventsDir, `${dateStr}.jsonl`);
51656
- if (!existsSync2(filePath))
51657
- continue;
51658
- filesScanned += 1;
51659
- try {
51660
- const stream = createReadStream(filePath, { encoding: "utf-8" });
51661
- const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
51662
- for await (const line2 of rl) {
51663
- if (linesScanned >= MAX_LINES_SCANNED) {
51664
- partial2 = true;
51665
- rl.close();
51666
- stream.destroy();
51667
- break;
51668
- }
51669
- linesScanned += 1;
51670
- if (!line2.trim())
51671
- continue;
51672
- let event;
51673
- try {
51674
- event = JSON.parse(line2);
51675
- } catch {
51676
- parseErrors += 1;
51677
- continue;
51678
- }
51679
- if (!event.ts_start)
51680
- continue;
51681
- const ts = Date.parse(event.ts_start);
51682
- if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
51683
- continue;
51684
- totalRequests += 1;
51685
- const isError = isErrorEvent(event);
51686
- if (isError) {
51687
- errorRequests += 1;
51688
- } else {
51689
- successRequests += 1;
51690
- }
51691
- const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
51692
- totalLatency += latency;
51693
- latencies.push(latency);
51694
- totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
51695
- totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
51696
- const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
51697
- const bucket = buckets[bucketIndex];
51698
- bucket.requests += 1;
51699
- bucket.latencySum += latency;
51700
- bucket.latencyCount += 1;
51701
- if (isError)
51702
- bucket.errors += 1;
51703
- const providerKey = event.provider || "unknown";
51704
- const providerRow = providerAgg.get(providerKey) ?? {
51705
- requests: 0,
51706
- errors: 0,
51707
- latencySum: 0
51708
- };
51709
- providerRow.requests += 1;
51710
- providerRow.latencySum += latency;
51711
- if (isError)
51712
- providerRow.errors += 1;
51713
- providerAgg.set(providerKey, providerRow);
51714
- const routeTypeKey = event.route_type || "unknown";
51715
- const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
51716
- requests: 0,
51717
- errors: 0,
51718
- latencySum: 0
51719
- };
51720
- routeTypeRow.requests += 1;
51721
- routeTypeRow.latencySum += latency;
51722
- if (isError)
51723
- routeTypeRow.errors += 1;
51724
- routeTypeAgg.set(routeTypeKey, routeTypeRow);
51725
- statusClasses[getStatusClass(event)] += 1;
51726
- }
51727
- } catch (err) {
51728
- warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
51729
- partial2 = true;
51730
- }
51731
- }
51732
- if (parseErrors > 0) {
51733
- warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
51734
- }
51735
- if (partial2) {
51736
- warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
51737
- }
51738
- latencies.sort((a, b) => a - b);
51739
- const series = buckets.map((bucket, index) => ({
51740
- ts: new Date(fromMs + index * bucketMs).toISOString(),
51741
- requests: bucket.requests,
51742
- errors: bucket.errors,
51743
- avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
51744
- }));
51745
- const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
51746
- key: key2,
51747
- requests: row.requests,
51748
- errorRate: toPercent(row.errors, row.requests),
51749
- avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
51750
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
51751
- const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
51752
- key: key2,
51753
- requests: row.requests,
51754
- errorRate: toPercent(row.errors, row.requests)
51755
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
51756
- const response = {
51757
- window: window2,
51758
- from: new Date(fromMs).toISOString(),
51759
- to: new Date(nowMs).toISOString(),
51760
- generatedAt: new Date(nowMs).toISOString(),
51761
- source: {
51762
- logEnabled: true,
51763
- baseDir,
51764
- filesScanned,
51765
- linesScanned,
51766
- partial: partial2
51767
- },
51768
- summary: {
51769
- totalRequests,
51770
- successRequests,
51771
- errorRequests,
51772
- successRate: toPercent(successRequests, totalRequests),
51773
- avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
51774
- p95LatencyMs: percentile(latencies, 0.95),
51775
- totalRequestBytes,
51776
- totalResponseBytes
51777
- },
51778
- series,
51779
- topProviders,
51780
- topRouteTypes,
51781
- statusClasses,
51782
- warnings
51783
- };
51784
- metricsCache.set(cacheKey, {
51785
- expiresAt: nowMs + CACHE_TTL_MS,
51786
- value: response
51787
- });
51788
- return response;
51789
- }
51790
-
51791
- // src/log-query.ts
51792
- import { createReadStream as createReadStream3, existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
51793
- import { join as join5, resolve as resolve5 } from "path";
51794
- import { createInterface as createInterface2 } from "readline";
51795
-
51796
- // src/log-index.ts
51797
- import { Database } from "bun:sqlite";
51798
- import {
51799
- closeSync,
51800
- createReadStream as createReadStream2,
51801
- existsSync as existsSync4,
51802
- mkdirSync as mkdirSync2,
51803
- openSync,
51804
- readSync,
51805
- statSync as statSync2
51806
- } from "fs";
51807
- import { join as join4 } from "path";
51808
-
51809
- // src/log-session-identity.ts
51810
- var USER_SESSION_DELIMITER = "_account__session_";
51811
- function toRecord(value) {
51812
- if (!value || typeof value !== "object" || Array.isArray(value))
51813
- return null;
51814
- return value;
51815
- }
51816
- function extractUserIdRawFromRequestBody(requestBody) {
51817
- const requestBodyRecord = toRecord(requestBody);
51818
- const metadata = toRecord(requestBodyRecord?.metadata);
51819
- if (!metadata) {
51820
- return {
51821
- hasMetadata: false,
51822
- userIdRaw: null
51823
- };
51824
- }
51825
- const userId = metadata.user_id;
51826
- if (typeof userId !== "string" || userId.trim() === "") {
51827
- return {
51828
- hasMetadata: true,
51829
- userIdRaw: null
51830
- };
51831
- }
51832
- return {
51833
- hasMetadata: true,
51834
- userIdRaw: userId
51835
- };
51836
- }
51837
- function parseUserSessionFromJsonFormat(userIdRaw) {
51838
- let parsed;
51839
- try {
51840
- parsed = JSON.parse(userIdRaw);
51841
- } catch {
51842
- return null;
51843
- }
51844
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
51845
- return null;
51846
- const obj = parsed;
51847
- const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
51848
- if (!sessionId)
51849
- return null;
51850
- const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
51851
- return { userKey: userKey || sessionId, sessionId };
51852
- }
51853
- function parseUserSessionFromUserIdRaw(userIdRaw) {
51854
- if (userIdRaw.trimStart().startsWith("{")) {
51855
- return parseUserSessionFromJsonFormat(userIdRaw);
51856
- }
51857
- const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
51858
- if (index <= 0)
51859
- return null;
51860
- const userKey = userIdRaw.slice(0, index).trim();
51861
- const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
51862
- if (!userKey || !sessionId)
51863
- return null;
51864
- return { userKey, sessionId };
51865
- }
51866
- function resolveLogSessionIdentity(requestBody) {
51867
- const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
51868
- if (!userIdRaw) {
51869
- return {
51870
- hasMetadata,
51871
- userIdRaw: null,
51872
- userKey: null,
51873
- sessionId: null
51874
- };
51875
- }
51876
- const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
51877
- return {
51878
- hasMetadata,
51879
- userIdRaw,
51880
- userKey: parsed?.userKey ?? null,
51881
- sessionId: parsed?.sessionId ?? null
51882
- };
51883
- }
51884
51703
 
51885
51704
  // src/token-usage.ts
51886
- import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
51887
- import { resolve as resolve4 } from "path";
51705
+ import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
51706
+ import { resolve as resolve5 } from "path";
51888
51707
  var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
51889
51708
  function asRecord(value) {
51890
51709
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -51977,7 +51796,7 @@ function inferProviderStyle(usage, providerHint) {
51977
51796
  }
51978
51797
  return "unknown";
51979
51798
  }
51980
- function createEmptyMetrics2(input) {
51799
+ function createEmptyMetrics(input) {
51981
51800
  return {
51982
51801
  schemaVersion: 1,
51983
51802
  source: input.source,
@@ -52033,7 +51852,7 @@ function hasAnyTokenSignal(metrics) {
52033
51852
  function normalizeUsageObject(input) {
52034
51853
  const { usage, source: source2, rawUsagePath, providerHint } = input;
52035
51854
  const providerStyle = inferProviderStyle(usage, providerHint);
52036
- const metrics = createEmptyMetrics2({
51855
+ const metrics = createEmptyMetrics({
52037
51856
  source: source2,
52038
51857
  providerStyle,
52039
51858
  rawUsage: usage,
@@ -52401,9 +52220,9 @@ function safeReadStreamFile(streamFile, baseDir) {
52401
52220
  try {
52402
52221
  const candidates = [streamFile];
52403
52222
  if (baseDir)
52404
- candidates.push(resolve4(baseDir, streamFile));
52223
+ candidates.push(resolve5(baseDir, streamFile));
52405
52224
  for (const candidate of candidates) {
52406
- const resolved = resolve4(candidate);
52225
+ const resolved = resolve5(candidate);
52407
52226
  if (!resolved.endsWith(".sse.raw"))
52408
52227
  continue;
52409
52228
  if (!existsSync3(resolved))
@@ -52415,7 +52234,7 @@ function safeReadStreamFile(streamFile, baseDir) {
52415
52234
  warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
52416
52235
  };
52417
52236
  }
52418
- return { content: readFileSync3(resolved, "utf-8"), warning: null };
52237
+ return { content: readFileSync4(resolved, "utf-8"), warning: null };
52419
52238
  }
52420
52239
  } catch (err) {
52421
52240
  return {
@@ -52462,6 +52281,448 @@ function enrichLogEventTokenUsage(event, options = {}) {
52462
52281
  };
52463
52282
  }
52464
52283
 
52284
+ // src/log-metrics.ts
52285
+ var WINDOW_MS = {
52286
+ "1h": 60 * 60 * 1000,
52287
+ "6h": 6 * 60 * 60 * 1000,
52288
+ "24h": 24 * 60 * 60 * 1000
52289
+ };
52290
+ var BUCKET_MS = {
52291
+ "1h": 5 * 60 * 1000,
52292
+ "6h": 15 * 60 * 1000,
52293
+ "24h": 30 * 60 * 1000
52294
+ };
52295
+ var TOP_LIMIT = 5;
52296
+ var MAX_LINES_SCANNED = 250000;
52297
+ var CACHE_TTL_MS = 15000;
52298
+ var metricsCache = new Map;
52299
+ function isLogMetricsWindow(value) {
52300
+ return value === "1h" || value === "6h" || value === "24h";
52301
+ }
52302
+ function toPercent(numerator, denominator) {
52303
+ if (denominator <= 0)
52304
+ return 0;
52305
+ return Number((numerator / denominator * 100).toFixed(2));
52306
+ }
52307
+ function toDayStart(ms) {
52308
+ const date5 = new Date(ms);
52309
+ return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
52310
+ }
52311
+ function listDateStrings(fromMs, toMs) {
52312
+ const result = [];
52313
+ for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
52314
+ result.push(new Date(day).toISOString().slice(0, 10));
52315
+ }
52316
+ return result;
52317
+ }
52318
+ function getStatusClass(event) {
52319
+ if (event.error_type)
52320
+ return "network_error";
52321
+ const status = event.upstream_status ?? 0;
52322
+ if (status >= 200 && status < 300)
52323
+ return "2xx";
52324
+ if (status >= 400 && status < 500)
52325
+ return "4xx";
52326
+ if (status >= 500)
52327
+ return "5xx";
52328
+ return "network_error";
52329
+ }
52330
+ function isErrorEvent(event) {
52331
+ if (event.error_type)
52332
+ return true;
52333
+ const status = event.upstream_status ?? 0;
52334
+ return status < 200 || status >= 400;
52335
+ }
52336
+ function percentile(sortedNumbers, ratio) {
52337
+ if (sortedNumbers.length === 0)
52338
+ return 0;
52339
+ const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
52340
+ return Math.round(sortedNumbers[index]);
52341
+ }
52342
+ function createEmptyMetrics2(window2, nowMs, source2, warnings = []) {
52343
+ const fromMs = nowMs - WINDOW_MS[window2];
52344
+ const bucketMs = BUCKET_MS[window2];
52345
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
52346
+ const series = Array.from({ length: bucketCount }, (_, i) => ({
52347
+ ts: new Date(fromMs + i * bucketMs).toISOString(),
52348
+ requests: 0,
52349
+ errors: 0,
52350
+ avgLatencyMs: 0
52351
+ }));
52352
+ return {
52353
+ window: window2,
52354
+ from: new Date(fromMs).toISOString(),
52355
+ to: new Date(nowMs).toISOString(),
52356
+ generatedAt: new Date(nowMs).toISOString(),
52357
+ source: source2,
52358
+ summary: {
52359
+ totalRequests: 0,
52360
+ successRequests: 0,
52361
+ errorRequests: 0,
52362
+ successRate: 0,
52363
+ avgLatencyMs: 0,
52364
+ p95LatencyMs: 0,
52365
+ totalRequestBytes: 0,
52366
+ totalResponseBytes: 0
52367
+ },
52368
+ tokens: {
52369
+ usageCount: 0,
52370
+ inputTokens: 0,
52371
+ outputTokens: 0,
52372
+ totalTokens: 0,
52373
+ cachedInputTokens: 0,
52374
+ cacheHitInputTokens: 0,
52375
+ cacheHitRateDenominatorTokens: 0,
52376
+ cacheHitRate: 0,
52377
+ reasoningTokens: 0,
52378
+ cost: null
52379
+ },
52380
+ series,
52381
+ topProviders: [],
52382
+ topRouteTypes: [],
52383
+ statusClasses: {
52384
+ "2xx": 0,
52385
+ "4xx": 0,
52386
+ "5xx": 0,
52387
+ network_error: 0
52388
+ },
52389
+ warnings
52390
+ };
52391
+ }
52392
+ async function getLogMetrics(options) {
52393
+ const window2 = options.window ?? "24h";
52394
+ const refresh = options.refresh === true;
52395
+ const nowMs = options.nowMs ?? Date.now();
52396
+ const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
52397
+ if (!logEnabled) {
52398
+ return createEmptyMetrics2(window2, nowMs, {
52399
+ logEnabled: false,
52400
+ baseDir: null,
52401
+ filesScanned: 0,
52402
+ linesScanned: 0,
52403
+ partial: false
52404
+ }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
52405
+ }
52406
+ const baseDir = resolveLogBaseDir(options.logConfig);
52407
+ const cacheKey = `${baseDir}:${window2}`;
52408
+ const cached2 = metricsCache.get(cacheKey);
52409
+ if (!refresh && cached2 && cached2.expiresAt > nowMs) {
52410
+ return cached2.value;
52411
+ }
52412
+ const eventsDir = join5(baseDir, "events");
52413
+ if (!existsSync4(eventsDir)) {
52414
+ const empty = createEmptyMetrics2(window2, nowMs, {
52415
+ logEnabled: true,
52416
+ baseDir,
52417
+ filesScanned: 0,
52418
+ linesScanned: 0,
52419
+ partial: false
52420
+ }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
52421
+ metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
52422
+ return empty;
52423
+ }
52424
+ const fromMs = nowMs - WINDOW_MS[window2];
52425
+ const bucketMs = BUCKET_MS[window2];
52426
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
52427
+ const buckets = Array.from({ length: bucketCount }, () => ({
52428
+ requests: 0,
52429
+ errors: 0,
52430
+ latencySum: 0,
52431
+ latencyCount: 0
52432
+ }));
52433
+ const providerAgg = new Map;
52434
+ const routeTypeAgg = new Map;
52435
+ const latencies = [];
52436
+ const statusClasses = {
52437
+ "2xx": 0,
52438
+ "4xx": 0,
52439
+ "5xx": 0,
52440
+ network_error: 0
52441
+ };
52442
+ let filesScanned = 0;
52443
+ let linesScanned = 0;
52444
+ let parseErrors = 0;
52445
+ let partial2 = false;
52446
+ let totalRequests = 0;
52447
+ let successRequests = 0;
52448
+ let errorRequests = 0;
52449
+ let totalLatency = 0;
52450
+ let totalRequestBytes = 0;
52451
+ let totalResponseBytes = 0;
52452
+ let tokenUsageCount = 0;
52453
+ let tokenInput = 0;
52454
+ let tokenOutput = 0;
52455
+ let tokenTotal = 0;
52456
+ let tokenCachedInput = 0;
52457
+ let tokenCacheHitInput = 0;
52458
+ let tokenCacheHitDenominator = 0;
52459
+ let tokenReasoning = 0;
52460
+ let tokenCost = 0;
52461
+ let tokenCostSeen = false;
52462
+ const warnings = [];
52463
+ const dateStrings = listDateStrings(fromMs, nowMs);
52464
+ for (const dateStr of dateStrings) {
52465
+ if (linesScanned >= MAX_LINES_SCANNED) {
52466
+ partial2 = true;
52467
+ break;
52468
+ }
52469
+ const filePath = join5(eventsDir, `${dateStr}.jsonl`);
52470
+ if (!existsSync4(filePath))
52471
+ continue;
52472
+ filesScanned += 1;
52473
+ try {
52474
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
52475
+ const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
52476
+ for await (const line2 of rl) {
52477
+ if (linesScanned >= MAX_LINES_SCANNED) {
52478
+ partial2 = true;
52479
+ rl.close();
52480
+ stream.destroy();
52481
+ break;
52482
+ }
52483
+ linesScanned += 1;
52484
+ if (!line2.trim())
52485
+ continue;
52486
+ let event;
52487
+ try {
52488
+ event = JSON.parse(line2);
52489
+ } catch {
52490
+ parseErrors += 1;
52491
+ continue;
52492
+ }
52493
+ if (!event.ts_start)
52494
+ continue;
52495
+ const ts = Date.parse(event.ts_start);
52496
+ if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
52497
+ continue;
52498
+ totalRequests += 1;
52499
+ const isError = isErrorEvent(event);
52500
+ if (isError) {
52501
+ errorRequests += 1;
52502
+ } else {
52503
+ successRequests += 1;
52504
+ }
52505
+ const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
52506
+ totalLatency += latency;
52507
+ latencies.push(latency);
52508
+ totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
52509
+ totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
52510
+ const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
52511
+ const bucket = buckets[bucketIndex];
52512
+ bucket.requests += 1;
52513
+ bucket.latencySum += latency;
52514
+ bucket.latencyCount += 1;
52515
+ if (isError)
52516
+ bucket.errors += 1;
52517
+ const providerKey = event.provider || "unknown";
52518
+ const providerRow = providerAgg.get(providerKey) ?? {
52519
+ requests: 0,
52520
+ errors: 0,
52521
+ latencySum: 0
52522
+ };
52523
+ providerRow.requests += 1;
52524
+ providerRow.latencySum += latency;
52525
+ if (isError)
52526
+ providerRow.errors += 1;
52527
+ providerAgg.set(providerKey, providerRow);
52528
+ const routeTypeKey = event.route_type || "unknown";
52529
+ const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
52530
+ requests: 0,
52531
+ errors: 0,
52532
+ latencySum: 0
52533
+ };
52534
+ routeTypeRow.requests += 1;
52535
+ routeTypeRow.latencySum += latency;
52536
+ if (isError)
52537
+ routeTypeRow.errors += 1;
52538
+ routeTypeAgg.set(routeTypeKey, routeTypeRow);
52539
+ statusClasses[getStatusClass(event)] += 1;
52540
+ const usage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
52541
+ if (usage) {
52542
+ tokenUsageCount += 1;
52543
+ tokenInput += Math.max(0, usage.inputTokens ?? 0);
52544
+ tokenOutput += Math.max(0, usage.outputTokens ?? 0);
52545
+ tokenTotal += Math.max(0, usage.totalTokens ?? 0);
52546
+ tokenCachedInput += Math.max(0, usage.cachedInputTokens ?? 0);
52547
+ tokenCacheHitInput += Math.max(0, usage.cacheHitInputTokens ?? 0);
52548
+ tokenCacheHitDenominator += Math.max(0, usage.cacheHitRateDenominatorTokens ?? 0);
52549
+ tokenReasoning += Math.max(0, usage.reasoningTokens ?? 0);
52550
+ if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
52551
+ tokenCost += usage.cost;
52552
+ tokenCostSeen = true;
52553
+ }
52554
+ }
52555
+ }
52556
+ } catch (err) {
52557
+ warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
52558
+ partial2 = true;
52559
+ }
52560
+ }
52561
+ if (parseErrors > 0) {
52562
+ warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
52563
+ }
52564
+ if (partial2) {
52565
+ warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
52566
+ }
52567
+ latencies.sort((a, b) => a - b);
52568
+ const series = buckets.map((bucket, index) => ({
52569
+ ts: new Date(fromMs + index * bucketMs).toISOString(),
52570
+ requests: bucket.requests,
52571
+ errors: bucket.errors,
52572
+ avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
52573
+ }));
52574
+ const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
52575
+ key: key2,
52576
+ requests: row.requests,
52577
+ errorRate: toPercent(row.errors, row.requests),
52578
+ avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
52579
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
52580
+ const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
52581
+ key: key2,
52582
+ requests: row.requests,
52583
+ errorRate: toPercent(row.errors, row.requests)
52584
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
52585
+ const response = {
52586
+ window: window2,
52587
+ from: new Date(fromMs).toISOString(),
52588
+ to: new Date(nowMs).toISOString(),
52589
+ generatedAt: new Date(nowMs).toISOString(),
52590
+ source: {
52591
+ logEnabled: true,
52592
+ baseDir,
52593
+ filesScanned,
52594
+ linesScanned,
52595
+ partial: partial2
52596
+ },
52597
+ summary: {
52598
+ totalRequests,
52599
+ successRequests,
52600
+ errorRequests,
52601
+ successRate: toPercent(successRequests, totalRequests),
52602
+ avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
52603
+ p95LatencyMs: percentile(latencies, 0.95),
52604
+ totalRequestBytes,
52605
+ totalResponseBytes
52606
+ },
52607
+ tokens: {
52608
+ usageCount: tokenUsageCount,
52609
+ inputTokens: tokenInput,
52610
+ outputTokens: tokenOutput,
52611
+ totalTokens: tokenTotal,
52612
+ cachedInputTokens: tokenCachedInput,
52613
+ cacheHitInputTokens: tokenCacheHitInput,
52614
+ cacheHitRateDenominatorTokens: tokenCacheHitDenominator,
52615
+ cacheHitRate: toPercent(tokenCacheHitInput, tokenCacheHitDenominator),
52616
+ reasoningTokens: tokenReasoning,
52617
+ cost: tokenCostSeen ? Number(tokenCost.toFixed(6)) : null
52618
+ },
52619
+ series,
52620
+ topProviders,
52621
+ topRouteTypes,
52622
+ statusClasses,
52623
+ warnings
52624
+ };
52625
+ metricsCache.set(cacheKey, {
52626
+ expiresAt: nowMs + CACHE_TTL_MS,
52627
+ value: response
52628
+ });
52629
+ return response;
52630
+ }
52631
+
52632
+ // src/log-query.ts
52633
+ import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
52634
+ import { join as join7, resolve as resolve6 } from "path";
52635
+ import { createInterface as createInterface2 } from "readline";
52636
+
52637
+ // src/log-index.ts
52638
+ import { Database } from "bun:sqlite";
52639
+ import {
52640
+ closeSync,
52641
+ createReadStream as createReadStream2,
52642
+ existsSync as existsSync5,
52643
+ mkdirSync as mkdirSync3,
52644
+ openSync,
52645
+ readSync,
52646
+ statSync as statSync2
52647
+ } from "fs";
52648
+ import { join as join6 } from "path";
52649
+
52650
+ // src/log-session-identity.ts
52651
+ var USER_SESSION_DELIMITER = "_account__session_";
52652
+ function toRecord(value) {
52653
+ if (!value || typeof value !== "object" || Array.isArray(value))
52654
+ return null;
52655
+ return value;
52656
+ }
52657
+ function extractUserIdRawFromRequestBody(requestBody) {
52658
+ const requestBodyRecord = toRecord(requestBody);
52659
+ const metadata = toRecord(requestBodyRecord?.metadata);
52660
+ if (!metadata) {
52661
+ return {
52662
+ hasMetadata: false,
52663
+ userIdRaw: null
52664
+ };
52665
+ }
52666
+ const userId = metadata.user_id;
52667
+ if (typeof userId !== "string" || userId.trim() === "") {
52668
+ return {
52669
+ hasMetadata: true,
52670
+ userIdRaw: null
52671
+ };
52672
+ }
52673
+ return {
52674
+ hasMetadata: true,
52675
+ userIdRaw: userId
52676
+ };
52677
+ }
52678
+ function parseUserSessionFromJsonFormat(userIdRaw) {
52679
+ let parsed;
52680
+ try {
52681
+ parsed = JSON.parse(userIdRaw);
52682
+ } catch {
52683
+ return null;
52684
+ }
52685
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
52686
+ return null;
52687
+ const obj = parsed;
52688
+ const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
52689
+ if (!sessionId)
52690
+ return null;
52691
+ const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
52692
+ return { userKey: userKey || sessionId, sessionId };
52693
+ }
52694
+ function parseUserSessionFromUserIdRaw(userIdRaw) {
52695
+ if (userIdRaw.trimStart().startsWith("{")) {
52696
+ return parseUserSessionFromJsonFormat(userIdRaw);
52697
+ }
52698
+ const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
52699
+ if (index <= 0)
52700
+ return null;
52701
+ const userKey = userIdRaw.slice(0, index).trim();
52702
+ const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
52703
+ if (!userKey || !sessionId)
52704
+ return null;
52705
+ return { userKey, sessionId };
52706
+ }
52707
+ function resolveLogSessionIdentity(requestBody) {
52708
+ const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
52709
+ if (!userIdRaw) {
52710
+ return {
52711
+ hasMetadata,
52712
+ userIdRaw: null,
52713
+ userKey: null,
52714
+ sessionId: null
52715
+ };
52716
+ }
52717
+ const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
52718
+ return {
52719
+ hasMetadata,
52720
+ userIdRaw,
52721
+ userKey: parsed?.userKey ?? null,
52722
+ sessionId: parsed?.sessionId ?? null
52723
+ };
52724
+ }
52725
+
52465
52726
  // src/log-index.ts
52466
52727
  var SCHEMA_VERSION = 3;
52467
52728
  var MAX_INDEX_QUEUE = 20000;
@@ -52849,6 +53110,31 @@ function buildWhereClause(query, options = {}) {
52849
53110
  usesFts
52850
53111
  };
52851
53112
  }
53113
+ function buildSessionsWhereClause(query) {
53114
+ const pseudo = {
53115
+ fromMs: query.fromMs,
53116
+ toMs: query.toMs,
53117
+ levels: [],
53118
+ providers: [],
53119
+ routeTypes: [],
53120
+ models: [],
53121
+ modelIns: [],
53122
+ modelOuts: [],
53123
+ users: query.users,
53124
+ sessions: query.sessions,
53125
+ statusClasses: [],
53126
+ hasError: null,
53127
+ q: query.q,
53128
+ sort: "time_desc",
53129
+ limit: 1,
53130
+ cursor: null
53131
+ };
53132
+ const { whereSql, params } = buildWhereClause(pseudo);
53133
+ return { whereSql, params };
53134
+ }
53135
+ function sortIndexedCountItems(map2) {
53136
+ return Array.from(map2.entries()).map(([key2, count]) => ({ key: key2, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
53137
+ }
52852
53138
 
52853
53139
  class LogIndex {
52854
53140
  baseDir;
@@ -52866,8 +53152,8 @@ class LogIndex {
52866
53152
  constructor(baseDir, config2) {
52867
53153
  this.baseDir = baseDir;
52868
53154
  this.config = config2;
52869
- mkdirSync2(baseDir, { recursive: true });
52870
- const dbPath = join4(baseDir, "logs-index.sqlite");
53155
+ mkdirSync3(baseDir, { recursive: true });
53156
+ const dbPath = join6(baseDir, "logs-index.sqlite");
52871
53157
  this.db = new Database(dbPath, { create: true, strict: true });
52872
53158
  this.configure();
52873
53159
  this.migrate();
@@ -52943,11 +53229,11 @@ class LogIndex {
52943
53229
  let scannedFiles = 0;
52944
53230
  let scannedLines = 0;
52945
53231
  let parseErrors = 0;
52946
- const eventsDir = join4(this.baseDir, "events");
53232
+ const eventsDir = join6(this.baseDir, "events");
52947
53233
  const dates = listDateStrings2(fromMs, toMs);
52948
53234
  for (const date5 of dates) {
52949
- const filePath = join4(eventsDir, `${date5}.jsonl`);
52950
- if (!existsSync4(filePath))
53235
+ const filePath = join6(eventsDir, `${date5}.jsonl`);
53236
+ if (!existsSync5(filePath))
52951
53237
  continue;
52952
53238
  const stats = statSync2(filePath);
52953
53239
  const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
@@ -53038,8 +53324,8 @@ class LogIndex {
53038
53324
  if (!parsedId)
53039
53325
  return null;
53040
53326
  const row = this.db.query("SELECT source_date, source_file, line_number, byte_offset FROM log_events WHERE id = ?").get(id);
53041
- const filePath = row?.source_file ?? join4(this.baseDir, "events", `${parsedId.date}.jsonl`);
53042
- if (!existsSync4(filePath))
53327
+ const filePath = row?.source_file ?? join6(this.baseDir, "events", `${parsedId.date}.jsonl`);
53328
+ if (!existsSync5(filePath))
53043
53329
  return null;
53044
53330
  const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
53045
53331
  if (!line2?.trim())
@@ -53056,6 +53342,163 @@ class LogIndex {
53056
53342
  }
53057
53343
  };
53058
53344
  }
53345
+ querySessions(query) {
53346
+ const startedAt = performance.now();
53347
+ const { whereSql, params } = buildSessionsWhereClause(query);
53348
+ const aggregatedWhere = `${whereSql} AND e.user_key IS NOT NULL AND e.session_id IS NOT NULL`;
53349
+ const summaryRow = this.db.query(`
53350
+ SELECT
53351
+ COUNT(*) AS totalRequests,
53352
+ COALESCE(SUM(has_metadata), 0) AS metadataRequests,
53353
+ COUNT(DISTINCT user_key) AS uniqueUsers,
53354
+ COUNT(DISTINCT CASE
53355
+ WHEN user_key IS NOT NULL AND session_id IS NOT NULL
53356
+ THEN user_key || ' ' || session_id
53357
+ END) AS uniqueSessions
53358
+ FROM log_events e
53359
+ ${whereSql}
53360
+ `).get(...params);
53361
+ const userRows = this.db.query(`
53362
+ SELECT
53363
+ user_key AS userKey,
53364
+ COUNT(*) AS requestCount,
53365
+ MIN(ts_ms) AS firstMs,
53366
+ MAX(ts_ms) AS lastMs,
53367
+ COUNT(DISTINCT session_id) AS sessionCount
53368
+ FROM log_events e
53369
+ ${aggregatedWhere}
53370
+ GROUP BY user_key
53371
+ `).all(...params);
53372
+ const sessionRows = this.db.query(`
53373
+ SELECT
53374
+ user_key AS userKey,
53375
+ session_id AS sessionId,
53376
+ COUNT(*) AS requestCount,
53377
+ MIN(ts_ms) AS firstMs,
53378
+ MAX(ts_ms) AS lastMs
53379
+ FROM log_events e
53380
+ ${aggregatedWhere}
53381
+ GROUP BY user_key, session_id
53382
+ `).all(...params);
53383
+ const userModelRows = this.db.query(`
53384
+ SELECT user_key AS userKey, model AS key, COUNT(*) AS count
53385
+ FROM log_events e
53386
+ ${aggregatedWhere}
53387
+ GROUP BY user_key, model
53388
+ `).all(...params);
53389
+ const userProviderRows = this.db.query(`
53390
+ SELECT user_key AS userKey, provider AS key, COUNT(*) AS count
53391
+ FROM log_events e
53392
+ ${aggregatedWhere}
53393
+ GROUP BY user_key, provider
53394
+ `).all(...params);
53395
+ const userRouteRows = this.db.query(`
53396
+ SELECT user_key AS userKey, route_type AS key, COUNT(*) AS count
53397
+ FROM log_events e
53398
+ ${aggregatedWhere}
53399
+ GROUP BY user_key, route_type
53400
+ `).all(...params);
53401
+ const sessionModelRows = this.db.query(`
53402
+ SELECT user_key AS userKey, session_id AS sessionId, model AS key, COUNT(*) AS count
53403
+ FROM log_events e
53404
+ ${aggregatedWhere}
53405
+ GROUP BY user_key, session_id, model
53406
+ `).all(...params);
53407
+ const latestRows = this.db.query(`
53408
+ SELECT userKey, sessionId, request_id AS latestRequestId
53409
+ FROM (
53410
+ SELECT
53411
+ user_key AS userKey,
53412
+ session_id AS sessionId,
53413
+ request_id,
53414
+ ROW_NUMBER() OVER (
53415
+ PARTITION BY user_key, session_id ORDER BY ts_ms DESC, id DESC
53416
+ ) AS rn
53417
+ FROM log_events e
53418
+ ${aggregatedWhere}
53419
+ )
53420
+ WHERE rn = 1
53421
+ `).all(...params);
53422
+ const userModels = new Map;
53423
+ const userProviders = new Map;
53424
+ const userRoutes = new Map;
53425
+ const sessionModels = new Map;
53426
+ const latestBySession = new Map;
53427
+ const addCount = (target, groupKey, key2, count) => {
53428
+ if (!key2)
53429
+ return;
53430
+ let inner = target.get(groupKey);
53431
+ if (!inner) {
53432
+ inner = new Map;
53433
+ target.set(groupKey, inner);
53434
+ }
53435
+ inner.set(key2, count);
53436
+ };
53437
+ for (const row of userModelRows)
53438
+ addCount(userModels, row.userKey, row.key, row.count);
53439
+ for (const row of userProviderRows)
53440
+ addCount(userProviders, row.userKey, row.key, row.count);
53441
+ for (const row of userRouteRows)
53442
+ addCount(userRoutes, row.userKey, row.key, row.count);
53443
+ for (const row of sessionModelRows) {
53444
+ addCount(sessionModels, `${row.userKey}\x00${row.sessionId}`, row.key, row.count);
53445
+ }
53446
+ for (const row of latestRows) {
53447
+ latestBySession.set(`${row.userKey}\x00${row.sessionId}`, row.latestRequestId);
53448
+ }
53449
+ const sessionsByUser = new Map;
53450
+ for (const row of sessionRows) {
53451
+ const sessionKey = `${row.userKey}\x00${row.sessionId}`;
53452
+ const session = {
53453
+ sessionId: row.sessionId,
53454
+ requestCount: row.requestCount,
53455
+ firstSeenAt: new Date(row.firstMs).toISOString(),
53456
+ lastSeenAt: new Date(row.lastMs).toISOString(),
53457
+ models: sortIndexedCountItems(sessionModels.get(sessionKey) ?? new Map),
53458
+ latestRequestId: latestBySession.get(sessionKey) ?? ""
53459
+ };
53460
+ const list = sessionsByUser.get(row.userKey);
53461
+ if (list) {
53462
+ list.push(session);
53463
+ } else {
53464
+ sessionsByUser.set(row.userKey, [session]);
53465
+ }
53466
+ }
53467
+ const users = userRows.map((row) => {
53468
+ const sessions = (sessionsByUser.get(row.userKey) ?? []).sort((a, b) => {
53469
+ if (a.requestCount !== b.requestCount)
53470
+ return b.requestCount - a.requestCount;
53471
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
53472
+ });
53473
+ return {
53474
+ userKey: row.userKey,
53475
+ requestCount: row.requestCount,
53476
+ sessionCount: row.sessionCount,
53477
+ firstSeenAt: new Date(row.firstMs).toISOString(),
53478
+ lastSeenAt: new Date(row.lastMs).toISOString(),
53479
+ models: sortIndexedCountItems(userModels.get(row.userKey) ?? new Map),
53480
+ providers: sortIndexedCountItems(userProviders.get(row.userKey) ?? new Map),
53481
+ routeTypes: sortIndexedCountItems(userRoutes.get(row.userKey) ?? new Map),
53482
+ sessions
53483
+ };
53484
+ }).sort((a, b) => {
53485
+ if (a.requestCount !== b.requestCount)
53486
+ return b.requestCount - a.requestCount;
53487
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
53488
+ });
53489
+ return {
53490
+ from: new Date(query.fromMs).toISOString(),
53491
+ to: new Date(query.toMs).toISOString(),
53492
+ summary: {
53493
+ totalRequests: Number(summaryRow.totalRequests) || 0,
53494
+ metadataRequests: Number(summaryRow.metadataRequests) || 0,
53495
+ uniqueUsers: Number(summaryRow.uniqueUsers) || 0,
53496
+ uniqueSessions: Number(summaryRow.uniqueSessions) || 0
53497
+ },
53498
+ users,
53499
+ queryMs: Math.round((performance.now() - startedAt) * 100) / 100
53500
+ };
53501
+ }
53059
53502
  configure() {
53060
53503
  this.db.exec(`
53061
53504
  PRAGMA journal_mode = WAL;
@@ -53438,6 +53881,65 @@ async function queryIndexedLogEvents(logConfig, query) {
53438
53881
  };
53439
53882
  }
53440
53883
  }
53884
+ async function queryIndexedLogSessions(logConfig, query) {
53885
+ if (!logConfig || logConfig.enabled === false) {
53886
+ return {
53887
+ from: new Date(query.fromMs).toISOString(),
53888
+ to: new Date(query.toMs).toISOString(),
53889
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
53890
+ users: [],
53891
+ meta: {
53892
+ scannedFiles: 0,
53893
+ scannedLines: 0,
53894
+ parseErrors: 0,
53895
+ truncated: false,
53896
+ indexUsed: true,
53897
+ indexFresh: true,
53898
+ queryMs: 0
53899
+ }
53900
+ };
53901
+ }
53902
+ const baseDir = resolveLogBaseDir(logConfig);
53903
+ const index = getLogIndex(baseDir);
53904
+ if (!index)
53905
+ return null;
53906
+ try {
53907
+ const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
53908
+ const result = index.querySessions(query);
53909
+ return {
53910
+ from: result.from,
53911
+ to: result.to,
53912
+ summary: result.summary,
53913
+ users: result.users,
53914
+ meta: {
53915
+ scannedFiles: freshness.scannedFiles,
53916
+ scannedLines: freshness.scannedLines,
53917
+ parseErrors: freshness.parseErrors,
53918
+ truncated: false,
53919
+ indexUsed: true,
53920
+ indexFresh: true,
53921
+ queryMs: result.queryMs
53922
+ }
53923
+ };
53924
+ } catch (err) {
53925
+ return {
53926
+ from: new Date(query.fromMs).toISOString(),
53927
+ to: new Date(query.toMs).toISOString(),
53928
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
53929
+ users: [],
53930
+ meta: {
53931
+ scannedFiles: 0,
53932
+ scannedLines: 0,
53933
+ parseErrors: 0,
53934
+ truncated: false,
53935
+ indexUsed: false,
53936
+ indexFresh: false,
53937
+ queryMs: 0,
53938
+ fallbackReason: err instanceof Error ? err.message : String(err)
53939
+ }
53940
+ };
53941
+ }
53942
+ }
53441
53943
  function getIndexedLogEventDetail(logConfig, id) {
53442
53944
  if (!logConfig || logConfig.enabled === false)
53443
53945
  return null;
@@ -53448,8 +53950,8 @@ function getIndexedLogEventDetail(logConfig, id) {
53448
53950
  const parsedId = decodeOffsetLogEventId(id);
53449
53951
  if (!parsedId)
53450
53952
  return null;
53451
- const filePath = join4(baseDir, "events", `${parsedId.date}.jsonl`);
53452
- if (!existsSync4(filePath))
53953
+ const filePath = join6(baseDir, "events", `${parsedId.date}.jsonl`);
53954
+ if (!existsSync5(filePath))
53453
53955
  return null;
53454
53956
  const line2 = readLineAtOffset(filePath, parsedId.offset);
53455
53957
  if (!line2?.trim())
@@ -53470,7 +53972,10 @@ function getIndexedLogEventDetail(logConfig, id) {
53470
53972
  var WINDOW_MS2 = {
53471
53973
  "1h": 60 * 60 * 1000,
53472
53974
  "6h": 6 * 60 * 60 * 1000,
53473
- "24h": 24 * 60 * 60 * 1000
53975
+ "24h": 24 * 60 * 60 * 1000,
53976
+ "7d": 7 * 24 * 60 * 60 * 1000,
53977
+ "1mo": 30 * 24 * 60 * 60 * 1000,
53978
+ "1y": 365 * 24 * 60 * 60 * 1000
53474
53979
  };
53475
53980
  var MAX_LINES_SCANNED2 = 250000;
53476
53981
  var MAX_QUERY_LIMIT = 200;
@@ -53556,12 +54061,8 @@ function buildMessage2(event) {
53556
54061
  const status = event.upstream_status ?? 0;
53557
54062
  return `${event.method} ${event.path} -> ${status}`;
53558
54063
  }
53559
- function containsKeyword(event, q) {
53560
- if (!q)
53561
- return true;
53562
- const identity = resolveLogSessionIdentity(event.request_body);
53563
- const keyword = q.toLowerCase();
53564
- const haystack = [
54064
+ function buildKeywordText(event, identity) {
54065
+ return [
53565
54066
  event.request_id,
53566
54067
  event.path,
53567
54068
  event.provider,
@@ -53575,7 +54076,13 @@ function containsKeyword(event, q) {
53575
54076
  event.error_message ?? "",
53576
54077
  buildMessage2(event)
53577
54078
  ].join(" ").toLowerCase();
53578
- return haystack.includes(keyword);
54079
+ }
54080
+ function containsKeyword(event, q) {
54081
+ if (!q)
54082
+ return true;
54083
+ const identity = resolveLogSessionIdentity(event.request_body);
54084
+ const keyword = q.toLowerCase();
54085
+ return buildKeywordText(event, identity).includes(keyword);
53579
54086
  }
53580
54087
  function createRunningStats() {
53581
54088
  return {
@@ -53743,7 +54250,7 @@ function normalizeQuery(input, maxLimit = MAX_QUERY_LIMIT) {
53743
54250
  }
53744
54251
  function eventToSummary(item) {
53745
54252
  const { event } = item;
53746
- const identity = resolveLogSessionIdentity(event.request_body);
54253
+ const identity = item.identity ?? resolveLogSessionIdentity(event.request_body);
53747
54254
  return {
53748
54255
  id: item.id,
53749
54256
  ts: event.ts_start,
@@ -53768,19 +54275,39 @@ function eventToSummary(item) {
53768
54275
  tokenUsage: item.tokenUsage
53769
54276
  };
53770
54277
  }
53771
- function createLogEventSummaryFromEvent(event, location) {
54278
+ function extractLogEventFacts(event) {
53772
54279
  const ts = Date.parse(event.ts_start);
54280
+ const level = getLevel2(event);
54281
+ const statusClass = getStatusClass3(event);
54282
+ const identity = resolveLogSessionIdentity(event.request_body);
54283
+ return {
54284
+ event,
54285
+ ts: Number.isFinite(ts) ? ts : Number.NaN,
54286
+ level,
54287
+ statusClass,
54288
+ model: event.model_out || event.model_in,
54289
+ identity,
54290
+ hasError: level === "error",
54291
+ keywordText: buildKeywordText(event, identity),
54292
+ tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
54293
+ };
54294
+ }
54295
+ function createLogEventSummaryFromFacts(facts, location) {
53773
54296
  return eventToSummary({
53774
54297
  id: location.id,
53775
54298
  date: location.date,
53776
54299
  line: location.line ?? 0,
53777
- ts: Number.isFinite(ts) ? ts : 0,
53778
- level: getLevel2(event),
53779
- statusClass: getStatusClass3(event),
53780
- event,
53781
- tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
54300
+ ts: facts.ts,
54301
+ level: facts.level,
54302
+ statusClass: facts.statusClass,
54303
+ event: facts.event,
54304
+ identity: facts.identity,
54305
+ tokenUsage: facts.tokenUsage
53782
54306
  });
53783
54307
  }
54308
+ function createLogEventSummaryFromEvent(event, location) {
54309
+ return createLogEventSummaryFromFacts(extractLogEventFacts(event), location);
54310
+ }
53784
54311
  function logEventMatchesQuery(event, query) {
53785
54312
  if (!event.ts_start)
53786
54313
  return false;
@@ -53867,23 +54394,23 @@ function readStreamContent(baseDir, streamFile) {
53867
54394
  if (!streamFile)
53868
54395
  return { content: null, warning: null };
53869
54396
  try {
53870
- const resolvedBase = resolve5(baseDir);
53871
- const resolvedFromFile = resolve5(streamFile);
54397
+ const resolvedBase = resolve6(baseDir);
54398
+ const resolvedFromFile = resolve6(streamFile);
53872
54399
  const looksLikeStreamFile = resolvedFromFile.endsWith(".sse.raw");
53873
54400
  if (!looksLikeStreamFile) {
53874
54401
  return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
53875
54402
  }
53876
- if (existsSync5(resolvedFromFile)) {
53877
- return { content: readFileSync4(resolvedFromFile, "utf-8"), warning: null };
54403
+ if (existsSync6(resolvedFromFile)) {
54404
+ return { content: readFileSync5(resolvedFromFile, "utf-8"), warning: null };
53878
54405
  }
53879
- const fallbackPath = resolve5(resolvedBase, streamFile);
54406
+ const fallbackPath = resolve6(resolvedBase, streamFile);
53880
54407
  if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
53881
54408
  return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
53882
54409
  }
53883
- if (!existsSync5(fallbackPath)) {
54410
+ if (!existsSync6(fallbackPath)) {
53884
54411
  return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
53885
54412
  }
53886
- return { content: readFileSync4(fallbackPath, "utf-8"), warning: null };
54413
+ return { content: readFileSync5(fallbackPath, "utf-8"), warning: null };
53887
54414
  } catch (err) {
53888
54415
  return {
53889
54416
  content: null,
@@ -53979,8 +54506,8 @@ async function buildLogEventDetail(id, parsed, location, context2) {
53979
54506
  };
53980
54507
  }
53981
54508
  async function scanEvents(baseDir, query) {
53982
- const eventsDir = join5(baseDir, "events");
53983
- if (!existsSync5(eventsDir)) {
54509
+ const eventsDir = join7(baseDir, "events");
54510
+ if (!existsSync6(eventsDir)) {
53984
54511
  return {
53985
54512
  items: [],
53986
54513
  stats: createEmptyLogQueryStats(),
@@ -54006,8 +54533,8 @@ async function scanEvents(baseDir, query) {
54006
54533
  truncated = true;
54007
54534
  break;
54008
54535
  }
54009
- const filePath = join5(eventsDir, `${date5}.jsonl`);
54010
- if (!existsSync5(filePath))
54536
+ const filePath = join7(eventsDir, `${date5}.jsonl`);
54537
+ if (!existsSync6(filePath))
54011
54538
  continue;
54012
54539
  scannedFiles += 1;
54013
54540
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
@@ -54096,7 +54623,10 @@ async function scanEvents(baseDir, query) {
54096
54623
  };
54097
54624
  }
54098
54625
  function isLogQueryWindow(value) {
54099
- return value === "1h" || value === "6h" || value === "24h";
54626
+ return value === "1h" || value === "6h" || value === "24h" || value === "7d" || value === "1mo" || value === "1y";
54627
+ }
54628
+ function getLogQueryWindowMs(window2) {
54629
+ return WINDOW_MS2[window2];
54100
54630
  }
54101
54631
  function resolveLogQueryRange(input) {
54102
54632
  const nowMs = input.nowMs ?? Date.now();
@@ -54189,8 +54719,8 @@ async function getLogEventDetailById(context2, id) {
54189
54719
  }
54190
54720
  const { date: date5, line: line2 } = decodeEventId(id);
54191
54721
  const baseDir = resolveLogBaseDir(context2.logConfig);
54192
- const filePath = join5(baseDir, "events", `${date5}.jsonl`);
54193
- if (!existsSync5(filePath))
54722
+ const filePath = join7(baseDir, "events", `${date5}.jsonl`);
54723
+ if (!existsSync6(filePath))
54194
54724
  return null;
54195
54725
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
54196
54726
  const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
@@ -54403,12 +54933,666 @@ function parseBooleanFlag(value) {
54403
54933
  throw new Error("hasError \u53C2\u6570\u4EC5\u652F\u6301 true/false/1/0");
54404
54934
  }
54405
54935
 
54936
+ // src/log-tail.ts
54937
+ var subscribers = new Set;
54938
+ function publishLogEvent(event) {
54939
+ for (const subscriber of subscribers) {
54940
+ try {
54941
+ subscriber(event);
54942
+ } catch {}
54943
+ }
54944
+ }
54945
+ function subscribeLogEvents(subscriber) {
54946
+ subscribers.add(subscriber);
54947
+ return () => {
54948
+ subscribers.delete(subscriber);
54949
+ };
54950
+ }
54951
+
54952
+ // src/network-access.ts
54953
+ var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
54954
+ function parseIpv4(address) {
54955
+ const parts = address.split(".");
54956
+ if (parts.length !== 4)
54957
+ return null;
54958
+ const octets = parts.map((part) => {
54959
+ if (!/^\d{1,3}$/.test(part))
54960
+ return Number.NaN;
54961
+ const value = Number.parseInt(part, 10);
54962
+ return value >= 0 && value <= 255 ? value : Number.NaN;
54963
+ });
54964
+ return octets.every(Number.isFinite) ? octets : null;
54965
+ }
54966
+ function normalizeIpAddress(raw2) {
54967
+ let address = raw2.trim().toLowerCase();
54968
+ if (address.startsWith("[")) {
54969
+ const end = address.indexOf("]");
54970
+ if (end !== -1)
54971
+ address = address.slice(1, end);
54972
+ }
54973
+ const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
54974
+ if (mappedIpv4) {
54975
+ return mappedIpv4[1];
54976
+ }
54977
+ return address;
54978
+ }
54979
+ function isLoopbackAddress(raw2) {
54980
+ if (!raw2)
54981
+ return false;
54982
+ const address = normalizeIpAddress(raw2);
54983
+ const ipv43 = parseIpv4(address);
54984
+ if (ipv43)
54985
+ return ipv43[0] === 127;
54986
+ return address === "::1";
54987
+ }
54988
+ function isLanAddress(raw2) {
54989
+ if (!raw2)
54990
+ return false;
54991
+ const address = normalizeIpAddress(raw2);
54992
+ const ipv43 = parseIpv4(address);
54993
+ if (ipv43) {
54994
+ const [a, b] = ipv43;
54995
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
54996
+ }
54997
+ return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
54998
+ }
54999
+ function decideNetworkAccess(serverConfig, rawRemoteAddress) {
55000
+ const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
55001
+ if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
55002
+ return { allowed: true, remoteAddress };
55003
+ }
55004
+ const lanEnabled = serverConfig?.lanAccess?.enabled === true;
55005
+ if (!lanEnabled) {
55006
+ return { allowed: false, remoteAddress, reason: "lan-disabled" };
55007
+ }
55008
+ if (!isLanAddress(remoteAddress)) {
55009
+ return { allowed: false, remoteAddress, reason: "non-lan-address" };
55010
+ }
55011
+ return { allowed: true, remoteAddress };
55012
+ }
55013
+ function getRemoteAddressFromContext(c2) {
55014
+ const env = c2.env;
55015
+ const value = env?.[REMOTE_ADDRESS_ENV_KEY];
55016
+ return typeof value === "string" && value.trim() ? value : null;
55017
+ }
55018
+ function createNetworkAccessMiddleware(store) {
55019
+ return async (c2, next) => {
55020
+ const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
55021
+ if (!decision.allowed) {
55022
+ return c2.json({
55023
+ error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
55024
+ remoteAddress: decision.remoteAddress
55025
+ }, 403);
55026
+ }
55027
+ await next();
55028
+ };
55029
+ }
55030
+
55031
+ // src/log-realtime.ts
55032
+ var WS_PATHNAME = "/api/logs/events/ws";
55033
+ var MAX_CONNECTIONS = 64;
55034
+ var MAX_SUBSCRIPTIONS = 64;
55035
+ var MAX_CLIENT_MESSAGE_BYTES = 16 * 1024;
55036
+ var MAX_GLOBAL_EVENT_QUEUE_ITEMS = 1000;
55037
+ var MAX_GLOBAL_EVENT_QUEUE_BYTES = 16 * 1024 * 1024;
55038
+ var MAX_PENDING_BYTES_PER_CONNECTION = 1024 * 1024;
55039
+ var MAX_TOTAL_PENDING_BYTES = 32 * 1024 * 1024;
55040
+ var BACKPRESSURE_BUFFERED_BYTES = 512 * 1024;
55041
+ var HEARTBEAT_INTERVAL_MS = 15000;
55042
+ var FLUSH_BUDGET_MS = 10;
55043
+ var MAX_Q_LENGTH2 = 200;
55044
+ function jsonResponse(status, body) {
55045
+ return new Response(JSON.stringify(body), {
55046
+ status,
55047
+ headers: { "Content-Type": "application/json; charset=utf-8" }
55048
+ });
55049
+ }
55050
+ function createId(prefix) {
55051
+ return `${prefix}_${crypto.randomUUID()}`;
55052
+ }
55053
+ function estimatePublishedEventBytes(event) {
55054
+ const bodyBytes = (event.event.request_bytes ?? 0) + (event.event.response_bytes ?? 0);
55055
+ const streamBytes = event.event.stream_bytes ?? 0;
55056
+ return 1024 + Math.min(64 * 1024, Math.max(0, bodyBytes + streamBytes));
55057
+ }
55058
+ function byteLength(text2) {
55059
+ return Buffer.byteLength(text2);
55060
+ }
55061
+ function isSocketOpen(ws) {
55062
+ return typeof ws.readyState !== "number" || ws.readyState === 1;
55063
+ }
55064
+ function bufferedAmount(ws) {
55065
+ return typeof ws.bufferedAmount === "number" ? ws.bufferedAmount : 0;
55066
+ }
55067
+ function parseClientMessage(raw2) {
55068
+ let text2;
55069
+ if (typeof raw2 === "string") {
55070
+ text2 = raw2;
55071
+ } else {
55072
+ text2 = new TextDecoder().decode(raw2);
55073
+ }
55074
+ if (byteLength(text2) > MAX_CLIENT_MESSAGE_BYTES) {
55075
+ throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u8FC7\u5927");
55076
+ }
55077
+ const parsed = JSON.parse(text2);
55078
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
55079
+ throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
55080
+ }
55081
+ const message = parsed;
55082
+ if (message.type !== "subscribe" && message.type !== "unsubscribe" && message.type !== "ping") {
55083
+ throw new Error("\u4E0D\u652F\u6301\u7684\u5B9E\u65F6\u65E5\u5FD7\u6D88\u606F\u7C7B\u578B");
55084
+ }
55085
+ return message;
55086
+ }
55087
+ function parseStringValue(value) {
55088
+ if (typeof value !== "string")
55089
+ return;
55090
+ const trimmed = value.trim();
55091
+ return trimmed || undefined;
55092
+ }
55093
+ function parseStringArrayValue(value) {
55094
+ if (Array.isArray(value)) {
55095
+ return value.flatMap((item) => typeof item === "string" ? parseCommaSeparated(item) : []);
55096
+ }
55097
+ if (typeof value === "string")
55098
+ return parseCommaSeparated(value);
55099
+ return [];
55100
+ }
55101
+ function parseBooleanValue(value) {
55102
+ if (typeof value === "boolean")
55103
+ return value;
55104
+ if (typeof value === "string")
55105
+ return parseBooleanFlag(value);
55106
+ return null;
55107
+ }
55108
+ function createQueryHash(value) {
55109
+ const text2 = JSON.stringify(value);
55110
+ let hash2 = 2166136261;
55111
+ for (let i = 0;i < text2.length; i += 1) {
55112
+ hash2 ^= text2.charCodeAt(i);
55113
+ hash2 = Math.imul(hash2, 16777619);
55114
+ }
55115
+ return `q_${(hash2 >>> 0).toString(36)}`;
55116
+ }
55117
+ function parseValidatedArray(value, validate, errorMessage) {
55118
+ const raw2 = parseStringArrayValue(value);
55119
+ const parsed = raw2.filter(validate);
55120
+ if (parsed.length !== raw2.length) {
55121
+ throw new Error(errorMessage);
55122
+ }
55123
+ return parsed;
55124
+ }
55125
+ function compileRealtimeQuery(input) {
55126
+ const query = input ?? {};
55127
+ const nowMs = Date.now();
55128
+ const windowRaw = parseStringValue(query.window) ?? "24h";
55129
+ if (!isLogQueryWindow(windowRaw)) {
55130
+ throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y");
55131
+ }
55132
+ const from = parseStringValue(query.from);
55133
+ const to = parseStringValue(query.to);
55134
+ const range = from || to ? {
55135
+ type: "fixed",
55136
+ ...resolveLogQueryRange({ window: windowRaw, from, to, nowMs })
55137
+ } : {
55138
+ type: "window",
55139
+ window: windowRaw,
55140
+ windowMs: getLogQueryWindowMs(windowRaw)
55141
+ };
55142
+ if (range.type === "fixed" && range.toMs <= nowMs) {
55143
+ throw new Error("\u56FA\u5B9A\u7ED3\u675F\u65F6\u95F4\u5DF2\u8FC7\u671F\uFF0C\u5B9E\u65F6\u63A8\u9001\u4EC5\u652F\u6301\u4ECD\u5728\u6EDA\u52A8\u7684\u67E5\u8BE2\u7A97\u53E3");
55144
+ }
55145
+ const sortRaw = parseStringValue(query.sort) ?? "time_desc";
55146
+ if (!validateSort(sortRaw)) {
55147
+ throw new Error("sort \u53C2\u6570\u4EC5\u652F\u6301 time_desc | time_asc");
55148
+ }
55149
+ const levels = parseValidatedArray(query.levels, validateLogLevel, "levels \u53C2\u6570\u4EC5\u652F\u6301 info,error");
55150
+ const statusClasses = parseValidatedArray(query.statusClass, validateStatusClass, "statusClass \u53C2\u6570\u4EC5\u652F\u6301 2xx,4xx,5xx,network_error");
55151
+ const q = (parseStringValue(query.q) ?? "").slice(0, MAX_Q_LENGTH2).toLowerCase();
55152
+ const normalized = {
55153
+ range,
55154
+ sort: sortRaw,
55155
+ levels,
55156
+ providers: parseStringArrayValue(query.provider),
55157
+ routeTypes: parseStringArrayValue(query.routeType),
55158
+ models: parseStringArrayValue(query.model),
55159
+ modelIns: parseStringArrayValue(query.modelIn),
55160
+ modelOuts: parseStringArrayValue(query.modelOut),
55161
+ users: parseStringArrayValue(query.user),
55162
+ sessions: parseStringArrayValue(query.session),
55163
+ statusClasses,
55164
+ hasError: parseBooleanValue(query.hasError),
55165
+ q
55166
+ };
55167
+ return {
55168
+ queryHash: createQueryHash(normalized),
55169
+ sort: sortRaw,
55170
+ range,
55171
+ levels: new Set(levels),
55172
+ providers: new Set(normalized.providers),
55173
+ routeTypes: new Set(normalized.routeTypes),
55174
+ models: new Set(normalized.models),
55175
+ modelIns: new Set(normalized.modelIns),
55176
+ modelOuts: new Set(normalized.modelOuts),
55177
+ users: new Set(normalized.users),
55178
+ sessions: new Set(normalized.sessions),
55179
+ statusClasses: new Set(statusClasses),
55180
+ hasError: normalized.hasError,
55181
+ q
55182
+ };
55183
+ }
55184
+ function setHas(set2, value) {
55185
+ return set2.size === 0 || set2.has(value);
55186
+ }
55187
+ function matchesCompiledQuery(query, facts, nowMs) {
55188
+ if (!facts.event.ts_start || !Number.isFinite(facts.ts))
55189
+ return false;
55190
+ const fromMs = query.range.type === "window" ? nowMs - query.range.windowMs : query.range.fromMs;
55191
+ const toMs = query.range.type === "window" ? nowMs : query.range.toMs;
55192
+ if (facts.ts < fromMs || facts.ts > toMs)
55193
+ return false;
55194
+ if (!setHas(query.levels, facts.level))
55195
+ return false;
55196
+ if (!setHas(query.providers, facts.event.provider))
55197
+ return false;
55198
+ if (!setHas(query.routeTypes, facts.event.route_type))
55199
+ return false;
55200
+ if (!setHas(query.models, facts.model))
55201
+ return false;
55202
+ if (!setHas(query.modelIns, facts.event.model_in))
55203
+ return false;
55204
+ if (!setHas(query.modelOuts, facts.event.model_out))
55205
+ return false;
55206
+ if (query.users.size > 0) {
55207
+ const matchedByRaw = facts.identity.userIdRaw ? query.users.has(facts.identity.userIdRaw) : false;
55208
+ const matchedByUserKey = facts.identity.userKey ? query.users.has(facts.identity.userKey) : false;
55209
+ if (!matchedByRaw && !matchedByUserKey)
55210
+ return false;
55211
+ }
55212
+ if (query.sessions.size > 0) {
55213
+ if (!facts.identity.sessionId || !query.sessions.has(facts.identity.sessionId))
55214
+ return false;
55215
+ }
55216
+ if (!setHas(query.statusClasses, facts.statusClass))
55217
+ return false;
55218
+ if (query.hasError !== null && query.hasError !== facts.hasError)
55219
+ return false;
55220
+ return !query.q || facts.keywordText.includes(query.q);
55221
+ }
55222
+ function createLogRealtimeRuntime(options) {
55223
+ const connections = new Map;
55224
+ const subscriptions = new Map;
55225
+ const eventQueue = [];
55226
+ let tailUnsubscribe = null;
55227
+ let flushTimer = null;
55228
+ let heartbeatTimer = null;
55229
+ let eventQueueBytes = 0;
55230
+ let totalPendingBytes = 0;
55231
+ let droppedUpstreamEvents = 0;
55232
+ const sendText = (connection, text2) => {
55233
+ if (!isSocketOpen(connection.ws))
55234
+ return false;
55235
+ const bytes = byteLength(text2);
55236
+ const shouldQueue = connection.queue.length > 0 || bufferedAmount(connection.ws) > BACKPRESSURE_BUFFERED_BYTES;
55237
+ if (!shouldQueue) {
55238
+ try {
55239
+ connection.ws.send(text2);
55240
+ return true;
55241
+ } catch {
55242
+ removeConnection(connection.id, "send-failed");
55243
+ return false;
55244
+ }
55245
+ }
55246
+ if (connection.pendingBytes + bytes > MAX_PENDING_BYTES_PER_CONNECTION || totalPendingBytes + bytes > MAX_TOTAL_PENDING_BYTES) {
55247
+ connection.droppedOutbound += 1;
55248
+ if (connection.droppedOutbound >= 3) {
55249
+ removeConnection(connection.id, "slow-client");
55250
+ }
55251
+ return false;
55252
+ }
55253
+ connection.queue.push({ text: text2, bytes });
55254
+ connection.pendingBytes += bytes;
55255
+ totalPendingBytes += bytes;
55256
+ return true;
55257
+ };
55258
+ const sendMessage = (connection, message) => {
55259
+ return sendText(connection, JSON.stringify(message));
55260
+ };
55261
+ const flushConnectionQueue = (connection) => {
55262
+ if (!isSocketOpen(connection.ws))
55263
+ return;
55264
+ while (connection.queue.length > 0 && bufferedAmount(connection.ws) <= BACKPRESSURE_BUFFERED_BYTES) {
55265
+ const item = connection.queue.shift();
55266
+ if (!item)
55267
+ break;
55268
+ connection.pendingBytes -= item.bytes;
55269
+ totalPendingBytes -= item.bytes;
55270
+ try {
55271
+ connection.ws.send(item.text);
55272
+ } catch {
55273
+ removeConnection(connection.id, "send-failed");
55274
+ return;
55275
+ }
55276
+ }
55277
+ };
55278
+ const sendError = (connection, error48, requestId) => {
55279
+ sendMessage(connection, { type: "error", requestId, error: error48 });
55280
+ };
55281
+ const ensureTailSubscription = () => {
55282
+ if (tailUnsubscribe || subscriptions.size === 0)
55283
+ return;
55284
+ tailUnsubscribe = subscribeLogEvents((event) => {
55285
+ if (subscriptions.size === 0)
55286
+ return;
55287
+ const bytes = estimatePublishedEventBytes(event);
55288
+ while (eventQueue.length >= MAX_GLOBAL_EVENT_QUEUE_ITEMS || eventQueueBytes + bytes > MAX_GLOBAL_EVENT_QUEUE_BYTES) {
55289
+ const dropped = eventQueue.shift();
55290
+ if (!dropped)
55291
+ break;
55292
+ eventQueueBytes -= dropped.bytes;
55293
+ droppedUpstreamEvents += 1;
55294
+ }
55295
+ eventQueue.push({ event, bytes });
55296
+ eventQueueBytes += bytes;
55297
+ scheduleFlush();
55298
+ });
55299
+ };
55300
+ const maybeStopTailSubscription = () => {
55301
+ if (subscriptions.size > 0)
55302
+ return;
55303
+ tailUnsubscribe?.();
55304
+ tailUnsubscribe = null;
55305
+ eventQueue.length = 0;
55306
+ eventQueueBytes = 0;
55307
+ droppedUpstreamEvents = 0;
55308
+ };
55309
+ const sendOverflow = (subscription, dropped, message) => {
55310
+ const connection = connections.get(subscription.connectionId);
55311
+ if (!connection)
55312
+ return;
55313
+ sendMessage(connection, {
55314
+ type: "overflow",
55315
+ subscriptionId: subscription.id,
55316
+ dropped,
55317
+ message
55318
+ });
55319
+ };
55320
+ const removeSubscription = (subscriptionId, reason, notify2 = true) => {
55321
+ const subscription = subscriptions.get(subscriptionId);
55322
+ if (!subscription)
55323
+ return;
55324
+ subscriptions.delete(subscriptionId);
55325
+ const connection = connections.get(subscription.connectionId);
55326
+ if (connection) {
55327
+ connection.subscriptions.delete(subscriptionId);
55328
+ if (notify2) {
55329
+ sendMessage(connection, {
55330
+ type: "unsubscribed",
55331
+ subscriptionId,
55332
+ reason
55333
+ });
55334
+ }
55335
+ }
55336
+ maybeStopTailSubscription();
55337
+ };
55338
+ function removeConnection(connectionId, reason) {
55339
+ const connection = connections.get(connectionId);
55340
+ if (!connection)
55341
+ return;
55342
+ connections.delete(connectionId);
55343
+ for (const subscriptionId of Array.from(connection.subscriptions)) {
55344
+ removeSubscription(subscriptionId, reason, false);
55345
+ }
55346
+ for (const item of connection.queue) {
55347
+ totalPendingBytes -= item.bytes;
55348
+ }
55349
+ connection.queue.length = 0;
55350
+ connection.pendingBytes = 0;
55351
+ try {
55352
+ connection.ws.close(1001, reason);
55353
+ } catch {}
55354
+ if (connections.size === 0 && heartbeatTimer) {
55355
+ clearInterval(heartbeatTimer);
55356
+ heartbeatTimer = null;
55357
+ }
55358
+ maybeStopTailSubscription();
55359
+ }
55360
+ const pruneExpiredSubscriptions = (nowMs) => {
55361
+ for (const subscription of Array.from(subscriptions.values())) {
55362
+ const range = subscription.query.range;
55363
+ if (range.type === "fixed" && range.toMs <= nowMs) {
55364
+ removeSubscription(subscription.id, "expired");
55365
+ }
55366
+ }
55367
+ };
55368
+ const flushEvents = () => {
55369
+ flushTimer = null;
55370
+ if (subscriptions.size === 0) {
55371
+ eventQueue.length = 0;
55372
+ eventQueueBytes = 0;
55373
+ droppedUpstreamEvents = 0;
55374
+ maybeStopTailSubscription();
55375
+ return;
55376
+ }
55377
+ const startedAt = Date.now();
55378
+ const nowMs = Date.now();
55379
+ pruneExpiredSubscriptions(nowMs);
55380
+ if (droppedUpstreamEvents > 0) {
55381
+ const dropped = droppedUpstreamEvents;
55382
+ droppedUpstreamEvents = 0;
55383
+ for (const subscription of subscriptions.values()) {
55384
+ sendOverflow(subscription, dropped, `\u5B9E\u65F6\u65E5\u5FD7\u961F\u5217\u5DF2\u4E22\u5F03 ${dropped} \u6761\u4E8B\u4EF6\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u4EE5\u8865\u9F50\u3002`);
55385
+ }
55386
+ }
55387
+ while (eventQueue.length > 0 && Date.now() - startedAt < FLUSH_BUDGET_MS) {
55388
+ const queued = eventQueue.shift();
55389
+ if (!queued)
55390
+ break;
55391
+ eventQueueBytes -= queued.bytes;
55392
+ const facts = extractLogEventFacts(queued.event.event);
55393
+ let summary = null;
55394
+ for (const subscription of subscriptions.values()) {
55395
+ if (!matchesCompiledQuery(subscription.query, facts, Date.now()))
55396
+ continue;
55397
+ const connection = connections.get(subscription.connectionId);
55398
+ if (!connection)
55399
+ continue;
55400
+ summary ??= createLogEventSummaryFromFacts(facts, {
55401
+ id: queued.event.id,
55402
+ date: queued.event.date,
55403
+ line: null
55404
+ });
55405
+ const sent = sendMessage(connection, {
55406
+ type: "log.event",
55407
+ subscriptionId: subscription.id,
55408
+ item: summary
55409
+ });
55410
+ if (!sent) {
55411
+ sendOverflow(subscription, 1, "\u5B9E\u65F6\u65E5\u5FD7\u5BA2\u6237\u7AEF\u53D1\u9001\u961F\u5217\u5DF2\u6EE1\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u4EE5\u8865\u9F50\u3002");
55412
+ }
55413
+ }
55414
+ }
55415
+ for (const connection of connections.values()) {
55416
+ flushConnectionQueue(connection);
55417
+ }
55418
+ if (eventQueue.length > 0)
55419
+ scheduleFlush();
55420
+ };
55421
+ function scheduleFlush() {
55422
+ if (flushTimer)
55423
+ return;
55424
+ flushTimer = setTimeout(flushEvents, 0);
55425
+ flushTimer.unref?.();
55426
+ }
55427
+ const ensureHeartbeat = () => {
55428
+ if (heartbeatTimer)
55429
+ return;
55430
+ heartbeatTimer = setInterval(() => {
55431
+ const ts = new Date().toISOString();
55432
+ pruneExpiredSubscriptions(Date.now());
55433
+ for (const connection of connections.values()) {
55434
+ sendMessage(connection, { type: "pong", ts });
55435
+ flushConnectionQueue(connection);
55436
+ }
55437
+ }, HEARTBEAT_INTERVAL_MS);
55438
+ heartbeatTimer.unref?.();
55439
+ };
55440
+ const subscribeConnection = (connection, requestId, query) => {
55441
+ if (subscriptions.size >= MAX_SUBSCRIPTIONS && connection.subscriptions.size === 0) {
55442
+ sendError(connection, "\u5B9E\u65F6\u65E5\u5FD7\u8BA2\u9605\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", requestId);
55443
+ return;
55444
+ }
55445
+ let compiled;
55446
+ try {
55447
+ compiled = compileRealtimeQuery(query);
55448
+ } catch (err) {
55449
+ sendError(connection, err instanceof Error ? err.message : String(err), requestId);
55450
+ return;
55451
+ }
55452
+ for (const subscriptionId2 of Array.from(connection.subscriptions)) {
55453
+ removeSubscription(subscriptionId2, "replaced");
55454
+ }
55455
+ const subscriptionId = createId("sub");
55456
+ const subscription = {
55457
+ id: subscriptionId,
55458
+ connectionId: connection.id,
55459
+ query: compiled
55460
+ };
55461
+ subscriptions.set(subscriptionId, subscription);
55462
+ connection.subscriptions.add(subscriptionId);
55463
+ ensureTailSubscription();
55464
+ sendMessage(connection, {
55465
+ type: "subscribed",
55466
+ requestId: requestId ?? null,
55467
+ subscriptionId,
55468
+ queryHash: compiled.queryHash,
55469
+ now: new Date().toISOString()
55470
+ });
55471
+ };
55472
+ const openConnection = (ws) => {
55473
+ const data = ws.data;
55474
+ const connection = {
55475
+ id: data.connectionId,
55476
+ remoteAddress: data.remoteAddress,
55477
+ ws,
55478
+ subscriptions: new Set,
55479
+ queue: [],
55480
+ pendingBytes: 0,
55481
+ droppedOutbound: 0
55482
+ };
55483
+ connections.set(connection.id, connection);
55484
+ ensureHeartbeat();
55485
+ sendMessage(connection, {
55486
+ type: "ready",
55487
+ connectionId: connection.id,
55488
+ now: new Date().toISOString()
55489
+ });
55490
+ };
55491
+ const handleMessage = (ws, raw2) => {
55492
+ const connection = connections.get(ws.data.connectionId);
55493
+ if (!connection)
55494
+ return;
55495
+ let message;
55496
+ try {
55497
+ message = parseClientMessage(raw2);
55498
+ } catch (err) {
55499
+ sendError(connection, err instanceof Error ? err.message : String(err));
55500
+ return;
55501
+ }
55502
+ if (message.type === "ping") {
55503
+ sendMessage(connection, { type: "pong", ts: message.ts ?? new Date().toISOString() });
55504
+ return;
55505
+ }
55506
+ if (message.type === "unsubscribe") {
55507
+ if (message.subscriptionId) {
55508
+ removeSubscription(message.subscriptionId, "client-unsubscribe");
55509
+ } else {
55510
+ for (const subscriptionId of Array.from(connection.subscriptions)) {
55511
+ removeSubscription(subscriptionId, "client-unsubscribe");
55512
+ }
55513
+ }
55514
+ return;
55515
+ }
55516
+ subscribeConnection(connection, message.requestId, message.query);
55517
+ };
55518
+ return {
55519
+ pathname: WS_PATHNAME,
55520
+ upgrade: (request, server, remoteAddress) => {
55521
+ const url2 = new URL(request.url);
55522
+ if (url2.pathname !== WS_PATHNAME)
55523
+ return { handled: false };
55524
+ const upgradeHeader = request.headers.get("upgrade")?.toLowerCase();
55525
+ if (upgradeHeader !== "websocket") {
55526
+ return {
55527
+ handled: true,
55528
+ response: jsonResponse(400, { error: "\u9700\u8981 WebSocket Upgrade" })
55529
+ };
55530
+ }
55531
+ const decision = decideNetworkAccess(options.store.get().server, remoteAddress);
55532
+ if (!decision.allowed) {
55533
+ return {
55534
+ handled: true,
55535
+ response: jsonResponse(403, {
55536
+ error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
55537
+ remoteAddress: decision.remoteAddress
55538
+ })
55539
+ };
55540
+ }
55541
+ if (connections.size >= MAX_CONNECTIONS) {
55542
+ return {
55543
+ handled: true,
55544
+ response: jsonResponse(503, { error: "\u5B9E\u65F6\u65E5\u5FD7\u8FDE\u63A5\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" })
55545
+ };
55546
+ }
55547
+ const upgraded = server.upgrade(request, {
55548
+ data: {
55549
+ kind: "log-realtime",
55550
+ connectionId: createId("conn"),
55551
+ remoteAddress: decision.remoteAddress
55552
+ }
55553
+ });
55554
+ return upgraded ? { handled: true, upgraded: true } : { handled: true, response: jsonResponse(400, { error: "WebSocket Upgrade \u5931\u8D25" }) };
55555
+ },
55556
+ websocket: {
55557
+ open: openConnection,
55558
+ message: handleMessage,
55559
+ drain: (ws) => {
55560
+ const connection = connections.get(ws.data.connectionId);
55561
+ if (connection)
55562
+ flushConnectionQueue(connection);
55563
+ },
55564
+ close: (ws) => {
55565
+ removeConnection(ws.data.connectionId, "closed");
55566
+ }
55567
+ },
55568
+ dispose: () => {
55569
+ if (flushTimer) {
55570
+ clearTimeout(flushTimer);
55571
+ flushTimer = null;
55572
+ }
55573
+ if (heartbeatTimer) {
55574
+ clearInterval(heartbeatTimer);
55575
+ heartbeatTimer = null;
55576
+ }
55577
+ tailUnsubscribe?.();
55578
+ tailUnsubscribe = null;
55579
+ for (const connectionId of Array.from(connections.keys())) {
55580
+ removeConnection(connectionId, "server-dispose");
55581
+ }
55582
+ eventQueue.length = 0;
55583
+ eventQueueBytes = 0;
55584
+ totalPendingBytes = 0;
55585
+ droppedUpstreamEvents = 0;
55586
+ }
55587
+ };
55588
+ }
55589
+
54406
55590
  // src/log-sessions.ts
54407
- import { createReadStream as createReadStream4, existsSync as existsSync6 } from "fs";
54408
- import { join as join6 } from "path";
55591
+ import { createReadStream as createReadStream4, existsSync as existsSync7 } from "fs";
55592
+ import { join as join8 } from "path";
54409
55593
  import { createInterface as createInterface3 } from "readline";
54410
55594
  var MAX_LINES_SCANNED3 = 250000;
54411
- var MAX_Q_LENGTH2 = 200;
55595
+ var MAX_Q_LENGTH3 = 200;
54412
55596
  function toDayStart4(ms) {
54413
55597
  const date5 = new Date(ms);
54414
55598
  return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
@@ -54427,7 +55611,7 @@ function normalizeInput(input) {
54427
55611
  toMs: input.toMs,
54428
55612
  users: (input.users ?? []).map((item) => item.trim()).filter(Boolean),
54429
55613
  sessions: (input.sessions ?? []).map((item) => item.trim()).filter(Boolean),
54430
- q: qRaw.length > MAX_Q_LENGTH2 ? qRaw.slice(0, MAX_Q_LENGTH2) : qRaw
55614
+ q: qRaw.length > MAX_Q_LENGTH3 ? qRaw.slice(0, MAX_Q_LENGTH3) : qRaw
54431
55615
  };
54432
55616
  }
54433
55617
  function incrementCount(map2, key2) {
@@ -54525,9 +55709,26 @@ async function queryLogSessions(context2, input) {
54525
55709
  if (!logEnabled) {
54526
55710
  return createEmptyResult(normalized.fromMs, normalized.toMs);
54527
55711
  }
55712
+ const indexed = await queryIndexedLogSessions(context2.logConfig, {
55713
+ fromMs: normalized.fromMs,
55714
+ toMs: normalized.toMs,
55715
+ users: normalized.users,
55716
+ sessions: normalized.sessions,
55717
+ q: normalized.q
55718
+ });
55719
+ if (indexed?.meta.indexUsed) {
55720
+ return {
55721
+ from: indexed.from,
55722
+ to: indexed.to,
55723
+ summary: indexed.summary,
55724
+ users: indexed.users,
55725
+ meta: indexed.meta
55726
+ };
55727
+ }
55728
+ const fallbackReason = indexed?.meta.fallbackReason;
54528
55729
  const baseDir = resolveLogBaseDir(context2.logConfig);
54529
- const eventsDir = join6(baseDir, "events");
54530
- if (!existsSync6(eventsDir)) {
55730
+ const eventsDir = join8(baseDir, "events");
55731
+ if (!existsSync7(eventsDir)) {
54531
55732
  return createEmptyResult(normalized.fromMs, normalized.toMs);
54532
55733
  }
54533
55734
  const usersMap = new Map;
@@ -54545,8 +55746,8 @@ async function queryLogSessions(context2, input) {
54545
55746
  truncated = true;
54546
55747
  break;
54547
55748
  }
54548
- const filePath = join6(eventsDir, `${date5}.jsonl`);
54549
- if (!existsSync6(filePath))
55749
+ const filePath = join8(eventsDir, `${date5}.jsonl`);
55750
+ if (!existsSync7(filePath))
54550
55751
  continue;
54551
55752
  scannedFiles += 1;
54552
55753
  const stream = createReadStream4(filePath, { encoding: "utf-8" });
@@ -54648,14 +55849,16 @@ async function queryLogSessions(context2, input) {
54648
55849
  scannedFiles,
54649
55850
  scannedLines,
54650
55851
  parseErrors,
54651
- truncated
55852
+ truncated,
55853
+ indexUsed: false,
55854
+ ...fallbackReason ? { fallbackReason } : {}
54652
55855
  }
54653
55856
  };
54654
55857
  }
54655
55858
 
54656
55859
  // src/log-storage.ts
54657
- import { existsSync as existsSync7, promises as fsPromises } from "fs";
54658
- import { join as join7 } from "path";
55860
+ import { existsSync as existsSync8, promises as fsPromises } from "fs";
55861
+ import { join as join9 } from "path";
54659
55862
  var cachedStorage = null;
54660
55863
  var calculationPromise = null;
54661
55864
  var lastCalculationTime = 0;
@@ -54663,7 +55866,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
54663
55866
  var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
54664
55867
  var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
54665
55868
  async function calculateDirSize(dirPath) {
54666
- if (!existsSync7(dirPath)) {
55869
+ if (!existsSync8(dirPath)) {
54667
55870
  return { bytes: 0, fileCount: 0 };
54668
55871
  }
54669
55872
  let bytes = 0;
@@ -54672,7 +55875,7 @@ async function calculateDirSize(dirPath) {
54672
55875
  try {
54673
55876
  const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
54674
55877
  for (const entry of entries) {
54675
- const fullPath = join7(currentPath, entry.name);
55878
+ const fullPath = join9(currentPath, entry.name);
54676
55879
  if (entry.isDirectory()) {
54677
55880
  await walk(fullPath);
54678
55881
  } else if (entry.isFile()) {
@@ -54703,8 +55906,8 @@ async function doCalculateStorage(logConfig) {
54703
55906
  }
54704
55907
  const baseDir = resolveLogBaseDir(logConfig);
54705
55908
  const [eventsResult, streamsResult] = await Promise.all([
54706
- calculateDirSize(join7(baseDir, "events")),
54707
- calculateDirSize(join7(baseDir, "streams"))
55909
+ calculateDirSize(join9(baseDir, "events")),
55910
+ calculateDirSize(join9(baseDir, "streams"))
54708
55911
  ]);
54709
55912
  const indexResult = await calculateIndexSize(baseDir);
54710
55913
  return {
@@ -54718,7 +55921,7 @@ async function doCalculateStorage(logConfig) {
54718
55921
  };
54719
55922
  }
54720
55923
  async function calculateIndexSize(baseDir) {
54721
- if (!existsSync7(baseDir)) {
55924
+ if (!existsSync8(baseDir)) {
54722
55925
  return { bytes: 0, fileCount: 0 };
54723
55926
  }
54724
55927
  let bytes = 0;
@@ -54728,7 +55931,7 @@ async function calculateIndexSize(baseDir) {
54728
55931
  for (const entry of entries) {
54729
55932
  if (!entry.isFile() || !entry.name.startsWith("logs-index.sqlite"))
54730
55933
  continue;
54731
- const stats = await fsPromises.stat(join7(baseDir, entry.name));
55934
+ const stats = await fsPromises.stat(join9(baseDir, entry.name));
54732
55935
  bytes += stats.size;
54733
55936
  fileCount += 1;
54734
55937
  }
@@ -54779,33 +55982,17 @@ function startLogStorageBackgroundTask(logConfig) {
54779
55982
  };
54780
55983
  }
54781
55984
 
54782
- // src/log-tail.ts
54783
- var subscribers = new Set;
54784
- function publishLogEvent(event) {
54785
- for (const subscriber of subscribers) {
54786
- try {
54787
- subscriber(event);
54788
- } catch {}
54789
- }
54790
- }
54791
- function subscribeLogEvents(subscriber) {
54792
- subscribers.add(subscriber);
54793
- return () => {
54794
- subscribers.delete(subscriber);
54795
- };
54796
- }
54797
-
54798
55985
  // src/logger.ts
54799
55986
  import {
54800
55987
  appendFileSync,
54801
55988
  closeSync as closeSync2,
54802
- existsSync as existsSync8,
54803
- mkdirSync as mkdirSync3,
55989
+ existsSync as existsSync9,
55990
+ mkdirSync as mkdirSync4,
54804
55991
  openSync as openSync2,
54805
55992
  statSync as statSync3,
54806
55993
  writeSync
54807
55994
  } from "fs";
54808
- import { join as join8 } from "path";
55995
+ import { join as join10 } from "path";
54809
55996
  class Logger {
54810
55997
  baseDir;
54811
55998
  eventsDir;
@@ -54820,8 +56007,8 @@ class Logger {
54820
56007
  this._bodyPolicy = config2.bodyPolicy ?? "off";
54821
56008
  this._streamsEnabled = config2.streams?.enabled !== false;
54822
56009
  this.maxStreamBytes = config2.streams?.maxBytesPerRequest ?? 10 * 1024 * 1024;
54823
- this.eventsDir = join8(baseDir, "events");
54824
- this.streamsDir = join8(baseDir, "streams");
56010
+ this.eventsDir = join10(baseDir, "events");
56011
+ this.streamsDir = join10(baseDir, "streams");
54825
56012
  if (this._enabled)
54826
56013
  this.ensureDirs();
54827
56014
  }
@@ -54833,14 +56020,14 @@ class Logger {
54833
56020
  }
54834
56021
  ensureDirs() {
54835
56022
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
54836
- if (!existsSync8(dir))
54837
- mkdirSync3(dir, { recursive: true });
56023
+ if (!existsSync9(dir))
56024
+ mkdirSync4(dir, { recursive: true });
54838
56025
  }
54839
56026
  }
54840
56027
  ensureStreamDateDir(dateStr) {
54841
- const dir = join8(this.streamsDir, dateStr);
54842
- if (!existsSync8(dir))
54843
- mkdirSync3(dir, { recursive: true });
56028
+ const dir = join10(this.streamsDir, dateStr);
56029
+ if (!existsSync9(dir))
56030
+ mkdirSync4(dir, { recursive: true });
54844
56031
  return dir;
54845
56032
  }
54846
56033
  writeEvent(event) {
@@ -54850,8 +56037,8 @@ class Logger {
54850
56037
  const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
54851
56038
  this.ensureDirs();
54852
56039
  const dateStr = enrichedEvent.ts_start.slice(0, 10);
54853
- const filePath = join8(this.eventsDir, `${dateStr}.jsonl`);
54854
- const offset = existsSync8(filePath) ? statSync3(filePath).size : 0;
56040
+ const filePath = join10(this.eventsDir, `${dateStr}.jsonl`);
56041
+ const offset = existsSync9(filePath) ? statSync3(filePath).size : 0;
54855
56042
  const line2 = `${JSON.stringify(enrichedEvent)}
54856
56043
  `;
54857
56044
  appendFileSync(filePath, line2);
@@ -54880,7 +56067,7 @@ class Logger {
54880
56067
  let fd;
54881
56068
  try {
54882
56069
  const dir = this.ensureStreamDateDir(dateStr);
54883
- filePath = join8(dir, `${requestId}.sse.raw`);
56070
+ filePath = join10(dir, `${requestId}.sse.raw`);
54884
56071
  fd = openSync2(filePath, "a");
54885
56072
  } catch (err) {
54886
56073
  console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
@@ -54970,85 +56157,6 @@ function normalizeUrl(rawUrl) {
54970
56157
  return rawUrl;
54971
56158
  }
54972
56159
 
54973
- // src/network-access.ts
54974
- var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
54975
- function parseIpv4(address) {
54976
- const parts = address.split(".");
54977
- if (parts.length !== 4)
54978
- return null;
54979
- const octets = parts.map((part) => {
54980
- if (!/^\d{1,3}$/.test(part))
54981
- return Number.NaN;
54982
- const value = Number.parseInt(part, 10);
54983
- return value >= 0 && value <= 255 ? value : Number.NaN;
54984
- });
54985
- return octets.every(Number.isFinite) ? octets : null;
54986
- }
54987
- function normalizeIpAddress(raw2) {
54988
- let address = raw2.trim().toLowerCase();
54989
- if (address.startsWith("[")) {
54990
- const end = address.indexOf("]");
54991
- if (end !== -1)
54992
- address = address.slice(1, end);
54993
- }
54994
- const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
54995
- if (mappedIpv4) {
54996
- return mappedIpv4[1];
54997
- }
54998
- return address;
54999
- }
55000
- function isLoopbackAddress(raw2) {
55001
- if (!raw2)
55002
- return false;
55003
- const address = normalizeIpAddress(raw2);
55004
- const ipv43 = parseIpv4(address);
55005
- if (ipv43)
55006
- return ipv43[0] === 127;
55007
- return address === "::1";
55008
- }
55009
- function isLanAddress(raw2) {
55010
- if (!raw2)
55011
- return false;
55012
- const address = normalizeIpAddress(raw2);
55013
- const ipv43 = parseIpv4(address);
55014
- if (ipv43) {
55015
- const [a, b] = ipv43;
55016
- return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
55017
- }
55018
- return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
55019
- }
55020
- function decideNetworkAccess(serverConfig, rawRemoteAddress) {
55021
- const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
55022
- if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
55023
- return { allowed: true, remoteAddress };
55024
- }
55025
- const lanEnabled = serverConfig?.lanAccess?.enabled === true;
55026
- if (!lanEnabled) {
55027
- return { allowed: false, remoteAddress, reason: "lan-disabled" };
55028
- }
55029
- if (!isLanAddress(remoteAddress)) {
55030
- return { allowed: false, remoteAddress, reason: "non-lan-address" };
55031
- }
55032
- return { allowed: true, remoteAddress };
55033
- }
55034
- function getRemoteAddressFromContext(c2) {
55035
- const env = c2.env;
55036
- const value = env?.[REMOTE_ADDRESS_ENV_KEY];
55037
- return typeof value === "string" && value.trim() ? value : null;
55038
- }
55039
- function createNetworkAccessMiddleware(store) {
55040
- return async (c2, next) => {
55041
- const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
55042
- if (!decision.allowed) {
55043
- return c2.json({
55044
- error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
55045
- remoteAddress: decision.remoteAddress
55046
- }, 403);
55047
- }
55048
- await next();
55049
- };
55050
- }
55051
-
55052
56160
  // src/openapi.ts
55053
56161
  var openAPISpec = {
55054
56162
  openapi: "3.0.0",
@@ -55267,7 +56375,7 @@ var openAPISpec = {
55267
56375
  required: false,
55268
56376
  schema: {
55269
56377
  type: "string",
55270
- enum: ["1h", "6h", "24h"],
56378
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
55271
56379
  default: "24h"
55272
56380
  },
55273
56381
  description: "\u65F6\u95F4\u7A97\u53E3\uFF08\u5F53\u672A\u63D0\u4F9B from/to \u65F6\u751F\u6548\uFF09"
@@ -55597,7 +56705,7 @@ var openAPISpec = {
55597
56705
  required: false,
55598
56706
  schema: {
55599
56707
  type: "string",
55600
- enum: ["1h", "6h", "24h"],
56708
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
55601
56709
  default: "24h"
55602
56710
  },
55603
56711
  description: "\u65F6\u95F4\u7A97\u53E3"
@@ -55718,7 +56826,7 @@ var openAPISpec = {
55718
56826
  required: false,
55719
56827
  schema: {
55720
56828
  type: "string",
55721
- enum: ["1h", "6h", "24h"],
56829
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
55722
56830
  default: "1h"
55723
56831
  }
55724
56832
  },
@@ -56155,7 +57263,7 @@ var openAPISpec = {
56155
57263
  // src/plugin-loader.ts
56156
57264
  import { mkdtemp, rm, writeFile } from "fs/promises";
56157
57265
  import { tmpdir } from "os";
56158
- import { join as join9, resolve as resolve6 } from "path";
57266
+ import { join as join11, resolve as resolve7 } from "path";
56159
57267
  function isLocalPath(pkg) {
56160
57268
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
56161
57269
  }
@@ -56178,7 +57286,7 @@ var remoteTmpDir = null;
56178
57286
  var remoteTmpFiles = [];
56179
57287
  async function ensureRemoteTmpDir() {
56180
57288
  if (!remoteTmpDir) {
56181
- remoteTmpDir = await mkdtemp(join9(tmpdir(), "local-router-plugins-"));
57289
+ remoteTmpDir = await mkdtemp(join11(tmpdir(), "local-router-plugins-"));
56182
57290
  }
56183
57291
  return remoteTmpDir;
56184
57292
  }
@@ -56191,7 +57299,7 @@ async function fetchRemotePlugin(url2) {
56191
57299
  const ext = inferExtension(url2, response.headers.get("content-type"));
56192
57300
  const dir = await ensureRemoteTmpDir();
56193
57301
  const fileName = `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
56194
- const filePath = join9(dir, fileName);
57302
+ const filePath = join11(dir, fileName);
56195
57303
  await writeFile(filePath, content, "utf-8");
56196
57304
  remoteTmpFiles.push(filePath);
56197
57305
  return filePath;
@@ -56211,7 +57319,7 @@ async function importPlugin(pkg, configDir) {
56211
57319
  const localPath = await fetchRemotePlugin(pkg);
56212
57320
  modulePath = `${localPath}?t=${Date.now()}`;
56213
57321
  } else if (isLocalPath(pkg)) {
56214
- const absolutePath = resolve6(configDir, pkg);
57322
+ const absolutePath = resolve7(configDir, pkg);
56215
57323
  modulePath = `${absolutePath}?t=${Date.now()}`;
56216
57324
  } else {
56217
57325
  modulePath = pkg;
@@ -56985,7 +58093,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
56985
58093
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
56986
58094
  const CRYPTO_SESSION_MAX = 512;
56987
58095
  const schemaPath = getBundledSchemaPath();
56988
- const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
58096
+ const schemaJson = JSON.parse(readFileSync6(schemaPath, "utf-8"));
56989
58097
  const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
56990
58098
  for (const [id, record2] of Array.from(cryptoSessions.entries())) {
56991
58099
  if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
@@ -57125,6 +58233,45 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57125
58233
  return c2.json({ error: `\u8BFB\u53D6\u914D\u7F6E schema \u5931\u8D25: ${err instanceof Error ? err.message : err}` }, 500);
57126
58234
  }
57127
58235
  });
58236
+ api2.get("/autostart", async (c2) => {
58237
+ try {
58238
+ const manager = createAutostartManager();
58239
+ const systemInstalled = await manager.isInstalled();
58240
+ const config2 = store.get();
58241
+ return c2.json({
58242
+ enabled: config2.server?.autostart ?? false,
58243
+ systemInstalled,
58244
+ platform: manager.platform,
58245
+ servicePath: manager.getServicePath()
58246
+ });
58247
+ } catch (err) {
58248
+ return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
58249
+ }
58250
+ });
58251
+ api2.put("/autostart", async (c2) => {
58252
+ try {
58253
+ const { enabled } = await c2.req.json();
58254
+ if (typeof enabled !== "boolean") {
58255
+ return c2.json({ error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" }, 400);
58256
+ }
58257
+ const manager = createAutostartManager();
58258
+ if (manager.platform === "unsupported") {
58259
+ return c2.json({ error: "\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8" }, 400);
58260
+ }
58261
+ if (enabled) {
58262
+ const { execPath, args } = getAutostartExecArgs();
58263
+ await manager.install({ execPath, args, label: "com.lakphy.local-router" });
58264
+ } else {
58265
+ await manager.uninstall();
58266
+ }
58267
+ const config2 = store.get();
58268
+ const updated = { ...config2, server: { ...config2.server, autostart: enabled } };
58269
+ store.save(updated);
58270
+ return c2.json({ ok: true, enabled });
58271
+ } catch (err) {
58272
+ return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
58273
+ }
58274
+ });
57128
58275
  api2.post("/chat/proxy", async (c2) => {
57129
58276
  let body;
57130
58277
  try {
@@ -57201,7 +58348,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57201
58348
  try {
57202
58349
  const windowRaw = c2.req.query("window") ?? "24h";
57203
58350
  if (!isLogQueryWindow(windowRaw)) {
57204
- return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
58351
+ return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
57205
58352
  }
57206
58353
  const range = resolveLogQueryRange({
57207
58354
  window: windowRaw,
@@ -57252,7 +58399,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57252
58399
  try {
57253
58400
  const windowRaw = c2.req.query("window") ?? "24h";
57254
58401
  if (!isLogQueryWindow(windowRaw)) {
57255
- return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
58402
+ return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
57256
58403
  }
57257
58404
  const range = resolveLogQueryRange({
57258
58405
  window: windowRaw,
@@ -57291,7 +58438,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57291
58438
  try {
57292
58439
  const windowRaw = c2.req.query("window") ?? "24h";
57293
58440
  if (!isLogQueryWindow(windowRaw)) {
57294
- return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
58441
+ return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
57295
58442
  }
57296
58443
  const range = resolveLogQueryRange({
57297
58444
  window: windowRaw,
@@ -57341,7 +58488,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57341
58488
  const target = c2.req.raw;
57342
58489
  const windowRaw = c2.req.query("window") ?? "1h";
57343
58490
  if (!isLogQueryWindow(windowRaw)) {
57344
- return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
58491
+ return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
57345
58492
  }
57346
58493
  const sortRaw = c2.req.query("sort") ?? "time_desc";
57347
58494
  if (!validateSort(sortRaw)) {
@@ -57590,7 +58737,7 @@ async function createApp(store, options) {
57590
58737
  }
57591
58738
  const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
57592
58739
  options?.registerCleanup?.(stopLogStorageTask);
57593
- const configDir = dirname2(resolve7(store.getPath()));
58740
+ const configDir = dirname3(resolve8(store.getPath()));
57594
58741
  const pluginManager = new PluginManager(configDir);
57595
58742
  const reloadResult = await pluginManager.reloadAll(config2.providers);
57596
58743
  if (!reloadResult.ok) {
@@ -57637,28 +58784,56 @@ async function createApp(store, options) {
57637
58784
  }
57638
58785
  return app;
57639
58786
  }
57640
- async function createDefaultAppFromProcessArgs() {
57641
- const configPath = parseConfigPath();
58787
+ async function createAppRuntimeFromConfigPath(configPath, listen) {
57642
58788
  const store = new ConfigStore(configPath);
57643
- return createApp(store, {
57644
- listen: {
57645
- host: process.env.HOST ?? "0.0.0.0",
57646
- port: Number.parseInt(process.env.PORT ?? "4099", 10)
58789
+ const cleanups = [];
58790
+ const app = await createApp(store, {
58791
+ listen,
58792
+ registerCleanup: (cleanup) => {
58793
+ cleanups.push(cleanup);
58794
+ }
58795
+ });
58796
+ const logRealtime = createLogRealtimeRuntime({ store });
58797
+ return {
58798
+ app,
58799
+ logRealtime,
58800
+ dispose: () => {
58801
+ logRealtime.dispose();
58802
+ for (const cleanup of cleanups.reverse()) {
58803
+ try {
58804
+ cleanup();
58805
+ } catch {}
58806
+ }
57647
58807
  }
58808
+ };
58809
+ }
58810
+ async function createDefaultAppRuntimeFromProcessArgs() {
58811
+ const configPath = parseConfigPath();
58812
+ return createAppRuntimeFromConfigPath(configPath, {
58813
+ host: process.env.HOST ?? "0.0.0.0",
58814
+ port: Number.parseInt(process.env.PORT ?? "4099", 10)
57648
58815
  });
57649
58816
  }
57650
58817
 
57651
58818
  // src/entry.ts
57652
- var app = await createDefaultAppFromProcessArgs();
58819
+ var runtime = await createDefaultAppRuntimeFromProcessArgs();
57653
58820
  var entry_default = {
57654
58821
  hostname: process.env.HOST ?? "0.0.0.0",
57655
58822
  port: Number.parseInt(process.env.PORT ?? "4099", 10),
57656
58823
  fetch(request, server) {
57657
58824
  const remoteAddress = server.requestIP(request)?.address ?? null;
57658
- return app.fetch(request, {
58825
+ const realtimeUpgrade = runtime.logRealtime.upgrade(request, server, remoteAddress);
58826
+ if (realtimeUpgrade.handled) {
58827
+ if (realtimeUpgrade.upgraded) {
58828
+ return;
58829
+ }
58830
+ return realtimeUpgrade.response ?? new Response("WebSocket Upgrade failed", { status: 400 });
58831
+ }
58832
+ return runtime.app.fetch(request, {
57659
58833
  [REMOTE_ADDRESS_ENV_KEY]: remoteAddress
57660
58834
  });
57661
- }
58835
+ },
58836
+ websocket: runtime.logRealtime.websocket
57662
58837
  };
57663
58838
  export {
57664
58839
  entry_default as default