@lakphy/local-router 0.5.2 → 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/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 readFileSync4 } from "fs";
9288
- import { dirname as dirname2, resolve as resolve6 } from "path";
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 existsSync4, readFileSync as readFileSync3 } from "fs";
51793
- import { join as join5, resolve as resolve4 } from "path";
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 existsSync3,
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 = 1;
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 (!existsSync3(filePath))
52950
+ if (!existsSync4(filePath))
52300
52951
  continue;
52301
- const stats = statSync(filePath);
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 (!existsSync3(filePath))
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 = statSync(item.filePath);
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 (!existsSync3(filePath))
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 = resolve4(baseDir);
53036
- const resolvedFromFile = resolve4(streamFile);
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 (existsSync4(resolvedFromFile)) {
53042
- return { content: readFileSync3(resolvedFromFile, "utf-8"), warning: null };
53876
+ if (existsSync5(resolvedFromFile)) {
53877
+ return { content: readFileSync4(resolvedFromFile, "utf-8"), warning: null };
53043
53878
  }
53044
- const fallbackPath = resolve4(resolvedBase, streamFile);
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 (!existsSync4(fallbackPath)) {
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: readFileSync3(fallbackPath, "utf-8"), warning: null };
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 (!existsSync4(eventsDir)) {
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 (!existsSync4(filePath))
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 (!existsSync4(filePath))
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 existsSync5 } from "fs";
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 (!existsSync5(eventsDir)) {
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 (!existsSync5(filePath))
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 existsSync6, promises as fsPromises } from "fs";
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 (!existsSync6(dirPath)) {
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 (!existsSync6(baseDir)) {
54721
+ if (!existsSync7(baseDir)) {
53825
54722
  return { bytes: 0, fileCount: 0 };
53826
54723
  }
53827
54724
  let bytes = 0;
@@ -53902,10 +54799,10 @@ function subscribeLogEvents(subscriber) {
53902
54799
  import {
53903
54800
  appendFileSync,
53904
54801
  closeSync as closeSync2,
53905
- existsSync as existsSync7,
54802
+ existsSync as existsSync8,
53906
54803
  mkdirSync as mkdirSync3,
53907
54804
  openSync as openSync2,
53908
- statSync as statSync2,
54805
+ statSync as statSync3,
53909
54806
  writeSync
53910
54807
  } from "fs";
53911
54808
  import { join as join8 } from "path";
@@ -53936,13 +54833,13 @@ class Logger {
53936
54833
  }
53937
54834
  ensureDirs() {
53938
54835
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
53939
- if (!existsSync7(dir))
54836
+ if (!existsSync8(dir))
53940
54837
  mkdirSync3(dir, { recursive: true });
53941
54838
  }
53942
54839
  }
53943
54840
  ensureStreamDateDir(dateStr) {
53944
54841
  const dir = join8(this.streamsDir, dateStr);
53945
- if (!existsSync7(dir))
54842
+ if (!existsSync8(dir))
53946
54843
  mkdirSync3(dir, { recursive: true });
53947
54844
  return dir;
53948
54845
  }
@@ -53950,11 +54847,12 @@ class Logger {
53950
54847
  if (!this._enabled)
53951
54848
  return;
53952
54849
  try {
54850
+ const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
53953
54851
  this.ensureDirs();
53954
- const dateStr = event.ts_start.slice(0, 10);
54852
+ const dateStr = enrichedEvent.ts_start.slice(0, 10);
53955
54853
  const filePath = join8(this.eventsDir, `${dateStr}.jsonl`);
53956
- const offset = existsSync7(filePath) ? statSync2(filePath).size : 0;
53957
- const line2 = `${JSON.stringify(event)}
54854
+ const offset = existsSync8(filePath) ? statSync3(filePath).size : 0;
54855
+ const line2 = `${JSON.stringify(enrichedEvent)}
53958
54856
  `;
53959
54857
  appendFileSync(filePath, line2);
53960
54858
  const id = encodeOffsetLogEventId(dateStr, offset);
@@ -53964,9 +54862,9 @@ class Logger {
53964
54862
  date: dateStr,
53965
54863
  offset,
53966
54864
  byteLength: Buffer.byteLength(line2),
53967
- event
54865
+ event: enrichedEvent
53968
54866
  });
53969
- publishLogEvent({ id, date: dateStr, filePath, offset, event });
54867
+ publishLogEvent({ id, date: dateStr, filePath, offset, event: enrichedEvent });
53970
54868
  } catch (err) {
53971
54869
  console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
53972
54870
  }
@@ -55257,7 +56155,7 @@ var openAPISpec = {
55257
56155
  // src/plugin-loader.ts
55258
56156
  import { mkdtemp, rm, writeFile } from "fs/promises";
55259
56157
  import { tmpdir } from "os";
55260
- import { join as join9, resolve as resolve5 } from "path";
56158
+ import { join as join9, resolve as resolve6 } from "path";
55261
56159
  function isLocalPath(pkg) {
55262
56160
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
55263
56161
  }
@@ -55313,7 +56211,7 @@ async function importPlugin(pkg, configDir) {
55313
56211
  const localPath = await fetchRemotePlugin(pkg);
55314
56212
  modulePath = `${localPath}?t=${Date.now()}`;
55315
56213
  } else if (isLocalPath(pkg)) {
55316
- const absolutePath = resolve5(configDir, pkg);
56214
+ const absolutePath = resolve6(configDir, pkg);
55317
56215
  modulePath = `${absolutePath}?t=${Date.now()}`;
55318
56216
  } else {
55319
56217
  modulePath = pkg;
@@ -55706,6 +56604,7 @@ async function proxyRequest(c2, options) {
55706
56604
  });
55707
56605
  }
55708
56606
  const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
56607
+ const tokenUsageCollector = createTokenUsageStreamCollector(`${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
55709
56608
  let upstreamBytes = 0;
55710
56609
  let writeEventCalled = false;
55711
56610
  const finalizeAndWriteEvent = () => {
@@ -55717,6 +56616,7 @@ async function proxyRequest(c2, options) {
55717
56616
  truncated: false,
55718
56617
  filePath: null
55719
56618
  };
56619
+ const tokenUsage2 = tokenUsageCollector.getUsage();
55720
56620
  logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
55721
56621
  upstream_status: sseStatus,
55722
56622
  content_type_res: contentTypeRes,
@@ -55728,6 +56628,7 @@ async function proxyRequest(c2, options) {
55728
56628
  stream_file_bytes: captureResult.bytesWritten
55729
56629
  },
55730
56630
  ...captureResult.truncated && { stream_file_truncated: true },
56631
+ ...tokenUsage2 && { token_usage: tokenUsage2 },
55731
56632
  ...requestBody !== undefined && { request_body: requestBody },
55732
56633
  ...pluginLogOverrides
55733
56634
  }));
@@ -55743,6 +56644,7 @@ async function proxyRequest(c2, options) {
55743
56644
  return;
55744
56645
  }
55745
56646
  upstreamBytes += value.byteLength;
56647
+ tokenUsageCollector.addChunk(value);
55746
56648
  capture?.write(value);
55747
56649
  controller.enqueue(value);
55748
56650
  } catch (err) {
@@ -55793,12 +56695,14 @@ async function proxyRequest(c2, options) {
55793
56695
  });
55794
56696
  }
55795
56697
  const responseBytes = Buffer.byteLength(responseText, "utf-8");
56698
+ const tokenUsage = extractTokenUsageFromResponseText(responseText, "response_body", `${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
55796
56699
  const eventOverrides = {
55797
56700
  upstream_status: upstreamRes.status,
55798
56701
  content_type_res: contentTypeRes,
55799
56702
  response_headers: finalResponseHeaders,
55800
56703
  response_bytes: responseBytes,
55801
56704
  provider_request_id: providerRequestId,
56705
+ ...tokenUsage && { token_usage: tokenUsage },
55802
56706
  ...pluginLogOverrides
55803
56707
  };
55804
56708
  if (requestBody !== undefined) {
@@ -56081,7 +56985,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
56081
56985
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
56082
56986
  const CRYPTO_SESSION_MAX = 512;
56083
56987
  const schemaPath = getBundledSchemaPath();
56084
- const schemaJson = JSON.parse(readFileSync4(schemaPath, "utf-8"));
56988
+ const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
56085
56989
  const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
56086
56990
  for (const [id, record2] of Array.from(cryptoSessions.entries())) {
56087
56991
  if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
@@ -56686,7 +57590,7 @@ async function createApp(store, options) {
56686
57590
  }
56687
57591
  const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
56688
57592
  options?.registerCleanup?.(stopLogStorageTask);
56689
- const configDir = dirname2(resolve6(store.getPath()));
57593
+ const configDir = dirname2(resolve7(store.getPath()));
56690
57594
  const pluginManager = new PluginManager(configDir);
56691
57595
  const reloadResult = await pluginManager.reloadAll(config2.providers);
56692
57596
  if (!reloadResult.ok) {