@lakphy/local-router 0.5.5 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -9179,257 +9179,6 @@ var init_output = __esm(() => {
9179
9179
  init_global_flags();
9180
9180
  });
9181
9181
 
9182
- // src/cli/runtime.ts
9183
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
9184
- import { homedir as homedir2 } from "os";
9185
- import { join as join3, resolve as resolve2 } from "path";
9186
- function getRuntimeDirs() {
9187
- const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
9188
- const root = override?.trim() ? override.trim() : join3(homedir2(), ".local-router");
9189
- return {
9190
- root,
9191
- run: join3(root, "run"),
9192
- logs: join3(root, "logs")
9193
- };
9194
- }
9195
- function getRuntimeFiles() {
9196
- const dirs = getRuntimeDirs();
9197
- return {
9198
- pid: join3(dirs.run, "local-router.pid"),
9199
- state: join3(dirs.run, "status.json"),
9200
- daemonLog: join3(dirs.logs, "daemon.log")
9201
- };
9202
- }
9203
- function ensureRuntimeDirs() {
9204
- const dirs = getRuntimeDirs();
9205
- mkdirSync3(dirs.root, { recursive: true });
9206
- mkdirSync3(dirs.run, { recursive: true });
9207
- mkdirSync3(dirs.logs, { recursive: true });
9208
- }
9209
- function writeRuntimeState(state) {
9210
- ensureRuntimeDirs();
9211
- const files = getRuntimeFiles();
9212
- writeFileSync3(files.pid, `${state.pid}
9213
- `, "utf-8");
9214
- writeFileSync3(files.state, JSON.stringify(state, null, 2), "utf-8");
9215
- }
9216
- function readRuntimeState() {
9217
- const files = getRuntimeFiles();
9218
- if (!existsSync2(files.state)) {
9219
- return null;
9220
- }
9221
- try {
9222
- return JSON.parse(readFileSync4(files.state, "utf-8"));
9223
- } catch {
9224
- return null;
9225
- }
9226
- }
9227
- function clearRuntimeFiles() {
9228
- const files = getRuntimeFiles();
9229
- rmSync(files.pid, { force: true });
9230
- rmSync(files.state, { force: true });
9231
- }
9232
- function resolveConfigArgPath(pathValue) {
9233
- return resolve2(pathValue);
9234
- }
9235
- var init_runtime = () => {};
9236
-
9237
- // src/cli/autostart.ts
9238
- import { execSync } from "child_process";
9239
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
9240
- import { homedir as homedir3, platform } from "os";
9241
- import { dirname as dirname2, join as join4 } from "path";
9242
- function getDaemonLogPath() {
9243
- return getRuntimeDirs().logs + "/daemon.log";
9244
- }
9245
- function getLaunchAgentPath() {
9246
- return join4(homedir3(), "Library", "LaunchAgents", `${LABEL}.plist`);
9247
- }
9248
- function buildPlist(opts) {
9249
- const logPath = getDaemonLogPath();
9250
- const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
9251
- `);
9252
- return `<?xml version="1.0" encoding="UTF-8"?>
9253
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9254
- <plist version="1.0">
9255
- <dict>
9256
- <key>Label</key>
9257
- <string>${escapeXml(opts.label)}</string>
9258
- <key>ProgramArguments</key>
9259
- <array>
9260
- ${args}
9261
- </array>
9262
- <key>RunAtLoad</key>
9263
- <true/>
9264
- <key>KeepAlive</key>
9265
- <false/>
9266
- <key>StandardOutPath</key>
9267
- <string>${escapeXml(logPath)}</string>
9268
- <key>StandardErrorPath</key>
9269
- <string>${escapeXml(logPath)}</string>
9270
- </dict>
9271
- </plist>
9272
- `;
9273
- }
9274
- function escapeXml(s) {
9275
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9276
- }
9277
- function createMacosManager() {
9278
- const plistPath = getLaunchAgentPath();
9279
- return {
9280
- platform: "macos",
9281
- async isInstalled() {
9282
- return existsSync3(plistPath);
9283
- },
9284
- async install(opts) {
9285
- const dir = dirname2(plistPath);
9286
- if (!existsSync3(dir))
9287
- mkdirSync4(dir, { recursive: true });
9288
- writeFileSync4(plistPath, buildPlist(opts), "utf-8");
9289
- try {
9290
- execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
9291
- } catch {}
9292
- execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9293
- },
9294
- async uninstall() {
9295
- if (!existsSync3(plistPath))
9296
- return;
9297
- try {
9298
- execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
9299
- } catch {}
9300
- rmSync2(plistPath, { force: true });
9301
- },
9302
- getServicePath() {
9303
- return plistPath;
9304
- }
9305
- };
9306
- }
9307
- function getSystemdUnitPath() {
9308
- return join4(homedir3(), ".config", "systemd", "user", "local-router.service");
9309
- }
9310
- function buildUnit(opts) {
9311
- const logPath = getDaemonLogPath();
9312
- const execStart = [opts.execPath, ...opts.args].join(" ");
9313
- return `[Unit]
9314
- Description=Local Router API Gateway
9315
- After=network-online.target
9316
-
9317
- [Service]
9318
- Type=simple
9319
- ExecStart=${execStart}
9320
- Restart=on-failure
9321
- RestartSec=5
9322
- StandardOutput=append:${logPath}
9323
- StandardError=append:${logPath}
9324
-
9325
- [Install]
9326
- WantedBy=default.target
9327
- `;
9328
- }
9329
- function createLinuxManager() {
9330
- const unitPath = getSystemdUnitPath();
9331
- return {
9332
- platform: "linux",
9333
- async isInstalled() {
9334
- if (!existsSync3(unitPath))
9335
- return false;
9336
- try {
9337
- const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
9338
- encoding: "utf-8"
9339
- }).trim();
9340
- return out === "enabled";
9341
- } catch {
9342
- return false;
9343
- }
9344
- },
9345
- async install(opts) {
9346
- const dir = dirname2(unitPath);
9347
- if (!existsSync3(dir))
9348
- mkdirSync4(dir, { recursive: true });
9349
- writeFileSync4(unitPath, buildUnit(opts), "utf-8");
9350
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9351
- execSync("systemctl --user enable local-router", { stdio: "ignore" });
9352
- },
9353
- async uninstall() {
9354
- try {
9355
- execSync("systemctl --user disable local-router", { stdio: "ignore" });
9356
- } catch {}
9357
- rmSync2(unitPath, { force: true });
9358
- try {
9359
- execSync("systemctl --user daemon-reload", { stdio: "ignore" });
9360
- } catch {}
9361
- },
9362
- getServicePath() {
9363
- return unitPath;
9364
- }
9365
- };
9366
- }
9367
- function createWindowsManager() {
9368
- return {
9369
- platform: "windows",
9370
- async isInstalled() {
9371
- try {
9372
- execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
9373
- return true;
9374
- } catch {
9375
- return false;
9376
- }
9377
- },
9378
- async install(opts) {
9379
- const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
9380
- execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
9381
- stdio: "ignore"
9382
- });
9383
- },
9384
- async uninstall() {
9385
- try {
9386
- execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
9387
- } catch {}
9388
- },
9389
- getServicePath() {
9390
- return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
9391
- }
9392
- };
9393
- }
9394
- function createUnsupportedManager() {
9395
- return {
9396
- platform: "unsupported",
9397
- async isInstalled() {
9398
- return false;
9399
- },
9400
- async install() {
9401
- throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9402
- },
9403
- async uninstall() {
9404
- throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
9405
- },
9406
- getServicePath() {
9407
- return "";
9408
- }
9409
- };
9410
- }
9411
- function createAutostartManager() {
9412
- const p = platform();
9413
- if (p === "darwin")
9414
- return createMacosManager();
9415
- if (p === "linux")
9416
- return createLinuxManager();
9417
- if (p === "win32")
9418
- return createWindowsManager();
9419
- return createUnsupportedManager();
9420
- }
9421
- function getAutostartExecArgs() {
9422
- const script = process.argv[1] ?? "dist/cli.js";
9423
- return {
9424
- execPath: process.execPath,
9425
- args: [script, "__run-server", "--mode", "daemon"]
9426
- };
9427
- }
9428
- var LABEL = "com.lakphy.local-router", WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", WIN_REG_VALUE = "LocalRouter";
9429
- var init_autostart = __esm(() => {
9430
- init_runtime();
9431
- });
9432
-
9433
9182
  // node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
9434
9183
  function getErrorMessage(error) {
9435
9184
  if (error == null) {
@@ -27547,14 +27296,14 @@ async function delay(delayInMs, options) {
27547
27296
  return Promise.resolve();
27548
27297
  }
27549
27298
  const signal = options == null ? undefined : options.abortSignal;
27550
- return new Promise((resolve22, reject) => {
27299
+ return new Promise((resolve2, reject) => {
27551
27300
  if (signal == null ? undefined : signal.aborted) {
27552
27301
  reject(createAbortError());
27553
27302
  return;
27554
27303
  }
27555
27304
  const timeoutId = setTimeout(() => {
27556
27305
  cleanup();
27557
- resolve22();
27306
+ resolve2();
27558
27307
  }, delayInMs);
27559
27308
  const cleanup = () => {
27560
27309
  clearTimeout(timeoutId);
@@ -29012,7 +28761,7 @@ function createProviderToolFactoryWithOutputSchema({
29012
28761
  supportsDeferredResults
29013
28762
  });
29014
28763
  }
29015
- async function resolve3(value) {
28764
+ async function resolve2(value) {
29016
28765
  if (typeof value === "function") {
29017
28766
  value = value();
29018
28767
  }
@@ -29051,13 +28800,13 @@ var DelayedPromise = class {
29051
28800
  if (this._promise) {
29052
28801
  return this._promise;
29053
28802
  }
29054
- this._promise = new Promise((resolve22, reject) => {
28803
+ this._promise = new Promise((resolve2, reject) => {
29055
28804
  if (this.status.type === "resolved") {
29056
- resolve22(this.status.value);
28805
+ resolve2(this.status.value);
29057
28806
  } else if (this.status.type === "rejected") {
29058
28807
  reject(this.status.error);
29059
28808
  }
29060
- this._resolve = resolve22;
28809
+ this._resolve = resolve2;
29061
28810
  this._reject = reject;
29062
28811
  });
29063
28812
  return this._promise;
@@ -31515,11 +31264,11 @@ var VERSION2 = "3.0.58", anthropicErrorDataSchema, anthropicFailedResponseHandle
31515
31264
  betas,
31516
31265
  headers
31517
31266
  }) {
31518
- return combineHeaders(await resolve3(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
31267
+ return combineHeaders(await resolve2(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
31519
31268
  }
31520
31269
  async getBetasFromHeaders(requestHeaders) {
31521
31270
  var _a16, _b16;
31522
- const configHeaders = await resolve3(this.config.headers);
31271
+ const configHeaders = await resolve2(this.config.headers);
31523
31272
  const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
31524
31273
  const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
31525
31274
  return new Set([
@@ -42350,7 +42099,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42350
42099
  try {
42351
42100
  const { value } = await getFromApi({
42352
42101
  url: `${this.config.baseURL}/config`,
42353
- headers: await resolve3(this.config.headers()),
42102
+ headers: await resolve2(this.config.headers()),
42354
42103
  successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
42355
42104
  failedResponseHandler: createJsonErrorResponseHandler({
42356
42105
  errorSchema: exports_external.any(),
@@ -42368,7 +42117,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42368
42117
  const baseUrl = new URL(this.config.baseURL);
42369
42118
  const { value } = await getFromApi({
42370
42119
  url: `${baseUrl.origin}/v1/credits`,
42371
- headers: await resolve3(this.config.headers()),
42120
+ headers: await resolve2(this.config.headers()),
42372
42121
  successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
42373
42122
  failedResponseHandler: createJsonErrorResponseHandler({
42374
42123
  errorSchema: exports_external.any(),
@@ -42401,7 +42150,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42401
42150
  async doGenerate(options) {
42402
42151
  const { args, warnings } = await this.getArgs(options);
42403
42152
  const { abortSignal } = options;
42404
- const resolvedHeaders = await resolve3(this.config.headers());
42153
+ const resolvedHeaders = await resolve2(this.config.headers());
42405
42154
  try {
42406
42155
  const {
42407
42156
  responseHeaders,
@@ -42409,7 +42158,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42409
42158
  rawValue: rawResponse
42410
42159
  } = await postJsonToApi({
42411
42160
  url: this.getUrl(),
42412
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve3(this.config.o11yHeaders)),
42161
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve2(this.config.o11yHeaders)),
42413
42162
  body: args,
42414
42163
  successfulResponseHandler: createJsonResponseHandler(exports_external.any()),
42415
42164
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -42432,11 +42181,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42432
42181
  async doStream(options) {
42433
42182
  const { args, warnings } = await this.getArgs(options);
42434
42183
  const { abortSignal } = options;
42435
- const resolvedHeaders = await resolve3(this.config.headers());
42184
+ const resolvedHeaders = await resolve2(this.config.headers());
42436
42185
  try {
42437
42186
  const { value: response, responseHeaders } = await postJsonToApi({
42438
42187
  url: this.getUrl(),
42439
- headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve3(this.config.o11yHeaders)),
42188
+ headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve2(this.config.o11yHeaders)),
42440
42189
  body: args,
42441
42190
  successfulResponseHandler: createEventSourceResponseHandler(exports_external.any()),
42442
42191
  failedResponseHandler: createJsonErrorResponseHandler({
@@ -42521,7 +42270,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42521
42270
  providerOptions
42522
42271
  }) {
42523
42272
  var _a92;
42524
- const resolvedHeaders = await resolve3(this.config.headers());
42273
+ const resolvedHeaders = await resolve2(this.config.headers());
42525
42274
  try {
42526
42275
  const {
42527
42276
  responseHeaders,
@@ -42529,7 +42278,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42529
42278
  rawValue
42530
42279
  } = await postJsonToApi({
42531
42280
  url: this.getUrl(),
42532
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
42281
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
42533
42282
  body: {
42534
42283
  values,
42535
42284
  ...providerOptions ? { providerOptions } : {}
@@ -42585,7 +42334,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42585
42334
  abortSignal
42586
42335
  }) {
42587
42336
  var _a92, _b92, _c, _d;
42588
- const resolvedHeaders = await resolve3(this.config.headers());
42337
+ const resolvedHeaders = await resolve2(this.config.headers());
42589
42338
  try {
42590
42339
  const {
42591
42340
  responseHeaders,
@@ -42593,7 +42342,7 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42593
42342
  rawValue
42594
42343
  } = await postJsonToApi({
42595
42344
  url: this.getUrl(),
42596
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders)),
42345
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders)),
42597
42346
  body: {
42598
42347
  prompt,
42599
42348
  n,
@@ -42668,11 +42417,11 @@ var import_oidc, import_oidc2, marker17 = "vercel.ai.gateway.error", symbol18, _
42668
42417
  abortSignal
42669
42418
  }) {
42670
42419
  var _a92;
42671
- const resolvedHeaders = await resolve3(this.config.headers());
42420
+ const resolvedHeaders = await resolve2(this.config.headers());
42672
42421
  try {
42673
42422
  const { responseHeaders, value: responseBody } = await postJsonToApi({
42674
42423
  url: this.getUrl(),
42675
- headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve3(this.config.o11yHeaders), { accept: "text/event-stream" }),
42424
+ headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve2(this.config.o11yHeaders), { accept: "text/event-stream" }),
42676
42425
  body: {
42677
42426
  prompt,
42678
42427
  n,
@@ -46662,8 +46411,8 @@ function writeToServerResponse({
46662
46411
  break;
46663
46412
  const canContinue = response.write(value);
46664
46413
  if (!canContinue) {
46665
- await new Promise((resolve32) => {
46666
- response.once("drain", resolve32);
46414
+ await new Promise((resolve3) => {
46415
+ response.once("drain", resolve3);
46667
46416
  });
46668
46417
  }
46669
46418
  }
@@ -47424,15 +47173,15 @@ async function consumeStream({
47424
47173
  }
47425
47174
  }
47426
47175
  function createResolvablePromise() {
47427
- let resolve32;
47176
+ let resolve3;
47428
47177
  let reject;
47429
47178
  const promise2 = new Promise((res, rej) => {
47430
- resolve32 = res;
47179
+ resolve3 = res;
47431
47180
  reject = rej;
47432
47181
  });
47433
47182
  return {
47434
47183
  promise: promise2,
47435
- resolve: resolve32,
47184
+ resolve: resolve3,
47436
47185
  reject
47437
47186
  };
47438
47187
  }
@@ -48014,7 +47763,7 @@ var import_api, import_api2, __defProp2, __export2 = (target, all) => {
48014
47763
  const schema = asSchema(inputSchema);
48015
47764
  return {
48016
47765
  name: "object",
48017
- responseFormat: resolve3(schema.jsonSchema).then((jsonSchema2) => ({
47766
+ responseFormat: resolve2(schema.jsonSchema).then((jsonSchema2) => ({
48018
47767
  type: "json",
48019
47768
  schema: jsonSchema2,
48020
47769
  ...name21 != null && { name: name21 },
@@ -48075,7 +47824,7 @@ var import_api, import_api2, __defProp2, __export2 = (target, all) => {
48075
47824
  const elementSchema = asSchema(inputElementSchema);
48076
47825
  return {
48077
47826
  name: "array",
48078
- responseFormat: resolve3(elementSchema.jsonSchema).then((jsonSchema2) => {
47827
+ responseFormat: resolve2(elementSchema.jsonSchema).then((jsonSchema2) => {
48079
47828
  const { $schema, ...itemSchema } = jsonSchema2;
48080
47829
  return {
48081
47830
  type: "json",
@@ -52573,7 +52322,7 @@ var init_path = () => {};
52573
52322
  var ENCODINGS, ENCODINGS_ORDERED_KEYS, DEFAULT_DOCUMENT = "index.html", serveStatic = (options) => {
52574
52323
  const root = options.root ?? "./";
52575
52324
  const optionPath = options.path;
52576
- const join5 = options.join ?? defaultJoin;
52325
+ const join3 = options.join ?? defaultJoin;
52577
52326
  return async (c, next) => {
52578
52327
  if (c.finalized) {
52579
52328
  return next();
@@ -52592,9 +52341,9 @@ var ENCODINGS, ENCODINGS_ORDERED_KEYS, DEFAULT_DOCUMENT = "index.html", serveSta
52592
52341
  return next();
52593
52342
  }
52594
52343
  }
52595
- let path = join5(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
52344
+ let path = join3(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
52596
52345
  if (options.isDir && await options.isDir(path)) {
52597
- path = join5(path, DEFAULT_DOCUMENT);
52346
+ path = join3(path, DEFAULT_DOCUMENT);
52598
52347
  }
52599
52348
  const getContent = options.getContent;
52600
52349
  let content = await getContent(path, c);
@@ -52642,7 +52391,7 @@ var init_serve_static = __esm(() => {
52642
52391
 
52643
52392
  // node_modules/.bun/hono@4.12.5/node_modules/hono/dist/adapter/bun/serve-static.js
52644
52393
  import { stat } from "fs/promises";
52645
- import { join as join5 } from "path";
52394
+ import { join as join3 } from "path";
52646
52395
  var serveStatic2 = (options) => {
52647
52396
  return async function serveStatic22(c, next) {
52648
52397
  const getContent = async (path) => {
@@ -52660,7 +52409,7 @@ var serveStatic2 = (options) => {
52660
52409
  return serveStatic({
52661
52410
  ...options,
52662
52411
  getContent,
52663
- join: join5,
52412
+ join: join3,
52664
52413
  isDir
52665
52414
  })(c, next);
52666
52415
  };
@@ -52826,6 +52575,257 @@ var init_bun = __esm(() => {
52826
52575
  init_server();
52827
52576
  });
52828
52577
 
52578
+ // src/cli/runtime.ts
52579
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync3 } from "fs";
52580
+ import { homedir as homedir2 } from "os";
52581
+ import { join as join4, resolve as resolve3 } from "path";
52582
+ function getRuntimeDirs() {
52583
+ const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
52584
+ const root = override?.trim() ? override.trim() : join4(homedir2(), ".local-router");
52585
+ return {
52586
+ root,
52587
+ run: join4(root, "run"),
52588
+ logs: join4(root, "logs")
52589
+ };
52590
+ }
52591
+ function getRuntimeFiles() {
52592
+ const dirs = getRuntimeDirs();
52593
+ return {
52594
+ pid: join4(dirs.run, "local-router.pid"),
52595
+ state: join4(dirs.run, "status.json"),
52596
+ daemonLog: join4(dirs.logs, "daemon.log")
52597
+ };
52598
+ }
52599
+ function ensureRuntimeDirs() {
52600
+ const dirs = getRuntimeDirs();
52601
+ mkdirSync3(dirs.root, { recursive: true });
52602
+ mkdirSync3(dirs.run, { recursive: true });
52603
+ mkdirSync3(dirs.logs, { recursive: true });
52604
+ }
52605
+ function writeRuntimeState(state) {
52606
+ ensureRuntimeDirs();
52607
+ const files = getRuntimeFiles();
52608
+ writeFileSync3(files.pid, `${state.pid}
52609
+ `, "utf-8");
52610
+ writeFileSync3(files.state, JSON.stringify(state, null, 2), "utf-8");
52611
+ }
52612
+ function readRuntimeState() {
52613
+ const files = getRuntimeFiles();
52614
+ if (!existsSync2(files.state)) {
52615
+ return null;
52616
+ }
52617
+ try {
52618
+ return JSON.parse(readFileSync4(files.state, "utf-8"));
52619
+ } catch {
52620
+ return null;
52621
+ }
52622
+ }
52623
+ function clearRuntimeFiles() {
52624
+ const files = getRuntimeFiles();
52625
+ rmSync(files.pid, { force: true });
52626
+ rmSync(files.state, { force: true });
52627
+ }
52628
+ function resolveConfigArgPath(pathValue) {
52629
+ return resolve3(pathValue);
52630
+ }
52631
+ var init_runtime = () => {};
52632
+
52633
+ // src/cli/autostart.ts
52634
+ import { execSync } from "child_process";
52635
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
52636
+ import { homedir as homedir3, platform } from "os";
52637
+ import { dirname as dirname3, join as join5 } from "path";
52638
+ function getDaemonLogPath() {
52639
+ return getRuntimeDirs().logs + "/daemon.log";
52640
+ }
52641
+ function getLaunchAgentPath() {
52642
+ return join5(homedir3(), "Library", "LaunchAgents", `${LABEL}.plist`);
52643
+ }
52644
+ function buildPlist(opts) {
52645
+ const logPath = getDaemonLogPath();
52646
+ const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
52647
+ `);
52648
+ return `<?xml version="1.0" encoding="UTF-8"?>
52649
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
52650
+ <plist version="1.0">
52651
+ <dict>
52652
+ <key>Label</key>
52653
+ <string>${escapeXml(opts.label)}</string>
52654
+ <key>ProgramArguments</key>
52655
+ <array>
52656
+ ${args}
52657
+ </array>
52658
+ <key>RunAtLoad</key>
52659
+ <true/>
52660
+ <key>KeepAlive</key>
52661
+ <false/>
52662
+ <key>StandardOutPath</key>
52663
+ <string>${escapeXml(logPath)}</string>
52664
+ <key>StandardErrorPath</key>
52665
+ <string>${escapeXml(logPath)}</string>
52666
+ </dict>
52667
+ </plist>
52668
+ `;
52669
+ }
52670
+ function escapeXml(s) {
52671
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52672
+ }
52673
+ function createMacosManager() {
52674
+ const plistPath = getLaunchAgentPath();
52675
+ return {
52676
+ platform: "macos",
52677
+ async isInstalled() {
52678
+ return existsSync3(plistPath);
52679
+ },
52680
+ async install(opts) {
52681
+ const dir = dirname3(plistPath);
52682
+ if (!existsSync3(dir))
52683
+ mkdirSync4(dir, { recursive: true });
52684
+ writeFileSync4(plistPath, buildPlist(opts), "utf-8");
52685
+ try {
52686
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
52687
+ } catch {}
52688
+ execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
52689
+ },
52690
+ async uninstall() {
52691
+ if (!existsSync3(plistPath))
52692
+ return;
52693
+ try {
52694
+ execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
52695
+ } catch {}
52696
+ rmSync2(plistPath, { force: true });
52697
+ },
52698
+ getServicePath() {
52699
+ return plistPath;
52700
+ }
52701
+ };
52702
+ }
52703
+ function getSystemdUnitPath() {
52704
+ return join5(homedir3(), ".config", "systemd", "user", "local-router.service");
52705
+ }
52706
+ function buildUnit(opts) {
52707
+ const logPath = getDaemonLogPath();
52708
+ const execStart = [opts.execPath, ...opts.args].join(" ");
52709
+ return `[Unit]
52710
+ Description=Local Router API Gateway
52711
+ After=network-online.target
52712
+
52713
+ [Service]
52714
+ Type=simple
52715
+ ExecStart=${execStart}
52716
+ Restart=on-failure
52717
+ RestartSec=5
52718
+ StandardOutput=append:${logPath}
52719
+ StandardError=append:${logPath}
52720
+
52721
+ [Install]
52722
+ WantedBy=default.target
52723
+ `;
52724
+ }
52725
+ function createLinuxManager() {
52726
+ const unitPath = getSystemdUnitPath();
52727
+ return {
52728
+ platform: "linux",
52729
+ async isInstalled() {
52730
+ if (!existsSync3(unitPath))
52731
+ return false;
52732
+ try {
52733
+ const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
52734
+ encoding: "utf-8"
52735
+ }).trim();
52736
+ return out === "enabled";
52737
+ } catch {
52738
+ return false;
52739
+ }
52740
+ },
52741
+ async install(opts) {
52742
+ const dir = dirname3(unitPath);
52743
+ if (!existsSync3(dir))
52744
+ mkdirSync4(dir, { recursive: true });
52745
+ writeFileSync4(unitPath, buildUnit(opts), "utf-8");
52746
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
52747
+ execSync("systemctl --user enable local-router", { stdio: "ignore" });
52748
+ },
52749
+ async uninstall() {
52750
+ try {
52751
+ execSync("systemctl --user disable local-router", { stdio: "ignore" });
52752
+ } catch {}
52753
+ rmSync2(unitPath, { force: true });
52754
+ try {
52755
+ execSync("systemctl --user daemon-reload", { stdio: "ignore" });
52756
+ } catch {}
52757
+ },
52758
+ getServicePath() {
52759
+ return unitPath;
52760
+ }
52761
+ };
52762
+ }
52763
+ function createWindowsManager() {
52764
+ return {
52765
+ platform: "windows",
52766
+ async isInstalled() {
52767
+ try {
52768
+ execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
52769
+ return true;
52770
+ } catch {
52771
+ return false;
52772
+ }
52773
+ },
52774
+ async install(opts) {
52775
+ const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
52776
+ execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
52777
+ stdio: "ignore"
52778
+ });
52779
+ },
52780
+ async uninstall() {
52781
+ try {
52782
+ execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
52783
+ } catch {}
52784
+ },
52785
+ getServicePath() {
52786
+ return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
52787
+ }
52788
+ };
52789
+ }
52790
+ function createUnsupportedManager() {
52791
+ return {
52792
+ platform: "unsupported",
52793
+ async isInstalled() {
52794
+ return false;
52795
+ },
52796
+ async install() {
52797
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
52798
+ },
52799
+ async uninstall() {
52800
+ throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
52801
+ },
52802
+ getServicePath() {
52803
+ return "";
52804
+ }
52805
+ };
52806
+ }
52807
+ function createAutostartManager() {
52808
+ const p = platform();
52809
+ if (p === "darwin")
52810
+ return createMacosManager();
52811
+ if (p === "linux")
52812
+ return createLinuxManager();
52813
+ if (p === "win32")
52814
+ return createWindowsManager();
52815
+ return createUnsupportedManager();
52816
+ }
52817
+ function getAutostartExecArgs() {
52818
+ const script = process.argv[1] ?? "dist/cli.js";
52819
+ return {
52820
+ execPath: process.execPath,
52821
+ args: [script, "__run-server", "--mode", "daemon"]
52822
+ };
52823
+ }
52824
+ var LABEL = "com.lakphy.local-router", WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", WIN_REG_VALUE = "LocalRouter";
52825
+ var init_autostart = __esm(() => {
52826
+ init_runtime();
52827
+ });
52828
+
52829
52829
  // src/config-store.ts
52830
52830
  import { writeFileSync as writeFileSync5 } from "fs";
52831
52831
  import { resolve as resolve4 } from "path";
@@ -52922,387 +52922,8 @@ var init_crypto = __esm(() => {
52922
52922
  ECDH_PARAMS = { name: "ECDH", namedCurve: "P-256" };
52923
52923
  });
52924
52924
 
52925
- // src/log-metrics.ts
52926
- import { createReadStream, existsSync as existsSync4 } from "fs";
52927
- import { join as join6 } from "path";
52928
- import { createInterface } from "readline";
52929
- function isLogMetricsWindow(value) {
52930
- return value === "1h" || value === "6h" || value === "24h";
52931
- }
52932
- function toPercent(numerator, denominator) {
52933
- if (denominator <= 0)
52934
- return 0;
52935
- return Number((numerator / denominator * 100).toFixed(2));
52936
- }
52937
- function toDayStart(ms) {
52938
- const date5 = new Date(ms);
52939
- return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
52940
- }
52941
- function listDateStrings(fromMs, toMs) {
52942
- const result = [];
52943
- for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
52944
- result.push(new Date(day).toISOString().slice(0, 10));
52945
- }
52946
- return result;
52947
- }
52948
- function getStatusClass(event) {
52949
- if (event.error_type)
52950
- return "network_error";
52951
- const status = event.upstream_status ?? 0;
52952
- if (status >= 200 && status < 300)
52953
- return "2xx";
52954
- if (status >= 400 && status < 500)
52955
- return "4xx";
52956
- if (status >= 500)
52957
- return "5xx";
52958
- return "network_error";
52959
- }
52960
- function isErrorEvent(event) {
52961
- if (event.error_type)
52962
- return true;
52963
- const status = event.upstream_status ?? 0;
52964
- return status < 200 || status >= 400;
52965
- }
52966
- function percentile(sortedNumbers, ratio) {
52967
- if (sortedNumbers.length === 0)
52968
- return 0;
52969
- const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
52970
- return Math.round(sortedNumbers[index]);
52971
- }
52972
- function createEmptyMetrics(window2, nowMs, source, warnings = []) {
52973
- const fromMs = nowMs - WINDOW_MS[window2];
52974
- const bucketMs = BUCKET_MS[window2];
52975
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
52976
- const series = Array.from({ length: bucketCount }, (_, i) => ({
52977
- ts: new Date(fromMs + i * bucketMs).toISOString(),
52978
- requests: 0,
52979
- errors: 0,
52980
- avgLatencyMs: 0
52981
- }));
52982
- return {
52983
- window: window2,
52984
- from: new Date(fromMs).toISOString(),
52985
- to: new Date(nowMs).toISOString(),
52986
- generatedAt: new Date(nowMs).toISOString(),
52987
- source,
52988
- summary: {
52989
- totalRequests: 0,
52990
- successRequests: 0,
52991
- errorRequests: 0,
52992
- successRate: 0,
52993
- avgLatencyMs: 0,
52994
- p95LatencyMs: 0,
52995
- totalRequestBytes: 0,
52996
- totalResponseBytes: 0
52997
- },
52998
- series,
52999
- topProviders: [],
53000
- topRouteTypes: [],
53001
- statusClasses: {
53002
- "2xx": 0,
53003
- "4xx": 0,
53004
- "5xx": 0,
53005
- network_error: 0
53006
- },
53007
- warnings
53008
- };
53009
- }
53010
- async function getLogMetrics(options) {
53011
- const window2 = options.window ?? "24h";
53012
- const refresh = options.refresh === true;
53013
- const nowMs = options.nowMs ?? Date.now();
53014
- const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
53015
- if (!logEnabled) {
53016
- return createEmptyMetrics(window2, nowMs, {
53017
- logEnabled: false,
53018
- baseDir: null,
53019
- filesScanned: 0,
53020
- linesScanned: 0,
53021
- partial: false
53022
- }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
53023
- }
53024
- const baseDir = resolveLogBaseDir(options.logConfig);
53025
- const cacheKey = `${baseDir}:${window2}`;
53026
- const cached2 = metricsCache.get(cacheKey);
53027
- if (!refresh && cached2 && cached2.expiresAt > nowMs) {
53028
- return cached2.value;
53029
- }
53030
- const eventsDir = join6(baseDir, "events");
53031
- if (!existsSync4(eventsDir)) {
53032
- const empty = createEmptyMetrics(window2, nowMs, {
53033
- logEnabled: true,
53034
- baseDir,
53035
- filesScanned: 0,
53036
- linesScanned: 0,
53037
- partial: false
53038
- }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
53039
- metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
53040
- return empty;
53041
- }
53042
- const fromMs = nowMs - WINDOW_MS[window2];
53043
- const bucketMs = BUCKET_MS[window2];
53044
- const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53045
- const buckets = Array.from({ length: bucketCount }, () => ({
53046
- requests: 0,
53047
- errors: 0,
53048
- latencySum: 0,
53049
- latencyCount: 0
53050
- }));
53051
- const providerAgg = new Map;
53052
- const routeTypeAgg = new Map;
53053
- const latencies = [];
53054
- const statusClasses = {
53055
- "2xx": 0,
53056
- "4xx": 0,
53057
- "5xx": 0,
53058
- network_error: 0
53059
- };
53060
- let filesScanned = 0;
53061
- let linesScanned = 0;
53062
- let parseErrors = 0;
53063
- let partial2 = false;
53064
- let totalRequests = 0;
53065
- let successRequests = 0;
53066
- let errorRequests = 0;
53067
- let totalLatency = 0;
53068
- let totalRequestBytes = 0;
53069
- let totalResponseBytes = 0;
53070
- const warnings = [];
53071
- const dateStrings = listDateStrings(fromMs, nowMs);
53072
- for (const dateStr of dateStrings) {
53073
- if (linesScanned >= MAX_LINES_SCANNED) {
53074
- partial2 = true;
53075
- break;
53076
- }
53077
- const filePath = join6(eventsDir, `${dateStr}.jsonl`);
53078
- if (!existsSync4(filePath))
53079
- continue;
53080
- filesScanned += 1;
53081
- try {
53082
- const stream = createReadStream(filePath, { encoding: "utf-8" });
53083
- const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
53084
- for await (const line of rl) {
53085
- if (linesScanned >= MAX_LINES_SCANNED) {
53086
- partial2 = true;
53087
- rl.close();
53088
- stream.destroy();
53089
- break;
53090
- }
53091
- linesScanned += 1;
53092
- if (!line.trim())
53093
- continue;
53094
- let event;
53095
- try {
53096
- event = JSON.parse(line);
53097
- } catch {
53098
- parseErrors += 1;
53099
- continue;
53100
- }
53101
- if (!event.ts_start)
53102
- continue;
53103
- const ts = Date.parse(event.ts_start);
53104
- if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
53105
- continue;
53106
- totalRequests += 1;
53107
- const isError = isErrorEvent(event);
53108
- if (isError) {
53109
- errorRequests += 1;
53110
- } else {
53111
- successRequests += 1;
53112
- }
53113
- const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
53114
- totalLatency += latency;
53115
- latencies.push(latency);
53116
- totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
53117
- totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
53118
- const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
53119
- const bucket = buckets[bucketIndex];
53120
- bucket.requests += 1;
53121
- bucket.latencySum += latency;
53122
- bucket.latencyCount += 1;
53123
- if (isError)
53124
- bucket.errors += 1;
53125
- const providerKey = event.provider || "unknown";
53126
- const providerRow = providerAgg.get(providerKey) ?? {
53127
- requests: 0,
53128
- errors: 0,
53129
- latencySum: 0
53130
- };
53131
- providerRow.requests += 1;
53132
- providerRow.latencySum += latency;
53133
- if (isError)
53134
- providerRow.errors += 1;
53135
- providerAgg.set(providerKey, providerRow);
53136
- const routeTypeKey = event.route_type || "unknown";
53137
- const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
53138
- requests: 0,
53139
- errors: 0,
53140
- latencySum: 0
53141
- };
53142
- routeTypeRow.requests += 1;
53143
- routeTypeRow.latencySum += latency;
53144
- if (isError)
53145
- routeTypeRow.errors += 1;
53146
- routeTypeAgg.set(routeTypeKey, routeTypeRow);
53147
- statusClasses[getStatusClass(event)] += 1;
53148
- }
53149
- } catch (err) {
53150
- warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
53151
- partial2 = true;
53152
- }
53153
- }
53154
- if (parseErrors > 0) {
53155
- warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
53156
- }
53157
- if (partial2) {
53158
- warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
53159
- }
53160
- latencies.sort((a, b) => a - b);
53161
- const series = buckets.map((bucket, index) => ({
53162
- ts: new Date(fromMs + index * bucketMs).toISOString(),
53163
- requests: bucket.requests,
53164
- errors: bucket.errors,
53165
- avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
53166
- }));
53167
- const topProviders = Array.from(providerAgg.entries()).map(([key, row]) => ({
53168
- key,
53169
- requests: row.requests,
53170
- errorRate: toPercent(row.errors, row.requests),
53171
- avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
53172
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53173
- const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key, row]) => ({
53174
- key,
53175
- requests: row.requests,
53176
- errorRate: toPercent(row.errors, row.requests)
53177
- })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53178
- const response = {
53179
- window: window2,
53180
- from: new Date(fromMs).toISOString(),
53181
- to: new Date(nowMs).toISOString(),
53182
- generatedAt: new Date(nowMs).toISOString(),
53183
- source: {
53184
- logEnabled: true,
53185
- baseDir,
53186
- filesScanned,
53187
- linesScanned,
53188
- partial: partial2
53189
- },
53190
- summary: {
53191
- totalRequests,
53192
- successRequests,
53193
- errorRequests,
53194
- successRate: toPercent(successRequests, totalRequests),
53195
- avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
53196
- p95LatencyMs: percentile(latencies, 0.95),
53197
- totalRequestBytes,
53198
- totalResponseBytes
53199
- },
53200
- series,
53201
- topProviders,
53202
- topRouteTypes,
53203
- statusClasses,
53204
- warnings
53205
- };
53206
- metricsCache.set(cacheKey, {
53207
- expiresAt: nowMs + CACHE_TTL_MS,
53208
- value: response
53209
- });
53210
- return response;
53211
- }
53212
- var WINDOW_MS, BUCKET_MS, TOP_LIMIT = 5, MAX_LINES_SCANNED = 250000, CACHE_TTL_MS = 15000, metricsCache;
53213
- var init_log_metrics = __esm(() => {
53214
- init_config();
53215
- WINDOW_MS = {
53216
- "1h": 60 * 60 * 1000,
53217
- "6h": 6 * 60 * 60 * 1000,
53218
- "24h": 24 * 60 * 60 * 1000
53219
- };
53220
- BUCKET_MS = {
53221
- "1h": 5 * 60 * 1000,
53222
- "6h": 15 * 60 * 1000,
53223
- "24h": 30 * 60 * 1000
53224
- };
53225
- metricsCache = new Map;
53226
- });
53227
-
53228
- // src/log-session-identity.ts
53229
- function toRecord(value) {
53230
- if (!value || typeof value !== "object" || Array.isArray(value))
53231
- return null;
53232
- return value;
53233
- }
53234
- function extractUserIdRawFromRequestBody(requestBody) {
53235
- const requestBodyRecord = toRecord(requestBody);
53236
- const metadata = toRecord(requestBodyRecord?.metadata);
53237
- if (!metadata) {
53238
- return {
53239
- hasMetadata: false,
53240
- userIdRaw: null
53241
- };
53242
- }
53243
- const userId = metadata.user_id;
53244
- if (typeof userId !== "string" || userId.trim() === "") {
53245
- return {
53246
- hasMetadata: true,
53247
- userIdRaw: null
53248
- };
53249
- }
53250
- return {
53251
- hasMetadata: true,
53252
- userIdRaw: userId
53253
- };
53254
- }
53255
- function parseUserSessionFromJsonFormat(userIdRaw) {
53256
- let parsed;
53257
- try {
53258
- parsed = JSON.parse(userIdRaw);
53259
- } catch {
53260
- return null;
53261
- }
53262
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
53263
- return null;
53264
- const obj = parsed;
53265
- const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
53266
- if (!sessionId)
53267
- return null;
53268
- const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
53269
- return { userKey: userKey || sessionId, sessionId };
53270
- }
53271
- function parseUserSessionFromUserIdRaw(userIdRaw) {
53272
- if (userIdRaw.trimStart().startsWith("{")) {
53273
- return parseUserSessionFromJsonFormat(userIdRaw);
53274
- }
53275
- const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
53276
- if (index <= 0)
53277
- return null;
53278
- const userKey = userIdRaw.slice(0, index).trim();
53279
- const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
53280
- if (!userKey || !sessionId)
53281
- return null;
53282
- return { userKey, sessionId };
53283
- }
53284
- function resolveLogSessionIdentity(requestBody) {
53285
- const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
53286
- if (!userIdRaw) {
53287
- return {
53288
- hasMetadata,
53289
- userIdRaw: null,
53290
- userKey: null,
53291
- sessionId: null
53292
- };
53293
- }
53294
- const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
53295
- return {
53296
- hasMetadata,
53297
- userIdRaw,
53298
- userKey: parsed?.userKey ?? null,
53299
- sessionId: parsed?.sessionId ?? null
53300
- };
53301
- }
53302
- var USER_SESSION_DELIMITER = "_account__session_";
53303
-
53304
52925
  // src/token-usage.ts
53305
- import { existsSync as existsSync5, readFileSync as readFileSync6, statSync } from "fs";
52926
+ import { existsSync as existsSync4, readFileSync as readFileSync6, statSync } from "fs";
53306
52927
  import { resolve as resolve5 } from "path";
53307
52928
  function asRecord(value) {
53308
52929
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -53395,7 +53016,7 @@ function inferProviderStyle(usage, providerHint) {
53395
53016
  }
53396
53017
  return "unknown";
53397
53018
  }
53398
- function createEmptyMetrics2(input) {
53019
+ function createEmptyMetrics(input) {
53399
53020
  return {
53400
53021
  schemaVersion: 1,
53401
53022
  source: input.source,
@@ -53451,7 +53072,7 @@ function hasAnyTokenSignal(metrics) {
53451
53072
  function normalizeUsageObject(input) {
53452
53073
  const { usage, source, rawUsagePath, providerHint } = input;
53453
53074
  const providerStyle = inferProviderStyle(usage, providerHint);
53454
- const metrics = createEmptyMetrics2({
53075
+ const metrics = createEmptyMetrics({
53455
53076
  source,
53456
53077
  providerStyle,
53457
53078
  rawUsage: usage,
@@ -53824,7 +53445,7 @@ function safeReadStreamFile(streamFile, baseDir) {
53824
53445
  const resolved = resolve5(candidate);
53825
53446
  if (!resolved.endsWith(".sse.raw"))
53826
53447
  continue;
53827
- if (!existsSync5(resolved))
53448
+ if (!existsSync4(resolved))
53828
53449
  continue;
53829
53450
  const stats = statSync(resolved);
53830
53451
  if (stats.size > MAX_STREAM_USAGE_BYTES) {
@@ -53884,6 +53505,435 @@ var init_token_usage = __esm(() => {
53884
53505
  MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
53885
53506
  });
53886
53507
 
53508
+ // src/log-metrics.ts
53509
+ import { createReadStream, existsSync as existsSync5 } from "fs";
53510
+ import { join as join6 } from "path";
53511
+ import { createInterface } from "readline";
53512
+ function isLogMetricsWindow(value) {
53513
+ return value === "1h" || value === "6h" || value === "24h";
53514
+ }
53515
+ function toPercent(numerator, denominator) {
53516
+ if (denominator <= 0)
53517
+ return 0;
53518
+ return Number((numerator / denominator * 100).toFixed(2));
53519
+ }
53520
+ function toDayStart(ms) {
53521
+ const date5 = new Date(ms);
53522
+ return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
53523
+ }
53524
+ function listDateStrings(fromMs, toMs) {
53525
+ const result = [];
53526
+ for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
53527
+ result.push(new Date(day).toISOString().slice(0, 10));
53528
+ }
53529
+ return result;
53530
+ }
53531
+ function getStatusClass(event) {
53532
+ if (event.error_type)
53533
+ return "network_error";
53534
+ const status = event.upstream_status ?? 0;
53535
+ if (status >= 200 && status < 300)
53536
+ return "2xx";
53537
+ if (status >= 400 && status < 500)
53538
+ return "4xx";
53539
+ if (status >= 500)
53540
+ return "5xx";
53541
+ return "network_error";
53542
+ }
53543
+ function isErrorEvent(event) {
53544
+ if (event.error_type)
53545
+ return true;
53546
+ const status = event.upstream_status ?? 0;
53547
+ return status < 200 || status >= 400;
53548
+ }
53549
+ function percentile(sortedNumbers, ratio) {
53550
+ if (sortedNumbers.length === 0)
53551
+ return 0;
53552
+ const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
53553
+ return Math.round(sortedNumbers[index]);
53554
+ }
53555
+ function createEmptyMetrics2(window2, nowMs, source, warnings = []) {
53556
+ const fromMs = nowMs - WINDOW_MS[window2];
53557
+ const bucketMs = BUCKET_MS[window2];
53558
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53559
+ const series = Array.from({ length: bucketCount }, (_, i) => ({
53560
+ ts: new Date(fromMs + i * bucketMs).toISOString(),
53561
+ requests: 0,
53562
+ errors: 0,
53563
+ avgLatencyMs: 0
53564
+ }));
53565
+ return {
53566
+ window: window2,
53567
+ from: new Date(fromMs).toISOString(),
53568
+ to: new Date(nowMs).toISOString(),
53569
+ generatedAt: new Date(nowMs).toISOString(),
53570
+ source,
53571
+ summary: {
53572
+ totalRequests: 0,
53573
+ successRequests: 0,
53574
+ errorRequests: 0,
53575
+ successRate: 0,
53576
+ avgLatencyMs: 0,
53577
+ p95LatencyMs: 0,
53578
+ totalRequestBytes: 0,
53579
+ totalResponseBytes: 0
53580
+ },
53581
+ tokens: {
53582
+ usageCount: 0,
53583
+ inputTokens: 0,
53584
+ outputTokens: 0,
53585
+ totalTokens: 0,
53586
+ cachedInputTokens: 0,
53587
+ cacheHitInputTokens: 0,
53588
+ cacheHitRateDenominatorTokens: 0,
53589
+ cacheHitRate: 0,
53590
+ reasoningTokens: 0,
53591
+ cost: null
53592
+ },
53593
+ series,
53594
+ topProviders: [],
53595
+ topRouteTypes: [],
53596
+ statusClasses: {
53597
+ "2xx": 0,
53598
+ "4xx": 0,
53599
+ "5xx": 0,
53600
+ network_error: 0
53601
+ },
53602
+ warnings
53603
+ };
53604
+ }
53605
+ async function getLogMetrics(options) {
53606
+ const window2 = options.window ?? "24h";
53607
+ const refresh = options.refresh === true;
53608
+ const nowMs = options.nowMs ?? Date.now();
53609
+ const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
53610
+ if (!logEnabled) {
53611
+ return createEmptyMetrics2(window2, nowMs, {
53612
+ logEnabled: false,
53613
+ baseDir: null,
53614
+ filesScanned: 0,
53615
+ linesScanned: 0,
53616
+ partial: false
53617
+ }, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
53618
+ }
53619
+ const baseDir = resolveLogBaseDir(options.logConfig);
53620
+ const cacheKey = `${baseDir}:${window2}`;
53621
+ const cached2 = metricsCache.get(cacheKey);
53622
+ if (!refresh && cached2 && cached2.expiresAt > nowMs) {
53623
+ return cached2.value;
53624
+ }
53625
+ const eventsDir = join6(baseDir, "events");
53626
+ if (!existsSync5(eventsDir)) {
53627
+ const empty = createEmptyMetrics2(window2, nowMs, {
53628
+ logEnabled: true,
53629
+ baseDir,
53630
+ filesScanned: 0,
53631
+ linesScanned: 0,
53632
+ partial: false
53633
+ }, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
53634
+ metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
53635
+ return empty;
53636
+ }
53637
+ const fromMs = nowMs - WINDOW_MS[window2];
53638
+ const bucketMs = BUCKET_MS[window2];
53639
+ const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
53640
+ const buckets = Array.from({ length: bucketCount }, () => ({
53641
+ requests: 0,
53642
+ errors: 0,
53643
+ latencySum: 0,
53644
+ latencyCount: 0
53645
+ }));
53646
+ const providerAgg = new Map;
53647
+ const routeTypeAgg = new Map;
53648
+ const latencies = [];
53649
+ const statusClasses = {
53650
+ "2xx": 0,
53651
+ "4xx": 0,
53652
+ "5xx": 0,
53653
+ network_error: 0
53654
+ };
53655
+ let filesScanned = 0;
53656
+ let linesScanned = 0;
53657
+ let parseErrors = 0;
53658
+ let partial2 = false;
53659
+ let totalRequests = 0;
53660
+ let successRequests = 0;
53661
+ let errorRequests = 0;
53662
+ let totalLatency = 0;
53663
+ let totalRequestBytes = 0;
53664
+ let totalResponseBytes = 0;
53665
+ let tokenUsageCount = 0;
53666
+ let tokenInput = 0;
53667
+ let tokenOutput = 0;
53668
+ let tokenTotal = 0;
53669
+ let tokenCachedInput = 0;
53670
+ let tokenCacheHitInput = 0;
53671
+ let tokenCacheHitDenominator = 0;
53672
+ let tokenReasoning = 0;
53673
+ let tokenCost = 0;
53674
+ let tokenCostSeen = false;
53675
+ const warnings = [];
53676
+ const dateStrings = listDateStrings(fromMs, nowMs);
53677
+ for (const dateStr of dateStrings) {
53678
+ if (linesScanned >= MAX_LINES_SCANNED) {
53679
+ partial2 = true;
53680
+ break;
53681
+ }
53682
+ const filePath = join6(eventsDir, `${dateStr}.jsonl`);
53683
+ if (!existsSync5(filePath))
53684
+ continue;
53685
+ filesScanned += 1;
53686
+ try {
53687
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
53688
+ const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
53689
+ for await (const line of rl) {
53690
+ if (linesScanned >= MAX_LINES_SCANNED) {
53691
+ partial2 = true;
53692
+ rl.close();
53693
+ stream.destroy();
53694
+ break;
53695
+ }
53696
+ linesScanned += 1;
53697
+ if (!line.trim())
53698
+ continue;
53699
+ let event;
53700
+ try {
53701
+ event = JSON.parse(line);
53702
+ } catch {
53703
+ parseErrors += 1;
53704
+ continue;
53705
+ }
53706
+ if (!event.ts_start)
53707
+ continue;
53708
+ const ts = Date.parse(event.ts_start);
53709
+ if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
53710
+ continue;
53711
+ totalRequests += 1;
53712
+ const isError = isErrorEvent(event);
53713
+ if (isError) {
53714
+ errorRequests += 1;
53715
+ } else {
53716
+ successRequests += 1;
53717
+ }
53718
+ const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
53719
+ totalLatency += latency;
53720
+ latencies.push(latency);
53721
+ totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
53722
+ totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
53723
+ const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
53724
+ const bucket = buckets[bucketIndex];
53725
+ bucket.requests += 1;
53726
+ bucket.latencySum += latency;
53727
+ bucket.latencyCount += 1;
53728
+ if (isError)
53729
+ bucket.errors += 1;
53730
+ const providerKey = event.provider || "unknown";
53731
+ const providerRow = providerAgg.get(providerKey) ?? {
53732
+ requests: 0,
53733
+ errors: 0,
53734
+ latencySum: 0
53735
+ };
53736
+ providerRow.requests += 1;
53737
+ providerRow.latencySum += latency;
53738
+ if (isError)
53739
+ providerRow.errors += 1;
53740
+ providerAgg.set(providerKey, providerRow);
53741
+ const routeTypeKey = event.route_type || "unknown";
53742
+ const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
53743
+ requests: 0,
53744
+ errors: 0,
53745
+ latencySum: 0
53746
+ };
53747
+ routeTypeRow.requests += 1;
53748
+ routeTypeRow.latencySum += latency;
53749
+ if (isError)
53750
+ routeTypeRow.errors += 1;
53751
+ routeTypeAgg.set(routeTypeKey, routeTypeRow);
53752
+ statusClasses[getStatusClass(event)] += 1;
53753
+ const usage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
53754
+ if (usage) {
53755
+ tokenUsageCount += 1;
53756
+ tokenInput += Math.max(0, usage.inputTokens ?? 0);
53757
+ tokenOutput += Math.max(0, usage.outputTokens ?? 0);
53758
+ tokenTotal += Math.max(0, usage.totalTokens ?? 0);
53759
+ tokenCachedInput += Math.max(0, usage.cachedInputTokens ?? 0);
53760
+ tokenCacheHitInput += Math.max(0, usage.cacheHitInputTokens ?? 0);
53761
+ tokenCacheHitDenominator += Math.max(0, usage.cacheHitRateDenominatorTokens ?? 0);
53762
+ tokenReasoning += Math.max(0, usage.reasoningTokens ?? 0);
53763
+ if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
53764
+ tokenCost += usage.cost;
53765
+ tokenCostSeen = true;
53766
+ }
53767
+ }
53768
+ }
53769
+ } catch (err) {
53770
+ warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
53771
+ partial2 = true;
53772
+ }
53773
+ }
53774
+ if (parseErrors > 0) {
53775
+ warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
53776
+ }
53777
+ if (partial2) {
53778
+ warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
53779
+ }
53780
+ latencies.sort((a, b) => a - b);
53781
+ const series = buckets.map((bucket, index) => ({
53782
+ ts: new Date(fromMs + index * bucketMs).toISOString(),
53783
+ requests: bucket.requests,
53784
+ errors: bucket.errors,
53785
+ avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
53786
+ }));
53787
+ const topProviders = Array.from(providerAgg.entries()).map(([key, row]) => ({
53788
+ key,
53789
+ requests: row.requests,
53790
+ errorRate: toPercent(row.errors, row.requests),
53791
+ avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
53792
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53793
+ const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key, row]) => ({
53794
+ key,
53795
+ requests: row.requests,
53796
+ errorRate: toPercent(row.errors, row.requests)
53797
+ })).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
53798
+ const response = {
53799
+ window: window2,
53800
+ from: new Date(fromMs).toISOString(),
53801
+ to: new Date(nowMs).toISOString(),
53802
+ generatedAt: new Date(nowMs).toISOString(),
53803
+ source: {
53804
+ logEnabled: true,
53805
+ baseDir,
53806
+ filesScanned,
53807
+ linesScanned,
53808
+ partial: partial2
53809
+ },
53810
+ summary: {
53811
+ totalRequests,
53812
+ successRequests,
53813
+ errorRequests,
53814
+ successRate: toPercent(successRequests, totalRequests),
53815
+ avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
53816
+ p95LatencyMs: percentile(latencies, 0.95),
53817
+ totalRequestBytes,
53818
+ totalResponseBytes
53819
+ },
53820
+ tokens: {
53821
+ usageCount: tokenUsageCount,
53822
+ inputTokens: tokenInput,
53823
+ outputTokens: tokenOutput,
53824
+ totalTokens: tokenTotal,
53825
+ cachedInputTokens: tokenCachedInput,
53826
+ cacheHitInputTokens: tokenCacheHitInput,
53827
+ cacheHitRateDenominatorTokens: tokenCacheHitDenominator,
53828
+ cacheHitRate: toPercent(tokenCacheHitInput, tokenCacheHitDenominator),
53829
+ reasoningTokens: tokenReasoning,
53830
+ cost: tokenCostSeen ? Number(tokenCost.toFixed(6)) : null
53831
+ },
53832
+ series,
53833
+ topProviders,
53834
+ topRouteTypes,
53835
+ statusClasses,
53836
+ warnings
53837
+ };
53838
+ metricsCache.set(cacheKey, {
53839
+ expiresAt: nowMs + CACHE_TTL_MS,
53840
+ value: response
53841
+ });
53842
+ return response;
53843
+ }
53844
+ var WINDOW_MS, BUCKET_MS, TOP_LIMIT = 5, MAX_LINES_SCANNED = 250000, CACHE_TTL_MS = 15000, metricsCache;
53845
+ var init_log_metrics = __esm(() => {
53846
+ init_config();
53847
+ init_token_usage();
53848
+ WINDOW_MS = {
53849
+ "1h": 60 * 60 * 1000,
53850
+ "6h": 6 * 60 * 60 * 1000,
53851
+ "24h": 24 * 60 * 60 * 1000
53852
+ };
53853
+ BUCKET_MS = {
53854
+ "1h": 5 * 60 * 1000,
53855
+ "6h": 15 * 60 * 1000,
53856
+ "24h": 30 * 60 * 1000
53857
+ };
53858
+ metricsCache = new Map;
53859
+ });
53860
+
53861
+ // src/log-session-identity.ts
53862
+ function toRecord(value) {
53863
+ if (!value || typeof value !== "object" || Array.isArray(value))
53864
+ return null;
53865
+ return value;
53866
+ }
53867
+ function extractUserIdRawFromRequestBody(requestBody) {
53868
+ const requestBodyRecord = toRecord(requestBody);
53869
+ const metadata = toRecord(requestBodyRecord?.metadata);
53870
+ if (!metadata) {
53871
+ return {
53872
+ hasMetadata: false,
53873
+ userIdRaw: null
53874
+ };
53875
+ }
53876
+ const userId = metadata.user_id;
53877
+ if (typeof userId !== "string" || userId.trim() === "") {
53878
+ return {
53879
+ hasMetadata: true,
53880
+ userIdRaw: null
53881
+ };
53882
+ }
53883
+ return {
53884
+ hasMetadata: true,
53885
+ userIdRaw: userId
53886
+ };
53887
+ }
53888
+ function parseUserSessionFromJsonFormat(userIdRaw) {
53889
+ let parsed;
53890
+ try {
53891
+ parsed = JSON.parse(userIdRaw);
53892
+ } catch {
53893
+ return null;
53894
+ }
53895
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
53896
+ return null;
53897
+ const obj = parsed;
53898
+ const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
53899
+ if (!sessionId)
53900
+ return null;
53901
+ const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
53902
+ return { userKey: userKey || sessionId, sessionId };
53903
+ }
53904
+ function parseUserSessionFromUserIdRaw(userIdRaw) {
53905
+ if (userIdRaw.trimStart().startsWith("{")) {
53906
+ return parseUserSessionFromJsonFormat(userIdRaw);
53907
+ }
53908
+ const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
53909
+ if (index <= 0)
53910
+ return null;
53911
+ const userKey = userIdRaw.slice(0, index).trim();
53912
+ const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
53913
+ if (!userKey || !sessionId)
53914
+ return null;
53915
+ return { userKey, sessionId };
53916
+ }
53917
+ function resolveLogSessionIdentity(requestBody) {
53918
+ const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
53919
+ if (!userIdRaw) {
53920
+ return {
53921
+ hasMetadata,
53922
+ userIdRaw: null,
53923
+ userKey: null,
53924
+ sessionId: null
53925
+ };
53926
+ }
53927
+ const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
53928
+ return {
53929
+ hasMetadata,
53930
+ userIdRaw,
53931
+ userKey: parsed?.userKey ?? null,
53932
+ sessionId: parsed?.sessionId ?? null
53933
+ };
53934
+ }
53935
+ var USER_SESSION_DELIMITER = "_account__session_";
53936
+
53887
53937
  // src/log-index.ts
53888
53938
  import { Database } from "bun:sqlite";
53889
53939
  import {
@@ -54275,6 +54325,31 @@ function buildWhereClause(query, options = {}) {
54275
54325
  usesFts
54276
54326
  };
54277
54327
  }
54328
+ function buildSessionsWhereClause(query) {
54329
+ const pseudo = {
54330
+ fromMs: query.fromMs,
54331
+ toMs: query.toMs,
54332
+ levels: [],
54333
+ providers: [],
54334
+ routeTypes: [],
54335
+ models: [],
54336
+ modelIns: [],
54337
+ modelOuts: [],
54338
+ users: query.users,
54339
+ sessions: query.sessions,
54340
+ statusClasses: [],
54341
+ hasError: null,
54342
+ q: query.q,
54343
+ sort: "time_desc",
54344
+ limit: 1,
54345
+ cursor: null
54346
+ };
54347
+ const { whereSql, params } = buildWhereClause(pseudo);
54348
+ return { whereSql, params };
54349
+ }
54350
+ function sortIndexedCountItems(map2) {
54351
+ return Array.from(map2.entries()).map(([key, count]) => ({ key, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
54352
+ }
54278
54353
 
54279
54354
  class LogIndex {
54280
54355
  baseDir;
@@ -54482,6 +54557,163 @@ class LogIndex {
54482
54557
  }
54483
54558
  };
54484
54559
  }
54560
+ querySessions(query) {
54561
+ const startedAt = performance.now();
54562
+ const { whereSql, params } = buildSessionsWhereClause(query);
54563
+ const aggregatedWhere = `${whereSql} AND e.user_key IS NOT NULL AND e.session_id IS NOT NULL`;
54564
+ const summaryRow = this.db.query(`
54565
+ SELECT
54566
+ COUNT(*) AS totalRequests,
54567
+ COALESCE(SUM(has_metadata), 0) AS metadataRequests,
54568
+ COUNT(DISTINCT user_key) AS uniqueUsers,
54569
+ COUNT(DISTINCT CASE
54570
+ WHEN user_key IS NOT NULL AND session_id IS NOT NULL
54571
+ THEN user_key || ' ' || session_id
54572
+ END) AS uniqueSessions
54573
+ FROM log_events e
54574
+ ${whereSql}
54575
+ `).get(...params);
54576
+ const userRows = this.db.query(`
54577
+ SELECT
54578
+ user_key AS userKey,
54579
+ COUNT(*) AS requestCount,
54580
+ MIN(ts_ms) AS firstMs,
54581
+ MAX(ts_ms) AS lastMs,
54582
+ COUNT(DISTINCT session_id) AS sessionCount
54583
+ FROM log_events e
54584
+ ${aggregatedWhere}
54585
+ GROUP BY user_key
54586
+ `).all(...params);
54587
+ const sessionRows = this.db.query(`
54588
+ SELECT
54589
+ user_key AS userKey,
54590
+ session_id AS sessionId,
54591
+ COUNT(*) AS requestCount,
54592
+ MIN(ts_ms) AS firstMs,
54593
+ MAX(ts_ms) AS lastMs
54594
+ FROM log_events e
54595
+ ${aggregatedWhere}
54596
+ GROUP BY user_key, session_id
54597
+ `).all(...params);
54598
+ const userModelRows = this.db.query(`
54599
+ SELECT user_key AS userKey, model AS key, COUNT(*) AS count
54600
+ FROM log_events e
54601
+ ${aggregatedWhere}
54602
+ GROUP BY user_key, model
54603
+ `).all(...params);
54604
+ const userProviderRows = this.db.query(`
54605
+ SELECT user_key AS userKey, provider AS key, COUNT(*) AS count
54606
+ FROM log_events e
54607
+ ${aggregatedWhere}
54608
+ GROUP BY user_key, provider
54609
+ `).all(...params);
54610
+ const userRouteRows = this.db.query(`
54611
+ SELECT user_key AS userKey, route_type AS key, COUNT(*) AS count
54612
+ FROM log_events e
54613
+ ${aggregatedWhere}
54614
+ GROUP BY user_key, route_type
54615
+ `).all(...params);
54616
+ const sessionModelRows = this.db.query(`
54617
+ SELECT user_key AS userKey, session_id AS sessionId, model AS key, COUNT(*) AS count
54618
+ FROM log_events e
54619
+ ${aggregatedWhere}
54620
+ GROUP BY user_key, session_id, model
54621
+ `).all(...params);
54622
+ const latestRows = this.db.query(`
54623
+ SELECT userKey, sessionId, request_id AS latestRequestId
54624
+ FROM (
54625
+ SELECT
54626
+ user_key AS userKey,
54627
+ session_id AS sessionId,
54628
+ request_id,
54629
+ ROW_NUMBER() OVER (
54630
+ PARTITION BY user_key, session_id ORDER BY ts_ms DESC, id DESC
54631
+ ) AS rn
54632
+ FROM log_events e
54633
+ ${aggregatedWhere}
54634
+ )
54635
+ WHERE rn = 1
54636
+ `).all(...params);
54637
+ const userModels = new Map;
54638
+ const userProviders = new Map;
54639
+ const userRoutes = new Map;
54640
+ const sessionModels = new Map;
54641
+ const latestBySession = new Map;
54642
+ const addCount = (target, groupKey, key, count) => {
54643
+ if (!key)
54644
+ return;
54645
+ let inner = target.get(groupKey);
54646
+ if (!inner) {
54647
+ inner = new Map;
54648
+ target.set(groupKey, inner);
54649
+ }
54650
+ inner.set(key, count);
54651
+ };
54652
+ for (const row of userModelRows)
54653
+ addCount(userModels, row.userKey, row.key, row.count);
54654
+ for (const row of userProviderRows)
54655
+ addCount(userProviders, row.userKey, row.key, row.count);
54656
+ for (const row of userRouteRows)
54657
+ addCount(userRoutes, row.userKey, row.key, row.count);
54658
+ for (const row of sessionModelRows) {
54659
+ addCount(sessionModels, `${row.userKey}\x00${row.sessionId}`, row.key, row.count);
54660
+ }
54661
+ for (const row of latestRows) {
54662
+ latestBySession.set(`${row.userKey}\x00${row.sessionId}`, row.latestRequestId);
54663
+ }
54664
+ const sessionsByUser = new Map;
54665
+ for (const row of sessionRows) {
54666
+ const sessionKey = `${row.userKey}\x00${row.sessionId}`;
54667
+ const session = {
54668
+ sessionId: row.sessionId,
54669
+ requestCount: row.requestCount,
54670
+ firstSeenAt: new Date(row.firstMs).toISOString(),
54671
+ lastSeenAt: new Date(row.lastMs).toISOString(),
54672
+ models: sortIndexedCountItems(sessionModels.get(sessionKey) ?? new Map),
54673
+ latestRequestId: latestBySession.get(sessionKey) ?? ""
54674
+ };
54675
+ const list = sessionsByUser.get(row.userKey);
54676
+ if (list) {
54677
+ list.push(session);
54678
+ } else {
54679
+ sessionsByUser.set(row.userKey, [session]);
54680
+ }
54681
+ }
54682
+ const users = userRows.map((row) => {
54683
+ const sessions = (sessionsByUser.get(row.userKey) ?? []).sort((a, b) => {
54684
+ if (a.requestCount !== b.requestCount)
54685
+ return b.requestCount - a.requestCount;
54686
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
54687
+ });
54688
+ return {
54689
+ userKey: row.userKey,
54690
+ requestCount: row.requestCount,
54691
+ sessionCount: row.sessionCount,
54692
+ firstSeenAt: new Date(row.firstMs).toISOString(),
54693
+ lastSeenAt: new Date(row.lastMs).toISOString(),
54694
+ models: sortIndexedCountItems(userModels.get(row.userKey) ?? new Map),
54695
+ providers: sortIndexedCountItems(userProviders.get(row.userKey) ?? new Map),
54696
+ routeTypes: sortIndexedCountItems(userRoutes.get(row.userKey) ?? new Map),
54697
+ sessions
54698
+ };
54699
+ }).sort((a, b) => {
54700
+ if (a.requestCount !== b.requestCount)
54701
+ return b.requestCount - a.requestCount;
54702
+ return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
54703
+ });
54704
+ return {
54705
+ from: new Date(query.fromMs).toISOString(),
54706
+ to: new Date(query.toMs).toISOString(),
54707
+ summary: {
54708
+ totalRequests: Number(summaryRow.totalRequests) || 0,
54709
+ metadataRequests: Number(summaryRow.metadataRequests) || 0,
54710
+ uniqueUsers: Number(summaryRow.uniqueUsers) || 0,
54711
+ uniqueSessions: Number(summaryRow.uniqueSessions) || 0
54712
+ },
54713
+ users,
54714
+ queryMs: Math.round((performance.now() - startedAt) * 100) / 100
54715
+ };
54716
+ }
54485
54717
  configure() {
54486
54718
  this.db.exec(`
54487
54719
  PRAGMA journal_mode = WAL;
@@ -54864,6 +55096,65 @@ async function queryIndexedLogEvents(logConfig, query) {
54864
55096
  };
54865
55097
  }
54866
55098
  }
55099
+ async function queryIndexedLogSessions(logConfig, query) {
55100
+ if (!logConfig || logConfig.enabled === false) {
55101
+ return {
55102
+ from: new Date(query.fromMs).toISOString(),
55103
+ to: new Date(query.toMs).toISOString(),
55104
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
55105
+ users: [],
55106
+ meta: {
55107
+ scannedFiles: 0,
55108
+ scannedLines: 0,
55109
+ parseErrors: 0,
55110
+ truncated: false,
55111
+ indexUsed: true,
55112
+ indexFresh: true,
55113
+ queryMs: 0
55114
+ }
55115
+ };
55116
+ }
55117
+ const baseDir = resolveLogBaseDir(logConfig);
55118
+ const index = getLogIndex(baseDir);
55119
+ if (!index)
55120
+ return null;
55121
+ try {
55122
+ const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
55123
+ const result = index.querySessions(query);
55124
+ return {
55125
+ from: result.from,
55126
+ to: result.to,
55127
+ summary: result.summary,
55128
+ users: result.users,
55129
+ meta: {
55130
+ scannedFiles: freshness.scannedFiles,
55131
+ scannedLines: freshness.scannedLines,
55132
+ parseErrors: freshness.parseErrors,
55133
+ truncated: false,
55134
+ indexUsed: true,
55135
+ indexFresh: true,
55136
+ queryMs: result.queryMs
55137
+ }
55138
+ };
55139
+ } catch (err) {
55140
+ return {
55141
+ from: new Date(query.fromMs).toISOString(),
55142
+ to: new Date(query.toMs).toISOString(),
55143
+ summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
55144
+ users: [],
55145
+ meta: {
55146
+ scannedFiles: 0,
55147
+ scannedLines: 0,
55148
+ parseErrors: 0,
55149
+ truncated: false,
55150
+ indexUsed: false,
55151
+ indexFresh: false,
55152
+ queryMs: 0,
55153
+ fallbackReason: err instanceof Error ? err.message : String(err)
55154
+ }
55155
+ };
55156
+ }
55157
+ }
54867
55158
  function getIndexedLogEventDetail(logConfig, id) {
54868
55159
  if (!logConfig || logConfig.enabled === false)
54869
55160
  return null;
@@ -55543,7 +55834,7 @@ async function scanEvents(baseDir, query) {
55543
55834
  };
55544
55835
  }
55545
55836
  function isLogQueryWindow(value) {
55546
- return value === "1h" || value === "6h" || value === "24h";
55837
+ return value === "1h" || value === "6h" || value === "24h" || value === "7d" || value === "1mo" || value === "1y";
55547
55838
  }
55548
55839
  function getLogQueryWindowMs(window2) {
55549
55840
  return WINDOW_MS2[window2];
@@ -55860,7 +56151,10 @@ var init_log_query = __esm(() => {
55860
56151
  WINDOW_MS2 = {
55861
56152
  "1h": 60 * 60 * 1000,
55862
56153
  "6h": 6 * 60 * 60 * 1000,
55863
- "24h": 24 * 60 * 60 * 1000
56154
+ "24h": 24 * 60 * 60 * 1000,
56155
+ "7d": 7 * 24 * 60 * 60 * 1000,
56156
+ "1mo": 30 * 24 * 60 * 60 * 1000,
56157
+ "1y": 365 * 24 * 60 * 60 * 1000
55864
56158
  };
55865
56159
  });
55866
56160
 
@@ -56049,7 +56343,7 @@ function compileRealtimeQuery(input) {
56049
56343
  const nowMs = Date.now();
56050
56344
  const windowRaw = parseStringValue(query.window) ?? "24h";
56051
56345
  if (!isLogQueryWindow(windowRaw)) {
56052
- throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
56346
+ throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y");
56053
56347
  }
56054
56348
  const from = parseStringValue(query.from);
56055
56349
  const to = parseStringValue(query.to);
@@ -56639,6 +56933,23 @@ async function queryLogSessions(context2, input) {
56639
56933
  if (!logEnabled) {
56640
56934
  return createEmptyResult(normalized.fromMs, normalized.toMs);
56641
56935
  }
56936
+ const indexed = await queryIndexedLogSessions(context2.logConfig, {
56937
+ fromMs: normalized.fromMs,
56938
+ toMs: normalized.toMs,
56939
+ users: normalized.users,
56940
+ sessions: normalized.sessions,
56941
+ q: normalized.q
56942
+ });
56943
+ if (indexed?.meta.indexUsed) {
56944
+ return {
56945
+ from: indexed.from,
56946
+ to: indexed.to,
56947
+ summary: indexed.summary,
56948
+ users: indexed.users,
56949
+ meta: indexed.meta
56950
+ };
56951
+ }
56952
+ const fallbackReason = indexed?.meta.fallbackReason;
56642
56953
  const baseDir = resolveLogBaseDir(context2.logConfig);
56643
56954
  const eventsDir = join9(baseDir, "events");
56644
56955
  if (!existsSync8(eventsDir)) {
@@ -56762,13 +57073,16 @@ async function queryLogSessions(context2, input) {
56762
57073
  scannedFiles,
56763
57074
  scannedLines,
56764
57075
  parseErrors,
56765
- truncated
57076
+ truncated,
57077
+ indexUsed: false,
57078
+ ...fallbackReason ? { fallbackReason } : {}
56766
57079
  }
56767
57080
  };
56768
57081
  }
56769
57082
  var MAX_LINES_SCANNED3 = 250000, MAX_Q_LENGTH3 = 200;
56770
57083
  var init_log_sessions = __esm(() => {
56771
57084
  init_config();
57085
+ init_log_index();
56772
57086
  });
56773
57087
 
56774
57088
  // src/log-storage.ts
@@ -57141,6 +57455,97 @@ var init_openapi = __esm(() => {
57141
57455
  }
57142
57456
  }
57143
57457
  },
57458
+ "/api/models": {
57459
+ get: {
57460
+ tags: ["Health"],
57461
+ summary: "\u55C5\u63A2\u53EF\u7528\u6A21\u578B\u8DEF\u7531",
57462
+ description: "\u8FD4\u56DE\u672C\u673A\u6307\u5B9A\u534F\u8BAE\u4E0B\u53EF\u7528\u7684\u6A21\u578B\u8DEF\u7531\u522B\u540D\uFF08routes[protocol] \u7684 key\uFF0C\u6392\u9664 * \u901A\u914D\uFF09\u3002\u4F9B\u5C40\u57DF\u7F51\u5185\u5176\u4ED6 local-router \u63A2\u6D4B\u4F7F\u7528\u3002",
57463
+ parameters: [
57464
+ {
57465
+ name: "protocol",
57466
+ in: "query",
57467
+ required: true,
57468
+ schema: {
57469
+ type: "string",
57470
+ enum: ["openai-completions", "openai-responses", "anthropic-messages"]
57471
+ },
57472
+ description: "\u534F\u8BAE\u7C7B\u578B"
57473
+ }
57474
+ ],
57475
+ responses: {
57476
+ "200": {
57477
+ description: "\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
57478
+ content: {
57479
+ "application/json": {
57480
+ schema: {
57481
+ type: "object",
57482
+ properties: {
57483
+ protocol: { type: "string", example: "anthropic-messages" },
57484
+ models: {
57485
+ type: "array",
57486
+ items: { type: "string" },
57487
+ example: ["claude-3-5-sonnet", "claude-3-opus"]
57488
+ }
57489
+ }
57490
+ }
57491
+ }
57492
+ }
57493
+ },
57494
+ "400": { description: "\u534F\u8BAE\u53C2\u6570\u7F3A\u5931\u6216\u65E0\u6548" }
57495
+ }
57496
+ }
57497
+ },
57498
+ "/api/providers/discover": {
57499
+ get: {
57500
+ tags: ["Health"],
57501
+ summary: "\u53D1\u73B0\u5C40\u57DF\u7F51 local-router \u7684\u6A21\u578B",
57502
+ description: "\u7531\u672C\u673A server \u4EE3\u4E3A\u8BF7\u6C42\u5BF9\u7AEF local-router \u7684 /api/models\uFF08\u89C4\u907F\u6D4F\u89C8\u5668\u8DE8\u57DF\uFF09\uFF0C\u8FD4\u56DE\u5BF9\u7AEF\u67D0\u534F\u8BAE\u4E0B\u7684\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u3002",
57503
+ parameters: [
57504
+ {
57505
+ name: "ip",
57506
+ in: "query",
57507
+ required: true,
57508
+ schema: { type: "string" },
57509
+ description: "\u5BF9\u7AEF IP"
57510
+ },
57511
+ {
57512
+ name: "port",
57513
+ in: "query",
57514
+ required: false,
57515
+ schema: { type: "string", default: "4099" },
57516
+ description: "\u5BF9\u7AEF\u7AEF\u53E3\uFF0C\u9ED8\u8BA4 4099"
57517
+ },
57518
+ {
57519
+ name: "protocol",
57520
+ in: "query",
57521
+ required: true,
57522
+ schema: {
57523
+ type: "string",
57524
+ enum: ["openai-completions", "openai-responses", "anthropic-messages"]
57525
+ },
57526
+ description: "\u534F\u8BAE\u7C7B\u578B"
57527
+ }
57528
+ ],
57529
+ responses: {
57530
+ "200": {
57531
+ description: "\u5BF9\u7AEF\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
57532
+ content: {
57533
+ "application/json": {
57534
+ schema: {
57535
+ type: "object",
57536
+ properties: {
57537
+ protocol: { type: "string", example: "anthropic-messages" },
57538
+ models: { type: "array", items: { type: "string" } }
57539
+ }
57540
+ }
57541
+ }
57542
+ }
57543
+ },
57544
+ "400": { description: "\u53C2\u6570\u7F3A\u5931" },
57545
+ "502": { description: "\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF\u6216\u5BF9\u7AEF\u8FD4\u56DE\u9519\u8BEF" }
57546
+ }
57547
+ }
57548
+ },
57144
57549
  "/api/metrics/logs": {
57145
57550
  get: {
57146
57551
  tags: ["Health"],
@@ -57299,7 +57704,7 @@ var init_openapi = __esm(() => {
57299
57704
  required: false,
57300
57705
  schema: {
57301
57706
  type: "string",
57302
- enum: ["1h", "6h", "24h"],
57707
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57303
57708
  default: "24h"
57304
57709
  },
57305
57710
  description: "\u65F6\u95F4\u7A97\u53E3\uFF08\u5F53\u672A\u63D0\u4F9B from/to \u65F6\u751F\u6548\uFF09"
@@ -57629,7 +58034,7 @@ var init_openapi = __esm(() => {
57629
58034
  required: false,
57630
58035
  schema: {
57631
58036
  type: "string",
57632
- enum: ["1h", "6h", "24h"],
58037
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57633
58038
  default: "24h"
57634
58039
  },
57635
58040
  description: "\u65F6\u95F4\u7A97\u53E3"
@@ -57750,7 +58155,7 @@ var init_openapi = __esm(() => {
57750
58155
  required: false,
57751
58156
  schema: {
57752
58157
  type: "string",
57753
- enum: ["1h", "6h", "24h"],
58158
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
57754
58159
  default: "1h"
57755
58160
  }
57756
58161
  },
@@ -59138,6 +59543,43 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59138
59543
  routeTypes: Object.keys(ROUTE_REGISTRY)
59139
59544
  });
59140
59545
  });
59546
+ api2.get("/models", (c) => {
59547
+ const protocol = c.req.query("protocol");
59548
+ const routeTypes = Object.keys(ROUTE_REGISTRY);
59549
+ if (!protocol || !routeTypes.includes(protocol)) {
59550
+ return c.json({ error: "invalid or missing protocol", routeTypes }, 400);
59551
+ }
59552
+ const routes = store.get().routes[protocol] ?? {};
59553
+ const models = Object.keys(routes).filter((k) => k !== "*");
59554
+ return c.json({ protocol, models });
59555
+ });
59556
+ api2.get("/providers/discover", async (c) => {
59557
+ const ip = c.req.query("ip");
59558
+ const portStr = c.req.query("port") ?? "4099";
59559
+ const protocol = c.req.query("protocol");
59560
+ if (!ip || !protocol) {
59561
+ return c.json({ error: "ip and protocol are required" }, 400);
59562
+ }
59563
+ const port = Number(portStr);
59564
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
59565
+ return c.json({ error: "\u7AEF\u53E3\u65E0\u6548\uFF0C\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570" }, 400);
59566
+ }
59567
+ if (!isLoopbackAddress(ip) && !isLanAddress(ip)) {
59568
+ return c.json({ error: "\u4EC5\u652F\u6301\u5C40\u57DF\u7F51\u6216\u672C\u673A IP \u5730\u5740" }, 400);
59569
+ }
59570
+ const url2 = `http://${ip}:${port}/api/models?protocol=${encodeURIComponent(protocol)}`;
59571
+ try {
59572
+ const res = await fetch(url2, { signal: AbortSignal.timeout(5000) });
59573
+ if (!res.ok) {
59574
+ const body = await res.json().catch(() => ({}));
59575
+ return c.json({ error: body.error ?? `remote returned ${res.status}` }, 502);
59576
+ }
59577
+ const data = await res.json();
59578
+ return c.json(data);
59579
+ } catch (err) {
59580
+ return c.json({ error: `\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF local-router: ${err instanceof Error ? err.message : err}` }, 502);
59581
+ }
59582
+ });
59141
59583
  api2.get("/config/schema", (c) => {
59142
59584
  try {
59143
59585
  return c.json(schemaJson);
@@ -59260,7 +59702,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59260
59702
  try {
59261
59703
  const windowRaw = c.req.query("window") ?? "24h";
59262
59704
  if (!isLogQueryWindow(windowRaw)) {
59263
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59705
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59264
59706
  }
59265
59707
  const range = resolveLogQueryRange({
59266
59708
  window: windowRaw,
@@ -59311,7 +59753,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59311
59753
  try {
59312
59754
  const windowRaw = c.req.query("window") ?? "24h";
59313
59755
  if (!isLogQueryWindow(windowRaw)) {
59314
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59756
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59315
59757
  }
59316
59758
  const range = resolveLogQueryRange({
59317
59759
  window: windowRaw,
@@ -59350,7 +59792,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59350
59792
  try {
59351
59793
  const windowRaw = c.req.query("window") ?? "24h";
59352
59794
  if (!isLogQueryWindow(windowRaw)) {
59353
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59795
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59354
59796
  }
59355
59797
  const range = resolveLogQueryRange({
59356
59798
  window: windowRaw,
@@ -59400,7 +59842,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
59400
59842
  const target = c.req.raw;
59401
59843
  const windowRaw = c.req.query("window") ?? "1h";
59402
59844
  if (!isLogQueryWindow(windowRaw)) {
59403
- return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
59845
+ return c.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
59404
59846
  }
59405
59847
  const sortRaw = c.req.query("sort") ?? "time_desc";
59406
59848
  if (!validateSort(sortRaw)) {
@@ -59721,7 +60163,6 @@ async function createAppRuntimeFromConfigPath(configPath, listen) {
59721
60163
  }
59722
60164
  var ROUTE_REGISTRY;
59723
60165
  var init_src = __esm(() => {
59724
- init_autostart();
59725
60166
  init_dist4();
59726
60167
  init_dist5();
59727
60168
  init_dist6();
@@ -59729,6 +60170,7 @@ var init_src = __esm(() => {
59729
60170
  init_dist9();
59730
60171
  init_dist10();
59731
60172
  init_bun();
60173
+ init_autostart();
59732
60174
  init_config();
59733
60175
  init_config_store();
59734
60176
  init_config_validate();
@@ -61223,6 +61665,7 @@ Commands:
61223
61665
  config provider list [--json] [--config <path>]
61224
61666
  config provider show <name> [--show-secrets] [--config <path>]
61225
61667
  config provider add <name> --type <type> --base <url> --api-key <key> --model <name> [--image-input] [--reasoning] [--proxy <url>] [--dry-run] [--config <path>]
61668
+ config provider add-lan <ip> --type <protocol> [--port 4099] [--dry-run] [--config <path>]
61226
61669
  config provider set <name> [--base <url>] [--api-key <key>] [--proxy <url>] [--dry-run] [--config <path>]
61227
61670
  config provider remove <name> [--force] [--dry-run] [--config <path>]
61228
61671
  config provider model list <provider> [--config <path>]
@@ -61402,6 +61845,89 @@ async function handleProviderAdd(args, flags) {
61402
61845
  }
61403
61846
  });
61404
61847
  }
61848
+ async function handleProviderAddLan(args, flags) {
61849
+ return runCommand({
61850
+ command: "config.provider.add-lan",
61851
+ flags,
61852
+ fn: async (ctx) => {
61853
+ const [ip, ...flagArgs] = args;
61854
+ ensureNoFlag("ip", ip);
61855
+ if (!ip) {
61856
+ throw new CliError("USAGE_ERROR", "ip \u5FC5\u586B", {
61857
+ hint: "\u7528\u6CD5: config provider add-lan <ip> --type <protocol> [--port 4099]"
61858
+ });
61859
+ }
61860
+ const parsed = parseArgs3({
61861
+ args: flagArgs,
61862
+ options: {
61863
+ type: { type: "string" },
61864
+ port: { type: "string", default: "4099" },
61865
+ "dry-run": { type: "boolean", default: false },
61866
+ config: { type: "string" }
61867
+ },
61868
+ allowPositionals: true,
61869
+ strict: false
61870
+ });
61871
+ const type = parsed.values.type;
61872
+ if (!type || !providerTypes().includes(type)) {
61873
+ throw new CliError("USAGE_ERROR", "type \u5FC5\u586B\u4E14\u5FC5\u987B\u662F openai-completions/openai-responses/anthropic-messages", { details: { acceptable: providerTypes() } });
61874
+ }
61875
+ const portStr = parsed.values.port ?? "4099";
61876
+ const port = Number(portStr);
61877
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
61878
+ throw new CliError("USAGE_ERROR", `\u7AEF\u53E3\u65E0\u6548: ${portStr}\uFF08\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570\uFF09`);
61879
+ }
61880
+ const name21 = `${ip}-${type}`;
61881
+ const { path, config: config2 } = readConfig(parsed.values.config);
61882
+ if (config2.providers[name21]) {
61883
+ throw new CliError("PROVIDER_EXISTS", `provider \u5DF2\u5B58\u5728: ${name21}`, {
61884
+ hint: "\u4F7F\u7528 `config provider set` \u4FEE\u6539\u5B57\u6BB5"
61885
+ });
61886
+ }
61887
+ const url2 = `http://${ip}:${port}/api/models?protocol=${encodeURIComponent(type)}`;
61888
+ let models;
61889
+ try {
61890
+ const res = await fetch(url2, { signal: AbortSignal.timeout(5000) });
61891
+ if (!res.ok) {
61892
+ const body = await res.json().catch(() => ({}));
61893
+ throw new CliError("UPSTREAM_UNREACHABLE", `\u5BF9\u7AEF\u8FD4\u56DE\u9519\u8BEF: ${body.error ?? res.status}`, {
61894
+ details: { url: url2 }
61895
+ });
61896
+ }
61897
+ const data = await res.json();
61898
+ models = Array.isArray(data.models) ? data.models : [];
61899
+ } catch (err) {
61900
+ if (err instanceof CliError)
61901
+ throw err;
61902
+ throw new CliError("UPSTREAM_UNREACHABLE", `\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF local-router: ${err instanceof Error ? err.message : err}`, { details: { url: url2 } });
61903
+ }
61904
+ if (models.length === 0) {
61905
+ throw new CliError("USAGE_ERROR", `\u5BF9\u7AEF\u5728\u534F\u8BAE "${type}" \u4E0B\u6CA1\u6709\u53EF\u7528\u7684\u6A21\u578B\u8DEF\u7531\uFF0C\u65E0\u6CD5\u521B\u5EFA provider`, { hint: "\u786E\u8BA4\u5BF9\u7AEF\u5DF2\u4E3A\u8BE5\u534F\u8BAE\u914D\u7F6E\u4E86\u5177\u4F53\u6A21\u578B\u8DEF\u7531\uFF08\u800C\u975E\u4EC5 * \u515C\u5E95\uFF09" });
61906
+ }
61907
+ const modelMap = {};
61908
+ for (const m of models) {
61909
+ modelMap[m] = { "image-input": false, reasoning: false };
61910
+ }
61911
+ config2.providers[name21] = {
61912
+ type,
61913
+ base: `http://${ip}:${port}/${type}`,
61914
+ apiKey: "no_key",
61915
+ models: modelMap
61916
+ };
61917
+ const result = applyConfigChange(path, config2, { dryRun: parsed.values["dry-run"] });
61918
+ emitResult(ctx, {
61919
+ command: "config.provider.add-lan",
61920
+ data: { provider: name21, models: models.length, ...result },
61921
+ md: {
61922
+ heading: `config.provider.add-lan \xB7 ${name21} \xB7 ${result.written ? "\u2713" : "dry-run"}`,
61923
+ data: applyResultToMd(result, `provider ${name21} (${models.length} \u4E2A\u6A21\u578B)`),
61924
+ hints: result.written ? ["\u70ED\u52A0\u8F7D: `local-router config apply`"] : ["\u6267\u884C\u5199\u5165: \u53BB\u6389 `--dry-run`"]
61925
+ },
61926
+ text: result.written ? `\u5DF2\u6DFB\u52A0 provider: ${name21}\uFF08\u55C5\u63A2\u5230 ${models.length} \u4E2A\u6A21\u578B\uFF09` : applyResultToText(result)
61927
+ });
61928
+ }
61929
+ });
61930
+ }
61405
61931
  async function handleProviderSet(args, flags) {
61406
61932
  return runCommand({
61407
61933
  command: "config.provider.set",
@@ -62024,6 +62550,8 @@ async function handleProvider(args, flags) {
62024
62550
  return handleProviderShow(rest, flags);
62025
62551
  case "add":
62026
62552
  return handleProviderAdd(rest, flags);
62553
+ case "add-lan":
62554
+ return handleProviderAddLan(rest, flags);
62027
62555
  case "set":
62028
62556
  return handleProviderSet(rest, flags);
62029
62557
  case "remove":
@@ -62131,7 +62659,11 @@ async function dispatchConfig(group, rest, flags) {
62131
62659
  init_registry();
62132
62660
  var PROVIDER_TYPE_ENUM = ["openai-completions", "openai-responses", "anthropic-messages"];
62133
62661
  var COMMON_CONFIG_FLAG = { name: "config", type: "string", description: "\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84" };
62134
- var DRY_RUN_FLAG = { name: "dry-run", type: "boolean", description: "\u53EA\u9884\u89C8 diff\uFF0C\u4E0D\u5199\u5165" };
62662
+ var DRY_RUN_FLAG = {
62663
+ name: "dry-run",
62664
+ type: "boolean",
62665
+ description: "\u53EA\u9884\u89C8 diff\uFF0C\u4E0D\u5199\u5165"
62666
+ };
62135
62667
  function forward(prefix) {
62136
62668
  return async (args, flags) => cmdConfig([...prefix, ...args], flags);
62137
62669
  }
@@ -62148,10 +62680,7 @@ defineCommand({
62148
62680
  defineCommand({
62149
62681
  name: "config diff",
62150
62682
  summary: "\u4E0E\u5907\u4EFD\u6216\u6307\u5B9A\u6587\u4EF6\u5BF9\u6BD4",
62151
- flags: [
62152
- { name: "against", type: "string", description: "\u5907\u4EFD id \u6216\u8DEF\u5F84" },
62153
- COMMON_CONFIG_FLAG
62154
- ],
62683
+ flags: [{ name: "against", type: "string", description: "\u5907\u4EFD id \u6216\u8DEF\u5F84" }, COMMON_CONFIG_FLAG],
62155
62684
  supportsJson: true,
62156
62685
  handler: forward(["diff"])
62157
62686
  });
@@ -62265,6 +62794,26 @@ defineCommand({
62265
62794
  supportsJson: true,
62266
62795
  handler: forward(["provider", "add"])
62267
62796
  });
62797
+ defineCommand({
62798
+ name: "config provider add-lan",
62799
+ summary: "\u4ECE\u5C40\u57DF\u7F51\u5185\u5176\u4ED6 local-router \u55C5\u63A2\u5E76\u65B0\u589E provider",
62800
+ positionals: [{ name: "ip", required: true, description: "\u5BF9\u7AEF IP" }],
62801
+ flags: [
62802
+ {
62803
+ name: "type",
62804
+ type: "enum",
62805
+ enum: [...PROVIDER_TYPE_ENUM],
62806
+ required: true,
62807
+ description: "\u534F\u8BAE\u7C7B\u578B"
62808
+ },
62809
+ { name: "port", type: "string", description: "\u5BF9\u7AEF\u7AEF\u53E3\uFF08\u9ED8\u8BA4 4099\uFF09" },
62810
+ DRY_RUN_FLAG,
62811
+ COMMON_CONFIG_FLAG
62812
+ ],
62813
+ mutates: true,
62814
+ supportsJson: true,
62815
+ handler: forward(["provider", "add-lan"])
62816
+ });
62268
62817
  defineCommand({
62269
62818
  name: "config provider set",
62270
62819
  summary: "\u4FEE\u6539 provider \u5B57\u6BB5",
@@ -62350,10 +62899,7 @@ defineCommand({
62350
62899
  defineCommand({
62351
62900
  name: "config route list",
62352
62901
  summary: "\u5217\u51FA\u6240\u6709\u8DEF\u7531",
62353
- flags: [
62354
- { name: "entry", type: "string", description: "\u53EA\u770B\u67D0\u5165\u53E3" },
62355
- COMMON_CONFIG_FLAG
62356
- ],
62902
+ flags: [{ name: "entry", type: "string", description: "\u53EA\u770B\u67D0\u5165\u53E3" }, COMMON_CONFIG_FLAG],
62357
62903
  supportsJson: true,
62358
62904
  handler: forward(["route", "list"])
62359
62905
  });
@@ -64676,7 +65222,7 @@ defineSchemaCommand({
64676
65222
  {
64677
65223
  name: "window",
64678
65224
  type: "enum",
64679
- enum: ["1h", "6h", "24h"],
65225
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64680
65226
  default: "24h",
64681
65227
  description: "\u65F6\u95F4\u7A97\u53E3"
64682
65228
  },
@@ -64809,7 +65355,7 @@ defineSchemaCommand({
64809
65355
  {
64810
65356
  name: "window",
64811
65357
  type: "enum",
64812
- enum: ["1h", "6h", "24h"],
65358
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64813
65359
  default: "24h",
64814
65360
  description: "\u65F6\u95F4\u7A97\u53E3"
64815
65361
  }
@@ -64933,7 +65479,7 @@ defineSchemaCommand({
64933
65479
  {
64934
65480
  name: "window",
64935
65481
  type: "enum",
64936
- enum: ["1h", "6h", "24h"],
65482
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
64937
65483
  default: "24h",
64938
65484
  description: "\u65F6\u95F4\u7A97\u53E3"
64939
65485
  },
@@ -65020,7 +65566,7 @@ defineSchemaCommand({
65020
65566
  {
65021
65567
  name: "window",
65022
65568
  type: "enum",
65023
- enum: ["1h", "6h", "24h"],
65569
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65024
65570
  default: "24h",
65025
65571
  description: "\u65F6\u95F4\u7A97\u53E3"
65026
65572
  },
@@ -65146,7 +65692,7 @@ defineSchemaCommand({
65146
65692
  {
65147
65693
  name: "window",
65148
65694
  type: "enum",
65149
- enum: ["1h", "6h", "24h"],
65695
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65150
65696
  default: "24h",
65151
65697
  description: "\u65F6\u95F4\u7A97\u53E3"
65152
65698
  }
@@ -65206,7 +65752,7 @@ defineSchemaCommand({
65206
65752
  {
65207
65753
  name: "window",
65208
65754
  type: "enum",
65209
- enum: ["1h", "6h", "24h"],
65755
+ enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
65210
65756
  default: "24h",
65211
65757
  description: "\u65F6\u95F4\u7A97\u53E3"
65212
65758
  },