@lakphy/local-router 0.5.5 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +967 -653
- package/dist/entry.js +1223 -909
- package/dist/web/assets/index-0-b0NcMV.js +192 -0
- package/dist/web/assets/index-_8dgANhJ.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BxjdLPIh.js +0 -190
- package/dist/web/assets/index-Dc2P7nS-.css +0 -2
package/dist/entry.js
CHANGED
|
@@ -51697,978 +51697,1029 @@ class CryptoSession {
|
|
|
51697
51697
|
}
|
|
51698
51698
|
|
|
51699
51699
|
// src/log-metrics.ts
|
|
51700
|
-
import { createReadStream, existsSync as
|
|
51700
|
+
import { createReadStream, existsSync as existsSync4 } from "fs";
|
|
51701
51701
|
import { join as join5 } from "path";
|
|
51702
51702
|
import { createInterface } from "readline";
|
|
51703
|
-
|
|
51704
|
-
|
|
51705
|
-
|
|
51706
|
-
|
|
51707
|
-
|
|
51708
|
-
|
|
51709
|
-
|
|
51710
|
-
|
|
51711
|
-
|
|
51712
|
-
};
|
|
51713
|
-
var TOP_LIMIT = 5;
|
|
51714
|
-
var MAX_LINES_SCANNED = 250000;
|
|
51715
|
-
var CACHE_TTL_MS = 15000;
|
|
51716
|
-
var metricsCache = new Map;
|
|
51717
|
-
function isLogMetricsWindow(value) {
|
|
51718
|
-
return value === "1h" || value === "6h" || value === "24h";
|
|
51719
|
-
}
|
|
51720
|
-
function toPercent(numerator, denominator) {
|
|
51721
|
-
if (denominator <= 0)
|
|
51722
|
-
return 0;
|
|
51723
|
-
return Number((numerator / denominator * 100).toFixed(2));
|
|
51703
|
+
|
|
51704
|
+
// src/token-usage.ts
|
|
51705
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
51706
|
+
import { resolve as resolve5 } from "path";
|
|
51707
|
+
var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
|
|
51708
|
+
function asRecord(value) {
|
|
51709
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51710
|
+
return null;
|
|
51711
|
+
return value;
|
|
51724
51712
|
}
|
|
51725
|
-
function
|
|
51726
|
-
|
|
51727
|
-
|
|
51713
|
+
function numeric(value) {
|
|
51714
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
51715
|
+
return value;
|
|
51716
|
+
if (typeof value === "string" && value.trim()) {
|
|
51717
|
+
const parsed = Number(value);
|
|
51718
|
+
if (Number.isFinite(parsed))
|
|
51719
|
+
return parsed;
|
|
51720
|
+
}
|
|
51721
|
+
return null;
|
|
51728
51722
|
}
|
|
51729
|
-
function
|
|
51730
|
-
|
|
51731
|
-
for (
|
|
51732
|
-
|
|
51723
|
+
function numberAt(value, path) {
|
|
51724
|
+
let current = value;
|
|
51725
|
+
for (const key2 of path) {
|
|
51726
|
+
const record2 = asRecord(current);
|
|
51727
|
+
if (!record2 || !(key2 in record2))
|
|
51728
|
+
return null;
|
|
51729
|
+
current = record2[key2];
|
|
51733
51730
|
}
|
|
51734
|
-
return
|
|
51731
|
+
return numeric(current);
|
|
51735
51732
|
}
|
|
51736
|
-
function
|
|
51737
|
-
|
|
51738
|
-
|
|
51739
|
-
|
|
51740
|
-
|
|
51741
|
-
|
|
51742
|
-
|
|
51743
|
-
return "4xx";
|
|
51744
|
-
if (status >= 500)
|
|
51745
|
-
return "5xx";
|
|
51746
|
-
return "network_error";
|
|
51733
|
+
function firstNumber(value, paths) {
|
|
51734
|
+
for (const path of paths) {
|
|
51735
|
+
const found = numberAt(value, path);
|
|
51736
|
+
if (found !== null)
|
|
51737
|
+
return found;
|
|
51738
|
+
}
|
|
51739
|
+
return null;
|
|
51747
51740
|
}
|
|
51748
|
-
function
|
|
51749
|
-
|
|
51750
|
-
|
|
51751
|
-
|
|
51752
|
-
return
|
|
51741
|
+
function maxNullable(...values) {
|
|
51742
|
+
const numbers = values.filter((value) => value !== null && value !== undefined);
|
|
51743
|
+
if (numbers.length === 0)
|
|
51744
|
+
return null;
|
|
51745
|
+
return Math.max(...numbers);
|
|
51753
51746
|
}
|
|
51754
|
-
function
|
|
51755
|
-
|
|
51756
|
-
|
|
51757
|
-
|
|
51758
|
-
|
|
51747
|
+
function sumNullable(...values) {
|
|
51748
|
+
let total = 0;
|
|
51749
|
+
let hasValue = false;
|
|
51750
|
+
for (const value of values) {
|
|
51751
|
+
if (value === null || value === undefined)
|
|
51752
|
+
continue;
|
|
51753
|
+
total += value;
|
|
51754
|
+
hasValue = true;
|
|
51755
|
+
}
|
|
51756
|
+
return hasValue ? total : null;
|
|
51759
51757
|
}
|
|
51760
|
-
function
|
|
51761
|
-
|
|
51762
|
-
|
|
51763
|
-
|
|
51764
|
-
const series = Array.from({ length: bucketCount }, (_, i) => ({
|
|
51765
|
-
ts: new Date(fromMs + i * bucketMs).toISOString(),
|
|
51766
|
-
requests: 0,
|
|
51767
|
-
errors: 0,
|
|
51768
|
-
avgLatencyMs: 0
|
|
51769
|
-
}));
|
|
51770
|
-
return {
|
|
51771
|
-
window: window2,
|
|
51772
|
-
from: new Date(fromMs).toISOString(),
|
|
51773
|
-
to: new Date(nowMs).toISOString(),
|
|
51774
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
51775
|
-
source: source2,
|
|
51776
|
-
summary: {
|
|
51777
|
-
totalRequests: 0,
|
|
51778
|
-
successRequests: 0,
|
|
51779
|
-
errorRequests: 0,
|
|
51780
|
-
successRate: 0,
|
|
51781
|
-
avgLatencyMs: 0,
|
|
51782
|
-
p95LatencyMs: 0,
|
|
51783
|
-
totalRequestBytes: 0,
|
|
51784
|
-
totalResponseBytes: 0
|
|
51785
|
-
},
|
|
51786
|
-
series,
|
|
51787
|
-
topProviders: [],
|
|
51788
|
-
topRouteTypes: [],
|
|
51789
|
-
statusClasses: {
|
|
51790
|
-
"2xx": 0,
|
|
51791
|
-
"4xx": 0,
|
|
51792
|
-
"5xx": 0,
|
|
51793
|
-
network_error: 0
|
|
51794
|
-
},
|
|
51795
|
-
warnings
|
|
51796
|
-
};
|
|
51758
|
+
function roundPercent(numerator, denominator) {
|
|
51759
|
+
if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
|
|
51760
|
+
return null;
|
|
51761
|
+
return Number((numerator / denominator * 100).toFixed(2));
|
|
51797
51762
|
}
|
|
51798
|
-
|
|
51799
|
-
const
|
|
51800
|
-
|
|
51801
|
-
|
|
51802
|
-
|
|
51803
|
-
|
|
51804
|
-
|
|
51805
|
-
|
|
51806
|
-
|
|
51807
|
-
|
|
51808
|
-
|
|
51809
|
-
|
|
51810
|
-
|
|
51763
|
+
function inferProviderStyle(usage, providerHint) {
|
|
51764
|
+
const hint = providerHint?.toLowerCase() ?? "";
|
|
51765
|
+
if (hint.includes("anthropic") || hint.includes("claude"))
|
|
51766
|
+
return "anthropic";
|
|
51767
|
+
if (hint.includes("gemini") || hint.includes("google"))
|
|
51768
|
+
return "gemini";
|
|
51769
|
+
if (hint.includes("deepseek"))
|
|
51770
|
+
return "deepseek";
|
|
51771
|
+
if (hint.includes("cohere"))
|
|
51772
|
+
return "cohere";
|
|
51773
|
+
if (hint.includes("mistral"))
|
|
51774
|
+
return "mistral";
|
|
51775
|
+
if (hint.includes("openrouter"))
|
|
51776
|
+
return "openrouter";
|
|
51777
|
+
if (hint.includes("openai") || hint.includes("gpt-"))
|
|
51778
|
+
return "openai";
|
|
51779
|
+
if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
|
|
51780
|
+
return "anthropic";
|
|
51811
51781
|
}
|
|
51812
|
-
|
|
51813
|
-
|
|
51814
|
-
const cached2 = metricsCache.get(cacheKey);
|
|
51815
|
-
if (!refresh && cached2 && cached2.expiresAt > nowMs) {
|
|
51816
|
-
return cached2.value;
|
|
51782
|
+
if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
|
|
51783
|
+
return "deepseek";
|
|
51817
51784
|
}
|
|
51818
|
-
|
|
51819
|
-
|
|
51820
|
-
const empty = createEmptyMetrics(window2, nowMs, {
|
|
51821
|
-
logEnabled: true,
|
|
51822
|
-
baseDir,
|
|
51823
|
-
filesScanned: 0,
|
|
51824
|
-
linesScanned: 0,
|
|
51825
|
-
partial: false
|
|
51826
|
-
}, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
|
|
51827
|
-
metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
|
|
51828
|
-
return empty;
|
|
51785
|
+
if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
|
|
51786
|
+
return "gemini";
|
|
51829
51787
|
}
|
|
51830
|
-
|
|
51831
|
-
|
|
51832
|
-
|
|
51833
|
-
|
|
51834
|
-
|
|
51835
|
-
|
|
51836
|
-
|
|
51837
|
-
|
|
51838
|
-
}
|
|
51839
|
-
|
|
51840
|
-
|
|
51841
|
-
|
|
51842
|
-
|
|
51843
|
-
|
|
51844
|
-
|
|
51845
|
-
|
|
51846
|
-
|
|
51788
|
+
if ("billed_units" in usage || "tokens" in usage) {
|
|
51789
|
+
return "cohere";
|
|
51790
|
+
}
|
|
51791
|
+
if ("prompt_tokens" in usage || "completion_tokens" in usage) {
|
|
51792
|
+
return "openai";
|
|
51793
|
+
}
|
|
51794
|
+
if ("input_tokens" in usage || "output_tokens" in usage) {
|
|
51795
|
+
return "openai";
|
|
51796
|
+
}
|
|
51797
|
+
return "unknown";
|
|
51798
|
+
}
|
|
51799
|
+
function createEmptyMetrics(input) {
|
|
51800
|
+
return {
|
|
51801
|
+
schemaVersion: 1,
|
|
51802
|
+
source: input.source,
|
|
51803
|
+
providerStyle: input.providerStyle,
|
|
51804
|
+
inputTokens: null,
|
|
51805
|
+
outputTokens: null,
|
|
51806
|
+
totalTokens: null,
|
|
51807
|
+
cachedInputTokens: null,
|
|
51808
|
+
cacheHitInputTokens: null,
|
|
51809
|
+
cacheHitRate: null,
|
|
51810
|
+
cacheHitRateDenominatorTokens: null,
|
|
51811
|
+
cacheHitRateFormula: null,
|
|
51812
|
+
cacheReadInputTokens: null,
|
|
51813
|
+
cacheCreationInputTokens: null,
|
|
51814
|
+
cacheCreationInputTokens5m: null,
|
|
51815
|
+
cacheCreationInputTokens1h: null,
|
|
51816
|
+
cacheWriteInputTokens: null,
|
|
51817
|
+
cacheMissInputTokens: null,
|
|
51818
|
+
reasoningTokens: null,
|
|
51819
|
+
audioInputTokens: null,
|
|
51820
|
+
audioOutputTokens: null,
|
|
51821
|
+
textInputTokens: null,
|
|
51822
|
+
textOutputTokens: null,
|
|
51823
|
+
acceptedPredictionTokens: null,
|
|
51824
|
+
rejectedPredictionTokens: null,
|
|
51825
|
+
toolUsePromptTokens: null,
|
|
51826
|
+
billableInputTokens: null,
|
|
51827
|
+
billableOutputTokens: null,
|
|
51828
|
+
creditUsage: null,
|
|
51829
|
+
cost: null,
|
|
51830
|
+
rawUsage: input.rawUsage,
|
|
51831
|
+
rawUsagePath: input.rawUsagePath,
|
|
51832
|
+
warnings: []
|
|
51847
51833
|
};
|
|
51848
|
-
|
|
51849
|
-
|
|
51850
|
-
|
|
51851
|
-
|
|
51852
|
-
|
|
51853
|
-
|
|
51854
|
-
|
|
51855
|
-
|
|
51856
|
-
|
|
51857
|
-
|
|
51858
|
-
|
|
51859
|
-
|
|
51860
|
-
|
|
51861
|
-
|
|
51862
|
-
|
|
51863
|
-
|
|
51834
|
+
}
|
|
51835
|
+
function hasAnyTokenSignal(metrics) {
|
|
51836
|
+
return [
|
|
51837
|
+
metrics.inputTokens,
|
|
51838
|
+
metrics.outputTokens,
|
|
51839
|
+
metrics.totalTokens,
|
|
51840
|
+
metrics.cachedInputTokens,
|
|
51841
|
+
metrics.cacheHitInputTokens,
|
|
51842
|
+
metrics.cacheReadInputTokens,
|
|
51843
|
+
metrics.cacheCreationInputTokens,
|
|
51844
|
+
metrics.cacheMissInputTokens,
|
|
51845
|
+
metrics.reasoningTokens,
|
|
51846
|
+
metrics.billableInputTokens,
|
|
51847
|
+
metrics.billableOutputTokens,
|
|
51848
|
+
metrics.creditUsage,
|
|
51849
|
+
metrics.cost
|
|
51850
|
+
].some((value) => value !== null);
|
|
51851
|
+
}
|
|
51852
|
+
function normalizeUsageObject(input) {
|
|
51853
|
+
const { usage, source: source2, rawUsagePath, providerHint } = input;
|
|
51854
|
+
const providerStyle = inferProviderStyle(usage, providerHint);
|
|
51855
|
+
const metrics = createEmptyMetrics({
|
|
51856
|
+
source: source2,
|
|
51857
|
+
providerStyle,
|
|
51858
|
+
rawUsage: usage,
|
|
51859
|
+
rawUsagePath
|
|
51860
|
+
});
|
|
51861
|
+
const usageBody = asRecord(usage.usageMetadata) ?? usage;
|
|
51862
|
+
metrics.inputTokens = firstNumber(usageBody, [
|
|
51863
|
+
["input_tokens"],
|
|
51864
|
+
["prompt_tokens"],
|
|
51865
|
+
["promptTokenCount"],
|
|
51866
|
+
["tokens", "input_tokens"],
|
|
51867
|
+
["billed_units", "input_tokens"]
|
|
51868
|
+
]);
|
|
51869
|
+
metrics.outputTokens = firstNumber(usageBody, [
|
|
51870
|
+
["output_tokens"],
|
|
51871
|
+
["completion_tokens"],
|
|
51872
|
+
["candidatesTokenCount"],
|
|
51873
|
+
["tokens", "output_tokens"],
|
|
51874
|
+
["billed_units", "output_tokens"]
|
|
51875
|
+
]);
|
|
51876
|
+
const explicitTotalTokens = firstNumber(usageBody, [
|
|
51877
|
+
["total_tokens"],
|
|
51878
|
+
["totalTokenCount"],
|
|
51879
|
+
["tokens", "total_tokens"]
|
|
51880
|
+
]);
|
|
51881
|
+
metrics.totalTokens = explicitTotalTokens;
|
|
51882
|
+
const cachedTokens = firstNumber(usageBody, [
|
|
51883
|
+
["input_tokens_details", "cached_tokens"],
|
|
51884
|
+
["prompt_tokens_details", "cached_tokens"],
|
|
51885
|
+
["cached_tokens"],
|
|
51886
|
+
["cachedContentTokenCount"]
|
|
51887
|
+
]);
|
|
51888
|
+
const cacheReadTokens = firstNumber(usageBody, [
|
|
51889
|
+
["cache_read_input_tokens"],
|
|
51890
|
+
["cacheReadInputTokens"],
|
|
51891
|
+
["claude_cache_read_input_tokens"]
|
|
51892
|
+
]);
|
|
51893
|
+
const promptCacheHitTokens = firstNumber(usageBody, [["prompt_cache_hit_tokens"]]);
|
|
51894
|
+
const promptCacheMissTokens = firstNumber(usageBody, [["prompt_cache_miss_tokens"]]);
|
|
51895
|
+
const cacheCreationTokens = firstNumber(usageBody, [
|
|
51896
|
+
["cache_creation_input_tokens"],
|
|
51897
|
+
["cacheCreationInputTokens"],
|
|
51898
|
+
["cache_creation", "input_tokens"],
|
|
51899
|
+
["claude_cache_creation_input_tokens"]
|
|
51900
|
+
]);
|
|
51901
|
+
metrics.cacheCreationInputTokens5m = firstNumber(usageBody, [
|
|
51902
|
+
["cache_creation", "ephemeral_5m_input_tokens"],
|
|
51903
|
+
["cache_creation", "ephemeral5mInputTokens"],
|
|
51904
|
+
["cache_creation_ephemeral_5m_input_tokens"],
|
|
51905
|
+
["claude_cache_creation_5_m_tokens"]
|
|
51906
|
+
]);
|
|
51907
|
+
metrics.cacheCreationInputTokens1h = firstNumber(usageBody, [
|
|
51908
|
+
["cache_creation", "ephemeral_1h_input_tokens"],
|
|
51909
|
+
["cache_creation", "ephemeral1hInputTokens"],
|
|
51910
|
+
["cache_creation_ephemeral_1h_input_tokens"],
|
|
51911
|
+
["claude_cache_creation_1_h_tokens"]
|
|
51912
|
+
]);
|
|
51913
|
+
metrics.cacheCreationInputTokens = maxNullable(cacheCreationTokens, sumNullable(metrics.cacheCreationInputTokens5m, metrics.cacheCreationInputTokens1h));
|
|
51914
|
+
metrics.cacheReadInputTokens = cacheReadTokens;
|
|
51915
|
+
metrics.cacheHitInputTokens = maxNullable(cachedTokens, cacheReadTokens, promptCacheHitTokens);
|
|
51916
|
+
metrics.cachedInputTokens = metrics.cacheHitInputTokens;
|
|
51917
|
+
metrics.cacheMissInputTokens = promptCacheMissTokens;
|
|
51918
|
+
metrics.cacheWriteInputTokens = firstNumber(usageBody, [
|
|
51919
|
+
["cache_write_input_tokens"],
|
|
51920
|
+
["cacheWriteInputTokens"]
|
|
51921
|
+
]);
|
|
51922
|
+
if (metrics.cacheWriteInputTokens === null && providerStyle === "anthropic") {
|
|
51923
|
+
metrics.cacheWriteInputTokens = metrics.cacheCreationInputTokens;
|
|
51924
|
+
}
|
|
51925
|
+
metrics.reasoningTokens = firstNumber(usageBody, [
|
|
51926
|
+
["output_tokens_details", "reasoning_tokens"],
|
|
51927
|
+
["completion_tokens_details", "reasoning_tokens"],
|
|
51928
|
+
["reasoning_tokens"],
|
|
51929
|
+
["thoughtsTokenCount"]
|
|
51930
|
+
]);
|
|
51931
|
+
metrics.audioInputTokens = firstNumber(usageBody, [
|
|
51932
|
+
["input_tokens_details", "audio_tokens"],
|
|
51933
|
+
["prompt_tokens_details", "audio_tokens"],
|
|
51934
|
+
["audio_input_tokens"]
|
|
51935
|
+
]);
|
|
51936
|
+
metrics.audioOutputTokens = firstNumber(usageBody, [
|
|
51937
|
+
["output_tokens_details", "audio_tokens"],
|
|
51938
|
+
["completion_tokens_details", "audio_tokens"],
|
|
51939
|
+
["audio_output_tokens"]
|
|
51940
|
+
]);
|
|
51941
|
+
metrics.textInputTokens = firstNumber(usageBody, [
|
|
51942
|
+
["input_tokens_details", "text_tokens"],
|
|
51943
|
+
["prompt_tokens_details", "text_tokens"],
|
|
51944
|
+
["text_input_tokens"]
|
|
51945
|
+
]);
|
|
51946
|
+
metrics.textOutputTokens = firstNumber(usageBody, [
|
|
51947
|
+
["output_tokens_details", "text_tokens"],
|
|
51948
|
+
["completion_tokens_details", "text_tokens"],
|
|
51949
|
+
["text_output_tokens"]
|
|
51950
|
+
]);
|
|
51951
|
+
metrics.acceptedPredictionTokens = firstNumber(usageBody, [
|
|
51952
|
+
["output_tokens_details", "accepted_prediction_tokens"],
|
|
51953
|
+
["completion_tokens_details", "accepted_prediction_tokens"],
|
|
51954
|
+
["accepted_prediction_tokens"]
|
|
51955
|
+
]);
|
|
51956
|
+
metrics.rejectedPredictionTokens = firstNumber(usageBody, [
|
|
51957
|
+
["output_tokens_details", "rejected_prediction_tokens"],
|
|
51958
|
+
["completion_tokens_details", "rejected_prediction_tokens"],
|
|
51959
|
+
["rejected_prediction_tokens"]
|
|
51960
|
+
]);
|
|
51961
|
+
metrics.toolUsePromptTokens = firstNumber(usageBody, [
|
|
51962
|
+
["toolUsePromptTokenCount"],
|
|
51963
|
+
["tool_use_prompt_tokens"]
|
|
51964
|
+
]);
|
|
51965
|
+
metrics.billableInputTokens = firstNumber(usageBody, [
|
|
51966
|
+
["billed_units", "input_tokens"],
|
|
51967
|
+
["billable_input_tokens"]
|
|
51968
|
+
]);
|
|
51969
|
+
metrics.billableOutputTokens = firstNumber(usageBody, [
|
|
51970
|
+
["billed_units", "output_tokens"],
|
|
51971
|
+
["billable_output_tokens"]
|
|
51972
|
+
]);
|
|
51973
|
+
metrics.creditUsage = firstNumber(usageBody, [["credit_usage"], ["creditUsage"]]);
|
|
51974
|
+
metrics.cost = firstNumber(usageBody, [["cost"], ["total_cost"], ["totalCost"]]);
|
|
51975
|
+
let cacheDenominator = null;
|
|
51976
|
+
let cacheFormula = null;
|
|
51977
|
+
if (providerStyle === "anthropic") {
|
|
51978
|
+
cacheDenominator = sumNullable(metrics.inputTokens, metrics.cacheReadInputTokens, metrics.cacheCreationInputTokens);
|
|
51979
|
+
cacheFormula = "cache_read_input_tokens / (input_tokens + cache_read_input_tokens + cache_creation_input_tokens)";
|
|
51980
|
+
} else if (providerStyle === "deepseek") {
|
|
51981
|
+
cacheDenominator = metrics.inputTokens ?? sumNullable(metrics.cacheHitInputTokens, metrics.cacheMissInputTokens);
|
|
51982
|
+
cacheFormula = "prompt_cache_hit_tokens / prompt_tokens";
|
|
51983
|
+
} else if (providerStyle === "gemini") {
|
|
51984
|
+
cacheDenominator = metrics.inputTokens;
|
|
51985
|
+
cacheFormula = "cachedContentTokenCount / promptTokenCount";
|
|
51986
|
+
} else if (providerStyle === "openai" || providerStyle === "mistral" || providerStyle === "openrouter" || providerStyle === "unknown") {
|
|
51987
|
+
cacheDenominator = metrics.inputTokens;
|
|
51988
|
+
cacheFormula = "cached_tokens / input_tokens";
|
|
51989
|
+
}
|
|
51990
|
+
if (metrics.cacheHitInputTokens !== null && cacheDenominator !== null && cacheDenominator > 0) {
|
|
51991
|
+
metrics.cacheHitRateDenominatorTokens = cacheDenominator;
|
|
51992
|
+
metrics.cacheHitRateFormula = cacheFormula;
|
|
51993
|
+
metrics.cacheHitRate = roundPercent(metrics.cacheHitInputTokens, cacheDenominator);
|
|
51994
|
+
if (metrics.cacheMissInputTokens === null) {
|
|
51995
|
+
metrics.cacheMissInputTokens = Math.max(0, cacheDenominator - metrics.cacheHitInputTokens);
|
|
51864
51996
|
}
|
|
51865
|
-
|
|
51866
|
-
|
|
51867
|
-
|
|
51868
|
-
|
|
51869
|
-
|
|
51870
|
-
|
|
51871
|
-
const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
51872
|
-
for await (const line2 of rl) {
|
|
51873
|
-
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
51874
|
-
partial2 = true;
|
|
51875
|
-
rl.close();
|
|
51876
|
-
stream.destroy();
|
|
51877
|
-
break;
|
|
51878
|
-
}
|
|
51879
|
-
linesScanned += 1;
|
|
51880
|
-
if (!line2.trim())
|
|
51881
|
-
continue;
|
|
51882
|
-
let event;
|
|
51883
|
-
try {
|
|
51884
|
-
event = JSON.parse(line2);
|
|
51885
|
-
} catch {
|
|
51886
|
-
parseErrors += 1;
|
|
51887
|
-
continue;
|
|
51888
|
-
}
|
|
51889
|
-
if (!event.ts_start)
|
|
51890
|
-
continue;
|
|
51891
|
-
const ts = Date.parse(event.ts_start);
|
|
51892
|
-
if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
|
|
51893
|
-
continue;
|
|
51894
|
-
totalRequests += 1;
|
|
51895
|
-
const isError = isErrorEvent(event);
|
|
51896
|
-
if (isError) {
|
|
51897
|
-
errorRequests += 1;
|
|
51898
|
-
} else {
|
|
51899
|
-
successRequests += 1;
|
|
51900
|
-
}
|
|
51901
|
-
const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
|
|
51902
|
-
totalLatency += latency;
|
|
51903
|
-
latencies.push(latency);
|
|
51904
|
-
totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
|
|
51905
|
-
totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
|
|
51906
|
-
const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
|
|
51907
|
-
const bucket = buckets[bucketIndex];
|
|
51908
|
-
bucket.requests += 1;
|
|
51909
|
-
bucket.latencySum += latency;
|
|
51910
|
-
bucket.latencyCount += 1;
|
|
51911
|
-
if (isError)
|
|
51912
|
-
bucket.errors += 1;
|
|
51913
|
-
const providerKey = event.provider || "unknown";
|
|
51914
|
-
const providerRow = providerAgg.get(providerKey) ?? {
|
|
51915
|
-
requests: 0,
|
|
51916
|
-
errors: 0,
|
|
51917
|
-
latencySum: 0
|
|
51918
|
-
};
|
|
51919
|
-
providerRow.requests += 1;
|
|
51920
|
-
providerRow.latencySum += latency;
|
|
51921
|
-
if (isError)
|
|
51922
|
-
providerRow.errors += 1;
|
|
51923
|
-
providerAgg.set(providerKey, providerRow);
|
|
51924
|
-
const routeTypeKey = event.route_type || "unknown";
|
|
51925
|
-
const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
|
|
51926
|
-
requests: 0,
|
|
51927
|
-
errors: 0,
|
|
51928
|
-
latencySum: 0
|
|
51929
|
-
};
|
|
51930
|
-
routeTypeRow.requests += 1;
|
|
51931
|
-
routeTypeRow.latencySum += latency;
|
|
51932
|
-
if (isError)
|
|
51933
|
-
routeTypeRow.errors += 1;
|
|
51934
|
-
routeTypeAgg.set(routeTypeKey, routeTypeRow);
|
|
51935
|
-
statusClasses[getStatusClass(event)] += 1;
|
|
51936
|
-
}
|
|
51937
|
-
} catch (err) {
|
|
51938
|
-
warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
|
|
51939
|
-
partial2 = true;
|
|
51997
|
+
}
|
|
51998
|
+
if (metrics.totalTokens === null && metrics.outputTokens !== null) {
|
|
51999
|
+
const effectiveInputTokens = metrics.cacheHitRateDenominatorTokens ?? metrics.inputTokens;
|
|
52000
|
+
if (effectiveInputTokens !== null) {
|
|
52001
|
+
metrics.totalTokens = effectiveInputTokens + metrics.outputTokens;
|
|
52002
|
+
metrics.warnings.push(metrics.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
51940
52003
|
}
|
|
51941
52004
|
}
|
|
51942
|
-
|
|
51943
|
-
|
|
52005
|
+
return hasAnyTokenSignal(metrics) ? metrics : null;
|
|
52006
|
+
}
|
|
52007
|
+
function collectUsageCandidates(value, prefix = "") {
|
|
52008
|
+
const record2 = asRecord(value);
|
|
52009
|
+
if (!record2)
|
|
52010
|
+
return [];
|
|
52011
|
+
const candidates = [];
|
|
52012
|
+
const candidateKeys = [
|
|
52013
|
+
"usage",
|
|
52014
|
+
"usageMetadata",
|
|
52015
|
+
"message.usage",
|
|
52016
|
+
"response.usage",
|
|
52017
|
+
"body.usage",
|
|
52018
|
+
"data.usage",
|
|
52019
|
+
"event.usage"
|
|
52020
|
+
];
|
|
52021
|
+
for (const key2 of candidateKeys) {
|
|
52022
|
+
const path = key2.split(".");
|
|
52023
|
+
let current = record2;
|
|
52024
|
+
for (const part of path) {
|
|
52025
|
+
const currentRecord = asRecord(current);
|
|
52026
|
+
current = currentRecord?.[part];
|
|
52027
|
+
}
|
|
52028
|
+
const usage = asRecord(current);
|
|
52029
|
+
if (usage) {
|
|
52030
|
+
candidates.push({ usage, path: prefix ? `${prefix}.${key2}` : key2 });
|
|
52031
|
+
}
|
|
52032
|
+
}
|
|
52033
|
+
const direct = normalizeUsageObject({
|
|
52034
|
+
usage: record2,
|
|
52035
|
+
source: "response_body",
|
|
52036
|
+
rawUsagePath: prefix || null
|
|
52037
|
+
});
|
|
52038
|
+
if (direct) {
|
|
52039
|
+
candidates.push({ usage: record2, path: prefix || "$" });
|
|
52040
|
+
}
|
|
52041
|
+
return candidates;
|
|
52042
|
+
}
|
|
52043
|
+
function mergeNumber(current, incoming, strategy = "max") {
|
|
52044
|
+
if (incoming === null)
|
|
52045
|
+
return current;
|
|
52046
|
+
if (current === null)
|
|
52047
|
+
return incoming;
|
|
52048
|
+
return strategy === "latest" ? incoming : Math.max(current, incoming);
|
|
52049
|
+
}
|
|
52050
|
+
function mergeTokenUsageMetrics(current, incoming) {
|
|
52051
|
+
if (!current)
|
|
52052
|
+
return incoming;
|
|
52053
|
+
if (!incoming)
|
|
52054
|
+
return current;
|
|
52055
|
+
const merged = {
|
|
52056
|
+
...current,
|
|
52057
|
+
source: incoming.source,
|
|
52058
|
+
providerStyle: current.providerStyle === "unknown" ? incoming.providerStyle : current.providerStyle,
|
|
52059
|
+
rawUsage: incoming.rawUsage ?? current.rawUsage,
|
|
52060
|
+
rawUsagePath: incoming.rawUsagePath ?? current.rawUsagePath,
|
|
52061
|
+
warnings: Array.from(new Set([...current.warnings, ...incoming.warnings]))
|
|
52062
|
+
};
|
|
52063
|
+
const numericKeys = [
|
|
52064
|
+
"inputTokens",
|
|
52065
|
+
"outputTokens",
|
|
52066
|
+
"totalTokens",
|
|
52067
|
+
"cachedInputTokens",
|
|
52068
|
+
"cacheHitInputTokens",
|
|
52069
|
+
"cacheHitRateDenominatorTokens",
|
|
52070
|
+
"cacheReadInputTokens",
|
|
52071
|
+
"cacheCreationInputTokens",
|
|
52072
|
+
"cacheCreationInputTokens5m",
|
|
52073
|
+
"cacheCreationInputTokens1h",
|
|
52074
|
+
"cacheWriteInputTokens",
|
|
52075
|
+
"cacheMissInputTokens",
|
|
52076
|
+
"reasoningTokens",
|
|
52077
|
+
"audioInputTokens",
|
|
52078
|
+
"audioOutputTokens",
|
|
52079
|
+
"textInputTokens",
|
|
52080
|
+
"textOutputTokens",
|
|
52081
|
+
"acceptedPredictionTokens",
|
|
52082
|
+
"rejectedPredictionTokens",
|
|
52083
|
+
"toolUsePromptTokens",
|
|
52084
|
+
"billableInputTokens",
|
|
52085
|
+
"billableOutputTokens",
|
|
52086
|
+
"creditUsage",
|
|
52087
|
+
"cost"
|
|
52088
|
+
];
|
|
52089
|
+
for (const key2 of numericKeys) {
|
|
52090
|
+
const value = mergeNumber(current[key2], incoming[key2], key2 === "cost" || key2 === "creditUsage" ? "latest" : "max");
|
|
52091
|
+
merged[key2] = value;
|
|
52092
|
+
}
|
|
52093
|
+
if (incoming.cacheHitRate !== null && (current.cacheHitRate === null || (incoming.cacheHitRateDenominatorTokens ?? 0) >= (current.cacheHitRateDenominatorTokens ?? 0))) {
|
|
52094
|
+
merged.cacheHitRate = incoming.cacheHitRate;
|
|
52095
|
+
merged.cacheHitRateFormula = incoming.cacheHitRateFormula;
|
|
52096
|
+
}
|
|
52097
|
+
if (merged.cacheHitInputTokens !== null && merged.cacheHitRateDenominatorTokens !== null) {
|
|
52098
|
+
merged.cacheHitRate = roundPercent(merged.cacheHitInputTokens, merged.cacheHitRateDenominatorTokens);
|
|
51944
52099
|
}
|
|
51945
|
-
if (
|
|
51946
|
-
|
|
52100
|
+
if (merged.totalTokens === null && merged.outputTokens !== null) {
|
|
52101
|
+
const effectiveInputTokens = merged.cacheHitRateDenominatorTokens ?? merged.inputTokens;
|
|
52102
|
+
if (effectiveInputTokens !== null) {
|
|
52103
|
+
merged.totalTokens = effectiveInputTokens + merged.outputTokens;
|
|
52104
|
+
merged.warnings.push(merged.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
52105
|
+
merged.warnings = Array.from(new Set(merged.warnings));
|
|
52106
|
+
}
|
|
51947
52107
|
}
|
|
51948
|
-
|
|
51949
|
-
const series = buckets.map((bucket, index) => ({
|
|
51950
|
-
ts: new Date(fromMs + index * bucketMs).toISOString(),
|
|
51951
|
-
requests: bucket.requests,
|
|
51952
|
-
errors: bucket.errors,
|
|
51953
|
-
avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
|
|
51954
|
-
}));
|
|
51955
|
-
const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
|
|
51956
|
-
key: key2,
|
|
51957
|
-
requests: row.requests,
|
|
51958
|
-
errorRate: toPercent(row.errors, row.requests),
|
|
51959
|
-
avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
|
|
51960
|
-
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
51961
|
-
const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
|
|
51962
|
-
key: key2,
|
|
51963
|
-
requests: row.requests,
|
|
51964
|
-
errorRate: toPercent(row.errors, row.requests)
|
|
51965
|
-
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
51966
|
-
const response = {
|
|
51967
|
-
window: window2,
|
|
51968
|
-
from: new Date(fromMs).toISOString(),
|
|
51969
|
-
to: new Date(nowMs).toISOString(),
|
|
51970
|
-
generatedAt: new Date(nowMs).toISOString(),
|
|
51971
|
-
source: {
|
|
51972
|
-
logEnabled: true,
|
|
51973
|
-
baseDir,
|
|
51974
|
-
filesScanned,
|
|
51975
|
-
linesScanned,
|
|
51976
|
-
partial: partial2
|
|
51977
|
-
},
|
|
51978
|
-
summary: {
|
|
51979
|
-
totalRequests,
|
|
51980
|
-
successRequests,
|
|
51981
|
-
errorRequests,
|
|
51982
|
-
successRate: toPercent(successRequests, totalRequests),
|
|
51983
|
-
avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
|
|
51984
|
-
p95LatencyMs: percentile(latencies, 0.95),
|
|
51985
|
-
totalRequestBytes,
|
|
51986
|
-
totalResponseBytes
|
|
51987
|
-
},
|
|
51988
|
-
series,
|
|
51989
|
-
topProviders,
|
|
51990
|
-
topRouteTypes,
|
|
51991
|
-
statusClasses,
|
|
51992
|
-
warnings
|
|
51993
|
-
};
|
|
51994
|
-
metricsCache.set(cacheKey, {
|
|
51995
|
-
expiresAt: nowMs + CACHE_TTL_MS,
|
|
51996
|
-
value: response
|
|
51997
|
-
});
|
|
51998
|
-
return response;
|
|
52108
|
+
return merged;
|
|
51999
52109
|
}
|
|
52000
|
-
|
|
52001
|
-
|
|
52002
|
-
|
|
52003
|
-
import { join as join7, resolve as resolve6 } from "path";
|
|
52004
|
-
import { createInterface as createInterface2 } from "readline";
|
|
52005
|
-
|
|
52006
|
-
// src/log-index.ts
|
|
52007
|
-
import { Database } from "bun:sqlite";
|
|
52008
|
-
import {
|
|
52009
|
-
closeSync,
|
|
52010
|
-
createReadStream as createReadStream2,
|
|
52011
|
-
existsSync as existsSync5,
|
|
52012
|
-
mkdirSync as mkdirSync3,
|
|
52013
|
-
openSync,
|
|
52014
|
-
readSync,
|
|
52015
|
-
statSync as statSync2
|
|
52016
|
-
} from "fs";
|
|
52017
|
-
import { join as join6 } from "path";
|
|
52018
|
-
|
|
52019
|
-
// src/log-session-identity.ts
|
|
52020
|
-
var USER_SESSION_DELIMITER = "_account__session_";
|
|
52021
|
-
function toRecord(value) {
|
|
52022
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52023
|
-
return null;
|
|
52024
|
-
return value;
|
|
52110
|
+
function toTokenUsageSummary(metrics) {
|
|
52111
|
+
const { rawUsage: _rawUsage, ...summary } = metrics;
|
|
52112
|
+
return summary;
|
|
52025
52113
|
}
|
|
52026
|
-
function
|
|
52027
|
-
|
|
52028
|
-
const
|
|
52029
|
-
|
|
52030
|
-
|
|
52031
|
-
|
|
52032
|
-
|
|
52033
|
-
|
|
52034
|
-
|
|
52035
|
-
|
|
52036
|
-
|
|
52037
|
-
return {
|
|
52038
|
-
hasMetadata: true,
|
|
52039
|
-
userIdRaw: null
|
|
52040
|
-
};
|
|
52114
|
+
function extractTokenUsageFromJson(value, options) {
|
|
52115
|
+
let merged = null;
|
|
52116
|
+
const candidates = collectUsageCandidates(value, options.rawUsagePathPrefix ?? "");
|
|
52117
|
+
for (const candidate of candidates) {
|
|
52118
|
+
const metrics = normalizeUsageObject({
|
|
52119
|
+
usage: candidate.usage,
|
|
52120
|
+
source: options.source,
|
|
52121
|
+
rawUsagePath: candidate.path,
|
|
52122
|
+
providerHint: options.providerHint
|
|
52123
|
+
});
|
|
52124
|
+
merged = mergeTokenUsageMetrics(merged, metrics);
|
|
52041
52125
|
}
|
|
52042
|
-
return
|
|
52043
|
-
hasMetadata: true,
|
|
52044
|
-
userIdRaw: userId
|
|
52045
|
-
};
|
|
52126
|
+
return merged;
|
|
52046
52127
|
}
|
|
52047
|
-
function
|
|
52048
|
-
|
|
52128
|
+
function extractTokenUsageFromResponseText(text2, source2 = "response_body", providerHint) {
|
|
52129
|
+
if (!text2?.trim())
|
|
52130
|
+
return null;
|
|
52049
52131
|
try {
|
|
52050
|
-
|
|
52132
|
+
return extractTokenUsageFromJson(JSON.parse(text2), { source: source2, providerHint });
|
|
52051
52133
|
} catch {
|
|
52052
52134
|
return null;
|
|
52053
52135
|
}
|
|
52054
|
-
|
|
52136
|
+
}
|
|
52137
|
+
function processSseMessage(dataLines, source2, providerHint) {
|
|
52138
|
+
if (dataLines.length === 0)
|
|
52055
52139
|
return null;
|
|
52056
|
-
const
|
|
52057
|
-
|
|
52058
|
-
if (!
|
|
52140
|
+
const data = dataLines.join(`
|
|
52141
|
+
`).trim();
|
|
52142
|
+
if (!data || data === "[DONE]")
|
|
52059
52143
|
return null;
|
|
52060
|
-
|
|
52061
|
-
|
|
52062
|
-
|
|
52063
|
-
|
|
52064
|
-
|
|
52065
|
-
|
|
52066
|
-
}
|
|
52067
|
-
const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
|
|
52068
|
-
if (index <= 0)
|
|
52144
|
+
try {
|
|
52145
|
+
return extractTokenUsageFromJson(JSON.parse(data), {
|
|
52146
|
+
source: source2,
|
|
52147
|
+
providerHint,
|
|
52148
|
+
rawUsagePathPrefix: source2 === "stream_file" ? "stream" : "stream"
|
|
52149
|
+
});
|
|
52150
|
+
} catch {
|
|
52069
52151
|
return null;
|
|
52070
|
-
|
|
52071
|
-
|
|
52072
|
-
|
|
52152
|
+
}
|
|
52153
|
+
}
|
|
52154
|
+
function extractTokenUsageFromSseText(text2, source2 = "stream_file", providerHint) {
|
|
52155
|
+
if (!text2?.trim())
|
|
52073
52156
|
return null;
|
|
52074
|
-
|
|
52157
|
+
let merged = null;
|
|
52158
|
+
let dataLines = [];
|
|
52159
|
+
const flush = () => {
|
|
52160
|
+
merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source2, providerHint));
|
|
52161
|
+
dataLines = [];
|
|
52162
|
+
};
|
|
52163
|
+
for (const rawLine of text2.split(/\r?\n/)) {
|
|
52164
|
+
if (rawLine === "") {
|
|
52165
|
+
flush();
|
|
52166
|
+
continue;
|
|
52167
|
+
}
|
|
52168
|
+
if (rawLine.startsWith("data:")) {
|
|
52169
|
+
dataLines.push(rawLine.slice(5).trimStart());
|
|
52170
|
+
}
|
|
52171
|
+
}
|
|
52172
|
+
flush();
|
|
52173
|
+
return merged;
|
|
52075
52174
|
}
|
|
52076
|
-
function
|
|
52077
|
-
const
|
|
52078
|
-
|
|
52175
|
+
function createTokenUsageStreamCollector(providerHint) {
|
|
52176
|
+
const decoder = new TextDecoder;
|
|
52177
|
+
let buffer2 = "";
|
|
52178
|
+
let dataLines = [];
|
|
52179
|
+
let latest = null;
|
|
52180
|
+
const flushMessage = () => {
|
|
52181
|
+
latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
|
|
52182
|
+
dataLines = [];
|
|
52183
|
+
};
|
|
52184
|
+
const processLine = (rawLine) => {
|
|
52185
|
+
const line2 = rawLine.replace(/\r$/, "");
|
|
52186
|
+
if (line2 === "") {
|
|
52187
|
+
flushMessage();
|
|
52188
|
+
return;
|
|
52189
|
+
}
|
|
52190
|
+
if (line2.startsWith("data:")) {
|
|
52191
|
+
dataLines.push(line2.slice(5).trimStart());
|
|
52192
|
+
}
|
|
52193
|
+
};
|
|
52194
|
+
return {
|
|
52195
|
+
addChunk(chunk) {
|
|
52196
|
+
buffer2 += decoder.decode(chunk, { stream: true });
|
|
52197
|
+
let newlineIndex = buffer2.indexOf(`
|
|
52198
|
+
`);
|
|
52199
|
+
while (newlineIndex >= 0) {
|
|
52200
|
+
processLine(buffer2.slice(0, newlineIndex));
|
|
52201
|
+
buffer2 = buffer2.slice(newlineIndex + 1);
|
|
52202
|
+
newlineIndex = buffer2.indexOf(`
|
|
52203
|
+
`);
|
|
52204
|
+
}
|
|
52205
|
+
},
|
|
52206
|
+
getUsage() {
|
|
52207
|
+
buffer2 += decoder.decode();
|
|
52208
|
+
if (buffer2) {
|
|
52209
|
+
processLine(buffer2);
|
|
52210
|
+
buffer2 = "";
|
|
52211
|
+
}
|
|
52212
|
+
flushMessage();
|
|
52213
|
+
return latest;
|
|
52214
|
+
}
|
|
52215
|
+
};
|
|
52216
|
+
}
|
|
52217
|
+
function safeReadStreamFile(streamFile, baseDir) {
|
|
52218
|
+
if (!streamFile)
|
|
52219
|
+
return { content: null, warning: null };
|
|
52220
|
+
try {
|
|
52221
|
+
const candidates = [streamFile];
|
|
52222
|
+
if (baseDir)
|
|
52223
|
+
candidates.push(resolve5(baseDir, streamFile));
|
|
52224
|
+
for (const candidate of candidates) {
|
|
52225
|
+
const resolved = resolve5(candidate);
|
|
52226
|
+
if (!resolved.endsWith(".sse.raw"))
|
|
52227
|
+
continue;
|
|
52228
|
+
if (!existsSync3(resolved))
|
|
52229
|
+
continue;
|
|
52230
|
+
const stats = statSync(resolved);
|
|
52231
|
+
if (stats.size > MAX_STREAM_USAGE_BYTES) {
|
|
52232
|
+
return {
|
|
52233
|
+
content: null,
|
|
52234
|
+
warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
|
|
52235
|
+
};
|
|
52236
|
+
}
|
|
52237
|
+
return { content: readFileSync4(resolved, "utf-8"), warning: null };
|
|
52238
|
+
}
|
|
52239
|
+
} catch (err) {
|
|
52079
52240
|
return {
|
|
52080
|
-
|
|
52081
|
-
|
|
52082
|
-
|
|
52083
|
-
|
|
52241
|
+
content: null,
|
|
52242
|
+
warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
52243
|
+
};
|
|
52244
|
+
}
|
|
52245
|
+
return { content: null, warning: null };
|
|
52246
|
+
}
|
|
52247
|
+
function extractTokenUsageFromLogEvent(event, options = {}) {
|
|
52248
|
+
if (event.token_usage) {
|
|
52249
|
+
return {
|
|
52250
|
+
rawUsage: null,
|
|
52251
|
+
...event.token_usage,
|
|
52252
|
+
source: event.token_usage.source ?? "explicit"
|
|
52084
52253
|
};
|
|
52085
52254
|
}
|
|
52086
|
-
const
|
|
52255
|
+
const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
|
|
52256
|
+
const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
|
|
52257
|
+
if (responseBodyUsage)
|
|
52258
|
+
return responseBodyUsage;
|
|
52259
|
+
const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
|
|
52260
|
+
if (responseAfterPluginsUsage)
|
|
52261
|
+
return responseAfterPluginsUsage;
|
|
52262
|
+
const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
|
|
52263
|
+
if (responseBeforePluginsUsage)
|
|
52264
|
+
return responseBeforePluginsUsage;
|
|
52265
|
+
const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
|
|
52266
|
+
return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
|
|
52267
|
+
}
|
|
52268
|
+
function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
|
|
52269
|
+
const metrics = extractTokenUsageFromLogEvent(event, options);
|
|
52270
|
+
return metrics ? toTokenUsageSummary(metrics) : null;
|
|
52271
|
+
}
|
|
52272
|
+
function enrichLogEventTokenUsage(event, options = {}) {
|
|
52273
|
+
if (event.token_usage)
|
|
52274
|
+
return event;
|
|
52275
|
+
const tokenUsage = extractTokenUsageFromLogEvent(event, options);
|
|
52276
|
+
if (!tokenUsage)
|
|
52277
|
+
return event;
|
|
52087
52278
|
return {
|
|
52088
|
-
|
|
52089
|
-
|
|
52090
|
-
userKey: parsed?.userKey ?? null,
|
|
52091
|
-
sessionId: parsed?.sessionId ?? null
|
|
52279
|
+
...event,
|
|
52280
|
+
token_usage: tokenUsage
|
|
52092
52281
|
};
|
|
52093
52282
|
}
|
|
52094
52283
|
|
|
52095
|
-
// src/
|
|
52096
|
-
|
|
52097
|
-
|
|
52098
|
-
|
|
52099
|
-
|
|
52100
|
-
|
|
52101
|
-
|
|
52102
|
-
|
|
52284
|
+
// src/log-metrics.ts
|
|
52285
|
+
var WINDOW_MS = {
|
|
52286
|
+
"1h": 60 * 60 * 1000,
|
|
52287
|
+
"6h": 6 * 60 * 60 * 1000,
|
|
52288
|
+
"24h": 24 * 60 * 60 * 1000
|
|
52289
|
+
};
|
|
52290
|
+
var BUCKET_MS = {
|
|
52291
|
+
"1h": 5 * 60 * 1000,
|
|
52292
|
+
"6h": 15 * 60 * 1000,
|
|
52293
|
+
"24h": 30 * 60 * 1000
|
|
52294
|
+
};
|
|
52295
|
+
var TOP_LIMIT = 5;
|
|
52296
|
+
var MAX_LINES_SCANNED = 250000;
|
|
52297
|
+
var CACHE_TTL_MS = 15000;
|
|
52298
|
+
var metricsCache = new Map;
|
|
52299
|
+
function isLogMetricsWindow(value) {
|
|
52300
|
+
return value === "1h" || value === "6h" || value === "24h";
|
|
52103
52301
|
}
|
|
52104
|
-
function
|
|
52105
|
-
if (
|
|
52106
|
-
return
|
|
52107
|
-
|
|
52108
|
-
const parsed = Number(value);
|
|
52109
|
-
if (Number.isFinite(parsed))
|
|
52110
|
-
return parsed;
|
|
52111
|
-
}
|
|
52112
|
-
return null;
|
|
52302
|
+
function toPercent(numerator, denominator) {
|
|
52303
|
+
if (denominator <= 0)
|
|
52304
|
+
return 0;
|
|
52305
|
+
return Number((numerator / denominator * 100).toFixed(2));
|
|
52113
52306
|
}
|
|
52114
|
-
function
|
|
52115
|
-
|
|
52116
|
-
|
|
52117
|
-
const record2 = asRecord(current);
|
|
52118
|
-
if (!record2 || !(key2 in record2))
|
|
52119
|
-
return null;
|
|
52120
|
-
current = record2[key2];
|
|
52121
|
-
}
|
|
52122
|
-
return numeric(current);
|
|
52307
|
+
function toDayStart(ms) {
|
|
52308
|
+
const date5 = new Date(ms);
|
|
52309
|
+
return Date.UTC(date5.getUTCFullYear(), date5.getUTCMonth(), date5.getUTCDate());
|
|
52123
52310
|
}
|
|
52124
|
-
function
|
|
52125
|
-
|
|
52126
|
-
|
|
52127
|
-
|
|
52128
|
-
return found;
|
|
52311
|
+
function listDateStrings(fromMs, toMs) {
|
|
52312
|
+
const result = [];
|
|
52313
|
+
for (let day = toDayStart(fromMs);day <= toDayStart(toMs); day += 24 * 60 * 60 * 1000) {
|
|
52314
|
+
result.push(new Date(day).toISOString().slice(0, 10));
|
|
52129
52315
|
}
|
|
52130
|
-
return
|
|
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);
|
|
52316
|
+
return result;
|
|
52137
52317
|
}
|
|
52138
|
-
function
|
|
52139
|
-
|
|
52140
|
-
|
|
52141
|
-
|
|
52142
|
-
|
|
52143
|
-
|
|
52144
|
-
|
|
52145
|
-
|
|
52146
|
-
|
|
52147
|
-
|
|
52318
|
+
function getStatusClass(event) {
|
|
52319
|
+
if (event.error_type)
|
|
52320
|
+
return "network_error";
|
|
52321
|
+
const status = event.upstream_status ?? 0;
|
|
52322
|
+
if (status >= 200 && status < 300)
|
|
52323
|
+
return "2xx";
|
|
52324
|
+
if (status >= 400 && status < 500)
|
|
52325
|
+
return "4xx";
|
|
52326
|
+
if (status >= 500)
|
|
52327
|
+
return "5xx";
|
|
52328
|
+
return "network_error";
|
|
52148
52329
|
}
|
|
52149
|
-
function
|
|
52150
|
-
if (
|
|
52151
|
-
return
|
|
52152
|
-
|
|
52330
|
+
function isErrorEvent(event) {
|
|
52331
|
+
if (event.error_type)
|
|
52332
|
+
return true;
|
|
52333
|
+
const status = event.upstream_status ?? 0;
|
|
52334
|
+
return status < 200 || status >= 400;
|
|
52153
52335
|
}
|
|
52154
|
-
function
|
|
52155
|
-
|
|
52156
|
-
|
|
52157
|
-
|
|
52158
|
-
|
|
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";
|
|
52336
|
+
function percentile(sortedNumbers, ratio) {
|
|
52337
|
+
if (sortedNumbers.length === 0)
|
|
52338
|
+
return 0;
|
|
52339
|
+
const index = Math.min(sortedNumbers.length - 1, Math.ceil(sortedNumbers.length * ratio) - 1);
|
|
52340
|
+
return Math.round(sortedNumbers[index]);
|
|
52189
52341
|
}
|
|
52190
|
-
function createEmptyMetrics2(
|
|
52342
|
+
function createEmptyMetrics2(window2, nowMs, source2, warnings = []) {
|
|
52343
|
+
const fromMs = nowMs - WINDOW_MS[window2];
|
|
52344
|
+
const bucketMs = BUCKET_MS[window2];
|
|
52345
|
+
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
52346
|
+
const series = Array.from({ length: bucketCount }, (_, i) => ({
|
|
52347
|
+
ts: new Date(fromMs + i * bucketMs).toISOString(),
|
|
52348
|
+
requests: 0,
|
|
52349
|
+
errors: 0,
|
|
52350
|
+
avgLatencyMs: 0
|
|
52351
|
+
}));
|
|
52191
52352
|
return {
|
|
52192
|
-
|
|
52193
|
-
|
|
52194
|
-
|
|
52195
|
-
|
|
52196
|
-
|
|
52197
|
-
|
|
52198
|
-
|
|
52199
|
-
|
|
52200
|
-
|
|
52201
|
-
|
|
52202
|
-
|
|
52203
|
-
|
|
52204
|
-
|
|
52205
|
-
|
|
52206
|
-
|
|
52207
|
-
|
|
52208
|
-
|
|
52209
|
-
|
|
52210
|
-
|
|
52211
|
-
|
|
52212
|
-
|
|
52213
|
-
|
|
52214
|
-
|
|
52215
|
-
|
|
52216
|
-
|
|
52217
|
-
|
|
52218
|
-
|
|
52219
|
-
|
|
52220
|
-
|
|
52221
|
-
|
|
52222
|
-
|
|
52223
|
-
|
|
52224
|
-
|
|
52225
|
-
|
|
52226
|
-
|
|
52227
|
-
|
|
52228
|
-
|
|
52229
|
-
|
|
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);
|
|
52353
|
+
window: window2,
|
|
52354
|
+
from: new Date(fromMs).toISOString(),
|
|
52355
|
+
to: new Date(nowMs).toISOString(),
|
|
52356
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
52357
|
+
source: source2,
|
|
52358
|
+
summary: {
|
|
52359
|
+
totalRequests: 0,
|
|
52360
|
+
successRequests: 0,
|
|
52361
|
+
errorRequests: 0,
|
|
52362
|
+
successRate: 0,
|
|
52363
|
+
avgLatencyMs: 0,
|
|
52364
|
+
p95LatencyMs: 0,
|
|
52365
|
+
totalRequestBytes: 0,
|
|
52366
|
+
totalResponseBytes: 0
|
|
52367
|
+
},
|
|
52368
|
+
tokens: {
|
|
52369
|
+
usageCount: 0,
|
|
52370
|
+
inputTokens: 0,
|
|
52371
|
+
outputTokens: 0,
|
|
52372
|
+
totalTokens: 0,
|
|
52373
|
+
cachedInputTokens: 0,
|
|
52374
|
+
cacheHitInputTokens: 0,
|
|
52375
|
+
cacheHitRateDenominatorTokens: 0,
|
|
52376
|
+
cacheHitRate: 0,
|
|
52377
|
+
reasoningTokens: 0,
|
|
52378
|
+
cost: null
|
|
52379
|
+
},
|
|
52380
|
+
series,
|
|
52381
|
+
topProviders: [],
|
|
52382
|
+
topRouteTypes: [],
|
|
52383
|
+
statusClasses: {
|
|
52384
|
+
"2xx": 0,
|
|
52385
|
+
"4xx": 0,
|
|
52386
|
+
"5xx": 0,
|
|
52387
|
+
network_error: 0
|
|
52388
|
+
},
|
|
52389
|
+
warnings
|
|
52390
|
+
};
|
|
52242
52391
|
}
|
|
52243
|
-
function
|
|
52244
|
-
const
|
|
52245
|
-
const
|
|
52246
|
-
const
|
|
52247
|
-
|
|
52248
|
-
|
|
52249
|
-
|
|
52250
|
-
|
|
52251
|
-
|
|
52252
|
-
|
|
52253
|
-
|
|
52254
|
-
|
|
52255
|
-
["
|
|
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;
|
|
52392
|
+
async function getLogMetrics(options) {
|
|
52393
|
+
const window2 = options.window ?? "24h";
|
|
52394
|
+
const refresh = options.refresh === true;
|
|
52395
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
52396
|
+
const logEnabled = options.logConfig?.enabled !== false && !!options.logConfig;
|
|
52397
|
+
if (!logEnabled) {
|
|
52398
|
+
return createEmptyMetrics2(window2, nowMs, {
|
|
52399
|
+
logEnabled: false,
|
|
52400
|
+
baseDir: null,
|
|
52401
|
+
filesScanned: 0,
|
|
52402
|
+
linesScanned: 0,
|
|
52403
|
+
partial: false
|
|
52404
|
+
}, ["\u65E5\u5FD7\u672A\u542F\u7528"]);
|
|
52315
52405
|
}
|
|
52316
|
-
|
|
52317
|
-
|
|
52318
|
-
|
|
52319
|
-
|
|
52320
|
-
|
|
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";
|
|
52406
|
+
const baseDir = resolveLogBaseDir(options.logConfig);
|
|
52407
|
+
const cacheKey = `${baseDir}:${window2}`;
|
|
52408
|
+
const cached2 = metricsCache.get(cacheKey);
|
|
52409
|
+
if (!refresh && cached2 && cached2.expiresAt > nowMs) {
|
|
52410
|
+
return cached2.value;
|
|
52380
52411
|
}
|
|
52381
|
-
|
|
52382
|
-
|
|
52383
|
-
|
|
52384
|
-
|
|
52385
|
-
|
|
52386
|
-
|
|
52387
|
-
|
|
52412
|
+
const eventsDir = join5(baseDir, "events");
|
|
52413
|
+
if (!existsSync4(eventsDir)) {
|
|
52414
|
+
const empty = createEmptyMetrics2(window2, nowMs, {
|
|
52415
|
+
logEnabled: true,
|
|
52416
|
+
baseDir,
|
|
52417
|
+
filesScanned: 0,
|
|
52418
|
+
linesScanned: 0,
|
|
52419
|
+
partial: false
|
|
52420
|
+
}, ["\u65E5\u5FD7\u76EE\u5F55\u4E0D\u5B58\u5728\uFF0C\u6682\u65E0\u53EF\u5206\u6790\u6570\u636E"]);
|
|
52421
|
+
metricsCache.set(cacheKey, { expiresAt: nowMs + CACHE_TTL_MS, value: empty });
|
|
52422
|
+
return empty;
|
|
52388
52423
|
}
|
|
52389
|
-
|
|
52390
|
-
|
|
52391
|
-
|
|
52392
|
-
|
|
52393
|
-
|
|
52424
|
+
const fromMs = nowMs - WINDOW_MS[window2];
|
|
52425
|
+
const bucketMs = BUCKET_MS[window2];
|
|
52426
|
+
const bucketCount = Math.max(1, Math.ceil((nowMs - fromMs) / bucketMs));
|
|
52427
|
+
const buckets = Array.from({ length: bucketCount }, () => ({
|
|
52428
|
+
requests: 0,
|
|
52429
|
+
errors: 0,
|
|
52430
|
+
latencySum: 0,
|
|
52431
|
+
latencyCount: 0
|
|
52432
|
+
}));
|
|
52433
|
+
const providerAgg = new Map;
|
|
52434
|
+
const routeTypeAgg = new Map;
|
|
52435
|
+
const latencies = [];
|
|
52436
|
+
const statusClasses = {
|
|
52437
|
+
"2xx": 0,
|
|
52438
|
+
"4xx": 0,
|
|
52439
|
+
"5xx": 0,
|
|
52440
|
+
network_error: 0
|
|
52441
|
+
};
|
|
52442
|
+
let filesScanned = 0;
|
|
52443
|
+
let linesScanned = 0;
|
|
52444
|
+
let parseErrors = 0;
|
|
52445
|
+
let partial2 = false;
|
|
52446
|
+
let totalRequests = 0;
|
|
52447
|
+
let successRequests = 0;
|
|
52448
|
+
let errorRequests = 0;
|
|
52449
|
+
let totalLatency = 0;
|
|
52450
|
+
let totalRequestBytes = 0;
|
|
52451
|
+
let totalResponseBytes = 0;
|
|
52452
|
+
let tokenUsageCount = 0;
|
|
52453
|
+
let tokenInput = 0;
|
|
52454
|
+
let tokenOutput = 0;
|
|
52455
|
+
let tokenTotal = 0;
|
|
52456
|
+
let tokenCachedInput = 0;
|
|
52457
|
+
let tokenCacheHitInput = 0;
|
|
52458
|
+
let tokenCacheHitDenominator = 0;
|
|
52459
|
+
let tokenReasoning = 0;
|
|
52460
|
+
let tokenCost = 0;
|
|
52461
|
+
let tokenCostSeen = false;
|
|
52462
|
+
const warnings = [];
|
|
52463
|
+
const dateStrings = listDateStrings(fromMs, nowMs);
|
|
52464
|
+
for (const dateStr of dateStrings) {
|
|
52465
|
+
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
52466
|
+
partial2 = true;
|
|
52467
|
+
break;
|
|
52468
|
+
}
|
|
52469
|
+
const filePath = join5(eventsDir, `${dateStr}.jsonl`);
|
|
52470
|
+
if (!existsSync4(filePath))
|
|
52471
|
+
continue;
|
|
52472
|
+
filesScanned += 1;
|
|
52473
|
+
try {
|
|
52474
|
+
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
52475
|
+
const rl = createInterface({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
52476
|
+
for await (const line2 of rl) {
|
|
52477
|
+
if (linesScanned >= MAX_LINES_SCANNED) {
|
|
52478
|
+
partial2 = true;
|
|
52479
|
+
rl.close();
|
|
52480
|
+
stream.destroy();
|
|
52481
|
+
break;
|
|
52482
|
+
}
|
|
52483
|
+
linesScanned += 1;
|
|
52484
|
+
if (!line2.trim())
|
|
52485
|
+
continue;
|
|
52486
|
+
let event;
|
|
52487
|
+
try {
|
|
52488
|
+
event = JSON.parse(line2);
|
|
52489
|
+
} catch {
|
|
52490
|
+
parseErrors += 1;
|
|
52491
|
+
continue;
|
|
52492
|
+
}
|
|
52493
|
+
if (!event.ts_start)
|
|
52494
|
+
continue;
|
|
52495
|
+
const ts = Date.parse(event.ts_start);
|
|
52496
|
+
if (!Number.isFinite(ts) || ts < fromMs || ts > nowMs)
|
|
52497
|
+
continue;
|
|
52498
|
+
totalRequests += 1;
|
|
52499
|
+
const isError = isErrorEvent(event);
|
|
52500
|
+
if (isError) {
|
|
52501
|
+
errorRequests += 1;
|
|
52502
|
+
} else {
|
|
52503
|
+
successRequests += 1;
|
|
52504
|
+
}
|
|
52505
|
+
const latency = Number.isFinite(event.latency_ms) ? Math.max(0, event.latency_ms ?? 0) : 0;
|
|
52506
|
+
totalLatency += latency;
|
|
52507
|
+
latencies.push(latency);
|
|
52508
|
+
totalRequestBytes += Math.max(0, event.request_bytes ?? 0);
|
|
52509
|
+
totalResponseBytes += Math.max(0, event.response_bytes ?? 0) + Math.max(0, event.stream_bytes ?? 0);
|
|
52510
|
+
const bucketIndex = Math.min(bucketCount - 1, Math.max(0, Math.floor((ts - fromMs) / bucketMs)));
|
|
52511
|
+
const bucket = buckets[bucketIndex];
|
|
52512
|
+
bucket.requests += 1;
|
|
52513
|
+
bucket.latencySum += latency;
|
|
52514
|
+
bucket.latencyCount += 1;
|
|
52515
|
+
if (isError)
|
|
52516
|
+
bucket.errors += 1;
|
|
52517
|
+
const providerKey = event.provider || "unknown";
|
|
52518
|
+
const providerRow = providerAgg.get(providerKey) ?? {
|
|
52519
|
+
requests: 0,
|
|
52520
|
+
errors: 0,
|
|
52521
|
+
latencySum: 0
|
|
52522
|
+
};
|
|
52523
|
+
providerRow.requests += 1;
|
|
52524
|
+
providerRow.latencySum += latency;
|
|
52525
|
+
if (isError)
|
|
52526
|
+
providerRow.errors += 1;
|
|
52527
|
+
providerAgg.set(providerKey, providerRow);
|
|
52528
|
+
const routeTypeKey = event.route_type || "unknown";
|
|
52529
|
+
const routeTypeRow = routeTypeAgg.get(routeTypeKey) ?? {
|
|
52530
|
+
requests: 0,
|
|
52531
|
+
errors: 0,
|
|
52532
|
+
latencySum: 0
|
|
52533
|
+
};
|
|
52534
|
+
routeTypeRow.requests += 1;
|
|
52535
|
+
routeTypeRow.latencySum += latency;
|
|
52536
|
+
if (isError)
|
|
52537
|
+
routeTypeRow.errors += 1;
|
|
52538
|
+
routeTypeAgg.set(routeTypeKey, routeTypeRow);
|
|
52539
|
+
statusClasses[getStatusClass(event)] += 1;
|
|
52540
|
+
const usage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
|
|
52541
|
+
if (usage) {
|
|
52542
|
+
tokenUsageCount += 1;
|
|
52543
|
+
tokenInput += Math.max(0, usage.inputTokens ?? 0);
|
|
52544
|
+
tokenOutput += Math.max(0, usage.outputTokens ?? 0);
|
|
52545
|
+
tokenTotal += Math.max(0, usage.totalTokens ?? 0);
|
|
52546
|
+
tokenCachedInput += Math.max(0, usage.cachedInputTokens ?? 0);
|
|
52547
|
+
tokenCacheHitInput += Math.max(0, usage.cacheHitInputTokens ?? 0);
|
|
52548
|
+
tokenCacheHitDenominator += Math.max(0, usage.cacheHitRateDenominatorTokens ?? 0);
|
|
52549
|
+
tokenReasoning += Math.max(0, usage.reasoningTokens ?? 0);
|
|
52550
|
+
if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
|
|
52551
|
+
tokenCost += usage.cost;
|
|
52552
|
+
tokenCostSeen = true;
|
|
52553
|
+
}
|
|
52554
|
+
}
|
|
52555
|
+
}
|
|
52556
|
+
} catch (err) {
|
|
52557
|
+
warnings.push(`\u8BFB\u53D6\u65E5\u5FD7\u6587\u4EF6\u5931\u8D25: ${filePath} (${err instanceof Error ? err.message : String(err)})`);
|
|
52558
|
+
partial2 = true;
|
|
52394
52559
|
}
|
|
52395
52560
|
}
|
|
52396
|
-
|
|
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
|
-
}
|
|
52561
|
+
if (parseErrors > 0) {
|
|
52562
|
+
warnings.push(`\u5DF2\u8DF3\u8FC7 ${parseErrors} \u884C\u65E0\u6548 JSON \u65E5\u5FD7`);
|
|
52423
52563
|
}
|
|
52424
|
-
|
|
52425
|
-
|
|
52426
|
-
source: "response_body",
|
|
52427
|
-
rawUsagePath: prefix || null
|
|
52428
|
-
});
|
|
52429
|
-
if (direct) {
|
|
52430
|
-
candidates.push({ usage: record2, path: prefix || "$" });
|
|
52564
|
+
if (partial2) {
|
|
52565
|
+
warnings.push("\u65E5\u5FD7\u626B\u63CF\u5DF2\u90E8\u5206\u622A\u65AD\uFF0C\u7ED3\u679C\u53EF\u80FD\u4E0D\u5B8C\u6574");
|
|
52431
52566
|
}
|
|
52432
|
-
|
|
52433
|
-
|
|
52434
|
-
|
|
52435
|
-
|
|
52436
|
-
|
|
52437
|
-
|
|
52438
|
-
|
|
52439
|
-
|
|
52440
|
-
|
|
52441
|
-
|
|
52442
|
-
|
|
52443
|
-
|
|
52444
|
-
|
|
52445
|
-
|
|
52446
|
-
|
|
52447
|
-
|
|
52448
|
-
|
|
52449
|
-
|
|
52450
|
-
|
|
52451
|
-
|
|
52452
|
-
|
|
52567
|
+
latencies.sort((a, b) => a - b);
|
|
52568
|
+
const series = buckets.map((bucket, index) => ({
|
|
52569
|
+
ts: new Date(fromMs + index * bucketMs).toISOString(),
|
|
52570
|
+
requests: bucket.requests,
|
|
52571
|
+
errors: bucket.errors,
|
|
52572
|
+
avgLatencyMs: bucket.latencyCount > 0 ? Math.round(bucket.latencySum / bucket.latencyCount) : 0
|
|
52573
|
+
}));
|
|
52574
|
+
const topProviders = Array.from(providerAgg.entries()).map(([key2, row]) => ({
|
|
52575
|
+
key: key2,
|
|
52576
|
+
requests: row.requests,
|
|
52577
|
+
errorRate: toPercent(row.errors, row.requests),
|
|
52578
|
+
avgLatencyMs: row.requests > 0 ? Math.round(row.latencySum / row.requests) : 0
|
|
52579
|
+
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
52580
|
+
const topRouteTypes = Array.from(routeTypeAgg.entries()).map(([key2, row]) => ({
|
|
52581
|
+
key: key2,
|
|
52582
|
+
requests: row.requests,
|
|
52583
|
+
errorRate: toPercent(row.errors, row.requests)
|
|
52584
|
+
})).sort((a, b) => b.requests - a.requests).slice(0, TOP_LIMIT);
|
|
52585
|
+
const response = {
|
|
52586
|
+
window: window2,
|
|
52587
|
+
from: new Date(fromMs).toISOString(),
|
|
52588
|
+
to: new Date(nowMs).toISOString(),
|
|
52589
|
+
generatedAt: new Date(nowMs).toISOString(),
|
|
52590
|
+
source: {
|
|
52591
|
+
logEnabled: true,
|
|
52592
|
+
baseDir,
|
|
52593
|
+
filesScanned,
|
|
52594
|
+
linesScanned,
|
|
52595
|
+
partial: partial2
|
|
52596
|
+
},
|
|
52597
|
+
summary: {
|
|
52598
|
+
totalRequests,
|
|
52599
|
+
successRequests,
|
|
52600
|
+
errorRequests,
|
|
52601
|
+
successRate: toPercent(successRequests, totalRequests),
|
|
52602
|
+
avgLatencyMs: totalRequests > 0 ? Math.round(totalLatency / totalRequests) : 0,
|
|
52603
|
+
p95LatencyMs: percentile(latencies, 0.95),
|
|
52604
|
+
totalRequestBytes,
|
|
52605
|
+
totalResponseBytes
|
|
52606
|
+
},
|
|
52607
|
+
tokens: {
|
|
52608
|
+
usageCount: tokenUsageCount,
|
|
52609
|
+
inputTokens: tokenInput,
|
|
52610
|
+
outputTokens: tokenOutput,
|
|
52611
|
+
totalTokens: tokenTotal,
|
|
52612
|
+
cachedInputTokens: tokenCachedInput,
|
|
52613
|
+
cacheHitInputTokens: tokenCacheHitInput,
|
|
52614
|
+
cacheHitRateDenominatorTokens: tokenCacheHitDenominator,
|
|
52615
|
+
cacheHitRate: toPercent(tokenCacheHitInput, tokenCacheHitDenominator),
|
|
52616
|
+
reasoningTokens: tokenReasoning,
|
|
52617
|
+
cost: tokenCostSeen ? Number(tokenCost.toFixed(6)) : null
|
|
52618
|
+
},
|
|
52619
|
+
series,
|
|
52620
|
+
topProviders,
|
|
52621
|
+
topRouteTypes,
|
|
52622
|
+
statusClasses,
|
|
52623
|
+
warnings
|
|
52453
52624
|
};
|
|
52454
|
-
|
|
52455
|
-
|
|
52456
|
-
|
|
52457
|
-
|
|
52458
|
-
|
|
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;
|
|
52625
|
+
metricsCache.set(cacheKey, {
|
|
52626
|
+
expiresAt: nowMs + CACHE_TTL_MS,
|
|
52627
|
+
value: response
|
|
52628
|
+
});
|
|
52629
|
+
return response;
|
|
52500
52630
|
}
|
|
52501
|
-
|
|
52502
|
-
|
|
52503
|
-
|
|
52631
|
+
|
|
52632
|
+
// src/log-query.ts
|
|
52633
|
+
import { createReadStream as createReadStream3, existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
52634
|
+
import { join as join7, resolve as resolve6 } from "path";
|
|
52635
|
+
import { createInterface as createInterface2 } from "readline";
|
|
52636
|
+
|
|
52637
|
+
// src/log-index.ts
|
|
52638
|
+
import { Database } from "bun:sqlite";
|
|
52639
|
+
import {
|
|
52640
|
+
closeSync,
|
|
52641
|
+
createReadStream as createReadStream2,
|
|
52642
|
+
existsSync as existsSync5,
|
|
52643
|
+
mkdirSync as mkdirSync3,
|
|
52644
|
+
openSync,
|
|
52645
|
+
readSync,
|
|
52646
|
+
statSync as statSync2
|
|
52647
|
+
} from "fs";
|
|
52648
|
+
import { join as join6 } from "path";
|
|
52649
|
+
|
|
52650
|
+
// src/log-session-identity.ts
|
|
52651
|
+
var USER_SESSION_DELIMITER = "_account__session_";
|
|
52652
|
+
function toRecord(value) {
|
|
52653
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
52654
|
+
return null;
|
|
52655
|
+
return value;
|
|
52504
52656
|
}
|
|
52505
|
-
function
|
|
52506
|
-
|
|
52507
|
-
const
|
|
52508
|
-
|
|
52509
|
-
|
|
52510
|
-
|
|
52511
|
-
|
|
52512
|
-
|
|
52513
|
-
providerHint: options.providerHint
|
|
52514
|
-
});
|
|
52515
|
-
merged = mergeTokenUsageMetrics(merged, metrics);
|
|
52657
|
+
function extractUserIdRawFromRequestBody(requestBody) {
|
|
52658
|
+
const requestBodyRecord = toRecord(requestBody);
|
|
52659
|
+
const metadata = toRecord(requestBodyRecord?.metadata);
|
|
52660
|
+
if (!metadata) {
|
|
52661
|
+
return {
|
|
52662
|
+
hasMetadata: false,
|
|
52663
|
+
userIdRaw: null
|
|
52664
|
+
};
|
|
52516
52665
|
}
|
|
52517
|
-
|
|
52518
|
-
|
|
52519
|
-
|
|
52520
|
-
|
|
52521
|
-
|
|
52522
|
-
|
|
52523
|
-
return extractTokenUsageFromJson(JSON.parse(text2), { source: source2, providerHint });
|
|
52524
|
-
} catch {
|
|
52525
|
-
return null;
|
|
52666
|
+
const userId = metadata.user_id;
|
|
52667
|
+
if (typeof userId !== "string" || userId.trim() === "") {
|
|
52668
|
+
return {
|
|
52669
|
+
hasMetadata: true,
|
|
52670
|
+
userIdRaw: null
|
|
52671
|
+
};
|
|
52526
52672
|
}
|
|
52673
|
+
return {
|
|
52674
|
+
hasMetadata: true,
|
|
52675
|
+
userIdRaw: userId
|
|
52676
|
+
};
|
|
52527
52677
|
}
|
|
52528
|
-
function
|
|
52529
|
-
|
|
52530
|
-
return null;
|
|
52531
|
-
const data = dataLines.join(`
|
|
52532
|
-
`).trim();
|
|
52533
|
-
if (!data || data === "[DONE]")
|
|
52534
|
-
return null;
|
|
52678
|
+
function parseUserSessionFromJsonFormat(userIdRaw) {
|
|
52679
|
+
let parsed;
|
|
52535
52680
|
try {
|
|
52536
|
-
|
|
52537
|
-
source: source2,
|
|
52538
|
-
providerHint,
|
|
52539
|
-
rawUsagePathPrefix: source2 === "stream_file" ? "stream" : "stream"
|
|
52540
|
-
});
|
|
52681
|
+
parsed = JSON.parse(userIdRaw);
|
|
52541
52682
|
} catch {
|
|
52542
52683
|
return null;
|
|
52543
52684
|
}
|
|
52544
|
-
|
|
52545
|
-
function extractTokenUsageFromSseText(text2, source2 = "stream_file", providerHint) {
|
|
52546
|
-
if (!text2?.trim())
|
|
52685
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
52547
52686
|
return null;
|
|
52548
|
-
|
|
52549
|
-
|
|
52550
|
-
|
|
52551
|
-
|
|
52552
|
-
|
|
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
|
-
};
|
|
52687
|
+
const obj = parsed;
|
|
52688
|
+
const sessionId = typeof obj.session_id === "string" ? obj.session_id.trim() : "";
|
|
52689
|
+
if (!sessionId)
|
|
52690
|
+
return null;
|
|
52691
|
+
const userKey = (typeof obj.account_uuid === "string" ? obj.account_uuid.trim() : "") || (typeof obj.device_id === "string" ? obj.device_id.trim() : "");
|
|
52692
|
+
return { userKey: userKey || sessionId, sessionId };
|
|
52607
52693
|
}
|
|
52608
|
-
function
|
|
52609
|
-
if (
|
|
52610
|
-
return
|
|
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
|
-
};
|
|
52694
|
+
function parseUserSessionFromUserIdRaw(userIdRaw) {
|
|
52695
|
+
if (userIdRaw.trimStart().startsWith("{")) {
|
|
52696
|
+
return parseUserSessionFromJsonFormat(userIdRaw);
|
|
52635
52697
|
}
|
|
52636
|
-
|
|
52698
|
+
const index = userIdRaw.indexOf(USER_SESSION_DELIMITER);
|
|
52699
|
+
if (index <= 0)
|
|
52700
|
+
return null;
|
|
52701
|
+
const userKey = userIdRaw.slice(0, index).trim();
|
|
52702
|
+
const sessionId = userIdRaw.slice(index + USER_SESSION_DELIMITER.length).trim();
|
|
52703
|
+
if (!userKey || !sessionId)
|
|
52704
|
+
return null;
|
|
52705
|
+
return { userKey, sessionId };
|
|
52637
52706
|
}
|
|
52638
|
-
function
|
|
52639
|
-
|
|
52707
|
+
function resolveLogSessionIdentity(requestBody) {
|
|
52708
|
+
const { hasMetadata, userIdRaw } = extractUserIdRawFromRequestBody(requestBody);
|
|
52709
|
+
if (!userIdRaw) {
|
|
52640
52710
|
return {
|
|
52641
|
-
|
|
52642
|
-
|
|
52643
|
-
|
|
52711
|
+
hasMetadata,
|
|
52712
|
+
userIdRaw: null,
|
|
52713
|
+
userKey: null,
|
|
52714
|
+
sessionId: null
|
|
52644
52715
|
};
|
|
52645
52716
|
}
|
|
52646
|
-
const
|
|
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;
|
|
52717
|
+
const parsed = parseUserSessionFromUserIdRaw(userIdRaw);
|
|
52669
52718
|
return {
|
|
52670
|
-
|
|
52671
|
-
|
|
52719
|
+
hasMetadata,
|
|
52720
|
+
userIdRaw,
|
|
52721
|
+
userKey: parsed?.userKey ?? null,
|
|
52722
|
+
sessionId: parsed?.sessionId ?? null
|
|
52672
52723
|
};
|
|
52673
52724
|
}
|
|
52674
52725
|
|
|
@@ -53059,6 +53110,31 @@ function buildWhereClause(query, options = {}) {
|
|
|
53059
53110
|
usesFts
|
|
53060
53111
|
};
|
|
53061
53112
|
}
|
|
53113
|
+
function buildSessionsWhereClause(query) {
|
|
53114
|
+
const pseudo = {
|
|
53115
|
+
fromMs: query.fromMs,
|
|
53116
|
+
toMs: query.toMs,
|
|
53117
|
+
levels: [],
|
|
53118
|
+
providers: [],
|
|
53119
|
+
routeTypes: [],
|
|
53120
|
+
models: [],
|
|
53121
|
+
modelIns: [],
|
|
53122
|
+
modelOuts: [],
|
|
53123
|
+
users: query.users,
|
|
53124
|
+
sessions: query.sessions,
|
|
53125
|
+
statusClasses: [],
|
|
53126
|
+
hasError: null,
|
|
53127
|
+
q: query.q,
|
|
53128
|
+
sort: "time_desc",
|
|
53129
|
+
limit: 1,
|
|
53130
|
+
cursor: null
|
|
53131
|
+
};
|
|
53132
|
+
const { whereSql, params } = buildWhereClause(pseudo);
|
|
53133
|
+
return { whereSql, params };
|
|
53134
|
+
}
|
|
53135
|
+
function sortIndexedCountItems(map2) {
|
|
53136
|
+
return Array.from(map2.entries()).map(([key2, count]) => ({ key: key2, count })).sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
53137
|
+
}
|
|
53062
53138
|
|
|
53063
53139
|
class LogIndex {
|
|
53064
53140
|
baseDir;
|
|
@@ -53266,6 +53342,163 @@ class LogIndex {
|
|
|
53266
53342
|
}
|
|
53267
53343
|
};
|
|
53268
53344
|
}
|
|
53345
|
+
querySessions(query) {
|
|
53346
|
+
const startedAt = performance.now();
|
|
53347
|
+
const { whereSql, params } = buildSessionsWhereClause(query);
|
|
53348
|
+
const aggregatedWhere = `${whereSql} AND e.user_key IS NOT NULL AND e.session_id IS NOT NULL`;
|
|
53349
|
+
const summaryRow = this.db.query(`
|
|
53350
|
+
SELECT
|
|
53351
|
+
COUNT(*) AS totalRequests,
|
|
53352
|
+
COALESCE(SUM(has_metadata), 0) AS metadataRequests,
|
|
53353
|
+
COUNT(DISTINCT user_key) AS uniqueUsers,
|
|
53354
|
+
COUNT(DISTINCT CASE
|
|
53355
|
+
WHEN user_key IS NOT NULL AND session_id IS NOT NULL
|
|
53356
|
+
THEN user_key || ' ' || session_id
|
|
53357
|
+
END) AS uniqueSessions
|
|
53358
|
+
FROM log_events e
|
|
53359
|
+
${whereSql}
|
|
53360
|
+
`).get(...params);
|
|
53361
|
+
const userRows = this.db.query(`
|
|
53362
|
+
SELECT
|
|
53363
|
+
user_key AS userKey,
|
|
53364
|
+
COUNT(*) AS requestCount,
|
|
53365
|
+
MIN(ts_ms) AS firstMs,
|
|
53366
|
+
MAX(ts_ms) AS lastMs,
|
|
53367
|
+
COUNT(DISTINCT session_id) AS sessionCount
|
|
53368
|
+
FROM log_events e
|
|
53369
|
+
${aggregatedWhere}
|
|
53370
|
+
GROUP BY user_key
|
|
53371
|
+
`).all(...params);
|
|
53372
|
+
const sessionRows = this.db.query(`
|
|
53373
|
+
SELECT
|
|
53374
|
+
user_key AS userKey,
|
|
53375
|
+
session_id AS sessionId,
|
|
53376
|
+
COUNT(*) AS requestCount,
|
|
53377
|
+
MIN(ts_ms) AS firstMs,
|
|
53378
|
+
MAX(ts_ms) AS lastMs
|
|
53379
|
+
FROM log_events e
|
|
53380
|
+
${aggregatedWhere}
|
|
53381
|
+
GROUP BY user_key, session_id
|
|
53382
|
+
`).all(...params);
|
|
53383
|
+
const userModelRows = this.db.query(`
|
|
53384
|
+
SELECT user_key AS userKey, model AS key, COUNT(*) AS count
|
|
53385
|
+
FROM log_events e
|
|
53386
|
+
${aggregatedWhere}
|
|
53387
|
+
GROUP BY user_key, model
|
|
53388
|
+
`).all(...params);
|
|
53389
|
+
const userProviderRows = this.db.query(`
|
|
53390
|
+
SELECT user_key AS userKey, provider AS key, COUNT(*) AS count
|
|
53391
|
+
FROM log_events e
|
|
53392
|
+
${aggregatedWhere}
|
|
53393
|
+
GROUP BY user_key, provider
|
|
53394
|
+
`).all(...params);
|
|
53395
|
+
const userRouteRows = this.db.query(`
|
|
53396
|
+
SELECT user_key AS userKey, route_type AS key, COUNT(*) AS count
|
|
53397
|
+
FROM log_events e
|
|
53398
|
+
${aggregatedWhere}
|
|
53399
|
+
GROUP BY user_key, route_type
|
|
53400
|
+
`).all(...params);
|
|
53401
|
+
const sessionModelRows = this.db.query(`
|
|
53402
|
+
SELECT user_key AS userKey, session_id AS sessionId, model AS key, COUNT(*) AS count
|
|
53403
|
+
FROM log_events e
|
|
53404
|
+
${aggregatedWhere}
|
|
53405
|
+
GROUP BY user_key, session_id, model
|
|
53406
|
+
`).all(...params);
|
|
53407
|
+
const latestRows = this.db.query(`
|
|
53408
|
+
SELECT userKey, sessionId, request_id AS latestRequestId
|
|
53409
|
+
FROM (
|
|
53410
|
+
SELECT
|
|
53411
|
+
user_key AS userKey,
|
|
53412
|
+
session_id AS sessionId,
|
|
53413
|
+
request_id,
|
|
53414
|
+
ROW_NUMBER() OVER (
|
|
53415
|
+
PARTITION BY user_key, session_id ORDER BY ts_ms DESC, id DESC
|
|
53416
|
+
) AS rn
|
|
53417
|
+
FROM log_events e
|
|
53418
|
+
${aggregatedWhere}
|
|
53419
|
+
)
|
|
53420
|
+
WHERE rn = 1
|
|
53421
|
+
`).all(...params);
|
|
53422
|
+
const userModels = new Map;
|
|
53423
|
+
const userProviders = new Map;
|
|
53424
|
+
const userRoutes = new Map;
|
|
53425
|
+
const sessionModels = new Map;
|
|
53426
|
+
const latestBySession = new Map;
|
|
53427
|
+
const addCount = (target, groupKey, key2, count) => {
|
|
53428
|
+
if (!key2)
|
|
53429
|
+
return;
|
|
53430
|
+
let inner = target.get(groupKey);
|
|
53431
|
+
if (!inner) {
|
|
53432
|
+
inner = new Map;
|
|
53433
|
+
target.set(groupKey, inner);
|
|
53434
|
+
}
|
|
53435
|
+
inner.set(key2, count);
|
|
53436
|
+
};
|
|
53437
|
+
for (const row of userModelRows)
|
|
53438
|
+
addCount(userModels, row.userKey, row.key, row.count);
|
|
53439
|
+
for (const row of userProviderRows)
|
|
53440
|
+
addCount(userProviders, row.userKey, row.key, row.count);
|
|
53441
|
+
for (const row of userRouteRows)
|
|
53442
|
+
addCount(userRoutes, row.userKey, row.key, row.count);
|
|
53443
|
+
for (const row of sessionModelRows) {
|
|
53444
|
+
addCount(sessionModels, `${row.userKey}\x00${row.sessionId}`, row.key, row.count);
|
|
53445
|
+
}
|
|
53446
|
+
for (const row of latestRows) {
|
|
53447
|
+
latestBySession.set(`${row.userKey}\x00${row.sessionId}`, row.latestRequestId);
|
|
53448
|
+
}
|
|
53449
|
+
const sessionsByUser = new Map;
|
|
53450
|
+
for (const row of sessionRows) {
|
|
53451
|
+
const sessionKey = `${row.userKey}\x00${row.sessionId}`;
|
|
53452
|
+
const session = {
|
|
53453
|
+
sessionId: row.sessionId,
|
|
53454
|
+
requestCount: row.requestCount,
|
|
53455
|
+
firstSeenAt: new Date(row.firstMs).toISOString(),
|
|
53456
|
+
lastSeenAt: new Date(row.lastMs).toISOString(),
|
|
53457
|
+
models: sortIndexedCountItems(sessionModels.get(sessionKey) ?? new Map),
|
|
53458
|
+
latestRequestId: latestBySession.get(sessionKey) ?? ""
|
|
53459
|
+
};
|
|
53460
|
+
const list = sessionsByUser.get(row.userKey);
|
|
53461
|
+
if (list) {
|
|
53462
|
+
list.push(session);
|
|
53463
|
+
} else {
|
|
53464
|
+
sessionsByUser.set(row.userKey, [session]);
|
|
53465
|
+
}
|
|
53466
|
+
}
|
|
53467
|
+
const users = userRows.map((row) => {
|
|
53468
|
+
const sessions = (sessionsByUser.get(row.userKey) ?? []).sort((a, b) => {
|
|
53469
|
+
if (a.requestCount !== b.requestCount)
|
|
53470
|
+
return b.requestCount - a.requestCount;
|
|
53471
|
+
return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
|
|
53472
|
+
});
|
|
53473
|
+
return {
|
|
53474
|
+
userKey: row.userKey,
|
|
53475
|
+
requestCount: row.requestCount,
|
|
53476
|
+
sessionCount: row.sessionCount,
|
|
53477
|
+
firstSeenAt: new Date(row.firstMs).toISOString(),
|
|
53478
|
+
lastSeenAt: new Date(row.lastMs).toISOString(),
|
|
53479
|
+
models: sortIndexedCountItems(userModels.get(row.userKey) ?? new Map),
|
|
53480
|
+
providers: sortIndexedCountItems(userProviders.get(row.userKey) ?? new Map),
|
|
53481
|
+
routeTypes: sortIndexedCountItems(userRoutes.get(row.userKey) ?? new Map),
|
|
53482
|
+
sessions
|
|
53483
|
+
};
|
|
53484
|
+
}).sort((a, b) => {
|
|
53485
|
+
if (a.requestCount !== b.requestCount)
|
|
53486
|
+
return b.requestCount - a.requestCount;
|
|
53487
|
+
return Date.parse(b.lastSeenAt) - Date.parse(a.lastSeenAt);
|
|
53488
|
+
});
|
|
53489
|
+
return {
|
|
53490
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53491
|
+
to: new Date(query.toMs).toISOString(),
|
|
53492
|
+
summary: {
|
|
53493
|
+
totalRequests: Number(summaryRow.totalRequests) || 0,
|
|
53494
|
+
metadataRequests: Number(summaryRow.metadataRequests) || 0,
|
|
53495
|
+
uniqueUsers: Number(summaryRow.uniqueUsers) || 0,
|
|
53496
|
+
uniqueSessions: Number(summaryRow.uniqueSessions) || 0
|
|
53497
|
+
},
|
|
53498
|
+
users,
|
|
53499
|
+
queryMs: Math.round((performance.now() - startedAt) * 100) / 100
|
|
53500
|
+
};
|
|
53501
|
+
}
|
|
53269
53502
|
configure() {
|
|
53270
53503
|
this.db.exec(`
|
|
53271
53504
|
PRAGMA journal_mode = WAL;
|
|
@@ -53648,6 +53881,65 @@ async function queryIndexedLogEvents(logConfig, query) {
|
|
|
53648
53881
|
};
|
|
53649
53882
|
}
|
|
53650
53883
|
}
|
|
53884
|
+
async function queryIndexedLogSessions(logConfig, query) {
|
|
53885
|
+
if (!logConfig || logConfig.enabled === false) {
|
|
53886
|
+
return {
|
|
53887
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53888
|
+
to: new Date(query.toMs).toISOString(),
|
|
53889
|
+
summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
|
|
53890
|
+
users: [],
|
|
53891
|
+
meta: {
|
|
53892
|
+
scannedFiles: 0,
|
|
53893
|
+
scannedLines: 0,
|
|
53894
|
+
parseErrors: 0,
|
|
53895
|
+
truncated: false,
|
|
53896
|
+
indexUsed: true,
|
|
53897
|
+
indexFresh: true,
|
|
53898
|
+
queryMs: 0
|
|
53899
|
+
}
|
|
53900
|
+
};
|
|
53901
|
+
}
|
|
53902
|
+
const baseDir = resolveLogBaseDir(logConfig);
|
|
53903
|
+
const index = getLogIndex(baseDir);
|
|
53904
|
+
if (!index)
|
|
53905
|
+
return null;
|
|
53906
|
+
try {
|
|
53907
|
+
const freshness = await index.ensureRangeIndexed(query.fromMs, query.toMs);
|
|
53908
|
+
const result = index.querySessions(query);
|
|
53909
|
+
return {
|
|
53910
|
+
from: result.from,
|
|
53911
|
+
to: result.to,
|
|
53912
|
+
summary: result.summary,
|
|
53913
|
+
users: result.users,
|
|
53914
|
+
meta: {
|
|
53915
|
+
scannedFiles: freshness.scannedFiles,
|
|
53916
|
+
scannedLines: freshness.scannedLines,
|
|
53917
|
+
parseErrors: freshness.parseErrors,
|
|
53918
|
+
truncated: false,
|
|
53919
|
+
indexUsed: true,
|
|
53920
|
+
indexFresh: true,
|
|
53921
|
+
queryMs: result.queryMs
|
|
53922
|
+
}
|
|
53923
|
+
};
|
|
53924
|
+
} catch (err) {
|
|
53925
|
+
return {
|
|
53926
|
+
from: new Date(query.fromMs).toISOString(),
|
|
53927
|
+
to: new Date(query.toMs).toISOString(),
|
|
53928
|
+
summary: { totalRequests: 0, metadataRequests: 0, uniqueUsers: 0, uniqueSessions: 0 },
|
|
53929
|
+
users: [],
|
|
53930
|
+
meta: {
|
|
53931
|
+
scannedFiles: 0,
|
|
53932
|
+
scannedLines: 0,
|
|
53933
|
+
parseErrors: 0,
|
|
53934
|
+
truncated: false,
|
|
53935
|
+
indexUsed: false,
|
|
53936
|
+
indexFresh: false,
|
|
53937
|
+
queryMs: 0,
|
|
53938
|
+
fallbackReason: err instanceof Error ? err.message : String(err)
|
|
53939
|
+
}
|
|
53940
|
+
};
|
|
53941
|
+
}
|
|
53942
|
+
}
|
|
53651
53943
|
function getIndexedLogEventDetail(logConfig, id) {
|
|
53652
53944
|
if (!logConfig || logConfig.enabled === false)
|
|
53653
53945
|
return null;
|
|
@@ -53680,7 +53972,10 @@ function getIndexedLogEventDetail(logConfig, id) {
|
|
|
53680
53972
|
var WINDOW_MS2 = {
|
|
53681
53973
|
"1h": 60 * 60 * 1000,
|
|
53682
53974
|
"6h": 6 * 60 * 60 * 1000,
|
|
53683
|
-
"24h": 24 * 60 * 60 * 1000
|
|
53975
|
+
"24h": 24 * 60 * 60 * 1000,
|
|
53976
|
+
"7d": 7 * 24 * 60 * 60 * 1000,
|
|
53977
|
+
"1mo": 30 * 24 * 60 * 60 * 1000,
|
|
53978
|
+
"1y": 365 * 24 * 60 * 60 * 1000
|
|
53684
53979
|
};
|
|
53685
53980
|
var MAX_LINES_SCANNED2 = 250000;
|
|
53686
53981
|
var MAX_QUERY_LIMIT = 200;
|
|
@@ -54328,7 +54623,7 @@ async function scanEvents(baseDir, query) {
|
|
|
54328
54623
|
};
|
|
54329
54624
|
}
|
|
54330
54625
|
function isLogQueryWindow(value) {
|
|
54331
|
-
return value === "1h" || value === "6h" || value === "24h";
|
|
54626
|
+
return value === "1h" || value === "6h" || value === "24h" || value === "7d" || value === "1mo" || value === "1y";
|
|
54332
54627
|
}
|
|
54333
54628
|
function getLogQueryWindowMs(window2) {
|
|
54334
54629
|
return WINDOW_MS2[window2];
|
|
@@ -54832,7 +55127,7 @@ function compileRealtimeQuery(input) {
|
|
|
54832
55127
|
const nowMs = Date.now();
|
|
54833
55128
|
const windowRaw = parseStringValue(query.window) ?? "24h";
|
|
54834
55129
|
if (!isLogQueryWindow(windowRaw)) {
|
|
54835
|
-
throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h");
|
|
55130
|
+
throw new Error("window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y");
|
|
54836
55131
|
}
|
|
54837
55132
|
const from = parseStringValue(query.from);
|
|
54838
55133
|
const to = parseStringValue(query.to);
|
|
@@ -55414,6 +55709,23 @@ async function queryLogSessions(context2, input) {
|
|
|
55414
55709
|
if (!logEnabled) {
|
|
55415
55710
|
return createEmptyResult(normalized.fromMs, normalized.toMs);
|
|
55416
55711
|
}
|
|
55712
|
+
const indexed = await queryIndexedLogSessions(context2.logConfig, {
|
|
55713
|
+
fromMs: normalized.fromMs,
|
|
55714
|
+
toMs: normalized.toMs,
|
|
55715
|
+
users: normalized.users,
|
|
55716
|
+
sessions: normalized.sessions,
|
|
55717
|
+
q: normalized.q
|
|
55718
|
+
});
|
|
55719
|
+
if (indexed?.meta.indexUsed) {
|
|
55720
|
+
return {
|
|
55721
|
+
from: indexed.from,
|
|
55722
|
+
to: indexed.to,
|
|
55723
|
+
summary: indexed.summary,
|
|
55724
|
+
users: indexed.users,
|
|
55725
|
+
meta: indexed.meta
|
|
55726
|
+
};
|
|
55727
|
+
}
|
|
55728
|
+
const fallbackReason = indexed?.meta.fallbackReason;
|
|
55417
55729
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
55418
55730
|
const eventsDir = join8(baseDir, "events");
|
|
55419
55731
|
if (!existsSync7(eventsDir)) {
|
|
@@ -55537,7 +55849,9 @@ async function queryLogSessions(context2, input) {
|
|
|
55537
55849
|
scannedFiles,
|
|
55538
55850
|
scannedLines,
|
|
55539
55851
|
parseErrors,
|
|
55540
|
-
truncated
|
|
55852
|
+
truncated,
|
|
55853
|
+
indexUsed: false,
|
|
55854
|
+
...fallbackReason ? { fallbackReason } : {}
|
|
55541
55855
|
}
|
|
55542
55856
|
};
|
|
55543
55857
|
}
|
|
@@ -56061,7 +56375,7 @@ var openAPISpec = {
|
|
|
56061
56375
|
required: false,
|
|
56062
56376
|
schema: {
|
|
56063
56377
|
type: "string",
|
|
56064
|
-
enum: ["1h", "6h", "24h"],
|
|
56378
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56065
56379
|
default: "24h"
|
|
56066
56380
|
},
|
|
56067
56381
|
description: "\u65F6\u95F4\u7A97\u53E3\uFF08\u5F53\u672A\u63D0\u4F9B from/to \u65F6\u751F\u6548\uFF09"
|
|
@@ -56391,7 +56705,7 @@ var openAPISpec = {
|
|
|
56391
56705
|
required: false,
|
|
56392
56706
|
schema: {
|
|
56393
56707
|
type: "string",
|
|
56394
|
-
enum: ["1h", "6h", "24h"],
|
|
56708
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56395
56709
|
default: "24h"
|
|
56396
56710
|
},
|
|
56397
56711
|
description: "\u65F6\u95F4\u7A97\u53E3"
|
|
@@ -56512,7 +56826,7 @@ var openAPISpec = {
|
|
|
56512
56826
|
required: false,
|
|
56513
56827
|
schema: {
|
|
56514
56828
|
type: "string",
|
|
56515
|
-
enum: ["1h", "6h", "24h"],
|
|
56829
|
+
enum: ["1h", "6h", "24h", "7d", "1mo", "1y"],
|
|
56516
56830
|
default: "1h"
|
|
56517
56831
|
}
|
|
56518
56832
|
},
|
|
@@ -58034,7 +58348,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58034
58348
|
try {
|
|
58035
58349
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58036
58350
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58037
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58351
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58038
58352
|
}
|
|
58039
58353
|
const range = resolveLogQueryRange({
|
|
58040
58354
|
window: windowRaw,
|
|
@@ -58085,7 +58399,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58085
58399
|
try {
|
|
58086
58400
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58087
58401
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58088
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58402
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58089
58403
|
}
|
|
58090
58404
|
const range = resolveLogQueryRange({
|
|
58091
58405
|
window: windowRaw,
|
|
@@ -58124,7 +58438,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58124
58438
|
try {
|
|
58125
58439
|
const windowRaw = c2.req.query("window") ?? "24h";
|
|
58126
58440
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58127
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58441
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58128
58442
|
}
|
|
58129
58443
|
const range = resolveLogQueryRange({
|
|
58130
58444
|
window: windowRaw,
|
|
@@ -58174,7 +58488,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
58174
58488
|
const target = c2.req.raw;
|
|
58175
58489
|
const windowRaw = c2.req.query("window") ?? "1h";
|
|
58176
58490
|
if (!isLogQueryWindow(windowRaw)) {
|
|
58177
|
-
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h" }, 400);
|
|
58491
|
+
return c2.json({ error: "window \u53C2\u6570\u4EC5\u652F\u6301 1h | 6h | 24h | 7d | 1mo | 1y" }, 400);
|
|
58178
58492
|
}
|
|
58179
58493
|
const sortRaw = c2.req.query("sort") ?? "time_desc";
|
|
58180
58494
|
if (!validateSort(sortRaw)) {
|