llm-usage-metrics 0.7.0 → 0.7.2

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/index.js CHANGED
@@ -106,9 +106,6 @@ function getParsingRuntimeConfig(env = process.env) {
106
106
  };
107
107
  }
108
108
 
109
- // src/update/update-notifier.ts
110
- import { spawn as spawn2 } from "child_process";
111
-
112
109
  // src/update/update-cache-repository.ts
113
110
  import { mkdir, readFile, writeFile } from "fs/promises";
114
111
  import path2 from "path";
@@ -529,7 +526,6 @@ async function runInteractiveInstallAndRestart(options) {
529
526
 
530
527
  // src/update/update-notifier.ts
531
528
  var UPDATE_CHECK_SKIP_ENV_VAR = "LLM_USAGE_SKIP_UPDATE_CHECK";
532
- var UPDATE_CHECK_REFRESH_ENV_VAR = "LLM_USAGE_REFRESH_UPDATE_CHECK";
533
529
  function isTruthyEnvFlag(value) {
534
530
  if (value === void 0) {
535
531
  return false;
@@ -588,32 +584,6 @@ function toResolveLatestVersionOptions(options, env) {
588
584
  now: options.now
589
585
  };
590
586
  }
591
- function runDetachedCommandWithSpawn(command, args, options = {}) {
592
- const child = spawn2(command, args, {
593
- env: options.env,
594
- stdio: options.stdio ?? "ignore",
595
- detached: true
596
- });
597
- child.on("error", () => void 0);
598
- child.unref();
599
- }
600
- function scheduleBackgroundUpdateRefresh(options, env, argv) {
601
- const spawnDetachedCommand = options.spawnDetachedCommand ?? runDetachedCommandWithSpawn;
602
- spawnDetachedCommand(options.execPath ?? process.execPath, argv.slice(1), {
603
- env: {
604
- ...env,
605
- [UPDATE_CHECK_REFRESH_ENV_VAR]: "1"
606
- },
607
- stdio: "ignore"
608
- });
609
- }
610
- async function refreshUpdateCheckCache(options) {
611
- try {
612
- const env = options.env ?? process.env;
613
- await resolveLatestVersion(toResolveLatestVersionOptions(options, env));
614
- } catch {
615
- }
616
- }
617
587
  async function checkForUpdatesAndMaybeRestart(options) {
618
588
  const env = options.env ?? process.env;
619
589
  const argv = options.argv ?? process.argv;
@@ -630,19 +600,7 @@ async function checkForUpdatesAndMaybeRestart(options) {
630
600
  return { continueExecution: true };
631
601
  }
632
602
  try {
633
- const resolveOptions = toResolveLatestVersionOptions(options, env);
634
- const cacheFilePath = resolveOptions.cacheFilePath ?? getDefaultUpdateCheckCachePath();
635
- const cachePayload = await readUpdateCheckCachePayload(cacheFilePath);
636
- const cacheTtlMs = resolveOptions.cacheTtlMs ?? DEFAULT_UPDATE_CHECK_CACHE_TTL_MS;
637
- const now = resolveOptions.now ?? Date.now;
638
- if (!cachePayload || !isCacheFresh(cachePayload, cacheTtlMs, now)) {
639
- try {
640
- scheduleBackgroundUpdateRefresh(options, env, argv);
641
- } catch {
642
- }
643
- return { continueExecution: true };
644
- }
645
- const latestVersion = cachePayload.latestVersion;
603
+ const latestVersion = await resolveLatestVersion(toResolveLatestVersionOptions(options, env));
646
604
  if (!latestVersion || !shouldOfferUpdate(options.currentVersion, latestVersion)) {
647
605
  return { continueExecution: true };
648
606
  }
@@ -2659,7 +2617,7 @@ function aggregateEfficiency(options) {
2659
2617
  }
2660
2618
 
2661
2619
  // src/efficiency/git-outcome-collector.ts
2662
- import { spawn as spawn3 } from "child_process";
2620
+ import { spawn as spawn2 } from "child_process";
2663
2621
  import { createInterface as createInterface2 } from "readline";
2664
2622
  import path3 from "path";
2665
2623
  import { stat } from "fs/promises";
@@ -2858,7 +2816,7 @@ function parseGitLogShortstatLines(lines, authorEmail) {
2858
2816
  }
2859
2817
  async function runGitCommand(repoDir, args) {
2860
2818
  return await new Promise((resolve, reject) => {
2861
- const child = spawn3("git", args, {
2819
+ const child = spawn2("git", args, {
2862
2820
  cwd: repoDir,
2863
2821
  env: {
2864
2822
  ...process.env,
@@ -3763,13 +3721,16 @@ function parseUsage(usage) {
3763
3721
  totalTokens
3764
3722
  };
3765
3723
  }
3766
- function createDedupKey(filePath, line, message) {
3724
+ function createDedupKey(filePath, line, message, timestamp, model) {
3767
3725
  const messageId = asTrimmedText(message.id);
3768
3726
  if (messageId) {
3769
3727
  return `${filePath}\0${messageId}`;
3770
3728
  }
3771
3729
  const uuid = asTrimmedText(line.uuid);
3772
- return uuid ? `${filePath}\0${uuid}` : void 0;
3730
+ if (uuid) {
3731
+ return `${filePath}\0${uuid}`;
3732
+ }
3733
+ return `${filePath}\0${timestamp}\0${model ?? ""}`;
3773
3734
  }
3774
3735
  function comparePendingEvents(left, right) {
3775
3736
  if (left.timestamp !== right.timestamp) {
@@ -3842,12 +3803,7 @@ var ClaudeSourceAdapter = class {
3842
3803
  incrementSkippedReason(skippedRowReasons, "invalid_timestamp");
3843
3804
  continue;
3844
3805
  }
3845
- const dedupKey = createDedupKey(filePath, line, message);
3846
- if (!dedupKey) {
3847
- skippedRows++;
3848
- incrementSkippedReason(skippedRowReasons, "missing_message_id");
3849
- continue;
3850
- }
3806
+ const dedupKey = createDedupKey(filePath, line, message, timestamp, model);
3851
3807
  const sessionId = asTrimmedText(line.sessionId) ?? getFallbackSessionId(filePath);
3852
3808
  const repoRoot = asTrimmedText(line.cwd);
3853
3809
  const provider = resolveProvider(message, model);
@@ -5050,17 +5006,257 @@ var OpenCodeSourceAdapter = class {
5050
5006
  }
5051
5007
  };
5052
5008
 
5053
- // src/sources/pi/pi-source-adapter.ts
5009
+ // src/sources/openclaw/openclaw-source-adapter.ts
5054
5010
  import os7 from "os";
5055
5011
  import path11 from "path";
5056
- var defaultSessionsDir3 = path11.join(os7.homedir(), ".pi", "agent", "sessions");
5012
+ var defaultAgentsDir = path11.join(os7.homedir(), ".openclaw", "agents");
5013
+ var SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
5014
+ var MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
5015
+ var MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
5016
+ var USAGE_LINE_PATTERN = /"usage"\s*:/u;
5017
+ var MODEL_LINE_PATTERN = /"model"\s*:/u;
5018
+ var PROVIDER_LINE_PATTERN = /"provider"\s*:/u;
5019
+ function shouldParseOpenClawJsonlLine(lineText) {
5020
+ return SESSION_LINE_PATTERN.test(lineText) || MESSAGE_LINE_PATTERN.test(lineText) || MODEL_CHANGE_LINE_PATTERN.test(lineText) || USAGE_LINE_PATTERN.test(lineText) || MODEL_LINE_PATTERN.test(lineText) || PROVIDER_LINE_PATTERN.test(lineText);
5021
+ }
5022
+ function getFallbackSessionId3(filePath) {
5023
+ return path11.basename(filePath, ".jsonl");
5024
+ }
5025
+ function resolveRepoRootFromRecord(record) {
5026
+ if (!record) {
5027
+ return void 0;
5028
+ }
5029
+ const pathRecord = asRecord(record.path);
5030
+ return asTrimmedText(record.cwd) ?? asTrimmedText(pathRecord?.root) ?? asTrimmedText(pathRecord?.cwd) ?? asTrimmedText(record.repo_root) ?? asTrimmedText(record.repoRoot) ?? asTrimmedText(record.project_root) ?? asTrimmedText(record.projectRoot) ?? asTrimmedText(record.workspace);
5031
+ }
5032
+ function firstText(...values) {
5033
+ for (const value of values) {
5034
+ const normalized = asTrimmedText(value);
5035
+ if (normalized) {
5036
+ return normalized;
5037
+ }
5038
+ }
5039
+ return void 0;
5040
+ }
5041
+ function isAssistantMessage(line, message) {
5042
+ return firstText(line.role, message.role)?.toLowerCase() === "assistant";
5043
+ }
5044
+ function isDeliveryMirror(provider, model, message) {
5045
+ const messageKind = firstText(message.kind, message.source, message.provenance);
5046
+ return provider?.toLowerCase() === "openclaw" || model?.toLowerCase() === "delivery-mirror" || messageKind?.toLowerCase() === "delivery-mirror";
5047
+ }
5048
+ function toFiniteNumber2(value) {
5049
+ if (value === null || value === void 0) {
5050
+ return void 0;
5051
+ }
5052
+ if (typeof value === "string" && value.trim().length === 0) {
5053
+ return void 0;
5054
+ }
5055
+ const parsed = typeof value === "number" ? value : Number(value);
5056
+ return Number.isFinite(parsed) ? parsed : void 0;
5057
+ }
5058
+ function extractCostUsd(usage) {
5059
+ const cost = asRecord(usage.cost);
5060
+ return toNumberLike(usage.costUsd) ?? toNumberLike(usage.cost_usd) ?? toNumberLike(usage.turnUsd) ?? toNumberLike(usage.turn_usd) ?? toNumberLike(usage.usd) ?? toNumberLike(usage.cost) ?? toNumberLike(cost?.total) ?? toNumberLike(cost?.usd) ?? toNumberLike(cost?.turnUsd) ?? toNumberLike(cost?.turn_usd);
5061
+ }
5062
+ function extractUsageFromRecord(usage) {
5063
+ const extracted = {
5064
+ inputTokens: toNumberLike(usage.input) ?? toNumberLike(usage.inputTokens) ?? toNumberLike(usage.input_tokens) ?? toNumberLike(usage.prompt_tokens),
5065
+ outputTokens: toNumberLike(usage.output) ?? toNumberLike(usage.outputTokens) ?? toNumberLike(usage.output_tokens) ?? toNumberLike(usage.completion_tokens),
5066
+ reasoningTokens: toNumberLike(usage.reasoning) ?? toNumberLike(usage.reasoningTokens) ?? toNumberLike(usage.reasoning_tokens) ?? toNumberLike(usage.reasoningOutput) ?? toNumberLike(usage.reasoning_output_tokens),
5067
+ cacheReadTokens: toNumberLike(usage.cacheRead) ?? toNumberLike(usage.cache_read) ?? toNumberLike(usage.cacheReadTokens) ?? toNumberLike(usage.cache_read_tokens) ?? toNumberLike(usage.cached) ?? toNumberLike(usage.cached_input_tokens),
5068
+ cacheWriteTokens: toNumberLike(usage.cacheWrite) ?? toNumberLike(usage.cache_write) ?? toNumberLike(usage.cacheWriteTokens) ?? toNumberLike(usage.cache_write_tokens),
5069
+ totalTokens: toNumberLike(usage.total) ?? toNumberLike(usage.totalTokens) ?? toNumberLike(usage.total_tokens),
5070
+ costUsd: extractCostUsd(usage)
5071
+ };
5072
+ const usageCandidates = [
5073
+ extracted.inputTokens,
5074
+ extracted.outputTokens,
5075
+ extracted.reasoningTokens,
5076
+ extracted.cacheReadTokens,
5077
+ extracted.cacheWriteTokens,
5078
+ extracted.totalTokens
5079
+ ];
5080
+ const hasPositiveUsageSignal = usageCandidates.some((value) => {
5081
+ const parsed = toFiniteNumber2(value);
5082
+ return parsed !== void 0 && parsed > 0;
5083
+ });
5084
+ const explicitCost = toFiniteNumber2(extracted.costUsd);
5085
+ const hasPositiveCostSignal = explicitCost !== void 0 && explicitCost > 0;
5086
+ return hasPositiveUsageSignal || hasPositiveCostSignal ? extracted : void 0;
5087
+ }
5088
+ function mergeUsageExtracts(primary, fallback) {
5089
+ if (!primary) {
5090
+ return fallback;
5091
+ }
5092
+ if (!fallback) {
5093
+ return primary;
5094
+ }
5095
+ return {
5096
+ inputTokens: primary.inputTokens ?? fallback.inputTokens,
5097
+ outputTokens: primary.outputTokens ?? fallback.outputTokens,
5098
+ reasoningTokens: primary.reasoningTokens ?? fallback.reasoningTokens,
5099
+ cacheReadTokens: primary.cacheReadTokens ?? fallback.cacheReadTokens,
5100
+ cacheWriteTokens: primary.cacheWriteTokens ?? fallback.cacheWriteTokens,
5101
+ totalTokens: primary.totalTokens ?? fallback.totalTokens,
5102
+ costUsd: primary.costUsd ?? fallback.costUsd
5103
+ };
5104
+ }
5105
+ function extractUsage(line, message) {
5106
+ const lineUsage = asRecord(line.usage);
5107
+ const messageUsage = asRecord(message.usage);
5108
+ const extractedLineUsage = lineUsage ? extractUsageFromRecord(lineUsage) : void 0;
5109
+ const extractedMessageUsage = messageUsage ? extractUsageFromRecord(messageUsage) : void 0;
5110
+ return mergeUsageExtracts(extractedLineUsage, extractedMessageUsage);
5111
+ }
5112
+ function resolveTimestamp2(line, message, state, fallbackTimestamp) {
5113
+ const candidates = [line.timestamp, message.timestamp, fallbackTimestamp, state.sessionTimestamp];
5114
+ for (const candidate of candidates) {
5115
+ const normalizedTimestamp = normalizeTimestampCandidate(candidate);
5116
+ if (normalizedTimestamp) {
5117
+ return normalizedTimestamp;
5118
+ }
5119
+ }
5120
+ return void 0;
5121
+ }
5122
+ function updateRuntimeStateFromRecord(state, record, nested) {
5123
+ state.provider = firstText(
5124
+ record.provider,
5125
+ record.modelProvider,
5126
+ record.model_provider,
5127
+ nested?.provider,
5128
+ nested?.modelProvider,
5129
+ nested?.model_provider
5130
+ ) ?? state.provider;
5131
+ state.model = firstText(
5132
+ record.model,
5133
+ record.modelId,
5134
+ record.model_id,
5135
+ nested?.model,
5136
+ nested?.modelId,
5137
+ nested?.model_id
5138
+ ) ?? state.model;
5139
+ state.repoRoot = resolveRepoRootFromRecord(record) ?? resolveRepoRootFromRecord(nested) ?? state.repoRoot;
5140
+ }
5141
+ var OpenClawSourceAdapter = class {
5142
+ id = "openclaw";
5143
+ agentsDir;
5144
+ requireAgentsDir;
5145
+ constructor(options = {}) {
5146
+ this.agentsDir = options.agentsDir ?? defaultAgentsDir;
5147
+ this.requireAgentsDir = options.requireAgentsDir ?? false;
5148
+ }
5149
+ async discoverFiles() {
5150
+ if (isBlankText(this.agentsDir)) {
5151
+ throw new Error("OpenClaw agents directory must be a non-empty path");
5152
+ }
5153
+ const normalizedAgentsDir = this.agentsDir.trim();
5154
+ if (this.requireAgentsDir) {
5155
+ const agentsDirStats = await pathStat(normalizedAgentsDir);
5156
+ if (!agentsDirStats) {
5157
+ throw new Error(
5158
+ `OpenClaw agents directory is missing or unreadable: ${normalizedAgentsDir}`
5159
+ );
5160
+ }
5161
+ if (!agentsDirStats.isDirectory()) {
5162
+ throw new Error(`OpenClaw agents directory is not a directory: ${normalizedAgentsDir}`);
5163
+ }
5164
+ }
5165
+ return discoverJsonlFiles(normalizedAgentsDir);
5166
+ }
5167
+ async parseFile(filePath) {
5168
+ return (await this.parseFileWithDiagnostics(filePath)).events;
5169
+ }
5170
+ async parseFileWithDiagnostics(filePath) {
5171
+ const events = [];
5172
+ let skippedRows = 0;
5173
+ const skippedRowReasons = /* @__PURE__ */ new Map();
5174
+ const fileStats = await pathStat(filePath);
5175
+ const fallbackTimestamp = fileStats?.mtime.toISOString();
5176
+ const state = {
5177
+ sessionId: getFallbackSessionId3(filePath)
5178
+ };
5179
+ for await (const line of readJsonlObjects(filePath, {
5180
+ shouldParseLine: shouldParseOpenClawJsonlLine
5181
+ })) {
5182
+ const message = asRecord(line.message) ?? line;
5183
+ if (line.type === "session") {
5184
+ state.sessionId = firstText(line.id, line.sessionId, line.session_id) ?? state.sessionId;
5185
+ state.sessionTimestamp = normalizeTimestampCandidate(line.timestamp) ?? state.sessionTimestamp;
5186
+ updateRuntimeStateFromRecord(state, line, message);
5187
+ continue;
5188
+ }
5189
+ if (line.type === "model_change") {
5190
+ updateRuntimeStateFromRecord(state, line, message);
5191
+ continue;
5192
+ }
5193
+ if (line.type !== "message" || !isAssistantMessage(line, message)) {
5194
+ updateRuntimeStateFromRecord(state, line, message);
5195
+ continue;
5196
+ }
5197
+ const rowProvider = firstText(
5198
+ line.provider,
5199
+ message.provider,
5200
+ line.modelProvider,
5201
+ message.modelProvider,
5202
+ line.model_provider,
5203
+ message.model_provider
5204
+ );
5205
+ const rowModel = firstText(
5206
+ line.model,
5207
+ message.model,
5208
+ line.modelId,
5209
+ message.modelId,
5210
+ line.model_id,
5211
+ message.model_id
5212
+ );
5213
+ if (isDeliveryMirror(rowProvider, rowModel, message)) {
5214
+ continue;
5215
+ }
5216
+ updateRuntimeStateFromRecord(state, line, message);
5217
+ const provider = rowProvider ?? state.provider;
5218
+ const model = rowModel ?? state.model;
5219
+ const usage = extractUsage(line, message);
5220
+ if (!usage) {
5221
+ continue;
5222
+ }
5223
+ const timestamp = resolveTimestamp2(line, message, state, fallbackTimestamp);
5224
+ if (!timestamp) {
5225
+ continue;
5226
+ }
5227
+ try {
5228
+ events.push(
5229
+ createUsageEvent({
5230
+ source: this.id,
5231
+ sessionId: state.sessionId,
5232
+ timestamp,
5233
+ repoRoot: state.repoRoot,
5234
+ provider,
5235
+ model,
5236
+ ...usage
5237
+ })
5238
+ );
5239
+ } catch {
5240
+ skippedRows += 1;
5241
+ incrementSkippedReason(skippedRowReasons, "invalid_usage_event");
5242
+ continue;
5243
+ }
5244
+ }
5245
+ return toParseDiagnostics(events, skippedRows, skippedRowReasons);
5246
+ }
5247
+ };
5248
+
5249
+ // src/sources/pi/pi-source-adapter.ts
5250
+ import os8 from "os";
5251
+ import path12 from "path";
5252
+ var defaultSessionsDir3 = path12.join(os8.homedir(), ".pi", "agent", "sessions");
5057
5253
  var PI_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
5058
5254
  var PI_SESSION_LINE_PATTERN = /"type"\s*:\s*"session"/u;
5059
5255
  var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
5060
5256
  function shouldParsePiJsonlLine(lineText) {
5061
5257
  return PI_MESSAGE_LINE_PATTERN.test(lineText) || PI_SESSION_LINE_PATTERN.test(lineText) || PI_MODEL_CHANGE_LINE_PATTERN.test(lineText);
5062
5258
  }
5063
- function resolveTimestamp2(line, message, state) {
5259
+ function resolveTimestamp3(line, message, state) {
5064
5260
  const candidates = [line.timestamp, message?.timestamp, state.sessionTimestamp];
5065
5261
  for (const candidate of candidates) {
5066
5262
  const normalizedTimestamp = normalizeTimestampCandidate(candidate);
@@ -5070,7 +5266,7 @@ function resolveTimestamp2(line, message, state) {
5070
5266
  }
5071
5267
  return void 0;
5072
5268
  }
5073
- function extractUsageFromRecord(usage) {
5269
+ function extractUsageFromRecord2(usage) {
5074
5270
  const cost = asRecord(usage.cost);
5075
5271
  const extracted = {
5076
5272
  inputTokens: toNumberLike(usage.input),
@@ -5083,7 +5279,7 @@ function extractUsageFromRecord(usage) {
5083
5279
  totalTokens: toNumberLike(usage.totalTokens),
5084
5280
  costUsd: toNumberLike(cost?.total)
5085
5281
  };
5086
- const toFiniteNumber2 = (value) => {
5282
+ const toFiniteNumber3 = (value) => {
5087
5283
  if (value === null || value === void 0) {
5088
5284
  return void 0;
5089
5285
  }
@@ -5105,18 +5301,18 @@ function extractUsageFromRecord(usage) {
5105
5301
  extracted.totalTokens
5106
5302
  ];
5107
5303
  const hasPositiveUsageSignal = usageCandidates.some((value) => {
5108
- const parsed = toFiniteNumber2(value);
5304
+ const parsed = toFiniteNumber3(value);
5109
5305
  return parsed !== void 0 && parsed > 0;
5110
5306
  });
5111
- const explicitCost = toFiniteNumber2(extracted.costUsd);
5307
+ const explicitCost = toFiniteNumber3(extracted.costUsd);
5112
5308
  const hasPositiveCostSignal = explicitCost !== void 0 && explicitCost > 0;
5113
5309
  return hasPositiveUsageSignal || hasPositiveCostSignal ? extracted : void 0;
5114
5310
  }
5115
- function extractUsage(line, message) {
5311
+ function extractUsage2(line, message) {
5116
5312
  const lineUsage = asRecord(line.usage);
5117
5313
  const messageUsage = asRecord(message?.usage);
5118
5314
  if (lineUsage) {
5119
- const extractedLineUsage = extractUsageFromRecord(lineUsage);
5315
+ const extractedLineUsage = extractUsageFromRecord2(lineUsage);
5120
5316
  if (extractedLineUsage) {
5121
5317
  return extractedLineUsage;
5122
5318
  }
@@ -5124,12 +5320,12 @@ function extractUsage(line, message) {
5124
5320
  if (!messageUsage) {
5125
5321
  return void 0;
5126
5322
  }
5127
- return extractUsageFromRecord(messageUsage);
5323
+ return extractUsageFromRecord2(messageUsage);
5128
5324
  }
5129
- function getFallbackSessionId3(filePath) {
5130
- return path11.basename(filePath, ".jsonl");
5325
+ function getFallbackSessionId4(filePath) {
5326
+ return path12.basename(filePath, ".jsonl");
5131
5327
  }
5132
- function resolveRepoRootFromRecord(record) {
5328
+ function resolveRepoRootFromRecord2(record) {
5133
5329
  if (!record) {
5134
5330
  return void 0;
5135
5331
  }
@@ -5159,37 +5355,37 @@ var PiSourceAdapter = class {
5159
5355
  }
5160
5356
  async parseFile(filePath) {
5161
5357
  const events = [];
5162
- const state = { sessionId: getFallbackSessionId3(filePath) };
5358
+ const state = { sessionId: getFallbackSessionId4(filePath) };
5163
5359
  for await (const line of readJsonlObjects(filePath, {
5164
5360
  shouldParseLine: shouldParsePiJsonlLine
5165
5361
  })) {
5166
5362
  if (line.type === "session") {
5167
5363
  state.sessionId = asTrimmedText(line.id) ?? state.sessionId;
5168
5364
  state.sessionTimestamp = asTrimmedText(line.timestamp) ?? state.sessionTimestamp;
5169
- state.repoRoot = resolveRepoRootFromRecord(line) ?? state.repoRoot;
5365
+ state.repoRoot = resolveRepoRootFromRecord2(line) ?? state.repoRoot;
5170
5366
  continue;
5171
5367
  }
5172
5368
  if (line.type === "model_change") {
5173
5369
  state.provider = asTrimmedText(line.provider) ?? state.provider;
5174
5370
  state.model = asTrimmedText(line.modelId) ?? asTrimmedText(line.model) ?? state.model;
5175
- state.repoRoot = resolveRepoRootFromRecord(line) ?? state.repoRoot;
5371
+ state.repoRoot = resolveRepoRootFromRecord2(line) ?? state.repoRoot;
5176
5372
  continue;
5177
5373
  }
5178
5374
  if (line.type !== "message") {
5179
5375
  continue;
5180
5376
  }
5181
5377
  const message = asRecord(line.message);
5182
- const usage = extractUsage(line, message);
5378
+ const usage = extractUsage2(line, message);
5183
5379
  if (!usage) {
5184
5380
  continue;
5185
5381
  }
5186
5382
  const provider = asTrimmedText(line.provider) ?? asTrimmedText(message?.provider) ?? state.provider;
5187
- const timestamp = resolveTimestamp2(line, message, state);
5383
+ const timestamp = resolveTimestamp3(line, message, state);
5188
5384
  if (!timestamp || !state.sessionId) {
5189
5385
  continue;
5190
5386
  }
5191
5387
  const model = asTrimmedText(line.model) ?? asTrimmedText(line.modelId) ?? asTrimmedText(message?.model) ?? state.model;
5192
- const repoRoot = resolveRepoRootFromRecord(line) ?? resolveRepoRootFromRecord(message) ?? state.repoRoot;
5388
+ const repoRoot = resolveRepoRootFromRecord2(line) ?? resolveRepoRootFromRecord2(message) ?? state.repoRoot;
5193
5389
  try {
5194
5390
  events.push(
5195
5391
  createUsageEvent({
@@ -5299,6 +5495,21 @@ var sourceRegistrations = [
5299
5495
  dbPath: options.opencodeDb
5300
5496
  })
5301
5497
  },
5498
+ {
5499
+ id: "openclaw",
5500
+ sourceDirOverride: { kind: "directory" },
5501
+ create: (_options, sourceDirectoryOverrides) => {
5502
+ const directoryConfig = resolveDirectoryConfig(
5503
+ "openclaw",
5504
+ void 0,
5505
+ sourceDirectoryOverrides
5506
+ );
5507
+ return new OpenClawSourceAdapter({
5508
+ agentsDir: directoryConfig.path,
5509
+ requireAgentsDir: directoryConfig.requireExistingPath
5510
+ });
5511
+ }
5512
+ },
5302
5513
  {
5303
5514
  id: "claude",
5304
5515
  sourceDirOverride: { kind: "directory" },
@@ -5636,7 +5847,7 @@ function normalizeSkippedRowReasons(value) {
5636
5847
 
5637
5848
  // src/cli/parse-file-cache.ts
5638
5849
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
5639
- import path12 from "path";
5850
+ import path13 from "path";
5640
5851
  var PARSE_FILE_CACHE_VERSION = 5;
5641
5852
  var CACHE_KEY_SEPARATOR = "\0";
5642
5853
  function createCacheKey(source, filePath) {
@@ -5863,7 +6074,7 @@ function normalizeCacheEntry(value) {
5863
6074
  };
5864
6075
  }
5865
6076
  function getDefaultParseFileCachePath() {
5866
- return path12.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
6077
+ return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
5867
6078
  }
5868
6079
  function normalizeCacheShardSource(source) {
5869
6080
  const normalizedSource = normalizeCacheSource(source);
@@ -5873,12 +6084,12 @@ function normalizeCacheShardSource(source) {
5873
6084
  return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
5874
6085
  }
5875
6086
  function getSourceShardedParseFileCachePath(cacheFilePath, source) {
5876
- const parsedPath = path12.parse(cacheFilePath);
6087
+ const parsedPath = path13.parse(cacheFilePath);
5877
6088
  const sourceShard = normalizeCacheShardSource(source);
5878
6089
  if (parsedPath.ext.length > 0) {
5879
- return path12.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
6090
+ return path13.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
5880
6091
  }
5881
- return path12.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
6092
+ return path13.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
5882
6093
  }
5883
6094
  var ParseFileCache = class _ParseFileCache {
5884
6095
  constructor(cacheFilePath, limits, now) {
@@ -5975,7 +6186,7 @@ var ParseFileCache = class _ParseFileCache {
5975
6186
  keptEntries.length = bestCount;
5976
6187
  payloadText = bestPayloadText;
5977
6188
  }
5978
- await mkdir2(path12.dirname(this.cacheFilePath), { recursive: true });
6189
+ await mkdir2(path13.dirname(this.cacheFilePath), { recursive: true });
5979
6190
  const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
5980
6191
  try {
5981
6192
  await writeFile2(temporaryPath, payloadText, "utf8");
@@ -6462,9 +6673,92 @@ function applyPricingToEvents(events, pricingSource) {
6462
6673
  });
6463
6674
  }
6464
6675
 
6676
+ // src/pricing/pricing-override-source.ts
6677
+ import { readFile as readFile5 } from "fs/promises";
6678
+ function toFiniteUsdRate(value) {
6679
+ if (value === null || value === void 0) {
6680
+ return void 0;
6681
+ }
6682
+ if (typeof value === "string" && value.trim() === "") {
6683
+ return void 0;
6684
+ }
6685
+ const parsed = typeof value === "number" ? value : Number(value);
6686
+ return Number.isFinite(parsed) ? parsed : void 0;
6687
+ }
6688
+ function normalizeReasoningBilling(value) {
6689
+ if (value === "included-in-output" || value === "separate") {
6690
+ return value;
6691
+ }
6692
+ return void 0;
6693
+ }
6694
+ function normalizePricingOverride(raw) {
6695
+ const inputPer1MUsd = toFiniteUsdRate(toNumberLike(raw.inputPer1MUsd));
6696
+ const outputPer1MUsd = toFiniteUsdRate(toNumberLike(raw.outputPer1MUsd));
6697
+ if (inputPer1MUsd === void 0 || outputPer1MUsd === void 0) {
6698
+ return void 0;
6699
+ }
6700
+ const cacheReadPer1MUsd = toFiniteUsdRate(toNumberLike(raw.cacheReadPer1MUsd));
6701
+ const cacheWritePer1MUsd = toFiniteUsdRate(toNumberLike(raw.cacheWritePer1MUsd));
6702
+ const reasoningPer1MUsd = toFiniteUsdRate(toNumberLike(raw.reasoningPer1MUsd));
6703
+ const reasoningBilling = normalizeReasoningBilling(raw.reasoningBilling);
6704
+ return {
6705
+ inputPer1MUsd,
6706
+ outputPer1MUsd,
6707
+ ...cacheReadPer1MUsd !== void 0 ? { cacheReadPer1MUsd } : {},
6708
+ ...cacheWritePer1MUsd !== void 0 ? { cacheWritePer1MUsd } : {},
6709
+ ...reasoningPer1MUsd !== void 0 ? { reasoningPer1MUsd } : {},
6710
+ ...reasoningBilling !== void 0 ? { reasoningBilling } : {}
6711
+ };
6712
+ }
6713
+ function normalizeOverrideFile(payload) {
6714
+ const root = asRecord(payload);
6715
+ const overrides = /* @__PURE__ */ new Map();
6716
+ const modelsRecord = asRecord(root?.models);
6717
+ if (!modelsRecord) {
6718
+ return overrides;
6719
+ }
6720
+ for (const [modelName, rawPricing] of Object.entries(modelsRecord)) {
6721
+ const normalizedModelName = asTrimmedText(modelName)?.toLowerCase();
6722
+ if (!normalizedModelName) {
6723
+ continue;
6724
+ }
6725
+ const pricing = normalizePricingOverride(asRecord(rawPricing) ?? {});
6726
+ if (pricing) {
6727
+ overrides.set(normalizedModelName, pricing);
6728
+ }
6729
+ }
6730
+ return overrides;
6731
+ }
6732
+ async function loadPricingOverrides(filePath) {
6733
+ const fileContents = await readFile5(filePath, "utf8");
6734
+ const parsed = JSON.parse(fileContents);
6735
+ return normalizeOverrideFile(parsed);
6736
+ }
6737
+ var PricingOverrideSource = class {
6738
+ overrides;
6739
+ delegate;
6740
+ constructor(overrides, delegate) {
6741
+ this.overrides = overrides;
6742
+ this.delegate = delegate;
6743
+ }
6744
+ resolveModelAlias(model) {
6745
+ if (this.overrides.has(model.toLowerCase())) {
6746
+ return model;
6747
+ }
6748
+ return this.delegate.resolveModelAlias(model);
6749
+ }
6750
+ getPricing(model) {
6751
+ const override = this.overrides.get(model.toLowerCase());
6752
+ if (override) {
6753
+ return override;
6754
+ }
6755
+ return this.delegate.getPricing(model);
6756
+ }
6757
+ };
6758
+
6465
6759
  // src/pricing/litellm-pricing-fetcher.ts
6466
- import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
6467
- import path13 from "path";
6760
+ import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
6761
+ import path14 from "path";
6468
6762
 
6469
6763
  // src/pricing/litellm-model-map.json
6470
6764
  var litellm_model_map_default = {
@@ -6648,7 +6942,7 @@ function normalizeLitellmPricingPayload(payload) {
6648
6942
  return normalizedPricing;
6649
6943
  }
6650
6944
  function getDefaultLiteLLMPricingCachePath() {
6651
- return path13.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6945
+ return path14.join(getUserCacheRootDir(), "llm-usage-metrics", "litellm-pricing-cache.json");
6652
6946
  }
6653
6947
  function stripProviderPrefix(model) {
6654
6948
  const slashIndex = model.lastIndexOf("/");
@@ -7007,7 +7301,7 @@ var LiteLLMPricingFetcher = class {
7007
7301
  async readCachePayload() {
7008
7302
  let content;
7009
7303
  try {
7010
- content = await readFile5(this.cacheFilePath, "utf8");
7304
+ content = await readFile6(this.cacheFilePath, "utf8");
7011
7305
  } catch {
7012
7306
  return void 0;
7013
7307
  }
@@ -7042,7 +7336,7 @@ var LiteLLMPricingFetcher = class {
7042
7336
  };
7043
7337
  }
7044
7338
  async writeCache() {
7045
- const directoryPath = path13.dirname(this.cacheFilePath);
7339
+ const directoryPath = path14.dirname(this.cacheFilePath);
7046
7340
  await mkdir3(directoryPath, { recursive: true });
7047
7341
  const payload = {
7048
7342
  fetchedAt: this.now(),
@@ -7054,7 +7348,27 @@ var LiteLLMPricingFetcher = class {
7054
7348
  };
7055
7349
 
7056
7350
  // src/cli/build-usage-data-pricing.ts
7351
+ function wrapWithPricingOverrides(overrides, delegate) {
7352
+ if (!overrides || overrides.size === 0) {
7353
+ return delegate;
7354
+ }
7355
+ return new PricingOverrideSource(overrides, delegate);
7356
+ }
7057
7357
  async function resolvePricingSource(options, runtimeConfig) {
7358
+ let pricingOverrides;
7359
+ if (options.pricingOverrides) {
7360
+ try {
7361
+ pricingOverrides = await loadPricingOverrides(options.pricingOverrides);
7362
+ } catch (error) {
7363
+ const reason = error instanceof Error ? error.message : String(error);
7364
+ throw new Error(
7365
+ `Could not load --pricing-overrides from ${options.pricingOverrides}: ${reason}`,
7366
+ {
7367
+ cause: error
7368
+ }
7369
+ );
7370
+ }
7371
+ }
7058
7372
  const litellmPricingFetcher = new LiteLLMPricingFetcher({
7059
7373
  sourceUrl: options.pricingUrl,
7060
7374
  offline: options.pricingOffline,
@@ -7063,10 +7377,11 @@ async function resolvePricingSource(options, runtimeConfig) {
7063
7377
  });
7064
7378
  try {
7065
7379
  const fromCache = await litellmPricingFetcher.load();
7380
+ const source = wrapWithPricingOverrides(pricingOverrides, litellmPricingFetcher);
7066
7381
  if (options.pricingOffline) {
7067
- return { source: litellmPricingFetcher, origin: "offline-cache" };
7382
+ return { source, origin: "offline-cache" };
7068
7383
  }
7069
- return { source: litellmPricingFetcher, origin: fromCache ? "cache" : "network" };
7384
+ return { source, origin: fromCache ? "cache" : "network" };
7070
7385
  } catch (error) {
7071
7386
  if (options.pricingOffline) {
7072
7387
  throw new Error("Offline pricing mode enabled but cached pricing is unavailable", {
@@ -7634,12 +7949,12 @@ function emitEnvVarOverrides(activeEnvOverrides, diagnosticsLogger) {
7634
7949
  }
7635
7950
 
7636
7951
  // src/cli/share-artifact.ts
7637
- import { spawn as spawn4 } from "child_process";
7952
+ import { spawn as spawn3 } from "child_process";
7638
7953
  import { constants as constants3 } from "fs";
7639
7954
  import { access as access3, writeFile as writeFile4 } from "fs/promises";
7640
- import path14 from "path";
7955
+ import path15 from "path";
7641
7956
  async function writeShareSvgFile(fileName, svgContent) {
7642
- const outputPath = path14.resolve(process.cwd(), fileName);
7957
+ const outputPath = path15.resolve(process.cwd(), fileName);
7643
7958
  await writeFile4(outputPath, svgContent, "utf8");
7644
7959
  return outputPath;
7645
7960
  }
@@ -7667,10 +7982,10 @@ async function resolveBinaryPath(primaryPath, fallbackNames) {
7667
7982
  if (await fileExists(primaryPath)) {
7668
7983
  return primaryPath;
7669
7984
  }
7670
- const pathDirs = (process.env.PATH ?? "").split(path14.delimiter).filter(Boolean);
7985
+ const pathDirs = (process.env.PATH ?? "").split(path15.delimiter).filter(Boolean);
7671
7986
  for (const fallbackName of fallbackNames) {
7672
7987
  for (const dir of pathDirs) {
7673
- const candidate = path14.join(dir, fallbackName);
7988
+ const candidate = path15.join(dir, fallbackName);
7674
7989
  if (await fileExists(candidate)) {
7675
7990
  return candidate;
7676
7991
  }
@@ -7716,7 +8031,7 @@ async function resolveOpenCommand(filePath, platform) {
7716
8031
  }
7717
8032
  async function spawnDetached(command, args) {
7718
8033
  await new Promise((resolve, reject) => {
7719
- const child = spawn4(command, args, {
8034
+ const child = spawn3(command, args, {
7720
8035
  detached: true,
7721
8036
  stdio: "ignore",
7722
8037
  windowsHide: true
@@ -9916,7 +10231,10 @@ function registerSharedReportOptions(command, profile) {
9916
10231
  "--model <name>",
9917
10232
  "Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
9918
10233
  collectRepeatedOption
9919
- ).option("--pricing-url <url>", "Override LiteLLM pricing source URL").option("--pricing-offline", "Use cached LiteLLM pricing only (no network fetch)").option(
10234
+ ).option("--pricing-url <url>", "Override LiteLLM pricing source URL").option(
10235
+ "--pricing-overrides <path>",
10236
+ "Path to a JSON file of per-model pricing overrides (takes precedence over LiteLLM)"
10237
+ ).option("--pricing-offline", "Use cached LiteLLM pricing only (no network fetch)").option(
9920
10238
  "--ignore-pricing-failures",
9921
10239
  "Continue without estimated costs when pricing cannot be loaded"
9922
10240
  ).option("--json", "Render output as JSON");
@@ -9968,7 +10286,7 @@ function createUsageReportDefinition(granularity) {
9968
10286
  includeInRootHelp: true
9969
10287
  },
9970
10288
  {
9971
- command: "llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
10289
+ command: "llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions --source-dir openclaw=/tmp/openclaw-agents",
9972
10290
  includeInRootHelp: true,
9973
10291
  includeInCliReference: true
9974
10292
  },
@@ -10230,15 +10548,6 @@ var { packageName, packageVersion } = loadPackageMetadataFromRuntime();
10230
10548
  var updateRuntimeConfig = getUpdateNotifierRuntimeConfig();
10231
10549
  var cli = createCli({ version: packageVersion });
10232
10550
  async function main() {
10233
- if (process.env[UPDATE_CHECK_REFRESH_ENV_VAR] === "1") {
10234
- await refreshUpdateCheckCache({
10235
- packageName,
10236
- currentVersion: packageVersion,
10237
- cacheTtlMs: updateRuntimeConfig.cacheTtlMs,
10238
- fetchTimeoutMs: updateRuntimeConfig.fetchTimeoutMs
10239
- });
10240
- return;
10241
- }
10242
10551
  const updateResult = await checkForUpdatesAndMaybeRestart({
10243
10552
  packageName,
10244
10553
  currentVersion: packageVersion,