@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/cli.js CHANGED
@@ -11166,7 +11166,7 @@ __export(exports_config_extra, {
11166
11166
  configDiff: () => configDiff,
11167
11167
  configBackupsList: () => configBackupsList
11168
11168
  });
11169
- import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
11169
+ import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
11170
11170
  import { dirname as dirname4, join as join12 } from "path";
11171
11171
  import { parseArgs as parseArgs2 } from "util";
11172
11172
  function maskApiKey(k) {
@@ -11245,15 +11245,15 @@ async function configDiff(args, flags) {
11245
11245
  strict: false
11246
11246
  });
11247
11247
  const path = resolveConfigPath(parsed.values.config);
11248
- if (!existsSync9(path)) {
11248
+ if (!existsSync10(path)) {
11249
11249
  throw new CliError("CONFIG_NOT_FOUND", `\u914D\u7F6E\u4E0D\u5B58\u5728: ${path}`);
11250
11250
  }
11251
- const after = readFileSync8(path, "utf-8");
11251
+ const after = readFileSync9(path, "utf-8");
11252
11252
  const against = parsed.values.against;
11253
11253
  let beforePath;
11254
11254
  if (!against) {
11255
11255
  const backupDir = join12(dirname4(path), ".backups");
11256
- if (!existsSync9(backupDir)) {
11256
+ if (!existsSync10(backupDir)) {
11257
11257
  throw new CliError("CONFIG_NOT_FOUND", "\u6CA1\u6709\u5907\u4EFD\u53EF\u5BF9\u6BD4", {
11258
11258
  hint: "\u6307\u5B9A --against <path|backup-id>"
11259
11259
  });
@@ -11263,20 +11263,20 @@ async function configDiff(args, flags) {
11263
11263
  throw new CliError("CONFIG_NOT_FOUND", "\u5907\u4EFD\u76EE\u5F55\u4E3A\u7A7A");
11264
11264
  }
11265
11265
  beforePath = join12(backupDir, files[0]);
11266
- } else if (existsSync9(against)) {
11266
+ } else if (existsSync10(against)) {
11267
11267
  beforePath = against;
11268
11268
  } else {
11269
11269
  const backupDir = join12(dirname4(path), ".backups");
11270
11270
  const candidate = join12(backupDir, `config-${against}.json5`);
11271
- if (existsSync9(candidate))
11271
+ if (existsSync10(candidate))
11272
11272
  beforePath = candidate;
11273
- else if (existsSync9(join12(backupDir, against)))
11273
+ else if (existsSync10(join12(backupDir, against)))
11274
11274
  beforePath = join12(backupDir, against);
11275
11275
  else {
11276
11276
  throw new CliError("CONFIG_NOT_FOUND", `--against \u4E0D\u5B58\u5728: ${against}`);
11277
11277
  }
11278
11278
  }
11279
- const before = readFileSync8(beforePath, "utf-8");
11279
+ const before = readFileSync9(beforePath, "utf-8");
11280
11280
  const diff = computeLineDiff(before, after);
11281
11281
  const { added, removed } = summarizeDiff(diff);
11282
11282
  emitResult(ctx, {
@@ -11320,10 +11320,10 @@ async function configImport(args, flags) {
11320
11320
  if (!file2 || file2 === "-") {
11321
11321
  raw2 = await readStdin();
11322
11322
  } else {
11323
- if (!existsSync9(file2)) {
11323
+ if (!existsSync10(file2)) {
11324
11324
  throw new CliError("CONFIG_NOT_FOUND", `\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${file2}`);
11325
11325
  }
11326
- raw2 = readFileSync8(file2, "utf-8");
11326
+ raw2 = readFileSync9(file2, "utf-8");
11327
11327
  }
11328
11328
  let parsedInput;
11329
11329
  try {
@@ -11332,7 +11332,7 @@ async function configImport(args, flags) {
11332
11332
  throw new CliError("CONFIG_INVALID", `\u8F93\u5165\u4E0D\u662F\u5408\u6CD5 JSON5: ${err instanceof Error ? err.message : err}`);
11333
11333
  }
11334
11334
  const path = resolveConfigPath(parsed.values.config);
11335
- const current = existsSync9(path) ? loadConfig(path) : null;
11335
+ const current = existsSync10(path) ? loadConfig(path) : null;
11336
11336
  let next;
11337
11337
  if (parsed.values.replace || !current) {
11338
11338
  next = parsedInput;
@@ -11380,10 +11380,10 @@ async function configPatch(args, flags) {
11380
11380
  if (!file2 || file2 === "-") {
11381
11381
  raw2 = await readStdin();
11382
11382
  } else {
11383
- if (!existsSync9(file2)) {
11383
+ if (!existsSync10(file2)) {
11384
11384
  throw new CliError("CONFIG_NOT_FOUND", `patch \u6587\u4EF6\u4E0D\u5B58\u5728: ${file2}`);
11385
11385
  }
11386
- raw2 = readFileSync8(file2, "utf-8");
11386
+ raw2 = readFileSync9(file2, "utf-8");
11387
11387
  }
11388
11388
  let ops;
11389
11389
  try {
@@ -11427,11 +11427,11 @@ ${result.diff}`
11427
11427
  }
11428
11428
  function listBackups(configPath) {
11429
11429
  const dir = join12(dirname4(configPath), ".backups");
11430
- if (!existsSync9(dir))
11430
+ if (!existsSync10(dir))
11431
11431
  return [];
11432
11432
  return readdirSync(dir).filter((f) => f.startsWith("config-") && f.endsWith(".json5")).map((f) => {
11433
11433
  const full = join12(dir, f);
11434
- const st = statSync4(full);
11434
+ const st = statSync5(full);
11435
11435
  return {
11436
11436
  id: f.replace(/^config-/, "").replace(/\.json5$/, ""),
11437
11437
  path: full,
@@ -11490,7 +11490,7 @@ async function configRollback(args, flags) {
11490
11490
  details: { available: backups.map((b) => b.id) }
11491
11491
  });
11492
11492
  }
11493
- const text2 = readFileSync8(target.path, "utf-8");
11493
+ const text2 = readFileSync9(target.path, "utf-8");
11494
11494
  let parsedConfig;
11495
11495
  try {
11496
11496
  parsedConfig = dist_default.parse(text2);
@@ -11677,13 +11677,13 @@ import { parseArgs as parseArgs3 } from "util";
11677
11677
 
11678
11678
  // src/cli/process.ts
11679
11679
  init_config();
11680
- import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
11680
+ import { closeSync as closeSync3, openSync as openSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
11681
11681
  import { setTimeout as sleep } from "timers/promises";
11682
11682
  import { parseArgs } from "util";
11683
11683
 
11684
11684
  // src/index.ts
11685
- import { readFileSync as readFileSync5 } from "fs";
11686
- import { dirname as dirname3, resolve as resolve6 } from "path";
11685
+ import { readFileSync as readFileSync6 } from "fs";
11686
+ import { dirname as dirname3, resolve as resolve7 } from "path";
11687
11687
 
11688
11688
  // node_modules/.bun/@ai-sdk+provider@3.0.8/node_modules/@ai-sdk/provider/dist/index.mjs
11689
11689
  var marker = "vercel.ai.error";
@@ -52928,8 +52928,8 @@ async function getLogMetrics(options) {
52928
52928
 
52929
52929
  // src/log-query.ts
52930
52930
  init_config();
52931
- import { createReadStream as createReadStream3, existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
52932
- import { join as join6, resolve as resolve4 } from "path";
52931
+ import { createReadStream as createReadStream3, existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
52932
+ import { join as join6, resolve as resolve5 } from "path";
52933
52933
  import { createInterface as createInterface2 } from "readline";
52934
52934
 
52935
52935
  // src/log-index.ts
@@ -52938,11 +52938,11 @@ import { Database } from "bun:sqlite";
52938
52938
  import {
52939
52939
  closeSync,
52940
52940
  createReadStream as createReadStream2,
52941
- existsSync as existsSync3,
52941
+ existsSync as existsSync4,
52942
52942
  mkdirSync as mkdirSync3,
52943
52943
  openSync,
52944
52944
  readSync,
52945
- statSync
52945
+ statSync as statSync2
52946
52946
  } from "fs";
52947
52947
  import { join as join5 } from "path";
52948
52948
 
@@ -53022,8 +53022,588 @@ function resolveLogSessionIdentity(requestBody) {
53022
53022
  };
53023
53023
  }
53024
53024
 
53025
+ // src/token-usage.ts
53026
+ import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
53027
+ import { resolve as resolve4 } from "path";
53028
+ var MAX_STREAM_USAGE_BYTES = 25 * 1024 * 1024;
53029
+ function asRecord(value) {
53030
+ if (!value || typeof value !== "object" || Array.isArray(value))
53031
+ return null;
53032
+ return value;
53033
+ }
53034
+ function numeric(value) {
53035
+ if (typeof value === "number" && Number.isFinite(value))
53036
+ return value;
53037
+ if (typeof value === "string" && value.trim()) {
53038
+ const parsed = Number(value);
53039
+ if (Number.isFinite(parsed))
53040
+ return parsed;
53041
+ }
53042
+ return null;
53043
+ }
53044
+ function numberAt(value, path) {
53045
+ let current = value;
53046
+ for (const key2 of path) {
53047
+ const record2 = asRecord(current);
53048
+ if (!record2 || !(key2 in record2))
53049
+ return null;
53050
+ current = record2[key2];
53051
+ }
53052
+ return numeric(current);
53053
+ }
53054
+ function firstNumber(value, paths) {
53055
+ for (const path of paths) {
53056
+ const found = numberAt(value, path);
53057
+ if (found !== null)
53058
+ return found;
53059
+ }
53060
+ return null;
53061
+ }
53062
+ function maxNullable(...values) {
53063
+ const numbers = values.filter((value) => value !== null && value !== undefined);
53064
+ if (numbers.length === 0)
53065
+ return null;
53066
+ return Math.max(...numbers);
53067
+ }
53068
+ function sumNullable(...values) {
53069
+ let total = 0;
53070
+ let hasValue = false;
53071
+ for (const value of values) {
53072
+ if (value === null || value === undefined)
53073
+ continue;
53074
+ total += value;
53075
+ hasValue = true;
53076
+ }
53077
+ return hasValue ? total : null;
53078
+ }
53079
+ function roundPercent(numerator, denominator) {
53080
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator <= 0)
53081
+ return null;
53082
+ return Number((numerator / denominator * 100).toFixed(2));
53083
+ }
53084
+ function inferProviderStyle(usage, providerHint) {
53085
+ const hint = providerHint?.toLowerCase() ?? "";
53086
+ if (hint.includes("anthropic") || hint.includes("claude"))
53087
+ return "anthropic";
53088
+ if (hint.includes("gemini") || hint.includes("google"))
53089
+ return "gemini";
53090
+ if (hint.includes("deepseek"))
53091
+ return "deepseek";
53092
+ if (hint.includes("cohere"))
53093
+ return "cohere";
53094
+ if (hint.includes("mistral"))
53095
+ return "mistral";
53096
+ if (hint.includes("openrouter"))
53097
+ return "openrouter";
53098
+ if (hint.includes("openai") || hint.includes("gpt-"))
53099
+ return "openai";
53100
+ if ("cache_read_input_tokens" in usage || "cache_creation_input_tokens" in usage) {
53101
+ return "anthropic";
53102
+ }
53103
+ if ("prompt_cache_hit_tokens" in usage || "prompt_cache_miss_tokens" in usage) {
53104
+ return "deepseek";
53105
+ }
53106
+ if ("promptTokenCount" in usage || "usageMetadata" in usage || "cachedContentTokenCount" in usage) {
53107
+ return "gemini";
53108
+ }
53109
+ if ("billed_units" in usage || "tokens" in usage) {
53110
+ return "cohere";
53111
+ }
53112
+ if ("prompt_tokens" in usage || "completion_tokens" in usage) {
53113
+ return "openai";
53114
+ }
53115
+ if ("input_tokens" in usage || "output_tokens" in usage) {
53116
+ return "openai";
53117
+ }
53118
+ return "unknown";
53119
+ }
53120
+ function createEmptyMetrics2(input) {
53121
+ return {
53122
+ schemaVersion: 1,
53123
+ source: input.source,
53124
+ providerStyle: input.providerStyle,
53125
+ inputTokens: null,
53126
+ outputTokens: null,
53127
+ totalTokens: null,
53128
+ cachedInputTokens: null,
53129
+ cacheHitInputTokens: null,
53130
+ cacheHitRate: null,
53131
+ cacheHitRateDenominatorTokens: null,
53132
+ cacheHitRateFormula: null,
53133
+ cacheReadInputTokens: null,
53134
+ cacheCreationInputTokens: null,
53135
+ cacheCreationInputTokens5m: null,
53136
+ cacheCreationInputTokens1h: null,
53137
+ cacheWriteInputTokens: null,
53138
+ cacheMissInputTokens: null,
53139
+ reasoningTokens: null,
53140
+ audioInputTokens: null,
53141
+ audioOutputTokens: null,
53142
+ textInputTokens: null,
53143
+ textOutputTokens: null,
53144
+ acceptedPredictionTokens: null,
53145
+ rejectedPredictionTokens: null,
53146
+ toolUsePromptTokens: null,
53147
+ billableInputTokens: null,
53148
+ billableOutputTokens: null,
53149
+ creditUsage: null,
53150
+ cost: null,
53151
+ rawUsage: input.rawUsage,
53152
+ rawUsagePath: input.rawUsagePath,
53153
+ warnings: []
53154
+ };
53155
+ }
53156
+ function hasAnyTokenSignal(metrics) {
53157
+ return [
53158
+ metrics.inputTokens,
53159
+ metrics.outputTokens,
53160
+ metrics.totalTokens,
53161
+ metrics.cachedInputTokens,
53162
+ metrics.cacheHitInputTokens,
53163
+ metrics.cacheReadInputTokens,
53164
+ metrics.cacheCreationInputTokens,
53165
+ metrics.cacheMissInputTokens,
53166
+ metrics.reasoningTokens,
53167
+ metrics.billableInputTokens,
53168
+ metrics.billableOutputTokens,
53169
+ metrics.creditUsage,
53170
+ metrics.cost
53171
+ ].some((value) => value !== null);
53172
+ }
53173
+ function normalizeUsageObject(input) {
53174
+ const { usage, source: source2, rawUsagePath, providerHint } = input;
53175
+ const providerStyle = inferProviderStyle(usage, providerHint);
53176
+ const metrics = createEmptyMetrics2({
53177
+ source: source2,
53178
+ providerStyle,
53179
+ rawUsage: usage,
53180
+ rawUsagePath
53181
+ });
53182
+ const usageBody = asRecord(usage.usageMetadata) ?? usage;
53183
+ metrics.inputTokens = firstNumber(usageBody, [
53184
+ ["input_tokens"],
53185
+ ["prompt_tokens"],
53186
+ ["promptTokenCount"],
53187
+ ["tokens", "input_tokens"],
53188
+ ["billed_units", "input_tokens"]
53189
+ ]);
53190
+ metrics.outputTokens = firstNumber(usageBody, [
53191
+ ["output_tokens"],
53192
+ ["completion_tokens"],
53193
+ ["candidatesTokenCount"],
53194
+ ["tokens", "output_tokens"],
53195
+ ["billed_units", "output_tokens"]
53196
+ ]);
53197
+ const explicitTotalTokens = firstNumber(usageBody, [
53198
+ ["total_tokens"],
53199
+ ["totalTokenCount"],
53200
+ ["tokens", "total_tokens"]
53201
+ ]);
53202
+ metrics.totalTokens = explicitTotalTokens;
53203
+ const cachedTokens = firstNumber(usageBody, [
53204
+ ["input_tokens_details", "cached_tokens"],
53205
+ ["prompt_tokens_details", "cached_tokens"],
53206
+ ["cached_tokens"],
53207
+ ["cachedContentTokenCount"]
53208
+ ]);
53209
+ const cacheReadTokens = firstNumber(usageBody, [
53210
+ ["cache_read_input_tokens"],
53211
+ ["cacheReadInputTokens"],
53212
+ ["claude_cache_read_input_tokens"]
53213
+ ]);
53214
+ const promptCacheHitTokens = firstNumber(usageBody, [["prompt_cache_hit_tokens"]]);
53215
+ const promptCacheMissTokens = firstNumber(usageBody, [["prompt_cache_miss_tokens"]]);
53216
+ const cacheCreationTokens = firstNumber(usageBody, [
53217
+ ["cache_creation_input_tokens"],
53218
+ ["cacheCreationInputTokens"],
53219
+ ["cache_creation", "input_tokens"],
53220
+ ["claude_cache_creation_input_tokens"]
53221
+ ]);
53222
+ metrics.cacheCreationInputTokens5m = firstNumber(usageBody, [
53223
+ ["cache_creation", "ephemeral_5m_input_tokens"],
53224
+ ["cache_creation", "ephemeral5mInputTokens"],
53225
+ ["cache_creation_ephemeral_5m_input_tokens"],
53226
+ ["claude_cache_creation_5_m_tokens"]
53227
+ ]);
53228
+ metrics.cacheCreationInputTokens1h = firstNumber(usageBody, [
53229
+ ["cache_creation", "ephemeral_1h_input_tokens"],
53230
+ ["cache_creation", "ephemeral1hInputTokens"],
53231
+ ["cache_creation_ephemeral_1h_input_tokens"],
53232
+ ["claude_cache_creation_1_h_tokens"]
53233
+ ]);
53234
+ metrics.cacheCreationInputTokens = maxNullable(cacheCreationTokens, sumNullable(metrics.cacheCreationInputTokens5m, metrics.cacheCreationInputTokens1h));
53235
+ metrics.cacheReadInputTokens = cacheReadTokens;
53236
+ metrics.cacheHitInputTokens = maxNullable(cachedTokens, cacheReadTokens, promptCacheHitTokens);
53237
+ metrics.cachedInputTokens = metrics.cacheHitInputTokens;
53238
+ metrics.cacheMissInputTokens = promptCacheMissTokens;
53239
+ metrics.cacheWriteInputTokens = firstNumber(usageBody, [
53240
+ ["cache_write_input_tokens"],
53241
+ ["cacheWriteInputTokens"]
53242
+ ]);
53243
+ if (metrics.cacheWriteInputTokens === null && providerStyle === "anthropic") {
53244
+ metrics.cacheWriteInputTokens = metrics.cacheCreationInputTokens;
53245
+ }
53246
+ metrics.reasoningTokens = firstNumber(usageBody, [
53247
+ ["output_tokens_details", "reasoning_tokens"],
53248
+ ["completion_tokens_details", "reasoning_tokens"],
53249
+ ["reasoning_tokens"],
53250
+ ["thoughtsTokenCount"]
53251
+ ]);
53252
+ metrics.audioInputTokens = firstNumber(usageBody, [
53253
+ ["input_tokens_details", "audio_tokens"],
53254
+ ["prompt_tokens_details", "audio_tokens"],
53255
+ ["audio_input_tokens"]
53256
+ ]);
53257
+ metrics.audioOutputTokens = firstNumber(usageBody, [
53258
+ ["output_tokens_details", "audio_tokens"],
53259
+ ["completion_tokens_details", "audio_tokens"],
53260
+ ["audio_output_tokens"]
53261
+ ]);
53262
+ metrics.textInputTokens = firstNumber(usageBody, [
53263
+ ["input_tokens_details", "text_tokens"],
53264
+ ["prompt_tokens_details", "text_tokens"],
53265
+ ["text_input_tokens"]
53266
+ ]);
53267
+ metrics.textOutputTokens = firstNumber(usageBody, [
53268
+ ["output_tokens_details", "text_tokens"],
53269
+ ["completion_tokens_details", "text_tokens"],
53270
+ ["text_output_tokens"]
53271
+ ]);
53272
+ metrics.acceptedPredictionTokens = firstNumber(usageBody, [
53273
+ ["output_tokens_details", "accepted_prediction_tokens"],
53274
+ ["completion_tokens_details", "accepted_prediction_tokens"],
53275
+ ["accepted_prediction_tokens"]
53276
+ ]);
53277
+ metrics.rejectedPredictionTokens = firstNumber(usageBody, [
53278
+ ["output_tokens_details", "rejected_prediction_tokens"],
53279
+ ["completion_tokens_details", "rejected_prediction_tokens"],
53280
+ ["rejected_prediction_tokens"]
53281
+ ]);
53282
+ metrics.toolUsePromptTokens = firstNumber(usageBody, [
53283
+ ["toolUsePromptTokenCount"],
53284
+ ["tool_use_prompt_tokens"]
53285
+ ]);
53286
+ metrics.billableInputTokens = firstNumber(usageBody, [
53287
+ ["billed_units", "input_tokens"],
53288
+ ["billable_input_tokens"]
53289
+ ]);
53290
+ metrics.billableOutputTokens = firstNumber(usageBody, [
53291
+ ["billed_units", "output_tokens"],
53292
+ ["billable_output_tokens"]
53293
+ ]);
53294
+ metrics.creditUsage = firstNumber(usageBody, [["credit_usage"], ["creditUsage"]]);
53295
+ metrics.cost = firstNumber(usageBody, [["cost"], ["total_cost"], ["totalCost"]]);
53296
+ let cacheDenominator = null;
53297
+ let cacheFormula = null;
53298
+ if (providerStyle === "anthropic") {
53299
+ cacheDenominator = sumNullable(metrics.inputTokens, metrics.cacheReadInputTokens, metrics.cacheCreationInputTokens);
53300
+ cacheFormula = "cache_read_input_tokens / (input_tokens + cache_read_input_tokens + cache_creation_input_tokens)";
53301
+ } else if (providerStyle === "deepseek") {
53302
+ cacheDenominator = metrics.inputTokens ?? sumNullable(metrics.cacheHitInputTokens, metrics.cacheMissInputTokens);
53303
+ cacheFormula = "prompt_cache_hit_tokens / prompt_tokens";
53304
+ } else if (providerStyle === "gemini") {
53305
+ cacheDenominator = metrics.inputTokens;
53306
+ cacheFormula = "cachedContentTokenCount / promptTokenCount";
53307
+ } else if (providerStyle === "openai" || providerStyle === "mistral" || providerStyle === "openrouter" || providerStyle === "unknown") {
53308
+ cacheDenominator = metrics.inputTokens;
53309
+ cacheFormula = "cached_tokens / input_tokens";
53310
+ }
53311
+ if (metrics.cacheHitInputTokens !== null && cacheDenominator !== null && cacheDenominator > 0) {
53312
+ metrics.cacheHitRateDenominatorTokens = cacheDenominator;
53313
+ metrics.cacheHitRateFormula = cacheFormula;
53314
+ metrics.cacheHitRate = roundPercent(metrics.cacheHitInputTokens, cacheDenominator);
53315
+ if (metrics.cacheMissInputTokens === null) {
53316
+ metrics.cacheMissInputTokens = Math.max(0, cacheDenominator - metrics.cacheHitInputTokens);
53317
+ }
53318
+ }
53319
+ if (metrics.totalTokens === null && metrics.outputTokens !== null) {
53320
+ const effectiveInputTokens = metrics.cacheHitRateDenominatorTokens ?? metrics.inputTokens;
53321
+ if (effectiveInputTokens !== null) {
53322
+ metrics.totalTokens = effectiveInputTokens + metrics.outputTokens;
53323
+ metrics.warnings.push(metrics.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
53324
+ }
53325
+ }
53326
+ return hasAnyTokenSignal(metrics) ? metrics : null;
53327
+ }
53328
+ function collectUsageCandidates(value, prefix = "") {
53329
+ const record2 = asRecord(value);
53330
+ if (!record2)
53331
+ return [];
53332
+ const candidates = [];
53333
+ const candidateKeys = [
53334
+ "usage",
53335
+ "usageMetadata",
53336
+ "message.usage",
53337
+ "response.usage",
53338
+ "body.usage",
53339
+ "data.usage",
53340
+ "event.usage"
53341
+ ];
53342
+ for (const key2 of candidateKeys) {
53343
+ const path = key2.split(".");
53344
+ let current = record2;
53345
+ for (const part of path) {
53346
+ const currentRecord = asRecord(current);
53347
+ current = currentRecord?.[part];
53348
+ }
53349
+ const usage = asRecord(current);
53350
+ if (usage) {
53351
+ candidates.push({ usage, path: prefix ? `${prefix}.${key2}` : key2 });
53352
+ }
53353
+ }
53354
+ const direct = normalizeUsageObject({
53355
+ usage: record2,
53356
+ source: "response_body",
53357
+ rawUsagePath: prefix || null
53358
+ });
53359
+ if (direct) {
53360
+ candidates.push({ usage: record2, path: prefix || "$" });
53361
+ }
53362
+ return candidates;
53363
+ }
53364
+ function mergeNumber(current, incoming, strategy = "max") {
53365
+ if (incoming === null)
53366
+ return current;
53367
+ if (current === null)
53368
+ return incoming;
53369
+ return strategy === "latest" ? incoming : Math.max(current, incoming);
53370
+ }
53371
+ function mergeTokenUsageMetrics(current, incoming) {
53372
+ if (!current)
53373
+ return incoming;
53374
+ if (!incoming)
53375
+ return current;
53376
+ const merged = {
53377
+ ...current,
53378
+ source: incoming.source,
53379
+ providerStyle: current.providerStyle === "unknown" ? incoming.providerStyle : current.providerStyle,
53380
+ rawUsage: incoming.rawUsage ?? current.rawUsage,
53381
+ rawUsagePath: incoming.rawUsagePath ?? current.rawUsagePath,
53382
+ warnings: Array.from(new Set([...current.warnings, ...incoming.warnings]))
53383
+ };
53384
+ const numericKeys = [
53385
+ "inputTokens",
53386
+ "outputTokens",
53387
+ "totalTokens",
53388
+ "cachedInputTokens",
53389
+ "cacheHitInputTokens",
53390
+ "cacheHitRateDenominatorTokens",
53391
+ "cacheReadInputTokens",
53392
+ "cacheCreationInputTokens",
53393
+ "cacheCreationInputTokens5m",
53394
+ "cacheCreationInputTokens1h",
53395
+ "cacheWriteInputTokens",
53396
+ "cacheMissInputTokens",
53397
+ "reasoningTokens",
53398
+ "audioInputTokens",
53399
+ "audioOutputTokens",
53400
+ "textInputTokens",
53401
+ "textOutputTokens",
53402
+ "acceptedPredictionTokens",
53403
+ "rejectedPredictionTokens",
53404
+ "toolUsePromptTokens",
53405
+ "billableInputTokens",
53406
+ "billableOutputTokens",
53407
+ "creditUsage",
53408
+ "cost"
53409
+ ];
53410
+ for (const key2 of numericKeys) {
53411
+ const value = mergeNumber(current[key2], incoming[key2], key2 === "cost" || key2 === "creditUsage" ? "latest" : "max");
53412
+ merged[key2] = value;
53413
+ }
53414
+ if (incoming.cacheHitRate !== null && (current.cacheHitRate === null || (incoming.cacheHitRateDenominatorTokens ?? 0) >= (current.cacheHitRateDenominatorTokens ?? 0))) {
53415
+ merged.cacheHitRate = incoming.cacheHitRate;
53416
+ merged.cacheHitRateFormula = incoming.cacheHitRateFormula;
53417
+ }
53418
+ if (merged.cacheHitInputTokens !== null && merged.cacheHitRateDenominatorTokens !== null) {
53419
+ merged.cacheHitRate = roundPercent(merged.cacheHitInputTokens, merged.cacheHitRateDenominatorTokens);
53420
+ }
53421
+ if (merged.totalTokens === null && merged.outputTokens !== null) {
53422
+ const effectiveInputTokens = merged.cacheHitRateDenominatorTokens ?? merged.inputTokens;
53423
+ if (effectiveInputTokens !== null) {
53424
+ merged.totalTokens = effectiveInputTokens + merged.outputTokens;
53425
+ merged.warnings.push(merged.cacheHitRateDenominatorTokens !== null ? "totalTokens \u7531 cacheHitRateDenominatorTokens + outputTokens \u63A8\u5BFC" : "totalTokens \u7531 inputTokens + outputTokens \u63A8\u5BFC");
53426
+ merged.warnings = Array.from(new Set(merged.warnings));
53427
+ }
53428
+ }
53429
+ return merged;
53430
+ }
53431
+ function toTokenUsageSummary(metrics) {
53432
+ const { rawUsage: _rawUsage, ...summary } = metrics;
53433
+ return summary;
53434
+ }
53435
+ function extractTokenUsageFromJson(value, options) {
53436
+ let merged = null;
53437
+ const candidates = collectUsageCandidates(value, options.rawUsagePathPrefix ?? "");
53438
+ for (const candidate of candidates) {
53439
+ const metrics = normalizeUsageObject({
53440
+ usage: candidate.usage,
53441
+ source: options.source,
53442
+ rawUsagePath: candidate.path,
53443
+ providerHint: options.providerHint
53444
+ });
53445
+ merged = mergeTokenUsageMetrics(merged, metrics);
53446
+ }
53447
+ return merged;
53448
+ }
53449
+ function extractTokenUsageFromResponseText(text2, source2 = "response_body", providerHint) {
53450
+ if (!text2?.trim())
53451
+ return null;
53452
+ try {
53453
+ return extractTokenUsageFromJson(JSON.parse(text2), { source: source2, providerHint });
53454
+ } catch {
53455
+ return null;
53456
+ }
53457
+ }
53458
+ function processSseMessage(dataLines, source2, providerHint) {
53459
+ if (dataLines.length === 0)
53460
+ return null;
53461
+ const data = dataLines.join(`
53462
+ `).trim();
53463
+ if (!data || data === "[DONE]")
53464
+ return null;
53465
+ try {
53466
+ return extractTokenUsageFromJson(JSON.parse(data), {
53467
+ source: source2,
53468
+ providerHint,
53469
+ rawUsagePathPrefix: source2 === "stream_file" ? "stream" : "stream"
53470
+ });
53471
+ } catch {
53472
+ return null;
53473
+ }
53474
+ }
53475
+ function extractTokenUsageFromSseText(text2, source2 = "stream_file", providerHint) {
53476
+ if (!text2?.trim())
53477
+ return null;
53478
+ let merged = null;
53479
+ let dataLines = [];
53480
+ const flush = () => {
53481
+ merged = mergeTokenUsageMetrics(merged, processSseMessage(dataLines, source2, providerHint));
53482
+ dataLines = [];
53483
+ };
53484
+ for (const rawLine of text2.split(/\r?\n/)) {
53485
+ if (rawLine === "") {
53486
+ flush();
53487
+ continue;
53488
+ }
53489
+ if (rawLine.startsWith("data:")) {
53490
+ dataLines.push(rawLine.slice(5).trimStart());
53491
+ }
53492
+ }
53493
+ flush();
53494
+ return merged;
53495
+ }
53496
+ function createTokenUsageStreamCollector(providerHint) {
53497
+ const decoder = new TextDecoder;
53498
+ let buffer2 = "";
53499
+ let dataLines = [];
53500
+ let latest = null;
53501
+ const flushMessage = () => {
53502
+ latest = mergeTokenUsageMetrics(latest, processSseMessage(dataLines, "stream_chunk", providerHint));
53503
+ dataLines = [];
53504
+ };
53505
+ const processLine = (rawLine) => {
53506
+ const line2 = rawLine.replace(/\r$/, "");
53507
+ if (line2 === "") {
53508
+ flushMessage();
53509
+ return;
53510
+ }
53511
+ if (line2.startsWith("data:")) {
53512
+ dataLines.push(line2.slice(5).trimStart());
53513
+ }
53514
+ };
53515
+ return {
53516
+ addChunk(chunk) {
53517
+ buffer2 += decoder.decode(chunk, { stream: true });
53518
+ let newlineIndex = buffer2.indexOf(`
53519
+ `);
53520
+ while (newlineIndex >= 0) {
53521
+ processLine(buffer2.slice(0, newlineIndex));
53522
+ buffer2 = buffer2.slice(newlineIndex + 1);
53523
+ newlineIndex = buffer2.indexOf(`
53524
+ `);
53525
+ }
53526
+ },
53527
+ getUsage() {
53528
+ buffer2 += decoder.decode();
53529
+ if (buffer2) {
53530
+ processLine(buffer2);
53531
+ buffer2 = "";
53532
+ }
53533
+ flushMessage();
53534
+ return latest;
53535
+ }
53536
+ };
53537
+ }
53538
+ function safeReadStreamFile(streamFile, baseDir) {
53539
+ if (!streamFile)
53540
+ return { content: null, warning: null };
53541
+ try {
53542
+ const candidates = [streamFile];
53543
+ if (baseDir)
53544
+ candidates.push(resolve4(baseDir, streamFile));
53545
+ for (const candidate of candidates) {
53546
+ const resolved = resolve4(candidate);
53547
+ if (!resolved.endsWith(".sse.raw"))
53548
+ continue;
53549
+ if (!existsSync3(resolved))
53550
+ continue;
53551
+ const stats = statSync(resolved);
53552
+ if (stats.size > MAX_STREAM_USAGE_BYTES) {
53553
+ return {
53554
+ content: null,
53555
+ warning: `stream_file \u8D85\u8FC7 ${MAX_STREAM_USAGE_BYTES} \u5B57\u8282\uFF0C\u5DF2\u8DF3\u8FC7 token usage \u56DE\u586B`
53556
+ };
53557
+ }
53558
+ return { content: readFileSync4(resolved, "utf-8"), warning: null };
53559
+ }
53560
+ } catch (err) {
53561
+ return {
53562
+ content: null,
53563
+ warning: `stream_file token usage \u8BFB\u53D6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
53564
+ };
53565
+ }
53566
+ return { content: null, warning: null };
53567
+ }
53568
+ function extractTokenUsageFromLogEvent(event, options = {}) {
53569
+ if (event.token_usage) {
53570
+ return {
53571
+ rawUsage: null,
53572
+ ...event.token_usage,
53573
+ source: event.token_usage.source ?? "explicit"
53574
+ };
53575
+ }
53576
+ const providerHint = [event.provider, event.route_type, event.model_in, event.model_out].filter(Boolean).join(" ");
53577
+ const responseBodyUsage = extractTokenUsageFromResponseText(event.response_body, "response_body", providerHint);
53578
+ if (responseBodyUsage)
53579
+ return responseBodyUsage;
53580
+ const responseAfterPluginsUsage = extractTokenUsageFromResponseText(event.response_body_after_plugins, "response_body_after_plugins", providerHint);
53581
+ if (responseAfterPluginsUsage)
53582
+ return responseAfterPluginsUsage;
53583
+ const responseBeforePluginsUsage = extractTokenUsageFromResponseText(event.response_body_before_plugins, "response_body_before_plugins", providerHint);
53584
+ if (responseBeforePluginsUsage)
53585
+ return responseBeforePluginsUsage;
53586
+ const streamContent = options.streamContent ?? safeReadStreamFile(event.stream_file, options.baseDir).content;
53587
+ return extractTokenUsageFromSseText(streamContent ?? undefined, "stream_file", providerHint);
53588
+ }
53589
+ function extractTokenUsageSummaryFromLogEvent(event, options = {}) {
53590
+ const metrics = extractTokenUsageFromLogEvent(event, options);
53591
+ return metrics ? toTokenUsageSummary(metrics) : null;
53592
+ }
53593
+ function enrichLogEventTokenUsage(event, options = {}) {
53594
+ if (event.token_usage)
53595
+ return event;
53596
+ const tokenUsage = extractTokenUsageFromLogEvent(event, options);
53597
+ if (!tokenUsage)
53598
+ return event;
53599
+ return {
53600
+ ...event,
53601
+ token_usage: tokenUsage
53602
+ };
53603
+ }
53604
+
53025
53605
  // src/log-index.ts
53026
- var SCHEMA_VERSION2 = 1;
53606
+ var SCHEMA_VERSION2 = 3;
53027
53607
  var MAX_INDEX_QUEUE = 20000;
53028
53608
  var INDEX_BATCH_SIZE = 250;
53029
53609
  var INDEX_FLUSH_DELAY_MS = 50;
@@ -53167,6 +53747,7 @@ function eventToRow(input) {
53167
53747
  const statusClass = getStatusClass2(event);
53168
53748
  const latencyMs = Math.max(0, event.latency_ms ?? 0);
53169
53749
  const model = event.model_out || event.model_in;
53750
+ const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir: input.baseDir });
53170
53751
  return {
53171
53752
  id: input.id,
53172
53753
  ts_ms: tsMs,
@@ -53194,8 +53775,46 @@ function eventToRow(input) {
53194
53775
  line_number: input.lineNumber,
53195
53776
  byte_offset: input.offset,
53196
53777
  byte_length: input.byteLength,
53197
- search_text: buildSearchText(event)
53198
- };
53778
+ search_text: buildSearchText(event),
53779
+ token_input: tokenUsage?.inputTokens ?? null,
53780
+ token_output: tokenUsage?.outputTokens ?? null,
53781
+ token_total: tokenUsage?.totalTokens ?? null,
53782
+ token_cached_input: tokenUsage?.cachedInputTokens ?? null,
53783
+ token_cache_hit_input: tokenUsage?.cacheHitInputTokens ?? null,
53784
+ token_cache_hit_rate: tokenUsage?.cacheHitRate ?? null,
53785
+ token_cache_hit_rate_denominator: tokenUsage?.cacheHitRateDenominatorTokens ?? null,
53786
+ token_cache_read_input: tokenUsage?.cacheReadInputTokens ?? null,
53787
+ token_cache_creation_input: tokenUsage?.cacheCreationInputTokens ?? null,
53788
+ token_cache_creation_input_5m: tokenUsage?.cacheCreationInputTokens5m ?? null,
53789
+ token_cache_creation_input_1h: tokenUsage?.cacheCreationInputTokens1h ?? null,
53790
+ token_cache_write_input: tokenUsage?.cacheWriteInputTokens ?? null,
53791
+ token_cache_miss_input: tokenUsage?.cacheMissInputTokens ?? null,
53792
+ token_reasoning: tokenUsage?.reasoningTokens ?? null,
53793
+ token_audio_input: tokenUsage?.audioInputTokens ?? null,
53794
+ token_audio_output: tokenUsage?.audioOutputTokens ?? null,
53795
+ token_text_input: tokenUsage?.textInputTokens ?? null,
53796
+ token_text_output: tokenUsage?.textOutputTokens ?? null,
53797
+ token_accepted_prediction: tokenUsage?.acceptedPredictionTokens ?? null,
53798
+ token_rejected_prediction: tokenUsage?.rejectedPredictionTokens ?? null,
53799
+ token_tool_use_prompt: tokenUsage?.toolUsePromptTokens ?? null,
53800
+ token_billable_input: tokenUsage?.billableInputTokens ?? null,
53801
+ token_billable_output: tokenUsage?.billableOutputTokens ?? null,
53802
+ token_credit_usage: tokenUsage?.creditUsage ?? null,
53803
+ token_cost: tokenUsage?.cost ?? null,
53804
+ token_source: tokenUsage?.source ?? null,
53805
+ token_provider_style: tokenUsage?.providerStyle ?? null,
53806
+ token_raw_usage_path: tokenUsage?.rawUsagePath ?? null,
53807
+ token_usage_json: tokenUsage ? JSON.stringify(tokenUsage) : null
53808
+ };
53809
+ }
53810
+ function parseTokenUsageSummary(value) {
53811
+ if (!value)
53812
+ return null;
53813
+ try {
53814
+ return JSON.parse(value);
53815
+ } catch {
53816
+ return null;
53817
+ }
53199
53818
  }
53200
53819
  function rowToSummary(row) {
53201
53820
  return {
@@ -53218,7 +53837,8 @@ function rowToSummary(row) {
53218
53837
  hasMetadata: row.has_metadata === 1,
53219
53838
  userIdRaw: row.user_id_raw,
53220
53839
  userKey: row.user_key,
53221
- sessionId: row.session_id
53840
+ sessionId: row.session_id,
53841
+ tokenUsage: parseTokenUsageSummary(row.token_usage_json)
53222
53842
  };
53223
53843
  }
53224
53844
  async function* readJsonlLinesWithOffsets(filePath) {
@@ -53288,7 +53908,22 @@ function createEmptyStats() {
53288
53908
  errorCount: 0,
53289
53909
  errorRate: 0,
53290
53910
  avgLatencyMs: 0,
53291
- p95LatencyMs: 0
53911
+ p95LatencyMs: 0,
53912
+ tokenUsageCount: 0,
53913
+ inputTokens: 0,
53914
+ outputTokens: 0,
53915
+ totalTokens: 0,
53916
+ cachedInputTokens: 0,
53917
+ cacheHitInputTokens: 0,
53918
+ cacheHitRate: 0,
53919
+ cacheHitRateDenominatorTokens: 0,
53920
+ cacheReadInputTokens: 0,
53921
+ cacheCreationInputTokens: 0,
53922
+ cacheWriteInputTokens: 0,
53923
+ cacheMissInputTokens: 0,
53924
+ reasoningTokens: 0,
53925
+ billableInputTokens: 0,
53926
+ billableOutputTokens: 0
53292
53927
  };
53293
53928
  }
53294
53929
  function createEmptyQueryResult(query, meta3 = {}) {
@@ -53381,12 +54016,28 @@ class LogIndex {
53381
54016
  id, ts_ms, ts_start, level, provider, route_type, model, model_in, model_out,
53382
54017
  path, request_id, latency_ms, upstream_status, status_class, has_error,
53383
54018
  message, error_type, has_metadata, user_id_raw, user_key, session_id,
53384
- source_date, source_file, line_number, byte_offset, byte_length, search_text
54019
+ source_date, source_file, line_number, byte_offset, byte_length, search_text,
54020
+ token_input, token_output, token_total, token_cached_input, token_cache_hit_input,
54021
+ token_cache_hit_rate, token_cache_hit_rate_denominator, token_cache_read_input,
54022
+ token_cache_creation_input, token_cache_creation_input_5m, token_cache_creation_input_1h,
54023
+ token_cache_write_input, token_cache_miss_input, token_reasoning, token_audio_input,
54024
+ token_audio_output, token_text_input, token_text_output, token_accepted_prediction,
54025
+ token_rejected_prediction, token_tool_use_prompt, token_billable_input,
54026
+ token_billable_output, token_credit_usage, token_cost, token_source,
54027
+ token_provider_style, token_raw_usage_path, token_usage_json
53385
54028
  ) VALUES (
53386
54029
  $id, $ts_ms, $ts_start, $level, $provider, $route_type, $model, $model_in, $model_out,
53387
54030
  $path, $request_id, $latency_ms, $upstream_status, $status_class, $has_error,
53388
54031
  $message, $error_type, $has_metadata, $user_id_raw, $user_key, $session_id,
53389
- $source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text
54032
+ $source_date, $source_file, $line_number, $byte_offset, $byte_length, $search_text,
54033
+ $token_input, $token_output, $token_total, $token_cached_input, $token_cache_hit_input,
54034
+ $token_cache_hit_rate, $token_cache_hit_rate_denominator, $token_cache_read_input,
54035
+ $token_cache_creation_input, $token_cache_creation_input_5m, $token_cache_creation_input_1h,
54036
+ $token_cache_write_input, $token_cache_miss_input, $token_reasoning, $token_audio_input,
54037
+ $token_audio_output, $token_text_input, $token_text_output, $token_accepted_prediction,
54038
+ $token_rejected_prediction, $token_tool_use_prompt, $token_billable_input,
54039
+ $token_billable_output, $token_credit_usage, $token_cost, $token_source,
54040
+ $token_provider_style, $token_raw_usage_path, $token_usage_json
53390
54041
  )
53391
54042
  `);
53392
54043
  this.deleteFtsStmt = this.db.prepare("DELETE FROM log_events_fts WHERE event_id = ?");
@@ -53436,9 +54087,9 @@ class LogIndex {
53436
54087
  const dates = listDateStrings2(fromMs, toMs);
53437
54088
  for (const date5 of dates) {
53438
54089
  const filePath = join5(eventsDir, `${date5}.jsonl`);
53439
- if (!existsSync3(filePath))
54090
+ if (!existsSync4(filePath))
53440
54091
  continue;
53441
- const stats = statSync(filePath);
54092
+ const stats = statSync2(filePath);
53442
54093
  const fileRow = this.db.query("SELECT size_bytes, mtime_ms FROM log_index_files WHERE file_path = ?").get(filePath);
53443
54094
  const sizeBytes = stats.size;
53444
54095
  const mtimeMs = Math.trunc(stats.mtimeMs);
@@ -53473,7 +54124,7 @@ class LogIndex {
53473
54124
  e.id, e.ts_start, e.level, e.provider, e.route_type, e.model, e.model_in,
53474
54125
  e.model_out, e.path, e.request_id, e.latency_ms, e.upstream_status,
53475
54126
  e.status_class, e.has_error, e.message, e.error_type, e.has_metadata,
53476
- e.user_id_raw, e.user_key, e.session_id, e.ts_ms
54127
+ e.user_id_raw, e.user_key, e.session_id, e.ts_ms, e.token_usage_json
53477
54128
  FROM log_events e
53478
54129
  ${whereSql}
53479
54130
  ${cursorClause}
@@ -53528,7 +54179,7 @@ class LogIndex {
53528
54179
  return null;
53529
54180
  const row = this.db.query("SELECT source_date, source_file, line_number, byte_offset FROM log_events WHERE id = ?").get(id);
53530
54181
  const filePath = row?.source_file ?? join5(this.baseDir, "events", `${parsedId.date}.jsonl`);
53531
- if (!existsSync3(filePath))
54182
+ if (!existsSync4(filePath))
53532
54183
  return null;
53533
54184
  const line2 = readLineAtOffset(filePath, row?.byte_offset ?? parsedId.offset);
53534
54185
  if (!line2?.trim())
@@ -53596,7 +54247,36 @@ class LogIndex {
53596
54247
  line_number INTEGER,
53597
54248
  byte_offset INTEGER NOT NULL,
53598
54249
  byte_length INTEGER NOT NULL,
53599
- search_text TEXT NOT NULL
54250
+ search_text TEXT NOT NULL,
54251
+ token_input INTEGER,
54252
+ token_output INTEGER,
54253
+ token_total INTEGER,
54254
+ token_cached_input INTEGER,
54255
+ token_cache_hit_input INTEGER,
54256
+ token_cache_hit_rate REAL,
54257
+ token_cache_hit_rate_denominator INTEGER,
54258
+ token_cache_read_input INTEGER,
54259
+ token_cache_creation_input INTEGER,
54260
+ token_cache_creation_input_5m INTEGER,
54261
+ token_cache_creation_input_1h INTEGER,
54262
+ token_cache_write_input INTEGER,
54263
+ token_cache_miss_input INTEGER,
54264
+ token_reasoning INTEGER,
54265
+ token_audio_input INTEGER,
54266
+ token_audio_output INTEGER,
54267
+ token_text_input INTEGER,
54268
+ token_text_output INTEGER,
54269
+ token_accepted_prediction INTEGER,
54270
+ token_rejected_prediction INTEGER,
54271
+ token_tool_use_prompt INTEGER,
54272
+ token_billable_input INTEGER,
54273
+ token_billable_output INTEGER,
54274
+ token_credit_usage REAL,
54275
+ token_cost REAL,
54276
+ token_source TEXT,
54277
+ token_provider_style TEXT,
54278
+ token_raw_usage_path TEXT,
54279
+ token_usage_json TEXT
53600
54280
  );
53601
54281
 
53602
54282
  CREATE VIRTUAL TABLE IF NOT EXISTS log_events_fts
@@ -53614,12 +54294,67 @@ class LogIndex {
53614
54294
  CREATE INDEX IF NOT EXISTS idx_log_events_session_time ON log_events(session_id, ts_ms DESC);
53615
54295
  CREATE INDEX IF NOT EXISTS idx_log_events_file ON log_events(source_file);
53616
54296
  `);
54297
+ this.ensureTokenColumns();
54298
+ this.db.exec(`
54299
+ CREATE INDEX IF NOT EXISTS idx_log_events_token_total_time ON log_events(token_total, ts_ms DESC);
54300
+ CREATE INDEX IF NOT EXISTS idx_log_events_token_input_time ON log_events(token_input, ts_ms DESC);
54301
+ CREATE INDEX IF NOT EXISTS idx_log_events_token_cache_hit_rate_time ON log_events(token_cache_hit_rate, ts_ms DESC);
54302
+ `);
54303
+ const versionRow = this.db.query("SELECT value FROM log_index_meta WHERE key = 'schema_version'").get();
54304
+ const previousVersion = Number.parseInt(versionRow?.value ?? "0", 10) || 0;
54305
+ if (previousVersion > 0 && previousVersion < SCHEMA_VERSION2) {
54306
+ this.db.exec(`
54307
+ DELETE FROM log_events_fts;
54308
+ DELETE FROM log_events;
54309
+ DELETE FROM log_index_files;
54310
+ `);
54311
+ }
53617
54312
  this.db.prepare(`
53618
54313
  INSERT INTO log_index_meta(key, value)
53619
54314
  VALUES ('schema_version', ?)
53620
54315
  ON CONFLICT(key) DO UPDATE SET value = excluded.value
53621
54316
  `).run(String(SCHEMA_VERSION2));
53622
54317
  }
54318
+ ensureTokenColumns() {
54319
+ const rows = this.db.query("PRAGMA table_info(log_events)").all();
54320
+ const existing = new Set(rows.map((row) => row.name));
54321
+ const columns = [
54322
+ { name: "token_input", type: "INTEGER" },
54323
+ { name: "token_output", type: "INTEGER" },
54324
+ { name: "token_total", type: "INTEGER" },
54325
+ { name: "token_cached_input", type: "INTEGER" },
54326
+ { name: "token_cache_hit_input", type: "INTEGER" },
54327
+ { name: "token_cache_hit_rate", type: "REAL" },
54328
+ { name: "token_cache_hit_rate_denominator", type: "INTEGER" },
54329
+ { name: "token_cache_read_input", type: "INTEGER" },
54330
+ { name: "token_cache_creation_input", type: "INTEGER" },
54331
+ { name: "token_cache_creation_input_5m", type: "INTEGER" },
54332
+ { name: "token_cache_creation_input_1h", type: "INTEGER" },
54333
+ { name: "token_cache_write_input", type: "INTEGER" },
54334
+ { name: "token_cache_miss_input", type: "INTEGER" },
54335
+ { name: "token_reasoning", type: "INTEGER" },
54336
+ { name: "token_audio_input", type: "INTEGER" },
54337
+ { name: "token_audio_output", type: "INTEGER" },
54338
+ { name: "token_text_input", type: "INTEGER" },
54339
+ { name: "token_text_output", type: "INTEGER" },
54340
+ { name: "token_accepted_prediction", type: "INTEGER" },
54341
+ { name: "token_rejected_prediction", type: "INTEGER" },
54342
+ { name: "token_tool_use_prompt", type: "INTEGER" },
54343
+ { name: "token_billable_input", type: "INTEGER" },
54344
+ { name: "token_billable_output", type: "INTEGER" },
54345
+ { name: "token_credit_usage", type: "REAL" },
54346
+ { name: "token_cost", type: "REAL" },
54347
+ { name: "token_source", type: "TEXT" },
54348
+ { name: "token_provider_style", type: "TEXT" },
54349
+ { name: "token_raw_usage_path", type: "TEXT" },
54350
+ { name: "token_usage_json", type: "TEXT" }
54351
+ ];
54352
+ for (const column2 of columns) {
54353
+ if (existing.has(column2.name))
54354
+ continue;
54355
+ this.db.exec(`ALTER TABLE log_events ADD COLUMN ${column2.name} ${column2.type}`);
54356
+ }
54357
+ }
53623
54358
  flushQueue() {
53624
54359
  if (this.queue.length === 0 || this.disposed)
53625
54360
  return;
@@ -53647,6 +54382,7 @@ class LogIndex {
53647
54382
  return;
53648
54383
  const id = encodeOffsetLogEventId(item.date, item.offset);
53649
54384
  const row = eventToRow({
54385
+ baseDir: this.baseDir,
53650
54386
  id,
53651
54387
  date: item.date,
53652
54388
  filePath: item.filePath,
@@ -53662,7 +54398,7 @@ class LogIndex {
53662
54398
  this.insertFtsStmt.run(id, row.search_text);
53663
54399
  if (!this.dirtyFiles.has(item.filePath)) {
53664
54400
  try {
53665
- const stats = statSync(item.filePath);
54401
+ const stats = statSync2(item.filePath);
53666
54402
  const indexedThrough = item.offset + item.byteLength;
53667
54403
  this.upsertFileStmt.run(item.filePath, item.date, Math.min(indexedThrough, stats.size), Math.trunc(stats.mtimeMs), Date.now());
53668
54404
  } catch {}
@@ -53689,6 +54425,7 @@ class LogIndex {
53689
54425
  continue;
53690
54426
  }
53691
54427
  const row = eventToRow({
54428
+ baseDir: this.baseDir,
53692
54429
  id: encodeOffsetLogEventId(date5, item.offset),
53693
54430
  date: date5,
53694
54431
  filePath,
@@ -53721,7 +54458,21 @@ class LogIndex {
53721
54458
  SELECT
53722
54459
  COUNT(*) AS total,
53723
54460
  COALESCE(SUM(has_error), 0) AS errorCount,
53724
- COALESCE(AVG(latency_ms), 0) AS avgLatencyMs
54461
+ COALESCE(AVG(latency_ms), 0) AS avgLatencyMs,
54462
+ COALESCE(SUM(CASE WHEN token_usage_json IS NOT NULL THEN 1 ELSE 0 END), 0) AS tokenUsageCount,
54463
+ COALESCE(SUM(token_input), 0) AS inputTokens,
54464
+ COALESCE(SUM(token_output), 0) AS outputTokens,
54465
+ COALESCE(SUM(token_total), 0) AS totalTokens,
54466
+ COALESCE(SUM(token_cached_input), 0) AS cachedInputTokens,
54467
+ COALESCE(SUM(token_cache_hit_input), 0) AS cacheHitInputTokens,
54468
+ COALESCE(SUM(token_cache_hit_rate_denominator), 0) AS cacheHitRateDenominatorTokens,
54469
+ COALESCE(SUM(token_cache_read_input), 0) AS cacheReadInputTokens,
54470
+ COALESCE(SUM(token_cache_creation_input), 0) AS cacheCreationInputTokens,
54471
+ COALESCE(SUM(token_cache_write_input), 0) AS cacheWriteInputTokens,
54472
+ COALESCE(SUM(token_cache_miss_input), 0) AS cacheMissInputTokens,
54473
+ COALESCE(SUM(token_reasoning), 0) AS reasoningTokens,
54474
+ COALESCE(SUM(token_billable_input), 0) AS billableInputTokens,
54475
+ COALESCE(SUM(token_billable_output), 0) AS billableOutputTokens
53725
54476
  FROM log_events e
53726
54477
  ${whereSql}
53727
54478
  `).get(...params);
@@ -53742,7 +54493,22 @@ class LogIndex {
53742
54493
  errorCount,
53743
54494
  errorRate: toPercent2(errorCount, total),
53744
54495
  avgLatencyMs: Math.round(Number(aggregate.avgLatencyMs) || 0),
53745
- p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0)
54496
+ p95LatencyMs: Math.round(p95Row?.latency_ms ?? 0),
54497
+ tokenUsageCount: Number(aggregate.tokenUsageCount) || 0,
54498
+ inputTokens: Number(aggregate.inputTokens) || 0,
54499
+ outputTokens: Number(aggregate.outputTokens) || 0,
54500
+ totalTokens: Number(aggregate.totalTokens) || 0,
54501
+ cachedInputTokens: Number(aggregate.cachedInputTokens) || 0,
54502
+ cacheHitInputTokens: Number(aggregate.cacheHitInputTokens) || 0,
54503
+ cacheHitRate: toPercent2(Number(aggregate.cacheHitInputTokens) || 0, Number(aggregate.cacheHitRateDenominatorTokens) || 0),
54504
+ cacheHitRateDenominatorTokens: Number(aggregate.cacheHitRateDenominatorTokens) || 0,
54505
+ cacheReadInputTokens: Number(aggregate.cacheReadInputTokens) || 0,
54506
+ cacheCreationInputTokens: Number(aggregate.cacheCreationInputTokens) || 0,
54507
+ cacheWriteInputTokens: Number(aggregate.cacheWriteInputTokens) || 0,
54508
+ cacheMissInputTokens: Number(aggregate.cacheMissInputTokens) || 0,
54509
+ reasoningTokens: Number(aggregate.reasoningTokens) || 0,
54510
+ billableInputTokens: Number(aggregate.billableInputTokens) || 0,
54511
+ billableOutputTokens: Number(aggregate.billableOutputTokens) || 0
53746
54512
  };
53747
54513
  }
53748
54514
  }
@@ -53823,7 +54589,7 @@ function getIndexedLogEventDetail(logConfig, id) {
53823
54589
  if (!parsedId)
53824
54590
  return null;
53825
54591
  const filePath = join5(baseDir, "events", `${parsedId.date}.jsonl`);
53826
- if (!existsSync3(filePath))
54592
+ if (!existsSync4(filePath))
53827
54593
  return null;
53828
54594
  const line2 = readLineAtOffset(filePath, parsedId.offset);
53829
54595
  if (!line2?.trim())
@@ -53956,8 +54722,25 @@ function createRunningStats() {
53956
54722
  total: 0,
53957
54723
  errorCount: 0,
53958
54724
  latencySum: 0,
53959
- latencyCounts: new Map
53960
- };
54725
+ latencyCounts: new Map,
54726
+ tokenUsageCount: 0,
54727
+ inputTokens: 0,
54728
+ outputTokens: 0,
54729
+ totalTokens: 0,
54730
+ cachedInputTokens: 0,
54731
+ cacheHitInputTokens: 0,
54732
+ cacheHitRateDenominatorTokens: 0,
54733
+ cacheReadInputTokens: 0,
54734
+ cacheCreationInputTokens: 0,
54735
+ cacheWriteInputTokens: 0,
54736
+ cacheMissInputTokens: 0,
54737
+ reasoningTokens: 0,
54738
+ billableInputTokens: 0,
54739
+ billableOutputTokens: 0
54740
+ };
54741
+ }
54742
+ function addNullable(total, value) {
54743
+ return total + (value ?? 0);
53961
54744
  }
53962
54745
  function updateRunningStats(stats, item) {
53963
54746
  stats.total += 1;
@@ -53968,6 +54751,23 @@ function updateRunningStats(stats, item) {
53968
54751
  stats.latencySum += latency;
53969
54752
  const roundedLatency = Math.round(latency);
53970
54753
  stats.latencyCounts.set(roundedLatency, (stats.latencyCounts.get(roundedLatency) ?? 0) + 1);
54754
+ const usage = item.tokenUsage;
54755
+ if (!usage)
54756
+ return;
54757
+ stats.tokenUsageCount += 1;
54758
+ stats.inputTokens = addNullable(stats.inputTokens, usage.inputTokens);
54759
+ stats.outputTokens = addNullable(stats.outputTokens, usage.outputTokens);
54760
+ stats.totalTokens = addNullable(stats.totalTokens, usage.totalTokens);
54761
+ stats.cachedInputTokens = addNullable(stats.cachedInputTokens, usage.cachedInputTokens);
54762
+ stats.cacheHitInputTokens = addNullable(stats.cacheHitInputTokens, usage.cacheHitInputTokens);
54763
+ stats.cacheHitRateDenominatorTokens = addNullable(stats.cacheHitRateDenominatorTokens, usage.cacheHitRateDenominatorTokens);
54764
+ stats.cacheReadInputTokens = addNullable(stats.cacheReadInputTokens, usage.cacheReadInputTokens);
54765
+ stats.cacheCreationInputTokens = addNullable(stats.cacheCreationInputTokens, usage.cacheCreationInputTokens);
54766
+ stats.cacheWriteInputTokens = addNullable(stats.cacheWriteInputTokens, usage.cacheWriteInputTokens);
54767
+ stats.cacheMissInputTokens = addNullable(stats.cacheMissInputTokens, usage.cacheMissInputTokens);
54768
+ stats.reasoningTokens = addNullable(stats.reasoningTokens, usage.reasoningTokens);
54769
+ stats.billableInputTokens = addNullable(stats.billableInputTokens, usage.billableInputTokens);
54770
+ stats.billableOutputTokens = addNullable(stats.billableOutputTokens, usage.billableOutputTokens);
53971
54771
  }
53972
54772
  function percentileFromCounts(latencyCounts, total, ratio) {
53973
54773
  if (total <= 0 || latencyCounts.size === 0)
@@ -53990,7 +54790,22 @@ function finalizeStats(stats) {
53990
54790
  errorCount: 0,
53991
54791
  errorRate: 0,
53992
54792
  avgLatencyMs: 0,
53993
- p95LatencyMs: 0
54793
+ p95LatencyMs: 0,
54794
+ tokenUsageCount: 0,
54795
+ inputTokens: 0,
54796
+ outputTokens: 0,
54797
+ totalTokens: 0,
54798
+ cachedInputTokens: 0,
54799
+ cacheHitInputTokens: 0,
54800
+ cacheHitRate: 0,
54801
+ cacheHitRateDenominatorTokens: 0,
54802
+ cacheReadInputTokens: 0,
54803
+ cacheCreationInputTokens: 0,
54804
+ cacheWriteInputTokens: 0,
54805
+ cacheMissInputTokens: 0,
54806
+ reasoningTokens: 0,
54807
+ billableInputTokens: 0,
54808
+ billableOutputTokens: 0
53994
54809
  };
53995
54810
  }
53996
54811
  return {
@@ -53998,8 +54813,26 @@ function finalizeStats(stats) {
53998
54813
  errorCount: stats.errorCount,
53999
54814
  errorRate: toPercent3(stats.errorCount, stats.total),
54000
54815
  avgLatencyMs: Math.round(stats.latencySum / stats.total),
54001
- p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95)
54002
- };
54816
+ p95LatencyMs: percentileFromCounts(stats.latencyCounts, stats.total, 0.95),
54817
+ tokenUsageCount: stats.tokenUsageCount,
54818
+ inputTokens: stats.inputTokens,
54819
+ outputTokens: stats.outputTokens,
54820
+ totalTokens: stats.totalTokens,
54821
+ cachedInputTokens: stats.cachedInputTokens,
54822
+ cacheHitInputTokens: stats.cacheHitInputTokens,
54823
+ cacheHitRate: toPercent3(stats.cacheHitInputTokens, stats.cacheHitRateDenominatorTokens),
54824
+ cacheHitRateDenominatorTokens: stats.cacheHitRateDenominatorTokens,
54825
+ cacheReadInputTokens: stats.cacheReadInputTokens,
54826
+ cacheCreationInputTokens: stats.cacheCreationInputTokens,
54827
+ cacheWriteInputTokens: stats.cacheWriteInputTokens,
54828
+ cacheMissInputTokens: stats.cacheMissInputTokens,
54829
+ reasoningTokens: stats.reasoningTokens,
54830
+ billableInputTokens: stats.billableInputTokens,
54831
+ billableOutputTokens: stats.billableOutputTokens
54832
+ };
54833
+ }
54834
+ function createEmptyLogQueryStats() {
54835
+ return finalizeStats(createRunningStats());
54003
54836
  }
54004
54837
  function compareLocatedEvents(a, b, sort) {
54005
54838
  if (a.ts !== b.ts) {
@@ -54071,7 +54904,8 @@ function eventToSummary(item) {
54071
54904
  hasMetadata: identity.hasMetadata,
54072
54905
  userIdRaw: identity.userIdRaw,
54073
54906
  userKey: identity.userKey,
54074
- sessionId: identity.sessionId
54907
+ sessionId: identity.sessionId,
54908
+ tokenUsage: item.tokenUsage
54075
54909
  };
54076
54910
  }
54077
54911
  function createLogEventSummaryFromEvent(event, location) {
@@ -54083,7 +54917,8 @@ function createLogEventSummaryFromEvent(event, location) {
54083
54917
  ts: Number.isFinite(ts) ? ts : 0,
54084
54918
  level: getLevel2(event),
54085
54919
  statusClass: getStatusClass3(event),
54086
- event
54920
+ event,
54921
+ tokenUsage: extractTokenUsageSummaryFromLogEvent(event)
54087
54922
  });
54088
54923
  }
54089
54924
  function logEventMatchesQuery(event, query) {
@@ -54172,23 +55007,23 @@ function readStreamContent(baseDir, streamFile) {
54172
55007
  if (!streamFile)
54173
55008
  return { content: null, warning: null };
54174
55009
  try {
54175
- const resolvedBase = resolve4(baseDir);
54176
- const resolvedFromFile = resolve4(streamFile);
55010
+ const resolvedBase = resolve5(baseDir);
55011
+ const resolvedFromFile = resolve5(streamFile);
54177
55012
  const looksLikeStreamFile = resolvedFromFile.endsWith(".sse.raw");
54178
55013
  if (!looksLikeStreamFile) {
54179
55014
  return { content: null, warning: "stream_file \u4E0D\u662F .sse.raw \u6587\u4EF6\uFF0C\u5DF2\u8DF3\u8FC7\u8BFB\u53D6\u3002" };
54180
55015
  }
54181
- if (existsSync4(resolvedFromFile)) {
54182
- return { content: readFileSync4(resolvedFromFile, "utf-8"), warning: null };
55016
+ if (existsSync5(resolvedFromFile)) {
55017
+ return { content: readFileSync5(resolvedFromFile, "utf-8"), warning: null };
54183
55018
  }
54184
- const fallbackPath = resolve4(resolvedBase, streamFile);
55019
+ const fallbackPath = resolve5(resolvedBase, streamFile);
54185
55020
  if (!fallbackPath.startsWith(`${resolvedBase}/`) && fallbackPath !== resolvedBase) {
54186
55021
  return { content: null, warning: "stream_file \u8DEF\u5F84\u975E\u6CD5\uFF0C\u5DF2\u62D2\u7EDD\u8BFB\u53D6\u3002" };
54187
55022
  }
54188
- if (!existsSync4(fallbackPath)) {
55023
+ if (!existsSync5(fallbackPath)) {
54189
55024
  return { content: null, warning: "stream_file \u4E0D\u5B58\u5728\uFF0C\u53EF\u80FD\u5DF2\u88AB\u6E05\u7406\u3002" };
54190
55025
  }
54191
- return { content: readFileSync4(fallbackPath, "utf-8"), warning: null };
55026
+ return { content: readFileSync5(fallbackPath, "utf-8"), warning: null };
54192
55027
  } catch (err) {
54193
55028
  return {
54194
55029
  content: null,
@@ -54205,6 +55040,10 @@ async function buildLogEventDetail(id, parsed, location, context2) {
54205
55040
  const responseBodyAvailable = event.response_body !== undefined;
54206
55041
  const streamCaptured = Boolean(event.stream_file);
54207
55042
  const { content: streamContent, warning: streamWarning } = readStreamContent(resolveLogBaseDir(context2.logConfig), event.stream_file);
55043
+ const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, {
55044
+ baseDir: resolveLogBaseDir(context2.logConfig),
55045
+ streamContent: streamContent ?? undefined
55046
+ }) ?? null;
54208
55047
  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;
54209
55048
  const pluginsSection = hasPluginData ? {
54210
55049
  request: event.plugins_request,
@@ -54230,7 +55069,16 @@ async function buildLogEventDetail(id, parsed, location, context2) {
54230
55069
  hasError: level === "error",
54231
55070
  model: event.model_out || event.model_in,
54232
55071
  modelIn: event.model_in,
54233
- modelOut: event.model_out
55072
+ modelOut: event.model_out,
55073
+ tokenUsage
55074
+ },
55075
+ usage: {
55076
+ tokenUsage,
55077
+ requestBytes: event.request_bytes ?? 0,
55078
+ responseBytes: event.response_bytes ?? null,
55079
+ streamBytes: event.stream_bytes ?? null,
55080
+ streamFileBytes: event.stream_file_bytes ?? null,
55081
+ streamFileTruncated: event.stream_file_truncated === true
54234
55082
  },
54235
55083
  request: {
54236
55084
  method: event.method,
@@ -54272,16 +55120,10 @@ async function buildLogEventDetail(id, parsed, location, context2) {
54272
55120
  }
54273
55121
  async function scanEvents(baseDir, query) {
54274
55122
  const eventsDir = join6(baseDir, "events");
54275
- if (!existsSync4(eventsDir)) {
55123
+ if (!existsSync5(eventsDir)) {
54276
55124
  return {
54277
55125
  items: [],
54278
- stats: {
54279
- total: 0,
54280
- errorCount: 0,
54281
- errorRate: 0,
54282
- avgLatencyMs: 0,
54283
- p95LatencyMs: 0
54284
- },
55126
+ stats: createEmptyLogQueryStats(),
54285
55127
  meta: {
54286
55128
  scannedFiles: 0,
54287
55129
  scannedLines: 0,
@@ -54305,7 +55147,7 @@ async function scanEvents(baseDir, query) {
54305
55147
  break;
54306
55148
  }
54307
55149
  const filePath = join6(eventsDir, `${date5}.jsonl`);
54308
- if (!existsSync4(filePath))
55150
+ if (!existsSync5(filePath))
54309
55151
  continue;
54310
55152
  scannedFiles += 1;
54311
55153
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
@@ -54367,6 +55209,7 @@ async function scanEvents(baseDir, query) {
54367
55209
  continue;
54368
55210
  if (!containsKeyword(event, query.q))
54369
55211
  continue;
55212
+ const tokenUsage = extractTokenUsageSummaryFromLogEvent(event, { baseDir });
54370
55213
  const located = {
54371
55214
  id: encodeEventId(date5, lineNumber),
54372
55215
  date: date5,
@@ -54374,7 +55217,8 @@ async function scanEvents(baseDir, query) {
54374
55217
  ts,
54375
55218
  level,
54376
55219
  statusClass,
54377
- event
55220
+ event,
55221
+ tokenUsage
54378
55222
  };
54379
55223
  updateRunningStats(runningStats, located);
54380
55224
  insertBoundedEvent(items, located, query.sort, maxKeep);
@@ -54434,13 +55278,7 @@ async function queryLogEventsInternal(context2, input, maxLimit) {
54434
55278
  items: [],
54435
55279
  nextCursor: null,
54436
55280
  hasMore: false,
54437
- stats: {
54438
- total: 0,
54439
- errorCount: 0,
54440
- errorRate: 0,
54441
- avgLatencyMs: 0,
54442
- p95LatencyMs: 0
54443
- },
55281
+ stats: createEmptyLogQueryStats(),
54444
55282
  meta: {
54445
55283
  scannedFiles: 0,
54446
55284
  scannedLines: 0,
@@ -54492,7 +55330,7 @@ async function getLogEventDetailById(context2, id) {
54492
55330
  const { date: date5, line: line2 } = decodeEventId(id);
54493
55331
  const baseDir = resolveLogBaseDir(context2.logConfig);
54494
55332
  const filePath = join6(baseDir, "events", `${date5}.jsonl`);
54495
- if (!existsSync4(filePath))
55333
+ if (!existsSync5(filePath))
54496
55334
  return null;
54497
55335
  const stream = createReadStream3(filePath, { encoding: "utf-8" });
54498
55336
  const rl = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
@@ -54523,6 +55361,7 @@ function escapeCsvValue(value) {
54523
55361
  return text2;
54524
55362
  }
54525
55363
  function toCsvRow(item) {
55364
+ const usage = item.tokenUsage;
54526
55365
  return [
54527
55366
  item.id,
54528
55367
  item.ts,
@@ -54543,7 +55382,36 @@ function toCsvRow(item) {
54543
55382
  item.userKey ?? "",
54544
55383
  item.sessionId ?? "",
54545
55384
  item.message,
54546
- item.errorType ?? ""
55385
+ item.errorType ?? "",
55386
+ usage?.inputTokens ?? "",
55387
+ usage?.outputTokens ?? "",
55388
+ usage?.totalTokens ?? "",
55389
+ usage?.cachedInputTokens ?? "",
55390
+ usage?.cacheHitInputTokens ?? "",
55391
+ usage?.cacheHitRate ?? "",
55392
+ usage?.cacheHitRateDenominatorTokens ?? "",
55393
+ usage?.cacheReadInputTokens ?? "",
55394
+ usage?.cacheCreationInputTokens ?? "",
55395
+ usage?.cacheCreationInputTokens5m ?? "",
55396
+ usage?.cacheCreationInputTokens1h ?? "",
55397
+ usage?.cacheWriteInputTokens ?? "",
55398
+ usage?.cacheMissInputTokens ?? "",
55399
+ usage?.reasoningTokens ?? "",
55400
+ usage?.audioInputTokens ?? "",
55401
+ usage?.audioOutputTokens ?? "",
55402
+ usage?.textInputTokens ?? "",
55403
+ usage?.textOutputTokens ?? "",
55404
+ usage?.acceptedPredictionTokens ?? "",
55405
+ usage?.rejectedPredictionTokens ?? "",
55406
+ usage?.toolUsePromptTokens ?? "",
55407
+ usage?.billableInputTokens ?? "",
55408
+ usage?.billableOutputTokens ?? "",
55409
+ usage?.creditUsage ?? "",
55410
+ usage?.cost ?? "",
55411
+ usage?.providerStyle ?? "",
55412
+ usage?.source ?? "",
55413
+ usage?.rawUsagePath ?? "",
55414
+ usage?.cacheHitRateFormula ?? ""
54547
55415
  ].map(escapeCsvValue).join(",");
54548
55416
  }
54549
55417
  function createCsvExportStream(items) {
@@ -54568,7 +55436,36 @@ function createCsvExportStream(items) {
54568
55436
  "userKey",
54569
55437
  "sessionId",
54570
55438
  "message",
54571
- "errorType"
55439
+ "errorType",
55440
+ "usage.inputTokens",
55441
+ "usage.outputTokens",
55442
+ "usage.totalTokens",
55443
+ "usage.cachedInputTokens",
55444
+ "usage.cacheHitInputTokens",
55445
+ "usage.cacheHitRate",
55446
+ "usage.cacheHitRateDenominatorTokens",
55447
+ "usage.cacheReadInputTokens",
55448
+ "usage.cacheCreationInputTokens",
55449
+ "usage.cacheCreationInputTokens5m",
55450
+ "usage.cacheCreationInputTokens1h",
55451
+ "usage.cacheWriteInputTokens",
55452
+ "usage.cacheMissInputTokens",
55453
+ "usage.reasoningTokens",
55454
+ "usage.audioInputTokens",
55455
+ "usage.audioOutputTokens",
55456
+ "usage.textInputTokens",
55457
+ "usage.textOutputTokens",
55458
+ "usage.acceptedPredictionTokens",
55459
+ "usage.rejectedPredictionTokens",
55460
+ "usage.toolUsePromptTokens",
55461
+ "usage.billableInputTokens",
55462
+ "usage.billableOutputTokens",
55463
+ "usage.creditUsage",
55464
+ "usage.cost",
55465
+ "usage.providerStyle",
55466
+ "usage.source",
55467
+ "usage.rawUsagePath",
55468
+ "usage.cacheHitRateFormula"
54572
55469
  ];
54573
55470
  return new ReadableStream({
54574
55471
  start(controller) {
@@ -54648,7 +55545,7 @@ function parseBooleanFlag(value) {
54648
55545
 
54649
55546
  // src/log-sessions.ts
54650
55547
  init_config();
54651
- import { createReadStream as createReadStream4, existsSync as existsSync5 } from "fs";
55548
+ import { createReadStream as createReadStream4, existsSync as existsSync6 } from "fs";
54652
55549
  import { join as join7 } from "path";
54653
55550
  import { createInterface as createInterface3 } from "readline";
54654
55551
  var MAX_LINES_SCANNED3 = 250000;
@@ -54771,7 +55668,7 @@ async function queryLogSessions(context2, input) {
54771
55668
  }
54772
55669
  const baseDir = resolveLogBaseDir(context2.logConfig);
54773
55670
  const eventsDir = join7(baseDir, "events");
54774
- if (!existsSync5(eventsDir)) {
55671
+ if (!existsSync6(eventsDir)) {
54775
55672
  return createEmptyResult(normalized.fromMs, normalized.toMs);
54776
55673
  }
54777
55674
  const usersMap = new Map;
@@ -54790,7 +55687,7 @@ async function queryLogSessions(context2, input) {
54790
55687
  break;
54791
55688
  }
54792
55689
  const filePath = join7(eventsDir, `${date5}.jsonl`);
54793
- if (!existsSync5(filePath))
55690
+ if (!existsSync6(filePath))
54794
55691
  continue;
54795
55692
  scannedFiles += 1;
54796
55693
  const stream = createReadStream4(filePath, { encoding: "utf-8" });
@@ -54899,7 +55796,7 @@ async function queryLogSessions(context2, input) {
54899
55796
 
54900
55797
  // src/log-storage.ts
54901
55798
  init_config();
54902
- import { existsSync as existsSync6, promises as fsPromises } from "fs";
55799
+ import { existsSync as existsSync7, promises as fsPromises } from "fs";
54903
55800
  import { join as join8 } from "path";
54904
55801
  var cachedStorage = null;
54905
55802
  var calculationPromise = null;
@@ -54908,7 +55805,7 @@ var CACHE_TTL_MS2 = 60 * 60 * 1000;
54908
55805
  var CALCULATION_INTERVAL_MS = 60 * 60 * 1000;
54909
55806
  var MIN_CALCULATION_INTERVAL_MS = 5 * 60 * 1000;
54910
55807
  async function calculateDirSize(dirPath) {
54911
- if (!existsSync6(dirPath)) {
55808
+ if (!existsSync7(dirPath)) {
54912
55809
  return { bytes: 0, fileCount: 0 };
54913
55810
  }
54914
55811
  let bytes = 0;
@@ -54963,7 +55860,7 @@ async function doCalculateStorage(logConfig) {
54963
55860
  };
54964
55861
  }
54965
55862
  async function calculateIndexSize(baseDir) {
54966
- if (!existsSync6(baseDir)) {
55863
+ if (!existsSync7(baseDir)) {
54967
55864
  return { bytes: 0, fileCount: 0 };
54968
55865
  }
54969
55866
  let bytes = 0;
@@ -55044,10 +55941,10 @@ function subscribeLogEvents(subscriber) {
55044
55941
  import {
55045
55942
  appendFileSync,
55046
55943
  closeSync as closeSync2,
55047
- existsSync as existsSync7,
55944
+ existsSync as existsSync8,
55048
55945
  mkdirSync as mkdirSync4,
55049
55946
  openSync as openSync2,
55050
- statSync as statSync2,
55947
+ statSync as statSync3,
55051
55948
  writeSync
55052
55949
  } from "fs";
55053
55950
  import { join as join9 } from "path";
@@ -55078,13 +55975,13 @@ class Logger {
55078
55975
  }
55079
55976
  ensureDirs() {
55080
55977
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
55081
- if (!existsSync7(dir))
55978
+ if (!existsSync8(dir))
55082
55979
  mkdirSync4(dir, { recursive: true });
55083
55980
  }
55084
55981
  }
55085
55982
  ensureStreamDateDir(dateStr) {
55086
55983
  const dir = join9(this.streamsDir, dateStr);
55087
- if (!existsSync7(dir))
55984
+ if (!existsSync8(dir))
55088
55985
  mkdirSync4(dir, { recursive: true });
55089
55986
  return dir;
55090
55987
  }
@@ -55092,11 +55989,12 @@ class Logger {
55092
55989
  if (!this._enabled)
55093
55990
  return;
55094
55991
  try {
55992
+ const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
55095
55993
  this.ensureDirs();
55096
- const dateStr = event.ts_start.slice(0, 10);
55994
+ const dateStr = enrichedEvent.ts_start.slice(0, 10);
55097
55995
  const filePath = join9(this.eventsDir, `${dateStr}.jsonl`);
55098
- const offset = existsSync7(filePath) ? statSync2(filePath).size : 0;
55099
- const line2 = `${JSON.stringify(event)}
55996
+ const offset = existsSync8(filePath) ? statSync3(filePath).size : 0;
55997
+ const line2 = `${JSON.stringify(enrichedEvent)}
55100
55998
  `;
55101
55999
  appendFileSync(filePath, line2);
55102
56000
  const id = encodeOffsetLogEventId(dateStr, offset);
@@ -55106,9 +56004,9 @@ class Logger {
55106
56004
  date: dateStr,
55107
56005
  offset,
55108
56006
  byteLength: Buffer.byteLength(line2),
55109
- event
56007
+ event: enrichedEvent
55110
56008
  });
55111
- publishLogEvent({ id, date: dateStr, filePath, offset, event });
56009
+ publishLogEvent({ id, date: dateStr, filePath, offset, event: enrichedEvent });
55112
56010
  } catch (err) {
55113
56011
  console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
55114
56012
  }
@@ -56399,7 +57297,7 @@ var openAPISpec = {
56399
57297
  // src/plugin-loader.ts
56400
57298
  import { mkdtemp, rm, writeFile } from "fs/promises";
56401
57299
  import { tmpdir } from "os";
56402
- import { join as join10, resolve as resolve5 } from "path";
57300
+ import { join as join10, resolve as resolve6 } from "path";
56403
57301
  function isLocalPath(pkg) {
56404
57302
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
56405
57303
  }
@@ -56455,7 +57353,7 @@ async function importPlugin(pkg, configDir) {
56455
57353
  const localPath = await fetchRemotePlugin(pkg);
56456
57354
  modulePath = `${localPath}?t=${Date.now()}`;
56457
57355
  } else if (isLocalPath(pkg)) {
56458
- const absolutePath = resolve5(configDir, pkg);
57356
+ const absolutePath = resolve6(configDir, pkg);
56459
57357
  modulePath = `${absolutePath}?t=${Date.now()}`;
56460
57358
  } else {
56461
57359
  modulePath = pkg;
@@ -56848,6 +57746,7 @@ async function proxyRequest(c2, options) {
56848
57746
  });
56849
57747
  }
56850
57748
  const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
57749
+ const tokenUsageCollector = createTokenUsageStreamCollector(`${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
56851
57750
  let upstreamBytes = 0;
56852
57751
  let writeEventCalled = false;
56853
57752
  const finalizeAndWriteEvent = () => {
@@ -56859,6 +57758,7 @@ async function proxyRequest(c2, options) {
56859
57758
  truncated: false,
56860
57759
  filePath: null
56861
57760
  };
57761
+ const tokenUsage2 = tokenUsageCollector.getUsage();
56862
57762
  logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
56863
57763
  upstream_status: sseStatus,
56864
57764
  content_type_res: contentTypeRes,
@@ -56870,6 +57770,7 @@ async function proxyRequest(c2, options) {
56870
57770
  stream_file_bytes: captureResult.bytesWritten
56871
57771
  },
56872
57772
  ...captureResult.truncated && { stream_file_truncated: true },
57773
+ ...tokenUsage2 && { token_usage: tokenUsage2 },
56873
57774
  ...requestBody !== undefined && { request_body: requestBody },
56874
57775
  ...pluginLogOverrides
56875
57776
  }));
@@ -56885,6 +57786,7 @@ async function proxyRequest(c2, options) {
56885
57786
  return;
56886
57787
  }
56887
57788
  upstreamBytes += value.byteLength;
57789
+ tokenUsageCollector.addChunk(value);
56888
57790
  capture?.write(value);
56889
57791
  controller.enqueue(value);
56890
57792
  } catch (err) {
@@ -56935,12 +57837,14 @@ async function proxyRequest(c2, options) {
56935
57837
  });
56936
57838
  }
56937
57839
  const responseBytes = Buffer.byteLength(responseText, "utf-8");
57840
+ const tokenUsage = extractTokenUsageFromResponseText(responseText, "response_body", `${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
56938
57841
  const eventOverrides = {
56939
57842
  upstream_status: upstreamRes.status,
56940
57843
  content_type_res: contentTypeRes,
56941
57844
  response_headers: finalResponseHeaders,
56942
57845
  response_bytes: responseBytes,
56943
57846
  provider_request_id: providerRequestId,
57847
+ ...tokenUsage && { token_usage: tokenUsage },
56944
57848
  ...pluginLogOverrides
56945
57849
  };
56946
57850
  if (requestBody !== undefined) {
@@ -57226,7 +58130,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57226
58130
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
57227
58131
  const CRYPTO_SESSION_MAX = 512;
57228
58132
  const schemaPath = getBundledSchemaPath();
57229
- const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
58133
+ const schemaJson = JSON.parse(readFileSync6(schemaPath, "utf-8"));
57230
58134
  const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
57231
58135
  for (const [id, record2] of Array.from(cryptoSessions.entries())) {
57232
58136
  if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
@@ -57831,7 +58735,7 @@ async function createApp(store, options) {
57831
58735
  }
57832
58736
  const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
57833
58737
  options?.registerCleanup?.(stopLogStorageTask);
57834
- const configDir = dirname3(resolve6(store.getPath()));
58738
+ const configDir = dirname3(resolve7(store.getPath()));
57835
58739
  const pluginManager = new PluginManager(configDir);
57836
58740
  const reloadResult = await pluginManager.reloadAll(config2.providers);
57837
58741
  if (!reloadResult.ok) {
@@ -57947,9 +58851,9 @@ async function startServer(options) {
57947
58851
  }
57948
58852
 
57949
58853
  // src/cli/runtime.ts
57950
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
58854
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync4 } from "fs";
57951
58855
  import { homedir as homedir2 } from "os";
57952
- import { join as join11, resolve as resolve7 } from "path";
58856
+ import { join as join11, resolve as resolve8 } from "path";
57953
58857
  function getRuntimeDirs() {
57954
58858
  const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
57955
58859
  const root2 = override?.trim() ? override.trim() : join11(homedir2(), ".local-router");
@@ -57982,11 +58886,11 @@ function writeRuntimeState(state) {
57982
58886
  }
57983
58887
  function readRuntimeState() {
57984
58888
  const files = getRuntimeFiles();
57985
- if (!existsSync8(files.state)) {
58889
+ if (!existsSync9(files.state)) {
57986
58890
  return null;
57987
58891
  }
57988
58892
  try {
57989
- return JSON.parse(readFileSync6(files.state, "utf-8"));
58893
+ return JSON.parse(readFileSync7(files.state, "utf-8"));
57990
58894
  } catch {
57991
58895
  return null;
57992
58896
  }
@@ -57997,7 +58901,7 @@ function clearRuntimeFiles() {
57997
58901
  rmSync(files.state, { force: true });
57998
58902
  }
57999
58903
  function resolveConfigArgPath(pathValue) {
58000
- return resolve7(pathValue);
58904
+ return resolve8(pathValue);
58001
58905
  }
58002
58906
 
58003
58907
  // src/cli/process.ts
@@ -58175,7 +59079,7 @@ async function startDaemon(flags) {
58175
59079
  }
58176
59080
  let tail = "";
58177
59081
  try {
58178
- const content = readFileSync7(files.daemonLog, "utf-8");
59082
+ const content = readFileSync8(files.daemonLog, "utf-8");
58179
59083
  tail = content.split(`
58180
59084
  `).slice(-20).join(`
58181
59085
  `);
@@ -58208,11 +59112,11 @@ async function stopProcess(graceMs = 8000) {
58208
59112
  }
58209
59113
  function readLogDelta(filePath, offset) {
58210
59114
  try {
58211
- const stats = statSync3(filePath);
59115
+ const stats = statSync4(filePath);
58212
59116
  if (stats.size <= offset) {
58213
59117
  return { content: "", nextOffset: offset };
58214
59118
  }
58215
- const full = readFileSync7(filePath, "utf-8");
59119
+ const full = readFileSync8(filePath, "utf-8");
58216
59120
  const content = full.slice(offset);
58217
59121
  return { content, nextOffset: full.length };
58218
59122
  } catch {
@@ -59225,13 +60129,13 @@ init_config();
59225
60129
  init_config_validate();
59226
60130
  init_errors();
59227
60131
  init_output();
59228
- import { existsSync as existsSync10 } from "fs";
60132
+ import { existsSync as existsSync11 } from "fs";
59229
60133
  import { parseArgs as parseArgs4 } from "util";
59230
60134
  init_registry();
59231
60135
  function tryRead(configArg) {
59232
60136
  try {
59233
60137
  const path = resolveConfigPath(configArg);
59234
- if (!existsSync10(path)) {
60138
+ if (!existsSync11(path)) {
59235
60139
  return { path, config: null, error: `\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}` };
59236
60140
  }
59237
60141
  const config2 = loadConfig(path);
@@ -59666,7 +60570,7 @@ defineCommand({
59666
60570
  });
59667
60571
 
59668
60572
  // src/cli/handlers/introspection.ts
59669
- import { readFileSync as readFileSync9 } from "fs";
60573
+ import { readFileSync as readFileSync10 } from "fs";
59670
60574
  import { fileURLToPath as fileURLToPath2 } from "url";
59671
60575
  import { parseArgs as parseArgs5 } from "util";
59672
60576
 
@@ -59942,7 +60846,7 @@ defineCommand({
59942
60846
  hint: "\u8BF7\u786E\u8BA4 npm \u5305\u5B8C\u6574\u6027\uFF0C\u6216\u5F00\u53D1\u6A21\u5F0F\u4E0B\u4ECE\u6E90\u7801\u8FD0\u884C"
59943
60847
  });
59944
60848
  }
59945
- const text2 = readFileSync9(fileURLToPath2(schemaUrl), "utf-8");
60849
+ const text2 = readFileSync10(fileURLToPath2(schemaUrl), "utf-8");
59946
60850
  const data2 = JSON.parse(text2);
59947
60851
  emitResult(ctx, {
59948
60852
  command: "schema.config",
@@ -60162,7 +61066,7 @@ defineCommand({
60162
61066
 
60163
61067
  // src/cli/handlers/lifecycle.ts
60164
61068
  init_config();
60165
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
61069
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
60166
61070
  import { setTimeout as sleep3 } from "timers/promises";
60167
61071
  import { parseArgs as parseArgs6 } from "util";
60168
61072
  init_errors();
@@ -60467,10 +61371,10 @@ defineCommand({
60467
61371
  })
60468
61372
  });
60469
61373
  function readLastLines(filePath, lines) {
60470
- if (!existsSync11(filePath)) {
61374
+ if (!existsSync12(filePath)) {
60471
61375
  return { content: "", offset: 0 };
60472
61376
  }
60473
- const full = readFileSync10(filePath, "utf-8");
61377
+ const full = readFileSync11(filePath, "utf-8");
60474
61378
  const rendered = full.split(`
60475
61379
  `).slice(-lines).join(`
60476
61380
  `);
@@ -60501,7 +61405,7 @@ defineCommand({
60501
61405
  const linesRaw = parsed.values.lines ?? "100";
60502
61406
  const lines = Number.parseInt(linesRaw, 10);
60503
61407
  const initial = readLastLines(files.daemonLog, Number.isFinite(lines) ? lines : 100);
60504
- if (!existsSync11(files.daemonLog)) {
61408
+ if (!existsSync12(files.daemonLog)) {
60505
61409
  emitResult(ctx, {
60506
61410
  command: "logs.daemon",
60507
61411
  data: { exists: false, path: files.daemonLog, lines: [] },