@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/config.schema.json +2 -2
- package/dist/cli.js +1239 -693
- package/dist/entry.js +1081 -639
- package/dist/web/assets/index-D1WTE7QU.js +192 -0
- package/dist/web/assets/index-DrH6dT5r.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BxjdLPIh.js +0 -190
- package/dist/web/assets/index-Dc2P7nS-.css +0 -2
package/dist/entry.js
CHANGED
|
@@ -9287,216 +9287,6 @@ var require_dist2 = __commonJS((exports, module) => {
|
|
|
9287
9287
|
import { readFileSync as readFileSync6 } from "fs";
|
|
9288
9288
|
import { dirname as dirname3, resolve as resolve8 } from "path";
|
|
9289
9289
|
|
|
9290
|
-
// src/cli/autostart.ts
|
|
9291
|
-
import { execSync } from "child_process";
|
|
9292
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
9293
|
-
import { homedir as homedir2, platform } from "os";
|
|
9294
|
-
import { dirname, join as join2 } from "path";
|
|
9295
|
-
|
|
9296
|
-
// src/cli/runtime.ts
|
|
9297
|
-
import { homedir } from "os";
|
|
9298
|
-
import { join, resolve } from "path";
|
|
9299
|
-
function getRuntimeDirs() {
|
|
9300
|
-
const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
|
|
9301
|
-
const root = override?.trim() ? override.trim() : join(homedir(), ".local-router");
|
|
9302
|
-
return {
|
|
9303
|
-
root,
|
|
9304
|
-
run: join(root, "run"),
|
|
9305
|
-
logs: join(root, "logs")
|
|
9306
|
-
};
|
|
9307
|
-
}
|
|
9308
|
-
|
|
9309
|
-
// src/cli/autostart.ts
|
|
9310
|
-
var LABEL = "com.lakphy.local-router";
|
|
9311
|
-
function getDaemonLogPath() {
|
|
9312
|
-
return getRuntimeDirs().logs + "/daemon.log";
|
|
9313
|
-
}
|
|
9314
|
-
function getLaunchAgentPath() {
|
|
9315
|
-
return join2(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
9316
|
-
}
|
|
9317
|
-
function buildPlist(opts) {
|
|
9318
|
-
const logPath = getDaemonLogPath();
|
|
9319
|
-
const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
|
|
9320
|
-
`);
|
|
9321
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
9322
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9323
|
-
<plist version="1.0">
|
|
9324
|
-
<dict>
|
|
9325
|
-
<key>Label</key>
|
|
9326
|
-
<string>${escapeXml(opts.label)}</string>
|
|
9327
|
-
<key>ProgramArguments</key>
|
|
9328
|
-
<array>
|
|
9329
|
-
${args}
|
|
9330
|
-
</array>
|
|
9331
|
-
<key>RunAtLoad</key>
|
|
9332
|
-
<true/>
|
|
9333
|
-
<key>KeepAlive</key>
|
|
9334
|
-
<false/>
|
|
9335
|
-
<key>StandardOutPath</key>
|
|
9336
|
-
<string>${escapeXml(logPath)}</string>
|
|
9337
|
-
<key>StandardErrorPath</key>
|
|
9338
|
-
<string>${escapeXml(logPath)}</string>
|
|
9339
|
-
</dict>
|
|
9340
|
-
</plist>
|
|
9341
|
-
`;
|
|
9342
|
-
}
|
|
9343
|
-
function escapeXml(s) {
|
|
9344
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
9345
|
-
}
|
|
9346
|
-
function createMacosManager() {
|
|
9347
|
-
const plistPath = getLaunchAgentPath();
|
|
9348
|
-
return {
|
|
9349
|
-
platform: "macos",
|
|
9350
|
-
async isInstalled() {
|
|
9351
|
-
return existsSync(plistPath);
|
|
9352
|
-
},
|
|
9353
|
-
async install(opts) {
|
|
9354
|
-
const dir = dirname(plistPath);
|
|
9355
|
-
if (!existsSync(dir))
|
|
9356
|
-
mkdirSync(dir, { recursive: true });
|
|
9357
|
-
writeFileSync(plistPath, buildPlist(opts), "utf-8");
|
|
9358
|
-
try {
|
|
9359
|
-
execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
|
|
9360
|
-
} catch {}
|
|
9361
|
-
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
|
|
9362
|
-
},
|
|
9363
|
-
async uninstall() {
|
|
9364
|
-
if (!existsSync(plistPath))
|
|
9365
|
-
return;
|
|
9366
|
-
try {
|
|
9367
|
-
execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
|
|
9368
|
-
} catch {}
|
|
9369
|
-
rmSync(plistPath, { force: true });
|
|
9370
|
-
},
|
|
9371
|
-
getServicePath() {
|
|
9372
|
-
return plistPath;
|
|
9373
|
-
}
|
|
9374
|
-
};
|
|
9375
|
-
}
|
|
9376
|
-
function getSystemdUnitPath() {
|
|
9377
|
-
return join2(homedir2(), ".config", "systemd", "user", "local-router.service");
|
|
9378
|
-
}
|
|
9379
|
-
function buildUnit(opts) {
|
|
9380
|
-
const logPath = getDaemonLogPath();
|
|
9381
|
-
const execStart = [opts.execPath, ...opts.args].join(" ");
|
|
9382
|
-
return `[Unit]
|
|
9383
|
-
Description=Local Router API Gateway
|
|
9384
|
-
After=network-online.target
|
|
9385
|
-
|
|
9386
|
-
[Service]
|
|
9387
|
-
Type=simple
|
|
9388
|
-
ExecStart=${execStart}
|
|
9389
|
-
Restart=on-failure
|
|
9390
|
-
RestartSec=5
|
|
9391
|
-
StandardOutput=append:${logPath}
|
|
9392
|
-
StandardError=append:${logPath}
|
|
9393
|
-
|
|
9394
|
-
[Install]
|
|
9395
|
-
WantedBy=default.target
|
|
9396
|
-
`;
|
|
9397
|
-
}
|
|
9398
|
-
function createLinuxManager() {
|
|
9399
|
-
const unitPath = getSystemdUnitPath();
|
|
9400
|
-
return {
|
|
9401
|
-
platform: "linux",
|
|
9402
|
-
async isInstalled() {
|
|
9403
|
-
if (!existsSync(unitPath))
|
|
9404
|
-
return false;
|
|
9405
|
-
try {
|
|
9406
|
-
const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
|
|
9407
|
-
encoding: "utf-8"
|
|
9408
|
-
}).trim();
|
|
9409
|
-
return out === "enabled";
|
|
9410
|
-
} catch {
|
|
9411
|
-
return false;
|
|
9412
|
-
}
|
|
9413
|
-
},
|
|
9414
|
-
async install(opts) {
|
|
9415
|
-
const dir = dirname(unitPath);
|
|
9416
|
-
if (!existsSync(dir))
|
|
9417
|
-
mkdirSync(dir, { recursive: true });
|
|
9418
|
-
writeFileSync(unitPath, buildUnit(opts), "utf-8");
|
|
9419
|
-
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
9420
|
-
execSync("systemctl --user enable local-router", { stdio: "ignore" });
|
|
9421
|
-
},
|
|
9422
|
-
async uninstall() {
|
|
9423
|
-
try {
|
|
9424
|
-
execSync("systemctl --user disable local-router", { stdio: "ignore" });
|
|
9425
|
-
} catch {}
|
|
9426
|
-
rmSync(unitPath, { force: true });
|
|
9427
|
-
try {
|
|
9428
|
-
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
9429
|
-
} catch {}
|
|
9430
|
-
},
|
|
9431
|
-
getServicePath() {
|
|
9432
|
-
return unitPath;
|
|
9433
|
-
}
|
|
9434
|
-
};
|
|
9435
|
-
}
|
|
9436
|
-
var WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
9437
|
-
var WIN_REG_VALUE = "LocalRouter";
|
|
9438
|
-
function createWindowsManager() {
|
|
9439
|
-
return {
|
|
9440
|
-
platform: "windows",
|
|
9441
|
-
async isInstalled() {
|
|
9442
|
-
try {
|
|
9443
|
-
execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
|
|
9444
|
-
return true;
|
|
9445
|
-
} catch {
|
|
9446
|
-
return false;
|
|
9447
|
-
}
|
|
9448
|
-
},
|
|
9449
|
-
async install(opts) {
|
|
9450
|
-
const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
|
|
9451
|
-
execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
|
|
9452
|
-
stdio: "ignore"
|
|
9453
|
-
});
|
|
9454
|
-
},
|
|
9455
|
-
async uninstall() {
|
|
9456
|
-
try {
|
|
9457
|
-
execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
|
|
9458
|
-
} catch {}
|
|
9459
|
-
},
|
|
9460
|
-
getServicePath() {
|
|
9461
|
-
return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
|
|
9462
|
-
}
|
|
9463
|
-
};
|
|
9464
|
-
}
|
|
9465
|
-
function createUnsupportedManager() {
|
|
9466
|
-
return {
|
|
9467
|
-
platform: "unsupported",
|
|
9468
|
-
async isInstalled() {
|
|
9469
|
-
return false;
|
|
9470
|
-
},
|
|
9471
|
-
async install() {
|
|
9472
|
-
throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
|
|
9473
|
-
},
|
|
9474
|
-
async uninstall() {
|
|
9475
|
-
throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
|
|
9476
|
-
},
|
|
9477
|
-
getServicePath() {
|
|
9478
|
-
return "";
|
|
9479
|
-
}
|
|
9480
|
-
};
|
|
9481
|
-
}
|
|
9482
|
-
function createAutostartManager() {
|
|
9483
|
-
const p = platform();
|
|
9484
|
-
if (p === "darwin")
|
|
9485
|
-
return createMacosManager();
|
|
9486
|
-
if (p === "linux")
|
|
9487
|
-
return createLinuxManager();
|
|
9488
|
-
if (p === "win32")
|
|
9489
|
-
return createWindowsManager();
|
|
9490
|
-
return createUnsupportedManager();
|
|
9491
|
-
}
|
|
9492
|
-
function getAutostartExecArgs() {
|
|
9493
|
-
const script = process.argv[1] ?? "dist/cli.js";
|
|
9494
|
-
return {
|
|
9495
|
-
execPath: process.execPath,
|
|
9496
|
-
args: [script, "__run-server", "--mode", "daemon"]
|
|
9497
|
-
};
|
|
9498
|
-
}
|
|
9499
|
-
|
|
9500
9290
|
// node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
|
|
9501
9291
|
var marker = "vercel.ai.error";
|
|
9502
9292
|
var symbol = Symbol.for(marker);
|
|
@@ -29343,7 +29133,7 @@ function createProviderToolFactoryWithOutputSchema({
|
|
|
29343
29133
|
supportsDeferredResults
|
|
29344
29134
|
});
|
|
29345
29135
|
}
|
|
29346
|
-
async function
|
|
29136
|
+
async function resolve(value) {
|
|
29347
29137
|
if (typeof value === "function") {
|
|
29348
29138
|
value = value();
|
|
29349
29139
|
}
|
|
@@ -32288,11 +32078,11 @@ var AnthropicMessagesLanguageModel = class {
|
|
|
32288
32078
|
betas,
|
|
32289
32079
|
headers
|
|
32290
32080
|
}) {
|
|
32291
|
-
return combineHeaders(await
|
|
32081
|
+
return combineHeaders(await resolve(this.config.headers), headers, betas.size > 0 ? { "anthropic-beta": Array.from(betas).join(",") } : {});
|
|
32292
32082
|
}
|
|
32293
32083
|
async getBetasFromHeaders(requestHeaders) {
|
|
32294
32084
|
var _a16, _b16;
|
|
32295
|
-
const configHeaders = await
|
|
32085
|
+
const configHeaders = await resolve(this.config.headers);
|
|
32296
32086
|
const configBetaHeader = (_a16 = configHeaders["anthropic-beta"]) != null ? _a16 : "";
|
|
32297
32087
|
const requestBetaHeader = (_b16 = requestHeaders == null ? undefined : requestHeaders["anthropic-beta"]) != null ? _b16 : "";
|
|
32298
32088
|
return new Set([
|
|
@@ -41477,7 +41267,7 @@ var GatewayFetchMetadata = class {
|
|
|
41477
41267
|
try {
|
|
41478
41268
|
const { value } = await getFromApi({
|
|
41479
41269
|
url: `${this.config.baseURL}/config`,
|
|
41480
|
-
headers: await
|
|
41270
|
+
headers: await resolve(this.config.headers()),
|
|
41481
41271
|
successfulResponseHandler: createJsonResponseHandler(gatewayAvailableModelsResponseSchema),
|
|
41482
41272
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
41483
41273
|
errorSchema: exports_external.any(),
|
|
@@ -41495,7 +41285,7 @@ var GatewayFetchMetadata = class {
|
|
|
41495
41285
|
const baseUrl = new URL(this.config.baseURL);
|
|
41496
41286
|
const { value } = await getFromApi({
|
|
41497
41287
|
url: `${baseUrl.origin}/v1/credits`,
|
|
41498
|
-
headers: await
|
|
41288
|
+
headers: await resolve(this.config.headers()),
|
|
41499
41289
|
successfulResponseHandler: createJsonResponseHandler(gatewayCreditsResponseSchema),
|
|
41500
41290
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
41501
41291
|
errorSchema: exports_external.any(),
|
|
@@ -41560,7 +41350,7 @@ var GatewayLanguageModel = class {
|
|
|
41560
41350
|
async doGenerate(options) {
|
|
41561
41351
|
const { args, warnings } = await this.getArgs(options);
|
|
41562
41352
|
const { abortSignal } = options;
|
|
41563
|
-
const resolvedHeaders = await
|
|
41353
|
+
const resolvedHeaders = await resolve(this.config.headers());
|
|
41564
41354
|
try {
|
|
41565
41355
|
const {
|
|
41566
41356
|
responseHeaders,
|
|
@@ -41568,7 +41358,7 @@ var GatewayLanguageModel = class {
|
|
|
41568
41358
|
rawValue: rawResponse
|
|
41569
41359
|
} = await postJsonToApi({
|
|
41570
41360
|
url: this.getUrl(),
|
|
41571
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await
|
|
41361
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, false), await resolve(this.config.o11yHeaders)),
|
|
41572
41362
|
body: args,
|
|
41573
41363
|
successfulResponseHandler: createJsonResponseHandler(exports_external.any()),
|
|
41574
41364
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -41591,11 +41381,11 @@ var GatewayLanguageModel = class {
|
|
|
41591
41381
|
async doStream(options) {
|
|
41592
41382
|
const { args, warnings } = await this.getArgs(options);
|
|
41593
41383
|
const { abortSignal } = options;
|
|
41594
|
-
const resolvedHeaders = await
|
|
41384
|
+
const resolvedHeaders = await resolve(this.config.headers());
|
|
41595
41385
|
try {
|
|
41596
41386
|
const { value: response, responseHeaders } = await postJsonToApi({
|
|
41597
41387
|
url: this.getUrl(),
|
|
41598
|
-
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await
|
|
41388
|
+
headers: combineHeaders(resolvedHeaders, options.headers, this.getModelConfigHeaders(this.modelId, true), await resolve(this.config.o11yHeaders)),
|
|
41599
41389
|
body: args,
|
|
41600
41390
|
successfulResponseHandler: createEventSourceResponseHandler(exports_external.any()),
|
|
41601
41391
|
failedResponseHandler: createJsonErrorResponseHandler({
|
|
@@ -41681,7 +41471,7 @@ var GatewayEmbeddingModel = class {
|
|
|
41681
41471
|
providerOptions
|
|
41682
41472
|
}) {
|
|
41683
41473
|
var _a92;
|
|
41684
|
-
const resolvedHeaders = await
|
|
41474
|
+
const resolvedHeaders = await resolve(this.config.headers());
|
|
41685
41475
|
try {
|
|
41686
41476
|
const {
|
|
41687
41477
|
responseHeaders,
|
|
@@ -41689,7 +41479,7 @@ var GatewayEmbeddingModel = class {
|
|
|
41689
41479
|
rawValue
|
|
41690
41480
|
} = await postJsonToApi({
|
|
41691
41481
|
url: this.getUrl(),
|
|
41692
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
41482
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
|
|
41693
41483
|
body: {
|
|
41694
41484
|
values,
|
|
41695
41485
|
...providerOptions ? { providerOptions } : {}
|
|
@@ -41751,7 +41541,7 @@ var GatewayImageModel = class {
|
|
|
41751
41541
|
abortSignal
|
|
41752
41542
|
}) {
|
|
41753
41543
|
var _a92, _b92, _c, _d;
|
|
41754
|
-
const resolvedHeaders = await
|
|
41544
|
+
const resolvedHeaders = await resolve(this.config.headers());
|
|
41755
41545
|
try {
|
|
41756
41546
|
const {
|
|
41757
41547
|
responseHeaders,
|
|
@@ -41759,7 +41549,7 @@ var GatewayImageModel = class {
|
|
|
41759
41549
|
rawValue
|
|
41760
41550
|
} = await postJsonToApi({
|
|
41761
41551
|
url: this.getUrl(),
|
|
41762
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
41552
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders)),
|
|
41763
41553
|
body: {
|
|
41764
41554
|
prompt,
|
|
41765
41555
|
n,
|
|
@@ -41874,11 +41664,11 @@ var GatewayVideoModel = class {
|
|
|
41874
41664
|
abortSignal
|
|
41875
41665
|
}) {
|
|
41876
41666
|
var _a92;
|
|
41877
|
-
const resolvedHeaders = await
|
|
41667
|
+
const resolvedHeaders = await resolve(this.config.headers());
|
|
41878
41668
|
try {
|
|
41879
41669
|
const { responseHeaders, value: responseBody } = await postJsonToApi({
|
|
41880
41670
|
url: this.getUrl(),
|
|
41881
|
-
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await
|
|
41671
|
+
headers: combineHeaders(resolvedHeaders, headers != null ? headers : {}, this.getModelConfigHeaders(), await resolve(this.config.o11yHeaders), { accept: "text/event-stream" }),
|
|
41882
41672
|
body: {
|
|
41883
41673
|
prompt,
|
|
41884
41674
|
n,
|
|
@@ -44749,7 +44539,7 @@ var object2 = ({
|
|
|
44749
44539
|
const schema = asSchema(inputSchema);
|
|
44750
44540
|
return {
|
|
44751
44541
|
name: "object",
|
|
44752
|
-
responseFormat:
|
|
44542
|
+
responseFormat: resolve(schema.jsonSchema).then((jsonSchema2) => ({
|
|
44753
44543
|
type: "json",
|
|
44754
44544
|
schema: jsonSchema2,
|
|
44755
44545
|
...name21 != null && { name: name21 },
|
|
@@ -44811,7 +44601,7 @@ var array2 = ({
|
|
|
44811
44601
|
const elementSchema = asSchema(inputElementSchema);
|
|
44812
44602
|
return {
|
|
44813
44603
|
name: "array",
|
|
44814
|
-
responseFormat:
|
|
44604
|
+
responseFormat: resolve(elementSchema.jsonSchema).then((jsonSchema2) => {
|
|
44815
44605
|
const { $schema, ...itemSchema } = jsonSchema2;
|
|
44816
44606
|
return {
|
|
44817
44607
|
type: "json",
|
|
@@ -50070,7 +49860,7 @@ var Hono2 = class extends Hono {
|
|
|
50070
49860
|
|
|
50071
49861
|
// node_modules/.bun/hono@4.12.5/node_modules/hono/dist/adapter/bun/serve-static.js
|
|
50072
49862
|
import { stat } from "fs/promises";
|
|
50073
|
-
import { join
|
|
49863
|
+
import { join } from "path";
|
|
50074
49864
|
|
|
50075
49865
|
// node_modules/.bun/hono@4.12.5/node_modules/hono/dist/utils/compress.js
|
|
50076
49866
|
var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
|
|
@@ -50175,7 +49965,7 @@ var DEFAULT_DOCUMENT = "index.html";
|
|
|
50175
49965
|
var serveStatic = (options) => {
|
|
50176
49966
|
const root = options.root ?? "./";
|
|
50177
49967
|
const optionPath = options.path;
|
|
50178
|
-
const
|
|
49968
|
+
const join = options.join ?? defaultJoin;
|
|
50179
49969
|
return async (c, next) => {
|
|
50180
49970
|
if (c.finalized) {
|
|
50181
49971
|
return next();
|
|
@@ -50194,9 +49984,9 @@ var serveStatic = (options) => {
|
|
|
50194
49984
|
return next();
|
|
50195
49985
|
}
|
|
50196
49986
|
}
|
|
50197
|
-
let path =
|
|
49987
|
+
let path = join(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename);
|
|
50198
49988
|
if (options.isDir && await options.isDir(path)) {
|
|
50199
|
-
path =
|
|
49989
|
+
path = join(path, DEFAULT_DOCUMENT);
|
|
50200
49990
|
}
|
|
50201
49991
|
const getContent = options.getContent;
|
|
50202
49992
|
let content = await getContent(path, c);
|
|
@@ -50248,7 +50038,7 @@ var serveStatic2 = (options) => {
|
|
|
50248
50038
|
return serveStatic({
|
|
50249
50039
|
...options,
|
|
50250
50040
|
getContent,
|
|
50251
|
-
join
|
|
50041
|
+
join,
|
|
50252
50042
|
isDir
|
|
50253
50043
|
})(c, next);
|
|
50254
50044
|
};
|
|
@@ -50339,6 +50129,216 @@ var upgradeWebSocket = defineWebSocketHelper((c, events) => {
|
|
|
50339
50129
|
return;
|
|
50340
50130
|
});
|
|
50341
50131
|
|
|
50132
|
+
// src/cli/autostart.ts
|
|
50133
|
+
import { execSync } from "child_process";
|
|
50134
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
50135
|
+
import { homedir as homedir2, platform } from "os";
|
|
50136
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
50137
|
+
|
|
50138
|
+
// src/cli/runtime.ts
|
|
50139
|
+
import { homedir } from "os";
|
|
50140
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
50141
|
+
function getRuntimeDirs() {
|
|
50142
|
+
const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
|
|
50143
|
+
const root = override?.trim() ? override.trim() : join2(homedir(), ".local-router");
|
|
50144
|
+
return {
|
|
50145
|
+
root,
|
|
50146
|
+
run: join2(root, "run"),
|
|
50147
|
+
logs: join2(root, "logs")
|
|
50148
|
+
};
|
|
50149
|
+
}
|
|
50150
|
+
|
|
50151
|
+
// src/cli/autostart.ts
|
|
50152
|
+
var LABEL = "com.lakphy.local-router";
|
|
50153
|
+
function getDaemonLogPath() {
|
|
50154
|
+
return getRuntimeDirs().logs + "/daemon.log";
|
|
50155
|
+
}
|
|
50156
|
+
function getLaunchAgentPath() {
|
|
50157
|
+
return join3(homedir2(), "Library", "LaunchAgents", `${LABEL}.plist`);
|
|
50158
|
+
}
|
|
50159
|
+
function buildPlist(opts) {
|
|
50160
|
+
const logPath = getDaemonLogPath();
|
|
50161
|
+
const args = [opts.execPath, ...opts.args].map((a) => ` <string>${escapeXml(a)}</string>`).join(`
|
|
50162
|
+
`);
|
|
50163
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
50164
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
50165
|
+
<plist version="1.0">
|
|
50166
|
+
<dict>
|
|
50167
|
+
<key>Label</key>
|
|
50168
|
+
<string>${escapeXml(opts.label)}</string>
|
|
50169
|
+
<key>ProgramArguments</key>
|
|
50170
|
+
<array>
|
|
50171
|
+
${args}
|
|
50172
|
+
</array>
|
|
50173
|
+
<key>RunAtLoad</key>
|
|
50174
|
+
<true/>
|
|
50175
|
+
<key>KeepAlive</key>
|
|
50176
|
+
<false/>
|
|
50177
|
+
<key>StandardOutPath</key>
|
|
50178
|
+
<string>${escapeXml(logPath)}</string>
|
|
50179
|
+
<key>StandardErrorPath</key>
|
|
50180
|
+
<string>${escapeXml(logPath)}</string>
|
|
50181
|
+
</dict>
|
|
50182
|
+
</plist>
|
|
50183
|
+
`;
|
|
50184
|
+
}
|
|
50185
|
+
function escapeXml(s) {
|
|
50186
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
50187
|
+
}
|
|
50188
|
+
function createMacosManager() {
|
|
50189
|
+
const plistPath = getLaunchAgentPath();
|
|
50190
|
+
return {
|
|
50191
|
+
platform: "macos",
|
|
50192
|
+
async isInstalled() {
|
|
50193
|
+
return existsSync(plistPath);
|
|
50194
|
+
},
|
|
50195
|
+
async install(opts) {
|
|
50196
|
+
const dir = dirname2(plistPath);
|
|
50197
|
+
if (!existsSync(dir))
|
|
50198
|
+
mkdirSync(dir, { recursive: true });
|
|
50199
|
+
writeFileSync(plistPath, buildPlist(opts), "utf-8");
|
|
50200
|
+
try {
|
|
50201
|
+
execSync(`launchctl bootout gui/$(id -u) ${plistPath} 2>/dev/null`, { stdio: "ignore" });
|
|
50202
|
+
} catch {}
|
|
50203
|
+
execSync(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
|
|
50204
|
+
},
|
|
50205
|
+
async uninstall() {
|
|
50206
|
+
if (!existsSync(plistPath))
|
|
50207
|
+
return;
|
|
50208
|
+
try {
|
|
50209
|
+
execSync(`launchctl bootout gui/$(id -u) ${plistPath}`, { stdio: "ignore" });
|
|
50210
|
+
} catch {}
|
|
50211
|
+
rmSync(plistPath, { force: true });
|
|
50212
|
+
},
|
|
50213
|
+
getServicePath() {
|
|
50214
|
+
return plistPath;
|
|
50215
|
+
}
|
|
50216
|
+
};
|
|
50217
|
+
}
|
|
50218
|
+
function getSystemdUnitPath() {
|
|
50219
|
+
return join3(homedir2(), ".config", "systemd", "user", "local-router.service");
|
|
50220
|
+
}
|
|
50221
|
+
function buildUnit(opts) {
|
|
50222
|
+
const logPath = getDaemonLogPath();
|
|
50223
|
+
const execStart = [opts.execPath, ...opts.args].join(" ");
|
|
50224
|
+
return `[Unit]
|
|
50225
|
+
Description=Local Router API Gateway
|
|
50226
|
+
After=network-online.target
|
|
50227
|
+
|
|
50228
|
+
[Service]
|
|
50229
|
+
Type=simple
|
|
50230
|
+
ExecStart=${execStart}
|
|
50231
|
+
Restart=on-failure
|
|
50232
|
+
RestartSec=5
|
|
50233
|
+
StandardOutput=append:${logPath}
|
|
50234
|
+
StandardError=append:${logPath}
|
|
50235
|
+
|
|
50236
|
+
[Install]
|
|
50237
|
+
WantedBy=default.target
|
|
50238
|
+
`;
|
|
50239
|
+
}
|
|
50240
|
+
function createLinuxManager() {
|
|
50241
|
+
const unitPath = getSystemdUnitPath();
|
|
50242
|
+
return {
|
|
50243
|
+
platform: "linux",
|
|
50244
|
+
async isInstalled() {
|
|
50245
|
+
if (!existsSync(unitPath))
|
|
50246
|
+
return false;
|
|
50247
|
+
try {
|
|
50248
|
+
const out = execSync("systemctl --user is-enabled local-router 2>/dev/null", {
|
|
50249
|
+
encoding: "utf-8"
|
|
50250
|
+
}).trim();
|
|
50251
|
+
return out === "enabled";
|
|
50252
|
+
} catch {
|
|
50253
|
+
return false;
|
|
50254
|
+
}
|
|
50255
|
+
},
|
|
50256
|
+
async install(opts) {
|
|
50257
|
+
const dir = dirname2(unitPath);
|
|
50258
|
+
if (!existsSync(dir))
|
|
50259
|
+
mkdirSync(dir, { recursive: true });
|
|
50260
|
+
writeFileSync(unitPath, buildUnit(opts), "utf-8");
|
|
50261
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
50262
|
+
execSync("systemctl --user enable local-router", { stdio: "ignore" });
|
|
50263
|
+
},
|
|
50264
|
+
async uninstall() {
|
|
50265
|
+
try {
|
|
50266
|
+
execSync("systemctl --user disable local-router", { stdio: "ignore" });
|
|
50267
|
+
} catch {}
|
|
50268
|
+
rmSync(unitPath, { force: true });
|
|
50269
|
+
try {
|
|
50270
|
+
execSync("systemctl --user daemon-reload", { stdio: "ignore" });
|
|
50271
|
+
} catch {}
|
|
50272
|
+
},
|
|
50273
|
+
getServicePath() {
|
|
50274
|
+
return unitPath;
|
|
50275
|
+
}
|
|
50276
|
+
};
|
|
50277
|
+
}
|
|
50278
|
+
var WIN_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
50279
|
+
var WIN_REG_VALUE = "LocalRouter";
|
|
50280
|
+
function createWindowsManager() {
|
|
50281
|
+
return {
|
|
50282
|
+
platform: "windows",
|
|
50283
|
+
async isInstalled() {
|
|
50284
|
+
try {
|
|
50285
|
+
execSync(`reg query "${WIN_REG_KEY}" /v ${WIN_REG_VALUE}`, { stdio: "ignore" });
|
|
50286
|
+
return true;
|
|
50287
|
+
} catch {
|
|
50288
|
+
return false;
|
|
50289
|
+
}
|
|
50290
|
+
},
|
|
50291
|
+
async install(opts) {
|
|
50292
|
+
const cmd = [opts.execPath, ...opts.args].map((a) => `"${a}"`).join(" ");
|
|
50293
|
+
execSync(`reg add "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /t REG_SZ /d "${cmd}" /f`, {
|
|
50294
|
+
stdio: "ignore"
|
|
50295
|
+
});
|
|
50296
|
+
},
|
|
50297
|
+
async uninstall() {
|
|
50298
|
+
try {
|
|
50299
|
+
execSync(`reg delete "${WIN_REG_KEY}" /v ${WIN_REG_VALUE} /f`, { stdio: "ignore" });
|
|
50300
|
+
} catch {}
|
|
50301
|
+
},
|
|
50302
|
+
getServicePath() {
|
|
50303
|
+
return `${WIN_REG_KEY}\\${WIN_REG_VALUE}`;
|
|
50304
|
+
}
|
|
50305
|
+
};
|
|
50306
|
+
}
|
|
50307
|
+
function createUnsupportedManager() {
|
|
50308
|
+
return {
|
|
50309
|
+
platform: "unsupported",
|
|
50310
|
+
async isInstalled() {
|
|
50311
|
+
return false;
|
|
50312
|
+
},
|
|
50313
|
+
async install() {
|
|
50314
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
|
|
50315
|
+
},
|
|
50316
|
+
async uninstall() {
|
|
50317
|
+
throw new Error("\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8");
|
|
50318
|
+
},
|
|
50319
|
+
getServicePath() {
|
|
50320
|
+
return "";
|
|
50321
|
+
}
|
|
50322
|
+
};
|
|
50323
|
+
}
|
|
50324
|
+
function createAutostartManager() {
|
|
50325
|
+
const p = platform();
|
|
50326
|
+
if (p === "darwin")
|
|
50327
|
+
return createMacosManager();
|
|
50328
|
+
if (p === "linux")
|
|
50329
|
+
return createLinuxManager();
|
|
50330
|
+
if (p === "win32")
|
|
50331
|
+
return createWindowsManager();
|
|
50332
|
+
return createUnsupportedManager();
|
|
50333
|
+
}
|
|
50334
|
+
function getAutostartExecArgs() {
|
|
50335
|
+
const script = process.argv[1] ?? "dist/cli.js";
|
|
50336
|
+
return {
|
|
50337
|
+
execPath: process.execPath,
|
|
50338
|
+
args: [script, "__run-server", "--mode", "daemon"]
|
|
50339
|
+
};
|
|
50340
|
+
}
|
|
50341
|
+
|
|
50342
50342
|
// src/config.ts
|
|
50343
50343
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
50344
50344
|
import { homedir as homedir3 } from "os";
|
|
@@ -51697,403 +51697,12 @@ class CryptoSession {
|
|
|
51697
51697
|
}
|
|
51698
51698
|
|
|
51699
51699
|
// src/log-metrics.ts
|
|
51700
|
-
import { createReadStream, existsSync as
|
|
51700
|
+
import { createReadStream, existsSync as existsSync4 } from "fs";
|
|
51701
51701
|
import { join as join5 } from "path";
|
|
51702
51702
|
import { createInterface } from "readline";
|
|
51703
|
-
var WINDOW_MS = {
|
|
51704
|
-
"1h": 60 * 60 * 1000,
|
|
51705
|
-
"6h": 6 * 60 * 60 * 1000,
|
|
51706
|
-
"24h": 24 * 60 * 60 * 1000
|
|
51707
|
-
};
|
|
51708
|
-
var BUCKET_MS = {
|
|
51709
|
-
"1h": 5 * 60 * 1000,
|
|
51710
|
-
"6h": 15 * 60 * 1000,
|
|
51711
|
-
"24h": 30 * 60 * 1000
|
|
51712
|
-
};
|
|
51713
|
-
var TOP_LIMIT = 5;
|
|
51714
|
-
var MAX_LINES_SCANNED = 250000;
|
|
51715
|
-
var CACHE_TTL_MS = 15000;
|
|
51716
|
-
var metricsCache = new Map;
|
|
51717
|
-
function isLogMetricsWindow(value) {
|
|
51718
|
-
return value === "1h" || value === "6h" || value === "24h";
|
|
51719
|
-
}
|
|
51720
|
-
function toPercent(numerator, denominator) {
|
|
51721
|
-
if (denominator <= 0)
|
|
51722
|
-
return 0;
|
|
51723
|
-
return Number((numerator / denominator * 100).toFixed(2));
|
|
51724
|
-
}
|
|
51725
|
-
function toDayStart(ms) {
|
|
51726
|
-
const date5 = new Date(ms);
|
|
51727
|
-
return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
|
|
51728
|
-
}
|
|
51729
|
-
function listDateStrings(fromMs, toMs) {
|
|
51730
|
-
const result = [];
|
|
51731
|
-
for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
|
|
51732
|
-
result.push(new Date(day).toISOString().slice(0, 10));
|
|
51733
|
-
}
|
|
51734
|
-
return result;
|
|
51735
|
-
}
|
|
51736
|
-
function getStatusClass(event) {
|
|
51737
|
-
if (event.error_type)
|
|
51738
|
-
return "network_error";
|
|
51739
|
-
const status = event.upstream_status ?? 0;
|
|
51740
|
-
if (status >= 200 && status < 300)
|
|
51741
|
-
return "2xx";
|
|
51742
|
-
if (status >= 400 && status < 500)
|
|
51743
|
-
return "4xx";
|
|
51744
|
-
if (status >= 500)
|
|
51745
|
-
return "5xx";
|
|
51746
|
-
return "network_error";
|
|
51747
|
-
}
|
|
51748
|
-
function isErrorEvent(event) {
|
|
51749
|
-
if (event.error_type)
|
|
51750
|
-
return true;
|
|
51751
|
-
const status = event.upstream_status ?? 0;
|
|
51752
|
-
return status < 200 || status >= 400;
|
|
51753
|
-
}
|
|
51754
|
-
function percentile(sortedNumbers, ratio) {
|
|
51755
|
-
if (sortedNumbers.length === 0)
|
|
51756
|
-
return 0;
|
|
51757
|
-
const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
|
|
51758
|
-
return Math.round(sortedNumbers[index]);
|
|
51759
|
-
}
|
|
51760
|
-
function createEmptyMetrics(window2, nowMs, source2, warnings = []) {
|
|
51761
|
-
const fromMs = nowMs - WINDOW_MS[window2];
|
|
51762
|
-
const bucketMs = BUCKET_MS[window2];
|
|
51763
|
-
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
51764
|
-
const series = Array.from({ length: bucketCount }, (_, i) => ({
|
|
51765
|
-
ts: new Date(fromMs + i * bucketMs).toISOString(),
|
|
51766
|
-
requests: 0,
|
|
51767
|
-
errors: 0,
|
|
51768
|
-
avgLatencyMs: 0
|
|
51769
|
-
}));
|
|
51770
|
-
return {
|
|
51771
|
-
window: window2,
|
|
51772
|
-
from: new Date(fromMs).toISOString(),
|
|
51773
|
-
to: new Date(nowMs).toISOString(),
|
|
51774
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
51775
|
-
source: source2,
|
|
51776
|
-
summary: {
|
|
51777
|
-
totalRequests: 0,
|
|
51778
|
-
successRequests: 0,
|
|
51779
|
-
errorRequests: 0,
|
|
51780
|
-
successRate: 0,
|
|
51781
|
-
avgLatencyMs: 0,
|
|
51782
|
-
p95LatencyMs: 0,
|
|
51783
|
-
totalRequestBytes: 0,
|
|
51784
|
-
totalResponseBytes: 0
|
|
51785
|
-
},
|
|
51786
|
-
series,
|
|
51787
|
-
topProviders: [],
|
|
51788
|
-
topRouteTypes: [],
|
|
51789
|
-
statusClasses: {
|
|
51790
|
-
"2xx": 0,
|
|
51791
|
-
"4xx": 0,
|
|
51792
|
-
"5xx": 0,
|
|
51793
|
-
network_error: 0
|
|
51794
|
-
},
|
|
51795
|
-
warnings
|
|
51796
|
-
};
|
|
51797
|
-
}
|
|
51798
|
-
async function getLogMetrics(options) {
|
|
51799
|
-
const window2 = options.window ?? "24h";
|
|
51800
|
-
const refresh = options.refresh === true;
|
|
51801
|
-
const nowMs = options.nowMs ?? Date.now();
|
|
51802
|
-
const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
|
|
51803
|
-
if (!logEnabled) {
|
|
51804
|
-
return createEmptyMetrics(window2, nowMs, {
|
|
51805
|
-
logEnabled: false,
|
|
51806
|
-
baseDir: null,
|
|
51807
|
-
filesScanned: 0,
|
|
51808
|
-
linesScanned: 0,
|
|
51809
|
-
partial: false
|
|
51810
|
-
}, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
|
|
51811
|
-
}
|
|
51812
|
-
const baseDir = resolveLogBaseDir(options.logConfig);
|
|
51813
|
-
const cacheKey = `${baseDir}:${window2}`;
|
|
51814
|
-
const cached2 = metricsCache.get(cacheKey);
|
|
51815
|
-
if (!refresh && cached2 && cached2.expiresAt > nowMs) {
|
|
51816
|
-
return cached2.value;
|
|
51817
|
-
}
|
|
51818
|
-
const eventsDir = join5(baseDir, "events");
|
|
51819
|
-
if (!existsSync3(eventsDir)) {
|
|
51820
|
-
const empty = createEmptyMetrics(window2, nowMs, {
|
|
51821
|
-
logEnabled: true,
|
|
51822
|
-
baseDir,
|
|
51823
|
-
filesScanned: 0,
|
|
51824
|
-
linesScanned: 0,
|
|
51825
|
-
partial: false
|
|
51826
|
-
}, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
|
|
51827
|
-
metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
|
|
51828
|
-
return empty;
|
|
51829
|
-
}
|
|
51830
|
-
const fromMs = nowMs - WINDOW_MS[window2];
|
|
51831
|
-
const bucketMs = BUCKET_MS[window2];
|
|
51832
|
-
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
51833
|
-
const buckets = Array.from({ length: bucketCount }, () => ({
|
|
51834
|
-
requests: 0,
|
|
51835
|
-
errors: 0,
|
|
51836
|
-
latencySum: 0,
|
|
51837
|
-
latencyCount: 0
|
|
51838
|
-
}));
|
|
51839
|
-
const providerAgg = new Map;
|
|
51840
|
-
const routeTypeAgg = new Map;
|
|
51841
|
-
const latencies = [];
|
|
51842
|
-
const statusClasses = {
|
|
51843
|
-
"2xx": 0,
|
|
51844
|
-
"4xx": 0,
|
|
51845
|
-
"5xx": 0,
|
|
51846
|
-
network_error: 0
|
|
51847
|
-
};
|
|
51848
|
-
let filesScanned = 0;
|
|
51849
|
-
let linesScanned = 0;
|
|
51850
|
-
let parseErrors = 0;
|
|
51851
|
-
let partial2 = false;
|
|
51852
|
-
let totalRequests = 0;
|
|
51853
|
-
let successRequests = 0;
|
|
51854
|
-
let errorRequests = 0;
|
|
51855
|
-
let totalLatency = 0;
|
|
51856
|
-
let totalRequestBytes = 0;
|
|
51857
|
-
let totalResponseBytes = 0;
|
|
51858
|
-
const warnings = [];
|
|
51859
|
-
const dateStrings = listDateStrings(fromMs, nowMs);
|
|
51860
|
-
for (const dateStr of dateStrings) {
|
|
51861
|
-
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
51862
|
-
partial2 = true;
|
|
51863
|
-
break;
|
|
51864
|
-
}
|
|
51865
|
-
const filePath = join5(eventsDir, `${dateStr}.jsonl`);
|
|
51866
|
-
if (!existsSync3(filePath))
|
|
51867
|
-
continue;
|
|
51868
|
-
filesScanned += 1;
|
|
51869
|
-
try {
|
|
51870
|
-
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
51871
|
-
const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
51872
|
-
for await (const line2 of rl) {
|
|
51873
|
-
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
51874
|
-
partial2 = true;
|
|
51875
|
-
rl.close();
|
|
51876
|
-
stream.destroy();
|
|
51877
|
-
break;
|
|
51878
|
-
}
|
|
51879
|
-
linesScanned += 1;
|
|
51880
|
-
if (!line2.trim())
|
|
51881
|
-
continue;
|
|
51882
|
-
let event;
|
|
51883
|
-
try {
|
|
51884
|
-
event = JSON.parse(line2);
|
|
51885
|
-
} catch {
|
|
51886
|
-
parseErrors += 1;
|
|
51887
|
-
continue;
|
|
51888
|
-
}
|
|
51889
|
-
if (!event.ts_start)
|
|
51890
|
-
continue;
|
|
51891
|
-
const ts = Date.parse(event.ts_start);
|
|
51892
|
-
if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
|
|
51893
|
-
continue;
|
|
51894
|
-
totalRequests += 1;
|
|
51895
|
-
const isError = isErrorEvent(event);
|
|
51896
|
-
if (isError) {
|
|
51897
|
-
errorRequests += 1;
|
|
51898
|
-
} else {
|
|
51899
|
-
successRequests += 1;
|
|
51900
|
-
}
|
|
51901
|
-
const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
|
|
51902
|
-
totalLatency += latency;
|
|
51903
|
-
latencies.push(latency);
|
|
51904
|
-
totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
|
|
51905
|
-
totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
|
|
51906
|
-
const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
|
|
51907
|
-
const bucket = buckets[bucketIndex];
|
|
51908
|
-
bucket.requests += 1;
|
|
51909
|
-
bucket.latencySum += latency;
|
|
51910
|
-
bucket.latencyCount += 1;
|
|
51911
|
-
if (isError)
|
|
51912
|
-
bucket.errors += 1;
|
|
51913
|
-
const providerKey = event.provider || "unknown";
|
|
51914
|
-
const providerRow = providerAgg.get(providerKey) ?? {
|
|
51915
|
-
requests: 0,
|
|
51916
|
-
errors: 0,
|
|
51917
|
-
latencySum: 0
|
|
51918
|
-
};
|
|
51919
|
-
providerRow.requests += 1;
|
|
51920
|
-
providerRow.latencySum += latency;
|
|
51921
|
-
if (isError)
|
|
51922
|
-
providerRow.errors += 1;
|
|
51923
|
-
providerAgg.set(providerKey, providerRow);
|
|
51924
|
-
const routeTypeKey = event.route_type || "unknown";
|
|
51925
|
-
const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
|
|
51926
|
-
requests: 0,
|
|
51927
|
-
errors: 0,
|
|
51928
|
-
latencySum: 0
|
|
51929
|
-
};
|
|
51930
|
-
routeTypeRow.requests += 1;
|
|
51931
|
-
routeTypeRow.latencySum += latency;
|
|
51932
|
-
if (isError)
|
|
51933
|
-
routeTypeRow.errors += 1;
|
|
51934
|
-
routeTypeAgg.set(routeTypeKey, routeTypeRow);
|
|
51935
|
-
statusClasses[getStatusClass(event)] += 1;
|
|
51936
|
-
}
|
|
51937
|
-
} catch (err) {
|
|
51938
|
-
warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
|
|
51939
|
-
partial2 = true;
|
|
51940
|
-
}
|
|
51941
|
-
}
|
|
51942
|
-
if (parseErrors > 0) {
|
|
51943
|
-
warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
|
|
51944
|
-
}
|
|
51945
|
-
if (partial2) {
|
|
51946
|
-
warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
|
|
51947
|
-
}
|
|
51948
|
-
latencies.sort((a, b) => a - b);
|
|
51949
|
-
const series = buckets.map((bucket, index) => ({
|
|
51950
|
-
ts: new Date(fromMs + index * bucketMs).toISOString(),
|
|
51951
|
-
requests: bucket.requests,
|
|
51952
|
-
errors: bucket.errors,
|
|
51953
|
-
avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
|
|
51954
|
-
}));
|
|
51955
|
-
const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
|
|
51956
|
-
key: key2,
|
|
51957
|
-
requests: row.requests,
|
|
51958
|
-
errorRate: toPercent(row.errors, row.requests),
|
|
51959
|
-
avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
|
|
51960
|
-
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
51961
|
-
const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
|
|
51962
|
-
key: key2,
|
|
51963
|
-
requests: row.requests,
|
|
51964
|
-
errorRate: toPercent(row.errors, row.requests)
|
|
51965
|
-
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
51966
|
-
const response = {
|
|
51967
|
-
window: window2,
|
|
51968
|
-
from: new Date(fromMs).toISOString(),
|
|
51969
|
-
to: new Date(nowMs).toISOString(),
|
|
51970
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
51971
|
-
source: {
|
|
51972
|
-
logEnabled: true,
|
|
51973
|
-
baseDir,
|
|
51974
|
-
filesScanned,
|
|
51975
|
-
linesScanned,
|
|
51976
|
-
partial: partial2
|
|
51977
|
-
},
|
|
51978
|
-
summary: {
|
|
51979
|
-
totalRequests,
|
|
51980
|
-
successRequests,
|
|
51981
|
-
errorRequests,
|
|
51982
|
-
successRate: toPercent(successRequests, totalRequests),
|
|
51983
|
-
avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
|
|
51984
|
-
p95LatencyMs: percentile(latencies, 0.95),
|
|
51985
|
-
totalRequestBytes,
|
|
51986
|
-
totalResponseBytes
|
|
51987
|
-
},
|
|
51988
|
-
series,
|
|
51989
|
-
topProviders,
|
|
51990
|
-
topRouteTypes,
|
|
51991
|
-
statusClasses,
|
|
51992
|
-
warnings
|
|
51993
|
-
};
|
|
51994
|
-
metricsCache.set(cacheKey, {
|
|
51995
|
-
expiresAt: nowMs + CACHE_TTL_MS,
|
|
51996
|
-
value: response
|
|
51997
|
-
});
|
|
51998
|
-
return response;
|
|
51999
|
-
}
|
|
52000
|
-
|
|
52001
|
-
// src/log-query.ts
|
|
52002
|
-
import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
52003
|
-
import { join as join7, resolve as resolve6 } from "path";
|
|
52004
|
-
import { createInterface as createInterface2 } from "readline";
|
|
52005
|
-
|
|
52006
|
-
// src/log-index.ts
|
|
52007
|
-
import { Database } from "bun:sqlite";
|
|
52008
|
-
import {
|
|
52009
|
-
closeSync,
|
|
52010
|
-
createReadStream as createReadStream2,
|
|
52011
|
-
existsSync as existsSync5,
|
|
52012
|
-
mkdirSync as mkdirSync3,
|
|
52013
|
-
openSync,
|
|
52014
|
-
readSync,
|
|
52015
|
-
statSync as statSync2
|
|
52016
|
-
} from "fs";
|
|
52017
|
-
import { join as join6 } from "path";
|
|
52018
|
-
|
|
52019
|
-
// src/log-session-identity.ts
|
|
52020
|
-
var USER_SESSION_DELIMITER = "_account__session_";
|
|
52021
|
-
function toRecord(value) {
|
|
52022
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52023
|
-
return null;
|
|
52024
|
-
return value;
|
|
52025
|
-
}
|
|
52026
|
-
function extractUserIdRawFromRequestBody(requestBody) {
|
|
52027
|
-
const requestBodyRecord = toRecord(requestBody);
|
|
52028
|
-
const metadata = toRecord(requestBodyRecord?.metadata);
|
|
52029
|
-
if (!metadata) {
|
|
52030
|
-
return {
|
|
52031
|
-
hasMetadata: false,
|
|
52032
|
-
userIdRaw: null
|
|
52033
|
-
};
|
|
52034
|
-
}
|
|
52035
|
-
const userId = metadata.user_id;
|
|
52036
|
-
if (typeof userId !== "string" || userId.trim() === "") {
|
|
52037
|
-
return {
|
|
52038
|
-
hasMetadata: true,
|
|
52039
|
-
userIdRaw: null
|
|
52040
|
-
};
|
|
52041
|
-
}
|
|
52042
|
-
return {
|
|
52043
|
-
hasMetadata: true,
|
|
52044
|
-
userIdRaw: userId
|
|
52045
|
-
};
|
|
52046
|
-
}
|
|
52047
|
-
function parseUserSessionFromJsonFormat(userIdRaw) {
|
|
52048
|
-
let parsed;
|
|
52049
|
-
try {
|
|
52050
|
-
parsed = JSON.parse(userIdRaw);
|
|
52051
|
-
} catch {
|
|
52052
|
-
return null;
|
|
52053
|
-
}
|
|
52054
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
52055
|
-
return null;
|
|
52056
|
-
const obj = parsed;
|
|
52057
|
-
const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
|
|
52058
|
-
if (!sessionId)
|
|
52059
|
-
return null;
|
|
52060
|
-
const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
|
|
52061
|
-
return { userKey: userKey || sessionId, sessionId };
|
|
52062
|
-
}
|
|
52063
|
-
function parseUserSessionFromUserIdRaw(userIdRaw) {
|
|
52064
|
-
if (userIdRaw.trimStart().startsWith("{")) {
|
|
52065
|
-
return parseUserSessionFromJsonFormat(userIdRaw);
|
|
52066
|
-
}
|
|
52067
|
-
const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
|
|
52068
|
-
if (index <= 0)
|
|
52069
|
-
return null;
|
|
52070
|
-
const userKey = userIdRaw.slice(0, index).trim();
|
|
52071
|
-
const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
|
|
52072
|
-
if (!userKey || !sessionId)
|
|
52073
|
-
return null;
|
|
52074
|
-
return { userKey, sessionId };
|
|
52075
|
-
}
|
|
52076
|
-
function resolveLogSessionIdentity(requestBody) {
|
|
52077
|
-
const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
|
|
52078
|
-
if (!userIdRaw) {
|
|
52079
|
-
return {
|
|
52080
|
-
hasMetadata,
|
|
52081
|
-
userIdRaw: null,
|
|
52082
|
-
userKey: null,
|
|
52083
|
-
sessionId: null
|
|
52084
|
-
};
|
|
52085
|
-
}
|
|
52086
|
-
const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
|
|
52087
|
-
return {
|
|
52088
|
-
hasMetadata,
|
|
52089
|
-
userIdRaw,
|
|
52090
|
-
userKey: parsed?.userKey ?? null,
|
|
52091
|
-
sessionId: parsed?.sessionId ?? null
|
|
52092
|
-
};
|
|
52093
|
-
}
|
|
52094
51703
|
|
|
52095
51704
|
// src/token-usage.ts
|
|
52096
|
-
import { existsSync as
|
|
51705
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
52097
51706
|
import { resolve as resolve5 } from "path";
|
|
52098
51707
|
var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
|
|
52099
51708
|
function asRecord(value) {
|
|
@@ -52187,7 +51796,7 @@ function inferProviderStyle(usage, providerHint) {
|
|
|
52187
51796
|
}
|
|
52188
51797
|
return "unknown";
|
|
52189
51798
|
}
|
|
52190
|
-
function
|
|
51799
|
+
function createEmptyMetrics(input) {
|
|
52191
51800
|
return {
|
|
52192
51801
|
schemaVersion: 1,
|
|
52193
51802
|
source: input.source,
|
|
@@ -52243,7 +51852,7 @@ function hasAnyTokenSignal(metrics) {
|
|
|
52243
51852
|
function normalizeUsageObject(input) {
|
|
52244
51853
|
const { usage, source: source2, rawUsagePath, providerHint } = input;
|
|
52245
51854
|
const providerStyle = inferProviderStyle(usage, providerHint);
|
|
52246
|
-
const metrics =
|
|
51855
|
+
const metrics = createEmptyMetrics({
|
|
52247
51856
|
source: source2,
|
|
52248
51857
|
providerStyle,
|
|
52249
51858
|
rawUsage: usage,
|
|
@@ -52616,7 +52225,7 @@ function safeReadStreamFile(streamFile, baseDir) {
|
|
|
52616
52225
|
const resolved = resolve5(candidate);
|
|
52617
52226
|
if (!resolved.endsWith(".sse.raw"))
|
|
52618
52227
|
continue;
|
|
52619
|
-
if (!
|
|
52228
|
+
if (!existsSync3(resolved))
|
|
52620
52229
|
continue;
|
|
52621
52230
|
const stats = statSync(resolved);
|
|
52622
52231
|
if (stats.size > MAX_STREAM_USAGE_BYTES) {
|
|
@@ -52672,6 +52281,448 @@ function enrichLogEventTokenUsage(event, options = {}) {
|
|
|
52672
52281
|
};
|
|
52673
52282
|
}
|
|
52674
52283
|
|
|
52284
|
+
// src/log-metrics.ts
|
|
52285
|
+
var WINDOW_MS = {
|
|
52286
|
+
"1h": 60 * 60 * 1000,
|
|
52287
|
+
"6h": 6 * 60 * 60 * 1000,
|
|
52288
|
+
"24h": 24 * 60 * 60 * 1000
|
|
52289
|
+
};
|
|
52290
|
+
var BUCKET_MS = {
|
|
52291
|
+
"1h": 5 * 60 * 1000,
|
|
52292
|
+
"6h": 15 * 60 * 1000,
|
|
52293
|
+
"24h": 30 * 60 * 1000
|
|
52294
|
+
};
|
|
52295
|
+
var TOP_LIMIT = 5;
|
|
52296
|
+
var MAX_LINES_SCANNED = 250000;
|
|
52297
|
+
var CACHE_TTL_MS = 15000;
|
|
52298
|
+
var metricsCache = new Map;
|
|
52299
|
+
function isLogMetricsWindow(value) {
|
|
52300
|
+
return value === "1h" || value === "6h" || value === "24h";
|
|
52301
|
+
}
|
|
52302
|
+
function toPercent(numerator, denominator) {
|
|
52303
|
+
if (denominator <= 0)
|
|
52304
|
+
return 0;
|
|
52305
|
+
return Number((numerator / denominator * 100).toFixed(2));
|
|
52306
|
+
}
|
|
52307
|
+
function toDayStart(ms) {
|
|
52308
|
+
const date5 = new Date(ms);
|
|
52309
|
+
return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
|
|
52310
|
+
}
|
|
52311
|
+
function listDateStrings(fromMs, toMs) {
|
|
52312
|
+
const result = [];
|
|
52313
|
+
for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
|
|
52314
|
+
result.push(new Date(day).toISOString().slice(0, 10));
|
|
52315
|
+
}
|
|
52316
|
+
return result;
|
|
52317
|
+
}
|
|
52318
|
+
function getStatusClass(event) {
|
|
52319
|
+
if (event.error_type)
|
|
52320
|
+
return "network_error";
|
|
52321
|
+
const status = event.upstream_status ?? 0;
|
|
52322
|
+
if (status >= 200 && status < 300)
|
|
52323
|
+
return "2xx";
|
|
52324
|
+
if (status >= 400 && status < 500)
|
|
52325
|
+
return "4xx";
|
|
52326
|
+
if (status >= 500)
|
|
52327
|
+
return "5xx";
|
|
52328
|
+
return "network_error";
|
|
52329
|
+
}
|
|
52330
|
+
function isErrorEvent(event) {
|
|
52331
|
+
if (event.error_type)
|
|
52332
|
+
return true;
|
|
52333
|
+
const status = event.upstream_status ?? 0;
|
|
52334
|
+
return status < 200 || status >= 400;
|
|
52335
|
+
}
|
|
52336
|
+
function percentile(sortedNumbers, ratio) {
|
|
52337
|
+
if (sortedNumbers.length === 0)
|
|
52338
|
+
return 0;
|
|
52339
|
+
const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
|
|
52340
|
+
return Math.round(sortedNumbers[index]);
|
|
52341
|
+
}
|
|
52342
|
+
function createEmptyMetrics2(window2, nowMs, source2, warnings = []) {
|
|
52343
|
+
const fromMs = nowMs - WINDOW_MS[window2];
|
|
52344
|
+
const bucketMs = BUCKET_MS[window2];
|
|
52345
|
+
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
52346
|
+
const series = Array.from({ length: bucketCount }, (_, i) => ({
|
|
52347
|
+
ts: new Date(fromMs + i * bucketMs).toISOString(),
|
|
52348
|
+
requests: 0,
|
|
52349
|
+
errors: 0,
|
|
52350
|
+
avgLatencyMs: 0
|
|
52351
|
+
}));
|
|
52352
|
+
return {
|
|
52353
|
+
window: window2,
|
|
52354
|
+
from: new Date(fromMs).toISOString(),
|
|
52355
|
+
to: new Date(nowMs).toISOString(),
|
|
52356
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
52357
|
+
source: source2,
|
|
52358
|
+
summary: {
|
|
52359
|
+
totalRequests: 0,
|
|
52360
|
+
successRequests: 0,
|
|
52361
|
+
errorRequests: 0,
|
|
52362
|
+
successRate: 0,
|
|
52363
|
+
avgLatencyMs: 0,
|
|
52364
|
+
p95LatencyMs: 0,
|
|
52365
|
+
totalRequestBytes: 0,
|
|
52366
|
+
totalResponseBytes: 0
|
|
52367
|
+
},
|
|
52368
|
+
tokens: {
|
|
52369
|
+
usageCount: 0,
|
|
52370
|
+
inputTokens: 0,
|
|
52371
|
+
outputTokens: 0,
|
|
52372
|
+
totalTokens: 0,
|
|
52373
|
+
cachedInputTokens: 0,
|
|
52374
|
+
cacheHitInputTokens: 0,
|
|
52375
|
+
cacheHitRateDenominatorTokens: 0,
|
|
52376
|
+
cacheHitRate: 0,
|
|
52377
|
+
reasoningTokens: 0,
|
|
52378
|
+
cost: null
|
|
52379
|
+
},
|
|
52380
|
+
series,
|
|
52381
|
+
topProviders: [],
|
|
52382
|
+
topRouteTypes: [],
|
|
52383
|
+
statusClasses: {
|
|
52384
|
+
"2xx": 0,
|
|
52385
|
+
"4xx": 0,
|
|
52386
|
+
"5xx": 0,
|
|
52387
|
+
network_error: 0
|
|
52388
|
+
},
|
|
52389
|
+
warnings
|
|
52390
|
+
};
|
|
52391
|
+
}
|
|
52392
|
+
async function getLogMetrics(options) {
|
|
52393
|
+
const window2 = options.window ?? "24h";
|
|
52394
|
+
const refresh = options.refresh === true;
|
|
52395
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
52396
|
+
const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
|
|
52397
|
+
if (!logEnabled) {
|
|
52398
|
+
return createEmptyMetrics2(window2, nowMs, {
|
|
52399
|
+
logEnabled: false,
|
|
52400
|
+
baseDir: null,
|
|
52401
|
+
filesScanned: 0,
|
|
52402
|
+
linesScanned: 0,
|
|
52403
|
+
partial: false
|
|
52404
|
+
}, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
|
|
52405
|
+
}
|
|
52406
|
+
const baseDir = resolveLogBaseDir(options.logConfig);
|
|
52407
|
+
const cacheKey = `${baseDir}:${window2}`;
|
|
52408
|
+
const cached2 = metricsCache.get(cacheKey);
|
|
52409
|
+
if (!refresh && cached2 && cached2.expiresAt > nowMs) {
|
|
52410
|
+
return cached2.value;
|
|
52411
|
+
}
|
|
52412
|
+
const eventsDir = join5(baseDir, "events");
|
|
52413
|
+
if (!existsSync4(eventsDir)) {
|
|
52414
|
+
const empty = createEmptyMetrics2(window2, nowMs, {
|
|
52415
|
+
logEnabled: true,
|
|
52416
|
+
baseDir,
|
|
52417
|
+
filesScanned: 0,
|
|
52418
|
+
linesScanned: 0,
|
|
52419
|
+
partial: false
|
|
52420
|
+
}, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
|
|
52421
|
+
metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
|
|
52422
|
+
return empty;
|
|
52423
|
+
}
|
|
52424
|
+
const fromMs = nowMs - WINDOW_MS[window2];
|
|
52425
|
+
const bucketMs = BUCKET_MS[window2];
|
|
52426
|
+
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
52427
|
+
const buckets = Array.from({ length: bucketCount }, () => ({
|
|
52428
|
+
requests: 0,
|
|
52429
|
+
errors: 0,
|
|
52430
|
+
latencySum: 0,
|
|
52431
|
+
latencyCount: 0
|
|
52432
|
+
}));
|
|
52433
|
+
const providerAgg = new Map;
|
|
52434
|
+
const routeTypeAgg = new Map;
|
|
52435
|
+
const latencies = [];
|
|
52436
|
+
const statusClasses = {
|
|
52437
|
+
"2xx": 0,
|
|
52438
|
+
"4xx": 0,
|
|
52439
|
+
"5xx": 0,
|
|
52440
|
+
network_error: 0
|
|
52441
|
+
};
|
|
52442
|
+
let filesScanned = 0;
|
|
52443
|
+
let linesScanned = 0;
|
|
52444
|
+
let parseErrors = 0;
|
|
52445
|
+
let partial2 = false;
|
|
52446
|
+
let totalRequests = 0;
|
|
52447
|
+
let successRequests = 0;
|
|
52448
|
+
let errorRequests = 0;
|
|
52449
|
+
let totalLatency = 0;
|
|
52450
|
+
let totalRequestBytes = 0;
|
|
52451
|
+
let totalResponseBytes = 0;
|
|
52452
|
+
let tokenUsageCount = 0;
|
|
52453
|
+
let tokenInput = 0;
|
|
52454
|
+
let tokenOutput = 0;
|
|
52455
|
+
let tokenTotal = 0;
|
|
52456
|
+
let tokenCachedInput = 0;
|
|
52457
|
+
let tokenCacheHitInput = 0;
|
|
52458
|
+
let tokenCacheHitDenominator = 0;
|
|
52459
|
+
let tokenReasoning = 0;
|
|
52460
|
+
let tokenCost = 0;
|
|
52461
|
+
let tokenCostSeen = false;
|
|
52462
|
+
const warnings = [];
|
|
52463
|
+
const dateStrings = listDateStrings(fromMs, nowMs);
|
|
52464
|
+
for (const dateStr of dateStrings) {
|
|
52465
|
+
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
52466
|
+
partial2 = true;
|
|
52467
|
+
break;
|
|
52468
|
+
}
|
|
52469
|
+
const filePath = join5(eventsDir, `${dateStr}.jsonl`);
|
|
52470
|
+
if (!existsSync4(filePath))
|
|
52471
|
+
continue;
|
|
52472
|
+
filesScanned += 1;
|
|
52473
|
+
try {
|
|
52474
|
+
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
52475
|
+
const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
52476
|
+
for await (const line2 of rl) {
|
|
52477
|
+
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
52478
|
+
partial2 = true;
|
|
52479
|
+
rl.close();
|
|
52480
|
+
stream.destroy();
|
|
52481
|
+
break;
|
|
52482
|
+
}
|
|
52483
|
+
linesScanned += 1;
|
|
52484
|
+
if (!line2.trim())
|
|
52485
|
+
continue;
|
|
52486
|
+
let event;
|
|
52487
|
+
try {
|
|
52488
|
+
event = JSON.parse(line2);
|
|
52489
|
+
} catch {
|
|
52490
|
+
parseErrors += 1;
|
|
52491
|
+
continue;
|
|
52492
|
+
}
|
|
52493
|
+
if (!event.ts_start)
|
|
52494
|
+
continue;
|
|
52495
|
+
const ts = Date.parse(event.ts_start);
|
|
52496
|
+
if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
|
|
52497
|
+
continue;
|
|
52498
|
+
totalRequests += 1;
|
|
52499
|
+
const isError = isErrorEvent(event);
|
|
52500
|
+
if (isError) {
|
|
52501
|
+
errorRequests += 1;
|
|
52502
|
+
} else {
|
|
52503
|
+
successRequests += 1;
|
|
52504
|
+
}
|
|
52505
|
+
const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
|
|
52506
|
+
totalLatency += latency;
|
|
52507
|
+
latencies.push(latency);
|
|
52508
|
+
totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
|
|
52509
|
+
totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
|
|
52510
|
+
const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
|
|
52511
|
+
const bucket = buckets[bucketIndex];
|
|
52512
|
+
bucket.requests += 1;
|
|
52513
|
+
bucket.latencySum += latency;
|
|
52514
|
+
bucket.latencyCount += 1;
|
|
52515
|
+
if (isError)
|
|
52516
|
+
bucket.errors += 1;
|
|
52517
|
+
const providerKey = event.provider || "unknown";
|
|
52518
|
+
const providerRow = providerAgg.get(providerKey) ?? {
|
|
52519
|
+
requests: 0,
|
|
52520
|
+
errors: 0,
|
|
52521
|
+
latencySum: 0
|
|
52522
|
+
};
|
|
52523
|
+
providerRow.requests += 1;
|
|
52524
|
+
providerRow.latencySum += latency;
|
|
52525
|
+
if (isError)
|
|
52526
|
+
providerRow.errors += 1;
|
|
52527
|
+
providerAgg.set(providerKey, providerRow);
|
|
52528
|
+
const routeTypeKey = event.route_type || "unknown";
|
|
52529
|
+
const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
|
|
52530
|
+
requests: 0,
|
|
52531
|
+
errors: 0,
|
|
52532
|
+
latencySum: 0
|
|
52533
|
+
};
|
|
52534
|
+
routeTypeRow.requests += 1;
|
|
52535
|
+
routeTypeRow.latencySum += latency;
|
|
52536
|
+
if (isError)
|
|
52537
|
+
routeTypeRow.errors += 1;
|
|
52538
|
+
routeTypeAgg.set(routeTypeKey, routeTypeRow);
|
|
52539
|
+
statusClasses[getStatusClass(event)] += 1;
|
|
52540
|
+
const usage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
|
|
52541
|
+
if (usage) {
|
|
52542
|
+
tokenUsageCount += 1;
|
|
52543
|
+
tokenInput += Math.max(0, usage.inputTokens ?? 0);
|
|
52544
|
+
tokenOutput += Math.max(0, usage.outputTokens ?? 0);
|
|
52545
|
+
tokenTotal += Math.max(0, usage.totalTokens ?? 0);
|
|
52546
|
+
tokenCachedInput += Math.max(0, usage.cachedInputTokens ?? 0);
|
|
52547
|
+
tokenCacheHitInput += Math.max(0, usage.cacheHitInputTokens ?? 0);
|
|
52548
|
+
tokenCacheHitDenominator += Math.max(0, usage.cacheHitRateDenominatorTokens ?? 0);
|
|
52549
|
+
tokenReasoning += Math.max(0, usage.reasoningTokens ?? 0);
|
|
52550
|
+
if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
|
|
52551
|
+
tokenCost += usage.cost;
|
|
52552
|
+
tokenCostSeen = true;
|
|
52553
|
+
}
|
|
52554
|
+
}
|
|
52555
|
+
}
|
|
52556
|
+
} catch (err) {
|
|
52557
|
+
warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
|
|
52558
|
+
partial2 = true;
|
|
52559
|
+
}
|
|
52560
|
+
}
|
|
52561
|
+
if (parseErrors > 0) {
|
|
52562
|
+
warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
|
|
52563
|
+
}
|
|
52564
|
+
if (partial2) {
|
|
52565
|
+
warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
|
|
52566
|
+
}
|
|
52567
|
+
latencies.sort((a, b) => a - b);
|
|
52568
|
+
const series = buckets.map((bucket, index) => ({
|
|
52569
|
+
ts: new Date(fromMs + index * bucketMs).toISOString(),
|
|
52570
|
+
requests: bucket.requests,
|
|
52571
|
+
errors: bucket.errors,
|
|
52572
|
+
avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
|
|
52573
|
+
}));
|
|
52574
|
+
const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
|
|
52575
|
+
key: key2,
|
|
52576
|
+
requests: row.requests,
|
|
52577
|
+
errorRate: toPercent(row.errors, row.requests),
|
|
52578
|
+
avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
|
|
52579
|
+
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
52580
|
+
const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
|
|
52581
|
+
key: key2,
|
|
52582
|
+
requests: row.requests,
|
|
52583
|
+
errorRate: toPercent(row.errors, row.requests)
|
|
52584
|
+
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
52585
|
+
const response = {
|
|
52586
|
+
window: window2,
|
|
52587
|
+
from: new Date(fromMs).toISOString(),
|
|
52588
|
+
to: new Date(nowMs).toISOString(),
|
|
52589
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
52590
|
+
source: {
|
|
52591
|
+
logEnabled: true,
|
|
52592
|
+
baseDir,
|
|
52593
|
+
filesScanned,
|
|
52594
|
+
linesScanned,
|
|
52595
|
+
partial: partial2
|
|
52596
|
+
},
|
|
52597
|
+
summary: {
|
|
52598
|
+
totalRequests,
|
|
52599
|
+
successRequests,
|
|
52600
|
+
errorRequests,
|
|
52601
|
+
successRate: toPercent(successRequests, totalRequests),
|
|
52602
|
+
avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
|
|
52603
|
+
p95LatencyMs: percentile(latencies, 0.95),
|
|
52604
|
+
totalRequestBytes,
|
|
52605
|
+
totalResponseBytes
|
|
52606
|
+
},
|
|
52607
|
+
tokens: {
|
|
52608
|
+
usageCount: tokenUsageCount,
|
|
52609
|
+
inputTokens: tokenInput,
|
|
52610
|
+
outputTokens: tokenOutput,
|
|
52611
|
+
totalTokens: tokenTotal,
|
|
52612
|
+
cachedInputTokens: tokenCachedInput,
|
|
52613
|
+
cacheHitInputTokens: tokenCacheHitInput,
|
|
52614
|
+
cacheHitRateDenominatorTokens: tokenCacheHitDenominator,
|
|
52615
|
+
cacheHitRate: toPercent(tokenCacheHitInput, tokenCacheHitDenominator),
|
|
52616
|
+
reasoningTokens: tokenReasoning,
|
|
52617
|
+
cost: tokenCostSeen ? Number(tokenCost.toFixed(6)) : null
|
|
52618
|
+
},
|
|
52619
|
+
series,
|
|
52620
|
+
topProviders,
|
|
52621
|
+
topRouteTypes,
|
|
52622
|
+
statusClasses,
|
|
52623
|
+
warnings
|
|
52624
|
+
};
|
|
52625
|
+
metricsCache.set(cacheKey, {
|
|
52626
|
+
expiresAt: nowMs + CACHE_TTL_MS,
|
|
52627
|
+
value: response
|
|
52628
|
+
});
|
|
52629
|
+
return response;
|
|
52630
|
+
}
|
|
52631
|
+
|
|
52632
|
+
// src/log-query.ts
|
|
52633
|
+
import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
52634
|
+
import { join as join7, resolve as resolve6 } from "path";
|
|
52635
|
+
import { createInterface as createInterface2 } from "readline";
|
|
52636
|
+
|
|
52637
|
+
// src/log-index.ts
|
|
52638
|
+
import { Database } from "bun:sqlite";
|
|
52639
|
+
import {
|
|
52640
|
+
closeSync,
|
|
52641
|
+
createReadStream as createReadStream2,
|
|
52642
|
+
existsSync as existsSync5,
|
|
52643
|
+
mkdirSync as mkdirSync3,
|
|
52644
|
+
openSync,
|
|
52645
|
+
readSync,
|
|
52646
|
+
statSync as statSync2
|
|
52647
|
+
} from "fs";
|
|
52648
|
+
import { join as join6 } from "path";
|
|
52649
|
+
|
|
52650
|
+
// src/log-session-identity.ts
|
|
52651
|
+
var USER_SESSION_DELIMITER = "_account__session_";
|
|
52652
|
+
function toRecord(value) {
|
|
52653
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52654
|
+
return null;
|
|
52655
|
+
return value;
|
|
52656
|
+
}
|
|
52657
|
+
function extractUserIdRawFromRequestBody(requestBody) {
|
|
52658
|
+
const requestBodyRecord = toRecord(requestBody);
|
|
52659
|
+
const metadata = toRecord(requestBodyRecord?.metadata);
|
|
52660
|
+
if (!metadata) {
|
|
52661
|
+
return {
|
|
52662
|
+
hasMetadata: false,
|
|
52663
|
+
userIdRaw: null
|
|
52664
|
+
};
|
|
52665
|
+
}
|
|
52666
|
+
const userId = metadata.user_id;
|
|
52667
|
+
if (typeof userId !== "string" || userId.trim() === "") {
|
|
52668
|
+
return {
|
|
52669
|
+
hasMetadata: true,
|
|
52670
|
+
userIdRaw: null
|
|
52671
|
+
};
|
|
52672
|
+
}
|
|
52673
|
+
return {
|
|
52674
|
+
hasMetadata: true,
|
|
52675
|
+
userIdRaw: userId
|
|
52676
|
+
};
|
|
52677
|
+
}
|
|
52678
|
+
function parseUserSessionFromJsonFormat(userIdRaw) {
|
|
52679
|
+
let parsed;
|
|
52680
|
+
try {
|
|
52681
|
+
parsed = JSON.parse(userIdRaw);
|
|
52682
|
+
} catch {
|
|
52683
|
+
return null;
|
|
52684
|
+
}
|
|
52685
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
52686
|
+
return null;
|
|
52687
|
+
const obj = parsed;
|
|
52688
|
+
const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
|
|
52689
|
+
if (!sessionId)
|
|
52690
|
+
return null;
|
|
52691
|
+
const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
|
|
52692
|
+
return { userKey: userKey || sessionId, sessionId };
|
|
52693
|
+
}
|
|
52694
|
+
function parseUserSessionFromUserIdRaw(userIdRaw) {
|
|
52695
|
+
if (userIdRaw.trimStart().startsWith("{")) {
|
|
52696
|
+
return parseUserSessionFromJsonFormat(userIdRaw);
|
|
52697
|
+
}
|
|
52698
|
+
const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
|
|
52699
|
+
if (index <= 0)
|
|
52700
|
+
return null;
|
|
52701
|
+
const userKey = userIdRaw.slice(0, index).trim();
|
|
52702
|
+
const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
|
|
52703
|
+
if (!userKey || !sessionId)
|
|
52704
|
+
return null;
|
|
52705
|
+
return { userKey, sessionId };
|
|
52706
|
+
}
|
|
52707
|
+
function resolveLogSessionIdentity(requestBody) {
|
|
52708
|
+
const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
|
|
52709
|
+
if (!userIdRaw) {
|
|
52710
|
+
return {
|
|
52711
|
+
hasMetadata,
|
|
52712
|
+
userIdRaw: null,
|
|
52713
|
+
userKey: null,
|
|
52714
|
+
sessionId: null
|
|
52715
|
+
};
|
|
52716
|
+
}
|
|
52717
|
+
const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
|
|
52718
|
+
return {
|
|
52719
|
+
hasMetadata,
|
|
52720
|
+
userIdRaw,
|
|
52721
|
+
userKey: parsed?.userKey ?? null,
|
|
52722
|
+
sessionId: parsed?.sessionId ?? null
|
|
52723
|
+
};
|
|
52724
|
+
}
|
|
52725
|
+
|
|
52675
52726
|
// src/log-index.ts
|
|
52676
52727
|
var SCHEMA_VERSION = 3;
|
|
52677
52728
|
var MAX_INDEX_QUEUE = 20000;
|
|
@@ -53059,6 +53110,31 @@ function buildWhereClause(query, options = {}) {
|
|
|
53059
53110
|
usesFts
|
|
53060
53111
|
};
|
|
53061
53112
|
}
|
|
53113
|
+
function buildSessionsWhereClause(query) {
|
|
53114
|
+
const pseudo = {
|
|
53115
|
+
fromMs: query.fromMs,
|
|
53116
|
+
toMs: query.toMs,
|
|
53117
|
+
levels: [],
|
|
53118
|
+
providers: [],
|
|
53119
|
+
routeTypes: [],
|
|
53120
|
+
models: [],
|
|
53121
|
+
modelIns: [],
|
|
53122
|
+
modelOuts: [],
|
|
53123
|
+
users: query.users,
|
|
53124
|
+
sessions: query.sessions,
|
|
53125
|
+
statusClasses: [],
|
|
53126
|
+
hasError: null,
|
|
53127
|
+
q: query.q,
|
|
53128
|
+
sort: "time_desc",
|
|
53129
|
+
limit: 1,
|
|
53130
|
+
cursor: null
|
|
53131
|
+
};
|
|
53132
|
+
const { whereSql, params } = buildWhereClause(pseudo);
|
|
53133
|
+
return { whereSql, params };
|
|
53134
|
+
}
|
|
53135
|
+
function sortIndexedCountItems(map2) {
|
|
53136
|
+
return Array.from(map2.entries()).map(([key2, count]) => ({ key: key2, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
53137
|
+
}
|
|
53062
53138
|
|
|
53063
53139
|
class LogIndex {
|
|
53064
53140
|
baseDir;
|
|
@@ -53266,6 +53342,163 @@ class LogIndex {
|
|
|
53266
53342
|
}
|
|
53267
53343
|
};
|
|
53268
53344
|
}
|
|
53345
|
+
querySessions(query) {
|
|
53346
|
+
const startedAt = performance.now();
|
|
53347
|
+
const { whereSql, params } = buildSessionsWhereClause(query);
|
|
53348
|
+
const aggregatedWhere = `${whereSql} AND e.user_key IS NOT NULL AND e.session_id IS NOT NULL`;
|
|
53349
|
+
const summaryRow = this.db.query(`
|
|
53350
|
+
SELECT
|
|
53351
|
+
COUNT(*) AS totalRequests,
|
|
53352
|
+
COALESCE(SUM(has_metadata), 0) AS metadataRequests,
|
|
53353
|
+
COUNT(DISTINCT user_key) AS uniqueUsers,
|
|
53354
|
+
COUNT(DISTINCT CASE
|
|
53355
|
+
WHEN user_key IS NOT NULL AND session_id IS NOT NULL
|
|
53356
|
+
THEN user_key || ' ' || session_id
|
|
53357
|
+
END) AS uniqueSessions
|
|
53358
|
+
FROM log_events e
|
|
53359
|
+
${whereSql}
|
|
53360
|
+
`).get(...params);
|
|
53361
|
+
const userRows = this.db.query(`
|
|
53362
|
+
SELECT
|
|
53363
|
+
user_key AS userKey,
|
|
53364
|
+
COUNT(*) AS requestCount,
|
|
53365
|
+
MIN(ts_ms) AS firstMs,
|
|
53366
|
+
MAX(ts_ms) AS lastMs,
|
|
53367
|
+
COUNT(DISTINCT session_id) AS sessionCount
|
|
53368
|
+
FROM log_events e
|
|
53369
|
+
${aggregatedWhere}
|
|
53370
|
+
GROUP BY user_key
|
|
53371
|
+
`).all(...params);
|
|
53372
|
+
const sessionRows = this.db.query(`
|
|
53373
|
+
SELECT
|
|
53374
|
+
user_key AS userKey,
|
|
53375
|
+
session_id AS sessionId,
|
|
53376
|
+
COUNT(*) AS requestCount,
|
|
53377
|
+
MIN(ts_ms) AS firstMs,
|
|
53378
|
+
MAX(ts_ms) AS lastMs
|
|
53379
|
+
FROM log_events e
|
|
53380
|
+
${aggregatedWhere}
|
|
53381
|
+
GROUP BY user_key, session_id
|
|
53382
|
+
`).all(...params);
|
|
53383
|
+
const userModelRows = this.db.query(`
|
|
53384
|
+
SELECT user_key AS userKey, model AS key, COUNT(*) AS count
|
|
53385
|
+
FROM log_events e
|
|
53386
|
+
${aggregatedWhere}
|
|
53387
|
+
GROUP BY user_key, model
|
|
53388
|
+
`).all(...params);
|
|
53389
|
+
const userProviderRows = this.db.query(`
|
|
53390
|
+
SELECT user_key AS userKey, provider AS key, COUNT(*) AS count
|
|
53391
|
+
FROM log_events e
|
|
53392
|
+
${aggregatedWhere}
|
|
53393
|
+
GROUP BY user_key, provider
|
|
53394
|
+
`).all(...params);
|
|
53395
|
+
const userRouteRows = this.db.query(`
|
|
53396
|
+
SELECT user_key AS userKey, route_type AS key, COUNT(*) AS count
|
|
53397
|
+
FROM log_events e
|
|
53398
|
+
${aggregatedWhere}
|
|
53399
|
+
GROUP BY user_key, route_type
|
|
53400
|
+
`).all(...params);
|
|
53401
|
+
const sessionModelRows = this.db.query(`
|
|
53402
|
+
SELECT user_key AS userKey, session_id AS sessionId, model AS key, COUNT(*) AS count
|
|
53403
|
+
FROM log_events e
|
|
53404
|
+
${aggregatedWhere}
|
|
53405
|
+
GROUP BY user_key, session_id, model
|
|
53406
|
+
`).all(...params);
|
|
53407
|
+
const latestRows = this.db.query(`
|
|
53408
|
+
SELECT userKey, sessionId, request_id AS latestRequestId
|
|
53409
|
+
FROM (
|
|
53410
|
+
SELECT
|
|
53411
|
+
user_key AS userKey,
|
|
53412
|
+
session_id AS sessionId,
|
|
53413
|
+
request_id,
|
|
53414
|
+
ROW_NUMBER() OVER (
|
|
53415
|
+
PARTITION BY user_key, session_id ORDER BY ts_ms DESC, id DESC
|
|
53416
|
+
) AS rn
|
|
53417
|
+
FROM log_events e
|
|
53418
|
+
${aggregatedWhere}
|
|
53419
|
+
)
|
|
53420
|
+
WHERE rn = 1
|
|
53421
|
+
`).all(...params);
|
|
53422
|
+
const userModels = new Map;
|
|
53423
|
+
const userProviders = new Map;
|
|
53424
|
+
const userRoutes = new Map;
|
|
53425
|
+
const sessionModels = new Map;
|
|
53426
|
+
const latestBySession = new Map;
|
|
53427
|
+
const addCount = (target, groupKey, key2, count) => {
|
|
53428
|
+
if (!key2)
|
|
53429
|
+
return;
|
|
53430
|
+
let inner = target.get(groupKey);
|
|
53431
|
+
if (!inner) {
|
|
53432
|
+
inner = new Map;
|
|
53433
|
+
target.set(groupKey, inner);
|
|
53434
|
+
}
|
|
53435
|
+
inner.set(key2, count);
|
|
53436
|
+
};
|
|
53437
|
+
for (const row of userModelRows)
|
|
53438
|
+
addCount(userModels, row.userKey, row.key, row.count);
|
|
53439
|
+
for (const row of userProviderRows)
|
|
53440
|
+
addCount(userProviders, row.userKey, row.key, row.count);
|
|
53441
|
+
for (const row of userRouteRows)
|
|
53442
|
+
addCount(userRoutes, row.userKey, row.key, row.count);
|
|
53443
|
+
for (const row of sessionModelRows) {
|
|
53444
|
+
addCount(sessionModels, `${row.userKey}\x00${row.sessionId}`, row.key, row.count);
|
|
53445
|
+
}
|
|
53446
|
+
for (const row of latestRows) {
|
|
53447
|
+
latestBySession.set(`${row.userKey}\x00${row.sessionId}`, row.latestRequestId);
|
|
53448
|
+
}
|
|
53449
|
+
const sessionsByUser = new Map;
|
|
53450
|
+
for (const row of sessionRows) {
|
|
53451
|
+
const sessionKey = `${row.userKey}\x00${row.sessionId}`;
|
|
53452
|
+
const session = {
|
|
53453
|
+
sessionId: row.sessionId,
|
|
53454
|
+
requestCount: row.requestCount,
|
|
53455
|
+
firstSeenAt: new Date(row.firstMs).toISOString(),
|
|
53456
|
+
lastSeenAt: new Date(row.lastMs).toISOString(),
|
|
53457
|
+
models: sortIndexedCountItems(sessionModels.get(sessionKey) ?? new Map),
|
|
53458
|
+
latestRequestId: latestBySession.get(sessionKey) ?? ""
|
|
53459
|
+
};
|
|
53460
|
+
const list = sessionsByUser.get(row.userKey);
|
|
53461
|
+
if (list) {
|
|
53462
|
+
list.push(session);
|
|
53463
|
+
} else {
|
|
53464
|
+
sessionsByUser.set(row.userKey, [session]);
|
|
53465
|
+
}
|
|
53466
|
+
}
|
|
53467
|
+
const users = userRows.map((row) => {
|
|
53468
|
+
const sessions = (sessionsByUser.get(row.userKey) ?? []).sort((a, b) => {
|
|
53469
|
+
if (a.requestCount !== b.requestCount)
|
|
53470
|
+
return b.requestCount - a.requestCount;
|
|
53471
|
+
return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
|
|
53472
|
+
});
|
|
53473
|
+
return {
|
|
53474
|
+
userKey: row.userKey,
|
|
53475
|
+
requestCount: row.requestCount,
|
|
53476
|
+
sessionCount: row.sessionCount,
|
|
53477
|
+
firstSeenAt: new Date(row.firstMs).toISOString(),
|
|
53478
|
+
lastSeenAt: new Date(row.lastMs).toISOString(),
|
|
53479
|
+
models: sortIndexedCountItems(userModels.get(row.userKey) ?? new Map),
|
|
53480
|
+
providers: sortIndexedCountItems(userProviders.get(row.userKey) ?? new Map),
|
|
53481
|
+
routeTypes: sortIndexedCountItems(userRoutes.get(row.userKey) ?? new Map),
|
|
53482
|
+
sessions
|
|
53483
|
+
};
|
|
53484
|
+
}).sort((a, b) => {
|
|
53485
|
+
if (a.requestCount !== b.requestCount)
|
|
53486
|
+
return b.requestCount - a.requestCount;
|
|
53487
|
+
return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
|
|
53488
|
+
});
|
|
53489
|
+
return {
|
|
53490
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53491
|
+
to: new Date(query.toMs).toISOString(),
|
|
53492
|
+
summary: {
|
|
53493
|
+
totalRequests: Number(summaryRow.totalRequests) || 0,
|
|
53494
|
+
metadataRequests: Number(summaryRow.metadataRequests) || 0,
|
|
53495
|
+
uniqueUsers: Number(summaryRow.uniqueUsers) || 0,
|
|
53496
|
+
uniqueSessions: Number(summaryRow.uniqueSessions) || 0
|
|
53497
|
+
},
|
|
53498
|
+
users,
|
|
53499
|
+
queryMs: Math.round((performance.now() - startedAt) * 100) / 100
|
|
53500
|
+
};
|
|
53501
|
+
}
|
|
53269
53502
|
configure() {
|
|
53270
53503
|
this.db.exec(`
|
|
53271
53504
|
PRAGMA journal_mode = WAL;
|
|
@@ -53648,6 +53881,65 @@ async function queryIndexedLogEvents(logConfig, query) {
|
|
|
53648
53881
|
};
|
|
53649
53882
|
}
|
|
53650
53883
|
}
|
|
53884
|
+
async function queryIndexedLogSessions(logConfig, query) {
|
|
53885
|
+
if (!logConfig || logConfig.enabled === false) {
|
|
53886
|
+
return {
|
|
53887
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53888
|
+
to: new Date(query.toMs).toISOString(),
|
|
53889
|
+
summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
|
|
53890
|
+
users: [],
|
|
53891
|
+
meta: {
|
|
53892
|
+
scannedFiles: 0,
|
|
53893
|
+
scannedLines: 0,
|
|
53894
|
+
parseErrors: 0,
|
|
53895
|
+
truncated: false,
|
|
53896
|
+
indexUsed: true,
|
|
53897
|
+
indexFresh: true,
|
|
53898
|
+
queryMs: 0
|
|
53899
|
+
}
|
|
53900
|
+
};
|
|
53901
|
+
}
|
|
53902
|
+
const baseDir = resolveLogBaseDir(logConfig);
|
|
53903
|
+
const index = getLogIndex(baseDir);
|
|
53904
|
+
if (!index)
|
|
53905
|
+
return null;
|
|
53906
|
+
try {
|
|
53907
|
+
const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
|
|
53908
|
+
const result = index.querySessions(query);
|
|
53909
|
+
return {
|
|
53910
|
+
from: result.from,
|
|
53911
|
+
to: result.to,
|
|
53912
|
+
summary: result.summary,
|
|
53913
|
+
users: result.users,
|
|
53914
|
+
meta: {
|
|
53915
|
+
scannedFiles: freshness.scannedFiles,
|
|
53916
|
+
scannedLines: freshness.scannedLines,
|
|
53917
|
+
parseErrors: freshness.parseErrors,
|
|
53918
|
+
truncated: false,
|
|
53919
|
+
indexUsed: true,
|
|
53920
|
+
indexFresh: true,
|
|
53921
|
+
queryMs: result.queryMs
|
|
53922
|
+
}
|
|
53923
|
+
};
|
|
53924
|
+
} catch (err) {
|
|
53925
|
+
return {
|
|
53926
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53927
|
+
to: new Date(query.toMs).toISOString(),
|
|
53928
|
+
summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
|
|
53929
|
+
users: [],
|
|
53930
|
+
meta: {
|
|
53931
|
+
scannedFiles: 0,
|
|
53932
|
+
scannedLines: 0,
|
|
53933
|
+
parseErrors: 0,
|
|
53934
|
+
truncated: false,
|
|
53935
|
+
indexUsed: false,
|
|
53936
|
+
indexFresh: false,
|
|
53937
|
+
queryMs: 0,
|
|
53938
|
+
fallbackReason: err instanceof Error ? err.message : String(err)
|
|
53939
|
+
}
|
|
53940
|
+
};
|
|
53941
|
+
}
|
|
53942
|
+
}
|
|
53651
53943
|
function getIndexedLogEventDetail(logConfig, id) {
|
|
53652
53944
|
if (!logConfig || logConfig.enabled === false)
|
|
53653
53945
|
return null;
|
|
@@ -53680,7 +53972,10 @@ function getIndexedLogEventDetail(logConfig, id) {
|
|
|
53680
53972
|
var WINDOW_MS2 = {
|
|
53681
53973
|
"1h": 60 * 60 * 1000,
|
|
53682
53974
|
"6h": 6 * 60 * 60 * 1000,
|
|
53683
|
-
"24h": 24 * 60 * 60 * 1000
|
|
53975
|
+
"24h": 24 * 60 * 60 * 1000,
|
|
53976
|
+
"7d": 7 * 24 * 60 * 60 * 1000,
|
|
53977
|
+
"1mo": 30 * 24 * 60 * 60 * 1000,
|
|
53978
|
+
"1y": 365 * 24 * 60 * 60 * 1000
|
|
53684
53979
|
};
|
|
53685
53980
|
var MAX_LINES_SCANNED2 = 250000;
|
|
53686
53981
|
var MAX_QUERY_LIMIT = 200;
|
|
@@ -54328,7 +54623,7 @@ async function scanEvents(baseDir, query) {
|
|
|
54328
54623
|
};
|
|
54329
54624
|
}
|
|
54330
54625
|
function isLogQueryWindow(value) {
|
|
54331
|
-
return value === "1h" || value === "6h" || value === "24h";
|
|
54626
|
+
return value === "1h" || value === "6h" || value === "24h" || value === "7d" || value === "1mo" || value === "1y";
|
|
54332
54627
|
}
|
|
54333
54628
|
function getLogQueryWindowMs(window2) {
|
|
54334
54629
|
return WINDOW_MS2[window2];
|
|
@@ -54832,7 +55127,7 @@ function compileRealtimeQuery(input) {
|
|
|
54832
55127
|
const nowMs = Date.now();
|
|
54833
55128
|
const windowRaw = parseStringValue(query.window) ?? "24h";
|
|
54834
55129
|
if (!isLogQueryWindow(windowRaw)) {
|
|
54835
|
-
throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
|
|
55130
|
+
throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y");
|
|
54836
55131
|
}
|
|
54837
55132
|
const from = parseStringValue(query.from);
|
|
54838
55133
|
const to = parseStringValue(query.to);
|
|
@@ -55414,6 +55709,23 @@ async function queryLogSessions(context2, input) {
|
|
|
55414
55709
|
if (!logEnabled) {
|
|
55415
55710
|
return createEmptyResult(normalized.fromMs, normalized.toMs);
|
|
55416
55711
|
}
|
|
55712
|
+
const indexed = await queryIndexedLogSessions(context2.logConfig, {
|
|
55713
|
+
fromMs: normalized.fromMs,
|
|
55714
|
+
toMs: normalized.toMs,
|
|
55715
|
+
users: normalized.users,
|
|
55716
|
+
sessions: normalized.sessions,
|
|
55717
|
+
q: normalized.q
|
|
55718
|
+
});
|
|
55719
|
+
if (indexed?.meta.indexUsed) {
|
|
55720
|
+
return {
|
|
55721
|
+
from: indexed.from,
|
|
55722
|
+
to: indexed.to,
|
|
55723
|
+
summary: indexed.summary,
|
|
55724
|
+
users: indexed.users,
|
|
55725
|
+
meta: indexed.meta
|
|
55726
|
+
};
|
|
55727
|
+
}
|
|
55728
|
+
const fallbackReason = indexed?.meta.fallbackReason;
|
|
55417
55729
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
55418
55730
|
const eventsDir = join8(baseDir, "events");
|
|
55419
55731
|
if (!existsSync7(eventsDir)) {
|
|
@@ -55537,7 +55849,9 @@ async function queryLogSessions(context2, input) {
|
|
|
55537
55849
|
scannedFiles,
|
|
55538
55850
|
scannedLines,
|
|
55539
55851
|
parseErrors,
|
|
55540
|
-
truncated
|
|
55852
|
+
truncated,
|
|
55853
|
+
indexUsed: false,
|
|
55854
|
+
...fallbackReason ? { fallbackReason } : {}
|
|
55541
55855
|
}
|
|
55542
55856
|
};
|
|
55543
55857
|
}
|
|
@@ -55903,6 +56217,97 @@ var openAPISpec = {
|
|
|
55903
56217
|
}
|
|
55904
56218
|
}
|
|
55905
56219
|
},
|
|
56220
|
+
"/api/models": {
|
|
56221
|
+
get: {
|
|
56222
|
+
tags: ["Health"],
|
|
56223
|
+
summary: "\u55C5\u63A2\u53EF\u7528\u6A21\u578B\u8DEF\u7531",
|
|
56224
|
+
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",
|
|
56225
|
+
parameters: [
|
|
56226
|
+
{
|
|
56227
|
+
name: "protocol",
|
|
56228
|
+
in: "query",
|
|
56229
|
+
required: true,
|
|
56230
|
+
schema: {
|
|
56231
|
+
type: "string",
|
|
56232
|
+
enum: ["openai-completions", "openai-responses", "anthropic-messages"]
|
|
56233
|
+
},
|
|
56234
|
+
description: "\u534F\u8BAE\u7C7B\u578B"
|
|
56235
|
+
}
|
|
56236
|
+
],
|
|
56237
|
+
responses: {
|
|
56238
|
+
"200": {
|
|
56239
|
+
description: "\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
|
|
56240
|
+
content: {
|
|
56241
|
+
"application/json": {
|
|
56242
|
+
schema: {
|
|
56243
|
+
type: "object",
|
|
56244
|
+
properties: {
|
|
56245
|
+
protocol: { type: "string", example: "anthropic-messages" },
|
|
56246
|
+
models: {
|
|
56247
|
+
type: "array",
|
|
56248
|
+
items: { type: "string" },
|
|
56249
|
+
example: ["claude-3-5-sonnet", "claude-3-opus"]
|
|
56250
|
+
}
|
|
56251
|
+
}
|
|
56252
|
+
}
|
|
56253
|
+
}
|
|
56254
|
+
}
|
|
56255
|
+
},
|
|
56256
|
+
"400": { description: "\u534F\u8BAE\u53C2\u6570\u7F3A\u5931\u6216\u65E0\u6548" }
|
|
56257
|
+
}
|
|
56258
|
+
}
|
|
56259
|
+
},
|
|
56260
|
+
"/api/providers/discover": {
|
|
56261
|
+
get: {
|
|
56262
|
+
tags: ["Health"],
|
|
56263
|
+
summary: "\u53D1\u73B0\u5C40\u57DF\u7F51 local-router \u7684\u6A21\u578B",
|
|
56264
|
+
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",
|
|
56265
|
+
parameters: [
|
|
56266
|
+
{
|
|
56267
|
+
name: "ip",
|
|
56268
|
+
in: "query",
|
|
56269
|
+
required: true,
|
|
56270
|
+
schema: { type: "string" },
|
|
56271
|
+
description: "\u5BF9\u7AEF IP"
|
|
56272
|
+
},
|
|
56273
|
+
{
|
|
56274
|
+
name: "port",
|
|
56275
|
+
in: "query",
|
|
56276
|
+
required: false,
|
|
56277
|
+
schema: { type: "string", default: "4099" },
|
|
56278
|
+
description: "\u5BF9\u7AEF\u7AEF\u53E3\uFF0C\u9ED8\u8BA4 4099"
|
|
56279
|
+
},
|
|
56280
|
+
{
|
|
56281
|
+
name: "protocol",
|
|
56282
|
+
in: "query",
|
|
56283
|
+
required: true,
|
|
56284
|
+
schema: {
|
|
56285
|
+
type: "string",
|
|
56286
|
+
enum: ["openai-completions", "openai-responses", "anthropic-messages"]
|
|
56287
|
+
},
|
|
56288
|
+
description: "\u534F\u8BAE\u7C7B\u578B"
|
|
56289
|
+
}
|
|
56290
|
+
],
|
|
56291
|
+
responses: {
|
|
56292
|
+
"200": {
|
|
56293
|
+
description: "\u5BF9\u7AEF\u53EF\u7528\u6A21\u578B\u8DEF\u7531\u5217\u8868",
|
|
56294
|
+
content: {
|
|
56295
|
+
"application/json": {
|
|
56296
|
+
schema: {
|
|
56297
|
+
type: "object",
|
|
56298
|
+
properties: {
|
|
56299
|
+
protocol: { type: "string", example: "anthropic-messages" },
|
|
56300
|
+
models: { type: "array", items: { type: "string" } }
|
|
56301
|
+
}
|
|
56302
|
+
}
|
|
56303
|
+
}
|
|
56304
|
+
}
|
|
56305
|
+
},
|
|
56306
|
+
"400": { description: "\u53C2\u6570\u7F3A\u5931" },
|
|
56307
|
+
"502": { description: "\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF\u6216\u5BF9\u7AEF\u8FD4\u56DE\u9519\u8BEF" }
|
|
56308
|
+
}
|
|
56309
|
+
}
|
|
56310
|
+
},
|
|
55906
56311
|
"/api/metrics/logs": {
|
|
55907
56312
|
get: {
|
|
55908
56313
|
tags: ["Health"],
|
|
@@ -56061,7 +56466,7 @@ var openAPISpec = {
|
|
|
56061
56466
|
required: false,
|
|
56062
56467
|
schema: {
|
|
56063
56468
|
type: "string",
|
|
56064
|
-
enum: ["1h", "6h", "24h"],
|
|
56469
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56065
56470
|
default: "24h"
|
|
56066
56471
|
},
|
|
56067
56472
|
description: "\u65F6\u95F4\u7A97\u53E3\uFF08\u5F53\u672A\u63D0\u4F9B from/to \u65F6\u751F\u6548\uFF09"
|
|
@@ -56391,7 +56796,7 @@ var openAPISpec = {
|
|
|
56391
56796
|
required: false,
|
|
56392
56797
|
schema: {
|
|
56393
56798
|
type: "string",
|
|
56394
|
-
enum: ["1h", "6h", "24h"],
|
|
56799
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56395
56800
|
default: "24h"
|
|
56396
56801
|
},
|
|
56397
56802
|
description: "\u65F6\u95F4\u7A97\u53E3"
|
|
@@ -56512,7 +56917,7 @@ var openAPISpec = {
|
|
|
56512
56917
|
required: false,
|
|
56513
56918
|
schema: {
|
|
56514
56919
|
type: "string",
|
|
56515
|
-
enum: ["1h", "6h", "24h"],
|
|
56920
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56516
56921
|
default: "1h"
|
|
56517
56922
|
}
|
|
56518
56923
|
},
|
|
@@ -57912,6 +58317,43 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
57912
58317
|
routeTypes: Object.keys(ROUTE_REGISTRY)
|
|
57913
58318
|
});
|
|
57914
58319
|
});
|
|
58320
|
+
api2.get("/models", (c2) => {
|
|
58321
|
+
const protocol = c2.req.query("protocol");
|
|
58322
|
+
const routeTypes = Object.keys(ROUTE_REGISTRY);
|
|
58323
|
+
if (!protocol || !routeTypes.includes(protocol)) {
|
|
58324
|
+
return c2.json({ error: "invalid or missing protocol", routeTypes }, 400);
|
|
58325
|
+
}
|
|
58326
|
+
const routes = store.get().routes[protocol] ?? {};
|
|
58327
|
+
const models = Object.keys(routes).filter((k) => k !== "*");
|
|
58328
|
+
return c2.json({ protocol, models });
|
|
58329
|
+
});
|
|
58330
|
+
api2.get("/providers/discover", async (c2) => {
|
|
58331
|
+
const ip = c2.req.query("ip");
|
|
58332
|
+
const portStr = c2.req.query("port") ?? "4099";
|
|
58333
|
+
const protocol = c2.req.query("protocol");
|
|
58334
|
+
if (!ip || !protocol) {
|
|
58335
|
+
return c2.json({ error: "ip and protocol are required" }, 400);
|
|
58336
|
+
}
|
|
58337
|
+
const port = Number(portStr);
|
|
58338
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
58339
|
+
return c2.json({ error: "\u7AEF\u53E3\u65E0\u6548\uFF0C\u5FC5\u987B\u662F 1-65535 \u7684\u6574\u6570" }, 400);
|
|
58340
|
+
}
|
|
58341
|
+
if (!isLoopbackAddress(ip) && !isLanAddress(ip)) {
|
|
58342
|
+
return c2.json({ error: "\u4EC5\u652F\u6301\u5C40\u57DF\u7F51\u6216\u672C\u673A IP \u5730\u5740" }, 400);
|
|
58343
|
+
}
|
|
58344
|
+
const url2 = `http://${ip}:${port}/api/models?protocol=${encodeURIComponent(protocol)}`;
|
|
58345
|
+
try {
|
|
58346
|
+
const res = await fetch(url2, { signal: AbortSignal.timeout(5000) });
|
|
58347
|
+
if (!res.ok) {
|
|
58348
|
+
const body = await res.json().catch(() => ({}));
|
|
58349
|
+
return c2.json({ error: body.error ?? `remote returned ${res.status}` }, 502);
|
|
58350
|
+
}
|
|
58351
|
+
const data = await res.json();
|
|
58352
|
+
return c2.json(data);
|
|
58353
|
+
} catch (err) {
|
|
58354
|
+
return c2.json({ error: `\u65E0\u6CD5\u8FDE\u63A5\u5BF9\u7AEF local-router: ${err instanceof Error ? err.message : err}` }, 502);
|
|
58355
|
+
}
|
|
58356
|
+
});
|
|
57915
58357
|
api2.get("/config/schema", (c2) => {
|
|
57916
58358
|
try {
|
|
57917
58359
|
return c2.json(schemaJson);
|
|
@@ -58034,7 +58476,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58034
58476
|
try {
|
|
58035
58477
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58036
58478
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58037
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58479
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58038
58480
|
}
|
|
58039
58481
|
const range = resolveLogQueryRange({
|
|
58040
58482
|
window: windowRaw,
|
|
@@ -58085,7 +58527,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58085
58527
|
try {
|
|
58086
58528
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58087
58529
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58088
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58530
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58089
58531
|
}
|
|
58090
58532
|
const range = resolveLogQueryRange({
|
|
58091
58533
|
window: windowRaw,
|
|
@@ -58124,7 +58566,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58124
58566
|
try {
|
|
58125
58567
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58126
58568
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58127
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58569
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58128
58570
|
}
|
|
58129
58571
|
const range = resolveLogQueryRange({
|
|
58130
58572
|
window: windowRaw,
|
|
@@ -58174,7 +58616,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58174
58616
|
const target = c2.req.raw;
|
|
58175
58617
|
const windowRaw = c2.req.query("window") ?? "1h";
|
|
58176
58618
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58177
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58619
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58178
58620
|
}
|
|
58179
58621
|
const sortRaw = c2.req.query("sort") ?? "time_desc";
|
|
58180
58622
|
if (!validateSort(sortRaw)) {
|