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