@lakphy/local-router 0.5.3 → 0.5.5

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,8 +51697,8 @@ 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 existsSync3 } from "fs";
51701
+ import { join as join5 } from "path";
51492
51702
  import { createInterface } from "readline";
51493
51703
  var WINDOW_MS = {
51494
51704
  "1h": 60 * 60 * 1000,
@@ -51605,8 +51815,8 @@ async function getLogMetrics(options) {
51605
51815
  if (!refresh && cached2 && cached2.expiresAt > nowMs) {
51606
51816
  return cached2.value;
51607
51817
  }
51608
- const eventsDir = join3(baseDir, "events");
51609
- if (!existsSync2(eventsDir)) {
51818
+ const eventsDir = join5(baseDir, "events");
51819
+ if (!existsSync3(eventsDir)) {
51610
51820
  const empty = createEmptyMetrics(window2, nowMs, {
51611
51821
  logEnabled: true,
51612
51822
  baseDir,
@@ -51652,8 +51862,8 @@ async function getLogMetrics(options) {
51652
51862
  partial2 = true;
51653
51863
  break;
51654
51864
  }
51655
- const filePath = join3(eventsDir, `${dateStr}.jsonl`);
51656
- if (!existsSync2(filePath))
51865
+ const filePath = join5(eventsDir, `${dateStr}.jsonl`);
51866
+ if (!existsSync3(filePath))
51657
51867
  continue;
51658
51868
  filesScanned += 1;
51659
51869
  try {
@@ -51789,8 +51999,8 @@ async function getLogMetrics(options) {
51789
51999
  }
51790
52000
 
51791
52001
  // 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";
52002
+ import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
52003
+ import { join as join7, resolve as resolve6 } from "path";
51794
52004
  import { createInterface as createInterface2 } from "readline";
51795
52005
 
51796
52006
  // src/log-index.ts
@@ -51798,13 +52008,13 @@ import { Database } from "bun:sqlite";
51798
52008
  import {
51799
52009
  closeSync,
51800
52010
  createReadStream as createReadStream2,
51801
- existsSync as existsSync4,
51802
- mkdirSync as mkdirSync2,
52011
+ existsSync as existsSync5,
52012
+ mkdirSync as mkdirSync3,
51803
52013
  openSync,
51804
52014
  readSync,
51805
52015
  statSync as statSync2
51806
52016
  } from "fs";
51807
- import { join as join4 } from "path";
52017
+ import { join as join6 } from "path";
51808
52018
 
51809
52019
  // src/log-session-identity.ts
51810
52020
  var USER_SESSION_DELIMITER = "_account__session_";
@@ -51883,8 +52093,8 @@ function resolveLogSessionIdentity(requestBody) {
51883
52093
  }
51884
52094
 
51885
52095
  // src/token-usage.ts
51886
- import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
51887
- import { resolve as resolve4 } from "path";
52096
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "fs";
52097
+ import { resolve as resolve5 } from "path";
51888
52098
  var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
51889
52099
  function asRecord(value) {
51890
52100
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -52401,12 +52611,12 @@ function safeReadStreamFile(streamFile, baseDir) {
52401
52611
  try {
52402
52612
  const candidates = [streamFile];
52403
52613
  if (baseDir)
52404
- candidates.push(resolve4(baseDir, streamFile));
52614
+ candidates.push(resolve5(baseDir, streamFile));
52405
52615
  for (const candidate of candidates) {
52406
- const resolved = resolve4(candidate);
52616
+ const resolved = resolve5(candidate);
52407
52617
  if (!resolved.endsWith(".sse.raw"))
52408
52618
  continue;
52409
- if (!existsSync3(resolved))
52619
+ if (!existsSync4(resolved))
52410
52620
  continue;
52411
52621
  const stats = statSync(resolved);
52412
52622
  if (stats.size > MAX_STREAM_USAGE_BYTES) {
@@ -52415,7 +52625,7 @@ function safeReadStreamFile(streamFile, baseDir) {
52415
52625
  warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
52416
52626
  };
52417
52627
  }
52418
- return { content: readFileSync3(resolved, "utf-8"), warning: null };
52628
+ return { content: readFileSync4(resolved, "utf-8"), warning: null };
52419
52629
  }
52420
52630
  } catch (err) {
52421
52631
  return {
@@ -52866,8 +53076,8 @@ class LogIndex {
52866
53076
  constructor(baseDir, config2) {
52867
53077
  this.baseDir = baseDir;
52868
53078
  this.config = config2;
52869
- mkdirSync2(baseDir, { recursive: true });
52870
- const dbPath = join4(baseDir, "logs-index.sqlite");
53079
+ mkdirSync3(baseDir, { recursive: true });
53080
+ const dbPath = join6(baseDir, "logs-index.sqlite");
52871
53081
  this.db = new Database(dbPath, { create: true, strict: true });
52872
53082
  this.configure();
52873
53083
  this.migrate();
@@ -52943,11 +53153,11 @@ class LogIndex {
52943
53153
  let scannedFiles = 0;
52944
53154
  let scannedLines = 0;
52945
53155
  let parseErrors = 0;
52946
- const eventsDir = join4(this.baseDir, "events");
53156
+ const eventsDir = join6(this.baseDir, "events");
52947
53157
  const dates = listDateStrings2(fromMs, toMs);
52948
53158
  for (const date5 of dates) {
52949
- const filePath = join4(eventsDir, `${date5}.jsonl`);
52950
- if (!existsSync4(filePath))
53159
+ const filePath = join6(eventsDir, `${date5}.jsonl`);
53160
+ if (!existsSync5(filePath))
52951
53161
  continue;
52952
53162
  const stats = statSync2(filePath);
52953
53163
  const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
@@ -53038,8 +53248,8 @@ class LogIndex {
53038
53248
  if (!parsedId)
53039
53249
  return null;
53040
53250
  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))
53251
+ const filePath = row?.source_file ?? join6(this.baseDir, "events", `${parsedId.date}.jsonl`);
53252
+ if (!existsSync5(filePath))
53043
53253
  return null;
53044
53254
  const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
53045
53255
  if (!line2?.trim())
@@ -53448,8 +53658,8 @@ function getIndexedLogEventDetail(logConfig, id) {
53448
53658
  const parsedId = decodeOffsetLogEventId(id);
53449
53659
  if (!parsedId)
53450
53660
  return null;
53451
- const filePath = join4(baseDir, "events", `${parsedId.date}.jsonl`);
53452
- if (!existsSync4(filePath))
53661
+ const filePath = join6(baseDir, "events", `${parsedId.date}.jsonl`);
53662
+ if (!existsSync5(filePath))
53453
53663
  return null;
53454
53664
  const line2 = readLineAtOffset(filePath, parsedId.offset);
53455
53665
  if (!line2?.trim())
@@ -53556,12 +53766,8 @@ function buildMessage2(event) {
53556
53766
  const status = event.upstream_status ?? 0;
53557
53767
  return `${event.method} ${event.path} -> ${status}`;
53558
53768
  }
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 = [
53769
+ function buildKeywordText(event, identity) {
53770
+ return [
53565
53771
  event.request_id,
53566
53772
  event.path,
53567
53773
  event.provider,
@@ -53575,7 +53781,13 @@ function containsKeyword(event, q) {
53575
53781
  event.error_message ?? "",
53576
53782
  buildMessage2(event)
53577
53783
  ].join(" ").toLowerCase();
53578
- return haystack.includes(keyword);
53784
+ }
53785
+ function containsKeyword(event, q) {
53786
+ if (!q)
53787
+ return true;
53788
+ const identity = resolveLogSessionIdentity(event.request_body);
53789
+ const keyword = q.toLowerCase();
53790
+ return buildKeywordText(event, identity).includes(keyword);
53579
53791
  }
53580
53792
  function createRunningStats() {
53581
53793
  return {
@@ -53743,7 +53955,7 @@ function normalizeQuery(input, maxLimit = MAX_QUERY_LIMIT) {
53743
53955
  }
53744
53956
  function eventToSummary(item) {
53745
53957
  const { event } = item;
53746
- const identity = resolveLogSessionIdentity(event.request_body);
53958
+ const identity = item.identity ?? resolveLogSessionIdentity(event.request_body);
53747
53959
  return {
53748
53960
  id: item.id,
53749
53961
  ts: event.ts_start,
@@ -53768,19 +53980,39 @@ function eventToSummary(item) {
53768
53980
  tokenUsage: item.tokenUsage
53769
53981
  };
53770
53982
  }
53771
- function createLogEventSummaryFromEvent(event, location) {
53983
+ function extractLogEventFacts(event) {
53772
53984
  const ts = Date.parse(event.ts_start);
53985
+ const level = getLevel2(event);
53986
+ const statusClass = getStatusClass3(event);
53987
+ const identity = resolveLogSessionIdentity(event.request_body);
53988
+ return {
53989
+ event,
53990
+ ts: Number.isFinite(ts) ? ts : Number.NaN,
53991
+ level,
53992
+ statusClass,
53993
+ model: event.model_out || event.model_in,
53994
+ identity,
53995
+ hasError: level === "error",
53996
+ keywordText: buildKeywordText(event, identity),
53997
+ tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
53998
+ };
53999
+ }
54000
+ function createLogEventSummaryFromFacts(facts, location) {
53773
54001
  return eventToSummary({
53774
54002
  id: location.id,
53775
54003
  date: location.date,
53776
54004
  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)
54005
+ ts: facts.ts,
54006
+ level: facts.level,
54007
+ statusClass: facts.statusClass,
54008
+ event: facts.event,
54009
+ identity: facts.identity,
54010
+ tokenUsage: facts.tokenUsage
53782
54011
  });
53783
54012
  }
54013
+ function createLogEventSummaryFromEvent(event, location) {
54014
+ return createLogEventSummaryFromFacts(extractLogEventFacts(event), location);
54015
+ }
53784
54016
  function logEventMatchesQuery(event, query) {
53785
54017
  if (!event.ts_start)
53786
54018
  return false;
@@ -53867,23 +54099,23 @@ function readStreamContent(baseDir, streamFile) {
53867
54099
  if (!streamFile)
53868
54100
  return { content: null, warning: null };
53869
54101
  try {
53870
- const resolvedBase = resolve5(baseDir);
53871
- const resolvedFromFile = resolve5(streamFile);
54102
+ const resolvedBase = resolve6(baseDir);
54103
+ const resolvedFromFile = resolve6(streamFile);
53872
54104
  const looksLikeStreamFile = resolvedFromFile.endsWith(".sse.raw");
53873
54105
  if (!looksLikeStreamFile) {
53874
54106
  return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
53875
54107
  }
53876
- if (existsSync5(resolvedFromFile)) {
53877
- return { content: readFileSync4(resolvedFromFile, "utf-8"), warning: null };
54108
+ if (existsSync6(resolvedFromFile)) {
54109
+ return { content: readFileSync5(resolvedFromFile, "utf-8"), warning: null };
53878
54110
  }
53879
- const fallbackPath = resolve5(resolvedBase, streamFile);
54111
+ const fallbackPath = resolve6(resolvedBase, streamFile);
53880
54112
  if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
53881
54113
  return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
53882
54114
  }
53883
- if (!existsSync5(fallbackPath)) {
54115
+ if (!existsSync6(fallbackPath)) {
53884
54116
  return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
53885
54117
  }
53886
- return { content: readFileSync4(fallbackPath, "utf-8"), warning: null };
54118
+ return { content: readFileSync5(fallbackPath, "utf-8"), warning: null };
53887
54119
  } catch (err) {
53888
54120
  return {
53889
54121
  content: null,
@@ -53979,8 +54211,8 @@ async function buildLogEventDetail(id, parsed, location, context2) {
53979
54211
  };
53980
54212
  }
53981
54213
  async function scanEvents(baseDir, query) {
53982
- const eventsDir = join5(baseDir, "events");
53983
- if (!existsSync5(eventsDir)) {
54214
+ const eventsDir = join7(baseDir, "events");
54215
+ if (!existsSync6(eventsDir)) {
53984
54216
  return {
53985
54217
  items: [],
53986
54218
  stats: createEmptyLogQueryStats(),
@@ -54006,8 +54238,8 @@ async function scanEvents(baseDir, query) {
54006
54238
  truncated = true;
54007
54239
  break;
54008
54240
  }
54009
- const filePath = join5(eventsDir, `${date5}.jsonl`);
54010
- if (!existsSync5(filePath))
54241
+ const filePath = join7(eventsDir, `${date5}.jsonl`);
54242
+ if (!existsSync6(filePath))
54011
54243
  continue;
54012
54244
  scannedFiles += 1;
54013
54245
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
@@ -54098,6 +54330,9 @@ async function scanEvents(baseDir, query) {
54098
54330
  function isLogQueryWindow(value) {
54099
54331
  return value === "1h" || value === "6h" || value === "24h";
54100
54332
  }
54333
+ function getLogQueryWindowMs(window2) {
54334
+ return WINDOW_MS2[window2];
54335
+ }
54101
54336
  function resolveLogQueryRange(input) {
54102
54337
  const nowMs = input.nowMs ?? Date.now();
54103
54338
  const hasFrom = !!input.from;
@@ -54189,8 +54424,8 @@ async function getLogEventDetailById(context2, id) {
54189
54424
  }
54190
54425
  const { date: date5, line: line2 } = decodeEventId(id);
54191
54426
  const baseDir = resolveLogBaseDir(context2.logConfig);
54192
- const filePath = join5(baseDir, "events", `${date5}.jsonl`);
54193
- if (!existsSync5(filePath))
54427
+ const filePath = join7(baseDir, "events", `${date5}.jsonl`);
54428
+ if (!existsSync6(filePath))
54194
54429
  return null;
54195
54430
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
54196
54431
  const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
@@ -54403,12 +54638,666 @@ function parseBooleanFlag(value) {
54403
54638
  throw new Error("hasError \u53C2\u6570\u4EC5\u652F\u6301 true/false/1/0");
54404
54639
  }
54405
54640
 
54641
+ // src/log-tail.ts
54642
+ var subscribers = new Set;
54643
+ function publishLogEvent(event) {
54644
+ for (const subscriber of subscribers) {
54645
+ try {
54646
+ subscriber(event);
54647
+ } catch {}
54648
+ }
54649
+ }
54650
+ function subscribeLogEvents(subscriber) {
54651
+ subscribers.add(subscriber);
54652
+ return () => {
54653
+ subscribers.delete(subscriber);
54654
+ };
54655
+ }
54656
+
54657
+ // src/network-access.ts
54658
+ var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
54659
+ function parseIpv4(address) {
54660
+ const parts = address.split(".");
54661
+ if (parts.length !== 4)
54662
+ return null;
54663
+ const octets = parts.map((part) => {
54664
+ if (!/^\d{1,3}$/.test(part))
54665
+ return Number.NaN;
54666
+ const value = Number.parseInt(part, 10);
54667
+ return value >= 0 && value <= 255 ? value : Number.NaN;
54668
+ });
54669
+ return octets.every(Number.isFinite) ? octets : null;
54670
+ }
54671
+ function normalizeIpAddress(raw2) {
54672
+ let address = raw2.trim().toLowerCase();
54673
+ if (address.startsWith("[")) {
54674
+ const end = address.indexOf("]");
54675
+ if (end !== -1)
54676
+ address = address.slice(1, end);
54677
+ }
54678
+ const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
54679
+ if (mappedIpv4) {
54680
+ return mappedIpv4[1];
54681
+ }
54682
+ return address;
54683
+ }
54684
+ function isLoopbackAddress(raw2) {
54685
+ if (!raw2)
54686
+ return false;
54687
+ const address = normalizeIpAddress(raw2);
54688
+ const ipv43 = parseIpv4(address);
54689
+ if (ipv43)
54690
+ return ipv43[0] === 127;
54691
+ return address === "::1";
54692
+ }
54693
+ function isLanAddress(raw2) {
54694
+ if (!raw2)
54695
+ return false;
54696
+ const address = normalizeIpAddress(raw2);
54697
+ const ipv43 = parseIpv4(address);
54698
+ if (ipv43) {
54699
+ const [a, b] = ipv43;
54700
+ return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
54701
+ }
54702
+ return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
54703
+ }
54704
+ function decideNetworkAccess(serverConfig, rawRemoteAddress) {
54705
+ const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
54706
+ if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
54707
+ return { allowed: true, remoteAddress };
54708
+ }
54709
+ const lanEnabled = serverConfig?.lanAccess?.enabled === true;
54710
+ if (!lanEnabled) {
54711
+ return { allowed: false, remoteAddress, reason: "lan-disabled" };
54712
+ }
54713
+ if (!isLanAddress(remoteAddress)) {
54714
+ return { allowed: false, remoteAddress, reason: "non-lan-address" };
54715
+ }
54716
+ return { allowed: true, remoteAddress };
54717
+ }
54718
+ function getRemoteAddressFromContext(c2) {
54719
+ const env = c2.env;
54720
+ const value = env?.[REMOTE_ADDRESS_ENV_KEY];
54721
+ return typeof value === "string" && value.trim() ? value : null;
54722
+ }
54723
+ function createNetworkAccessMiddleware(store) {
54724
+ return async (c2, next) => {
54725
+ const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
54726
+ if (!decision.allowed) {
54727
+ return c2.json({
54728
+ 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",
54729
+ remoteAddress: decision.remoteAddress
54730
+ }, 403);
54731
+ }
54732
+ await next();
54733
+ };
54734
+ }
54735
+
54736
+ // src/log-realtime.ts
54737
+ var WS_PATHNAME = "/api/logs/events/ws";
54738
+ var MAX_CONNECTIONS = 64;
54739
+ var MAX_SUBSCRIPTIONS = 64;
54740
+ var MAX_CLIENT_MESSAGE_BYTES = 16 * 1024;
54741
+ var MAX_GLOBAL_EVENT_QUEUE_ITEMS = 1000;
54742
+ var MAX_GLOBAL_EVENT_QUEUE_BYTES = 16 * 1024 * 1024;
54743
+ var MAX_PENDING_BYTES_PER_CONNECTION = 1024 * 1024;
54744
+ var MAX_TOTAL_PENDING_BYTES = 32 * 1024 * 1024;
54745
+ var BACKPRESSURE_BUFFERED_BYTES = 512 * 1024;
54746
+ var HEARTBEAT_INTERVAL_MS = 15000;
54747
+ var FLUSH_BUDGET_MS = 10;
54748
+ var MAX_Q_LENGTH2 = 200;
54749
+ function jsonResponse(status, body) {
54750
+ return new Response(JSON.stringify(body), {
54751
+ status,
54752
+ headers: { "Content-Type": "application/json; charset=utf-8" }
54753
+ });
54754
+ }
54755
+ function createId(prefix) {
54756
+ return `${prefix}_${crypto.randomUUID()}`;
54757
+ }
54758
+ function estimatePublishedEventBytes(event) {
54759
+ const bodyBytes = (event.event.request_bytes ?? 0) + (event.event.response_bytes ?? 0);
54760
+ const streamBytes = event.event.stream_bytes ?? 0;
54761
+ return 1024 + Math.min(64 * 1024, Math.max(0, bodyBytes + streamBytes));
54762
+ }
54763
+ function byteLength(text2) {
54764
+ return Buffer.byteLength(text2);
54765
+ }
54766
+ function isSocketOpen(ws) {
54767
+ return typeof ws.readyState !== "number" || ws.readyState === 1;
54768
+ }
54769
+ function bufferedAmount(ws) {
54770
+ return typeof ws.bufferedAmount === "number" ? ws.bufferedAmount : 0;
54771
+ }
54772
+ function parseClientMessage(raw2) {
54773
+ let text2;
54774
+ if (typeof raw2 === "string") {
54775
+ text2 = raw2;
54776
+ } else {
54777
+ text2 = new TextDecoder().decode(raw2);
54778
+ }
54779
+ if (byteLength(text2) > MAX_CLIENT_MESSAGE_BYTES) {
54780
+ throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u8FC7\u5927");
54781
+ }
54782
+ const parsed = JSON.parse(text2);
54783
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
54784
+ throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
54785
+ }
54786
+ const message = parsed;
54787
+ if (message.type !== "subscribe" && message.type !== "unsubscribe" && message.type !== "ping") {
54788
+ throw new Error("\u4E0D\u652F\u6301\u7684\u5B9E\u65F6\u65E5\u5FD7\u6D88\u606F\u7C7B\u578B");
54789
+ }
54790
+ return message;
54791
+ }
54792
+ function parseStringValue(value) {
54793
+ if (typeof value !== "string")
54794
+ return;
54795
+ const trimmed = value.trim();
54796
+ return trimmed || undefined;
54797
+ }
54798
+ function parseStringArrayValue(value) {
54799
+ if (Array.isArray(value)) {
54800
+ return value.flatMap((item) => typeof item === "string" ? parseCommaSeparated(item) : []);
54801
+ }
54802
+ if (typeof value === "string")
54803
+ return parseCommaSeparated(value);
54804
+ return [];
54805
+ }
54806
+ function parseBooleanValue(value) {
54807
+ if (typeof value === "boolean")
54808
+ return value;
54809
+ if (typeof value === "string")
54810
+ return parseBooleanFlag(value);
54811
+ return null;
54812
+ }
54813
+ function createQueryHash(value) {
54814
+ const text2 = JSON.stringify(value);
54815
+ let hash2 = 2166136261;
54816
+ for (let i = 0;i < text2.length; i += 1) {
54817
+ hash2 ^= text2.charCodeAt(i);
54818
+ hash2 = Math.imul(hash2, 16777619);
54819
+ }
54820
+ return `q_${(hash2 >>> 0).toString(36)}`;
54821
+ }
54822
+ function parseValidatedArray(value, validate, errorMessage) {
54823
+ const raw2 = parseStringArrayValue(value);
54824
+ const parsed = raw2.filter(validate);
54825
+ if (parsed.length !== raw2.length) {
54826
+ throw new Error(errorMessage);
54827
+ }
54828
+ return parsed;
54829
+ }
54830
+ function compileRealtimeQuery(input) {
54831
+ const query = input ?? {};
54832
+ const nowMs = Date.now();
54833
+ const windowRaw = parseStringValue(query.window) ?? "24h";
54834
+ if (!isLogQueryWindow(windowRaw)) {
54835
+ throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
54836
+ }
54837
+ const from = parseStringValue(query.from);
54838
+ const to = parseStringValue(query.to);
54839
+ const range = from || to ? {
54840
+ type: "fixed",
54841
+ ...resolveLogQueryRange({ window: windowRaw, from, to, nowMs })
54842
+ } : {
54843
+ type: "window",
54844
+ window: windowRaw,
54845
+ windowMs: getLogQueryWindowMs(windowRaw)
54846
+ };
54847
+ if (range.type === "fixed" && range.toMs <= nowMs) {
54848
+ 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");
54849
+ }
54850
+ const sortRaw = parseStringValue(query.sort) ?? "time_desc";
54851
+ if (!validateSort(sortRaw)) {
54852
+ throw new Error("sort \u53C2\u6570\u4EC5\u652F\u6301 time_desc | time_asc");
54853
+ }
54854
+ const levels = parseValidatedArray(query.levels, validateLogLevel, "levels \u53C2\u6570\u4EC5\u652F\u6301 info,error");
54855
+ const statusClasses = parseValidatedArray(query.statusClass, validateStatusClass, "statusClass \u53C2\u6570\u4EC5\u652F\u6301 2xx,4xx,5xx,network_error");
54856
+ const q = (parseStringValue(query.q) ?? "").slice(0, MAX_Q_LENGTH2).toLowerCase();
54857
+ const normalized = {
54858
+ range,
54859
+ sort: sortRaw,
54860
+ levels,
54861
+ providers: parseStringArrayValue(query.provider),
54862
+ routeTypes: parseStringArrayValue(query.routeType),
54863
+ models: parseStringArrayValue(query.model),
54864
+ modelIns: parseStringArrayValue(query.modelIn),
54865
+ modelOuts: parseStringArrayValue(query.modelOut),
54866
+ users: parseStringArrayValue(query.user),
54867
+ sessions: parseStringArrayValue(query.session),
54868
+ statusClasses,
54869
+ hasError: parseBooleanValue(query.hasError),
54870
+ q
54871
+ };
54872
+ return {
54873
+ queryHash: createQueryHash(normalized),
54874
+ sort: sortRaw,
54875
+ range,
54876
+ levels: new Set(levels),
54877
+ providers: new Set(normalized.providers),
54878
+ routeTypes: new Set(normalized.routeTypes),
54879
+ models: new Set(normalized.models),
54880
+ modelIns: new Set(normalized.modelIns),
54881
+ modelOuts: new Set(normalized.modelOuts),
54882
+ users: new Set(normalized.users),
54883
+ sessions: new Set(normalized.sessions),
54884
+ statusClasses: new Set(statusClasses),
54885
+ hasError: normalized.hasError,
54886
+ q
54887
+ };
54888
+ }
54889
+ function setHas(set2, value) {
54890
+ return set2.size === 0 || set2.has(value);
54891
+ }
54892
+ function matchesCompiledQuery(query, facts, nowMs) {
54893
+ if (!facts.event.ts_start || !Number.isFinite(facts.ts))
54894
+ return false;
54895
+ const fromMs = query.range.type === "window" ? nowMs - query.range.windowMs : query.range.fromMs;
54896
+ const toMs = query.range.type === "window" ? nowMs : query.range.toMs;
54897
+ if (facts.ts < fromMs || facts.ts > toMs)
54898
+ return false;
54899
+ if (!setHas(query.levels, facts.level))
54900
+ return false;
54901
+ if (!setHas(query.providers, facts.event.provider))
54902
+ return false;
54903
+ if (!setHas(query.routeTypes, facts.event.route_type))
54904
+ return false;
54905
+ if (!setHas(query.models, facts.model))
54906
+ return false;
54907
+ if (!setHas(query.modelIns, facts.event.model_in))
54908
+ return false;
54909
+ if (!setHas(query.modelOuts, facts.event.model_out))
54910
+ return false;
54911
+ if (query.users.size > 0) {
54912
+ const matchedByRaw = facts.identity.userIdRaw ? query.users.has(facts.identity.userIdRaw) : false;
54913
+ const matchedByUserKey = facts.identity.userKey ? query.users.has(facts.identity.userKey) : false;
54914
+ if (!matchedByRaw && !matchedByUserKey)
54915
+ return false;
54916
+ }
54917
+ if (query.sessions.size > 0) {
54918
+ if (!facts.identity.sessionId || !query.sessions.has(facts.identity.sessionId))
54919
+ return false;
54920
+ }
54921
+ if (!setHas(query.statusClasses, facts.statusClass))
54922
+ return false;
54923
+ if (query.hasError !== null && query.hasError !== facts.hasError)
54924
+ return false;
54925
+ return !query.q || facts.keywordText.includes(query.q);
54926
+ }
54927
+ function createLogRealtimeRuntime(options) {
54928
+ const connections = new Map;
54929
+ const subscriptions = new Map;
54930
+ const eventQueue = [];
54931
+ let tailUnsubscribe = null;
54932
+ let flushTimer = null;
54933
+ let heartbeatTimer = null;
54934
+ let eventQueueBytes = 0;
54935
+ let totalPendingBytes = 0;
54936
+ let droppedUpstreamEvents = 0;
54937
+ const sendText = (connection, text2) => {
54938
+ if (!isSocketOpen(connection.ws))
54939
+ return false;
54940
+ const bytes = byteLength(text2);
54941
+ const shouldQueue = connection.queue.length > 0 || bufferedAmount(connection.ws) > BACKPRESSURE_BUFFERED_BYTES;
54942
+ if (!shouldQueue) {
54943
+ try {
54944
+ connection.ws.send(text2);
54945
+ return true;
54946
+ } catch {
54947
+ removeConnection(connection.id, "send-failed");
54948
+ return false;
54949
+ }
54950
+ }
54951
+ if (connection.pendingBytes + bytes > MAX_PENDING_BYTES_PER_CONNECTION || totalPendingBytes + bytes > MAX_TOTAL_PENDING_BYTES) {
54952
+ connection.droppedOutbound += 1;
54953
+ if (connection.droppedOutbound >= 3) {
54954
+ removeConnection(connection.id, "slow-client");
54955
+ }
54956
+ return false;
54957
+ }
54958
+ connection.queue.push({ text: text2, bytes });
54959
+ connection.pendingBytes += bytes;
54960
+ totalPendingBytes += bytes;
54961
+ return true;
54962
+ };
54963
+ const sendMessage = (connection, message) => {
54964
+ return sendText(connection, JSON.stringify(message));
54965
+ };
54966
+ const flushConnectionQueue = (connection) => {
54967
+ if (!isSocketOpen(connection.ws))
54968
+ return;
54969
+ while (connection.queue.length > 0 && bufferedAmount(connection.ws) <= BACKPRESSURE_BUFFERED_BYTES) {
54970
+ const item = connection.queue.shift();
54971
+ if (!item)
54972
+ break;
54973
+ connection.pendingBytes -= item.bytes;
54974
+ totalPendingBytes -= item.bytes;
54975
+ try {
54976
+ connection.ws.send(item.text);
54977
+ } catch {
54978
+ removeConnection(connection.id, "send-failed");
54979
+ return;
54980
+ }
54981
+ }
54982
+ };
54983
+ const sendError = (connection, error48, requestId) => {
54984
+ sendMessage(connection, { type: "error", requestId, error: error48 });
54985
+ };
54986
+ const ensureTailSubscription = () => {
54987
+ if (tailUnsubscribe || subscriptions.size === 0)
54988
+ return;
54989
+ tailUnsubscribe = subscribeLogEvents((event) => {
54990
+ if (subscriptions.size === 0)
54991
+ return;
54992
+ const bytes = estimatePublishedEventBytes(event);
54993
+ while (eventQueue.length >= MAX_GLOBAL_EVENT_QUEUE_ITEMS || eventQueueBytes + bytes > MAX_GLOBAL_EVENT_QUEUE_BYTES) {
54994
+ const dropped = eventQueue.shift();
54995
+ if (!dropped)
54996
+ break;
54997
+ eventQueueBytes -= dropped.bytes;
54998
+ droppedUpstreamEvents += 1;
54999
+ }
55000
+ eventQueue.push({ event, bytes });
55001
+ eventQueueBytes += bytes;
55002
+ scheduleFlush();
55003
+ });
55004
+ };
55005
+ const maybeStopTailSubscription = () => {
55006
+ if (subscriptions.size > 0)
55007
+ return;
55008
+ tailUnsubscribe?.();
55009
+ tailUnsubscribe = null;
55010
+ eventQueue.length = 0;
55011
+ eventQueueBytes = 0;
55012
+ droppedUpstreamEvents = 0;
55013
+ };
55014
+ const sendOverflow = (subscription, dropped, message) => {
55015
+ const connection = connections.get(subscription.connectionId);
55016
+ if (!connection)
55017
+ return;
55018
+ sendMessage(connection, {
55019
+ type: "overflow",
55020
+ subscriptionId: subscription.id,
55021
+ dropped,
55022
+ message
55023
+ });
55024
+ };
55025
+ const removeSubscription = (subscriptionId, reason, notify2 = true) => {
55026
+ const subscription = subscriptions.get(subscriptionId);
55027
+ if (!subscription)
55028
+ return;
55029
+ subscriptions.delete(subscriptionId);
55030
+ const connection = connections.get(subscription.connectionId);
55031
+ if (connection) {
55032
+ connection.subscriptions.delete(subscriptionId);
55033
+ if (notify2) {
55034
+ sendMessage(connection, {
55035
+ type: "unsubscribed",
55036
+ subscriptionId,
55037
+ reason
55038
+ });
55039
+ }
55040
+ }
55041
+ maybeStopTailSubscription();
55042
+ };
55043
+ function removeConnection(connectionId, reason) {
55044
+ const connection = connections.get(connectionId);
55045
+ if (!connection)
55046
+ return;
55047
+ connections.delete(connectionId);
55048
+ for (const subscriptionId of Array.from(connection.subscriptions)) {
55049
+ removeSubscription(subscriptionId, reason, false);
55050
+ }
55051
+ for (const item of connection.queue) {
55052
+ totalPendingBytes -= item.bytes;
55053
+ }
55054
+ connection.queue.length = 0;
55055
+ connection.pendingBytes = 0;
55056
+ try {
55057
+ connection.ws.close(1001, reason);
55058
+ } catch {}
55059
+ if (connections.size === 0 && heartbeatTimer) {
55060
+ clearInterval(heartbeatTimer);
55061
+ heartbeatTimer = null;
55062
+ }
55063
+ maybeStopTailSubscription();
55064
+ }
55065
+ const pruneExpiredSubscriptions = (nowMs) => {
55066
+ for (const subscription of Array.from(subscriptions.values())) {
55067
+ const range = subscription.query.range;
55068
+ if (range.type === "fixed" && range.toMs <= nowMs) {
55069
+ removeSubscription(subscription.id, "expired");
55070
+ }
55071
+ }
55072
+ };
55073
+ const flushEvents = () => {
55074
+ flushTimer = null;
55075
+ if (subscriptions.size === 0) {
55076
+ eventQueue.length = 0;
55077
+ eventQueueBytes = 0;
55078
+ droppedUpstreamEvents = 0;
55079
+ maybeStopTailSubscription();
55080
+ return;
55081
+ }
55082
+ const startedAt = Date.now();
55083
+ const nowMs = Date.now();
55084
+ pruneExpiredSubscriptions(nowMs);
55085
+ if (droppedUpstreamEvents > 0) {
55086
+ const dropped = droppedUpstreamEvents;
55087
+ droppedUpstreamEvents = 0;
55088
+ for (const subscription of subscriptions.values()) {
55089
+ 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`);
55090
+ }
55091
+ }
55092
+ while (eventQueue.length > 0 && Date.now() - startedAt < FLUSH_BUDGET_MS) {
55093
+ const queued = eventQueue.shift();
55094
+ if (!queued)
55095
+ break;
55096
+ eventQueueBytes -= queued.bytes;
55097
+ const facts = extractLogEventFacts(queued.event.event);
55098
+ let summary = null;
55099
+ for (const subscription of subscriptions.values()) {
55100
+ if (!matchesCompiledQuery(subscription.query, facts, Date.now()))
55101
+ continue;
55102
+ const connection = connections.get(subscription.connectionId);
55103
+ if (!connection)
55104
+ continue;
55105
+ summary ??= createLogEventSummaryFromFacts(facts, {
55106
+ id: queued.event.id,
55107
+ date: queued.event.date,
55108
+ line: null
55109
+ });
55110
+ const sent = sendMessage(connection, {
55111
+ type: "log.event",
55112
+ subscriptionId: subscription.id,
55113
+ item: summary
55114
+ });
55115
+ if (!sent) {
55116
+ 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");
55117
+ }
55118
+ }
55119
+ }
55120
+ for (const connection of connections.values()) {
55121
+ flushConnectionQueue(connection);
55122
+ }
55123
+ if (eventQueue.length > 0)
55124
+ scheduleFlush();
55125
+ };
55126
+ function scheduleFlush() {
55127
+ if (flushTimer)
55128
+ return;
55129
+ flushTimer = setTimeout(flushEvents, 0);
55130
+ flushTimer.unref?.();
55131
+ }
55132
+ const ensureHeartbeat = () => {
55133
+ if (heartbeatTimer)
55134
+ return;
55135
+ heartbeatTimer = setInterval(() => {
55136
+ const ts = new Date().toISOString();
55137
+ pruneExpiredSubscriptions(Date.now());
55138
+ for (const connection of connections.values()) {
55139
+ sendMessage(connection, { type: "pong", ts });
55140
+ flushConnectionQueue(connection);
55141
+ }
55142
+ }, HEARTBEAT_INTERVAL_MS);
55143
+ heartbeatTimer.unref?.();
55144
+ };
55145
+ const subscribeConnection = (connection, requestId, query) => {
55146
+ if (subscriptions.size >= MAX_SUBSCRIPTIONS && connection.subscriptions.size === 0) {
55147
+ sendError(connection, "\u5B9E\u65F6\u65E5\u5FD7\u8BA2\u9605\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", requestId);
55148
+ return;
55149
+ }
55150
+ let compiled;
55151
+ try {
55152
+ compiled = compileRealtimeQuery(query);
55153
+ } catch (err) {
55154
+ sendError(connection, err instanceof Error ? err.message : String(err), requestId);
55155
+ return;
55156
+ }
55157
+ for (const subscriptionId2 of Array.from(connection.subscriptions)) {
55158
+ removeSubscription(subscriptionId2, "replaced");
55159
+ }
55160
+ const subscriptionId = createId("sub");
55161
+ const subscription = {
55162
+ id: subscriptionId,
55163
+ connectionId: connection.id,
55164
+ query: compiled
55165
+ };
55166
+ subscriptions.set(subscriptionId, subscription);
55167
+ connection.subscriptions.add(subscriptionId);
55168
+ ensureTailSubscription();
55169
+ sendMessage(connection, {
55170
+ type: "subscribed",
55171
+ requestId: requestId ?? null,
55172
+ subscriptionId,
55173
+ queryHash: compiled.queryHash,
55174
+ now: new Date().toISOString()
55175
+ });
55176
+ };
55177
+ const openConnection = (ws) => {
55178
+ const data = ws.data;
55179
+ const connection = {
55180
+ id: data.connectionId,
55181
+ remoteAddress: data.remoteAddress,
55182
+ ws,
55183
+ subscriptions: new Set,
55184
+ queue: [],
55185
+ pendingBytes: 0,
55186
+ droppedOutbound: 0
55187
+ };
55188
+ connections.set(connection.id, connection);
55189
+ ensureHeartbeat();
55190
+ sendMessage(connection, {
55191
+ type: "ready",
55192
+ connectionId: connection.id,
55193
+ now: new Date().toISOString()
55194
+ });
55195
+ };
55196
+ const handleMessage = (ws, raw2) => {
55197
+ const connection = connections.get(ws.data.connectionId);
55198
+ if (!connection)
55199
+ return;
55200
+ let message;
55201
+ try {
55202
+ message = parseClientMessage(raw2);
55203
+ } catch (err) {
55204
+ sendError(connection, err instanceof Error ? err.message : String(err));
55205
+ return;
55206
+ }
55207
+ if (message.type === "ping") {
55208
+ sendMessage(connection, { type: "pong", ts: message.ts ?? new Date().toISOString() });
55209
+ return;
55210
+ }
55211
+ if (message.type === "unsubscribe") {
55212
+ if (message.subscriptionId) {
55213
+ removeSubscription(message.subscriptionId, "client-unsubscribe");
55214
+ } else {
55215
+ for (const subscriptionId of Array.from(connection.subscriptions)) {
55216
+ removeSubscription(subscriptionId, "client-unsubscribe");
55217
+ }
55218
+ }
55219
+ return;
55220
+ }
55221
+ subscribeConnection(connection, message.requestId, message.query);
55222
+ };
55223
+ return {
55224
+ pathname: WS_PATHNAME,
55225
+ upgrade: (request, server, remoteAddress) => {
55226
+ const url2 = new URL(request.url);
55227
+ if (url2.pathname !== WS_PATHNAME)
55228
+ return { handled: false };
55229
+ const upgradeHeader = request.headers.get("upgrade")?.toLowerCase();
55230
+ if (upgradeHeader !== "websocket") {
55231
+ return {
55232
+ handled: true,
55233
+ response: jsonResponse(400, { error: "\u9700\u8981 WebSocket Upgrade" })
55234
+ };
55235
+ }
55236
+ const decision = decideNetworkAccess(options.store.get().server, remoteAddress);
55237
+ if (!decision.allowed) {
55238
+ return {
55239
+ handled: true,
55240
+ response: jsonResponse(403, {
55241
+ 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",
55242
+ remoteAddress: decision.remoteAddress
55243
+ })
55244
+ };
55245
+ }
55246
+ if (connections.size >= MAX_CONNECTIONS) {
55247
+ return {
55248
+ handled: true,
55249
+ response: jsonResponse(503, { error: "\u5B9E\u65F6\u65E5\u5FD7\u8FDE\u63A5\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" })
55250
+ };
55251
+ }
55252
+ const upgraded = server.upgrade(request, {
55253
+ data: {
55254
+ kind: "log-realtime",
55255
+ connectionId: createId("conn"),
55256
+ remoteAddress: decision.remoteAddress
55257
+ }
55258
+ });
55259
+ return upgraded ? { handled: true, upgraded: true } : { handled: true, response: jsonResponse(400, { error: "WebSocket Upgrade \u5931\u8D25" }) };
55260
+ },
55261
+ websocket: {
55262
+ open: openConnection,
55263
+ message: handleMessage,
55264
+ drain: (ws) => {
55265
+ const connection = connections.get(ws.data.connectionId);
55266
+ if (connection)
55267
+ flushConnectionQueue(connection);
55268
+ },
55269
+ close: (ws) => {
55270
+ removeConnection(ws.data.connectionId, "closed");
55271
+ }
55272
+ },
55273
+ dispose: () => {
55274
+ if (flushTimer) {
55275
+ clearTimeout(flushTimer);
55276
+ flushTimer = null;
55277
+ }
55278
+ if (heartbeatTimer) {
55279
+ clearInterval(heartbeatTimer);
55280
+ heartbeatTimer = null;
55281
+ }
55282
+ tailUnsubscribe?.();
55283
+ tailUnsubscribe = null;
55284
+ for (const connectionId of Array.from(connections.keys())) {
55285
+ removeConnection(connectionId, "server-dispose");
55286
+ }
55287
+ eventQueue.length = 0;
55288
+ eventQueueBytes = 0;
55289
+ totalPendingBytes = 0;
55290
+ droppedUpstreamEvents = 0;
55291
+ }
55292
+ };
55293
+ }
55294
+
54406
55295
  // src/log-sessions.ts
54407
- import { createReadStream as createReadStream4, existsSync as existsSync6 } from "fs";
54408
- import { join as join6 } from "path";
55296
+ import { createReadStream as createReadStream4, existsSync as existsSync7 } from "fs";
55297
+ import { join as join8 } from "path";
54409
55298
  import { createInterface as createInterface3 } from "readline";
54410
55299
  var MAX_LINES_SCANNED3 = 250000;
54411
- var MAX_Q_LENGTH2 = 200;
55300
+ var MAX_Q_LENGTH3 = 200;
54412
55301
  function toDayStart4(ms) {
54413
55302
  const date5 = new Date(ms);
54414
55303
  return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
@@ -54427,7 +55316,7 @@ function normalizeInput(input) {
54427
55316
  toMs: input.toMs,
54428
55317
  users: (input.users ?? []).map((item) => item.trim()).filter(Boolean),
54429
55318
  sessions: (input.sessions ?? []).map((item) => item.trim()).filter(Boolean),
54430
- q: qRaw.length > MAX_Q_LENGTH2 ? qRaw.slice(0, MAX_Q_LENGTH2) : qRaw
55319
+ q: qRaw.length > MAX_Q_LENGTH3 ? qRaw.slice(0, MAX_Q_LENGTH3) : qRaw
54431
55320
  };
54432
55321
  }
54433
55322
  function incrementCount(map2, key2) {
@@ -54526,8 +55415,8 @@ async function queryLogSessions(context2, input) {
54526
55415
  return createEmptyResult(normalized.fromMs, normalized.toMs);
54527
55416
  }
54528
55417
  const baseDir = resolveLogBaseDir(context2.logConfig);
54529
- const eventsDir = join6(baseDir, "events");
54530
- if (!existsSync6(eventsDir)) {
55418
+ const eventsDir = join8(baseDir, "events");
55419
+ if (!existsSync7(eventsDir)) {
54531
55420
  return createEmptyResult(normalized.fromMs, normalized.toMs);
54532
55421
  }
54533
55422
  const usersMap = new Map;
@@ -54545,8 +55434,8 @@ async function queryLogSessions(context2, input) {
54545
55434
  truncated = true;
54546
55435
  break;
54547
55436
  }
54548
- const filePath = join6(eventsDir, `${date5}.jsonl`);
54549
- if (!existsSync6(filePath))
55437
+ const filePath = join8(eventsDir, `${date5}.jsonl`);
55438
+ if (!existsSync7(filePath))
54550
55439
  continue;
54551
55440
  scannedFiles += 1;
54552
55441
  const stream = createReadStream4(filePath, { encoding: "utf-8" });
@@ -54654,8 +55543,8 @@ async function queryLogSessions(context2, input) {
54654
55543
  }
54655
55544
 
54656
55545
  // src/log-storage.ts
54657
- import { existsSync as existsSync7, promises as fsPromises } from "fs";
54658
- import { join as join7 } from "path";
55546
+ import { existsSync as existsSync8, promises as fsPromises } from "fs";
55547
+ import { join as join9 } from "path";
54659
55548
  var cachedStorage = null;
54660
55549
  var calculationPromise = null;
54661
55550
  var lastCalculationTime = 0;
@@ -54663,7 +55552,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
54663
55552
  var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
54664
55553
  var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
54665
55554
  async function calculateDirSize(dirPath) {
54666
- if (!existsSync7(dirPath)) {
55555
+ if (!existsSync8(dirPath)) {
54667
55556
  return { bytes: 0, fileCount: 0 };
54668
55557
  }
54669
55558
  let bytes = 0;
@@ -54672,7 +55561,7 @@ async function calculateDirSize(dirPath) {
54672
55561
  try {
54673
55562
  const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
54674
55563
  for (const entry of entries) {
54675
- const fullPath = join7(currentPath, entry.name);
55564
+ const fullPath = join9(currentPath, entry.name);
54676
55565
  if (entry.isDirectory()) {
54677
55566
  await walk(fullPath);
54678
55567
  } else if (entry.isFile()) {
@@ -54703,8 +55592,8 @@ async function doCalculateStorage(logConfig) {
54703
55592
  }
54704
55593
  const baseDir = resolveLogBaseDir(logConfig);
54705
55594
  const [eventsResult, streamsResult] = await Promise.all([
54706
- calculateDirSize(join7(baseDir, "events")),
54707
- calculateDirSize(join7(baseDir, "streams"))
55595
+ calculateDirSize(join9(baseDir, "events")),
55596
+ calculateDirSize(join9(baseDir, "streams"))
54708
55597
  ]);
54709
55598
  const indexResult = await calculateIndexSize(baseDir);
54710
55599
  return {
@@ -54718,7 +55607,7 @@ async function doCalculateStorage(logConfig) {
54718
55607
  };
54719
55608
  }
54720
55609
  async function calculateIndexSize(baseDir) {
54721
- if (!existsSync7(baseDir)) {
55610
+ if (!existsSync8(baseDir)) {
54722
55611
  return { bytes: 0, fileCount: 0 };
54723
55612
  }
54724
55613
  let bytes = 0;
@@ -54728,7 +55617,7 @@ async function calculateIndexSize(baseDir) {
54728
55617
  for (const entry of entries) {
54729
55618
  if (!entry.isFile() || !entry.name.startsWith("logs-index.sqlite"))
54730
55619
  continue;
54731
- const stats = await fsPromises.stat(join7(baseDir, entry.name));
55620
+ const stats = await fsPromises.stat(join9(baseDir, entry.name));
54732
55621
  bytes += stats.size;
54733
55622
  fileCount += 1;
54734
55623
  }
@@ -54779,33 +55668,17 @@ function startLogStorageBackgroundTask(logConfig) {
54779
55668
  };
54780
55669
  }
54781
55670
 
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
55671
  // src/logger.ts
54799
55672
  import {
54800
55673
  appendFileSync,
54801
55674
  closeSync as closeSync2,
54802
- existsSync as existsSync8,
54803
- mkdirSync as mkdirSync3,
55675
+ existsSync as existsSync9,
55676
+ mkdirSync as mkdirSync4,
54804
55677
  openSync as openSync2,
54805
55678
  statSync as statSync3,
54806
55679
  writeSync
54807
55680
  } from "fs";
54808
- import { join as join8 } from "path";
55681
+ import { join as join10 } from "path";
54809
55682
  class Logger {
54810
55683
  baseDir;
54811
55684
  eventsDir;
@@ -54820,8 +55693,8 @@ class Logger {
54820
55693
  this._bodyPolicy = config2.bodyPolicy ?? "off";
54821
55694
  this._streamsEnabled = config2.streams?.enabled !== false;
54822
55695
  this.maxStreamBytes = config2.streams?.maxBytesPerRequest ?? 10 * 1024 * 1024;
54823
- this.eventsDir = join8(baseDir, "events");
54824
- this.streamsDir = join8(baseDir, "streams");
55696
+ this.eventsDir = join10(baseDir, "events");
55697
+ this.streamsDir = join10(baseDir, "streams");
54825
55698
  if (this._enabled)
54826
55699
  this.ensureDirs();
54827
55700
  }
@@ -54833,14 +55706,14 @@ class Logger {
54833
55706
  }
54834
55707
  ensureDirs() {
54835
55708
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
54836
- if (!existsSync8(dir))
54837
- mkdirSync3(dir, { recursive: true });
55709
+ if (!existsSync9(dir))
55710
+ mkdirSync4(dir, { recursive: true });
54838
55711
  }
54839
55712
  }
54840
55713
  ensureStreamDateDir(dateStr) {
54841
- const dir = join8(this.streamsDir, dateStr);
54842
- if (!existsSync8(dir))
54843
- mkdirSync3(dir, { recursive: true });
55714
+ const dir = join10(this.streamsDir, dateStr);
55715
+ if (!existsSync9(dir))
55716
+ mkdirSync4(dir, { recursive: true });
54844
55717
  return dir;
54845
55718
  }
54846
55719
  writeEvent(event) {
@@ -54850,8 +55723,8 @@ class Logger {
54850
55723
  const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
54851
55724
  this.ensureDirs();
54852
55725
  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;
55726
+ const filePath = join10(this.eventsDir, `${dateStr}.jsonl`);
55727
+ const offset = existsSync9(filePath) ? statSync3(filePath).size : 0;
54855
55728
  const line2 = `${JSON.stringify(enrichedEvent)}
54856
55729
  `;
54857
55730
  appendFileSync(filePath, line2);
@@ -54880,7 +55753,7 @@ class Logger {
54880
55753
  let fd;
54881
55754
  try {
54882
55755
  const dir = this.ensureStreamDateDir(dateStr);
54883
- filePath = join8(dir, `${requestId}.sse.raw`);
55756
+ filePath = join10(dir, `${requestId}.sse.raw`);
54884
55757
  fd = openSync2(filePath, "a");
54885
55758
  } catch (err) {
54886
55759
  console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
@@ -54970,85 +55843,6 @@ function normalizeUrl(rawUrl) {
54970
55843
  return rawUrl;
54971
55844
  }
54972
55845
 
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
55846
  // src/openapi.ts
55053
55847
  var openAPISpec = {
55054
55848
  openapi: "3.0.0",
@@ -56155,7 +56949,7 @@ var openAPISpec = {
56155
56949
  // src/plugin-loader.ts
56156
56950
  import { mkdtemp, rm, writeFile } from "fs/promises";
56157
56951
  import { tmpdir } from "os";
56158
- import { join as join9, resolve as resolve6 } from "path";
56952
+ import { join as join11, resolve as resolve7 } from "path";
56159
56953
  function isLocalPath(pkg) {
56160
56954
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
56161
56955
  }
@@ -56178,7 +56972,7 @@ var remoteTmpDir = null;
56178
56972
  var remoteTmpFiles = [];
56179
56973
  async function ensureRemoteTmpDir() {
56180
56974
  if (!remoteTmpDir) {
56181
- remoteTmpDir = await mkdtemp(join9(tmpdir(), "local-router-plugins-"));
56975
+ remoteTmpDir = await mkdtemp(join11(tmpdir(), "local-router-plugins-"));
56182
56976
  }
56183
56977
  return remoteTmpDir;
56184
56978
  }
@@ -56191,7 +56985,7 @@ async function fetchRemotePlugin(url2) {
56191
56985
  const ext = inferExtension(url2, response.headers.get("content-type"));
56192
56986
  const dir = await ensureRemoteTmpDir();
56193
56987
  const fileName = `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
56194
- const filePath = join9(dir, fileName);
56988
+ const filePath = join11(dir, fileName);
56195
56989
  await writeFile(filePath, content, "utf-8");
56196
56990
  remoteTmpFiles.push(filePath);
56197
56991
  return filePath;
@@ -56211,7 +57005,7 @@ async function importPlugin(pkg, configDir) {
56211
57005
  const localPath = await fetchRemotePlugin(pkg);
56212
57006
  modulePath = `${localPath}?t=${Date.now()}`;
56213
57007
  } else if (isLocalPath(pkg)) {
56214
- const absolutePath = resolve6(configDir, pkg);
57008
+ const absolutePath = resolve7(configDir, pkg);
56215
57009
  modulePath = `${absolutePath}?t=${Date.now()}`;
56216
57010
  } else {
56217
57011
  modulePath = pkg;
@@ -56985,7 +57779,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
56985
57779
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
56986
57780
  const CRYPTO_SESSION_MAX = 512;
56987
57781
  const schemaPath = getBundledSchemaPath();
56988
- const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
57782
+ const schemaJson = JSON.parse(readFileSync6(schemaPath, "utf-8"));
56989
57783
  const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
56990
57784
  for (const [id, record2] of Array.from(cryptoSessions.entries())) {
56991
57785
  if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
@@ -57125,6 +57919,45 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57125
57919
  return c2.json({ error: `\u8BFB\u53D6\u914D\u7F6E schema \u5931\u8D25: ${err instanceof Error ? err.message : err}` }, 500);
57126
57920
  }
57127
57921
  });
57922
+ api2.get("/autostart", async (c2) => {
57923
+ try {
57924
+ const manager = createAutostartManager();
57925
+ const systemInstalled = await manager.isInstalled();
57926
+ const config2 = store.get();
57927
+ return c2.json({
57928
+ enabled: config2.server?.autostart ?? false,
57929
+ systemInstalled,
57930
+ platform: manager.platform,
57931
+ servicePath: manager.getServicePath()
57932
+ });
57933
+ } catch (err) {
57934
+ return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
57935
+ }
57936
+ });
57937
+ api2.put("/autostart", async (c2) => {
57938
+ try {
57939
+ const { enabled } = await c2.req.json();
57940
+ if (typeof enabled !== "boolean") {
57941
+ return c2.json({ error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" }, 400);
57942
+ }
57943
+ const manager = createAutostartManager();
57944
+ if (manager.platform === "unsupported") {
57945
+ return c2.json({ error: "\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8" }, 400);
57946
+ }
57947
+ if (enabled) {
57948
+ const { execPath, args } = getAutostartExecArgs();
57949
+ await manager.install({ execPath, args, label: "com.lakphy.local-router" });
57950
+ } else {
57951
+ await manager.uninstall();
57952
+ }
57953
+ const config2 = store.get();
57954
+ const updated = { ...config2, server: { ...config2.server, autostart: enabled } };
57955
+ store.save(updated);
57956
+ return c2.json({ ok: true, enabled });
57957
+ } catch (err) {
57958
+ return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
57959
+ }
57960
+ });
57128
57961
  api2.post("/chat/proxy", async (c2) => {
57129
57962
  let body;
57130
57963
  try {
@@ -57590,7 +58423,7 @@ async function createApp(store, options) {
57590
58423
  }
57591
58424
  const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
57592
58425
  options?.registerCleanup?.(stopLogStorageTask);
57593
- const configDir = dirname2(resolve7(store.getPath()));
58426
+ const configDir = dirname3(resolve8(store.getPath()));
57594
58427
  const pluginManager = new PluginManager(configDir);
57595
58428
  const reloadResult = await pluginManager.reloadAll(config2.providers);
57596
58429
  if (!reloadResult.ok) {
@@ -57637,28 +58470,56 @@ async function createApp(store, options) {
57637
58470
  }
57638
58471
  return app;
57639
58472
  }
57640
- async function createDefaultAppFromProcessArgs() {
57641
- const configPath = parseConfigPath();
58473
+ async function createAppRuntimeFromConfigPath(configPath, listen) {
57642
58474
  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)
58475
+ const cleanups = [];
58476
+ const app = await createApp(store, {
58477
+ listen,
58478
+ registerCleanup: (cleanup) => {
58479
+ cleanups.push(cleanup);
57647
58480
  }
57648
58481
  });
58482
+ const logRealtime = createLogRealtimeRuntime({ store });
58483
+ return {
58484
+ app,
58485
+ logRealtime,
58486
+ dispose: () => {
58487
+ logRealtime.dispose();
58488
+ for (const cleanup of cleanups.reverse()) {
58489
+ try {
58490
+ cleanup();
58491
+ } catch {}
58492
+ }
58493
+ }
58494
+ };
58495
+ }
58496
+ async function createDefaultAppRuntimeFromProcessArgs() {
58497
+ const configPath = parseConfigPath();
58498
+ return createAppRuntimeFromConfigPath(configPath, {
58499
+ host: process.env.HOST ?? "0.0.0.0",
58500
+ port: Number.parseInt(process.env.PORT ?? "4099", 10)
58501
+ });
57649
58502
  }
57650
58503
 
57651
58504
  // src/entry.ts
57652
- var app = await createDefaultAppFromProcessArgs();
58505
+ var runtime = await createDefaultAppRuntimeFromProcessArgs();
57653
58506
  var entry_default = {
57654
58507
  hostname: process.env.HOST ?? "0.0.0.0",
57655
58508
  port: Number.parseInt(process.env.PORT ?? "4099", 10),
57656
58509
  fetch(request, server) {
57657
58510
  const remoteAddress = server.requestIP(request)?.address ?? null;
57658
- return app.fetch(request, {
58511
+ const realtimeUpgrade = runtime.logRealtime.upgrade(request, server, remoteAddress);
58512
+ if (realtimeUpgrade.handled) {
58513
+ if (realtimeUpgrade.upgraded) {
58514
+ return;
58515
+ }
58516
+ return realtimeUpgrade.response ?? new Response("WebSocket Upgrade failed", { status: 400 });
58517
+ }
58518
+ return runtime.app.fetch(request, {
57659
58519
  [REMOTE_ADDRESS_ENV_KEY]: remoteAddress
57660
58520
  });
57661
- }
58521
+ },
58522
+ websocket: runtime.logRealtime.websocket
57662
58523
  };
57663
58524
  export {
57664
58525
  entry_default as default