@lakphy/local-router 0.5.1 → 0.5.3
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 +1184 -237
- package/dist/entry.js +1131 -184
- package/dist/web/assets/index-CQ4HNKVY.css +2 -0
- package/dist/web/assets/index-HmBORmZo.js +190 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BprGtkte.js +0 -191
- package/dist/web/assets/index-OkpSAlwA.css +0 -2
package/dist/entry.js
CHANGED
|
@@ -9284,8 +9284,8 @@ var require_dist2 = __commonJS((exports, module) => {
|
|
|
9284
9284
|
});
|
|
9285
9285
|
|
|
9286
9286
|
// src/index.ts
|
|
9287
|
-
import { readFileSync as
|
|
9288
|
-
import { dirname as dirname2, resolve as
|
|
9287
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9288
|
+
import { dirname as dirname2, resolve as resolve7 } from "path";
|
|
9289
9289
|
|
|
9290
9290
|
// node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
|
|
9291
9291
|
var marker = "vercel.ai.error";
|
|
@@ -51789,8 +51789,8 @@ async function getLogMetrics(options) {
|
|
|
51789
51789
|
}
|
|
51790
51790
|
|
|
51791
51791
|
// src/log-query.ts
|
|
51792
|
-
import { createReadStream as createReadStream3, existsSync as
|
|
51793
|
-
import { join as join5, resolve as
|
|
51792
|
+
import { createReadStream as createReadStream3, existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
51793
|
+
import { join as join5, resolve as resolve5 } from "path";
|
|
51794
51794
|
import { createInterface as createInterface2 } from "readline";
|
|
51795
51795
|
|
|
51796
51796
|
// src/log-index.ts
|
|
@@ -51798,11 +51798,11 @@ import { Database } from "bun:sqlite";
|
|
|
51798
51798
|
import {
|
|
51799
51799
|
closeSync,
|
|
51800
51800
|
createReadStream as createReadStream2,
|
|
51801
|
-
existsSync as
|
|
51801
|
+
existsSync as existsSync4,
|
|
51802
51802
|
mkdirSync as mkdirSync2,
|
|
51803
51803
|
openSync,
|
|
51804
51804
|
readSync,
|
|
51805
|
-
statSync
|
|
51805
|
+
statSync as statSync2
|
|
51806
51806
|
} from "fs";
|
|
51807
51807
|
import { join as join4 } from "path";
|
|
51808
51808
|
|
|
@@ -51882,8 +51882,588 @@ function resolveLogSessionIdentity(requestBody) {
|
|
|
51882
51882
|
};
|
|
51883
51883
|
}
|
|
51884
51884
|
|
|
51885
|
+
// src/token-usage.ts
|
|
51886
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
51887
|
+
import { resolve as resolve4 } from "path";
|
|
51888
|
+
var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
|
|
51889
|
+
function asRecord(value) {
|
|
51890
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
51891
|
+
return null;
|
|
51892
|
+
return value;
|
|
51893
|
+
}
|
|
51894
|
+
function numeric(value) {
|
|
51895
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
51896
|
+
return value;
|
|
51897
|
+
if (typeof value === "string" && value.trim()) {
|
|
51898
|
+
const parsed = Number(value);
|
|
51899
|
+
if (Number.isFinite(parsed))
|
|
51900
|
+
return parsed;
|
|
51901
|
+
}
|
|
51902
|
+
return null;
|
|
51903
|
+
}
|
|
51904
|
+
function numberAt(value, path) {
|
|
51905
|
+
let current = value;
|
|
51906
|
+
for (const key2 of path) {
|
|
51907
|
+
const record2 = asRecord(current);
|
|
51908
|
+
if (!record2 || !(key2 in record2))
|
|
51909
|
+
return null;
|
|
51910
|
+
current = record2[key2];
|
|
51911
|
+
}
|
|
51912
|
+
return numeric(current);
|
|
51913
|
+
}
|
|
51914
|
+
function firstNumber(value, paths) {
|
|
51915
|
+
for (const path of paths) {
|
|
51916
|
+
const found = numberAt(value, path);
|
|
51917
|
+
if (found !== null)
|
|
51918
|
+
return found;
|
|
51919
|
+
}
|
|
51920
|
+
return null;
|
|
51921
|
+
}
|
|
51922
|
+
function maxNullable(...values) {
|
|
51923
|
+
const numbers = values.filter((value) => value !== null && value !== undefined);
|
|
51924
|
+
if (numbers.length === 0)
|
|
51925
|
+
return null;
|
|
51926
|
+
return Math.max(...numbers);
|
|
51927
|
+
}
|
|
51928
|
+
function sumNullable(...values) {
|
|
51929
|
+
let total = 0;
|
|
51930
|
+
let hasValue = false;
|
|
51931
|
+
for (const value of values) {
|
|
51932
|
+
if (value === null || value === undefined)
|
|
51933
|
+
continue;
|
|
51934
|
+
total += value;
|
|
51935
|
+
hasValue = true;
|
|
51936
|
+
}
|
|
51937
|
+
return hasValue ? total : null;
|
|
51938
|
+
}
|
|
51939
|
+
function roundPercent(numerator, denominator) {
|
|
51940
|
+
if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
|
|
51941
|
+
return null;
|
|
51942
|
+
return Number((numerator / denominator * 100).toFixed(2));
|
|
51943
|
+
}
|
|
51944
|
+
function inferProviderStyle(usage, providerHint) {
|
|
51945
|
+
const hint = providerHint?.toLowerCase() ?? "";
|
|
51946
|
+
if (hint.includes("anthropic") || hint.includes("claude"))
|
|
51947
|
+
return "anthropic";
|
|
51948
|
+
if (hint.includes("gemini") || hint.includes("google"))
|
|
51949
|
+
return "gemini";
|
|
51950
|
+
if (hint.includes("deepseek"))
|
|
51951
|
+
return "deepseek";
|
|
51952
|
+
if (hint.includes("cohere"))
|
|
51953
|
+
return "cohere";
|
|
51954
|
+
if (hint.includes("mistral"))
|
|
51955
|
+
return "mistral";
|
|
51956
|
+
if (hint.includes("openrouter"))
|
|
51957
|
+
return "openrouter";
|
|
51958
|
+
if (hint.includes("openai") || hint.includes("gpt-"))
|
|
51959
|
+
return "openai";
|
|
51960
|
+
if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
|
|
51961
|
+
return "anthropic";
|
|
51962
|
+
}
|
|
51963
|
+
if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
|
|
51964
|
+
return "deepseek";
|
|
51965
|
+
}
|
|
51966
|
+
if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
|
|
51967
|
+
return "gemini";
|
|
51968
|
+
}
|
|
51969
|
+
if ("billed_units" in usage || "tokens" in usage) {
|
|
51970
|
+
return "cohere";
|
|
51971
|
+
}
|
|
51972
|
+
if ("prompt_tokens" in usage || "completion_tokens" in usage) {
|
|
51973
|
+
return "openai";
|
|
51974
|
+
}
|
|
51975
|
+
if ("input_tokens" in usage || "output_tokens" in usage) {
|
|
51976
|
+
return "openai";
|
|
51977
|
+
}
|
|
51978
|
+
return "unknown";
|
|
51979
|
+
}
|
|
51980
|
+
function createEmptyMetrics2(input) {
|
|
51981
|
+
return {
|
|
51982
|
+
schemaVersion: 1,
|
|
51983
|
+
source: input.source,
|
|
51984
|
+
providerStyle: input.providerStyle,
|
|
51985
|
+
inputTokens: null,
|
|
51986
|
+
outputTokens: null,
|
|
51987
|
+
totalTokens: null,
|
|
51988
|
+
cachedInputTokens: null,
|
|
51989
|
+
cacheHitInputTokens: null,
|
|
51990
|
+
cacheHitRate: null,
|
|
51991
|
+
cacheHitRateDenominatorTokens: null,
|
|
51992
|
+
cacheHitRateFormula: null,
|
|
51993
|
+
cacheReadInputTokens: null,
|
|
51994
|
+
cacheCreationInputTokens: null,
|
|
51995
|
+
cacheCreationInputTokens5m: null,
|
|
51996
|
+
cacheCreationInputTokens1h: null,
|
|
51997
|
+
cacheWriteInputTokens: null,
|
|
51998
|
+
cacheMissInputTokens: null,
|
|
51999
|
+
reasoningTokens: null,
|
|
52000
|
+
audioInputTokens: null,
|
|
52001
|
+
audioOutputTokens: null,
|
|
52002
|
+
textInputTokens: null,
|
|
52003
|
+
textOutputTokens: null,
|
|
52004
|
+
acceptedPredictionTokens: null,
|
|
52005
|
+
rejectedPredictionTokens: null,
|
|
52006
|
+
toolUsePromptTokens: null,
|
|
52007
|
+
billableInputTokens: null,
|
|
52008
|
+
billableOutputTokens: null,
|
|
52009
|
+
creditUsage: null,
|
|
52010
|
+
cost: null,
|
|
52011
|
+
rawUsage: input.rawUsage,
|
|
52012
|
+
rawUsagePath: input.rawUsagePath,
|
|
52013
|
+
warnings: []
|
|
52014
|
+
};
|
|
52015
|
+
}
|
|
52016
|
+
function hasAnyTokenSignal(metrics) {
|
|
52017
|
+
return [
|
|
52018
|
+
metrics.inputTokens,
|
|
52019
|
+
metrics.outputTokens,
|
|
52020
|
+
metrics.totalTokens,
|
|
52021
|
+
metrics.cachedInputTokens,
|
|
52022
|
+
metrics.cacheHitInputTokens,
|
|
52023
|
+
metrics.cacheReadInputTokens,
|
|
52024
|
+
metrics.cacheCreationInputTokens,
|
|
52025
|
+
metrics.cacheMissInputTokens,
|
|
52026
|
+
metrics.reasoningTokens,
|
|
52027
|
+
metrics.billableInputTokens,
|
|
52028
|
+
metrics.billableOutputTokens,
|
|
52029
|
+
metrics.creditUsage,
|
|
52030
|
+
metrics.cost
|
|
52031
|
+
].some((value) => value !== null);
|
|
52032
|
+
}
|
|
52033
|
+
function normalizeUsageObject(input) {
|
|
52034
|
+
const { usage, source: source2, rawUsagePath, providerHint } = input;
|
|
52035
|
+
const providerStyle = inferProviderStyle(usage, providerHint);
|
|
52036
|
+
const metrics = createEmptyMetrics2({
|
|
52037
|
+
source: source2,
|
|
52038
|
+
providerStyle,
|
|
52039
|
+
rawUsage: usage,
|
|
52040
|
+
rawUsagePath
|
|
52041
|
+
});
|
|
52042
|
+
const usageBody = asRecord(usage.usageMetadata) ?? usage;
|
|
52043
|
+
metrics.inputTokens = firstNumber(usageBody, [
|
|
52044
|
+
["input_tokens"],
|
|
52045
|
+
["prompt_tokens"],
|
|
52046
|
+
["promptTokenCount"],
|
|
52047
|
+
["tokens", "input_tokens"],
|
|
52048
|
+
["billed_units", "input_tokens"]
|
|
52049
|
+
]);
|
|
52050
|
+
metrics.outputTokens = firstNumber(usageBody, [
|
|
52051
|
+
["output_tokens"],
|
|
52052
|
+
["completion_tokens"],
|
|
52053
|
+
["candidatesTokenCount"],
|
|
52054
|
+
["tokens", "output_tokens"],
|
|
52055
|
+
["billed_units", "output_tokens"]
|
|
52056
|
+
]);
|
|
52057
|
+
const explicitTotalTokens = firstNumber(usageBody, [
|
|
52058
|
+
["total_tokens"],
|
|
52059
|
+
["totalTokenCount"],
|
|
52060
|
+
["tokens", "total_tokens"]
|
|
52061
|
+
]);
|
|
52062
|
+
metrics.totalTokens = explicitTotalTokens;
|
|
52063
|
+
const cachedTokens = firstNumber(usageBody, [
|
|
52064
|
+
["input_tokens_details", "cached_tokens"],
|
|
52065
|
+
["prompt_tokens_details", "cached_tokens"],
|
|
52066
|
+
["cached_tokens"],
|
|
52067
|
+
["cachedContentTokenCount"]
|
|
52068
|
+
]);
|
|
52069
|
+
const cacheReadTokens = firstNumber(usageBody, [
|
|
52070
|
+
["cache_read_input_tokens"],
|
|
52071
|
+
["cacheReadInputTokens"],
|
|
52072
|
+
["claude_cache_read_input_tokens"]
|
|
52073
|
+
]);
|
|
52074
|
+
const promptCacheHitTokens = firstNumber(usageBody, [["prompt_cache_hit_tokens"]]);
|
|
52075
|
+
const promptCacheMissTokens = firstNumber(usageBody, [["prompt_cache_miss_tokens"]]);
|
|
52076
|
+
const cacheCreationTokens = firstNumber(usageBody, [
|
|
52077
|
+
["cache_creation_input_tokens"],
|
|
52078
|
+
["cacheCreationInputTokens"],
|
|
52079
|
+
["cache_creation", "input_tokens"],
|
|
52080
|
+
["claude_cache_creation_input_tokens"]
|
|
52081
|
+
]);
|
|
52082
|
+
metrics.cacheCreationInputTokens5m = firstNumber(usageBody, [
|
|
52083
|
+
["cache_creation", "ephemeral_5m_input_tokens"],
|
|
52084
|
+
["cache_creation", "ephemeral5mInputTokens"],
|
|
52085
|
+
["cache_creation_ephemeral_5m_input_tokens"],
|
|
52086
|
+
["claude_cache_creation_5_m_tokens"]
|
|
52087
|
+
]);
|
|
52088
|
+
metrics.cacheCreationInputTokens1h = firstNumber(usageBody, [
|
|
52089
|
+
["cache_creation", "ephemeral_1h_input_tokens"],
|
|
52090
|
+
["cache_creation", "ephemeral1hInputTokens"],
|
|
52091
|
+
["cache_creation_ephemeral_1h_input_tokens"],
|
|
52092
|
+
["claude_cache_creation_1_h_tokens"]
|
|
52093
|
+
]);
|
|
52094
|
+
metrics.cacheCreationInputTokens = maxNullable(cacheCreationTokens, sumNullable(metrics.cacheCreationInputTokens5m, metrics.cacheCreationInputTokens1h));
|
|
52095
|
+
metrics.cacheReadInputTokens = cacheReadTokens;
|
|
52096
|
+
metrics.cacheHitInputTokens = maxNullable(cachedTokens, cacheReadTokens, promptCacheHitTokens);
|
|
52097
|
+
metrics.cachedInputTokens = metrics.cacheHitInputTokens;
|
|
52098
|
+
metrics.cacheMissInputTokens = promptCacheMissTokens;
|
|
52099
|
+
metrics.cacheWriteInputTokens = firstNumber(usageBody, [
|
|
52100
|
+
["cache_write_input_tokens"],
|
|
52101
|
+
["cacheWriteInputTokens"]
|
|
52102
|
+
]);
|
|
52103
|
+
if (metrics.cacheWriteInputTokens === null && providerStyle === "anthropic") {
|
|
52104
|
+
metrics.cacheWriteInputTokens = metrics.cacheCreationInputTokens;
|
|
52105
|
+
}
|
|
52106
|
+
metrics.reasoningTokens = firstNumber(usageBody, [
|
|
52107
|
+
["output_tokens_details", "reasoning_tokens"],
|
|
52108
|
+
["completion_tokens_details", "reasoning_tokens"],
|
|
52109
|
+
["reasoning_tokens"],
|
|
52110
|
+
["thoughtsTokenCount"]
|
|
52111
|
+
]);
|
|
52112
|
+
metrics.audioInputTokens = firstNumber(usageBody, [
|
|
52113
|
+
["input_tokens_details", "audio_tokens"],
|
|
52114
|
+
["prompt_tokens_details", "audio_tokens"],
|
|
52115
|
+
["audio_input_tokens"]
|
|
52116
|
+
]);
|
|
52117
|
+
metrics.audioOutputTokens = firstNumber(usageBody, [
|
|
52118
|
+
["output_tokens_details", "audio_tokens"],
|
|
52119
|
+
["completion_tokens_details", "audio_tokens"],
|
|
52120
|
+
["audio_output_tokens"]
|
|
52121
|
+
]);
|
|
52122
|
+
metrics.textInputTokens = firstNumber(usageBody, [
|
|
52123
|
+
["input_tokens_details", "text_tokens"],
|
|
52124
|
+
["prompt_tokens_details", "text_tokens"],
|
|
52125
|
+
["text_input_tokens"]
|
|
52126
|
+
]);
|
|
52127
|
+
metrics.textOutputTokens = firstNumber(usageBody, [
|
|
52128
|
+
["output_tokens_details", "text_tokens"],
|
|
52129
|
+
["completion_tokens_details", "text_tokens"],
|
|
52130
|
+
["text_output_tokens"]
|
|
52131
|
+
]);
|
|
52132
|
+
metrics.acceptedPredictionTokens = firstNumber(usageBody, [
|
|
52133
|
+
["output_tokens_details", "accepted_prediction_tokens"],
|
|
52134
|
+
["completion_tokens_details", "accepted_prediction_tokens"],
|
|
52135
|
+
["accepted_prediction_tokens"]
|
|
52136
|
+
]);
|
|
52137
|
+
metrics.rejectedPredictionTokens = firstNumber(usageBody, [
|
|
52138
|
+
["output_tokens_details", "rejected_prediction_tokens"],
|
|
52139
|
+
["completion_tokens_details", "rejected_prediction_tokens"],
|
|
52140
|
+
["rejected_prediction_tokens"]
|
|
52141
|
+
]);
|
|
52142
|
+
metrics.toolUsePromptTokens = firstNumber(usageBody, [
|
|
52143
|
+
["toolUsePromptTokenCount"],
|
|
52144
|
+
["tool_use_prompt_tokens"]
|
|
52145
|
+
]);
|
|
52146
|
+
metrics.billableInputTokens = firstNumber(usageBody, [
|
|
52147
|
+
["billed_units", "input_tokens"],
|
|
52148
|
+
["billable_input_tokens"]
|
|
52149
|
+
]);
|
|
52150
|
+
metrics.billableOutputTokens = firstNumber(usageBody, [
|
|
52151
|
+
["billed_units", "output_tokens"],
|
|
52152
|
+
["billable_output_tokens"]
|
|
52153
|
+
]);
|
|
52154
|
+
metrics.creditUsage = firstNumber(usageBody, [["credit_usage"], ["creditUsage"]]);
|
|
52155
|
+
metrics.cost = firstNumber(usageBody, [["cost"], ["total_cost"], ["totalCost"]]);
|
|
52156
|
+
let cacheDenominator = null;
|
|
52157
|
+
let cacheFormula = null;
|
|
52158
|
+
if (providerStyle === "anthropic") {
|
|
52159
|
+
cacheDenominator = sumNullable(metrics.inputTokens, metrics.cacheReadInputTokens, metrics.cacheCreationInputTokens);
|
|
52160
|
+
cacheFormula = "cache_read_input_tokens / (input_tokens + cache_read_input_tokens + cache_creation_input_tokens)";
|
|
52161
|
+
} else if (providerStyle === "deepseek") {
|
|
52162
|
+
cacheDenominator = metrics.inputTokens ?? sumNullable(metrics.cacheHitInputTokens, metrics.cacheMissInputTokens);
|
|
52163
|
+
cacheFormula = "prompt_cache_hit_tokens / prompt_tokens";
|
|
52164
|
+
} else if (providerStyle === "gemini") {
|
|
52165
|
+
cacheDenominator = metrics.inputTokens;
|
|
52166
|
+
cacheFormula = "cachedContentTokenCount / promptTokenCount";
|
|
52167
|
+
} else if (providerStyle === "openai" || providerStyle === "mistral" || providerStyle === "openrouter" || providerStyle === "unknown") {
|
|
52168
|
+
cacheDenominator = metrics.inputTokens;
|
|
52169
|
+
cacheFormula = "cached_tokens / input_tokens";
|
|
52170
|
+
}
|
|
52171
|
+
if (metrics.cacheHitInputTokens !== null && cacheDenominator !== null && cacheDenominator > 0) {
|
|
52172
|
+
metrics.cacheHitRateDenominatorTokens = cacheDenominator;
|
|
52173
|
+
metrics.cacheHitRateFormula = cacheFormula;
|
|
52174
|
+
metrics.cacheHitRate = roundPercent(metrics.cacheHitInputTokens, cacheDenominator);
|
|
52175
|
+
if (metrics.cacheMissInputTokens === null) {
|
|
52176
|
+
metrics.cacheMissInputTokens = Math.max(0, cacheDenominator - metrics.cacheHitInputTokens);
|
|
52177
|
+
}
|
|
52178
|
+
}
|
|
52179
|
+
if (metrics.totalTokens === null && metrics.outputTokens !== null) {
|
|
52180
|
+
const effectiveInputTokens = metrics.cacheHitRateDenominatorTokens ?? metrics.inputTokens;
|
|
52181
|
+
if (effectiveInputTokens !== null) {
|
|
52182
|
+
metrics.totalTokens = effectiveInputTokens + metrics.outputTokens;
|
|
52183
|
+
metrics.warnings.push(metrics.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
52184
|
+
}
|
|
52185
|
+
}
|
|
52186
|
+
return hasAnyTokenSignal(metrics) ? metrics : null;
|
|
52187
|
+
}
|
|
52188
|
+
function collectUsageCandidates(value, prefix = "") {
|
|
52189
|
+
const record2 = asRecord(value);
|
|
52190
|
+
if (!record2)
|
|
52191
|
+
return [];
|
|
52192
|
+
const candidates = [];
|
|
52193
|
+
const candidateKeys = [
|
|
52194
|
+
"usage",
|
|
52195
|
+
"usageMetadata",
|
|
52196
|
+
"message.usage",
|
|
52197
|
+
"response.usage",
|
|
52198
|
+
"body.usage",
|
|
52199
|
+
"data.usage",
|
|
52200
|
+
"event.usage"
|
|
52201
|
+
];
|
|
52202
|
+
for (const key2 of candidateKeys) {
|
|
52203
|
+
const path = key2.split(".");
|
|
52204
|
+
let current = record2;
|
|
52205
|
+
for (const part of path) {
|
|
52206
|
+
const currentRecord = asRecord(current);
|
|
52207
|
+
current = currentRecord?.[part];
|
|
52208
|
+
}
|
|
52209
|
+
const usage = asRecord(current);
|
|
52210
|
+
if (usage) {
|
|
52211
|
+
candidates.push({ usage, path: prefix ? `${prefix}.${key2}` : key2 });
|
|
52212
|
+
}
|
|
52213
|
+
}
|
|
52214
|
+
const direct = normalizeUsageObject({
|
|
52215
|
+
usage: record2,
|
|
52216
|
+
source: "response_body",
|
|
52217
|
+
rawUsagePath: prefix || null
|
|
52218
|
+
});
|
|
52219
|
+
if (direct) {
|
|
52220
|
+
candidates.push({ usage: record2, path: prefix || "$" });
|
|
52221
|
+
}
|
|
52222
|
+
return candidates;
|
|
52223
|
+
}
|
|
52224
|
+
function mergeNumber(current, incoming, strategy = "max") {
|
|
52225
|
+
if (incoming === null)
|
|
52226
|
+
return current;
|
|
52227
|
+
if (current === null)
|
|
52228
|
+
return incoming;
|
|
52229
|
+
return strategy === "latest" ? incoming : Math.max(current, incoming);
|
|
52230
|
+
}
|
|
52231
|
+
function mergeTokenUsageMetrics(current, incoming) {
|
|
52232
|
+
if (!current)
|
|
52233
|
+
return incoming;
|
|
52234
|
+
if (!incoming)
|
|
52235
|
+
return current;
|
|
52236
|
+
const merged = {
|
|
52237
|
+
...current,
|
|
52238
|
+
source: incoming.source,
|
|
52239
|
+
providerStyle: current.providerStyle === "unknown" ? incoming.providerStyle : current.providerStyle,
|
|
52240
|
+
rawUsage: incoming.rawUsage ?? current.rawUsage,
|
|
52241
|
+
rawUsagePath: incoming.rawUsagePath ?? current.rawUsagePath,
|
|
52242
|
+
warnings: Array.from(new Set([...current.warnings, ...incoming.warnings]))
|
|
52243
|
+
};
|
|
52244
|
+
const numericKeys = [
|
|
52245
|
+
"inputTokens",
|
|
52246
|
+
"outputTokens",
|
|
52247
|
+
"totalTokens",
|
|
52248
|
+
"cachedInputTokens",
|
|
52249
|
+
"cacheHitInputTokens",
|
|
52250
|
+
"cacheHitRateDenominatorTokens",
|
|
52251
|
+
"cacheReadInputTokens",
|
|
52252
|
+
"cacheCreationInputTokens",
|
|
52253
|
+
"cacheCreationInputTokens5m",
|
|
52254
|
+
"cacheCreationInputTokens1h",
|
|
52255
|
+
"cacheWriteInputTokens",
|
|
52256
|
+
"cacheMissInputTokens",
|
|
52257
|
+
"reasoningTokens",
|
|
52258
|
+
"audioInputTokens",
|
|
52259
|
+
"audioOutputTokens",
|
|
52260
|
+
"textInputTokens",
|
|
52261
|
+
"textOutputTokens",
|
|
52262
|
+
"acceptedPredictionTokens",
|
|
52263
|
+
"rejectedPredictionTokens",
|
|
52264
|
+
"toolUsePromptTokens",
|
|
52265
|
+
"billableInputTokens",
|
|
52266
|
+
"billableOutputTokens",
|
|
52267
|
+
"creditUsage",
|
|
52268
|
+
"cost"
|
|
52269
|
+
];
|
|
52270
|
+
for (const key2 of numericKeys) {
|
|
52271
|
+
const value = mergeNumber(current[key2], incoming[key2], key2 === "cost" || key2 === "creditUsage" ? "latest" : "max");
|
|
52272
|
+
merged[key2] = value;
|
|
52273
|
+
}
|
|
52274
|
+
if (incoming.cacheHitRate !== null && (current.cacheHitRate === null || (incoming.cacheHitRateDenominatorTokens ?? 0) >= (current.cacheHitRateDenominatorTokens ?? 0))) {
|
|
52275
|
+
merged.cacheHitRate = incoming.cacheHitRate;
|
|
52276
|
+
merged.cacheHitRateFormula = incoming.cacheHitRateFormula;
|
|
52277
|
+
}
|
|
52278
|
+
if (merged.cacheHitInputTokens !== null && merged.cacheHitRateDenominatorTokens !== null) {
|
|
52279
|
+
merged.cacheHitRate = roundPercent(merged.cacheHitInputTokens, merged.cacheHitRateDenominatorTokens);
|
|
52280
|
+
}
|
|
52281
|
+
if (merged.totalTokens === null && merged.outputTokens !== null) {
|
|
52282
|
+
const effectiveInputTokens = merged.cacheHitRateDenominatorTokens ?? merged.inputTokens;
|
|
52283
|
+
if (effectiveInputTokens !== null) {
|
|
52284
|
+
merged.totalTokens = effectiveInputTokens + merged.outputTokens;
|
|
52285
|
+
merged.warnings.push(merged.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
|
|
52286
|
+
merged.warnings = Array.from(new Set(merged.warnings));
|
|
52287
|
+
}
|
|
52288
|
+
}
|
|
52289
|
+
return merged;
|
|
52290
|
+
}
|
|
52291
|
+
function toTokenUsageSummary(metrics) {
|
|
52292
|
+
const { rawUsage: _rawUsage, ...summary } = metrics;
|
|
52293
|
+
return summary;
|
|
52294
|
+
}
|
|
52295
|
+
function extractTokenUsageFromJson(value, options) {
|
|
52296
|
+
let merged = null;
|
|
52297
|
+
const candidates = collectUsageCandidates(value, options.rawUsagePathPrefix ?? "");
|
|
52298
|
+
for (const candidate of candidates) {
|
|
52299
|
+
const metrics = normalizeUsageObject({
|
|
52300
|
+
usage: candidate.usage,
|
|
52301
|
+
source: options.source,
|
|
52302
|
+
rawUsagePath: candidate.path,
|
|
52303
|
+
providerHint: options.providerHint
|
|
52304
|
+
});
|
|
52305
|
+
merged = mergeTokenUsageMetrics(merged, metrics);
|
|
52306
|
+
}
|
|
52307
|
+
return merged;
|
|
52308
|
+
}
|
|
52309
|
+
function extractTokenUsageFromResponseText(text2, source2 = "response_body", providerHint) {
|
|
52310
|
+
if (!text2?.trim())
|
|
52311
|
+
return null;
|
|
52312
|
+
try {
|
|
52313
|
+
return extractTokenUsageFromJson(JSON.parse(text2), { source: source2, providerHint });
|
|
52314
|
+
} catch {
|
|
52315
|
+
return null;
|
|
52316
|
+
}
|
|
52317
|
+
}
|
|
52318
|
+
function processSseMessage(dataLines, source2, providerHint) {
|
|
52319
|
+
if (dataLines.length === 0)
|
|
52320
|
+
return null;
|
|
52321
|
+
const data = dataLines.join(`
|
|
52322
|
+
`).trim();
|
|
52323
|
+
if (!data || data === "[DONE]")
|
|
52324
|
+
return null;
|
|
52325
|
+
try {
|
|
52326
|
+
return extractTokenUsageFromJson(JSON.parse(data), {
|
|
52327
|
+
source: source2,
|
|
52328
|
+
providerHint,
|
|
52329
|
+
rawUsagePathPrefix: source2 === "stream_file" ? "stream" : "stream"
|
|
52330
|
+
});
|
|
52331
|
+
} catch {
|
|
52332
|
+
return null;
|
|
52333
|
+
}
|
|
52334
|
+
}
|
|
52335
|
+
function extractTokenUsageFromSseText(text2, source2 = "stream_file", providerHint) {
|
|
52336
|
+
if (!text2?.trim())
|
|
52337
|
+
return null;
|
|
52338
|
+
let merged = null;
|
|
52339
|
+
let dataLines = [];
|
|
52340
|
+
const flush = () => {
|
|
52341
|
+
merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source2, providerHint));
|
|
52342
|
+
dataLines = [];
|
|
52343
|
+
};
|
|
52344
|
+
for (const rawLine of text2.split(/\r?\n/)) {
|
|
52345
|
+
if (rawLine === "") {
|
|
52346
|
+
flush();
|
|
52347
|
+
continue;
|
|
52348
|
+
}
|
|
52349
|
+
if (rawLine.startsWith("data:")) {
|
|
52350
|
+
dataLines.push(rawLine.slice(5).trimStart());
|
|
52351
|
+
}
|
|
52352
|
+
}
|
|
52353
|
+
flush();
|
|
52354
|
+
return merged;
|
|
52355
|
+
}
|
|
52356
|
+
function createTokenUsageStreamCollector(providerHint) {
|
|
52357
|
+
const decoder = new TextDecoder;
|
|
52358
|
+
let buffer2 = "";
|
|
52359
|
+
let dataLines = [];
|
|
52360
|
+
let latest = null;
|
|
52361
|
+
const flushMessage = () => {
|
|
52362
|
+
latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
|
|
52363
|
+
dataLines = [];
|
|
52364
|
+
};
|
|
52365
|
+
const processLine = (rawLine) => {
|
|
52366
|
+
const line2 = rawLine.replace(/\r$/, "");
|
|
52367
|
+
if (line2 === "") {
|
|
52368
|
+
flushMessage();
|
|
52369
|
+
return;
|
|
52370
|
+
}
|
|
52371
|
+
if (line2.startsWith("data:")) {
|
|
52372
|
+
dataLines.push(line2.slice(5).trimStart());
|
|
52373
|
+
}
|
|
52374
|
+
};
|
|
52375
|
+
return {
|
|
52376
|
+
addChunk(chunk) {
|
|
52377
|
+
buffer2 += decoder.decode(chunk, { stream: true });
|
|
52378
|
+
let newlineIndex = buffer2.indexOf(`
|
|
52379
|
+
`);
|
|
52380
|
+
while (newlineIndex >= 0) {
|
|
52381
|
+
processLine(buffer2.slice(0, newlineIndex));
|
|
52382
|
+
buffer2 = buffer2.slice(newlineIndex + 1);
|
|
52383
|
+
newlineIndex = buffer2.indexOf(`
|
|
52384
|
+
`);
|
|
52385
|
+
}
|
|
52386
|
+
},
|
|
52387
|
+
getUsage() {
|
|
52388
|
+
buffer2 += decoder.decode();
|
|
52389
|
+
if (buffer2) {
|
|
52390
|
+
processLine(buffer2);
|
|
52391
|
+
buffer2 = "";
|
|
52392
|
+
}
|
|
52393
|
+
flushMessage();
|
|
52394
|
+
return latest;
|
|
52395
|
+
}
|
|
52396
|
+
};
|
|
52397
|
+
}
|
|
52398
|
+
function safeReadStreamFile(streamFile, baseDir) {
|
|
52399
|
+
if (!streamFile)
|
|
52400
|
+
return { content: null, warning: null };
|
|
52401
|
+
try {
|
|
52402
|
+
const candidates = [streamFile];
|
|
52403
|
+
if (baseDir)
|
|
52404
|
+
candidates.push(resolve4(baseDir, streamFile));
|
|
52405
|
+
for (const candidate of candidates) {
|
|
52406
|
+
const resolved = resolve4(candidate);
|
|
52407
|
+
if (!resolved.endsWith(".sse.raw"))
|
|
52408
|
+
continue;
|
|
52409
|
+
if (!existsSync3(resolved))
|
|
52410
|
+
continue;
|
|
52411
|
+
const stats = statSync(resolved);
|
|
52412
|
+
if (stats.size > MAX_STREAM_USAGE_BYTES) {
|
|
52413
|
+
return {
|
|
52414
|
+
content: null,
|
|
52415
|
+
warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
|
|
52416
|
+
};
|
|
52417
|
+
}
|
|
52418
|
+
return { content: readFileSync3(resolved, "utf-8"), warning: null };
|
|
52419
|
+
}
|
|
52420
|
+
} catch (err) {
|
|
52421
|
+
return {
|
|
52422
|
+
content: null,
|
|
52423
|
+
warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
|
|
52424
|
+
};
|
|
52425
|
+
}
|
|
52426
|
+
return { content: null, warning: null };
|
|
52427
|
+
}
|
|
52428
|
+
function extractTokenUsageFromLogEvent(event, options = {}) {
|
|
52429
|
+
if (event.token_usage) {
|
|
52430
|
+
return {
|
|
52431
|
+
rawUsage: null,
|
|
52432
|
+
...event.token_usage,
|
|
52433
|
+
source: event.token_usage.source ?? "explicit"
|
|
52434
|
+
};
|
|
52435
|
+
}
|
|
52436
|
+
const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
|
|
52437
|
+
const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
|
|
52438
|
+
if (responseBodyUsage)
|
|
52439
|
+
return responseBodyUsage;
|
|
52440
|
+
const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
|
|
52441
|
+
if (responseAfterPluginsUsage)
|
|
52442
|
+
return responseAfterPluginsUsage;
|
|
52443
|
+
const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
|
|
52444
|
+
if (responseBeforePluginsUsage)
|
|
52445
|
+
return responseBeforePluginsUsage;
|
|
52446
|
+
const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
|
|
52447
|
+
return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
|
|
52448
|
+
}
|
|
52449
|
+
function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
|
|
52450
|
+
const metrics = extractTokenUsageFromLogEvent(event, options);
|
|
52451
|
+
return metrics ? toTokenUsageSummary(metrics) : null;
|
|
52452
|
+
}
|
|
52453
|
+
function enrichLogEventTokenUsage(event, options = {}) {
|
|
52454
|
+
if (event.token_usage)
|
|
52455
|
+
return event;
|
|
52456
|
+
const tokenUsage = extractTokenUsageFromLogEvent(event, options);
|
|
52457
|
+
if (!tokenUsage)
|
|
52458
|
+
return event;
|
|
52459
|
+
return {
|
|
52460
|
+
...event,
|
|
52461
|
+
token_usage: tokenUsage
|
|
52462
|
+
};
|
|
52463
|
+
}
|
|
52464
|
+
|
|
51885
52465
|
// src/log-index.ts
|
|
51886
|
-
var SCHEMA_VERSION =
|
|
52466
|
+
var SCHEMA_VERSION = 3;
|
|
51887
52467
|
var MAX_INDEX_QUEUE = 20000;
|
|
51888
52468
|
var INDEX_BATCH_SIZE = 250;
|
|
51889
52469
|
var INDEX_FLUSH_DELAY_MS = 50;
|
|
@@ -52027,6 +52607,7 @@ function eventToRow(input) {
|
|
|
52027
52607
|
const statusClass = getStatusClass2(event);
|
|
52028
52608
|
const latencyMs = Math.max(0, event.latency_ms ?? 0);
|
|
52029
52609
|
const model = event.model_out || event.model_in;
|
|
52610
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir: input.baseDir });
|
|
52030
52611
|
return {
|
|
52031
52612
|
id: input.id,
|
|
52032
52613
|
ts_ms: tsMs,
|
|
@@ -52054,8 +52635,46 @@ function eventToRow(input) {
|
|
|
52054
52635
|
line_number: input.lineNumber,
|
|
52055
52636
|
byte_offset: input.offset,
|
|
52056
52637
|
byte_length: input.byteLength,
|
|
52057
|
-
search_text: buildSearchText(event)
|
|
52058
|
-
|
|
52638
|
+
search_text: buildSearchText(event),
|
|
52639
|
+
token_input: tokenUsage?.inputTokens ?? null,
|
|
52640
|
+
token_output: tokenUsage?.outputTokens ?? null,
|
|
52641
|
+
token_total: tokenUsage?.totalTokens ?? null,
|
|
52642
|
+
token_cached_input: tokenUsage?.cachedInputTokens ?? null,
|
|
52643
|
+
token_cache_hit_input: tokenUsage?.cacheHitInputTokens ?? null,
|
|
52644
|
+
token_cache_hit_rate: tokenUsage?.cacheHitRate ?? null,
|
|
52645
|
+
token_cache_hit_rate_denominator: tokenUsage?.cacheHitRateDenominatorTokens ?? null,
|
|
52646
|
+
token_cache_read_input: tokenUsage?.cacheReadInputTokens ?? null,
|
|
52647
|
+
token_cache_creation_input: tokenUsage?.cacheCreationInputTokens ?? null,
|
|
52648
|
+
token_cache_creation_input_5m: tokenUsage?.cacheCreationInputTokens5m ?? null,
|
|
52649
|
+
token_cache_creation_input_1h: tokenUsage?.cacheCreationInputTokens1h ?? null,
|
|
52650
|
+
token_cache_write_input: tokenUsage?.cacheWriteInputTokens ?? null,
|
|
52651
|
+
token_cache_miss_input: tokenUsage?.cacheMissInputTokens ?? null,
|
|
52652
|
+
token_reasoning: tokenUsage?.reasoningTokens ?? null,
|
|
52653
|
+
token_audio_input: tokenUsage?.audioInputTokens ?? null,
|
|
52654
|
+
token_audio_output: tokenUsage?.audioOutputTokens ?? null,
|
|
52655
|
+
token_text_input: tokenUsage?.textInputTokens ?? null,
|
|
52656
|
+
token_text_output: tokenUsage?.textOutputTokens ?? null,
|
|
52657
|
+
token_accepted_prediction: tokenUsage?.acceptedPredictionTokens ?? null,
|
|
52658
|
+
token_rejected_prediction: tokenUsage?.rejectedPredictionTokens ?? null,
|
|
52659
|
+
token_tool_use_prompt: tokenUsage?.toolUsePromptTokens ?? null,
|
|
52660
|
+
token_billable_input: tokenUsage?.billableInputTokens ?? null,
|
|
52661
|
+
token_billable_output: tokenUsage?.billableOutputTokens ?? null,
|
|
52662
|
+
token_credit_usage: tokenUsage?.creditUsage ?? null,
|
|
52663
|
+
token_cost: tokenUsage?.cost ?? null,
|
|
52664
|
+
token_source: tokenUsage?.source ?? null,
|
|
52665
|
+
token_provider_style: tokenUsage?.providerStyle ?? null,
|
|
52666
|
+
token_raw_usage_path: tokenUsage?.rawUsagePath ?? null,
|
|
52667
|
+
token_usage_json: tokenUsage ? JSON.stringify(tokenUsage) : null
|
|
52668
|
+
};
|
|
52669
|
+
}
|
|
52670
|
+
function parseTokenUsageSummary(value) {
|
|
52671
|
+
if (!value)
|
|
52672
|
+
return null;
|
|
52673
|
+
try {
|
|
52674
|
+
return JSON.parse(value);
|
|
52675
|
+
} catch {
|
|
52676
|
+
return null;
|
|
52677
|
+
}
|
|
52059
52678
|
}
|
|
52060
52679
|
function rowToSummary(row) {
|
|
52061
52680
|
return {
|
|
@@ -52078,7 +52697,8 @@ function rowToSummary(row) {
|
|
|
52078
52697
|
hasMetadata: row.has_metadata === 1,
|
|
52079
52698
|
userIdRaw: row.user_id_raw,
|
|
52080
52699
|
userKey: row.user_key,
|
|
52081
|
-
sessionId: row.session_id
|
|
52700
|
+
sessionId: row.session_id,
|
|
52701
|
+
tokenUsage: parseTokenUsageSummary(row.token_usage_json)
|
|
52082
52702
|
};
|
|
52083
52703
|
}
|
|
52084
52704
|
async function* readJsonlLinesWithOffsets(filePath) {
|
|
@@ -52148,7 +52768,22 @@ function createEmptyStats() {
|
|
|
52148
52768
|
errorCount: 0,
|
|
52149
52769
|
errorRate: 0,
|
|
52150
52770
|
avgLatencyMs: 0,
|
|
52151
|
-
p95LatencyMs: 0
|
|
52771
|
+
p95LatencyMs: 0,
|
|
52772
|
+
tokenUsageCount: 0,
|
|
52773
|
+
inputTokens: 0,
|
|
52774
|
+
outputTokens: 0,
|
|
52775
|
+
totalTokens: 0,
|
|
52776
|
+
cachedInputTokens: 0,
|
|
52777
|
+
cacheHitInputTokens: 0,
|
|
52778
|
+
cacheHitRate: 0,
|
|
52779
|
+
cacheHitRateDenominatorTokens: 0,
|
|
52780
|
+
cacheReadInputTokens: 0,
|
|
52781
|
+
cacheCreationInputTokens: 0,
|
|
52782
|
+
cacheWriteInputTokens: 0,
|
|
52783
|
+
cacheMissInputTokens: 0,
|
|
52784
|
+
reasoningTokens: 0,
|
|
52785
|
+
billableInputTokens: 0,
|
|
52786
|
+
billableOutputTokens: 0
|
|
52152
52787
|
};
|
|
52153
52788
|
}
|
|
52154
52789
|
function createEmptyQueryResult(query, meta3 = {}) {
|
|
@@ -52241,12 +52876,28 @@ class LogIndex {
|
|
|
52241
52876
|
id, ts_ms, ts_start, level, provider, route_type, model, model_in, model_out,
|
|
52242
52877
|
path, request_id, latency_ms, upstream_status, status_class, has_error,
|
|
52243
52878
|
message, error_type, has_metadata, user_id_raw, user_key, session_id,
|
|
52244
|
-
source_date, source_file, line_number, byte_offset, byte_length, search_text
|
|
52879
|
+
source_date, source_file, line_number, byte_offset, byte_length, search_text,
|
|
52880
|
+
token_input, token_output, token_total, token_cached_input, token_cache_hit_input,
|
|
52881
|
+
token_cache_hit_rate, token_cache_hit_rate_denominator, token_cache_read_input,
|
|
52882
|
+
token_cache_creation_input, token_cache_creation_input_5m, token_cache_creation_input_1h,
|
|
52883
|
+
token_cache_write_input, token_cache_miss_input, token_reasoning, token_audio_input,
|
|
52884
|
+
token_audio_output, token_text_input, token_text_output, token_accepted_prediction,
|
|
52885
|
+
token_rejected_prediction, token_tool_use_prompt, token_billable_input,
|
|
52886
|
+
token_billable_output, token_credit_usage, token_cost, token_source,
|
|
52887
|
+
token_provider_style, token_raw_usage_path, token_usage_json
|
|
52245
52888
|
) VALUES (
|
|
52246
52889
|
$id, $ts_ms, $ts_start, $level, $provider, $route_type, $model, $model_in, $model_out,
|
|
52247
52890
|
$path, $request_id, $latency_ms, $upstream_status, $status_class, $has_error,
|
|
52248
52891
|
$message, $error_type, $has_metadata, $user_id_raw, $user_key, $session_id,
|
|
52249
|
-
$source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text
|
|
52892
|
+
$source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text,
|
|
52893
|
+
$token_input, $token_output, $token_total, $token_cached_input, $token_cache_hit_input,
|
|
52894
|
+
$token_cache_hit_rate, $token_cache_hit_rate_denominator, $token_cache_read_input,
|
|
52895
|
+
$token_cache_creation_input, $token_cache_creation_input_5m, $token_cache_creation_input_1h,
|
|
52896
|
+
$token_cache_write_input, $token_cache_miss_input, $token_reasoning, $token_audio_input,
|
|
52897
|
+
$token_audio_output, $token_text_input, $token_text_output, $token_accepted_prediction,
|
|
52898
|
+
$token_rejected_prediction, $token_tool_use_prompt, $token_billable_input,
|
|
52899
|
+
$token_billable_output, $token_credit_usage, $token_cost, $token_source,
|
|
52900
|
+
$token_provider_style, $token_raw_usage_path, $token_usage_json
|
|
52250
52901
|
)
|
|
52251
52902
|
`);
|
|
52252
52903
|
this.deleteFtsStmt = this.db.prepare("DELETE FROM log_events_fts WHERE event_id = ?");
|
|
@@ -52296,9 +52947,9 @@ class LogIndex {
|
|
|
52296
52947
|
const dates = listDateStrings2(fromMs, toMs);
|
|
52297
52948
|
for (const date5 of dates) {
|
|
52298
52949
|
const filePath = join4(eventsDir, `${date5}.jsonl`);
|
|
52299
|
-
if (!
|
|
52950
|
+
if (!existsSync4(filePath))
|
|
52300
52951
|
continue;
|
|
52301
|
-
const stats =
|
|
52952
|
+
const stats = statSync2(filePath);
|
|
52302
52953
|
const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
|
|
52303
52954
|
const sizeBytes = stats.size;
|
|
52304
52955
|
const mtimeMs = Math.trunc(stats.mtimeMs);
|
|
@@ -52333,7 +52984,7 @@ class LogIndex {
|
|
|
52333
52984
|
e.id, e.ts_start, e.level, e.provider, e.route_type, e.model, e.model_in,
|
|
52334
52985
|
e.model_out, e.path, e.request_id, e.latency_ms, e.upstream_status,
|
|
52335
52986
|
e.status_class, e.has_error, e.message, e.error_type, e.has_metadata,
|
|
52336
|
-
e.user_id_raw, e.user_key, e.session_id, e.ts_ms
|
|
52987
|
+
e.user_id_raw, e.user_key, e.session_id, e.ts_ms, e.token_usage_json
|
|
52337
52988
|
FROM log_events e
|
|
52338
52989
|
${whereSql}
|
|
52339
52990
|
${cursorClause}
|
|
@@ -52388,7 +53039,7 @@ class LogIndex {
|
|
|
52388
53039
|
return null;
|
|
52389
53040
|
const row = this.db.query("SELECT source_date, source_file, line_number, byte_offset FROM log_events WHERE id = ?").get(id);
|
|
52390
53041
|
const filePath = row?.source_file ?? join4(this.baseDir, "events", `${parsedId.date}.jsonl`);
|
|
52391
|
-
if (!
|
|
53042
|
+
if (!existsSync4(filePath))
|
|
52392
53043
|
return null;
|
|
52393
53044
|
const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
|
|
52394
53045
|
if (!line2?.trim())
|
|
@@ -52456,7 +53107,36 @@ class LogIndex {
|
|
|
52456
53107
|
line_number INTEGER,
|
|
52457
53108
|
byte_offset INTEGER NOT NULL,
|
|
52458
53109
|
byte_length INTEGER NOT NULL,
|
|
52459
|
-
search_text TEXT NOT NULL
|
|
53110
|
+
search_text TEXT NOT NULL,
|
|
53111
|
+
token_input INTEGER,
|
|
53112
|
+
token_output INTEGER,
|
|
53113
|
+
token_total INTEGER,
|
|
53114
|
+
token_cached_input INTEGER,
|
|
53115
|
+
token_cache_hit_input INTEGER,
|
|
53116
|
+
token_cache_hit_rate REAL,
|
|
53117
|
+
token_cache_hit_rate_denominator INTEGER,
|
|
53118
|
+
token_cache_read_input INTEGER,
|
|
53119
|
+
token_cache_creation_input INTEGER,
|
|
53120
|
+
token_cache_creation_input_5m INTEGER,
|
|
53121
|
+
token_cache_creation_input_1h INTEGER,
|
|
53122
|
+
token_cache_write_input INTEGER,
|
|
53123
|
+
token_cache_miss_input INTEGER,
|
|
53124
|
+
token_reasoning INTEGER,
|
|
53125
|
+
token_audio_input INTEGER,
|
|
53126
|
+
token_audio_output INTEGER,
|
|
53127
|
+
token_text_input INTEGER,
|
|
53128
|
+
token_text_output INTEGER,
|
|
53129
|
+
token_accepted_prediction INTEGER,
|
|
53130
|
+
token_rejected_prediction INTEGER,
|
|
53131
|
+
token_tool_use_prompt INTEGER,
|
|
53132
|
+
token_billable_input INTEGER,
|
|
53133
|
+
token_billable_output INTEGER,
|
|
53134
|
+
token_credit_usage REAL,
|
|
53135
|
+
token_cost REAL,
|
|
53136
|
+
token_source TEXT,
|
|
53137
|
+
token_provider_style TEXT,
|
|
53138
|
+
token_raw_usage_path TEXT,
|
|
53139
|
+
token_usage_json TEXT
|
|
52460
53140
|
);
|
|
52461
53141
|
|
|
52462
53142
|
CREATE VIRTUAL TABLE IF NOT EXISTS log_events_fts
|
|
@@ -52474,12 +53154,67 @@ class LogIndex {
|
|
|
52474
53154
|
CREATE INDEX IF NOT EXISTS idx_log_events_session_time ON log_events(session_id, ts_ms DESC);
|
|
52475
53155
|
CREATE INDEX IF NOT EXISTS idx_log_events_file ON log_events(source_file);
|
|
52476
53156
|
`);
|
|
53157
|
+
this.ensureTokenColumns();
|
|
53158
|
+
this.db.exec(`
|
|
53159
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_total_time ON log_events(token_total, ts_ms DESC);
|
|
53160
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_input_time ON log_events(token_input, ts_ms DESC);
|
|
53161
|
+
CREATE INDEX IF NOT EXISTS idx_log_events_token_cache_hit_rate_time ON log_events(token_cache_hit_rate, ts_ms DESC);
|
|
53162
|
+
`);
|
|
53163
|
+
const versionRow = this.db.query("SELECT value FROM log_index_meta WHERE key = 'schema_version'").get();
|
|
53164
|
+
const previousVersion = Number.parseInt(versionRow?.value ?? "0", 10) || 0;
|
|
53165
|
+
if (previousVersion > 0 && previousVersion < SCHEMA_VERSION) {
|
|
53166
|
+
this.db.exec(`
|
|
53167
|
+
DELETE FROM log_events_fts;
|
|
53168
|
+
DELETE FROM log_events;
|
|
53169
|
+
DELETE FROM log_index_files;
|
|
53170
|
+
`);
|
|
53171
|
+
}
|
|
52477
53172
|
this.db.prepare(`
|
|
52478
53173
|
INSERT INTO log_index_meta(key, value)
|
|
52479
53174
|
VALUES ('schema_version', ?)
|
|
52480
53175
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
52481
53176
|
`).run(String(SCHEMA_VERSION));
|
|
52482
53177
|
}
|
|
53178
|
+
ensureTokenColumns() {
|
|
53179
|
+
const rows = this.db.query("PRAGMA table_info(log_events)").all();
|
|
53180
|
+
const existing = new Set(rows.map((row) => row.name));
|
|
53181
|
+
const columns = [
|
|
53182
|
+
{ name: "token_input", type: "INTEGER" },
|
|
53183
|
+
{ name: "token_output", type: "INTEGER" },
|
|
53184
|
+
{ name: "token_total", type: "INTEGER" },
|
|
53185
|
+
{ name: "token_cached_input", type: "INTEGER" },
|
|
53186
|
+
{ name: "token_cache_hit_input", type: "INTEGER" },
|
|
53187
|
+
{ name: "token_cache_hit_rate", type: "REAL" },
|
|
53188
|
+
{ name: "token_cache_hit_rate_denominator", type: "INTEGER" },
|
|
53189
|
+
{ name: "token_cache_read_input", type: "INTEGER" },
|
|
53190
|
+
{ name: "token_cache_creation_input", type: "INTEGER" },
|
|
53191
|
+
{ name: "token_cache_creation_input_5m", type: "INTEGER" },
|
|
53192
|
+
{ name: "token_cache_creation_input_1h", type: "INTEGER" },
|
|
53193
|
+
{ name: "token_cache_write_input", type: "INTEGER" },
|
|
53194
|
+
{ name: "token_cache_miss_input", type: "INTEGER" },
|
|
53195
|
+
{ name: "token_reasoning", type: "INTEGER" },
|
|
53196
|
+
{ name: "token_audio_input", type: "INTEGER" },
|
|
53197
|
+
{ name: "token_audio_output", type: "INTEGER" },
|
|
53198
|
+
{ name: "token_text_input", type: "INTEGER" },
|
|
53199
|
+
{ name: "token_text_output", type: "INTEGER" },
|
|
53200
|
+
{ name: "token_accepted_prediction", type: "INTEGER" },
|
|
53201
|
+
{ name: "token_rejected_prediction", type: "INTEGER" },
|
|
53202
|
+
{ name: "token_tool_use_prompt", type: "INTEGER" },
|
|
53203
|
+
{ name: "token_billable_input", type: "INTEGER" },
|
|
53204
|
+
{ name: "token_billable_output", type: "INTEGER" },
|
|
53205
|
+
{ name: "token_credit_usage", type: "REAL" },
|
|
53206
|
+
{ name: "token_cost", type: "REAL" },
|
|
53207
|
+
{ name: "token_source", type: "TEXT" },
|
|
53208
|
+
{ name: "token_provider_style", type: "TEXT" },
|
|
53209
|
+
{ name: "token_raw_usage_path", type: "TEXT" },
|
|
53210
|
+
{ name: "token_usage_json", type: "TEXT" }
|
|
53211
|
+
];
|
|
53212
|
+
for (const column2 of columns) {
|
|
53213
|
+
if (existing.has(column2.name))
|
|
53214
|
+
continue;
|
|
53215
|
+
this.db.exec(`ALTER TABLE log_events ADD COLUMN ${column2.name} ${column2.type}`);
|
|
53216
|
+
}
|
|
53217
|
+
}
|
|
52483
53218
|
flushQueue() {
|
|
52484
53219
|
if (this.queue.length === 0 || this.disposed)
|
|
52485
53220
|
return;
|
|
@@ -52507,6 +53242,7 @@ class LogIndex {
|
|
|
52507
53242
|
return;
|
|
52508
53243
|
const id = encodeOffsetLogEventId(item.date, item.offset);
|
|
52509
53244
|
const row = eventToRow({
|
|
53245
|
+
baseDir: this.baseDir,
|
|
52510
53246
|
id,
|
|
52511
53247
|
date: item.date,
|
|
52512
53248
|
filePath: item.filePath,
|
|
@@ -52522,7 +53258,7 @@ class LogIndex {
|
|
|
52522
53258
|
this.insertFtsStmt.run(id, row.search_text);
|
|
52523
53259
|
if (!this.dirtyFiles.has(item.filePath)) {
|
|
52524
53260
|
try {
|
|
52525
|
-
const stats =
|
|
53261
|
+
const stats = statSync2(item.filePath);
|
|
52526
53262
|
const indexedThrough = item.offset + item.byteLength;
|
|
52527
53263
|
this.upsertFileStmt.run(item.filePath, item.date, Math.min(indexedThrough, stats.size), Math.trunc(stats.mtimeMs), Date.now());
|
|
52528
53264
|
} catch {}
|
|
@@ -52549,6 +53285,7 @@ class LogIndex {
|
|
|
52549
53285
|
continue;
|
|
52550
53286
|
}
|
|
52551
53287
|
const row = eventToRow({
|
|
53288
|
+
baseDir: this.baseDir,
|
|
52552
53289
|
id: encodeOffsetLogEventId(date5, item.offset),
|
|
52553
53290
|
date: date5,
|
|
52554
53291
|
filePath,
|
|
@@ -52581,7 +53318,21 @@ class LogIndex {
|
|
|
52581
53318
|
SELECT
|
|
52582
53319
|
COUNT(*) AS total,
|
|
52583
53320
|
COALESCE(SUM(has_error), 0) AS errorCount,
|
|
52584
|
-
COALESCE(AVG(latency_ms), 0) AS avgLatencyMs
|
|
53321
|
+
COALESCE(AVG(latency_ms), 0) AS avgLatencyMs,
|
|
53322
|
+
COALESCE(SUM(CASE WHEN token_usage_json IS NOT NULL THEN 1 ELSE 0 END), 0) AS tokenUsageCount,
|
|
53323
|
+
COALESCE(SUM(token_input), 0) AS inputTokens,
|
|
53324
|
+
COALESCE(SUM(token_output), 0) AS outputTokens,
|
|
53325
|
+
COALESCE(SUM(token_total), 0) AS totalTokens,
|
|
53326
|
+
COALESCE(SUM(token_cached_input), 0) AS cachedInputTokens,
|
|
53327
|
+
COALESCE(SUM(token_cache_hit_input), 0) AS cacheHitInputTokens,
|
|
53328
|
+
COALESCE(SUM(token_cache_hit_rate_denominator), 0) AS cacheHitRateDenominatorTokens,
|
|
53329
|
+
COALESCE(SUM(token_cache_read_input), 0) AS cacheReadInputTokens,
|
|
53330
|
+
COALESCE(SUM(token_cache_creation_input), 0) AS cacheCreationInputTokens,
|
|
53331
|
+
COALESCE(SUM(token_cache_write_input), 0) AS cacheWriteInputTokens,
|
|
53332
|
+
COALESCE(SUM(token_cache_miss_input), 0) AS cacheMissInputTokens,
|
|
53333
|
+
COALESCE(SUM(token_reasoning), 0) AS reasoningTokens,
|
|
53334
|
+
COALESCE(SUM(token_billable_input), 0) AS billableInputTokens,
|
|
53335
|
+
COALESCE(SUM(token_billable_output), 0) AS billableOutputTokens
|
|
52585
53336
|
FROM log_events e
|
|
52586
53337
|
${whereSql}
|
|
52587
53338
|
`).get(...params);
|
|
@@ -52602,7 +53353,22 @@ class LogIndex {
|
|
|
52602
53353
|
errorCount,
|
|
52603
53354
|
errorRate: toPercent2(errorCount, total),
|
|
52604
53355
|
avgLatencyMs: Math.round(Number(aggregate.avgLatencyMs) || 0),
|
|
52605
|
-
p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0)
|
|
53356
|
+
p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0),
|
|
53357
|
+
tokenUsageCount: Number(aggregate.tokenUsageCount) || 0,
|
|
53358
|
+
inputTokens: Number(aggregate.inputTokens) || 0,
|
|
53359
|
+
outputTokens: Number(aggregate.outputTokens) || 0,
|
|
53360
|
+
totalTokens: Number(aggregate.totalTokens) || 0,
|
|
53361
|
+
cachedInputTokens: Number(aggregate.cachedInputTokens) || 0,
|
|
53362
|
+
cacheHitInputTokens: Number(aggregate.cacheHitInputTokens) || 0,
|
|
53363
|
+
cacheHitRate: toPercent2(Number(aggregate.cacheHitInputTokens) || 0, Number(aggregate.cacheHitRateDenominatorTokens) || 0),
|
|
53364
|
+
cacheHitRateDenominatorTokens: Number(aggregate.cacheHitRateDenominatorTokens) || 0,
|
|
53365
|
+
cacheReadInputTokens: Number(aggregate.cacheReadInputTokens) || 0,
|
|
53366
|
+
cacheCreationInputTokens: Number(aggregate.cacheCreationInputTokens) || 0,
|
|
53367
|
+
cacheWriteInputTokens: Number(aggregate.cacheWriteInputTokens) || 0,
|
|
53368
|
+
cacheMissInputTokens: Number(aggregate.cacheMissInputTokens) || 0,
|
|
53369
|
+
reasoningTokens: Number(aggregate.reasoningTokens) || 0,
|
|
53370
|
+
billableInputTokens: Number(aggregate.billableInputTokens) || 0,
|
|
53371
|
+
billableOutputTokens: Number(aggregate.billableOutputTokens) || 0
|
|
52606
53372
|
};
|
|
52607
53373
|
}
|
|
52608
53374
|
}
|
|
@@ -52683,7 +53449,7 @@ function getIndexedLogEventDetail(logConfig, id) {
|
|
|
52683
53449
|
if (!parsedId)
|
|
52684
53450
|
return null;
|
|
52685
53451
|
const filePath = join4(baseDir, "events", `${parsedId.date}.jsonl`);
|
|
52686
|
-
if (!
|
|
53452
|
+
if (!existsSync4(filePath))
|
|
52687
53453
|
return null;
|
|
52688
53454
|
const line2 = readLineAtOffset(filePath, parsedId.offset);
|
|
52689
53455
|
if (!line2?.trim())
|
|
@@ -52816,8 +53582,25 @@ function createRunningStats() {
|
|
|
52816
53582
|
total: 0,
|
|
52817
53583
|
errorCount: 0,
|
|
52818
53584
|
latencySum: 0,
|
|
52819
|
-
latencyCounts: new Map
|
|
52820
|
-
|
|
53585
|
+
latencyCounts: new Map,
|
|
53586
|
+
tokenUsageCount: 0,
|
|
53587
|
+
inputTokens: 0,
|
|
53588
|
+
outputTokens: 0,
|
|
53589
|
+
totalTokens: 0,
|
|
53590
|
+
cachedInputTokens: 0,
|
|
53591
|
+
cacheHitInputTokens: 0,
|
|
53592
|
+
cacheHitRateDenominatorTokens: 0,
|
|
53593
|
+
cacheReadInputTokens: 0,
|
|
53594
|
+
cacheCreationInputTokens: 0,
|
|
53595
|
+
cacheWriteInputTokens: 0,
|
|
53596
|
+
cacheMissInputTokens: 0,
|
|
53597
|
+
reasoningTokens: 0,
|
|
53598
|
+
billableInputTokens: 0,
|
|
53599
|
+
billableOutputTokens: 0
|
|
53600
|
+
};
|
|
53601
|
+
}
|
|
53602
|
+
function addNullable(total, value) {
|
|
53603
|
+
return total + (value ?? 0);
|
|
52821
53604
|
}
|
|
52822
53605
|
function updateRunningStats(stats, item) {
|
|
52823
53606
|
stats.total += 1;
|
|
@@ -52828,6 +53611,23 @@ function updateRunningStats(stats, item) {
|
|
|
52828
53611
|
stats.latencySum += latency;
|
|
52829
53612
|
const roundedLatency = Math.round(latency);
|
|
52830
53613
|
stats.latencyCounts.set(roundedLatency, (stats.latencyCounts.get(roundedLatency) ?? 0) + 1);
|
|
53614
|
+
const usage = item.tokenUsage;
|
|
53615
|
+
if (!usage)
|
|
53616
|
+
return;
|
|
53617
|
+
stats.tokenUsageCount += 1;
|
|
53618
|
+
stats.inputTokens = addNullable(stats.inputTokens, usage.inputTokens);
|
|
53619
|
+
stats.outputTokens = addNullable(stats.outputTokens, usage.outputTokens);
|
|
53620
|
+
stats.totalTokens = addNullable(stats.totalTokens, usage.totalTokens);
|
|
53621
|
+
stats.cachedInputTokens = addNullable(stats.cachedInputTokens, usage.cachedInputTokens);
|
|
53622
|
+
stats.cacheHitInputTokens = addNullable(stats.cacheHitInputTokens, usage.cacheHitInputTokens);
|
|
53623
|
+
stats.cacheHitRateDenominatorTokens = addNullable(stats.cacheHitRateDenominatorTokens, usage.cacheHitRateDenominatorTokens);
|
|
53624
|
+
stats.cacheReadInputTokens = addNullable(stats.cacheReadInputTokens, usage.cacheReadInputTokens);
|
|
53625
|
+
stats.cacheCreationInputTokens = addNullable(stats.cacheCreationInputTokens, usage.cacheCreationInputTokens);
|
|
53626
|
+
stats.cacheWriteInputTokens = addNullable(stats.cacheWriteInputTokens, usage.cacheWriteInputTokens);
|
|
53627
|
+
stats.cacheMissInputTokens = addNullable(stats.cacheMissInputTokens, usage.cacheMissInputTokens);
|
|
53628
|
+
stats.reasoningTokens = addNullable(stats.reasoningTokens, usage.reasoningTokens);
|
|
53629
|
+
stats.billableInputTokens = addNullable(stats.billableInputTokens, usage.billableInputTokens);
|
|
53630
|
+
stats.billableOutputTokens = addNullable(stats.billableOutputTokens, usage.billableOutputTokens);
|
|
52831
53631
|
}
|
|
52832
53632
|
function percentileFromCounts(latencyCounts, total, ratio) {
|
|
52833
53633
|
if (total <= 0 || latencyCounts.size === 0)
|
|
@@ -52850,7 +53650,22 @@ function finalizeStats(stats) {
|
|
|
52850
53650
|
errorCount: 0,
|
|
52851
53651
|
errorRate: 0,
|
|
52852
53652
|
avgLatencyMs: 0,
|
|
52853
|
-
p95LatencyMs: 0
|
|
53653
|
+
p95LatencyMs: 0,
|
|
53654
|
+
tokenUsageCount: 0,
|
|
53655
|
+
inputTokens: 0,
|
|
53656
|
+
outputTokens: 0,
|
|
53657
|
+
totalTokens: 0,
|
|
53658
|
+
cachedInputTokens: 0,
|
|
53659
|
+
cacheHitInputTokens: 0,
|
|
53660
|
+
cacheHitRate: 0,
|
|
53661
|
+
cacheHitRateDenominatorTokens: 0,
|
|
53662
|
+
cacheReadInputTokens: 0,
|
|
53663
|
+
cacheCreationInputTokens: 0,
|
|
53664
|
+
cacheWriteInputTokens: 0,
|
|
53665
|
+
cacheMissInputTokens: 0,
|
|
53666
|
+
reasoningTokens: 0,
|
|
53667
|
+
billableInputTokens: 0,
|
|
53668
|
+
billableOutputTokens: 0
|
|
52854
53669
|
};
|
|
52855
53670
|
}
|
|
52856
53671
|
return {
|
|
@@ -52858,8 +53673,26 @@ function finalizeStats(stats) {
|
|
|
52858
53673
|
errorCount: stats.errorCount,
|
|
52859
53674
|
errorRate: toPercent3(stats.errorCount, stats.total),
|
|
52860
53675
|
avgLatencyMs: Math.round(stats.latencySum / stats.total),
|
|
52861
|
-
p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95)
|
|
52862
|
-
|
|
53676
|
+
p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95),
|
|
53677
|
+
tokenUsageCount: stats.tokenUsageCount,
|
|
53678
|
+
inputTokens: stats.inputTokens,
|
|
53679
|
+
outputTokens: stats.outputTokens,
|
|
53680
|
+
totalTokens: stats.totalTokens,
|
|
53681
|
+
cachedInputTokens: stats.cachedInputTokens,
|
|
53682
|
+
cacheHitInputTokens: stats.cacheHitInputTokens,
|
|
53683
|
+
cacheHitRate: toPercent3(stats.cacheHitInputTokens, stats.cacheHitRateDenominatorTokens),
|
|
53684
|
+
cacheHitRateDenominatorTokens: stats.cacheHitRateDenominatorTokens,
|
|
53685
|
+
cacheReadInputTokens: stats.cacheReadInputTokens,
|
|
53686
|
+
cacheCreationInputTokens: stats.cacheCreationInputTokens,
|
|
53687
|
+
cacheWriteInputTokens: stats.cacheWriteInputTokens,
|
|
53688
|
+
cacheMissInputTokens: stats.cacheMissInputTokens,
|
|
53689
|
+
reasoningTokens: stats.reasoningTokens,
|
|
53690
|
+
billableInputTokens: stats.billableInputTokens,
|
|
53691
|
+
billableOutputTokens: stats.billableOutputTokens
|
|
53692
|
+
};
|
|
53693
|
+
}
|
|
53694
|
+
function createEmptyLogQueryStats() {
|
|
53695
|
+
return finalizeStats(createRunningStats());
|
|
52863
53696
|
}
|
|
52864
53697
|
function compareLocatedEvents(a, b, sort) {
|
|
52865
53698
|
if (a.ts !== b.ts) {
|
|
@@ -52931,7 +53764,8 @@ function eventToSummary(item) {
|
|
|
52931
53764
|
hasMetadata: identity.hasMetadata,
|
|
52932
53765
|
userIdRaw: identity.userIdRaw,
|
|
52933
53766
|
userKey: identity.userKey,
|
|
52934
|
-
sessionId: identity.sessionId
|
|
53767
|
+
sessionId: identity.sessionId,
|
|
53768
|
+
tokenUsage: item.tokenUsage
|
|
52935
53769
|
};
|
|
52936
53770
|
}
|
|
52937
53771
|
function createLogEventSummaryFromEvent(event, location) {
|
|
@@ -52943,7 +53777,8 @@ function createLogEventSummaryFromEvent(event, location) {
|
|
|
52943
53777
|
ts: Number.isFinite(ts) ? ts : 0,
|
|
52944
53778
|
level: getLevel2(event),
|
|
52945
53779
|
statusClass: getStatusClass3(event),
|
|
52946
|
-
event
|
|
53780
|
+
event,
|
|
53781
|
+
tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
|
|
52947
53782
|
});
|
|
52948
53783
|
}
|
|
52949
53784
|
function logEventMatchesQuery(event, query) {
|
|
@@ -53032,23 +53867,23 @@ function readStreamContent(baseDir, streamFile) {
|
|
|
53032
53867
|
if (!streamFile)
|
|
53033
53868
|
return { content: null, warning: null };
|
|
53034
53869
|
try {
|
|
53035
|
-
const resolvedBase =
|
|
53036
|
-
const resolvedFromFile =
|
|
53870
|
+
const resolvedBase = resolve5(baseDir);
|
|
53871
|
+
const resolvedFromFile = resolve5(streamFile);
|
|
53037
53872
|
const looksLikeStreamFile = resolvedFromFile.endsWith(".sse.raw");
|
|
53038
53873
|
if (!looksLikeStreamFile) {
|
|
53039
53874
|
return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
|
|
53040
53875
|
}
|
|
53041
|
-
if (
|
|
53042
|
-
return { content:
|
|
53876
|
+
if (existsSync5(resolvedFromFile)) {
|
|
53877
|
+
return { content: readFileSync4(resolvedFromFile, "utf-8"), warning: null };
|
|
53043
53878
|
}
|
|
53044
|
-
const fallbackPath =
|
|
53879
|
+
const fallbackPath = resolve5(resolvedBase, streamFile);
|
|
53045
53880
|
if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
|
|
53046
53881
|
return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
|
|
53047
53882
|
}
|
|
53048
|
-
if (!
|
|
53883
|
+
if (!existsSync5(fallbackPath)) {
|
|
53049
53884
|
return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
|
|
53050
53885
|
}
|
|
53051
|
-
return { content:
|
|
53886
|
+
return { content: readFileSync4(fallbackPath, "utf-8"), warning: null };
|
|
53052
53887
|
} catch (err) {
|
|
53053
53888
|
return {
|
|
53054
53889
|
content: null,
|
|
@@ -53065,6 +53900,10 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53065
53900
|
const responseBodyAvailable = event.response_body !== undefined;
|
|
53066
53901
|
const streamCaptured = Boolean(event.stream_file);
|
|
53067
53902
|
const { content: streamContent, warning: streamWarning } = readStreamContent(resolveLogBaseDir(context2.logConfig), event.stream_file);
|
|
53903
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, {
|
|
53904
|
+
baseDir: resolveLogBaseDir(context2.logConfig),
|
|
53905
|
+
streamContent: streamContent ?? undefined
|
|
53906
|
+
}) ?? null;
|
|
53068
53907
|
const hasPluginData = event.plugins_request || event.plugins_response || event.request_body_after_plugins !== undefined || event.request_url_after_plugins !== undefined || event.response_body_before_plugins !== undefined || event.response_body_after_plugins !== undefined;
|
|
53069
53908
|
const pluginsSection = hasPluginData ? {
|
|
53070
53909
|
request: event.plugins_request,
|
|
@@ -53090,7 +53929,16 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53090
53929
|
hasError: level === "error",
|
|
53091
53930
|
model: event.model_out || event.model_in,
|
|
53092
53931
|
modelIn: event.model_in,
|
|
53093
|
-
modelOut: event.model_out
|
|
53932
|
+
modelOut: event.model_out,
|
|
53933
|
+
tokenUsage
|
|
53934
|
+
},
|
|
53935
|
+
usage: {
|
|
53936
|
+
tokenUsage,
|
|
53937
|
+
requestBytes: event.request_bytes ?? 0,
|
|
53938
|
+
responseBytes: event.response_bytes ?? null,
|
|
53939
|
+
streamBytes: event.stream_bytes ?? null,
|
|
53940
|
+
streamFileBytes: event.stream_file_bytes ?? null,
|
|
53941
|
+
streamFileTruncated: event.stream_file_truncated === true
|
|
53094
53942
|
},
|
|
53095
53943
|
request: {
|
|
53096
53944
|
method: event.method,
|
|
@@ -53132,16 +53980,10 @@ async function buildLogEventDetail(id, parsed, location, context2) {
|
|
|
53132
53980
|
}
|
|
53133
53981
|
async function scanEvents(baseDir, query) {
|
|
53134
53982
|
const eventsDir = join5(baseDir, "events");
|
|
53135
|
-
if (!
|
|
53983
|
+
if (!existsSync5(eventsDir)) {
|
|
53136
53984
|
return {
|
|
53137
53985
|
items: [],
|
|
53138
|
-
stats:
|
|
53139
|
-
total: 0,
|
|
53140
|
-
errorCount: 0,
|
|
53141
|
-
errorRate: 0,
|
|
53142
|
-
avgLatencyMs: 0,
|
|
53143
|
-
p95LatencyMs: 0
|
|
53144
|
-
},
|
|
53986
|
+
stats: createEmptyLogQueryStats(),
|
|
53145
53987
|
meta: {
|
|
53146
53988
|
scannedFiles: 0,
|
|
53147
53989
|
scannedLines: 0,
|
|
@@ -53165,7 +54007,7 @@ async function scanEvents(baseDir, query) {
|
|
|
53165
54007
|
break;
|
|
53166
54008
|
}
|
|
53167
54009
|
const filePath = join5(eventsDir, `${date5}.jsonl`);
|
|
53168
|
-
if (!
|
|
54010
|
+
if (!existsSync5(filePath))
|
|
53169
54011
|
continue;
|
|
53170
54012
|
scannedFiles += 1;
|
|
53171
54013
|
const stream = createReadStream3(filePath, { encoding: "utf-8" });
|
|
@@ -53227,6 +54069,7 @@ async function scanEvents(baseDir, query) {
|
|
|
53227
54069
|
continue;
|
|
53228
54070
|
if (!containsKeyword(event, query.q))
|
|
53229
54071
|
continue;
|
|
54072
|
+
const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
|
|
53230
54073
|
const located = {
|
|
53231
54074
|
id: encodeEventId(date5, lineNumber),
|
|
53232
54075
|
date: date5,
|
|
@@ -53234,7 +54077,8 @@ async function scanEvents(baseDir, query) {
|
|
|
53234
54077
|
ts,
|
|
53235
54078
|
level,
|
|
53236
54079
|
statusClass,
|
|
53237
|
-
event
|
|
54080
|
+
event,
|
|
54081
|
+
tokenUsage
|
|
53238
54082
|
};
|
|
53239
54083
|
updateRunningStats(runningStats, located);
|
|
53240
54084
|
insertBoundedEvent(items, located, query.sort, maxKeep);
|
|
@@ -53294,13 +54138,7 @@ async function queryLogEventsInternal(context2, input, maxLimit) {
|
|
|
53294
54138
|
items: [],
|
|
53295
54139
|
nextCursor: null,
|
|
53296
54140
|
hasMore: false,
|
|
53297
|
-
stats:
|
|
53298
|
-
total: 0,
|
|
53299
|
-
errorCount: 0,
|
|
53300
|
-
errorRate: 0,
|
|
53301
|
-
avgLatencyMs: 0,
|
|
53302
|
-
p95LatencyMs: 0
|
|
53303
|
-
},
|
|
54141
|
+
stats: createEmptyLogQueryStats(),
|
|
53304
54142
|
meta: {
|
|
53305
54143
|
scannedFiles: 0,
|
|
53306
54144
|
scannedLines: 0,
|
|
@@ -53352,7 +54190,7 @@ async function getLogEventDetailById(context2, id) {
|
|
|
53352
54190
|
const { date: date5, line: line2 } = decodeEventId(id);
|
|
53353
54191
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
53354
54192
|
const filePath = join5(baseDir, "events", `${date5}.jsonl`);
|
|
53355
|
-
if (!
|
|
54193
|
+
if (!existsSync5(filePath))
|
|
53356
54194
|
return null;
|
|
53357
54195
|
const stream = createReadStream3(filePath, { encoding: "utf-8" });
|
|
53358
54196
|
const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -53383,6 +54221,7 @@ function escapeCsvValue(value) {
|
|
|
53383
54221
|
return text2;
|
|
53384
54222
|
}
|
|
53385
54223
|
function toCsvRow(item) {
|
|
54224
|
+
const usage = item.tokenUsage;
|
|
53386
54225
|
return [
|
|
53387
54226
|
item.id,
|
|
53388
54227
|
item.ts,
|
|
@@ -53403,7 +54242,36 @@ function toCsvRow(item) {
|
|
|
53403
54242
|
item.userKey ?? "",
|
|
53404
54243
|
item.sessionId ?? "",
|
|
53405
54244
|
item.message,
|
|
53406
|
-
item.errorType ?? ""
|
|
54245
|
+
item.errorType ?? "",
|
|
54246
|
+
usage?.inputTokens ?? "",
|
|
54247
|
+
usage?.outputTokens ?? "",
|
|
54248
|
+
usage?.totalTokens ?? "",
|
|
54249
|
+
usage?.cachedInputTokens ?? "",
|
|
54250
|
+
usage?.cacheHitInputTokens ?? "",
|
|
54251
|
+
usage?.cacheHitRate ?? "",
|
|
54252
|
+
usage?.cacheHitRateDenominatorTokens ?? "",
|
|
54253
|
+
usage?.cacheReadInputTokens ?? "",
|
|
54254
|
+
usage?.cacheCreationInputTokens ?? "",
|
|
54255
|
+
usage?.cacheCreationInputTokens5m ?? "",
|
|
54256
|
+
usage?.cacheCreationInputTokens1h ?? "",
|
|
54257
|
+
usage?.cacheWriteInputTokens ?? "",
|
|
54258
|
+
usage?.cacheMissInputTokens ?? "",
|
|
54259
|
+
usage?.reasoningTokens ?? "",
|
|
54260
|
+
usage?.audioInputTokens ?? "",
|
|
54261
|
+
usage?.audioOutputTokens ?? "",
|
|
54262
|
+
usage?.textInputTokens ?? "",
|
|
54263
|
+
usage?.textOutputTokens ?? "",
|
|
54264
|
+
usage?.acceptedPredictionTokens ?? "",
|
|
54265
|
+
usage?.rejectedPredictionTokens ?? "",
|
|
54266
|
+
usage?.toolUsePromptTokens ?? "",
|
|
54267
|
+
usage?.billableInputTokens ?? "",
|
|
54268
|
+
usage?.billableOutputTokens ?? "",
|
|
54269
|
+
usage?.creditUsage ?? "",
|
|
54270
|
+
usage?.cost ?? "",
|
|
54271
|
+
usage?.providerStyle ?? "",
|
|
54272
|
+
usage?.source ?? "",
|
|
54273
|
+
usage?.rawUsagePath ?? "",
|
|
54274
|
+
usage?.cacheHitRateFormula ?? ""
|
|
53407
54275
|
].map(escapeCsvValue).join(",");
|
|
53408
54276
|
}
|
|
53409
54277
|
function createCsvExportStream(items) {
|
|
@@ -53428,7 +54296,36 @@ function createCsvExportStream(items) {
|
|
|
53428
54296
|
"userKey",
|
|
53429
54297
|
"sessionId",
|
|
53430
54298
|
"message",
|
|
53431
|
-
"errorType"
|
|
54299
|
+
"errorType",
|
|
54300
|
+
"usage.inputTokens",
|
|
54301
|
+
"usage.outputTokens",
|
|
54302
|
+
"usage.totalTokens",
|
|
54303
|
+
"usage.cachedInputTokens",
|
|
54304
|
+
"usage.cacheHitInputTokens",
|
|
54305
|
+
"usage.cacheHitRate",
|
|
54306
|
+
"usage.cacheHitRateDenominatorTokens",
|
|
54307
|
+
"usage.cacheReadInputTokens",
|
|
54308
|
+
"usage.cacheCreationInputTokens",
|
|
54309
|
+
"usage.cacheCreationInputTokens5m",
|
|
54310
|
+
"usage.cacheCreationInputTokens1h",
|
|
54311
|
+
"usage.cacheWriteInputTokens",
|
|
54312
|
+
"usage.cacheMissInputTokens",
|
|
54313
|
+
"usage.reasoningTokens",
|
|
54314
|
+
"usage.audioInputTokens",
|
|
54315
|
+
"usage.audioOutputTokens",
|
|
54316
|
+
"usage.textInputTokens",
|
|
54317
|
+
"usage.textOutputTokens",
|
|
54318
|
+
"usage.acceptedPredictionTokens",
|
|
54319
|
+
"usage.rejectedPredictionTokens",
|
|
54320
|
+
"usage.toolUsePromptTokens",
|
|
54321
|
+
"usage.billableInputTokens",
|
|
54322
|
+
"usage.billableOutputTokens",
|
|
54323
|
+
"usage.creditUsage",
|
|
54324
|
+
"usage.cost",
|
|
54325
|
+
"usage.providerStyle",
|
|
54326
|
+
"usage.source",
|
|
54327
|
+
"usage.rawUsagePath",
|
|
54328
|
+
"usage.cacheHitRateFormula"
|
|
53432
54329
|
];
|
|
53433
54330
|
return new ReadableStream({
|
|
53434
54331
|
start(controller) {
|
|
@@ -53507,7 +54404,7 @@ function parseBooleanFlag(value) {
|
|
|
53507
54404
|
}
|
|
53508
54405
|
|
|
53509
54406
|
// src/log-sessions.ts
|
|
53510
|
-
import { createReadStream as createReadStream4, existsSync as
|
|
54407
|
+
import { createReadStream as createReadStream4, existsSync as existsSync6 } from "fs";
|
|
53511
54408
|
import { join as join6 } from "path";
|
|
53512
54409
|
import { createInterface as createInterface3 } from "readline";
|
|
53513
54410
|
var MAX_LINES_SCANNED3 = 250000;
|
|
@@ -53630,7 +54527,7 @@ async function queryLogSessions(context2, input) {
|
|
|
53630
54527
|
}
|
|
53631
54528
|
const baseDir = resolveLogBaseDir(context2.logConfig);
|
|
53632
54529
|
const eventsDir = join6(baseDir, "events");
|
|
53633
|
-
if (!
|
|
54530
|
+
if (!existsSync6(eventsDir)) {
|
|
53634
54531
|
return createEmptyResult(normalized.fromMs, normalized.toMs);
|
|
53635
54532
|
}
|
|
53636
54533
|
const usersMap = new Map;
|
|
@@ -53649,7 +54546,7 @@ async function queryLogSessions(context2, input) {
|
|
|
53649
54546
|
break;
|
|
53650
54547
|
}
|
|
53651
54548
|
const filePath = join6(eventsDir, `${date5}.jsonl`);
|
|
53652
|
-
if (!
|
|
54549
|
+
if (!existsSync6(filePath))
|
|
53653
54550
|
continue;
|
|
53654
54551
|
scannedFiles += 1;
|
|
53655
54552
|
const stream = createReadStream4(filePath, { encoding: "utf-8" });
|
|
@@ -53757,7 +54654,7 @@ async function queryLogSessions(context2, input) {
|
|
|
53757
54654
|
}
|
|
53758
54655
|
|
|
53759
54656
|
// src/log-storage.ts
|
|
53760
|
-
import { existsSync as
|
|
54657
|
+
import { existsSync as existsSync7, promises as fsPromises } from "fs";
|
|
53761
54658
|
import { join as join7 } from "path";
|
|
53762
54659
|
var cachedStorage = null;
|
|
53763
54660
|
var calculationPromise = null;
|
|
@@ -53766,7 +54663,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
|
|
|
53766
54663
|
var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
|
|
53767
54664
|
var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
|
|
53768
54665
|
async function calculateDirSize(dirPath) {
|
|
53769
|
-
if (!
|
|
54666
|
+
if (!existsSync7(dirPath)) {
|
|
53770
54667
|
return { bytes: 0, fileCount: 0 };
|
|
53771
54668
|
}
|
|
53772
54669
|
let bytes = 0;
|
|
@@ -53821,7 +54718,7 @@ async function doCalculateStorage(logConfig) {
|
|
|
53821
54718
|
};
|
|
53822
54719
|
}
|
|
53823
54720
|
async function calculateIndexSize(baseDir) {
|
|
53824
|
-
if (!
|
|
54721
|
+
if (!existsSync7(baseDir)) {
|
|
53825
54722
|
return { bytes: 0, fileCount: 0 };
|
|
53826
54723
|
}
|
|
53827
54724
|
let bytes = 0;
|
|
@@ -53899,7 +54796,15 @@ function subscribeLogEvents(subscriber) {
|
|
|
53899
54796
|
}
|
|
53900
54797
|
|
|
53901
54798
|
// src/logger.ts
|
|
53902
|
-
import {
|
|
54799
|
+
import {
|
|
54800
|
+
appendFileSync,
|
|
54801
|
+
closeSync as closeSync2,
|
|
54802
|
+
existsSync as existsSync8,
|
|
54803
|
+
mkdirSync as mkdirSync3,
|
|
54804
|
+
openSync as openSync2,
|
|
54805
|
+
statSync as statSync3,
|
|
54806
|
+
writeSync
|
|
54807
|
+
} from "fs";
|
|
53903
54808
|
import { join as join8 } from "path";
|
|
53904
54809
|
class Logger {
|
|
53905
54810
|
baseDir;
|
|
@@ -53928,13 +54833,13 @@ class Logger {
|
|
|
53928
54833
|
}
|
|
53929
54834
|
ensureDirs() {
|
|
53930
54835
|
for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
|
|
53931
|
-
if (!
|
|
54836
|
+
if (!existsSync8(dir))
|
|
53932
54837
|
mkdirSync3(dir, { recursive: true });
|
|
53933
54838
|
}
|
|
53934
54839
|
}
|
|
53935
54840
|
ensureStreamDateDir(dateStr) {
|
|
53936
54841
|
const dir = join8(this.streamsDir, dateStr);
|
|
53937
|
-
if (!
|
|
54842
|
+
if (!existsSync8(dir))
|
|
53938
54843
|
mkdirSync3(dir, { recursive: true });
|
|
53939
54844
|
return dir;
|
|
53940
54845
|
}
|
|
@@ -53942,11 +54847,12 @@ class Logger {
|
|
|
53942
54847
|
if (!this._enabled)
|
|
53943
54848
|
return;
|
|
53944
54849
|
try {
|
|
54850
|
+
const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
|
|
53945
54851
|
this.ensureDirs();
|
|
53946
|
-
const dateStr =
|
|
54852
|
+
const dateStr = enrichedEvent.ts_start.slice(0, 10);
|
|
53947
54853
|
const filePath = join8(this.eventsDir, `${dateStr}.jsonl`);
|
|
53948
|
-
const offset =
|
|
53949
|
-
const line2 = `${JSON.stringify(
|
|
54854
|
+
const offset = existsSync8(filePath) ? statSync3(filePath).size : 0;
|
|
54855
|
+
const line2 = `${JSON.stringify(enrichedEvent)}
|
|
53950
54856
|
`;
|
|
53951
54857
|
appendFileSync(filePath, line2);
|
|
53952
54858
|
const id = encodeOffsetLogEventId(dateStr, offset);
|
|
@@ -53956,29 +54862,80 @@ class Logger {
|
|
|
53956
54862
|
date: dateStr,
|
|
53957
54863
|
offset,
|
|
53958
54864
|
byteLength: Buffer.byteLength(line2),
|
|
53959
|
-
event
|
|
54865
|
+
event: enrichedEvent
|
|
53960
54866
|
});
|
|
53961
|
-
publishLogEvent({ id, date: dateStr, filePath, offset, event });
|
|
54867
|
+
publishLogEvent({ id, date: dateStr, filePath, offset, event: enrichedEvent });
|
|
53962
54868
|
} catch (err) {
|
|
53963
54869
|
console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
|
|
53964
54870
|
}
|
|
53965
54871
|
}
|
|
53966
|
-
|
|
53967
|
-
if (!this._enabled || !this._streamsEnabled)
|
|
53968
|
-
return
|
|
54872
|
+
openStreamCapture(requestId, dateStr) {
|
|
54873
|
+
if (!this._enabled || !this._streamsEnabled) {
|
|
54874
|
+
return makeNoopStreamCaptureHandle();
|
|
54875
|
+
}
|
|
54876
|
+
const maxStreamBytes = this.maxStreamBytes;
|
|
54877
|
+
const truncationMarker = Buffer.from(`
|
|
54878
|
+
[TRUNCATED]`);
|
|
54879
|
+
let filePath;
|
|
54880
|
+
let fd;
|
|
53969
54881
|
try {
|
|
53970
54882
|
const dir = this.ensureStreamDateDir(dateStr);
|
|
53971
|
-
|
|
53972
|
-
|
|
53973
|
-
[TRUNCATED]` : content;
|
|
53974
|
-
writeFileSync3(filePath, toWrite);
|
|
53975
|
-
return filePath;
|
|
54883
|
+
filePath = join8(dir, `${requestId}.sse.raw`);
|
|
54884
|
+
fd = openSync2(filePath, "a");
|
|
53976
54885
|
} catch (err) {
|
|
53977
|
-
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\
|
|
53978
|
-
return
|
|
54886
|
+
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
|
|
54887
|
+
return makeNoopStreamCaptureHandle();
|
|
53979
54888
|
}
|
|
54889
|
+
let bytes = 0;
|
|
54890
|
+
let truncated = false;
|
|
54891
|
+
let finalized = false;
|
|
54892
|
+
return {
|
|
54893
|
+
filePath,
|
|
54894
|
+
write(chunk) {
|
|
54895
|
+
if (finalized || truncated || fd == null)
|
|
54896
|
+
return;
|
|
54897
|
+
try {
|
|
54898
|
+
if (bytes + chunk.byteLength > maxStreamBytes) {
|
|
54899
|
+
const remaining = Math.max(0, maxStreamBytes - bytes);
|
|
54900
|
+
if (remaining > 0) {
|
|
54901
|
+
writeSync(fd, chunk.subarray(0, remaining));
|
|
54902
|
+
bytes += remaining;
|
|
54903
|
+
}
|
|
54904
|
+
writeSync(fd, truncationMarker);
|
|
54905
|
+
truncated = true;
|
|
54906
|
+
return;
|
|
54907
|
+
}
|
|
54908
|
+
writeSync(fd, chunk);
|
|
54909
|
+
bytes += chunk.byteLength;
|
|
54910
|
+
} catch (err) {
|
|
54911
|
+
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
|
|
54912
|
+
truncated = true;
|
|
54913
|
+
}
|
|
54914
|
+
},
|
|
54915
|
+
finalize() {
|
|
54916
|
+
if (finalized)
|
|
54917
|
+
return { bytesWritten: bytes, truncated, filePath };
|
|
54918
|
+
finalized = true;
|
|
54919
|
+
if (fd != null) {
|
|
54920
|
+
try {
|
|
54921
|
+
closeSync2(fd);
|
|
54922
|
+
} catch {}
|
|
54923
|
+
fd = null;
|
|
54924
|
+
}
|
|
54925
|
+
return { bytesWritten: bytes, truncated, filePath };
|
|
54926
|
+
}
|
|
54927
|
+
};
|
|
53980
54928
|
}
|
|
53981
54929
|
}
|
|
54930
|
+
function makeNoopStreamCaptureHandle() {
|
|
54931
|
+
return {
|
|
54932
|
+
filePath: null,
|
|
54933
|
+
write() {},
|
|
54934
|
+
finalize() {
|
|
54935
|
+
return { bytesWritten: 0, truncated: false, filePath: null };
|
|
54936
|
+
}
|
|
54937
|
+
};
|
|
54938
|
+
}
|
|
53982
54939
|
var instance = null;
|
|
53983
54940
|
function initLogger(baseDir, config2) {
|
|
53984
54941
|
instance = new Logger(baseDir, config2);
|
|
@@ -55198,7 +56155,7 @@ var openAPISpec = {
|
|
|
55198
56155
|
// src/plugin-loader.ts
|
|
55199
56156
|
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
55200
56157
|
import { tmpdir } from "os";
|
|
55201
|
-
import { join as join9, resolve as
|
|
56158
|
+
import { join as join9, resolve as resolve6 } from "path";
|
|
55202
56159
|
function isLocalPath(pkg) {
|
|
55203
56160
|
return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
|
|
55204
56161
|
}
|
|
@@ -55254,7 +56211,7 @@ async function importPlugin(pkg, configDir) {
|
|
|
55254
56211
|
const localPath = await fetchRemotePlugin(pkg);
|
|
55255
56212
|
modulePath = `${localPath}?t=${Date.now()}`;
|
|
55256
56213
|
} else if (isLocalPath(pkg)) {
|
|
55257
|
-
const absolutePath =
|
|
56214
|
+
const absolutePath = resolve6(configDir, pkg);
|
|
55258
56215
|
modulePath = `${absolutePath}?t=${Date.now()}`;
|
|
55259
56216
|
} else {
|
|
55260
56217
|
modulePath = pkg;
|
|
@@ -55370,11 +56327,6 @@ class PluginManager {
|
|
|
55370
56327
|
}
|
|
55371
56328
|
}
|
|
55372
56329
|
|
|
55373
|
-
// src/proxy.ts
|
|
55374
|
-
import { appendFile, readFile, unlink } from "fs/promises";
|
|
55375
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
55376
|
-
import { join as join10 } from "path";
|
|
55377
|
-
|
|
55378
56330
|
// src/plugin-engine.ts
|
|
55379
56331
|
async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
|
|
55380
56332
|
let currentUrl = url2;
|
|
@@ -55564,28 +56516,6 @@ function buildLogEvent(logMeta, targetUrl, proxyUrl, tsEnd, overrides) {
|
|
|
55564
56516
|
...overrides
|
|
55565
56517
|
};
|
|
55566
56518
|
}
|
|
55567
|
-
function createTempStreamCapturePath(requestId) {
|
|
55568
|
-
return join10(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
|
|
55569
|
-
}
|
|
55570
|
-
async function appendTempStreamCapture(filePath, chunk) {
|
|
55571
|
-
await appendFile(filePath, chunk);
|
|
55572
|
-
}
|
|
55573
|
-
async function flushTempCaptureToLogger(tempPath, requestId, dateStr, logger) {
|
|
55574
|
-
if (!logger)
|
|
55575
|
-
return null;
|
|
55576
|
-
try {
|
|
55577
|
-
const text2 = await readFile(tempPath, "utf-8").catch((err) => {
|
|
55578
|
-
if (err.code === "ENOENT")
|
|
55579
|
-
return "";
|
|
55580
|
-
throw err;
|
|
55581
|
-
});
|
|
55582
|
-
return logger.writeStreamFile(requestId, dateStr, text2);
|
|
55583
|
-
} finally {
|
|
55584
|
-
await unlink(tempPath).catch(() => {
|
|
55585
|
-
return;
|
|
55586
|
-
});
|
|
55587
|
-
}
|
|
55588
|
-
}
|
|
55589
56519
|
async function proxyRequest(c2, options) {
|
|
55590
56520
|
const { logMeta, plugins, pluginConfigs } = options;
|
|
55591
56521
|
const logger = getLogger();
|
|
@@ -55593,19 +56523,20 @@ async function proxyRequest(c2, options) {
|
|
|
55593
56523
|
const hasPlugins = plugins && plugins.length > 0;
|
|
55594
56524
|
let targetUrl = options.targetUrl;
|
|
55595
56525
|
let headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
|
|
55596
|
-
let
|
|
56526
|
+
let currentBody = options.body;
|
|
56527
|
+
const pluginCtx = Object.freeze({
|
|
56528
|
+
requestId: logMeta.requestId,
|
|
56529
|
+
provider: logMeta.provider,
|
|
56530
|
+
modelIn: logMeta.modelIn,
|
|
56531
|
+
modelOut: logMeta.modelOut,
|
|
56532
|
+
routeType: logMeta.routeType,
|
|
56533
|
+
isStream: logMeta.isStream
|
|
56534
|
+
});
|
|
56535
|
+
const wantsBodyLog = shouldLog && logger?.bodyPolicy !== "off";
|
|
56536
|
+
const requestBodySnapshot = wantsBodyLog ? JSON.parse(JSON.stringify(options.body)) : undefined;
|
|
55597
56537
|
const pluginLogOverrides = {};
|
|
55598
56538
|
if (hasPlugins) {
|
|
55599
|
-
const
|
|
55600
|
-
const ctx = {
|
|
55601
|
-
requestId: logMeta.requestId,
|
|
55602
|
-
provider: logMeta.provider,
|
|
55603
|
-
modelIn: logMeta.modelIn,
|
|
55604
|
-
modelOut: logMeta.modelOut,
|
|
55605
|
-
routeType: logMeta.routeType,
|
|
55606
|
-
isStream: logMeta.isStream
|
|
55607
|
-
};
|
|
55608
|
-
const result = await executeRequestPlugins(plugins, ctx, targetUrl, headers, bodyObj);
|
|
56539
|
+
const result = await executeRequestPlugins(plugins, pluginCtx, targetUrl, headers, currentBody);
|
|
55609
56540
|
if (pluginConfigs) {
|
|
55610
56541
|
pluginLogOverrides.plugins_request = pluginConfigs;
|
|
55611
56542
|
}
|
|
@@ -55614,20 +56545,20 @@ async function proxyRequest(c2, options) {
|
|
|
55614
56545
|
pluginLogOverrides.request_url_after_plugins = targetUrl;
|
|
55615
56546
|
}
|
|
55616
56547
|
headers = result.headers;
|
|
55617
|
-
|
|
55618
|
-
|
|
55619
|
-
bodyStr = newBodyStr;
|
|
56548
|
+
if (result.body !== currentBody) {
|
|
56549
|
+
currentBody = result.body;
|
|
55620
56550
|
pluginLogOverrides.request_body_after_plugins = result.body;
|
|
55621
56551
|
}
|
|
55622
56552
|
}
|
|
55623
|
-
const
|
|
56553
|
+
const wireBody = JSON.stringify(currentBody);
|
|
56554
|
+
const requestBody = requestBodySnapshot;
|
|
55624
56555
|
const proxy = options.proxy?.trim() ? options.proxy.trim() : undefined;
|
|
55625
56556
|
let upstreamRes;
|
|
55626
56557
|
try {
|
|
55627
56558
|
upstreamRes = await fetch(targetUrl, {
|
|
55628
56559
|
method: c2.req.method,
|
|
55629
56560
|
headers,
|
|
55630
|
-
body:
|
|
56561
|
+
body: wireBody,
|
|
55631
56562
|
...proxy ? { proxy } : {},
|
|
55632
56563
|
decompress: true
|
|
55633
56564
|
});
|
|
@@ -55657,15 +56588,7 @@ async function proxyRequest(c2, options) {
|
|
|
55657
56588
|
let sseHeaders = responseHeaders;
|
|
55658
56589
|
let sseTransform = null;
|
|
55659
56590
|
if (hasPlugins) {
|
|
55660
|
-
const
|
|
55661
|
-
requestId: logMeta.requestId,
|
|
55662
|
-
provider: logMeta.provider,
|
|
55663
|
-
modelIn: logMeta.modelIn,
|
|
55664
|
-
modelOut: logMeta.modelOut,
|
|
55665
|
-
routeType: logMeta.routeType,
|
|
55666
|
-
isStream: logMeta.isStream
|
|
55667
|
-
};
|
|
55668
|
-
const sseResult = await createSSEPluginTransform(plugins, ctx, upstreamRes.status, responseHeaders);
|
|
56591
|
+
const sseResult = await createSSEPluginTransform(plugins, pluginCtx, upstreamRes.status, responseHeaders);
|
|
55669
56592
|
sseStatus = sseResult.status;
|
|
55670
56593
|
sseHeaders = sseResult.headers;
|
|
55671
56594
|
sseTransform = sseResult.transform;
|
|
@@ -55674,47 +56597,75 @@ async function proxyRequest(c2, options) {
|
|
|
55674
56597
|
}
|
|
55675
56598
|
}
|
|
55676
56599
|
if (!shouldLog) {
|
|
55677
|
-
const
|
|
55678
|
-
return new Response(
|
|
56600
|
+
const outputBody = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
|
|
56601
|
+
return new Response(outputBody, {
|
|
55679
56602
|
status: sseStatus,
|
|
55680
56603
|
headers: sseHeaders
|
|
55681
56604
|
});
|
|
55682
56605
|
}
|
|
55683
|
-
const
|
|
55684
|
-
|
|
55685
|
-
|
|
55686
|
-
|
|
55687
|
-
|
|
55688
|
-
|
|
55689
|
-
|
|
55690
|
-
|
|
55691
|
-
|
|
55692
|
-
|
|
55693
|
-
|
|
55694
|
-
|
|
55695
|
-
|
|
56606
|
+
const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
|
|
56607
|
+
const tokenUsageCollector = createTokenUsageStreamCollector(`${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
|
|
56608
|
+
let upstreamBytes = 0;
|
|
56609
|
+
let writeEventCalled = false;
|
|
56610
|
+
const finalizeAndWriteEvent = () => {
|
|
56611
|
+
if (writeEventCalled)
|
|
56612
|
+
return;
|
|
56613
|
+
writeEventCalled = true;
|
|
56614
|
+
const captureResult = capture?.finalize() ?? {
|
|
56615
|
+
bytesWritten: 0,
|
|
56616
|
+
truncated: false,
|
|
56617
|
+
filePath: null
|
|
56618
|
+
};
|
|
56619
|
+
const tokenUsage2 = tokenUsageCollector.getUsage();
|
|
56620
|
+
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
56621
|
+
upstream_status: sseStatus,
|
|
56622
|
+
content_type_res: contentTypeRes,
|
|
56623
|
+
response_headers: sseHeaders,
|
|
56624
|
+
stream_bytes: upstreamBytes,
|
|
56625
|
+
provider_request_id: providerRequestId,
|
|
56626
|
+
...captureResult.filePath != null && { stream_file: captureResult.filePath },
|
|
56627
|
+
...captureResult.bytesWritten > 0 && {
|
|
56628
|
+
stream_file_bytes: captureResult.bytesWritten
|
|
56629
|
+
},
|
|
56630
|
+
...captureResult.truncated && { stream_file_truncated: true },
|
|
56631
|
+
...tokenUsage2 && { token_usage: tokenUsage2 },
|
|
56632
|
+
...requestBody !== undefined && { request_body: requestBody },
|
|
56633
|
+
...pluginLogOverrides
|
|
56634
|
+
}));
|
|
56635
|
+
};
|
|
56636
|
+
const upstreamReader = upstreamRes.body.getReader();
|
|
56637
|
+
const tappedStream = new ReadableStream({
|
|
56638
|
+
async pull(controller) {
|
|
56639
|
+
try {
|
|
56640
|
+
const { done, value } = await upstreamReader.read();
|
|
56641
|
+
if (done) {
|
|
56642
|
+
controller.close();
|
|
56643
|
+
finalizeAndWriteEvent();
|
|
56644
|
+
return;
|
|
56645
|
+
}
|
|
56646
|
+
upstreamBytes += value.byteLength;
|
|
56647
|
+
tokenUsageCollector.addChunk(value);
|
|
56648
|
+
capture?.write(value);
|
|
56649
|
+
controller.enqueue(value);
|
|
56650
|
+
} catch (err) {
|
|
56651
|
+
finalizeAndWriteEvent();
|
|
56652
|
+
controller.error(err);
|
|
56653
|
+
}
|
|
56654
|
+
},
|
|
56655
|
+
cancel(reason) {
|
|
56656
|
+
try {
|
|
56657
|
+
upstreamReader.cancel(reason).catch(() => {
|
|
56658
|
+
return;
|
|
56659
|
+
});
|
|
56660
|
+
} finally {
|
|
56661
|
+
finalizeAndWriteEvent();
|
|
55696
56662
|
}
|
|
55697
|
-
streamFile = await flushTempCaptureToLogger(tempPath, logMeta.requestId, dateStr, logger);
|
|
55698
|
-
} catch (err) {
|
|
55699
|
-
await unlink(tempPath).catch(() => {
|
|
55700
|
-
return;
|
|
55701
|
-
});
|
|
55702
|
-
console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5904\u7406\u5931\u8D25:", err);
|
|
55703
|
-
} finally {
|
|
55704
|
-
logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
|
|
55705
|
-
upstream_status: sseStatus,
|
|
55706
|
-
content_type_res: contentTypeRes,
|
|
55707
|
-
response_headers: sseHeaders,
|
|
55708
|
-
stream_bytes: streamBytes,
|
|
55709
|
-
provider_request_id: providerRequestId,
|
|
55710
|
-
...streamFile != null && { stream_file: streamFile },
|
|
55711
|
-
...requestBody !== undefined && { request_body: requestBody },
|
|
55712
|
-
...pluginLogOverrides
|
|
55713
|
-
}));
|
|
55714
56663
|
}
|
|
55715
|
-
})
|
|
55716
|
-
|
|
55717
|
-
|
|
56664
|
+
});
|
|
56665
|
+
let stream = tappedStream;
|
|
56666
|
+
if (sseTransform)
|
|
56667
|
+
stream = stream.pipeThrough(sseTransform);
|
|
56668
|
+
return new Response(stream, {
|
|
55718
56669
|
status: sseStatus,
|
|
55719
56670
|
headers: sseHeaders
|
|
55720
56671
|
});
|
|
@@ -55723,15 +56674,7 @@ async function proxyRequest(c2, options) {
|
|
|
55723
56674
|
let responseStatus = upstreamRes.status;
|
|
55724
56675
|
let finalResponseHeaders = responseHeaders;
|
|
55725
56676
|
if (hasPlugins) {
|
|
55726
|
-
const
|
|
55727
|
-
requestId: logMeta.requestId,
|
|
55728
|
-
provider: logMeta.provider,
|
|
55729
|
-
modelIn: logMeta.modelIn,
|
|
55730
|
-
modelOut: logMeta.modelOut,
|
|
55731
|
-
routeType: logMeta.routeType,
|
|
55732
|
-
isStream: logMeta.isStream
|
|
55733
|
-
};
|
|
55734
|
-
const result = await executeJsonResponsePlugins(plugins, ctx, upstreamRes.status, responseHeaders, responseText);
|
|
56677
|
+
const result = await executeJsonResponsePlugins(plugins, pluginCtx, upstreamRes.status, responseHeaders, responseText);
|
|
55735
56678
|
if (pluginConfigs) {
|
|
55736
56679
|
pluginLogOverrides.plugins_response = pluginConfigs;
|
|
55737
56680
|
}
|
|
@@ -55752,12 +56695,14 @@ async function proxyRequest(c2, options) {
|
|
|
55752
56695
|
});
|
|
55753
56696
|
}
|
|
55754
56697
|
const responseBytes = Buffer.byteLength(responseText, "utf-8");
|
|
56698
|
+
const tokenUsage = extractTokenUsageFromResponseText(responseText, "response_body", `${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
|
|
55755
56699
|
const eventOverrides = {
|
|
55756
56700
|
upstream_status: upstreamRes.status,
|
|
55757
56701
|
content_type_res: contentTypeRes,
|
|
55758
56702
|
response_headers: finalResponseHeaders,
|
|
55759
56703
|
response_bytes: responseBytes,
|
|
55760
56704
|
provider_request_id: providerRequestId,
|
|
56705
|
+
...tokenUsage && { token_usage: tokenUsage },
|
|
55761
56706
|
...pluginLogOverrides
|
|
55762
56707
|
};
|
|
55763
56708
|
if (requestBody !== undefined) {
|
|
@@ -55808,8 +56753,10 @@ function createModelRoutingHandler(options) {
|
|
|
55808
56753
|
return c2.json({ error: `provider "${target.provider}" \u672A\u5728\u914D\u7F6E\u4E2D\u5B9A\u4E49` }, 500);
|
|
55809
56754
|
}
|
|
55810
56755
|
payload.model = target.model;
|
|
55811
|
-
const body = JSON.stringify(payload);
|
|
55812
56756
|
const targetUrl = buildTargetUrl(provider.base);
|
|
56757
|
+
const contentLengthHeader = c2.req.header("content-length");
|
|
56758
|
+
const parsedContentLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
|
|
56759
|
+
const requestBytes = Number.isInteger(parsedContentLength) && parsedContentLength >= 0 ? parsedContentLength : Buffer.byteLength(JSON.stringify(payload), "utf-8");
|
|
55813
56760
|
const logMeta = {
|
|
55814
56761
|
requestId: crypto.randomUUID(),
|
|
55815
56762
|
tsStart: Date.now(),
|
|
@@ -55823,7 +56770,7 @@ function createModelRoutingHandler(options) {
|
|
|
55823
56770
|
path: c2.req.path,
|
|
55824
56771
|
contentTypeReq: c2.req.header("content-type") ?? null,
|
|
55825
56772
|
userAgent: c2.req.header("user-agent") ?? null,
|
|
55826
|
-
requestBytes
|
|
56773
|
+
requestBytes,
|
|
55827
56774
|
requestHeaders: collectHeaders(c2.req.raw.headers)
|
|
55828
56775
|
};
|
|
55829
56776
|
const plugins = pluginManager?.getPlugins(target.provider) ?? [];
|
|
@@ -55833,7 +56780,7 @@ function createModelRoutingHandler(options) {
|
|
|
55833
56780
|
apiKey: provider.apiKey,
|
|
55834
56781
|
proxy: provider.proxy,
|
|
55835
56782
|
authType,
|
|
55836
|
-
body,
|
|
56783
|
+
body: payload,
|
|
55837
56784
|
logMeta,
|
|
55838
56785
|
plugins: plugins.length > 0 ? plugins : undefined,
|
|
55839
56786
|
pluginConfigs: pluginConfigs.length > 0 ? pluginConfigs.map((lp) => ({
|
|
@@ -56038,7 +56985,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
|
|
|
56038
56985
|
const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
|
|
56039
56986
|
const CRYPTO_SESSION_MAX = 512;
|
|
56040
56987
|
const schemaPath = getBundledSchemaPath();
|
|
56041
|
-
const schemaJson = JSON.parse(
|
|
56988
|
+
const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
|
|
56042
56989
|
const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
|
|
56043
56990
|
for (const [id, record2] of Array.from(cryptoSessions.entries())) {
|
|
56044
56991
|
if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
|
|
@@ -56643,7 +57590,7 @@ async function createApp(store, options) {
|
|
|
56643
57590
|
}
|
|
56644
57591
|
const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
|
|
56645
57592
|
options?.registerCleanup?.(stopLogStorageTask);
|
|
56646
|
-
const configDir = dirname2(
|
|
57593
|
+
const configDir = dirname2(resolve7(store.getPath()));
|
|
56647
57594
|
const pluginManager = new PluginManager(configDir);
|
|
56648
57595
|
const reloadResult = await pluginManager.reloadAll(config2.providers);
|
|
56649
57596
|
if (!reloadResult.ok) {
|