@lakphy/local-router 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -11166,8 +11166,8 @@ __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";
11170
- import { dirname as dirname4, join as join13 } from "path";
11169
+ import { existsSync as existsSync10, readdirSync, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
11170
+ import { dirname as dirname4, join as join12 } from "path";
11171
11171
  import { parseArgs as parseArgs2 } from "util";
11172
11172
  function maskApiKey(k) {
11173
11173
  if (k.length <= 8)
@@ -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
- const backupDir = join13(dirname4(path), ".backups");
11256
- if (!existsSync9(backupDir)) {
11255
+ const backupDir = join12(dirname4(path), ".backups");
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
  });
@@ -11262,21 +11262,21 @@ async function configDiff(args, flags) {
11262
11262
  if (files.length === 0) {
11263
11263
  throw new CliError("CONFIG_NOT_FOUND", "\u5907\u4EFD\u76EE\u5F55\u4E3A\u7A7A");
11264
11264
  }
11265
- beforePath = join13(backupDir, files[0]);
11266
- } else if (existsSync9(against)) {
11265
+ beforePath = join12(backupDir, files[0]);
11266
+ } else if (existsSync10(against)) {
11267
11267
  beforePath = against;
11268
11268
  } else {
11269
- const backupDir = join13(dirname4(path), ".backups");
11270
- const candidate = join13(backupDir, `config-${against}.json5`);
11271
- if (existsSync9(candidate))
11269
+ const backupDir = join12(dirname4(path), ".backups");
11270
+ const candidate = join12(backupDir, `config-${against}.json5`);
11271
+ if (existsSync10(candidate))
11272
11272
  beforePath = candidate;
11273
- else if (existsSync9(join13(backupDir, against)))
11274
- beforePath = join13(backupDir, against);
11273
+ else if (existsSync10(join12(backupDir, against)))
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 {
@@ -11426,12 +11426,12 @@ ${result.diff}`
11426
11426
  });
11427
11427
  }
11428
11428
  function listBackups(configPath) {
11429
- const dir = join13(dirname4(configPath), ".backups");
11430
- if (!existsSync9(dir))
11429
+ const dir = join12(dirname4(configPath), ".backups");
11430
+ if (!existsSync10(dir))
11431
11431
  return [];
11432
11432
  return readdirSync(dir).filter((f) => f.startsWith("config-") && f.endsWith(".json5")).map((f) => {
11433
- const full = join13(dir, f);
11434
- const st = statSync4(full);
11433
+ const full = join12(dir, f);
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 closeSync2, openSync as openSync2, 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;
@@ -55041,7 +55938,15 @@ function subscribeLogEvents(subscriber) {
55041
55938
  }
55042
55939
 
55043
55940
  // src/logger.ts
55044
- import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
55941
+ import {
55942
+ appendFileSync,
55943
+ closeSync as closeSync2,
55944
+ existsSync as existsSync8,
55945
+ mkdirSync as mkdirSync4,
55946
+ openSync as openSync2,
55947
+ statSync as statSync3,
55948
+ writeSync
55949
+ } from "fs";
55045
55950
  import { join as join9 } from "path";
55046
55951
  class Logger {
55047
55952
  baseDir;
@@ -55070,13 +55975,13 @@ class Logger {
55070
55975
  }
55071
55976
  ensureDirs() {
55072
55977
  for (const dir of [this.baseDir, this.eventsDir, this.streamsDir]) {
55073
- if (!existsSync7(dir))
55978
+ if (!existsSync8(dir))
55074
55979
  mkdirSync4(dir, { recursive: true });
55075
55980
  }
55076
55981
  }
55077
55982
  ensureStreamDateDir(dateStr) {
55078
55983
  const dir = join9(this.streamsDir, dateStr);
55079
- if (!existsSync7(dir))
55984
+ if (!existsSync8(dir))
55080
55985
  mkdirSync4(dir, { recursive: true });
55081
55986
  return dir;
55082
55987
  }
@@ -55084,11 +55989,12 @@ class Logger {
55084
55989
  if (!this._enabled)
55085
55990
  return;
55086
55991
  try {
55992
+ const enrichedEvent = enrichLogEventTokenUsage(event, { baseDir: this.baseDir });
55087
55993
  this.ensureDirs();
55088
- const dateStr = event.ts_start.slice(0, 10);
55994
+ const dateStr = enrichedEvent.ts_start.slice(0, 10);
55089
55995
  const filePath = join9(this.eventsDir, `${dateStr}.jsonl`);
55090
- const offset = existsSync7(filePath) ? statSync2(filePath).size : 0;
55091
- const line2 = `${JSON.stringify(event)}
55996
+ const offset = existsSync8(filePath) ? statSync3(filePath).size : 0;
55997
+ const line2 = `${JSON.stringify(enrichedEvent)}
55092
55998
  `;
55093
55999
  appendFileSync(filePath, line2);
55094
56000
  const id = encodeOffsetLogEventId(dateStr, offset);
@@ -55098,29 +56004,80 @@ class Logger {
55098
56004
  date: dateStr,
55099
56005
  offset,
55100
56006
  byteLength: Buffer.byteLength(line2),
55101
- event
56007
+ event: enrichedEvent
55102
56008
  });
55103
- publishLogEvent({ id, date: dateStr, filePath, offset, event });
56009
+ publishLogEvent({ id, date: dateStr, filePath, offset, event: enrichedEvent });
55104
56010
  } catch (err) {
55105
56011
  console.error("[logger] \u4E8B\u4EF6\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
55106
56012
  }
55107
56013
  }
55108
- writeStreamFile(requestId, dateStr, content) {
55109
- if (!this._enabled || !this._streamsEnabled)
55110
- return null;
56014
+ openStreamCapture(requestId, dateStr) {
56015
+ if (!this._enabled || !this._streamsEnabled) {
56016
+ return makeNoopStreamCaptureHandle();
56017
+ }
56018
+ const maxStreamBytes = this.maxStreamBytes;
56019
+ const truncationMarker = Buffer.from(`
56020
+ [TRUNCATED]`);
56021
+ let filePath;
56022
+ let fd;
55111
56023
  try {
55112
56024
  const dir = this.ensureStreamDateDir(dateStr);
55113
- const filePath = join9(dir, `${requestId}.sse.raw`);
55114
- const toWrite = content.length > this.maxStreamBytes ? `${content.slice(0, this.maxStreamBytes)}
55115
- [TRUNCATED]` : content;
55116
- writeFileSync4(filePath, toWrite);
55117
- return filePath;
56025
+ filePath = join9(dir, `${requestId}.sse.raw`);
56026
+ fd = openSync2(filePath, "a");
55118
56027
  } catch (err) {
55119
- console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
55120
- return null;
56028
+ console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u6253\u5F00\u5931\u8D25:", err);
56029
+ return makeNoopStreamCaptureHandle();
55121
56030
  }
56031
+ let bytes = 0;
56032
+ let truncated = false;
56033
+ let finalized = false;
56034
+ return {
56035
+ filePath,
56036
+ write(chunk) {
56037
+ if (finalized || truncated || fd == null)
56038
+ return;
56039
+ try {
56040
+ if (bytes + chunk.byteLength > maxStreamBytes) {
56041
+ const remaining = Math.max(0, maxStreamBytes - bytes);
56042
+ if (remaining > 0) {
56043
+ writeSync(fd, chunk.subarray(0, remaining));
56044
+ bytes += remaining;
56045
+ }
56046
+ writeSync(fd, truncationMarker);
56047
+ truncated = true;
56048
+ return;
56049
+ }
56050
+ writeSync(fd, chunk);
56051
+ bytes += chunk.byteLength;
56052
+ } catch (err) {
56053
+ console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5199\u5165\u5931\u8D25:", err);
56054
+ truncated = true;
56055
+ }
56056
+ },
56057
+ finalize() {
56058
+ if (finalized)
56059
+ return { bytesWritten: bytes, truncated, filePath };
56060
+ finalized = true;
56061
+ if (fd != null) {
56062
+ try {
56063
+ closeSync2(fd);
56064
+ } catch {}
56065
+ fd = null;
56066
+ }
56067
+ return { bytesWritten: bytes, truncated, filePath };
56068
+ }
56069
+ };
55122
56070
  }
55123
56071
  }
56072
+ function makeNoopStreamCaptureHandle() {
56073
+ return {
56074
+ filePath: null,
56075
+ write() {},
56076
+ finalize() {
56077
+ return { bytesWritten: 0, truncated: false, filePath: null };
56078
+ }
56079
+ };
56080
+ }
55124
56081
  var instance = null;
55125
56082
  function initLogger(baseDir, config2) {
55126
56083
  instance = new Logger(baseDir, config2);
@@ -56340,7 +57297,7 @@ var openAPISpec = {
56340
57297
  // src/plugin-loader.ts
56341
57298
  import { mkdtemp, rm, writeFile } from "fs/promises";
56342
57299
  import { tmpdir } from "os";
56343
- import { join as join10, resolve as resolve5 } from "path";
57300
+ import { join as join10, resolve as resolve6 } from "path";
56344
57301
  function isLocalPath(pkg) {
56345
57302
  return pkg.startsWith("./") || pkg.startsWith("../") || pkg.startsWith("/") || /^[A-Za-z]:[\\/]/.test(pkg);
56346
57303
  }
@@ -56396,7 +57353,7 @@ async function importPlugin(pkg, configDir) {
56396
57353
  const localPath = await fetchRemotePlugin(pkg);
56397
57354
  modulePath = `${localPath}?t=${Date.now()}`;
56398
57355
  } else if (isLocalPath(pkg)) {
56399
- const absolutePath = resolve5(configDir, pkg);
57356
+ const absolutePath = resolve6(configDir, pkg);
56400
57357
  modulePath = `${absolutePath}?t=${Date.now()}`;
56401
57358
  } else {
56402
57359
  modulePath = pkg;
@@ -56512,11 +57469,6 @@ class PluginManager {
56512
57469
  }
56513
57470
  }
56514
57471
 
56515
- // src/proxy.ts
56516
- import { appendFile, readFile, unlink } from "fs/promises";
56517
- import { tmpdir as tmpdir2 } from "os";
56518
- import { join as join11 } from "path";
56519
-
56520
57472
  // src/plugin-engine.ts
56521
57473
  async function executeRequestPlugins(plugins, ctx, url2, headers, body) {
56522
57474
  let currentUrl = url2;
@@ -56706,28 +57658,6 @@ function buildLogEvent(logMeta, targetUrl, proxyUrl, tsEnd, overrides) {
56706
57658
  ...overrides
56707
57659
  };
56708
57660
  }
56709
- function createTempStreamCapturePath(requestId) {
56710
- return join11(tmpdir2(), `local-router-stream-${requestId}-${Date.now()}.sse.raw`);
56711
- }
56712
- async function appendTempStreamCapture(filePath, chunk) {
56713
- await appendFile(filePath, chunk);
56714
- }
56715
- async function flushTempCaptureToLogger(tempPath, requestId, dateStr, logger) {
56716
- if (!logger)
56717
- return null;
56718
- try {
56719
- const text2 = await readFile(tempPath, "utf-8").catch((err) => {
56720
- if (err.code === "ENOENT")
56721
- return "";
56722
- throw err;
56723
- });
56724
- return logger.writeStreamFile(requestId, dateStr, text2);
56725
- } finally {
56726
- await unlink(tempPath).catch(() => {
56727
- return;
56728
- });
56729
- }
56730
- }
56731
57661
  async function proxyRequest(c2, options) {
56732
57662
  const { logMeta, plugins, pluginConfigs } = options;
56733
57663
  const logger = getLogger();
@@ -56735,19 +57665,20 @@ async function proxyRequest(c2, options) {
56735
57665
  const hasPlugins = plugins && plugins.length > 0;
56736
57666
  let targetUrl = options.targetUrl;
56737
57667
  let headers = buildUpstreamHeaders(c2.req.raw.headers, options.apiKey, options.authType);
56738
- let bodyStr = options.body;
57668
+ let currentBody = options.body;
57669
+ const pluginCtx = Object.freeze({
57670
+ requestId: logMeta.requestId,
57671
+ provider: logMeta.provider,
57672
+ modelIn: logMeta.modelIn,
57673
+ modelOut: logMeta.modelOut,
57674
+ routeType: logMeta.routeType,
57675
+ isStream: logMeta.isStream
57676
+ });
57677
+ const wantsBodyLog = shouldLog && logger?.bodyPolicy !== "off";
57678
+ const requestBodySnapshot = wantsBodyLog ? JSON.parse(JSON.stringify(options.body)) : undefined;
56739
57679
  const pluginLogOverrides = {};
56740
57680
  if (hasPlugins) {
56741
- const bodyObj = JSON.parse(bodyStr);
56742
- const ctx = {
56743
- requestId: logMeta.requestId,
56744
- provider: logMeta.provider,
56745
- modelIn: logMeta.modelIn,
56746
- modelOut: logMeta.modelOut,
56747
- routeType: logMeta.routeType,
56748
- isStream: logMeta.isStream
56749
- };
56750
- const result = await executeRequestPlugins(plugins, ctx, targetUrl, headers, bodyObj);
57681
+ const result = await executeRequestPlugins(plugins, pluginCtx, targetUrl, headers, currentBody);
56751
57682
  if (pluginConfigs) {
56752
57683
  pluginLogOverrides.plugins_request = pluginConfigs;
56753
57684
  }
@@ -56756,20 +57687,20 @@ async function proxyRequest(c2, options) {
56756
57687
  pluginLogOverrides.request_url_after_plugins = targetUrl;
56757
57688
  }
56758
57689
  headers = result.headers;
56759
- const newBodyStr = JSON.stringify(result.body);
56760
- if (newBodyStr !== bodyStr) {
56761
- bodyStr = newBodyStr;
57690
+ if (result.body !== currentBody) {
57691
+ currentBody = result.body;
56762
57692
  pluginLogOverrides.request_body_after_plugins = result.body;
56763
57693
  }
56764
57694
  }
56765
- const requestBody = shouldLog && logger?.bodyPolicy !== "off" ? JSON.parse(options.body) : undefined;
57695
+ const wireBody = JSON.stringify(currentBody);
57696
+ const requestBody = requestBodySnapshot;
56766
57697
  const proxy = options.proxy?.trim() ? options.proxy.trim() : undefined;
56767
57698
  let upstreamRes;
56768
57699
  try {
56769
57700
  upstreamRes = await fetch(targetUrl, {
56770
57701
  method: c2.req.method,
56771
57702
  headers,
56772
- body: bodyStr,
57703
+ body: wireBody,
56773
57704
  ...proxy ? { proxy } : {},
56774
57705
  decompress: true
56775
57706
  });
@@ -56799,15 +57730,7 @@ async function proxyRequest(c2, options) {
56799
57730
  let sseHeaders = responseHeaders;
56800
57731
  let sseTransform = null;
56801
57732
  if (hasPlugins) {
56802
- const ctx = {
56803
- requestId: logMeta.requestId,
56804
- provider: logMeta.provider,
56805
- modelIn: logMeta.modelIn,
56806
- modelOut: logMeta.modelOut,
56807
- routeType: logMeta.routeType,
56808
- isStream: logMeta.isStream
56809
- };
56810
- const sseResult = await createSSEPluginTransform(plugins, ctx, upstreamRes.status, responseHeaders);
57733
+ const sseResult = await createSSEPluginTransform(plugins, pluginCtx, upstreamRes.status, responseHeaders);
56811
57734
  sseStatus = sseResult.status;
56812
57735
  sseHeaders = sseResult.headers;
56813
57736
  sseTransform = sseResult.transform;
@@ -56816,47 +57739,75 @@ async function proxyRequest(c2, options) {
56816
57739
  }
56817
57740
  }
56818
57741
  if (!shouldLog) {
56819
- const outputBody2 = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
56820
- return new Response(outputBody2, {
57742
+ const outputBody = sseTransform ? upstreamRes.body.pipeThrough(sseTransform) : upstreamRes.body;
57743
+ return new Response(outputBody, {
56821
57744
  status: sseStatus,
56822
57745
  headers: sseHeaders
56823
57746
  });
56824
57747
  }
56825
- const [clientStream, logStream] = upstreamRes.body.tee();
56826
- (async () => {
56827
- const tempPath = createTempStreamCapturePath(logMeta.requestId);
56828
- let streamBytes = 0;
56829
- let streamFile = null;
56830
- try {
56831
- const reader = logStream.getReader();
56832
- while (true) {
56833
- const { done, value } = await reader.read();
56834
- if (done)
56835
- break;
56836
- streamBytes += value.byteLength;
56837
- await appendTempStreamCapture(tempPath, value);
57748
+ const capture = logger?.openStreamCapture(logMeta.requestId, dateStr) ?? null;
57749
+ const tokenUsageCollector = createTokenUsageStreamCollector(`${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
57750
+ let upstreamBytes = 0;
57751
+ let writeEventCalled = false;
57752
+ const finalizeAndWriteEvent = () => {
57753
+ if (writeEventCalled)
57754
+ return;
57755
+ writeEventCalled = true;
57756
+ const captureResult = capture?.finalize() ?? {
57757
+ bytesWritten: 0,
57758
+ truncated: false,
57759
+ filePath: null
57760
+ };
57761
+ const tokenUsage2 = tokenUsageCollector.getUsage();
57762
+ logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
57763
+ upstream_status: sseStatus,
57764
+ content_type_res: contentTypeRes,
57765
+ response_headers: sseHeaders,
57766
+ stream_bytes: upstreamBytes,
57767
+ provider_request_id: providerRequestId,
57768
+ ...captureResult.filePath != null && { stream_file: captureResult.filePath },
57769
+ ...captureResult.bytesWritten > 0 && {
57770
+ stream_file_bytes: captureResult.bytesWritten
57771
+ },
57772
+ ...captureResult.truncated && { stream_file_truncated: true },
57773
+ ...tokenUsage2 && { token_usage: tokenUsage2 },
57774
+ ...requestBody !== undefined && { request_body: requestBody },
57775
+ ...pluginLogOverrides
57776
+ }));
57777
+ };
57778
+ const upstreamReader = upstreamRes.body.getReader();
57779
+ const tappedStream = new ReadableStream({
57780
+ async pull(controller) {
57781
+ try {
57782
+ const { done, value } = await upstreamReader.read();
57783
+ if (done) {
57784
+ controller.close();
57785
+ finalizeAndWriteEvent();
57786
+ return;
57787
+ }
57788
+ upstreamBytes += value.byteLength;
57789
+ tokenUsageCollector.addChunk(value);
57790
+ capture?.write(value);
57791
+ controller.enqueue(value);
57792
+ } catch (err) {
57793
+ finalizeAndWriteEvent();
57794
+ controller.error(err);
57795
+ }
57796
+ },
57797
+ cancel(reason) {
57798
+ try {
57799
+ upstreamReader.cancel(reason).catch(() => {
57800
+ return;
57801
+ });
57802
+ } finally {
57803
+ finalizeAndWriteEvent();
56838
57804
  }
56839
- streamFile = await flushTempCaptureToLogger(tempPath, logMeta.requestId, dateStr, logger);
56840
- } catch (err) {
56841
- await unlink(tempPath).catch(() => {
56842
- return;
56843
- });
56844
- console.error("[logger] \u6D41\u5F0F\u65E5\u5FD7\u5904\u7406\u5931\u8D25:", err);
56845
- } finally {
56846
- logger?.writeEvent(buildLogEvent(logMeta, targetUrl, proxy, Date.now(), {
56847
- upstream_status: sseStatus,
56848
- content_type_res: contentTypeRes,
56849
- response_headers: sseHeaders,
56850
- stream_bytes: streamBytes,
56851
- provider_request_id: providerRequestId,
56852
- ...streamFile != null && { stream_file: streamFile },
56853
- ...requestBody !== undefined && { request_body: requestBody },
56854
- ...pluginLogOverrides
56855
- }));
56856
57805
  }
56857
- })();
56858
- const outputBody = sseTransform ? clientStream.pipeThrough(sseTransform) : clientStream;
56859
- return new Response(outputBody, {
57806
+ });
57807
+ let stream = tappedStream;
57808
+ if (sseTransform)
57809
+ stream = stream.pipeThrough(sseTransform);
57810
+ return new Response(stream, {
56860
57811
  status: sseStatus,
56861
57812
  headers: sseHeaders
56862
57813
  });
@@ -56865,15 +57816,7 @@ async function proxyRequest(c2, options) {
56865
57816
  let responseStatus = upstreamRes.status;
56866
57817
  let finalResponseHeaders = responseHeaders;
56867
57818
  if (hasPlugins) {
56868
- const ctx = {
56869
- requestId: logMeta.requestId,
56870
- provider: logMeta.provider,
56871
- modelIn: logMeta.modelIn,
56872
- modelOut: logMeta.modelOut,
56873
- routeType: logMeta.routeType,
56874
- isStream: logMeta.isStream
56875
- };
56876
- const result = await executeJsonResponsePlugins(plugins, ctx, upstreamRes.status, responseHeaders, responseText);
57819
+ const result = await executeJsonResponsePlugins(plugins, pluginCtx, upstreamRes.status, responseHeaders, responseText);
56877
57820
  if (pluginConfigs) {
56878
57821
  pluginLogOverrides.plugins_response = pluginConfigs;
56879
57822
  }
@@ -56894,12 +57837,14 @@ async function proxyRequest(c2, options) {
56894
57837
  });
56895
57838
  }
56896
57839
  const responseBytes = Buffer.byteLength(responseText, "utf-8");
57840
+ const tokenUsage = extractTokenUsageFromResponseText(responseText, "response_body", `${logMeta.provider} ${logMeta.routeType} ${logMeta.modelIn} ${logMeta.modelOut}`);
56897
57841
  const eventOverrides = {
56898
57842
  upstream_status: upstreamRes.status,
56899
57843
  content_type_res: contentTypeRes,
56900
57844
  response_headers: finalResponseHeaders,
56901
57845
  response_bytes: responseBytes,
56902
57846
  provider_request_id: providerRequestId,
57847
+ ...tokenUsage && { token_usage: tokenUsage },
56903
57848
  ...pluginLogOverrides
56904
57849
  };
56905
57850
  if (requestBody !== undefined) {
@@ -56950,8 +57895,10 @@ function createModelRoutingHandler(options) {
56950
57895
  return c2.json({ error: `provider "${target.provider}" \u672A\u5728\u914D\u7F6E\u4E2D\u5B9A\u4E49` }, 500);
56951
57896
  }
56952
57897
  payload.model = target.model;
56953
- const body = JSON.stringify(payload);
56954
57898
  const targetUrl = buildTargetUrl(provider.base);
57899
+ const contentLengthHeader = c2.req.header("content-length");
57900
+ const parsedContentLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
57901
+ const requestBytes = Number.isInteger(parsedContentLength) && parsedContentLength >= 0 ? parsedContentLength : Buffer.byteLength(JSON.stringify(payload), "utf-8");
56955
57902
  const logMeta = {
56956
57903
  requestId: crypto.randomUUID(),
56957
57904
  tsStart: Date.now(),
@@ -56965,7 +57912,7 @@ function createModelRoutingHandler(options) {
56965
57912
  path: c2.req.path,
56966
57913
  contentTypeReq: c2.req.header("content-type") ?? null,
56967
57914
  userAgent: c2.req.header("user-agent") ?? null,
56968
- requestBytes: Buffer.byteLength(body, "utf-8"),
57915
+ requestBytes,
56969
57916
  requestHeaders: collectHeaders(c2.req.raw.headers)
56970
57917
  };
56971
57918
  const plugins = pluginManager?.getPlugins(target.provider) ?? [];
@@ -56975,7 +57922,7 @@ function createModelRoutingHandler(options) {
56975
57922
  apiKey: provider.apiKey,
56976
57923
  proxy: provider.proxy,
56977
57924
  authType,
56978
- body,
57925
+ body: payload,
56979
57926
  logMeta,
56980
57927
  plugins: plugins.length > 0 ? plugins : undefined,
56981
57928
  pluginConfigs: pluginConfigs.length > 0 ? pluginConfigs.map((lp) => ({
@@ -57183,7 +58130,7 @@ function createAdminApiRoutes(store, pluginManager, registerCleanup) {
57183
58130
  const CRYPTO_SESSION_TTL_MS = 2 * 60 * 1000;
57184
58131
  const CRYPTO_SESSION_MAX = 512;
57185
58132
  const schemaPath = getBundledSchemaPath();
57186
- const schemaJson = JSON.parse(readFileSync5(schemaPath, "utf-8"));
58133
+ const schemaJson = JSON.parse(readFileSync6(schemaPath, "utf-8"));
57187
58134
  const pruneExpiredCryptoSessions = (now2 = Date.now()) => {
57188
58135
  for (const [id, record2] of Array.from(cryptoSessions.entries())) {
57189
58136
  if (now2 - record2.createdAt > CRYPTO_SESSION_TTL_MS) {
@@ -57788,7 +58735,7 @@ async function createApp(store, options) {
57788
58735
  }
57789
58736
  const stopLogStorageTask = startLogStorageBackgroundTask(config2.log);
57790
58737
  options?.registerCleanup?.(stopLogStorageTask);
57791
- const configDir = dirname3(resolve6(store.getPath()));
58738
+ const configDir = dirname3(resolve7(store.getPath()));
57792
58739
  const pluginManager = new PluginManager(configDir);
57793
58740
  const reloadResult = await pluginManager.reloadAll(config2.providers);
57794
58741
  if (!reloadResult.ok) {
@@ -57904,24 +58851,24 @@ async function startServer(options) {
57904
58851
  }
57905
58852
 
57906
58853
  // src/cli/runtime.ts
57907
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync5 } from "fs";
58854
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync4 } from "fs";
57908
58855
  import { homedir as homedir2 } from "os";
57909
- import { join as join12, resolve as resolve7 } from "path";
58856
+ import { join as join11, resolve as resolve8 } from "path";
57910
58857
  function getRuntimeDirs() {
57911
58858
  const override = process.env.LOCAL_ROUTER_RUNTIME_DIR;
57912
- const root2 = override?.trim() ? override.trim() : join12(homedir2(), ".local-router");
58859
+ const root2 = override?.trim() ? override.trim() : join11(homedir2(), ".local-router");
57913
58860
  return {
57914
58861
  root: root2,
57915
- run: join12(root2, "run"),
57916
- logs: join12(root2, "logs")
58862
+ run: join11(root2, "run"),
58863
+ logs: join11(root2, "logs")
57917
58864
  };
57918
58865
  }
57919
58866
  function getRuntimeFiles() {
57920
58867
  const dirs = getRuntimeDirs();
57921
58868
  return {
57922
- pid: join12(dirs.run, "local-router.pid"),
57923
- state: join12(dirs.run, "status.json"),
57924
- daemonLog: join12(dirs.logs, "daemon.log")
58869
+ pid: join11(dirs.run, "local-router.pid"),
58870
+ state: join11(dirs.run, "status.json"),
58871
+ daemonLog: join11(dirs.logs, "daemon.log")
57925
58872
  };
57926
58873
  }
57927
58874
  function ensureRuntimeDirs() {
@@ -57933,17 +58880,17 @@ function ensureRuntimeDirs() {
57933
58880
  function writeRuntimeState(state) {
57934
58881
  ensureRuntimeDirs();
57935
58882
  const files = getRuntimeFiles();
57936
- writeFileSync5(files.pid, `${state.pid}
58883
+ writeFileSync4(files.pid, `${state.pid}
57937
58884
  `, "utf-8");
57938
- writeFileSync5(files.state, JSON.stringify(state, null, 2), "utf-8");
58885
+ writeFileSync4(files.state, JSON.stringify(state, null, 2), "utf-8");
57939
58886
  }
57940
58887
  function readRuntimeState() {
57941
58888
  const files = getRuntimeFiles();
57942
- if (!existsSync8(files.state)) {
58889
+ if (!existsSync9(files.state)) {
57943
58890
  return null;
57944
58891
  }
57945
58892
  try {
57946
- return JSON.parse(readFileSync6(files.state, "utf-8"));
58893
+ return JSON.parse(readFileSync7(files.state, "utf-8"));
57947
58894
  } catch {
57948
58895
  return null;
57949
58896
  }
@@ -57954,7 +58901,7 @@ function clearRuntimeFiles() {
57954
58901
  rmSync(files.state, { force: true });
57955
58902
  }
57956
58903
  function resolveConfigArgPath(pathValue) {
57957
- return resolve7(pathValue);
58904
+ return resolve8(pathValue);
57958
58905
  }
57959
58906
 
57960
58907
  // src/cli/process.ts
@@ -58094,8 +59041,8 @@ async function startDaemon(flags) {
58094
59041
  }
58095
59042
  ensureRuntimeDirs();
58096
59043
  const files = getRuntimeFiles();
58097
- const stdoutFd = openSync2(files.daemonLog, "a");
58098
- const stderrFd = openSync2(files.daemonLog, "a");
59044
+ const stdoutFd = openSync3(files.daemonLog, "a");
59045
+ const stderrFd = openSync3(files.daemonLog, "a");
58099
59046
  const childArgs = [process.argv[1] ?? "src/cli.ts", "__run-server", "--mode", "daemon"];
58100
59047
  if (flags.config) {
58101
59048
  childArgs.push("--config", resolveConfigArgPath(flags.config));
@@ -58116,8 +59063,8 @@ async function startDaemon(flags) {
58116
59063
  stderr: stderrFd,
58117
59064
  detached: true
58118
59065
  });
58119
- closeSync2(stdoutFd);
58120
- closeSync2(stderrFd);
59066
+ closeSync3(stdoutFd);
59067
+ closeSync3(stderrFd);
58121
59068
  child.unref();
58122
59069
  for (let i = 0;i < 24; i += 1) {
58123
59070
  await sleep(250);
@@ -58132,7 +59079,7 @@ async function startDaemon(flags) {
58132
59079
  }
58133
59080
  let tail = "";
58134
59081
  try {
58135
- const content = readFileSync7(files.daemonLog, "utf-8");
59082
+ const content = readFileSync8(files.daemonLog, "utf-8");
58136
59083
  tail = content.split(`
58137
59084
  `).slice(-20).join(`
58138
59085
  `);
@@ -58165,11 +59112,11 @@ async function stopProcess(graceMs = 8000) {
58165
59112
  }
58166
59113
  function readLogDelta(filePath, offset) {
58167
59114
  try {
58168
- const stats = statSync3(filePath);
59115
+ const stats = statSync4(filePath);
58169
59116
  if (stats.size <= offset) {
58170
59117
  return { content: "", nextOffset: offset };
58171
59118
  }
58172
- const full = readFileSync7(filePath, "utf-8");
59119
+ const full = readFileSync8(filePath, "utf-8");
58173
59120
  const content = full.slice(offset);
58174
59121
  return { content, nextOffset: full.length };
58175
59122
  } catch {
@@ -59182,13 +60129,13 @@ init_config();
59182
60129
  init_config_validate();
59183
60130
  init_errors();
59184
60131
  init_output();
59185
- import { existsSync as existsSync10 } from "fs";
60132
+ import { existsSync as existsSync11 } from "fs";
59186
60133
  import { parseArgs as parseArgs4 } from "util";
59187
60134
  init_registry();
59188
60135
  function tryRead(configArg) {
59189
60136
  try {
59190
60137
  const path = resolveConfigPath(configArg);
59191
- if (!existsSync10(path)) {
60138
+ if (!existsSync11(path)) {
59192
60139
  return { path, config: null, error: `\u914D\u7F6E\u6587\u4EF6\u4E0D\u5B58\u5728: ${path}` };
59193
60140
  }
59194
60141
  const config2 = loadConfig(path);
@@ -59623,7 +60570,7 @@ defineCommand({
59623
60570
  });
59624
60571
 
59625
60572
  // src/cli/handlers/introspection.ts
59626
- import { readFileSync as readFileSync9 } from "fs";
60573
+ import { readFileSync as readFileSync10 } from "fs";
59627
60574
  import { fileURLToPath as fileURLToPath2 } from "url";
59628
60575
  import { parseArgs as parseArgs5 } from "util";
59629
60576
 
@@ -59899,7 +60846,7 @@ defineCommand({
59899
60846
  hint: "\u8BF7\u786E\u8BA4 npm \u5305\u5B8C\u6574\u6027\uFF0C\u6216\u5F00\u53D1\u6A21\u5F0F\u4E0B\u4ECE\u6E90\u7801\u8FD0\u884C"
59900
60847
  });
59901
60848
  }
59902
- const text2 = readFileSync9(fileURLToPath2(schemaUrl), "utf-8");
60849
+ const text2 = readFileSync10(fileURLToPath2(schemaUrl), "utf-8");
59903
60850
  const data2 = JSON.parse(text2);
59904
60851
  emitResult(ctx, {
59905
60852
  command: "schema.config",
@@ -60119,7 +61066,7 @@ defineCommand({
60119
61066
 
60120
61067
  // src/cli/handlers/lifecycle.ts
60121
61068
  init_config();
60122
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
61069
+ import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
60123
61070
  import { setTimeout as sleep3 } from "timers/promises";
60124
61071
  import { parseArgs as parseArgs6 } from "util";
60125
61072
  init_errors();
@@ -60424,10 +61371,10 @@ defineCommand({
60424
61371
  })
60425
61372
  });
60426
61373
  function readLastLines(filePath, lines) {
60427
- if (!existsSync11(filePath)) {
61374
+ if (!existsSync12(filePath)) {
60428
61375
  return { content: "", offset: 0 };
60429
61376
  }
60430
- const full = readFileSync10(filePath, "utf-8");
61377
+ const full = readFileSync11(filePath, "utf-8");
60431
61378
  const rendered = full.split(`
60432
61379
  `).slice(-lines).join(`
60433
61380
  `);
@@ -60458,7 +61405,7 @@ defineCommand({
60458
61405
  const linesRaw = parsed.values.lines ?? "100";
60459
61406
  const lines = Number.parseInt(linesRaw, 10);
60460
61407
  const initial = readLastLines(files.daemonLog, Number.isFinite(lines) ? lines : 100);
60461
- if (!existsSync11(files.daemonLog)) {
61408
+ if (!existsSync12(files.daemonLog)) {
60462
61409
  emitResult(ctx, {
60463
61410
  command: "logs.daemon",
60464
61411
  data: { exists: false, path: files.daemonLog, lines: [] },