@lakphy/local-router 0.5.2 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.schema.json +22 -0
- package/dist/cli.js +36537 -32159
- package/dist/entry.js +2042 -277
- package/dist/web/assets/index-BxjdLPIh.js +190 -0
- package/dist/web/assets/index-Dc2P7nS-.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BprGtkte.js +0 -191
- package/dist/web/assets/index-OkpSAlwA.css +0 -2
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,8 +51697,8 @@ 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 existsSync3 } from "fs";
|
|
51701
|
+
import { join as join5 } from "path";
|
|
51492
51702
|
import { createInterface } from "readline";
|
|
51493
51703
|
var WINDOW_MS = {
|
|
51494
51704
|
"1h": 60 * 60 * 1000,
|
|
@@ -51605,8 +51815,8 @@ async function getLogMetrics(options) {
|
|
|
51605
51815
|
if (!refresh && cached2 && cached2.expiresAt > nowMs) {
|
|
51606
51816
|
return cached2.value;
|
|
51607
51817
|
}
|
|
51608
|
-
const eventsDir =
|
|
51609
|
-
if (!
|
|
51818
|
+
const eventsDir = join5(baseDir, "events");
|
|
51819
|
+
if (!existsSync3(eventsDir)) {
|
|
51610
51820
|
const empty = createEmptyMetrics(window2, nowMs, {
|
|
51611
51821
|
logEnabled: true,
|
|
51612
51822
|
baseDir,
|
|
@@ -51652,8 +51862,8 @@ async function getLogMetrics(options) {
|
|
|
51652
51862
|
partial2 = true;
|
|
51653
51863
|
break;
|
|
51654
51864
|
}
|
|
51655
|
-
const filePath =
|
|
51656
|
-
if (!
|
|
51865
|
+
const filePath = join5(eventsDir, `${dateStr}.jsonl`);
|
|
51866
|
+
if (!existsSync3(filePath))
|
|
51657
51867
|
continue;
|
|
51658
51868
|
filesScanned += 1;
|
|
51659
51869
|
try {
|
|
@@ -51789,8 +51999,8 @@ async function getLogMetrics(options) {
|
|
|
51789
51999
|
}
|
|
51790
52000
|
|
|
51791
52001
|
// src/log-query.ts
|
|
51792
|
-
import { createReadStream as createReadStream3, existsSync as
|
|
51793
|
-
import { join as
|
|
52002
|
+
import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
52003
|
+
import { join as join7, resolve as resolve6 } from "path";
|
|
51794
52004
|
import { createInterface as createInterface2 } from "readline";
|
|
51795
52005
|
|
|
51796
52006
|
// src/log-index.ts
|
|
@@ -51798,13 +52008,13 @@ import { Database } from "bun:sqlite";
|
|
|
51798
52008
|
import {
|
|
51799
52009
|
closeSync,
|
|
51800
52010
|
createReadStream as createReadStream2,
|
|
51801
|
-
existsSync as
|
|
51802
|
-
mkdirSync as
|
|
52011
|
+
existsSync as existsSync5,
|
|
52012
|
+
mkdirSync as mkdirSync3,
|
|
51803
52013
|
openSync,
|
|
51804
52014
|
readSync,
|
|
51805
|
-
statSync
|
|
52015
|
+
statSync as statSync2
|
|
51806
52016
|
} from "fs";
|
|
51807
|
-
import { join as
|
|
52017
|
+
import { join as join6 } from "path";
|
|
51808
52018
|
|
|
51809
52019
|
// src/log-session-identity.ts
|
|
51810
52020
|
var USER_SESSION_DELIMITER = "_account__session_";
|
|
@@ -51882,8 +52092,588 @@ function resolveLogSessionIdentity(requestBody) {
|
|
|
51882
52092
|
};
|
|
51883
52093
|
}
|
|
51884
52094
|
|
|
52095
|
+
// src/token-usage.ts
|
|
52096
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "fs";
|
|
52097
|
+
import { resolve as resolve5 } from "path";
|
|
52098
|
+
var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
|
|
52099
|
+
function asRecord(value) {
|
|
52100
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52101
|
+
return null;
|
|
52102
|
+
return value;
|
|
52103
|
+
}
|
|
52104
|
+
function numeric(value) {
|
|
52105
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
52106
|
+
return value;
|
|
52107
|
+
if (typeof value === "string" && value.trim()) {
|
|
52108
|
+
const parsed = Number(value);
|
|
52109
|
+
if (Number.isFinite(parsed))
|
|
52110
|
+
return parsed;
|
|
52111
|
+
}
|
|
52112
|
+
return null;
|
|
52113
|
+
}
|
|
52114
|
+
function numberAt(value, path) {
|
|
52115
|
+
let current = value;
|
|
52116
|
+
for (const key2 of path) {
|
|
52117
|
+
const record2 = asRecord(current);
|
|
52118
|
+
if (!record2 || !(key2 in record2))
|
|
52119
|
+
return null;
|
|
52120
|
+
current = record2[key2];
|
|
52121
|
+
}
|
|
52122
|
+
return numeric(current);
|
|
52123
|
+
}
|
|
52124
|
+
function firstNumber(value, paths) {
|
|
52125
|
+
for (const path of paths) {
|
|
52126
|
+
const found = numberAt(value, path);
|
|
52127
|
+
if (found !== null)
|
|
52128
|
+
return found;
|
|
52129
|
+
}
|
|
52130
|
+
return null;
|
|
52131
|
+
}
|
|
52132
|
+
function maxNullable(...values) {
|
|
52133
|
+
const numbers = values.filter((value) => value !== null && value !== undefined);
|
|
52134
|
+
if (numbers.length === 0)
|
|
52135
|
+
return null;
|
|
52136
|
+
return Math.max(...numbers);
|
|
52137
|
+
}
|
|
52138
|
+
function sumNullable(...values) {
|
|
52139
|
+
let total = 0;
|
|
52140
|
+
let hasValue = false;
|
|
52141
|
+
for (const value of values) {
|
|
52142
|
+
if (value === null || value === undefined)
|
|
52143
|
+
continue;
|
|
52144
|
+
total += value;
|
|
52145
|
+
hasValue = true;
|
|
52146
|
+
}
|
|
52147
|
+
return hasValue ? total : null;
|
|
52148
|
+
}
|
|
52149
|
+
function roundPercent(numerator, denominator) {
|
|
52150
|
+
if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
|
|
52151
|
+
return null;
|
|
52152
|
+
return Number((numerator / denominator * 100).toFixed(2));
|
|
52153
|
+
}
|
|
52154
|
+
function inferProviderStyle(usage, providerHint) {
|
|
52155
|
+
const hint = providerHint?.toLowerCase() ?? "";
|
|
52156
|
+
if (hint.includes("anthropic") || hint.includes("claude"))
|
|
52157
|
+
return "anthropic";
|
|
52158
|
+
if (hint.includes("gemini") || hint.includes("google"))
|
|
52159
|
+
return "gemini";
|
|
52160
|
+
if (hint.includes("deepseek"))
|
|
52161
|
+
return "deepseek";
|
|
52162
|
+
if (hint.includes("cohere"))
|
|
52163
|
+
return "cohere";
|
|
52164
|
+
if (hint.includes("mistral"))
|
|
52165
|
+
return "mistral";
|
|
52166
|
+
if (hint.includes("openrouter"))
|
|
52167
|
+
return "openrouter";
|
|
52168
|
+
if (hint.includes("openai") || hint.includes("gpt-"))
|
|
52169
|
+
return "openai";
|
|
52170
|
+
if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
|
|
52171
|
+
return "anthropic";
|
|
52172
|
+
}
|
|
52173
|
+
if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
|
|
52174
|
+
return "deepseek";
|
|
52175
|
+
}
|
|
52176
|
+
if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
|
|
52177
|
+
return "gemini";
|
|
52178
|
+
}
|
|
52179
|
+
if ("billed_units" in usage || "tokens" in usage) {
|
|
52180
|
+
return "cohere";
|
|
52181
|
+
}
|
|
52182
|
+
if ("prompt_tokens" in usage || "completion_tokens" in usage) {
|
|
52183
|
+
return "openai";
|
|
52184
|
+
}
|
|
52185
|
+
if ("input_tokens" in usage || "output_tokens" in usage) {
|
|
52186
|
+
return "openai";
|
|
52187
|
+
}
|
|
52188
|
+
return "unknown";
|
|
52189
|
+
}
|
|
52190
|
+
function createEmptyMetrics2(input) {
|
|
52191
|
+
return {
|
|
52192
|
+
schemaVersion: 1,
|
|
52193
|
+
source: input.source,
|
|
52194
|
+
providerStyle: input.providerStyle,
|
|
52195
|
+
inputTokens: null,
|
|
52196
|
+
outputTokens: null,
|
|
52197
|
+
totalTokens: null,
|
|
52198
|
+
cachedInputTokens: null,
|
|
52199
|
+
cacheHitInputTokens: null,
|
|
52200
|
+
cacheHitRate: null,
|
|
52201
|
+
cacheHitRateDenominatorTokens: null,
|
|
52202
|
+
cacheHitRateFormula: null,
|
|
52203
|
+
cacheReadInputTokens: null,
|
|
52204
|
+
cacheCreationInputTokens: null,
|
|
52205
|
+
cacheCreationInputTokens5m: null,
|
|
52206
|
+
cacheCreationInputTokens1h: null,
|
|
52207
|
+
cacheWriteInputTokens: null,
|
|
52208
|
+
cacheMissInputTokens: null,
|
|
52209
|
+
reasoningTokens: null,
|
|
52210
|
+
audioInputTokens: null,
|
|
52211
|
+
audioOutputTokens: null,
|
|
52212
|
+
textInputTokens: null,
|
|
52213
|
+
textOutputTokens: null,
|
|
52214
|
+
acceptedPredictionTokens: null,
|
|
52215
|
+
rejectedPredictionTokens: null,
|
|
52216
|
+
toolUsePromptTokens: null,
|
|
52217
|
+
billableInputTokens: null,
|
|
52218
|
+
billableOutputTokens: null,
|
|
52219
|
+
creditUsage: null,
|
|
52220
|
+
cost: null,
|
|
52221
|
+
rawUsage: input.rawUsage,
|
|
52222
|
+
rawUsagePath: input.rawUsagePath,
|
|
52223
|
+
warnings: []
|
|
52224
|
+
};
|
|
52225
|
+
}
|
|
52226
|
+
function hasAnyTokenSignal(metrics) {
|
|
52227
|
+
return [
|
|
52228
|
+
metrics.inputTokens,
|
|
52229
|
+
metrics.outputTokens,
|
|
52230
|
+
metrics.totalTokens,
|
|
52231
|
+
metrics.cachedInputTokens,
|
|
52232
|
+
metrics.cacheHitInputTokens,
|
|
52233
|
+
metrics.cacheReadInputTokens,
|
|
52234
|
+
metrics.cacheCreationInputTokens,
|
|
52235
|
+
metrics.cacheMissInputTokens,
|
|
52236
|
+
metrics.reasoningTokens,
|
|
52237
|
+
metrics.billableInputTokens,
|
|
52238
|
+
metrics.billableOutputTokens,
|
|
52239
|
+
metrics.creditUsage,
|
|
52240
|
+
metrics.cost
|
|
52241
|
+
].some((value) => value !== null);
|
|
52242
|
+
}
|
|
52243
|
+
function normalizeUsageObject(input) {
|
|
52244
|
+
const { usage, source: source2, rawUsagePath, providerHint } = input;
|
|
52245
|
+
const providerStyle = inferProviderStyle(usage, providerHint);
|
|
52246
|
+
const metrics = createEmptyMetrics2({
|
|
52247
|
+
source: source2,
|
|
52248
|
+
providerStyle,
|
|
52249
|
+
rawUsage: usage,
|
|
52250
|
+
rawUsagePath
|
|
52251
|
+
});
|
|
52252
|
+
const usageBody = asRecord(usage.usageMetadata) ?? usage;
|
|
52253
|
+
metrics.inputTokens = firstNumber(usageBody, [
|
|
52254
|
+
["input_tokens"],
|
|
52255
|
+
["prompt_tokens"],
|
|
52256
|
+
["promptTokenCount"],
|
|
52257
|
+
["tokens", "input_tokens"],
|
|
52258
|
+
["billed_units", "input_tokens"]
|
|
52259
|
+
]);
|
|
52260
|
+
metrics.outputTokens = firstNumber(usageBody, [
|
|
52261
|
+
["output_tokens"],
|
|
52262
|
+
["completion_tokens"],
|
|
52263
|
+
["candidatesTokenCount"],
|
|
52264
|
+
["tokens", "output_tokens"],
|
|
52265
|
+
["billed_units", "output_tokens"]
|
|
52266
|
+
]);
|
|
52267
|
+
const explicitTotalTokens = firstNumber(usageBody, [
|
|
52268
|
+
["total_tokens"],
|
|
52269
|
+
["totalTokenCount"],
|
|
52270
|
+
["tokens", "total_tokens"]
|
|
52271
|
+
]);
|
|
52272
|
+
metrics.totalTokens = explicitTotalTokens;
|
|
52273
|
+
const cachedTokens = firstNumber(usageBody, [
|
|
52274
|
+
["input_tokens_details", "cached_tokens"],
|
|
52275
|
+
["prompt_tokens_details", "cached_tokens"],
|
|
52276
|
+
["cached_tokens"],
|
|
52277
|
+
["cachedContentTokenCount"]
|
|
52278
|
+
]);
|
|
52279
|
+
const cacheReadTokens = firstNumber(usageBody, [
|
|
52280
|
+
["cache_read_input_tokens"],
|
|
52281
|
+
["cacheReadInputTokens"],
|
|
52282
|
+
["claude_cache_read_input_tokens"]
|
|
52283
|
+
]);
|
|
52284
|
+
const promptCacheHitTokens = firstNumber(usageBody, [["prompt_cache_hit_tokens"]]);
|
|
52285
|
+
const promptCacheMissTokens = firstNumber(usageBody, [["prompt_cache_miss_tokens"]]);
|
|
52286
|
+
const cacheCreationTokens = firstNumber(usageBody, [
|
|
52287
|
+
["cache_creation_input_tokens"],
|
|
52288
|
+
["cacheCreationInputTokens"],
|
|
52289
|
+
["cache_creation", "input_tokens"],
|
|
52290
|
+
["claude_cache_creation_input_tokens"]
|
|
52291
|
+
]);
|
|
52292
|
+
metrics.cacheCreationInputTokens5m = firstNumber(usageBody, [
|
|
52293
|
+
["cache_creation", "ephemeral_5m_input_tokens"],
|
|
52294
|
+
["cache_creation", "ephemeral5mInputTokens"],
|
|
52295
|
+
["cache_creation_ephemeral_5m_input_tokens"],
|
|
52296
|
+
["claude_cache_creation_5_m_tokens"]
|
|
52297
|
+
]);
|
|
52298
|
+
metrics.cacheCreationInputTokens1h = firstNumber(usageBody, [
|
|
52299
|
+
["cache_creation", "ephemeral_1h_input_tokens"],
|
|
52300
|
+
["cache_creation", "ephemeral1hInputTokens"],
|
|
52301
|
+
["cache_creation_ephemeral_1h_input_tokens"],
|
|
52302
|
+
["claude_cache_creation_1_h_tokens"]
|
|
52303
|
+
]);
|
|
52304
|
+
metrics.cacheCreationInputTokens = maxNullable(cacheCreationTokens, sumNullable(metrics.cacheCreationInputTokens5m, metrics.cacheCreationInputTokens1h));
|
|
52305
|
+
metrics.cacheReadInputTokens = cacheReadTokens;
|
|
52306
|
+
metrics.cacheHitInputTokens = maxNullable(cachedTokens, cacheReadTokens, promptCacheHitTokens);
|
|
52307
|
+
metrics.cachedInputTokens = metrics.cacheHitInputTokens;
|
|
52308
|
+
metrics.cacheMissInputTokens = promptCacheMissTokens;
|
|
52309
|
+
metrics.cacheWriteInputTokens = firstNumber(usageBody, [
|
|
52310
|
+
["cache_write_input_tokens"],
|
|
52311
|
+
["cacheWriteInputTokens"]
|
|
52312
|
+
]);
|
|
52313
|
+
if (metrics.cacheWriteInputTokens === null && providerStyle === "anthropic") {
|
|
52314
|
+
metrics.cacheWriteInputTokens = metrics.cacheCreationInputTokens;
|
|
52315
|
+
}
|
|
52316
|
+
metrics.reasoningTokens = firstNumber(usageBody, [
|
|
52317
|
+
["output_tokens_details", "reasoning_tokens"],
|
|
52318
|
+
["completion_tokens_details", "reasoning_tokens"],
|
|
52319
|
+
["reasoning_tokens"],
|
|
52320
|
+
["thoughtsTokenCount"]
|
|
52321
|
+
]);
|
|
52322
|
+
metrics.audioInputTokens = firstNumber(usageBody, [
|
|
52323
|
+
["input_tokens_details", "audio_tokens"],
|
|
52324
|
+
["prompt_tokens_details", "audio_tokens"],
|
|
52325
|
+
["audio_input_tokens"]
|
|
52326
|
+
]);
|
|
52327
|
+
metrics.audioOutputTokens = firstNumber(usageBody, [
|
|
52328
|
+
["output_tokens_details", "audio_tokens"],
|
|
52329
|
+
["completion_tokens_details", "audio_tokens"],
|
|
52330
|
+
["audio_output_tokens"]
|
|
52331
|
+
]);
|
|
52332
|
+
metrics.textInputTokens = firstNumber(usageBody, [
|
|
52333
|
+
["input_tokens_details", "text_tokens"],
|
|
52334
|
+
["prompt_tokens_details", "text_tokens"],
|
|
52335
|
+
["text_input_tokens"]
|
|
52336
|
+
]);
|
|
52337
|
+
metrics.textOutputTokens = firstNumber(usageBody, [
|
|
52338
|
+
["output_tokens_details", "text_tokens"],
|
|
52339
|
+
["completion_tokens_details", "text_tokens"],
|
|
52340
|
+
["text_output_tokens"]
|
|
52341
|
+
]);
|
|
52342
|
+
metrics.acceptedPredictionTokens = firstNumber(usageBody, [
|
|
52343
|
+
["output_tokens_details", "accepted_prediction_tokens"],
|
|
52344
|
+
["completion_tokens_details", "accepted_prediction_tokens"],
|
|
52345
|
+
["accepted_prediction_tokens"]
|
|
52346
|
+
]);
|
|
52347
|
+
metrics.rejectedPredictionTokens = firstNumber(usageBody, [
|
|
52348
|
+
["output_tokens_details", "rejected_prediction_tokens"],
|
|
52349
|
+
["completion_tokens_details", "rejected_prediction_tokens"],
|
|
52350
|
+
["rejected_prediction_tokens"]
|
|
52351
|
+
]);
|
|
52352
|
+
metrics.toolUsePromptTokens = firstNumber(usageBody, [
|
|
52353
|
+
["toolUsePromptTokenCount"],
|
|
52354
|
+
["tool_use_prompt_tokens"]
|
|
52355
|
+
]);
|
|
52356
|
+
metrics.billableInputTokens = firstNumber(usageBody, [
|
|
52357
|
+
["billed_units", "input_tokens"],
|
|
52358
|
+
["billable_input_tokens"]
|
|
52359
|
+
]);
|
|
52360
|
+
metrics.billableOutputTokens = firstNumber(usageBody, [
|
|
52361
|
+
["billed_units", "output_tokens"],
|
|
52362
|
+
["billable_output_tokens"]
|
|
52363
|
+
]);
|
|
52364
|
+
metrics.creditUsage = firstNumber(usageBody, [["credit_usage"], ["creditUsage"]]);
|
|
52365
|
+
metrics.cost = firstNumber(usageBody, [["cost"], ["total_cost"], ["totalCost"]]);
|
|
52366
|
+
let cacheDenominator = null;
|
|
52367
|
+
let cacheFormula = null;
|
|
52368
|
+
if (providerStyle === "anthropic") {
|
|
52369
|
+
cacheDenominator = sumNullable(metrics.inputTokens, metrics.cacheReadInputTokens, metrics.cacheCreationInputTokens);
|
|
52370
|
+
cacheFormula = "cache_read_input_tokens / (input_tokens + cache_read_input_tokens + cache_creation_input_tokens)";
|
|
52371
|
+
} else if (providerStyle === "deepseek") {
|
|
52372
|
+
cacheDenominator = metrics.inputTokens ?? sumNullable(metrics.cacheHitInputTokens, metrics.cacheMissInputTokens);
|
|
52373
|
+
cacheFormula = "prompt_cache_hit_tokens / prompt_tokens";
|
|
52374
|
+
} else if (providerStyle === "gemini") {
|
|
52375
|
+
cacheDenominator = metrics.inputTokens;
|
|
52376
|
+
cacheFormula = "cachedContentTokenCount / promptTokenCount";
|
|
52377
|
+
} else if (providerStyle === "openai" || providerStyle === "mistral" || providerStyle === "openrouter" || providerStyle === "unknown") {
|
|
52378
|
+
cacheDenominator = metrics.inputTokens;
|
|
52379
|
+
cacheFormula = "cached_tokens / input_tokens";
|
|
52380
|
+
}
|
|
52381
|
+
if (metrics.cacheHitInputTokens !== null && cacheDenominator !== null && cacheDenominator > 0) {
|
|
52382
|
+
metrics.cacheHitRateDenominatorTokens = cacheDenominator;
|
|
52383
|
+
metrics.cacheHitRateFormula = cacheFormula;
|
|
52384
|
+
metrics.cacheHitRate = roundPercent(metrics.cacheHitInputTokens, cacheDenominator);
|
|
52385
|
+
if (metrics.cacheMissInputTokens === null) {
|
|
52386
|
+
metrics.cacheMissInputTokens = Math.max(0, cacheDenominator - metrics.cacheHitInputTokens);
|
|
52387
|
+
}
|
|
52388
|
+
}
|
|
52389
|
+
if (metrics.totalTokens === null && metrics.outputTokens !== null) {
|
|
52390
|
+
const effectiveInputTokens = metrics.cacheHitRateDenominatorTokens ?? metrics.inputTokens;
|
|
52391
|
+
if (effectiveInputTokens !== null) {
|
|
52392
|
+
metrics.totalTokens = effectiveInputTokens + metrics.outputTokens;
|
|
52393
|
+
metrics.warnings.push(metrics.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
52394
|
+
}
|
|
52395
|
+
}
|
|
52396
|
+
return hasAnyTokenSignal(metrics) ? metrics : null;
|
|
52397
|
+
}
|
|
52398
|
+
function collectUsageCandidates(value, prefix = "") {
|
|
52399
|
+
const record2 = asRecord(value);
|
|
52400
|
+
if (!record2)
|
|
52401
|
+
return [];
|
|
52402
|
+
const candidates = [];
|
|
52403
|
+
const candidateKeys = [
|
|
52404
|
+
"usage",
|
|
52405
|
+
"usageMetadata",
|
|
52406
|
+
"message.usage",
|
|
52407
|
+
"response.usage",
|
|
52408
|
+
"body.usage",
|
|
52409
|
+
"data.usage",
|
|
52410
|
+
"event.usage"
|
|
52411
|
+
];
|
|
52412
|
+
for (const key2 of candidateKeys) {
|
|
52413
|
+
const path = key2.split(".");
|
|
52414
|
+
let current = record2;
|
|
52415
|
+
for (const part of path) {
|
|
52416
|
+
const currentRecord = asRecord(current);
|
|
52417
|
+
current = currentRecord?.[part];
|
|
52418
|
+
}
|
|
52419
|
+
const usage = asRecord(current);
|
|
52420
|
+
if (usage) {
|
|
52421
|
+
candidates.push({ usage, path: prefix ? `${prefix}.${key2}` : key2 });
|
|
52422
|
+
}
|
|
52423
|
+
}
|
|
52424
|
+
const direct = normalizeUsageObject({
|
|
52425
|
+
usage: record2,
|
|
52426
|
+
source: "response_body",
|
|
52427
|
+
rawUsagePath: prefix || null
|
|
52428
|
+
});
|
|
52429
|
+
if (direct) {
|
|
52430
|
+
candidates.push({ usage: record2, path: prefix || "$" });
|
|
52431
|
+
}
|
|
52432
|
+
return candidates;
|
|
52433
|
+
}
|
|
52434
|
+
function mergeNumber(current, incoming, strategy = "max") {
|
|
52435
|
+
if (incoming === null)
|
|
52436
|
+
return current;
|
|
52437
|
+
if (current === null)
|
|
52438
|
+
return incoming;
|
|
52439
|
+
return strategy === "latest" ? incoming : Math.max(current, incoming);
|
|
52440
|
+
}
|
|
52441
|
+
function mergeTokenUsageMetrics(current, incoming) {
|
|
52442
|
+
if (!current)
|
|
52443
|
+
return incoming;
|
|
52444
|
+
if (!incoming)
|
|
52445
|
+
return current;
|
|
52446
|
+
const merged = {
|
|
52447
|
+
...current,
|
|
52448
|
+
source: incoming.source,
|
|
52449
|
+
providerStyle: current.providerStyle === "unknown" ? incoming.providerStyle : current.providerStyle,
|
|
52450
|
+
rawUsage: incoming.rawUsage ?? current.rawUsage,
|
|
52451
|
+
rawUsagePath: incoming.rawUsagePath ?? current.rawUsagePath,
|
|
52452
|
+
warnings: Array.from(new Set([...current.warnings, ...incoming.warnings]))
|
|
52453
|
+
};
|
|
52454
|
+
const numericKeys = [
|
|
52455
|
+
"inputTokens",
|
|
52456
|
+
"outputTokens",
|
|
52457
|
+
"totalTokens",
|
|
52458
|
+
"cachedInputTokens",
|
|
52459
|
+
"cacheHitInputTokens",
|
|
52460
|
+
"cacheHitRateDenominatorTokens",
|
|
52461
|
+
"cacheReadInputTokens",
|
|
52462
|
+
"cacheCreationInputTokens",
|
|
52463
|
+
"cacheCreationInputTokens5m",
|
|
52464
|
+
"cacheCreationInputTokens1h",
|
|
52465
|
+
"cacheWriteInputTokens",
|
|
52466
|
+
"cacheMissInputTokens",
|
|
52467
|
+
"reasoningTokens",
|
|
52468
|
+
"audioInputTokens",
|
|
52469
|
+
"audioOutputTokens",
|
|
52470
|
+
"textInputTokens",
|
|
52471
|
+
"textOutputTokens",
|
|
52472
|
+
"acceptedPredictionTokens",
|
|
52473
|
+
"rejectedPredictionTokens",
|
|
52474
|
+
"toolUsePromptTokens",
|
|
52475
|
+
"billableInputTokens",
|
|
52476
|
+
"billableOutputTokens",
|
|
52477
|
+
"creditUsage",
|
|
52478
|
+
"cost"
|
|
52479
|
+
];
|
|
52480
|
+
for (const key2 of numericKeys) {
|
|
52481
|
+
const value = mergeNumber(current[key2], incoming[key2], key2 === "cost" || key2 === "creditUsage" ? "latest" : "max");
|
|
52482
|
+
merged[key2] = value;
|
|
52483
|
+
}
|
|
52484
|
+
if (incoming.cacheHitRate !== null && (current.cacheHitRate === null || (incoming.cacheHitRateDenominatorTokens ?? 0) >= (current.cacheHitRateDenominatorTokens ?? 0))) {
|
|
52485
|
+
merged.cacheHitRate = incoming.cacheHitRate;
|
|
52486
|
+
merged.cacheHitRateFormula = incoming.cacheHitRateFormula;
|
|
52487
|
+
}
|
|
52488
|
+
if (merged.cacheHitInputTokens !== null && merged.cacheHitRateDenominatorTokens !== null) {
|
|
52489
|
+
merged.cacheHitRate = roundPercent(merged.cacheHitInputTokens, merged.cacheHitRateDenominatorTokens);
|
|
52490
|
+
}
|
|
52491
|
+
if (merged.totalTokens === null && merged.outputTokens !== null) {
|
|
52492
|
+
const effectiveInputTokens = merged.cacheHitRateDenominatorTokens ?? merged.inputTokens;
|
|
52493
|
+
if (effectiveInputTokens !== null) {
|
|
52494
|
+
merged.totalTokens = effectiveInputTokens + merged.outputTokens;
|
|
52495
|
+
merged.warnings.push(merged.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
52496
|
+
merged.warnings = Array.from(new Set(merged.warnings));
|
|
52497
|
+
}
|
|
52498
|
+
}
|
|
52499
|
+
return merged;
|
|
52500
|
+
}
|
|
52501
|
+
function toTokenUsageSummary(metrics) {
|
|
52502
|
+
const { rawUsage: _rawUsage, ...summary } = metrics;
|
|
52503
|
+
return summary;
|
|
52504
|
+
}
|
|
52505
|
+
function extractTokenUsageFromJson(value, options) {
|
|
52506
|
+
let merged = null;
|
|
52507
|
+
const candidates = collectUsageCandidates(value, options.rawUsagePathPrefix ?? "");
|
|
52508
|
+
for (const candidate of candidates) {
|
|
52509
|
+
const metrics = normalizeUsageObject({
|
|
52510
|
+
usage: candidate.usage,
|
|
52511
|
+
source: options.source,
|
|
52512
|
+
rawUsagePath: candidate.path,
|
|
52513
|
+
providerHint: options.providerHint
|
|
52514
|
+
});
|
|
52515
|
+
merged = mergeTokenUsageMetrics(merged, metrics);
|
|
52516
|
+
}
|
|
52517
|
+
return merged;
|
|
52518
|
+
}
|
|
52519
|
+
function extractTokenUsageFromResponseText(text2, source2 = "response_body", providerHint) {
|
|
52520
|
+
if (!text2?.trim())
|
|
52521
|
+
return null;
|
|
52522
|
+
try {
|
|
52523
|
+
return extractTokenUsageFromJson(JSON.parse(text2), { source: source2, providerHint });
|
|
52524
|
+
} catch {
|
|
52525
|
+
return null;
|
|
52526
|
+
}
|
|
52527
|
+
}
|
|
52528
|
+
function processSseMessage(dataLines, source2, providerHint) {
|
|
52529
|
+
if (dataLines.length === 0)
|
|
52530
|
+
return null;
|
|
52531
|
+
const data = dataLines.join(`
|
|
52532
|
+
`).trim();
|
|
52533
|
+
if (!data || data === "[DONE]")
|
|
52534
|
+
return null;
|
|
52535
|
+
try {
|
|
52536
|
+
return extractTokenUsageFromJson(JSON.parse(data), {
|
|
52537
|
+
source: source2,
|
|
52538
|
+
providerHint,
|
|
52539
|
+
rawUsagePathPrefix: source2 === "stream_file" ? "stream" : "stream"
|
|
52540
|
+
});
|
|
52541
|
+
} catch {
|
|
52542
|
+
return null;
|
|
52543
|
+
}
|
|
52544
|
+
}
|
|
52545
|
+
function extractTokenUsageFromSseText(text2, source2 = "stream_file", providerHint) {
|
|
52546
|
+
if (!text2?.trim())
|
|
52547
|
+
return null;
|
|
52548
|
+
let merged = null;
|
|
52549
|
+
let dataLines = [];
|
|
52550
|
+
const flush = () => {
|
|
52551
|
+
merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source2, providerHint));
|
|
52552
|
+
dataLines = [];
|
|
52553
|
+
};
|
|
52554
|
+
for (const rawLine of text2.split(/\r?\n/)) {
|
|
52555
|
+
if (rawLine === "") {
|
|
52556
|
+
flush();
|
|
52557
|
+
continue;
|
|
52558
|
+
}
|
|
52559
|
+
if (rawLine.startsWith("data:")) {
|
|
52560
|
+
dataLines.push(rawLine.slice(5).trimStart());
|
|
52561
|
+
}
|
|
52562
|
+
}
|
|
52563
|
+
flush();
|
|
52564
|
+
return merged;
|
|
52565
|
+
}
|
|
52566
|
+
function createTokenUsageStreamCollector(providerHint) {
|
|
52567
|
+
const decoder = new TextDecoder;
|
|
52568
|
+
let buffer2 = "";
|
|
52569
|
+
let dataLines = [];
|
|
52570
|
+
let latest = null;
|
|
52571
|
+
const flushMessage = () => {
|
|
52572
|
+
latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
|
|
52573
|
+
dataLines = [];
|
|
52574
|
+
};
|
|
52575
|
+
const processLine = (rawLine) => {
|
|
52576
|
+
const line2 = rawLine.replace(/\r$/, "");
|
|
52577
|
+
if (line2 === "") {
|
|
52578
|
+
flushMessage();
|
|
52579
|
+
return;
|
|
52580
|
+
}
|
|
52581
|
+
if (line2.startsWith("data:")) {
|
|
52582
|
+
dataLines.push(line2.slice(5).trimStart());
|
|
52583
|
+
}
|
|
52584
|
+
};
|
|
52585
|
+
return {
|
|
52586
|
+
addChunk(chunk) {
|
|
52587
|
+
buffer2 += decoder.decode(chunk, { stream: true });
|
|
52588
|
+
let newlineIndex = buffer2.indexOf(`
|
|
52589
|
+
`);
|
|
52590
|
+
while (newlineIndex >= 0) {
|
|
52591
|
+
processLine(buffer2.slice(0, newlineIndex));
|
|
52592
|
+
buffer2 = buffer2.slice(newlineIndex + 1);
|
|
52593
|
+
newlineIndex = buffer2.indexOf(`
|
|
52594
|
+
`);
|
|
52595
|
+
}
|
|
52596
|
+
},
|
|
52597
|
+
getUsage() {
|
|
52598
|
+
buffer2 += decoder.decode();
|
|
52599
|
+
if (buffer2) {
|
|
52600
|
+
processLine(buffer2);
|
|
52601
|
+
buffer2 = "";
|
|
52602
|
+
}
|
|
52603
|
+
flushMessage();
|
|
52604
|
+
return latest;
|
|
52605
|
+
}
|
|
52606
|
+
};
|
|
52607
|
+
}
|
|
52608
|
+
function safeReadStreamFile(streamFile, baseDir) {
|
|
52609
|
+
if (!streamFile)
|
|
52610
|
+
return { content: null, warning: null };
|
|
52611
|
+
try {
|
|
52612
|
+
const candidates = [streamFile];
|
|
52613
|
+
if (baseDir)
|
|
52614
|
+
candidates.push(resolve5(baseDir, streamFile));
|
|
52615
|
+
for (const candidate of candidates) {
|
|
52616
|
+
const resolved = resolve5(candidate);
|
|
52617
|
+
if (!resolved.endsWith(".sse.raw"))
|
|
52618
|
+
continue;
|
|
52619
|
+
if (!existsSync4(resolved))
|
|
52620
|
+
continue;
|
|
52621
|
+
const stats = statSync(resolved);
|
|
52622
|
+
if (stats.size > MAX_STREAM_USAGE_BYTES) {
|
|
52623
|
+
return {
|
|
52624
|
+
content: null,
|
|
52625
|
+
warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
|
|
52626
|
+
};
|
|
52627
|
+
}
|
|
52628
|
+
return { content: readFileSync4(resolved, "utf-8"), warning: null };
|
|
52629
|
+
}
|
|
52630
|
+
} catch (err) {
|
|
52631
|
+
return {
|
|
52632
|
+
content: null,
|
|
52633
|
+
warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
52634
|
+
};
|
|
52635
|
+
}
|
|
52636
|
+
return { content: null, warning: null };
|
|
52637
|
+
}
|
|
52638
|
+
function extractTokenUsageFromLogEvent(event, options = {}) {
|
|
52639
|
+
if (event.token_usage) {
|
|
52640
|
+
return {
|
|
52641
|
+
rawUsage: null,
|
|
52642
|
+
...event.token_usage,
|
|
52643
|
+
source: event.token_usage.source ?? "explicit"
|
|
52644
|
+
};
|
|
52645
|
+
}
|
|
52646
|
+
const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
|
|
52647
|
+
const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
|
|
52648
|
+
if (responseBodyUsage)
|
|
52649
|
+
return responseBodyUsage;
|
|
52650
|
+
const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
|
|
52651
|
+
if (responseAfterPluginsUsage)
|
|
52652
|
+
return responseAfterPluginsUsage;
|
|
52653
|
+
const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
|
|
52654
|
+
if (responseBeforePluginsUsage)
|
|
52655
|
+
return responseBeforePluginsUsage;
|
|
52656
|
+
const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
|
|
52657
|
+
return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
|
|
52658
|
+
}
|
|
52659
|
+
function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
|
|
52660
|
+
const metrics = extractTokenUsageFromLogEvent(event, options);
|
|
52661
|
+
return metrics ? toTokenUsageSummary(metrics) : null;
|
|
52662
|
+
}
|
|
52663
|
+
function enrichLogEventTokenUsage(event, options = {}) {
|
|
52664
|
+
if (event.token_usage)
|
|
52665
|
+
return event;
|
|
52666
|
+
const tokenUsage = extractTokenUsageFromLogEvent(event, options);
|
|
52667
|
+
if (!tokenUsage)
|
|
52668
|
+
return event;
|
|
52669
|
+
return {
|
|
52670
|
+
...event,
|
|
52671
|
+
token_usage: tokenUsage
|
|
52672
|
+
};
|
|
52673
|
+
}
|
|
52674
|
+
|
|
51885
52675
|
// src/log-index.ts
|
|
51886
|
-
var SCHEMA_VERSION =
|
|
52676
|
+
var SCHEMA_VERSION = 3;
|
|
51887
52677
|
var MAX_INDEX_QUEUE = 20000;
|
|
51888
52678
|
var INDEX_BATCH_SIZE = 250;
|
|
51889
52679
|
var INDEX_FLUSH_DELAY_MS = 50;
|
|
@@ -52027,6 +52817,7 @@ function eventToRow(input) {
|
|
|
52027
52817
|
const statusClass = getStatusClass2(event);
|
|
52028
52818
|
const latencyMs = Math.max(0, event.latency_ms ?? 0);
|
|
52029
52819
|
const model = event.model_out || event.model_in;
|
|
52820
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir: input.baseDir });
|
|
52030
52821
|
return {
|
|
52031
52822
|
id: input.id,
|
|
52032
52823
|
ts_ms: tsMs,
|
|
@@ -52054,8 +52845,46 @@ function eventToRow(input) {
|
|
|
52054
52845
|
line_number: input.lineNumber,
|
|
52055
52846
|
byte_offset: input.offset,
|
|
52056
52847
|
byte_length: input.byteLength,
|
|
52057
|
-
search_text: buildSearchText(event)
|
|
52058
|
-
|
|
52848
|
+
search_text: buildSearchText(event),
|
|
52849
|
+
token_input: tokenUsage?.inputTokens ?? null,
|
|
52850
|
+
token_output: tokenUsage?.outputTokens ?? null,
|
|
52851
|
+
token_total: tokenUsage?.totalTokens ?? null,
|
|
52852
|
+
token_cached_input: tokenUsage?.cachedInputTokens ?? null,
|
|
52853
|
+
token_cache_hit_input: tokenUsage?.cacheHitInputTokens ?? null,
|
|
52854
|
+
token_cache_hit_rate: tokenUsage?.cacheHitRate ?? null,
|
|
52855
|
+
token_cache_hit_rate_denominator: tokenUsage?.cacheHitRateDenominatorTokens ?? null,
|
|
52856
|
+
token_cache_read_input: tokenUsage?.cacheReadInputTokens ?? null,
|
|
52857
|
+
token_cache_creation_input: tokenUsage?.cacheCreationInputTokens ?? null,
|
|
52858
|
+
token_cache_creation_input_5m: tokenUsage?.cacheCreationInputTokens5m ?? null,
|
|
52859
|
+
token_cache_creation_input_1h: tokenUsage?.cacheCreationInputTokens1h ?? null,
|
|
52860
|
+
token_cache_write_input: tokenUsage?.cacheWriteInputTokens ?? null,
|
|
52861
|
+
token_cache_miss_input: tokenUsage?.cacheMissInputTokens ?? null,
|
|
52862
|
+
token_reasoning: tokenUsage?.reasoningTokens ?? null,
|
|
52863
|
+
token_audio_input: tokenUsage?.audioInputTokens ?? null,
|
|
52864
|
+
token_audio_output: tokenUsage?.audioOutputTokens ?? null,
|
|
52865
|
+
token_text_input: tokenUsage?.textInputTokens ?? null,
|
|
52866
|
+
token_text_output: tokenUsage?.textOutputTokens ?? null,
|
|
52867
|
+
token_accepted_prediction: tokenUsage?.acceptedPredictionTokens ?? null,
|
|
52868
|
+
token_rejected_prediction: tokenUsage?.rejectedPredictionTokens ?? null,
|
|
52869
|
+
token_tool_use_prompt: tokenUsage?.toolUsePromptTokens ?? null,
|
|
52870
|
+
token_billable_input: tokenUsage?.billableInputTokens ?? null,
|
|
52871
|
+
token_billable_output: tokenUsage?.billableOutputTokens ?? null,
|
|
52872
|
+
token_credit_usage: tokenUsage?.creditUsage ?? null,
|
|
52873
|
+
token_cost: tokenUsage?.cost ?? null,
|
|
52874
|
+
token_source: tokenUsage?.source ?? null,
|
|
52875
|
+
token_provider_style: tokenUsage?.providerStyle ?? null,
|
|
52876
|
+
token_raw_usage_path: tokenUsage?.rawUsagePath ?? null,
|
|
52877
|
+
token_usage_json: tokenUsage ? JSON.stringify(tokenUsage) : null
|
|
52878
|
+
};
|
|
52879
|
+
}
|
|
52880
|
+
function parseTokenUsageSummary(value) {
|
|
52881
|
+
if (!value)
|
|
52882
|
+
return null;
|
|
52883
|
+
try {
|
|
52884
|
+
return JSON.parse(value);
|
|
52885
|
+
} catch {
|
|
52886
|
+
return null;
|
|
52887
|
+
}
|
|
52059
52888
|
}
|
|
52060
52889
|
function rowToSummary(row) {
|
|
52061
52890
|
return {
|
|
@@ -52078,7 +52907,8 @@ function rowToSummary(row) {
|
|
|
52078
52907
|
hasMetadata: row.has_metadata === 1,
|
|
52079
52908
|
userIdRaw: row.user_id_raw,
|
|
52080
52909
|
userKey: row.user_key,
|
|
52081
|
-
sessionId: row.session_id
|
|
52910
|
+
sessionId: row.session_id,
|
|
52911
|
+
tokenUsage: parseTokenUsageSummary(row.token_usage_json)
|
|
52082
52912
|
};
|
|
52083
52913
|
}
|
|
52084
52914
|
async function* readJsonlLinesWithOffsets(filePath) {
|
|
@@ -52148,7 +52978,22 @@ function createEmptyStats() {
|
|
|
52148
52978
|
errorCount: 0,
|
|
52149
52979
|
errorRate: 0,
|
|
52150
52980
|
avgLatencyMs: 0,
|
|
52151
|
-
p95LatencyMs: 0
|
|
52981
|
+
p95LatencyMs: 0,
|
|
52982
|
+
tokenUsageCount: 0,
|
|
52983
|
+
inputTokens: 0,
|
|
52984
|
+
outputTokens: 0,
|
|
52985
|
+
totalTokens: 0,
|
|
52986
|
+
cachedInputTokens: 0,
|
|
52987
|
+
cacheHitInputTokens: 0,
|
|
52988
|
+
cacheHitRate: 0,
|
|
52989
|
+
cacheHitRateDenominatorTokens: 0,
|
|
52990
|
+
cacheReadInputTokens: 0,
|
|
52991
|
+
cacheCreationInputTokens: 0,
|
|
52992
|
+
cacheWriteInputTokens: 0,
|
|
52993
|
+
cacheMissInputTokens: 0,
|
|
52994
|
+
reasoningTokens: 0,
|
|
52995
|
+
billableInputTokens: 0,
|
|
52996
|
+
billableOutputTokens: 0
|
|
52152
52997
|
};
|
|
52153
52998
|
}
|
|
52154
52999
|
function createEmptyQueryResult(query, meta3 = {}) {
|
|
@@ -52231,8 +53076,8 @@ class LogIndex {
|
|
|
52231
53076
|
constructor(baseDir, config2) {
|
|
52232
53077
|
this.baseDir = baseDir;
|
|
52233
53078
|
this.config = config2;
|
|
52234
|
-
|
|
52235
|
-
const dbPath =
|
|
53079
|
+
mkdirSync3(baseDir, { recursive: true });
|
|
53080
|
+
const dbPath = join6(baseDir, "logs-index.sqlite");
|
|
52236
53081
|
this.db = new Database(dbPath, { create: true, strict: true });
|
|
52237
53082
|
this.configure();
|
|
52238
53083
|
this.migrate();
|
|
@@ -52241,12 +53086,28 @@ class LogIndex {
|
|
|
52241
53086
|
id, ts_ms, ts_start, level, provider, route_type, model, model_in, model_out,
|
|
52242
53087
|
path, request_id, latency_ms, upstream_status, status_class, has_error,
|
|
52243
53088
|
message, error_type, has_metadata, user_id_raw, user_key, session_id,
|
|
52244
|
-
source_date, source_file, line_number, byte_offset, byte_length, search_text
|
|
53089
|
+
source_date, source_file, line_number, byte_offset, byte_length, search_text,
|
|
53090
|
+
token_input, token_output, token_total, token_cached_input, token_cache_hit_input,
|
|
53091
|
+
token_cache_hit_rate, token_cache_hit_rate_denominator, token_cache_read_input,
|
|
53092
|
+
token_cache_creation_input, token_cache_creation_input_5m, token_cache_creation_input_1h,
|
|
53093
|
+
token_cache_write_input, token_cache_miss_input, token_reasoning, token_audio_input,
|
|
53094
|
+
token_audio_output, token_text_input, token_text_output, token_accepted_prediction,
|
|
53095
|
+
token_rejected_prediction, token_tool_use_prompt, token_billable_input,
|
|
53096
|
+
token_billable_output, token_credit_usage, token_cost, token_source,
|
|
53097
|
+
token_provider_style, token_raw_usage_path, token_usage_json
|
|
52245
53098
|
) VALUES (
|
|
52246
53099
|
$id, $ts_ms, $ts_start, $level, $provider, $route_type, $model, $model_in, $model_out,
|
|
52247
53100
|
$path, $request_id, $latency_ms, $upstream_status, $status_class, $has_error,
|
|
52248
53101
|
$message, $error_type, $has_metadata, $user_id_raw, $user_key, $session_id,
|
|
52249
|
-
$source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text
|
|
53102
|
+
$source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text,
|
|
53103
|
+
$token_input, $token_output, $token_total, $token_cached_input, $token_cache_hit_input,
|
|
53104
|
+
$token_cache_hit_rate, $token_cache_hit_rate_denominator, $token_cache_read_input,
|
|
53105
|
+
$token_cache_creation_input, $token_cache_creation_input_5m, $token_cache_creation_input_1h,
|
|
53106
|
+
$token_cache_write_input, $token_cache_miss_input, $token_reasoning, $token_audio_input,
|
|
53107
|
+
$token_audio_output, $token_text_input, $token_text_output, $token_accepted_prediction,
|
|
53108
|
+
$token_rejected_prediction, $token_tool_use_prompt, $token_billable_input,
|
|
53109
|
+
$token_billable_output, $token_credit_usage, $token_cost, $token_source,
|
|
53110
|
+
$token_provider_style, $token_raw_usage_path, $token_usage_json
|
|
52250
53111
|
)
|
|
52251
53112
|
`);
|
|
52252
53113
|
this.deleteFtsStmt = this.db.prepare("DELETE FROM log_events_fts WHERE event_id = ?");
|
|
@@ -52292,13 +53153,13 @@ class LogIndex {
|
|
|
52292
53153
|
let scannedFiles = 0;
|
|
52293
53154
|
let scannedLines = 0;
|
|
52294
53155
|
let parseErrors = 0;
|
|
52295
|
-
const eventsDir =
|
|
53156
|
+
const eventsDir = join6(this.baseDir, "events");
|
|
52296
53157
|
const dates = listDateStrings2(fromMs, toMs);
|
|
52297
53158
|
for (const date5 of dates) {
|
|
52298
|
-
const filePath =
|
|
52299
|
-
if (!
|
|
53159
|
+
const filePath = join6(eventsDir, `${date5}.jsonl`);
|
|
53160
|
+
if (!existsSync5(filePath))
|
|
52300
53161
|
continue;
|
|
52301
|
-
const stats =
|
|
53162
|
+
const stats = statSync2(filePath);
|
|
52302
53163
|
const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
|
|
52303
53164
|
const sizeBytes = stats.size;
|
|
52304
53165
|
const mtimeMs = Math.trunc(stats.mtimeMs);
|
|
@@ -52333,7 +53194,7 @@ class LogIndex {
|
|
|
52333
53194
|
e.id, e.ts_start, e.level, e.provider, e.route_type, e.model, e.model_in,
|
|
52334
53195
|
e.model_out, e.path, e.request_id, e.latency_ms, e.upstream_status,
|
|
52335
53196
|
e.status_class, e.has_error, e.message, e.error_type, e.has_metadata,
|
|
52336
|
-
e.user_id_raw, e.user_key, e.session_id, e.ts_ms
|
|
53197
|
+
e.user_id_raw, e.user_key, e.session_id, e.ts_ms, e.token_usage_json
|
|
52337
53198
|
FROM log_events e
|
|
52338
53199
|
${whereSql}
|
|
52339
53200
|
${cursorClause}
|
|
@@ -52387,8 +53248,8 @@ class LogIndex {
|
|
|
52387
53248
|
if (!parsedId)
|
|
52388
53249
|
return null;
|
|
52389
53250
|
const row = this.db.query("SELECT source_date, source_file, line_number, byte_offset FROM log_events WHERE id = ?").get(id);
|
|
52390
|
-
const filePath = row?.source_file ??
|
|
52391
|
-
if (!
|
|
53251
|
+
const filePath = row?.source_file ?? join6(this.baseDir, "events", `${parsedId.date}.jsonl`);
|
|
53252
|
+
if (!existsSync5(filePath))
|
|
52392
53253
|
return null;
|
|
52393
53254
|
const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
|
|
52394
53255
|
if (!line2?.trim())
|
|
@@ -52456,7 +53317,36 @@ class LogIndex {
|
|
|
52456
53317
|
line_number INTEGER,
|
|
52457
53318
|
byte_offset INTEGER NOT NULL,
|
|
52458
53319
|
byte_length INTEGER NOT NULL,
|
|
52459
|
-
search_text TEXT NOT NULL
|
|
53320
|
+
search_text TEXT NOT NULL,
|
|
53321
|
+
token_input INTEGER,
|
|
53322
|
+
token_output INTEGER,
|
|
53323
|
+
token_total INTEGER,
|
|
53324
|
+
token_cached_input INTEGER,
|
|
53325
|
+
token_cache_hit_input INTEGER,
|
|
53326
|
+
token_cache_hit_rate REAL,
|
|
53327
|
+
token_cache_hit_rate_denominator INTEGER,
|
|
53328
|
+
token_cache_read_input INTEGER,
|
|
53329
|
+
token_cache_creation_input INTEGER,
|
|
53330
|
+
token_cache_creation_input_5m INTEGER,
|
|
53331
|
+
token_cache_creation_input_1h INTEGER,
|
|
53332
|
+
token_cache_write_input INTEGER,
|
|
53333
|
+
token_cache_miss_input INTEGER,
|
|
53334
|
+
token_reasoning INTEGER,
|
|
53335
|
+
token_audio_input INTEGER,
|
|
53336
|
+
token_audio_output INTEGER,
|
|
53337
|
+
token_text_input INTEGER,
|
|
53338
|
+
token_text_output INTEGER,
|
|
53339
|
+
token_accepted_prediction INTEGER,
|
|
53340
|
+
token_rejected_prediction INTEGER,
|
|
53341
|
+
token_tool_use_prompt INTEGER,
|
|
53342
|
+
token_billable_input INTEGER,
|
|
53343
|
+
token_billable_output INTEGER,
|
|
53344
|
+
token_credit_usage REAL,
|
|
53345
|
+
token_cost REAL,
|
|
53346
|
+
token_source TEXT,
|
|
53347
|
+
token_provider_style TEXT,
|
|
53348
|
+
token_raw_usage_path TEXT,
|
|
53349
|
+
token_usage_json TEXT
|
|
52460
53350
|
);
|
|
52461
53351
|
|
|
52462
53352
|
CREATE VIRTUAL TABLE IF NOT EXISTS log_events_fts
|
|
@@ -52474,12 +53364,67 @@ class LogIndex {
|
|
|
52474
53364
|
CREATE INDEX IF NOT EXISTS idx_log_events_session_time ON log_events(session_id, ts_ms DESC);
|
|
52475
53365
|
CREATE INDEX IF NOT EXISTS idx_log_events_file ON log_events(source_file);
|
|
52476
53366
|
`);
|
|
53367
|
+
this.ensureTokenColumns();
|
|
53368
|
+
this.db.exec(`
|
|
53369
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_total_time ON log_events(token_total, ts_ms DESC);
|
|
53370
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_input_time ON log_events(token_input, ts_ms DESC);
|
|
53371
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_cache_hit_rate_time ON log_events(token_cache_hit_rate, ts_ms DESC);
|
|
53372
|
+
`);
|
|
53373
|
+
const versionRow = this.db.query("SELECT value FROM log_index_meta WHERE key = 'schema_version'").get();
|
|
53374
|
+
const previousVersion = Number.parseInt(versionRow?.value ?? "0", 10) || 0;
|
|
53375
|
+
if (previousVersion > 0 && previousVersion < SCHEMA_VERSION) {
|
|
53376
|
+
this.db.exec(`
|
|
53377
|
+
DELETE FROM log_events_fts;
|
|
53378
|
+
DELETE FROM log_events;
|
|
53379
|
+
DELETE FROM log_index_files;
|
|
53380
|
+
`);
|
|
53381
|
+
}
|
|
52477
53382
|
this.db.prepare(`
|
|
52478
53383
|
INSERT INTO log_index_meta(key, value)
|
|
52479
53384
|
VALUES ('schema_version', ?)
|
|
52480
53385
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
52481
53386
|
`).run(String(SCHEMA_VERSION));
|
|
52482
53387
|
}
|
|
53388
|
+
ensureTokenColumns() {
|
|
53389
|
+
const rows = this.db.query("PRAGMA table_info(log_events)").all();
|
|
53390
|
+
const existing = new Set(rows.map((row) => row.name));
|
|
53391
|
+
const columns = [
|
|
53392
|
+
{ name: "token_input", type: "INTEGER" },
|
|
53393
|
+
{ name: "token_output", type: "INTEGER" },
|
|
53394
|
+
{ name: "token_total", type: "INTEGER" },
|
|
53395
|
+
{ name: "token_cached_input", type: "INTEGER" },
|
|
53396
|
+
{ name: "token_cache_hit_input", type: "INTEGER" },
|
|
53397
|
+
{ name: "token_cache_hit_rate", type: "REAL" },
|
|
53398
|
+
{ name: "token_cache_hit_rate_denominator", type: "INTEGER" },
|
|
53399
|
+
{ name: "token_cache_read_input", type: "INTEGER" },
|
|
53400
|
+
{ name: "token_cache_creation_input", type: "INTEGER" },
|
|
53401
|
+
{ name: "token_cache_creation_input_5m", type: "INTEGER" },
|
|
53402
|
+
{ name: "token_cache_creation_input_1h", type: "INTEGER" },
|
|
53403
|
+
{ name: "token_cache_write_input", type: "INTEGER" },
|
|
53404
|
+
{ name: "token_cache_miss_input", type: "INTEGER" },
|
|
53405
|
+
{ name: "token_reasoning", type: "INTEGER" },
|
|
53406
|
+
{ name: "token_audio_input", type: "INTEGER" },
|
|
53407
|
+
{ name: "token_audio_output", type: "INTEGER" },
|
|
53408
|
+
{ name: "token_text_input", type: "INTEGER" },
|
|
53409
|
+
{ name: "token_text_output", type: "INTEGER" },
|
|
53410
|
+
{ name: "token_accepted_prediction", type: "INTEGER" },
|
|
53411
|
+
{ name: "token_rejected_prediction", type: "INTEGER" },
|
|
53412
|
+
{ name: "token_tool_use_prompt", type: "INTEGER" },
|
|
53413
|
+
{ name: "token_billable_input", type: "INTEGER" },
|
|
53414
|
+
{ name: "token_billable_output", type: "INTEGER" },
|
|
53415
|
+
{ name: "token_credit_usage", type: "REAL" },
|
|
53416
|
+
{ name: "token_cost", type: "REAL" },
|
|
53417
|
+
{ name: "token_source", type: "TEXT" },
|
|
53418
|
+
{ name: "token_provider_style", type: "TEXT" },
|
|
53419
|
+
{ name: "token_raw_usage_path", type: "TEXT" },
|
|
53420
|
+
{ name: "token_usage_json", type: "TEXT" }
|
|
53421
|
+
];
|
|
53422
|
+
for (const column2 of columns) {
|
|
53423
|
+
if (existing.has(column2.name))
|
|
53424
|
+
continue;
|
|
53425
|
+
this.db.exec(`ALTER TABLE log_events ADD COLUMN ${column2.name} ${column2.type}`);
|
|
53426
|
+
}
|
|
53427
|
+
}
|
|
52483
53428
|
flushQueue() {
|
|
52484
53429
|
if (this.queue.length === 0 || this.disposed)
|
|
52485
53430
|
return;
|
|
@@ -52507,6 +53452,7 @@ class LogIndex {
|
|
|
52507
53452
|
return;
|
|
52508
53453
|
const id = encodeOffsetLogEventId(item.date, item.offset);
|
|
52509
53454
|
const row = eventToRow({
|
|
53455
|
+
baseDir: this.baseDir,
|
|
52510
53456
|
id,
|
|
52511
53457
|
date: item.date,
|
|
52512
53458
|
filePath: item.filePath,
|
|
@@ -52522,7 +53468,7 @@ class LogIndex {
|
|
|
52522
53468
|
this.insertFtsStmt.run(id, row.search_text);
|
|
52523
53469
|
if (!this.dirtyFiles.has(item.filePath)) {
|
|
52524
53470
|
try {
|
|
52525
|
-
const stats =
|
|
53471
|
+
const stats = statSync2(item.filePath);
|
|
52526
53472
|
const indexedThrough = item.offset + item.byteLength;
|
|
52527
53473
|
this.upsertFileStmt.run(item.filePath, item.date, Math.min(indexedThrough, stats.size), Math.trunc(stats.mtimeMs), Date.now());
|
|
52528
53474
|
} catch {}
|
|
@@ -52549,6 +53495,7 @@ class LogIndex {
|
|
|
52549
53495
|
continue;
|
|
52550
53496
|
}
|
|
52551
53497
|
const row = eventToRow({
|
|
53498
|
+
baseDir: this.baseDir,
|
|
52552
53499
|
id: encodeOffsetLogEventId(date5, item.offset),
|
|
52553
53500
|
date: date5,
|
|
52554
53501
|
filePath,
|
|
@@ -52581,7 +53528,21 @@ class LogIndex {
|
|
|
52581
53528
|
SELECT
|
|
52582
53529
|
COUNT(*) AS total,
|
|
52583
53530
|
COALESCE(SUM(has_error), 0) AS errorCount,
|
|
52584
|
-
COALESCE(AVG(latency_ms), 0) AS avgLatencyMs
|
|
53531
|
+
COALESCE(AVG(latency_ms), 0) AS avgLatencyMs,
|
|
53532
|
+
COALESCE(SUM(CASE WHEN token_usage_json IS NOT NULL THEN 1 ELSE 0 END), 0) AS tokenUsageCount,
|
|
53533
|
+
COALESCE(SUM(token_input), 0) AS inputTokens,
|
|
53534
|
+
COALESCE(SUM(token_output), 0) AS outputTokens,
|
|
53535
|
+
COALESCE(SUM(token_total), 0) AS totalTokens,
|
|
53536
|
+
COALESCE(SUM(token_cached_input), 0) AS cachedInputTokens,
|
|
53537
|
+
COALESCE(SUM(token_cache_hit_input), 0) AS cacheHitInputTokens,
|
|
53538
|
+
COALESCE(SUM(token_cache_hit_rate_denominator), 0) AS cacheHitRateDenominatorTokens,
|
|
53539
|
+
COALESCE(SUM(token_cache_read_input), 0) AS cacheReadInputTokens,
|
|
53540
|
+
COALESCE(SUM(token_cache_creation_input), 0) AS cacheCreationInputTokens,
|
|
53541
|
+
COALESCE(SUM(token_cache_write_input), 0) AS cacheWriteInputTokens,
|
|
53542
|
+
COALESCE(SUM(token_cache_miss_input), 0) AS cacheMissInputTokens,
|
|
53543
|
+
COALESCE(SUM(token_reasoning), 0) AS reasoningTokens,
|
|
53544
|
+
COALESCE(SUM(token_billable_input), 0) AS billableInputTokens,
|
|
53545
|
+
COALESCE(SUM(token_billable_output), 0) AS billableOutputTokens
|
|
52585
53546
|
FROM log_events e
|
|
52586
53547
|
${whereSql}
|
|
52587
53548
|
`).get(...params);
|
|
@@ -52602,7 +53563,22 @@ class LogIndex {
|
|
|
52602
53563
|
errorCount,
|
|
52603
53564
|
errorRate: toPercent2(errorCount, total),
|
|
52604
53565
|
avgLatencyMs: Math.round(Number(aggregate.avgLatencyMs) || 0),
|
|
52605
|
-
p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0)
|
|
53566
|
+
p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0),
|
|
53567
|
+
tokenUsageCount: Number(aggregate.tokenUsageCount) || 0,
|
|
53568
|
+
inputTokens: Number(aggregate.inputTokens) || 0,
|
|
53569
|
+
outputTokens: Number(aggregate.outputTokens) || 0,
|
|
53570
|
+
totalTokens: Number(aggregate.totalTokens) || 0,
|
|
53571
|
+
cachedInputTokens: Number(aggregate.cachedInputTokens) || 0,
|
|
53572
|
+
cacheHitInputTokens: Number(aggregate.cacheHitInputTokens) || 0,
|
|
53573
|
+
cacheHitRate: toPercent2(Number(aggregate.cacheHitInputTokens) || 0, Number(aggregate.cacheHitRateDenominatorTokens) || 0),
|
|
53574
|
+
cacheHitRateDenominatorTokens: Number(aggregate.cacheHitRateDenominatorTokens) || 0,
|
|
53575
|
+
cacheReadInputTokens: Number(aggregate.cacheReadInputTokens) || 0,
|
|
53576
|
+
cacheCreationInputTokens: Number(aggregate.cacheCreationInputTokens) || 0,
|
|
53577
|
+
cacheWriteInputTokens: Number(aggregate.cacheWriteInputTokens) || 0,
|
|
53578
|
+
cacheMissInputTokens: Number(aggregate.cacheMissInputTokens) || 0,
|
|
53579
|
+
reasoningTokens: Number(aggregate.reasoningTokens) || 0,
|
|
53580
|
+
billableInputTokens: Number(aggregate.billableInputTokens) || 0,
|
|
53581
|
+
billableOutputTokens: Number(aggregate.billableOutputTokens) || 0
|
|
52606
53582
|
};
|
|
52607
53583
|
}
|
|
52608
53584
|
}
|
|
@@ -52682,8 +53658,8 @@ function getIndexedLogEventDetail(logConfig, id) {
|
|
|
52682
53658
|
const parsedId = decodeOffsetLogEventId(id);
|
|
52683
53659
|
if (!parsedId)
|
|
52684
53660
|
return null;
|
|
52685
|
-
const filePath =
|
|
52686
|
-
if (!
|
|
53661
|
+
const filePath = join6(baseDir, "events", `${parsedId.date}.jsonl`);
|
|
53662
|
+
if (!existsSync5(filePath))
|
|
52687
53663
|
return null;
|
|
52688
53664
|
const line2 = readLineAtOffset(filePath, parsedId.offset);
|
|
52689
53665
|
if (!line2?.trim())
|
|
@@ -52790,12 +53766,8 @@ function buildMessage2(event) {
|
|
|
52790
53766
|
const status = event.upstream_status ?? 0;
|
|
52791
53767
|
return `${event.method} ${event.path} -> ${status}`;
|
|
52792
53768
|
}
|
|
52793
|
-
function
|
|
52794
|
-
|
|
52795
|
-
return true;
|
|
52796
|
-
const identity = resolveLogSessionIdentity(event.request_body);
|
|
52797
|
-
const keyword = q.toLowerCase();
|
|
52798
|
-
const haystack = [
|
|
53769
|
+
function buildKeywordText(event, identity) {
|
|
53770
|
+
return [
|
|
52799
53771
|
event.request_id,
|
|
52800
53772
|
event.path,
|
|
52801
53773
|
event.provider,
|
|
@@ -52809,15 +53781,38 @@ function containsKeyword(event, q) {
|
|
|
52809
53781
|
event.error_message ?? "",
|
|
52810
53782
|
buildMessage2(event)
|
|
52811
53783
|
].join(" ").toLowerCase();
|
|
52812
|
-
|
|
53784
|
+
}
|
|
53785
|
+
function containsKeyword(event, q) {
|
|
53786
|
+
if (!q)
|
|
53787
|
+
return true;
|
|
53788
|
+
const identity = resolveLogSessionIdentity(event.request_body);
|
|
53789
|
+
const keyword = q.toLowerCase();
|
|
53790
|
+
return buildKeywordText(event, identity).includes(keyword);
|
|
52813
53791
|
}
|
|
52814
53792
|
function createRunningStats() {
|
|
52815
53793
|
return {
|
|
52816
53794
|
total: 0,
|
|
52817
53795
|
errorCount: 0,
|
|
52818
53796
|
latencySum: 0,
|
|
52819
|
-
latencyCounts: new Map
|
|
52820
|
-
|
|
53797
|
+
latencyCounts: new Map,
|
|
53798
|
+
tokenUsageCount: 0,
|
|
53799
|
+
inputTokens: 0,
|
|
53800
|
+
outputTokens: 0,
|
|
53801
|
+
totalTokens: 0,
|
|
53802
|
+
cachedInputTokens: 0,
|
|
53803
|
+
cacheHitInputTokens: 0,
|
|
53804
|
+
cacheHitRateDenominatorTokens: 0,
|
|
53805
|
+
cacheReadInputTokens: 0,
|
|
53806
|
+
cacheCreationInputTokens: 0,
|
|
53807
|
+
cacheWriteInputTokens: 0,
|
|
53808
|
+
cacheMissInputTokens: 0,
|
|
53809
|
+
reasoningTokens: 0,
|
|
53810
|
+
billableInputTokens: 0,
|
|
53811
|
+
billableOutputTokens: 0
|
|
53812
|
+
};
|
|
53813
|
+
}
|
|
53814
|
+
function addNullable(total, value) {
|
|
53815
|
+
return total + (value ?? 0);
|
|
52821
53816
|
}
|
|
52822
53817
|
function updateRunningStats(stats, item) {
|
|
52823
53818
|
stats.total += 1;
|
|
@@ -52828,6 +53823,23 @@ function updateRunningStats(stats, item) {
|
|
|
52828
53823
|
stats.latencySum += latency;
|
|
52829
53824
|
const roundedLatency = Math.round(latency);
|
|
52830
53825
|
stats.latencyCounts.set(roundedLatency, (stats.latencyCounts.get(roundedLatency) ?? 0) + 1);
|
|
53826
|
+
const usage = item.tokenUsage;
|
|
53827
|
+
if (!usage)
|
|
53828
|
+
return;
|
|
53829
|
+
stats.tokenUsageCount += 1;
|
|
53830
|
+
stats.inputTokens = addNullable(stats.inputTokens, usage.inputTokens);
|
|
53831
|
+
stats.outputTokens = addNullable(stats.outputTokens, usage.outputTokens);
|
|
53832
|
+
stats.totalTokens = addNullable(stats.totalTokens, usage.totalTokens);
|
|
53833
|
+
stats.cachedInputTokens = addNullable(stats.cachedInputTokens, usage.cachedInputTokens);
|
|
53834
|
+
stats.cacheHitInputTokens = addNullable(stats.cacheHitInputTokens, usage.cacheHitInputTokens);
|
|
53835
|
+
stats.cacheHitRateDenominatorTokens = addNullable(stats.cacheHitRateDenominatorTokens, usage.cacheHitRateDenominatorTokens);
|
|
53836
|
+
stats.cacheReadInputTokens = addNullable(stats.cacheReadInputTokens, usage.cacheReadInputTokens);
|
|
53837
|
+
stats.cacheCreationInputTokens = addNullable(stats.cacheCreationInputTokens, usage.cacheCreationInputTokens);
|
|
53838
|
+
stats.cacheWriteInputTokens = addNullable(stats.cacheWriteInputTokens, usage.cacheWriteInputTokens);
|
|
53839
|
+
stats.cacheMissInputTokens = addNullable(stats.cacheMissInputTokens, usage.cacheMissInputTokens);
|
|
53840
|
+
stats.reasoningTokens = addNullable(stats.reasoningTokens, usage.reasoningTokens);
|
|
53841
|
+
stats.billableInputTokens = addNullable(stats.billableInputTokens, usage.billableInputTokens);
|
|
53842
|
+
stats.billableOutputTokens = addNullable(stats.billableOutputTokens, usage.billableOutputTokens);
|
|
52831
53843
|
}
|
|
52832
53844
|
function percentileFromCounts(latencyCounts, total, ratio) {
|
|
52833
53845
|
if (total <= 0 || latencyCounts.size === 0)
|
|
@@ -52850,7 +53862,22 @@ function finalizeStats(stats) {
|
|
|
52850
53862
|
errorCount: 0,
|
|
52851
53863
|
errorRate: 0,
|
|
52852
53864
|
avgLatencyMs: 0,
|
|
52853
|
-
p95LatencyMs: 0
|
|
53865
|
+
p95LatencyMs: 0,
|
|
53866
|
+
tokenUsageCount: 0,
|
|
53867
|
+
inputTokens: 0,
|
|
53868
|
+
outputTokens: 0,
|
|
53869
|
+
totalTokens: 0,
|
|
53870
|
+
cachedInputTokens: 0,
|
|
53871
|
+
cacheHitInputTokens: 0,
|
|
53872
|
+
cacheHitRate: 0,
|
|
53873
|
+
cacheHitRateDenominatorTokens: 0,
|
|
53874
|
+
cacheReadInputTokens: 0,
|
|
53875
|
+
cacheCreationInputTokens: 0,
|
|
53876
|
+
cacheWriteInputTokens: 0,
|
|
53877
|
+
cacheMissInputTokens: 0,
|
|
53878
|
+
reasoningTokens: 0,
|
|
53879
|
+
billableInputTokens: 0,
|
|
53880
|
+
billableOutputTokens: 0
|
|
52854
53881
|
};
|
|
52855
53882
|
}
|
|
52856
53883
|
return {
|
|
@@ -52858,8 +53885,26 @@ function finalizeStats(stats) {
|
|
|
52858
53885
|
errorCount: stats.errorCount,
|
|
52859
53886
|
errorRate: toPercent3(stats.errorCount, stats.total),
|
|
52860
53887
|
avgLatencyMs: Math.round(stats.latencySum / stats.total),
|
|
52861
|
-
p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95)
|
|
52862
|
-
|
|
53888
|
+
p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95),
|
|
53889
|
+
tokenUsageCount: stats.tokenUsageCount,
|
|
53890
|
+
inputTokens: stats.inputTokens,
|
|
53891
|
+
outputTokens: stats.outputTokens,
|
|
53892
|
+
totalTokens: stats.totalTokens,
|
|
53893
|
+
cachedInputTokens: stats.cachedInputTokens,
|
|
53894
|
+
cacheHitInputTokens: stats.cacheHitInputTokens,
|
|
53895
|
+
cacheHitRate: toPercent3(stats.cacheHitInputTokens, stats.cacheHitRateDenominatorTokens),
|
|
53896
|
+
cacheHitRateDenominatorTokens: stats.cacheHitRateDenominatorTokens,
|
|
53897
|
+
cacheReadInputTokens: stats.cacheReadInputTokens,
|
|
53898
|
+
cacheCreationInputTokens: stats.cacheCreationInputTokens,
|
|
53899
|
+
cacheWriteInputTokens: stats.cacheWriteInputTokens,
|
|
53900
|
+
cacheMissInputTokens: stats.cacheMissInputTokens,
|
|
53901
|
+
reasoningTokens: stats.reasoningTokens,
|
|
53902
|
+
billableInputTokens: stats.billableInputTokens,
|
|
53903
|
+
billableOutputTokens: stats.billableOutputTokens
|
|
53904
|
+
};
|
|
53905
|
+
}
|
|
53906
|
+
function createEmptyLogQueryStats() {
|
|
53907
|
+
return finalizeStats(createRunningStats());
|
|
52863
53908
|
}
|
|
52864
53909
|
function compareLocatedEvents(a, b, sort) {
|
|
52865
53910
|
if (a.ts !== b.ts) {
|
|
@@ -52910,7 +53955,7 @@ function normalizeQuery(input, maxLimit = MAX_QUERY_LIMIT) {
|
|
|
52910
53955
|
}
|
|
52911
53956
|
function eventToSummary(item) {
|
|
52912
53957
|
const { event } = item;
|
|
52913
|
-
const identity = resolveLogSessionIdentity(event.request_body);
|
|
53958
|
+
const identity = item.identity ?? resolveLogSessionIdentity(event.request_body);
|
|
52914
53959
|
return {
|
|
52915
53960
|
id: item.id,
|
|
52916
53961
|
ts: event.ts_start,
|
|
@@ -52931,21 +53976,43 @@ function eventToSummary(item) {
|
|
|
52931
53976
|
hasMetadata: identity.hasMetadata,
|
|
52932
53977
|
userIdRaw: identity.userIdRaw,
|
|
52933
53978
|
userKey: identity.userKey,
|
|
52934
|
-
sessionId: identity.sessionId
|
|
53979
|
+
sessionId: identity.sessionId,
|
|
53980
|
+
tokenUsage: item.tokenUsage
|
|
52935
53981
|
};
|
|
52936
53982
|
}
|
|
52937
|
-
function
|
|
53983
|
+
function extractLogEventFacts(event) {
|
|
52938
53984
|
const ts = Date.parse(event.ts_start);
|
|
53985
|
+
const level = getLevel2(event);
|
|
53986
|
+
const statusClass = getStatusClass3(event);
|
|
53987
|
+
const identity = resolveLogSessionIdentity(event.request_body);
|
|
53988
|
+
return {
|
|
53989
|
+
event,
|
|
53990
|
+
ts: Number.isFinite(ts) ? ts : Number.NaN,
|
|
53991
|
+
level,
|
|
53992
|
+
statusClass,
|
|
53993
|
+
model: event.model_out || event.model_in,
|
|
53994
|
+
identity,
|
|
53995
|
+
hasError: level === "error",
|
|
53996
|
+
keywordText: buildKeywordText(event, identity),
|
|
53997
|
+
tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
|
|
53998
|
+
};
|
|
53999
|
+
}
|
|
54000
|
+
function createLogEventSummaryFromFacts(facts, location) {
|
|
52939
54001
|
return eventToSummary({
|
|
52940
54002
|
id: location.id,
|
|
52941
54003
|
date: location.date,
|
|
52942
54004
|
line: location.line ?? 0,
|
|
52943
|
-
ts:
|
|
52944
|
-
level:
|
|
52945
|
-
statusClass:
|
|
52946
|
-
event
|
|
54005
|
+
ts: facts.ts,
|
|
54006
|
+
level: facts.level,
|
|
54007
|
+
statusClass: facts.statusClass,
|
|
54008
|
+
event: facts.event,
|
|
54009
|
+
identity: facts.identity,
|
|
54010
|
+
tokenUsage: facts.tokenUsage
|
|
52947
54011
|
});
|
|
52948
54012
|
}
|
|
54013
|
+
function createLogEventSummaryFromEvent(event, location) {
|
|
54014
|
+
return createLogEventSummaryFromFacts(extractLogEventFacts(event), location);
|
|
54015
|
+
}
|
|
52949
54016
|
function logEventMatchesQuery(event, query) {
|
|
52950
54017
|
if (!event.ts_start)
|
|
52951
54018
|
return false;
|
|
@@ -53032,23 +54099,23 @@ function readStreamContent(baseDir, streamFile) {
|
|
|
53032
54099
|
if (!streamFile)
|
|
53033
54100
|
return { content: null, warning: null };
|
|
53034
54101
|
try {
|
|
53035
|
-
const resolvedBase =
|
|
53036
|
-
const resolvedFromFile =
|
|
54102
|
+
const resolvedBase = resolve6(baseDir);
|
|
54103
|
+
const resolvedFromFile = resolve6(streamFile);
|
|
53037
54104
|
const looksLikeStreamFile = resolvedFromFile.endsWith(".sse.raw");
|
|
53038
54105
|
if (!looksLikeStreamFile) {
|
|
53039
54106
|
return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
|
|
53040
54107
|
}
|
|
53041
|
-
if (
|
|
53042
|
-
return { content:
|
|
54108
|
+
if (existsSync6(resolvedFromFile)) {
|
|
54109
|
+
return { content: readFileSync5(resolvedFromFile, "utf-8"), warning: null };
|
|
53043
54110
|
}
|
|
53044
|
-
const fallbackPath =
|
|
54111
|
+
const fallbackPath = resolve6(resolvedBase, streamFile);
|
|
53045
54112
|
if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
|
|
53046
54113
|
return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
|
|
53047
54114
|
}
|
|
53048
|
-
if (!
|
|
54115
|
+
if (!existsSync6(fallbackPath)) {
|
|
53049
54116
|
return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
|
|
53050
54117
|
}
|
|
53051
|
-
return { content:
|
|
54118
|
+
return { content: readFileSync5(fallbackPath, "utf-8"), warning: null };
|
|
53052
54119
|
} catch (err) {
|
|
53053
54120
|
return {
|
|
53054
54121
|
content: null,
|
|
@@ -53065,6 +54132,10 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53065
54132
|
const responseBodyAvailable = event.response_body !== undefined;
|
|
53066
54133
|
const streamCaptured = Boolean(event.stream_file);
|
|
53067
54134
|
const { content: streamContent, warning: streamWarning } = readStreamContent(resolveLogBaseDir(context2.logConfig), event.stream_file);
|
|
54135
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, {
|
|
54136
|
+
baseDir: resolveLogBaseDir(context2.logConfig),
|
|
54137
|
+
streamContent: streamContent ?? undefined
|
|
54138
|
+
}) ?? null;
|
|
53068
54139
|
const hasPluginData = event.plugins_request || event.plugins_response || event.request_body_after_plugins !== undefined || event.request_url_after_plugins !== undefined || event.response_body_before_plugins !== undefined || event.response_body_after_plugins !== undefined;
|
|
53069
54140
|
const pluginsSection = hasPluginData ? {
|
|
53070
54141
|
request: event.plugins_request,
|
|
@@ -53090,7 +54161,16 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53090
54161
|
hasError: level === "error",
|
|
53091
54162
|
model: event.model_out || event.model_in,
|
|
53092
54163
|
modelIn: event.model_in,
|
|
53093
|
-
modelOut: event.model_out
|
|
54164
|
+
modelOut: event.model_out,
|
|
54165
|
+
tokenUsage
|
|
54166
|
+
},
|
|
54167
|
+
usage: {
|
|
54168
|
+
tokenUsage,
|
|
54169
|
+
requestBytes: event.request_bytes ?? 0,
|
|
54170
|
+
responseBytes: event.response_bytes ?? null,
|
|
54171
|
+
streamBytes: event.stream_bytes ?? null,
|
|
54172
|
+
streamFileBytes: event.stream_file_bytes ?? null,
|
|
54173
|
+
streamFileTruncated: event.stream_file_truncated === true
|
|
53094
54174
|
},
|
|
53095
54175
|
request: {
|
|
53096
54176
|
method: event.method,
|
|
@@ -53131,17 +54211,11 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53131
54211
|
};
|
|
53132
54212
|
}
|
|
53133
54213
|
async function scanEvents(baseDir, query) {
|
|
53134
|
-
const eventsDir =
|
|
53135
|
-
if (!
|
|
54214
|
+
const eventsDir = join7(baseDir, "events");
|
|
54215
|
+
if (!existsSync6(eventsDir)) {
|
|
53136
54216
|
return {
|
|
53137
54217
|
items: [],
|
|
53138
|
-
stats:
|
|
53139
|
-
total: 0,
|
|
53140
|
-
errorCount: 0,
|
|
53141
|
-
errorRate: 0,
|
|
53142
|
-
avgLatencyMs: 0,
|
|
53143
|
-
p95LatencyMs: 0
|
|
53144
|
-
},
|
|
54218
|
+
stats: createEmptyLogQueryStats(),
|
|
53145
54219
|
meta: {
|
|
53146
54220
|
scannedFiles: 0,
|
|
53147
54221
|
scannedLines: 0,
|
|
@@ -53164,8 +54238,8 @@ async function scanEvents(baseDir, query) {
|
|
|
53164
54238
|
truncated = true;
|
|
53165
54239
|
break;
|
|
53166
54240
|
}
|
|
53167
|
-
const filePath =
|
|
53168
|
-
if (!
|
|
54241
|
+
const filePath = join7(eventsDir, `${date5}.jsonl`);
|
|
54242
|
+
if (!existsSync6(filePath))
|
|
53169
54243
|
continue;
|
|
53170
54244
|
scannedFiles += 1;
|
|
53171
54245
|
const stream = createReadStream3(filePath, { encoding: "utf-8" });
|
|
@@ -53227,6 +54301,7 @@ async function scanEvents(baseDir, query) {
|
|
|
53227
54301
|
continue;
|
|
53228
54302
|
if (!containsKeyword(event, query.q))
|
|
53229
54303
|
continue;
|
|
54304
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
|
|
53230
54305
|
const located = {
|
|
53231
54306
|
id: encodeEventId(date5, lineNumber),
|
|
53232
54307
|
date: date5,
|
|
@@ -53234,7 +54309,8 @@ async function scanEvents(baseDir, query) {
|
|
|
53234
54309
|
ts,
|
|
53235
54310
|
level,
|
|
53236
54311
|
statusClass,
|
|
53237
|
-
event
|
|
54312
|
+
event,
|
|
54313
|
+
tokenUsage
|
|
53238
54314
|
};
|
|
53239
54315
|
updateRunningStats(runningStats, located);
|
|
53240
54316
|
insertBoundedEvent(items, located, query.sort, maxKeep);
|
|
@@ -53254,6 +54330,9 @@ async function scanEvents(baseDir, query) {
|
|
|
53254
54330
|
function isLogQueryWindow(value) {
|
|
53255
54331
|
return value === "1h" || value === "6h" || value === "24h";
|
|
53256
54332
|
}
|
|
54333
|
+
function getLogQueryWindowMs(window2) {
|
|
54334
|
+
return WINDOW_MS2[window2];
|
|
54335
|
+
}
|
|
53257
54336
|
function resolveLogQueryRange(input) {
|
|
53258
54337
|
const nowMs = input.nowMs ?? Date.now();
|
|
53259
54338
|
const hasFrom = !!input.from;
|
|
@@ -53294,13 +54373,7 @@ async function queryLogEventsInternal(context2, input, maxLimit) {
|
|
|
53294
54373
|
items: [],
|
|
53295
54374
|
nextCursor: null,
|
|
53296
54375
|
hasMore: false,
|
|
53297
|
-
stats:
|
|
53298
|
-
total: 0,
|
|
53299
|
-
errorCount: 0,
|
|
53300
|
-
errorRate: 0,
|
|
53301
|
-
avgLatencyMs: 0,
|
|
53302
|
-
p95LatencyMs: 0
|
|
53303
|
-
},
|
|
54376
|
+
stats: createEmptyLogQueryStats(),
|
|
53304
54377
|
meta: {
|
|
53305
54378
|
scannedFiles: 0,
|
|
53306
54379
|
scannedLines: 0,
|
|
@@ -53351,8 +54424,8 @@ async function getLogEventDetailById(context2, id) {
|
|
|
53351
54424
|
}
|
|
53352
54425
|
const { date: date5, line: line2 } = decodeEventId(id);
|
|
53353
54426
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
53354
|
-
const filePath =
|
|
53355
|
-
if (!
|
|
54427
|
+
const filePath = join7(baseDir, "events", `${date5}.jsonl`);
|
|
54428
|
+
if (!existsSync6(filePath))
|
|
53356
54429
|
return null;
|
|
53357
54430
|
const stream = createReadStream3(filePath, { encoding: "utf-8" });
|
|
53358
54431
|
const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -53383,6 +54456,7 @@ function escapeCsvValue(value) {
|
|
|
53383
54456
|
return text2;
|
|
53384
54457
|
}
|
|
53385
54458
|
function toCsvRow(item) {
|
|
54459
|
+
const usage = item.tokenUsage;
|
|
53386
54460
|
return [
|
|
53387
54461
|
item.id,
|
|
53388
54462
|
item.ts,
|
|
@@ -53403,7 +54477,36 @@ function toCsvRow(item) {
|
|
|
53403
54477
|
item.userKey ?? "",
|
|
53404
54478
|
item.sessionId ?? "",
|
|
53405
54479
|
item.message,
|
|
53406
|
-
item.errorType ?? ""
|
|
54480
|
+
item.errorType ?? "",
|
|
54481
|
+
usage?.inputTokens ?? "",
|
|
54482
|
+
usage?.outputTokens ?? "",
|
|
54483
|
+
usage?.totalTokens ?? "",
|
|
54484
|
+
usage?.cachedInputTokens ?? "",
|
|
54485
|
+
usage?.cacheHitInputTokens ?? "",
|
|
54486
|
+
usage?.cacheHitRate ?? "",
|
|
54487
|
+
usage?.cacheHitRateDenominatorTokens ?? "",
|
|
54488
|
+
usage?.cacheReadInputTokens ?? "",
|
|
54489
|
+
usage?.cacheCreationInputTokens ?? "",
|
|
54490
|
+
usage?.cacheCreationInputTokens5m ?? "",
|
|
54491
|
+
usage?.cacheCreationInputTokens1h ?? "",
|
|
54492
|
+
usage?.cacheWriteInputTokens ?? "",
|
|
54493
|
+
usage?.cacheMissInputTokens ?? "",
|
|
54494
|
+
usage?.reasoningTokens ?? "",
|
|
54495
|
+
usage?.audioInputTokens ?? "",
|
|
54496
|
+
usage?.audioOutputTokens ?? "",
|
|
54497
|
+
usage?.textInputTokens ?? "",
|
|
54498
|
+
usage?.textOutputTokens ?? "",
|
|
54499
|
+
usage?.acceptedPredictionTokens ?? "",
|
|
54500
|
+
usage?.rejectedPredictionTokens ?? "",
|
|
54501
|
+
usage?.toolUsePromptTokens ?? "",
|
|
54502
|
+
usage?.billableInputTokens ?? "",
|
|
54503
|
+
usage?.billableOutputTokens ?? "",
|
|
54504
|
+
usage?.creditUsage ?? "",
|
|
54505
|
+
usage?.cost ?? "",
|
|
54506
|
+
usage?.providerStyle ?? "",
|
|
54507
|
+
usage?.source ?? "",
|
|
54508
|
+
usage?.rawUsagePath ?? "",
|
|
54509
|
+
usage?.cacheHitRateFormula ?? ""
|
|
53407
54510
|
].map(escapeCsvValue).join(",");
|
|
53408
54511
|
}
|
|
53409
54512
|
function createCsvExportStream(items) {
|
|
@@ -53428,7 +54531,36 @@ function createCsvExportStream(items) {
|
|
|
53428
54531
|
"userKey",
|
|
53429
54532
|
"sessionId",
|
|
53430
54533
|
"message",
|
|
53431
|
-
"errorType"
|
|
54534
|
+
"errorType",
|
|
54535
|
+
"usage.inputTokens",
|
|
54536
|
+
"usage.outputTokens",
|
|
54537
|
+
"usage.totalTokens",
|
|
54538
|
+
"usage.cachedInputTokens",
|
|
54539
|
+
"usage.cacheHitInputTokens",
|
|
54540
|
+
"usage.cacheHitRate",
|
|
54541
|
+
"usage.cacheHitRateDenominatorTokens",
|
|
54542
|
+
"usage.cacheReadInputTokens",
|
|
54543
|
+
"usage.cacheCreationInputTokens",
|
|
54544
|
+
"usage.cacheCreationInputTokens5m",
|
|
54545
|
+
"usage.cacheCreationInputTokens1h",
|
|
54546
|
+
"usage.cacheWriteInputTokens",
|
|
54547
|
+
"usage.cacheMissInputTokens",
|
|
54548
|
+
"usage.reasoningTokens",
|
|
54549
|
+
"usage.audioInputTokens",
|
|
54550
|
+
"usage.audioOutputTokens",
|
|
54551
|
+
"usage.textInputTokens",
|
|
54552
|
+
"usage.textOutputTokens",
|
|
54553
|
+
"usage.acceptedPredictionTokens",
|
|
54554
|
+
"usage.rejectedPredictionTokens",
|
|
54555
|
+
"usage.toolUsePromptTokens",
|
|
54556
|
+
"usage.billableInputTokens",
|
|
54557
|
+
"usage.billableOutputTokens",
|
|
54558
|
+
"usage.creditUsage",
|
|
54559
|
+
"usage.cost",
|
|
54560
|
+
"usage.providerStyle",
|
|
54561
|
+
"usage.source",
|
|
54562
|
+
"usage.rawUsagePath",
|
|
54563
|
+
"usage.cacheHitRateFormula"
|
|
53432
54564
|
];
|
|
53433
54565
|
return new ReadableStream({
|
|
53434
54566
|
start(controller) {
|
|
@@ -53506,12 +54638,666 @@ function parseBooleanFlag(value) {
|
|
|
53506
54638
|
throw new Error("hasError \u53C2\u6570\u4EC5\u652F\u6301 true/false/1/0");
|
|
53507
54639
|
}
|
|
53508
54640
|
|
|
54641
|
+
// src/log-tail.ts
|
|
54642
|
+
var subscribers = new Set;
|
|
54643
|
+
function publishLogEvent(event) {
|
|
54644
|
+
for (const subscriber of subscribers) {
|
|
54645
|
+
try {
|
|
54646
|
+
subscriber(event);
|
|
54647
|
+
} catch {}
|
|
54648
|
+
}
|
|
54649
|
+
}
|
|
54650
|
+
function subscribeLogEvents(subscriber) {
|
|
54651
|
+
subscribers.add(subscriber);
|
|
54652
|
+
return () => {
|
|
54653
|
+
subscribers.delete(subscriber);
|
|
54654
|
+
};
|
|
54655
|
+
}
|
|
54656
|
+
|
|
54657
|
+
// src/network-access.ts
|
|
54658
|
+
var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
|
|
54659
|
+
function parseIpv4(address) {
|
|
54660
|
+
const parts = address.split(".");
|
|
54661
|
+
if (parts.length !== 4)
|
|
54662
|
+
return null;
|
|
54663
|
+
const octets = parts.map((part) => {
|
|
54664
|
+
if (!/^\d{1,3}$/.test(part))
|
|
54665
|
+
return Number.NaN;
|
|
54666
|
+
const value = Number.parseInt(part, 10);
|
|
54667
|
+
return value >= 0 && value <= 255 ? value : Number.NaN;
|
|
54668
|
+
});
|
|
54669
|
+
return octets.every(Number.isFinite) ? octets : null;
|
|
54670
|
+
}
|
|
54671
|
+
function normalizeIpAddress(raw2) {
|
|
54672
|
+
let address = raw2.trim().toLowerCase();
|
|
54673
|
+
if (address.startsWith("[")) {
|
|
54674
|
+
const end = address.indexOf("]");
|
|
54675
|
+
if (end !== -1)
|
|
54676
|
+
address = address.slice(1, end);
|
|
54677
|
+
}
|
|
54678
|
+
const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
54679
|
+
if (mappedIpv4) {
|
|
54680
|
+
return mappedIpv4[1];
|
|
54681
|
+
}
|
|
54682
|
+
return address;
|
|
54683
|
+
}
|
|
54684
|
+
function isLoopbackAddress(raw2) {
|
|
54685
|
+
if (!raw2)
|
|
54686
|
+
return false;
|
|
54687
|
+
const address = normalizeIpAddress(raw2);
|
|
54688
|
+
const ipv43 = parseIpv4(address);
|
|
54689
|
+
if (ipv43)
|
|
54690
|
+
return ipv43[0] === 127;
|
|
54691
|
+
return address === "::1";
|
|
54692
|
+
}
|
|
54693
|
+
function isLanAddress(raw2) {
|
|
54694
|
+
if (!raw2)
|
|
54695
|
+
return false;
|
|
54696
|
+
const address = normalizeIpAddress(raw2);
|
|
54697
|
+
const ipv43 = parseIpv4(address);
|
|
54698
|
+
if (ipv43) {
|
|
54699
|
+
const [a, b] = ipv43;
|
|
54700
|
+
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
54701
|
+
}
|
|
54702
|
+
return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
|
|
54703
|
+
}
|
|
54704
|
+
function decideNetworkAccess(serverConfig, rawRemoteAddress) {
|
|
54705
|
+
const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
|
|
54706
|
+
if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
|
|
54707
|
+
return { allowed: true, remoteAddress };
|
|
54708
|
+
}
|
|
54709
|
+
const lanEnabled = serverConfig?.lanAccess?.enabled === true;
|
|
54710
|
+
if (!lanEnabled) {
|
|
54711
|
+
return { allowed: false, remoteAddress, reason: "lan-disabled" };
|
|
54712
|
+
}
|
|
54713
|
+
if (!isLanAddress(remoteAddress)) {
|
|
54714
|
+
return { allowed: false, remoteAddress, reason: "non-lan-address" };
|
|
54715
|
+
}
|
|
54716
|
+
return { allowed: true, remoteAddress };
|
|
54717
|
+
}
|
|
54718
|
+
function getRemoteAddressFromContext(c2) {
|
|
54719
|
+
const env = c2.env;
|
|
54720
|
+
const value = env?.[REMOTE_ADDRESS_ENV_KEY];
|
|
54721
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
54722
|
+
}
|
|
54723
|
+
function createNetworkAccessMiddleware(store) {
|
|
54724
|
+
return async (c2, next) => {
|
|
54725
|
+
const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
|
|
54726
|
+
if (!decision.allowed) {
|
|
54727
|
+
return c2.json({
|
|
54728
|
+
error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
|
|
54729
|
+
remoteAddress: decision.remoteAddress
|
|
54730
|
+
}, 403);
|
|
54731
|
+
}
|
|
54732
|
+
await next();
|
|
54733
|
+
};
|
|
54734
|
+
}
|
|
54735
|
+
|
|
54736
|
+
// src/log-realtime.ts
|
|
54737
|
+
var WS_PATHNAME = "/api/logs/events/ws";
|
|
54738
|
+
var MAX_CONNECTIONS = 64;
|
|
54739
|
+
var MAX_SUBSCRIPTIONS = 64;
|
|
54740
|
+
var MAX_CLIENT_MESSAGE_BYTES = 16 * 1024;
|
|
54741
|
+
var MAX_GLOBAL_EVENT_QUEUE_ITEMS = 1000;
|
|
54742
|
+
var MAX_GLOBAL_EVENT_QUEUE_BYTES = 16 * 1024 * 1024;
|
|
54743
|
+
var MAX_PENDING_BYTES_PER_CONNECTION = 1024 * 1024;
|
|
54744
|
+
var MAX_TOTAL_PENDING_BYTES = 32 * 1024 * 1024;
|
|
54745
|
+
var BACKPRESSURE_BUFFERED_BYTES = 512 * 1024;
|
|
54746
|
+
var HEARTBEAT_INTERVAL_MS = 15000;
|
|
54747
|
+
var FLUSH_BUDGET_MS = 10;
|
|
54748
|
+
var MAX_Q_LENGTH2 = 200;
|
|
54749
|
+
function jsonResponse(status, body) {
|
|
54750
|
+
return new Response(JSON.stringify(body), {
|
|
54751
|
+
status,
|
|
54752
|
+
headers: { "Content-Type": "application/json; charset=utf-8" }
|
|
54753
|
+
});
|
|
54754
|
+
}
|
|
54755
|
+
function createId(prefix) {
|
|
54756
|
+
return `${prefix}_${crypto.randomUUID()}`;
|
|
54757
|
+
}
|
|
54758
|
+
function estimatePublishedEventBytes(event) {
|
|
54759
|
+
const bodyBytes = (event.event.request_bytes ?? 0) + (event.event.response_bytes ?? 0);
|
|
54760
|
+
const streamBytes = event.event.stream_bytes ?? 0;
|
|
54761
|
+
return 1024 + Math.min(64 * 1024, Math.max(0, bodyBytes + streamBytes));
|
|
54762
|
+
}
|
|
54763
|
+
function byteLength(text2) {
|
|
54764
|
+
return Buffer.byteLength(text2);
|
|
54765
|
+
}
|
|
54766
|
+
function isSocketOpen(ws) {
|
|
54767
|
+
return typeof ws.readyState !== "number" || ws.readyState === 1;
|
|
54768
|
+
}
|
|
54769
|
+
function bufferedAmount(ws) {
|
|
54770
|
+
return typeof ws.bufferedAmount === "number" ? ws.bufferedAmount : 0;
|
|
54771
|
+
}
|
|
54772
|
+
function parseClientMessage(raw2) {
|
|
54773
|
+
let text2;
|
|
54774
|
+
if (typeof raw2 === "string") {
|
|
54775
|
+
text2 = raw2;
|
|
54776
|
+
} else {
|
|
54777
|
+
text2 = new TextDecoder().decode(raw2);
|
|
54778
|
+
}
|
|
54779
|
+
if (byteLength(text2) > MAX_CLIENT_MESSAGE_BYTES) {
|
|
54780
|
+
throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u8FC7\u5927");
|
|
54781
|
+
}
|
|
54782
|
+
const parsed = JSON.parse(text2);
|
|
54783
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
54784
|
+
throw new Error("\u5BA2\u6237\u7AEF\u6D88\u606F\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
|
|
54785
|
+
}
|
|
54786
|
+
const message = parsed;
|
|
54787
|
+
if (message.type !== "subscribe" && message.type !== "unsubscribe" && message.type !== "ping") {
|
|
54788
|
+
throw new Error("\u4E0D\u652F\u6301\u7684\u5B9E\u65F6\u65E5\u5FD7\u6D88\u606F\u7C7B\u578B");
|
|
54789
|
+
}
|
|
54790
|
+
return message;
|
|
54791
|
+
}
|
|
54792
|
+
function parseStringValue(value) {
|
|
54793
|
+
if (typeof value !== "string")
|
|
54794
|
+
return;
|
|
54795
|
+
const trimmed = value.trim();
|
|
54796
|
+
return trimmed || undefined;
|
|
54797
|
+
}
|
|
54798
|
+
function parseStringArrayValue(value) {
|
|
54799
|
+
if (Array.isArray(value)) {
|
|
54800
|
+
return value.flatMap((item) => typeof item === "string" ? parseCommaSeparated(item) : []);
|
|
54801
|
+
}
|
|
54802
|
+
if (typeof value === "string")
|
|
54803
|
+
return parseCommaSeparated(value);
|
|
54804
|
+
return [];
|
|
54805
|
+
}
|
|
54806
|
+
function parseBooleanValue(value) {
|
|
54807
|
+
if (typeof value === "boolean")
|
|
54808
|
+
return value;
|
|
54809
|
+
if (typeof value === "string")
|
|
54810
|
+
return parseBooleanFlag(value);
|
|
54811
|
+
return null;
|
|
54812
|
+
}
|
|
54813
|
+
function createQueryHash(value) {
|
|
54814
|
+
const text2 = JSON.stringify(value);
|
|
54815
|
+
let hash2 = 2166136261;
|
|
54816
|
+
for (let i = 0;i < text2.length; i += 1) {
|
|
54817
|
+
hash2 ^= text2.charCodeAt(i);
|
|
54818
|
+
hash2 = Math.imul(hash2, 16777619);
|
|
54819
|
+
}
|
|
54820
|
+
return `q_${(hash2 >>> 0).toString(36)}`;
|
|
54821
|
+
}
|
|
54822
|
+
function parseValidatedArray(value, validate, errorMessage) {
|
|
54823
|
+
const raw2 = parseStringArrayValue(value);
|
|
54824
|
+
const parsed = raw2.filter(validate);
|
|
54825
|
+
if (parsed.length !== raw2.length) {
|
|
54826
|
+
throw new Error(errorMessage);
|
|
54827
|
+
}
|
|
54828
|
+
return parsed;
|
|
54829
|
+
}
|
|
54830
|
+
function compileRealtimeQuery(input) {
|
|
54831
|
+
const query = input ?? {};
|
|
54832
|
+
const nowMs = Date.now();
|
|
54833
|
+
const windowRaw = parseStringValue(query.window) ?? "24h";
|
|
54834
|
+
if (!isLogQueryWindow(windowRaw)) {
|
|
54835
|
+
throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
|
|
54836
|
+
}
|
|
54837
|
+
const from = parseStringValue(query.from);
|
|
54838
|
+
const to = parseStringValue(query.to);
|
|
54839
|
+
const range = from || to ? {
|
|
54840
|
+
type: "fixed",
|
|
54841
|
+
...resolveLogQueryRange({ window: windowRaw, from, to, nowMs })
|
|
54842
|
+
} : {
|
|
54843
|
+
type: "window",
|
|
54844
|
+
window: windowRaw,
|
|
54845
|
+
windowMs: getLogQueryWindowMs(windowRaw)
|
|
54846
|
+
};
|
|
54847
|
+
if (range.type === "fixed" && range.toMs <= nowMs) {
|
|
54848
|
+
throw new Error("\u56FA\u5B9A\u7ED3\u675F\u65F6\u95F4\u5DF2\u8FC7\u671F\uFF0C\u5B9E\u65F6\u63A8\u9001\u4EC5\u652F\u6301\u4ECD\u5728\u6EDA\u52A8\u7684\u67E5\u8BE2\u7A97\u53E3");
|
|
54849
|
+
}
|
|
54850
|
+
const sortRaw = parseStringValue(query.sort) ?? "time_desc";
|
|
54851
|
+
if (!validateSort(sortRaw)) {
|
|
54852
|
+
throw new Error("sort \u53C2\u6570\u4EC5\u652F\u6301 time_desc | time_asc");
|
|
54853
|
+
}
|
|
54854
|
+
const levels = parseValidatedArray(query.levels, validateLogLevel, "levels \u53C2\u6570\u4EC5\u652F\u6301 info,error");
|
|
54855
|
+
const statusClasses = parseValidatedArray(query.statusClass, validateStatusClass, "statusClass \u53C2\u6570\u4EC5\u652F\u6301 2xx,4xx,5xx,network_error");
|
|
54856
|
+
const q = (parseStringValue(query.q) ?? "").slice(0, MAX_Q_LENGTH2).toLowerCase();
|
|
54857
|
+
const normalized = {
|
|
54858
|
+
range,
|
|
54859
|
+
sort: sortRaw,
|
|
54860
|
+
levels,
|
|
54861
|
+
providers: parseStringArrayValue(query.provider),
|
|
54862
|
+
routeTypes: parseStringArrayValue(query.routeType),
|
|
54863
|
+
models: parseStringArrayValue(query.model),
|
|
54864
|
+
modelIns: parseStringArrayValue(query.modelIn),
|
|
54865
|
+
modelOuts: parseStringArrayValue(query.modelOut),
|
|
54866
|
+
users: parseStringArrayValue(query.user),
|
|
54867
|
+
sessions: parseStringArrayValue(query.session),
|
|
54868
|
+
statusClasses,
|
|
54869
|
+
hasError: parseBooleanValue(query.hasError),
|
|
54870
|
+
q
|
|
54871
|
+
};
|
|
54872
|
+
return {
|
|
54873
|
+
queryHash: createQueryHash(normalized),
|
|
54874
|
+
sort: sortRaw,
|
|
54875
|
+
range,
|
|
54876
|
+
levels: new Set(levels),
|
|
54877
|
+
providers: new Set(normalized.providers),
|
|
54878
|
+
routeTypes: new Set(normalized.routeTypes),
|
|
54879
|
+
models: new Set(normalized.models),
|
|
54880
|
+
modelIns: new Set(normalized.modelIns),
|
|
54881
|
+
modelOuts: new Set(normalized.modelOuts),
|
|
54882
|
+
users: new Set(normalized.users),
|
|
54883
|
+
sessions: new Set(normalized.sessions),
|
|
54884
|
+
statusClasses: new Set(statusClasses),
|
|
54885
|
+
hasError: normalized.hasError,
|
|
54886
|
+
q
|
|
54887
|
+
};
|
|
54888
|
+
}
|
|
54889
|
+
function setHas(set2, value) {
|
|
54890
|
+
return set2.size === 0 || set2.has(value);
|
|
54891
|
+
}
|
|
54892
|
+
function matchesCompiledQuery(query, facts, nowMs) {
|
|
54893
|
+
if (!facts.event.ts_start || !Number.isFinite(facts.ts))
|
|
54894
|
+
return false;
|
|
54895
|
+
const fromMs = query.range.type === "window" ? nowMs - query.range.windowMs : query.range.fromMs;
|
|
54896
|
+
const toMs = query.range.type === "window" ? nowMs : query.range.toMs;
|
|
54897
|
+
if (facts.ts < fromMs || facts.ts > toMs)
|
|
54898
|
+
return false;
|
|
54899
|
+
if (!setHas(query.levels, facts.level))
|
|
54900
|
+
return false;
|
|
54901
|
+
if (!setHas(query.providers, facts.event.provider))
|
|
54902
|
+
return false;
|
|
54903
|
+
if (!setHas(query.routeTypes, facts.event.route_type))
|
|
54904
|
+
return false;
|
|
54905
|
+
if (!setHas(query.models, facts.model))
|
|
54906
|
+
return false;
|
|
54907
|
+
if (!setHas(query.modelIns, facts.event.model_in))
|
|
54908
|
+
return false;
|
|
54909
|
+
if (!setHas(query.modelOuts, facts.event.model_out))
|
|
54910
|
+
return false;
|
|
54911
|
+
if (query.users.size > 0) {
|
|
54912
|
+
const matchedByRaw = facts.identity.userIdRaw ? query.users.has(facts.identity.userIdRaw) : false;
|
|
54913
|
+
const matchedByUserKey = facts.identity.userKey ? query.users.has(facts.identity.userKey) : false;
|
|
54914
|
+
if (!matchedByRaw && !matchedByUserKey)
|
|
54915
|
+
return false;
|
|
54916
|
+
}
|
|
54917
|
+
if (query.sessions.size > 0) {
|
|
54918
|
+
if (!facts.identity.sessionId || !query.sessions.has(facts.identity.sessionId))
|
|
54919
|
+
return false;
|
|
54920
|
+
}
|
|
54921
|
+
if (!setHas(query.statusClasses, facts.statusClass))
|
|
54922
|
+
return false;
|
|
54923
|
+
if (query.hasError !== null && query.hasError !== facts.hasError)
|
|
54924
|
+
return false;
|
|
54925
|
+
return !query.q || facts.keywordText.includes(query.q);
|
|
54926
|
+
}
|
|
54927
|
+
function createLogRealtimeRuntime(options) {
|
|
54928
|
+
const connections = new Map;
|
|
54929
|
+
const subscriptions = new Map;
|
|
54930
|
+
const eventQueue = [];
|
|
54931
|
+
let tailUnsubscribe = null;
|
|
54932
|
+
let flushTimer = null;
|
|
54933
|
+
let heartbeatTimer = null;
|
|
54934
|
+
let eventQueueBytes = 0;
|
|
54935
|
+
let totalPendingBytes = 0;
|
|
54936
|
+
let droppedUpstreamEvents = 0;
|
|
54937
|
+
const sendText = (connection, text2) => {
|
|
54938
|
+
if (!isSocketOpen(connection.ws))
|
|
54939
|
+
return false;
|
|
54940
|
+
const bytes = byteLength(text2);
|
|
54941
|
+
const shouldQueue = connection.queue.length > 0 || bufferedAmount(connection.ws) > BACKPRESSURE_BUFFERED_BYTES;
|
|
54942
|
+
if (!shouldQueue) {
|
|
54943
|
+
try {
|
|
54944
|
+
connection.ws.send(text2);
|
|
54945
|
+
return true;
|
|
54946
|
+
} catch {
|
|
54947
|
+
removeConnection(connection.id, "send-failed");
|
|
54948
|
+
return false;
|
|
54949
|
+
}
|
|
54950
|
+
}
|
|
54951
|
+
if (connection.pendingBytes + bytes > MAX_PENDING_BYTES_PER_CONNECTION || totalPendingBytes + bytes > MAX_TOTAL_PENDING_BYTES) {
|
|
54952
|
+
connection.droppedOutbound += 1;
|
|
54953
|
+
if (connection.droppedOutbound >= 3) {
|
|
54954
|
+
removeConnection(connection.id, "slow-client");
|
|
54955
|
+
}
|
|
54956
|
+
return false;
|
|
54957
|
+
}
|
|
54958
|
+
connection.queue.push({ text: text2, bytes });
|
|
54959
|
+
connection.pendingBytes += bytes;
|
|
54960
|
+
totalPendingBytes += bytes;
|
|
54961
|
+
return true;
|
|
54962
|
+
};
|
|
54963
|
+
const sendMessage = (connection, message) => {
|
|
54964
|
+
return sendText(connection, JSON.stringify(message));
|
|
54965
|
+
};
|
|
54966
|
+
const flushConnectionQueue = (connection) => {
|
|
54967
|
+
if (!isSocketOpen(connection.ws))
|
|
54968
|
+
return;
|
|
54969
|
+
while (connection.queue.length > 0 && bufferedAmount(connection.ws) <= BACKPRESSURE_BUFFERED_BYTES) {
|
|
54970
|
+
const item = connection.queue.shift();
|
|
54971
|
+
if (!item)
|
|
54972
|
+
break;
|
|
54973
|
+
connection.pendingBytes -= item.bytes;
|
|
54974
|
+
totalPendingBytes -= item.bytes;
|
|
54975
|
+
try {
|
|
54976
|
+
connection.ws.send(item.text);
|
|
54977
|
+
} catch {
|
|
54978
|
+
removeConnection(connection.id, "send-failed");
|
|
54979
|
+
return;
|
|
54980
|
+
}
|
|
54981
|
+
}
|
|
54982
|
+
};
|
|
54983
|
+
const sendError = (connection, error48, requestId) => {
|
|
54984
|
+
sendMessage(connection, { type: "error", requestId, error: error48 });
|
|
54985
|
+
};
|
|
54986
|
+
const ensureTailSubscription = () => {
|
|
54987
|
+
if (tailUnsubscribe || subscriptions.size === 0)
|
|
54988
|
+
return;
|
|
54989
|
+
tailUnsubscribe = subscribeLogEvents((event) => {
|
|
54990
|
+
if (subscriptions.size === 0)
|
|
54991
|
+
return;
|
|
54992
|
+
const bytes = estimatePublishedEventBytes(event);
|
|
54993
|
+
while (eventQueue.length >= MAX_GLOBAL_EVENT_QUEUE_ITEMS || eventQueueBytes + bytes > MAX_GLOBAL_EVENT_QUEUE_BYTES) {
|
|
54994
|
+
const dropped = eventQueue.shift();
|
|
54995
|
+
if (!dropped)
|
|
54996
|
+
break;
|
|
54997
|
+
eventQueueBytes -= dropped.bytes;
|
|
54998
|
+
droppedUpstreamEvents += 1;
|
|
54999
|
+
}
|
|
55000
|
+
eventQueue.push({ event, bytes });
|
|
55001
|
+
eventQueueBytes += bytes;
|
|
55002
|
+
scheduleFlush();
|
|
55003
|
+
});
|
|
55004
|
+
};
|
|
55005
|
+
const maybeStopTailSubscription = () => {
|
|
55006
|
+
if (subscriptions.size > 0)
|
|
55007
|
+
return;
|
|
55008
|
+
tailUnsubscribe?.();
|
|
55009
|
+
tailUnsubscribe = null;
|
|
55010
|
+
eventQueue.length = 0;
|
|
55011
|
+
eventQueueBytes = 0;
|
|
55012
|
+
droppedUpstreamEvents = 0;
|
|
55013
|
+
};
|
|
55014
|
+
const sendOverflow = (subscription, dropped, message) => {
|
|
55015
|
+
const connection = connections.get(subscription.connectionId);
|
|
55016
|
+
if (!connection)
|
|
55017
|
+
return;
|
|
55018
|
+
sendMessage(connection, {
|
|
55019
|
+
type: "overflow",
|
|
55020
|
+
subscriptionId: subscription.id,
|
|
55021
|
+
dropped,
|
|
55022
|
+
message
|
|
55023
|
+
});
|
|
55024
|
+
};
|
|
55025
|
+
const removeSubscription = (subscriptionId, reason, notify2 = true) => {
|
|
55026
|
+
const subscription = subscriptions.get(subscriptionId);
|
|
55027
|
+
if (!subscription)
|
|
55028
|
+
return;
|
|
55029
|
+
subscriptions.delete(subscriptionId);
|
|
55030
|
+
const connection = connections.get(subscription.connectionId);
|
|
55031
|
+
if (connection) {
|
|
55032
|
+
connection.subscriptions.delete(subscriptionId);
|
|
55033
|
+
if (notify2) {
|
|
55034
|
+
sendMessage(connection, {
|
|
55035
|
+
type: "unsubscribed",
|
|
55036
|
+
subscriptionId,
|
|
55037
|
+
reason
|
|
55038
|
+
});
|
|
55039
|
+
}
|
|
55040
|
+
}
|
|
55041
|
+
maybeStopTailSubscription();
|
|
55042
|
+
};
|
|
55043
|
+
function removeConnection(connectionId, reason) {
|
|
55044
|
+
const connection = connections.get(connectionId);
|
|
55045
|
+
if (!connection)
|
|
55046
|
+
return;
|
|
55047
|
+
connections.delete(connectionId);
|
|
55048
|
+
for (const subscriptionId of Array.from(connection.subscriptions)) {
|
|
55049
|
+
removeSubscription(subscriptionId, reason, false);
|
|
55050
|
+
}
|
|
55051
|
+
for (const item of connection.queue) {
|
|
55052
|
+
totalPendingBytes -= item.bytes;
|
|
55053
|
+
}
|
|
55054
|
+
connection.queue.length = 0;
|
|
55055
|
+
connection.pendingBytes = 0;
|
|
55056
|
+
try {
|
|
55057
|
+
connection.ws.close(1001, reason);
|
|
55058
|
+
} catch {}
|
|
55059
|
+
if (connections.size === 0 && heartbeatTimer) {
|
|
55060
|
+
clearInterval(heartbeatTimer);
|
|
55061
|
+
heartbeatTimer = null;
|
|
55062
|
+
}
|
|
55063
|
+
maybeStopTailSubscription();
|
|
55064
|
+
}
|
|
55065
|
+
const pruneExpiredSubscriptions = (nowMs) => {
|
|
55066
|
+
for (const subscription of Array.from(subscriptions.values())) {
|
|
55067
|
+
const range = subscription.query.range;
|
|
55068
|
+
if (range.type === "fixed" && range.toMs <= nowMs) {
|
|
55069
|
+
removeSubscription(subscription.id, "expired");
|
|
55070
|
+
}
|
|
55071
|
+
}
|
|
55072
|
+
};
|
|
55073
|
+
const flushEvents = () => {
|
|
55074
|
+
flushTimer = null;
|
|
55075
|
+
if (subscriptions.size === 0) {
|
|
55076
|
+
eventQueue.length = 0;
|
|
55077
|
+
eventQueueBytes = 0;
|
|
55078
|
+
droppedUpstreamEvents = 0;
|
|
55079
|
+
maybeStopTailSubscription();
|
|
55080
|
+
return;
|
|
55081
|
+
}
|
|
55082
|
+
const startedAt = Date.now();
|
|
55083
|
+
const nowMs = Date.now();
|
|
55084
|
+
pruneExpiredSubscriptions(nowMs);
|
|
55085
|
+
if (droppedUpstreamEvents > 0) {
|
|
55086
|
+
const dropped = droppedUpstreamEvents;
|
|
55087
|
+
droppedUpstreamEvents = 0;
|
|
55088
|
+
for (const subscription of subscriptions.values()) {
|
|
55089
|
+
sendOverflow(subscription, dropped, `\u5B9E\u65F6\u65E5\u5FD7\u961F\u5217\u5DF2\u4E22\u5F03 ${dropped} \u6761\u4E8B\u4EF6\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u4EE5\u8865\u9F50\u3002`);
|
|
55090
|
+
}
|
|
55091
|
+
}
|
|
55092
|
+
while (eventQueue.length > 0 && Date.now() - startedAt < FLUSH_BUDGET_MS) {
|
|
55093
|
+
const queued = eventQueue.shift();
|
|
55094
|
+
if (!queued)
|
|
55095
|
+
break;
|
|
55096
|
+
eventQueueBytes -= queued.bytes;
|
|
55097
|
+
const facts = extractLogEventFacts(queued.event.event);
|
|
55098
|
+
let summary = null;
|
|
55099
|
+
for (const subscription of subscriptions.values()) {
|
|
55100
|
+
if (!matchesCompiledQuery(subscription.query, facts, Date.now()))
|
|
55101
|
+
continue;
|
|
55102
|
+
const connection = connections.get(subscription.connectionId);
|
|
55103
|
+
if (!connection)
|
|
55104
|
+
continue;
|
|
55105
|
+
summary ??= createLogEventSummaryFromFacts(facts, {
|
|
55106
|
+
id: queued.event.id,
|
|
55107
|
+
date: queued.event.date,
|
|
55108
|
+
line: null
|
|
55109
|
+
});
|
|
55110
|
+
const sent = sendMessage(connection, {
|
|
55111
|
+
type: "log.event",
|
|
55112
|
+
subscriptionId: subscription.id,
|
|
55113
|
+
item: summary
|
|
55114
|
+
});
|
|
55115
|
+
if (!sent) {
|
|
55116
|
+
sendOverflow(subscription, 1, "\u5B9E\u65F6\u65E5\u5FD7\u5BA2\u6237\u7AEF\u53D1\u9001\u961F\u5217\u5DF2\u6EE1\uFF0C\u8BF7\u91CD\u65B0\u67E5\u8BE2\u4EE5\u8865\u9F50\u3002");
|
|
55117
|
+
}
|
|
55118
|
+
}
|
|
55119
|
+
}
|
|
55120
|
+
for (const connection of connections.values()) {
|
|
55121
|
+
flushConnectionQueue(connection);
|
|
55122
|
+
}
|
|
55123
|
+
if (eventQueue.length > 0)
|
|
55124
|
+
scheduleFlush();
|
|
55125
|
+
};
|
|
55126
|
+
function scheduleFlush() {
|
|
55127
|
+
if (flushTimer)
|
|
55128
|
+
return;
|
|
55129
|
+
flushTimer = setTimeout(flushEvents, 0);
|
|
55130
|
+
flushTimer.unref?.();
|
|
55131
|
+
}
|
|
55132
|
+
const ensureHeartbeat = () => {
|
|
55133
|
+
if (heartbeatTimer)
|
|
55134
|
+
return;
|
|
55135
|
+
heartbeatTimer = setInterval(() => {
|
|
55136
|
+
const ts = new Date().toISOString();
|
|
55137
|
+
pruneExpiredSubscriptions(Date.now());
|
|
55138
|
+
for (const connection of connections.values()) {
|
|
55139
|
+
sendMessage(connection, { type: "pong", ts });
|
|
55140
|
+
flushConnectionQueue(connection);
|
|
55141
|
+
}
|
|
55142
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
55143
|
+
heartbeatTimer.unref?.();
|
|
55144
|
+
};
|
|
55145
|
+
const subscribeConnection = (connection, requestId, query) => {
|
|
55146
|
+
if (subscriptions.size >= MAX_SUBSCRIPTIONS && connection.subscriptions.size === 0) {
|
|
55147
|
+
sendError(connection, "\u5B9E\u65F6\u65E5\u5FD7\u8BA2\u9605\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", requestId);
|
|
55148
|
+
return;
|
|
55149
|
+
}
|
|
55150
|
+
let compiled;
|
|
55151
|
+
try {
|
|
55152
|
+
compiled = compileRealtimeQuery(query);
|
|
55153
|
+
} catch (err) {
|
|
55154
|
+
sendError(connection, err instanceof Error ? err.message : String(err), requestId);
|
|
55155
|
+
return;
|
|
55156
|
+
}
|
|
55157
|
+
for (const subscriptionId2 of Array.from(connection.subscriptions)) {
|
|
55158
|
+
removeSubscription(subscriptionId2, "replaced");
|
|
55159
|
+
}
|
|
55160
|
+
const subscriptionId = createId("sub");
|
|
55161
|
+
const subscription = {
|
|
55162
|
+
id: subscriptionId,
|
|
55163
|
+
connectionId: connection.id,
|
|
55164
|
+
query: compiled
|
|
55165
|
+
};
|
|
55166
|
+
subscriptions.set(subscriptionId, subscription);
|
|
55167
|
+
connection.subscriptions.add(subscriptionId);
|
|
55168
|
+
ensureTailSubscription();
|
|
55169
|
+
sendMessage(connection, {
|
|
55170
|
+
type: "subscribed",
|
|
55171
|
+
requestId: requestId ?? null,
|
|
55172
|
+
subscriptionId,
|
|
55173
|
+
queryHash: compiled.queryHash,
|
|
55174
|
+
now: new Date().toISOString()
|
|
55175
|
+
});
|
|
55176
|
+
};
|
|
55177
|
+
const openConnection = (ws) => {
|
|
55178
|
+
const data = ws.data;
|
|
55179
|
+
const connection = {
|
|
55180
|
+
id: data.connectionId,
|
|
55181
|
+
remoteAddress: data.remoteAddress,
|
|
55182
|
+
ws,
|
|
55183
|
+
subscriptions: new Set,
|
|
55184
|
+
queue: [],
|
|
55185
|
+
pendingBytes: 0,
|
|
55186
|
+
droppedOutbound: 0
|
|
55187
|
+
};
|
|
55188
|
+
connections.set(connection.id, connection);
|
|
55189
|
+
ensureHeartbeat();
|
|
55190
|
+
sendMessage(connection, {
|
|
55191
|
+
type: "ready",
|
|
55192
|
+
connectionId: connection.id,
|
|
55193
|
+
now: new Date().toISOString()
|
|
55194
|
+
});
|
|
55195
|
+
};
|
|
55196
|
+
const handleMessage = (ws, raw2) => {
|
|
55197
|
+
const connection = connections.get(ws.data.connectionId);
|
|
55198
|
+
if (!connection)
|
|
55199
|
+
return;
|
|
55200
|
+
let message;
|
|
55201
|
+
try {
|
|
55202
|
+
message = parseClientMessage(raw2);
|
|
55203
|
+
} catch (err) {
|
|
55204
|
+
sendError(connection, err instanceof Error ? err.message : String(err));
|
|
55205
|
+
return;
|
|
55206
|
+
}
|
|
55207
|
+
if (message.type === "ping") {
|
|
55208
|
+
sendMessage(connection, { type: "pong", ts: message.ts ?? new Date().toISOString() });
|
|
55209
|
+
return;
|
|
55210
|
+
}
|
|
55211
|
+
if (message.type === "unsubscribe") {
|
|
55212
|
+
if (message.subscriptionId) {
|
|
55213
|
+
removeSubscription(message.subscriptionId, "client-unsubscribe");
|
|
55214
|
+
} else {
|
|
55215
|
+
for (const subscriptionId of Array.from(connection.subscriptions)) {
|
|
55216
|
+
removeSubscription(subscriptionId, "client-unsubscribe");
|
|
55217
|
+
}
|
|
55218
|
+
}
|
|
55219
|
+
return;
|
|
55220
|
+
}
|
|
55221
|
+
subscribeConnection(connection, message.requestId, message.query);
|
|
55222
|
+
};
|
|
55223
|
+
return {
|
|
55224
|
+
pathname: WS_PATHNAME,
|
|
55225
|
+
upgrade: (request, server, remoteAddress) => {
|
|
55226
|
+
const url2 = new URL(request.url);
|
|
55227
|
+
if (url2.pathname !== WS_PATHNAME)
|
|
55228
|
+
return { handled: false };
|
|
55229
|
+
const upgradeHeader = request.headers.get("upgrade")?.toLowerCase();
|
|
55230
|
+
if (upgradeHeader !== "websocket") {
|
|
55231
|
+
return {
|
|
55232
|
+
handled: true,
|
|
55233
|
+
response: jsonResponse(400, { error: "\u9700\u8981 WebSocket Upgrade" })
|
|
55234
|
+
};
|
|
55235
|
+
}
|
|
55236
|
+
const decision = decideNetworkAccess(options.store.get().server, remoteAddress);
|
|
55237
|
+
if (!decision.allowed) {
|
|
55238
|
+
return {
|
|
55239
|
+
handled: true,
|
|
55240
|
+
response: jsonResponse(403, {
|
|
55241
|
+
error: decision.reason === "lan-disabled" ? "\u5C40\u57DF\u7F51\u670D\u52A1\u672A\u5F00\u542F\uFF0C\u5DF2\u62D2\u7EDD\u975E\u672C\u673A\u8BF7\u6C42" : "\u4EC5\u5141\u8BB8\u672C\u673A\u6216\u5C40\u57DF\u7F51\u6765\u6E90\u8BBF\u95EE",
|
|
55242
|
+
remoteAddress: decision.remoteAddress
|
|
55243
|
+
})
|
|
55244
|
+
};
|
|
55245
|
+
}
|
|
55246
|
+
if (connections.size >= MAX_CONNECTIONS) {
|
|
55247
|
+
return {
|
|
55248
|
+
handled: true,
|
|
55249
|
+
response: jsonResponse(503, { error: "\u5B9E\u65F6\u65E5\u5FD7\u8FDE\u63A5\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" })
|
|
55250
|
+
};
|
|
55251
|
+
}
|
|
55252
|
+
const upgraded = server.upgrade(request, {
|
|
55253
|
+
data: {
|
|
55254
|
+
kind: "log-realtime",
|
|
55255
|
+
connectionId: createId("conn"),
|
|
55256
|
+
remoteAddress: decision.remoteAddress
|
|
55257
|
+
}
|
|
55258
|
+
});
|
|
55259
|
+
return upgraded ? { handled: true, upgraded: true } : { handled: true, response: jsonResponse(400, { error: "WebSocket Upgrade \u5931\u8D25" }) };
|
|
55260
|
+
},
|
|
55261
|
+
websocket: {
|
|
55262
|
+
open: openConnection,
|
|
55263
|
+
message: handleMessage,
|
|
55264
|
+
drain: (ws) => {
|
|
55265
|
+
const connection = connections.get(ws.data.connectionId);
|
|
55266
|
+
if (connection)
|
|
55267
|
+
flushConnectionQueue(connection);
|
|
55268
|
+
},
|
|
55269
|
+
close: (ws) => {
|
|
55270
|
+
removeConnection(ws.data.connectionId, "closed");
|
|
55271
|
+
}
|
|
55272
|
+
},
|
|
55273
|
+
dispose: () => {
|
|
55274
|
+
if (flushTimer) {
|
|
55275
|
+
clearTimeout(flushTimer);
|
|
55276
|
+
flushTimer = null;
|
|
55277
|
+
}
|
|
55278
|
+
if (heartbeatTimer) {
|
|
55279
|
+
clearInterval(heartbeatTimer);
|
|
55280
|
+
heartbeatTimer = null;
|
|
55281
|
+
}
|
|
55282
|
+
tailUnsubscribe?.();
|
|
55283
|
+
tailUnsubscribe = null;
|
|
55284
|
+
for (const connectionId of Array.from(connections.keys())) {
|
|
55285
|
+
removeConnection(connectionId, "server-dispose");
|
|
55286
|
+
}
|
|
55287
|
+
eventQueue.length = 0;
|
|
55288
|
+
eventQueueBytes = 0;
|
|
55289
|
+
totalPendingBytes = 0;
|
|
55290
|
+
droppedUpstreamEvents = 0;
|
|
55291
|
+
}
|
|
55292
|
+
};
|
|
55293
|
+
}
|
|
55294
|
+
|
|
53509
55295
|
// src/log-sessions.ts
|
|
53510
|
-
import { createReadStream as createReadStream4, existsSync as
|
|
53511
|
-
import { join as
|
|
55296
|
+
import { createReadStream as createReadStream4, existsSync as existsSync7 } from "fs";
|
|
55297
|
+
import { join as join8 } from "path";
|
|
53512
55298
|
import { createInterface as createInterface3 } from "readline";
|
|
53513
55299
|
var MAX_LINES_SCANNED3 = 250000;
|
|
53514
|
-
var
|
|
55300
|
+
var MAX_Q_LENGTH3 = 200;
|
|
53515
55301
|
function toDayStart4(ms) {
|
|
53516
55302
|
const date5 = new Date(ms);
|
|
53517
55303
|
return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
|
|
@@ -53530,7 +55316,7 @@ function normalizeInput(input) {
|
|
|
53530
55316
|
toMs: input.toMs,
|
|
53531
55317
|
users: (input.users ?? []).map((item) => item.trim()).filter(Boolean),
|
|
53532
55318
|
sessions: (input.sessions ?? []).map((item) => item.trim()).filter(Boolean),
|
|
53533
|
-
q: qRaw.length >
|
|
55319
|
+
q: qRaw.length > MAX_Q_LENGTH3 ? qRaw.slice(0, MAX_Q_LENGTH3) : qRaw
|
|
53534
55320
|
};
|
|
53535
55321
|
}
|
|
53536
55322
|
function incrementCount(map2, key2) {
|
|
@@ -53629,8 +55415,8 @@ async function queryLogSessions(context2, input) {
|
|
|
53629
55415
|
return createEmptyResult(normalized.fromMs, normalized.toMs);
|
|
53630
55416
|
}
|
|
53631
55417
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
53632
|
-
const eventsDir =
|
|
53633
|
-
if (!
|
|
55418
|
+
const eventsDir = join8(baseDir, "events");
|
|
55419
|
+
if (!existsSync7(eventsDir)) {
|
|
53634
55420
|
return createEmptyResult(normalized.fromMs, normalized.toMs);
|
|
53635
55421
|
}
|
|
53636
55422
|
const usersMap = new Map;
|
|
@@ -53648,8 +55434,8 @@ async function queryLogSessions(context2, input) {
|
|
|
53648
55434
|
truncated = true;
|
|
53649
55435
|
break;
|
|
53650
55436
|
}
|
|
53651
|
-
const filePath =
|
|
53652
|
-
if (!
|
|
55437
|
+
const filePath = join8(eventsDir, `${date5}.jsonl`);
|
|
55438
|
+
if (!existsSync7(filePath))
|
|
53653
55439
|
continue;
|
|
53654
55440
|
scannedFiles += 1;
|
|
53655
55441
|
const stream = createReadStream4(filePath, { encoding: "utf-8" });
|
|
@@ -53757,8 +55543,8 @@ async function queryLogSessions(context2, input) {
|
|
|
53757
55543
|
}
|
|
53758
55544
|
|
|
53759
55545
|
// src/log-storage.ts
|
|
53760
|
-
import { existsSync as
|
|
53761
|
-
import { join as
|
|
55546
|
+
import { existsSync as existsSync8, promises as fsPromises } from "fs";
|
|
55547
|
+
import { join as join9 } from "path";
|
|
53762
55548
|
var cachedStorage = null;
|
|
53763
55549
|
var calculationPromise = null;
|
|
53764
55550
|
var lastCalculationTime = 0;
|
|
@@ -53766,7 +55552,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
|
|
|
53766
55552
|
var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
|
|
53767
55553
|
var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
|
|
53768
55554
|
async function calculateDirSize(dirPath) {
|
|
53769
|
-
if (!
|
|
55555
|
+
if (!existsSync8(dirPath)) {
|
|
53770
55556
|
return { bytes: 0, fileCount: 0 };
|
|
53771
55557
|
}
|
|
53772
55558
|
let bytes = 0;
|
|
@@ -53775,7 +55561,7 @@ async function calculateDirSize(dirPath) {
|
|
|
53775
55561
|
try {
|
|
53776
55562
|
const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
|
|
53777
55563
|
for (const entry of entries) {
|
|
53778
|
-
const fullPath =
|
|
55564
|
+
const fullPath = join9(currentPath, entry.name);
|
|
53779
55565
|
if (entry.isDirectory()) {
|
|
53780
55566
|
await walk(fullPath);
|
|
53781
55567
|
} else if (entry.isFile()) {
|
|
@@ -53806,8 +55592,8 @@ async function doCalculateStorage(logConfig) {
|
|
|
53806
55592
|
}
|
|
53807
55593
|
const baseDir = resolveLogBaseDir(logConfig);
|
|
53808
55594
|
const [eventsResult, streamsResult] = await Promise.all([
|
|
53809
|
-
calculateDirSize(
|
|
53810
|
-
calculateDirSize(
|
|
55595
|
+
calculateDirSize(join9(baseDir, "events")),
|
|
55596
|
+
calculateDirSize(join9(baseDir, "streams"))
|
|
53811
55597
|
]);
|
|
53812
55598
|
const indexResult = await calculateIndexSize(baseDir);
|
|
53813
55599
|
return {
|
|
@@ -53821,7 +55607,7 @@ async function doCalculateStorage(logConfig) {
|
|
|
53821
55607
|
};
|
|
53822
55608
|
}
|
|
53823
55609
|
async function calculateIndexSize(baseDir) {
|
|
53824
|
-
if (!
|
|
55610
|
+
if (!existsSync8(baseDir)) {
|
|
53825
55611
|
return { bytes: 0, fileCount: 0 };
|
|
53826
55612
|
}
|
|
53827
55613
|
let bytes = 0;
|
|
@@ -53831,7 +55617,7 @@ async function calculateIndexSize(baseDir) {
|
|
|
53831
55617
|
for (const entry of entries) {
|
|
53832
55618
|
if (!entry.isFile() || !entry.name.startsWith("logs-index.sqlite"))
|
|
53833
55619
|
continue;
|
|
53834
|
-
const stats = await fsPromises.stat(
|
|
55620
|
+
const stats = await fsPromises.stat(join9(baseDir, entry.name));
|
|
53835
55621
|
bytes += stats.size;
|
|
53836
55622
|
fileCount += 1;
|
|
53837
55623
|
}
|
|
@@ -53882,33 +55668,17 @@ function startLogStorageBackgroundTask(logConfig) {
|
|
|
53882
55668
|
};
|
|
53883
55669
|
}
|
|
53884
55670
|
|
|
53885
|
-
// src/log-tail.ts
|
|
53886
|
-
var subscribers = new Set;
|
|
53887
|
-
function publishLogEvent(event) {
|
|
53888
|
-
for (const subscriber of subscribers) {
|
|
53889
|
-
try {
|
|
53890
|
-
subscriber(event);
|
|
53891
|
-
} catch {}
|
|
53892
|
-
}
|
|
53893
|
-
}
|
|
53894
|
-
function subscribeLogEvents(subscriber) {
|
|
53895
|
-
subscribers.add(subscriber);
|
|
53896
|
-
return () => {
|
|
53897
|
-
subscribers.delete(subscriber);
|
|
53898
|
-
};
|
|
53899
|
-
}
|
|
53900
|
-
|
|
53901
55671
|
// src/logger.ts
|
|
53902
55672
|
import {
|
|
53903
55673
|
appendFileSync,
|
|
53904
55674
|
closeSync as closeSync2,
|
|
53905
|
-
existsSync as
|
|
53906
|
-
mkdirSync as
|
|
55675
|
+
existsSync as existsSync9,
|
|
55676
|
+
mkdirSync as mkdirSync4,
|
|
53907
55677
|
openSync as openSync2,
|
|
53908
|
-
statSync as
|
|
55678
|
+
statSync as statSync3,
|
|
53909
55679
|
writeSync
|
|
53910
55680
|
} from "fs";
|
|
53911
|
-
import { join as
|
|
55681
|
+
import { join as join10 } from "path";
|
|
53912
55682
|
class Logger {
|
|
53913
55683
|
baseDir;
|
|
53914
55684
|
eventsDir;
|
|
@@ -53923,8 +55693,8 @@ class Logger {
|
|
|
53923
55693
|
this._bodyPolicy = config2.bodyPolicy ?? "off";
|
|
53924
55694
|
this._streamsEnabled = config2.streams?.enabled !== false;
|
|
53925
55695
|
this.maxStreamBytes = config2.streams?.maxBytesPerRequest ?? 10 * 1024 * 1024;
|
|
53926
|
-
this.eventsDir =
|
|
53927
|
-
this.streamsDir =
|
|
55696
|
+
this.eventsDir = join10(baseDir, "events");
|
|
55697
|
+
this.streamsDir = join10(baseDir, "streams");
|
|
53928
55698
|
if (this._enabled)
|
|
53929
55699
|
this.ensureDirs();
|
|
53930
55700
|
}
|
|
@@ -53936,25 +55706,26 @@ class Logger {
|
|
|
53936
55706
|
}
|
|
53937
55707
|
ensureDirs() {
|
|
53938
55708
|
for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
|
|
53939
|
-
if (!
|
|
53940
|
-
|
|
55709
|
+
if (!existsSync9(dir))
|
|
55710
|
+
mkdirSync4(dir, { recursive: true });
|
|
53941
55711
|
}
|
|
53942
55712
|
}
|
|
53943
55713
|
ensureStreamDateDir(dateStr) {
|
|
53944
|
-
const dir =
|
|
53945
|
-
if (!
|
|
53946
|
-
|
|
55714
|
+
const dir = join10(this.streamsDir, dateStr);
|
|
55715
|
+
if (!existsSync9(dir))
|
|
55716
|
+
mkdirSync4(dir, { recursive: true });
|
|
53947
55717
|
return dir;
|
|
53948
55718
|
}
|
|
53949
55719
|
writeEvent(event) {
|
|
53950
55720
|
if (!this._enabled)
|
|
53951
55721
|
return;
|
|
53952
55722
|
try {
|
|
55723
|
+
const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
|
|
53953
55724
|
this.ensureDirs();
|
|
53954
|
-
const dateStr =
|
|
53955
|
-
const filePath =
|
|
53956
|
-
const offset =
|
|
53957
|
-
const line2 = `${JSON.stringify(
|
|
55725
|
+
const dateStr = enrichedEvent.ts_start.slice(0, 10);
|
|
55726
|
+
const filePath = join10(this.eventsDir, `${dateStr}.jsonl`);
|
|
55727
|
+
const offset = existsSync9(filePath) ? statSync3(filePath).size : 0;
|
|
55728
|
+
const line2 = `${JSON.stringify(enrichedEvent)}
|
|
53958
55729
|
`;
|
|
53959
55730
|
appendFileSync(filePath, line2);
|
|
53960
55731
|
const id = encodeOffsetLogEventId(dateStr, offset);
|
|
@@ -53964,9 +55735,9 @@ class Logger {
|
|
|
53964
55735
|
date: dateStr,
|
|
53965
55736
|
offset,
|
|
53966
55737
|
byteLength: Buffer.byteLength(line2),
|
|
53967
|
-
event
|
|
55738
|
+
event: enrichedEvent
|
|
53968
55739
|
});
|
|
53969
|
-
publishLogEvent({ id, date: dateStr, filePath, offset, event });
|
|
55740
|
+
publishLogEvent({ id, date: dateStr, filePath, offset, event: enrichedEvent });
|
|
53970
55741
|
} catch (err) {
|
|
53971
55742
|
console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
|
|
53972
55743
|
}
|
|
@@ -53982,7 +55753,7 @@ class Logger {
|
|
|
53982
55753
|
let fd;
|
|
53983
55754
|
try {
|
|
53984
55755
|
const dir = this.ensureStreamDateDir(dateStr);
|
|
53985
|
-
filePath =
|
|
55756
|
+
filePath = join10(dir, `${requestId}.sse.raw`);
|
|
53986
55757
|
fd = openSync2(filePath, "a");
|
|
53987
55758
|
} catch (err) {
|
|
53988
55759
|
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
|
|
@@ -54072,85 +55843,6 @@ function normalizeUrl(rawUrl) {
|
|
|
54072
55843
|
return rawUrl;
|
|
54073
55844
|
}
|
|
54074
55845
|
|
|
54075
|
-
// src/network-access.ts
|
|
54076
|
-
var REMOTE_ADDRESS_ENV_KEY = "LOCAL_ROUTER_REMOTE_ADDRESS";
|
|
54077
|
-
function parseIpv4(address) {
|
|
54078
|
-
const parts = address.split(".");
|
|
54079
|
-
if (parts.length !== 4)
|
|
54080
|
-
return null;
|
|
54081
|
-
const octets = parts.map((part) => {
|
|
54082
|
-
if (!/^\d{1,3}$/.test(part))
|
|
54083
|
-
return Number.NaN;
|
|
54084
|
-
const value = Number.parseInt(part, 10);
|
|
54085
|
-
return value >= 0 && value <= 255 ? value : Number.NaN;
|
|
54086
|
-
});
|
|
54087
|
-
return octets.every(Number.isFinite) ? octets : null;
|
|
54088
|
-
}
|
|
54089
|
-
function normalizeIpAddress(raw2) {
|
|
54090
|
-
let address = raw2.trim().toLowerCase();
|
|
54091
|
-
if (address.startsWith("[")) {
|
|
54092
|
-
const end = address.indexOf("]");
|
|
54093
|
-
if (end !== -1)
|
|
54094
|
-
address = address.slice(1, end);
|
|
54095
|
-
}
|
|
54096
|
-
const mappedIpv4 = address.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
|
|
54097
|
-
if (mappedIpv4) {
|
|
54098
|
-
return mappedIpv4[1];
|
|
54099
|
-
}
|
|
54100
|
-
return address;
|
|
54101
|
-
}
|
|
54102
|
-
function isLoopbackAddress(raw2) {
|
|
54103
|
-
if (!raw2)
|
|
54104
|
-
return false;
|
|
54105
|
-
const address = normalizeIpAddress(raw2);
|
|
54106
|
-
const ipv43 = parseIpv4(address);
|
|
54107
|
-
if (ipv43)
|
|
54108
|
-
return ipv43[0] === 127;
|
|
54109
|
-
return address === "::1";
|
|
54110
|
-
}
|
|
54111
|
-
function isLanAddress(raw2) {
|
|
54112
|
-
if (!raw2)
|
|
54113
|
-
return false;
|
|
54114
|
-
const address = normalizeIpAddress(raw2);
|
|
54115
|
-
const ipv43 = parseIpv4(address);
|
|
54116
|
-
if (ipv43) {
|
|
54117
|
-
const [a, b] = ipv43;
|
|
54118
|
-
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254;
|
|
54119
|
-
}
|
|
54120
|
-
return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address);
|
|
54121
|
-
}
|
|
54122
|
-
function decideNetworkAccess(serverConfig, rawRemoteAddress) {
|
|
54123
|
-
const remoteAddress = rawRemoteAddress ? normalizeIpAddress(rawRemoteAddress) : null;
|
|
54124
|
-
if (!remoteAddress || isLoopbackAddress(remoteAddress)) {
|
|
54125
|
-
return { allowed: true, remoteAddress };
|
|
54126
|
-
}
|
|
54127
|
-
const lanEnabled = serverConfig?.lanAccess?.enabled === true;
|
|
54128
|
-
if (!lanEnabled) {
|
|
54129
|
-
return { allowed: false, remoteAddress, reason: "lan-disabled" };
|
|
54130
|
-
}
|
|
54131
|
-
if (!isLanAddress(remoteAddress)) {
|
|
54132
|
-
return { allowed: false, remoteAddress, reason: "non-lan-address" };
|
|
54133
|
-
}
|
|
54134
|
-
return { allowed: true, remoteAddress };
|
|
54135
|
-
}
|
|
54136
|
-
function getRemoteAddressFromContext(c2) {
|
|
54137
|
-
const env = c2.env;
|
|
54138
|
-
const value = env?.[REMOTE_ADDRESS_ENV_KEY];
|
|
54139
|
-
return typeof value === "string" && value.trim() ? value : null;
|
|
54140
|
-
}
|
|
54141
|
-
function createNetworkAccessMiddleware(store) {
|
|
54142
|
-
return async (c2, next) => {
|
|
54143
|
-
const decision = decideNetworkAccess(store.get().server, getRemoteAddressFromContext(c2));
|
|
54144
|
-
if (!decision.allowed) {
|
|
54145
|
-
return c2.json({
|
|
54146
|
-
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",
|
|
54147
|
-
remoteAddress: decision.remoteAddress
|
|
54148
|
-
}, 403);
|
|
54149
|
-
}
|
|
54150
|
-
await next();
|
|
54151
|
-
};
|
|
54152
|
-
}
|
|
54153
|
-
|
|
54154
55846
|
// src/openapi.ts
|
|
54155
55847
|
var openAPISpec = {
|
|
54156
55848
|
openapi: "3.0.0",
|
|
@@ -55257,7 +56949,7 @@ var openAPISpec = {
|
|
|
55257
56949
|
// src/plugin-loader.ts
|
|
55258
56950
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
55259
56951
|
import { tmpdir } from "os";
|
|
55260
|
-
import { join as
|
|
56952
|
+
import { join as join11, resolve as resolve7 } from "path";
|
|
55261
56953
|
function isLocalPath(pkg) {
|
|
55262
56954
|
return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
|
|
55263
56955
|
}
|
|
@@ -55280,7 +56972,7 @@ var remoteTmpDir = null;
|
|
|
55280
56972
|
var remoteTmpFiles = [];
|
|
55281
56973
|
async function ensureRemoteTmpDir() {
|
|
55282
56974
|
if (!remoteTmpDir) {
|
|
55283
|
-
remoteTmpDir = await mkdtemp(
|
|
56975
|
+
remoteTmpDir = await mkdtemp(join11(tmpdir(), "local-router-plugins-"));
|
|
55284
56976
|
}
|
|
55285
56977
|
return remoteTmpDir;
|
|
55286
56978
|
}
|
|
@@ -55293,7 +56985,7 @@ async function fetchRemotePlugin(url2) {
|
|
|
55293
56985
|
const ext = inferExtension(url2, response.headers.get("content-type"));
|
|
55294
56986
|
const dir = await ensureRemoteTmpDir();
|
|
55295
56987
|
const fileName = `plugin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}${ext}`;
|
|
55296
|
-
const filePath =
|
|
56988
|
+
const filePath = join11(dir, fileName);
|
|
55297
56989
|
await writeFile(filePath, content, "utf-8");
|
|
55298
56990
|
remoteTmpFiles.push(filePath);
|
|
55299
56991
|
return filePath;
|
|
@@ -55313,7 +57005,7 @@ async function importPlugin(pkg, configDir) {
|
|
|
55313
57005
|
const localPath = await fetchRemotePlugin(pkg);
|
|
55314
57006
|
modulePath = `${localPath}?t=${Date.now()}`;
|
|
55315
57007
|
} else if (isLocalPath(pkg)) {
|
|
55316
|
-
const absolutePath =
|
|
57008
|
+
const absolutePath = resolve7(configDir, pkg);
|
|
55317
57009
|
modulePath = `${absolutePath}?t=${Date.now()}`;
|
|
55318
57010
|
} else {
|
|
55319
57011
|
modulePath = pkg;
|
|
@@ -55706,6 +57398,7 @@ async function proxyRequest(c2, options) {
|
|
|
55706
57398
|
});
|
|
55707
57399
|
}
|
|
55708
57400
|
const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
|
|
57401
|
+
const tokenUsageCollector = createTokenUsageStreamCollector(`${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
|
|
55709
57402
|
let upstreamBytes = 0;
|
|
55710
57403
|
let writeEventCalled = false;
|
|
55711
57404
|
const finalizeAndWriteEvent = () => {
|
|
@@ -55717,6 +57410,7 @@ async function proxyRequest(c2, options) {
|
|
|
55717
57410
|
truncated: false,
|
|
55718
57411
|
filePath: null
|
|
55719
57412
|
};
|
|
57413
|
+
const tokenUsage2 = tokenUsageCollector.getUsage();
|
|
55720
57414
|
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
55721
57415
|
upstream_status: sseStatus,
|
|
55722
57416
|
content_type_res: contentTypeRes,
|
|
@@ -55728,6 +57422,7 @@ async function proxyRequest(c2, options) {
|
|
|
55728
57422
|
stream_file_bytes: captureResult.bytesWritten
|
|
55729
57423
|
},
|
|
55730
57424
|
...captureResult.truncated && { stream_file_truncated: true },
|
|
57425
|
+
...tokenUsage2 && { token_usage: tokenUsage2 },
|
|
55731
57426
|
...requestBody !== undefined && { request_body: requestBody },
|
|
55732
57427
|
...pluginLogOverrides
|
|
55733
57428
|
}));
|
|
@@ -55743,6 +57438,7 @@ async function proxyRequest(c2, options) {
|
|
|
55743
57438
|
return;
|
|
55744
57439
|
}
|
|
55745
57440
|
upstreamBytes += value.byteLength;
|
|
57441
|
+
tokenUsageCollector.addChunk(value);
|
|
55746
57442
|
capture?.write(value);
|
|
55747
57443
|
controller.enqueue(value);
|
|
55748
57444
|
} catch (err) {
|
|
@@ -55793,12 +57489,14 @@ async function proxyRequest(c2, options) {
|
|
|
55793
57489
|
});
|
|
55794
57490
|
}
|
|
55795
57491
|
const responseBytes = Buffer.byteLength(responseText, "utf-8");
|
|
57492
|
+
const tokenUsage = extractTokenUsageFromResponseText(responseText, "response_body", `${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
|
|
55796
57493
|
const eventOverrides = {
|
|
55797
57494
|
upstream_status: upstreamRes.status,
|
|
55798
57495
|
content_type_res: contentTypeRes,
|
|
55799
57496
|
response_headers: finalResponseHeaders,
|
|
55800
57497
|
response_bytes: responseBytes,
|
|
55801
57498
|
provider_request_id: providerRequestId,
|
|
57499
|
+
...tokenUsage && { token_usage: tokenUsage },
|
|
55802
57500
|
...pluginLogOverrides
|
|
55803
57501
|
};
|
|
55804
57502
|
if (requestBody !== undefined) {
|
|
@@ -56081,7 +57779,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
56081
57779
|
const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
|
|
56082
57780
|
const CRYPTO_SESSION_MAX = 512;
|
|
56083
57781
|
const schemaPath = getBundledSchemaPath();
|
|
56084
|
-
const schemaJson = JSON.parse(
|
|
57782
|
+
const schemaJson = JSON.parse(readFileSync6(schemaPath, "utf-8"));
|
|
56085
57783
|
const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
|
|
56086
57784
|
for (const [id, record2] of Array.from(cryptoSessions.entries())) {
|
|
56087
57785
|
if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
|
|
@@ -56221,6 +57919,45 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
56221
57919
|
return c2.json({ error: `\u8BFB\u53D6\u914D\u7F6E schema \u5931\u8D25: ${err instanceof Error ? err.message : err}` }, 500);
|
|
56222
57920
|
}
|
|
56223
57921
|
});
|
|
57922
|
+
api2.get("/autostart", async (c2) => {
|
|
57923
|
+
try {
|
|
57924
|
+
const manager = createAutostartManager();
|
|
57925
|
+
const systemInstalled = await manager.isInstalled();
|
|
57926
|
+
const config2 = store.get();
|
|
57927
|
+
return c2.json({
|
|
57928
|
+
enabled: config2.server?.autostart ?? false,
|
|
57929
|
+
systemInstalled,
|
|
57930
|
+
platform: manager.platform,
|
|
57931
|
+
servicePath: manager.getServicePath()
|
|
57932
|
+
});
|
|
57933
|
+
} catch (err) {
|
|
57934
|
+
return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
57935
|
+
}
|
|
57936
|
+
});
|
|
57937
|
+
api2.put("/autostart", async (c2) => {
|
|
57938
|
+
try {
|
|
57939
|
+
const { enabled } = await c2.req.json();
|
|
57940
|
+
if (typeof enabled !== "boolean") {
|
|
57941
|
+
return c2.json({ error: "enabled \u5FC5\u987B\u662F\u5E03\u5C14\u503C" }, 400);
|
|
57942
|
+
}
|
|
57943
|
+
const manager = createAutostartManager();
|
|
57944
|
+
if (manager.platform === "unsupported") {
|
|
57945
|
+
return c2.json({ error: "\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u81EA\u542F\u52A8" }, 400);
|
|
57946
|
+
}
|
|
57947
|
+
if (enabled) {
|
|
57948
|
+
const { execPath, args } = getAutostartExecArgs();
|
|
57949
|
+
await manager.install({ execPath, args, label: "com.lakphy.local-router" });
|
|
57950
|
+
} else {
|
|
57951
|
+
await manager.uninstall();
|
|
57952
|
+
}
|
|
57953
|
+
const config2 = store.get();
|
|
57954
|
+
const updated = { ...config2, server: { ...config2.server, autostart: enabled } };
|
|
57955
|
+
store.save(updated);
|
|
57956
|
+
return c2.json({ ok: true, enabled });
|
|
57957
|
+
} catch (err) {
|
|
57958
|
+
return c2.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
57959
|
+
}
|
|
57960
|
+
});
|
|
56224
57961
|
api2.post("/chat/proxy", async (c2) => {
|
|
56225
57962
|
let body;
|
|
56226
57963
|
try {
|
|
@@ -56686,7 +58423,7 @@ async function createApp(store, options) {
|
|
|
56686
58423
|
}
|
|
56687
58424
|
const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
|
|
56688
58425
|
options?.registerCleanup?.(stopLogStorageTask);
|
|
56689
|
-
const configDir =
|
|
58426
|
+
const configDir = dirname3(resolve8(store.getPath()));
|
|
56690
58427
|
const pluginManager = new PluginManager(configDir);
|
|
56691
58428
|
const reloadResult = await pluginManager.reloadAll(config2.providers);
|
|
56692
58429
|
if (!reloadResult.ok) {
|
|
@@ -56733,28 +58470,56 @@ async function createApp(store, options) {
|
|
|
56733
58470
|
}
|
|
56734
58471
|
return app;
|
|
56735
58472
|
}
|
|
56736
|
-
async function
|
|
56737
|
-
const configPath = parseConfigPath();
|
|
58473
|
+
async function createAppRuntimeFromConfigPath(configPath, listen) {
|
|
56738
58474
|
const store = new ConfigStore(configPath);
|
|
56739
|
-
|
|
56740
|
-
|
|
56741
|
-
|
|
56742
|
-
|
|
58475
|
+
const cleanups = [];
|
|
58476
|
+
const app = await createApp(store, {
|
|
58477
|
+
listen,
|
|
58478
|
+
registerCleanup: (cleanup) => {
|
|
58479
|
+
cleanups.push(cleanup);
|
|
56743
58480
|
}
|
|
56744
58481
|
});
|
|
58482
|
+
const logRealtime = createLogRealtimeRuntime({ store });
|
|
58483
|
+
return {
|
|
58484
|
+
app,
|
|
58485
|
+
logRealtime,
|
|
58486
|
+
dispose: () => {
|
|
58487
|
+
logRealtime.dispose();
|
|
58488
|
+
for (const cleanup of cleanups.reverse()) {
|
|
58489
|
+
try {
|
|
58490
|
+
cleanup();
|
|
58491
|
+
} catch {}
|
|
58492
|
+
}
|
|
58493
|
+
}
|
|
58494
|
+
};
|
|
58495
|
+
}
|
|
58496
|
+
async function createDefaultAppRuntimeFromProcessArgs() {
|
|
58497
|
+
const configPath = parseConfigPath();
|
|
58498
|
+
return createAppRuntimeFromConfigPath(configPath, {
|
|
58499
|
+
host: process.env.HOST ?? "0.0.0.0",
|
|
58500
|
+
port: Number.parseInt(process.env.PORT ?? "4099", 10)
|
|
58501
|
+
});
|
|
56745
58502
|
}
|
|
56746
58503
|
|
|
56747
58504
|
// src/entry.ts
|
|
56748
|
-
var
|
|
58505
|
+
var runtime = await createDefaultAppRuntimeFromProcessArgs();
|
|
56749
58506
|
var entry_default = {
|
|
56750
58507
|
hostname: process.env.HOST ?? "0.0.0.0",
|
|
56751
58508
|
port: Number.parseInt(process.env.PORT ?? "4099", 10),
|
|
56752
58509
|
fetch(request, server) {
|
|
56753
58510
|
const remoteAddress = server.requestIP(request)?.address ?? null;
|
|
56754
|
-
|
|
58511
|
+
const realtimeUpgrade = runtime.logRealtime.upgrade(request, server, remoteAddress);
|
|
58512
|
+
if (realtimeUpgrade.handled) {
|
|
58513
|
+
if (realtimeUpgrade.upgraded) {
|
|
58514
|
+
return;
|
|
58515
|
+
}
|
|
58516
|
+
return realtimeUpgrade.response ?? new Response("WebSocket Upgrade failed", { status: 400 });
|
|
58517
|
+
}
|
|
58518
|
+
return runtime.app.fetch(request, {
|
|
56755
58519
|
[REMOTE_ADDRESS_ENV_KEY]: remoteAddress
|
|
56756
58520
|
});
|
|
56757
|
-
}
|
|
58521
|
+
},
|
|
58522
|
+
websocket: runtime.logRealtime.websocket
|
|
56758
58523
|
};
|
|
56759
58524
|
export {
|
|
56760
58525
|
entry_default as default
|