llm-usage-metrics 0.3.6 → 0.3.7

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
@@ -538,7 +538,15 @@ function isTruthyEnvFlag(value) {
538
538
  }
539
539
  function shouldSkipUpdateCheckForArgv(argv) {
540
540
  const executableArgs = argv.slice(2);
541
- const commandNames = /* @__PURE__ */ new Set(["daily", "weekly", "monthly", "efficiency", "help", "version"]);
541
+ const commandNames = /* @__PURE__ */ new Set([
542
+ "daily",
543
+ "weekly",
544
+ "monthly",
545
+ "efficiency",
546
+ "optimize",
547
+ "help",
548
+ "version"
549
+ ]);
542
550
  if (executableArgs.length === 0) {
543
551
  return false;
544
552
  }
@@ -674,6 +682,35 @@ function normalizeModelList(models) {
674
682
  return [...deduplicated].sort(compareByCodePoint);
675
683
  }
676
684
 
685
+ // src/domain/provider-normalization.ts
686
+ var billingProviderAliases = /* @__PURE__ */ new Map([
687
+ ["openai-codex", "openai"],
688
+ ["github-copilot", "github"]
689
+ ]);
690
+ var billingProviderPrefixAliases = [
691
+ ["openai-", "openai"],
692
+ ["openai/", "openai"]
693
+ ];
694
+ function normalizeProviderToBillingEntity(provider) {
695
+ if (!provider) {
696
+ return void 0;
697
+ }
698
+ const normalizedProvider = provider.trim().toLowerCase();
699
+ if (normalizedProvider.length === 0) {
700
+ return void 0;
701
+ }
702
+ const aliasedProvider = billingProviderAliases.get(normalizedProvider);
703
+ if (aliasedProvider) {
704
+ return aliasedProvider;
705
+ }
706
+ for (const [prefix, billingProvider] of billingProviderPrefixAliases) {
707
+ if (normalizedProvider.startsWith(prefix)) {
708
+ return billingProvider;
709
+ }
710
+ }
711
+ return normalizedProvider;
712
+ }
713
+
677
714
  // src/domain/usage-event.ts
678
715
  function normalizeSourceId(value) {
679
716
  if (typeof value !== "string") {
@@ -696,6 +733,9 @@ function normalizeOptionalText(value) {
696
733
  const normalized = value.trim();
697
734
  return normalized || void 0;
698
735
  }
736
+ function normalizeOptionalProvider(value) {
737
+ return normalizeProviderToBillingEntity(value);
738
+ }
699
739
  function normalizeOptionalPath(value) {
700
740
  return normalizeOptionalText(value);
701
741
  }
@@ -735,7 +775,7 @@ function createUsageEvent(input) {
735
775
  sessionId: requireText(input.sessionId, "sessionId"),
736
776
  timestamp: normalizeTimestamp(input.timestamp),
737
777
  repoRoot: normalizeOptionalPath(input.repoRoot),
738
- provider: normalizeOptionalText(input.provider),
778
+ provider: normalizeOptionalProvider(input.provider),
739
779
  model: normalizeOptionalModel(input.model),
740
780
  inputTokens,
741
781
  outputTokens,
@@ -3264,6 +3304,42 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
3264
3304
  };
3265
3305
  }
3266
3306
 
3307
+ // src/cli/build-usage-data-diagnostics.ts
3308
+ function buildUsageDiagnostics(params) {
3309
+ const parseResultBySource = new Map(
3310
+ params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
3311
+ );
3312
+ const sessionStats = params.adaptersToParse.map((adapter) => {
3313
+ const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
3314
+ return {
3315
+ source: adapter.id,
3316
+ filesFound: parseResult?.filesFound ?? 0,
3317
+ eventsParsed: parseResult?.events.length ?? 0
3318
+ };
3319
+ });
3320
+ const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
3321
+ source: result.source,
3322
+ skippedRows: result.skippedRows,
3323
+ reasons: result.skippedRowReasons
3324
+ }));
3325
+ return {
3326
+ sessionStats,
3327
+ sourceFailures: params.sourceFailures,
3328
+ skippedRows,
3329
+ pricingOrigin: params.pricingOrigin,
3330
+ pricingWarning: params.pricingWarning,
3331
+ activeEnvOverrides: params.activeEnvOverrides,
3332
+ timezone: params.timezone
3333
+ };
3334
+ }
3335
+ function assembleUsageDataResult(events, rows, diagnostics) {
3336
+ return {
3337
+ events,
3338
+ rows,
3339
+ diagnostics
3340
+ };
3341
+ }
3342
+
3267
3343
  // src/config/env-var-display.ts
3268
3344
  var ENV_VARS_TO_DISPLAY = [
3269
3345
  { name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
@@ -3304,42 +3380,6 @@ function formatEnvVarOverrides(overrides) {
3304
3380
  return lines;
3305
3381
  }
3306
3382
 
3307
- // src/cli/build-usage-data-diagnostics.ts
3308
- function buildUsageDiagnostics(params) {
3309
- const parseResultBySource = new Map(
3310
- params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
3311
- );
3312
- const sessionStats = params.adaptersToParse.map((adapter) => {
3313
- const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
3314
- return {
3315
- source: adapter.id,
3316
- filesFound: parseResult?.filesFound ?? 0,
3317
- eventsParsed: parseResult?.events.length ?? 0
3318
- };
3319
- });
3320
- const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
3321
- source: result.source,
3322
- skippedRows: result.skippedRows,
3323
- reasons: result.skippedRowReasons
3324
- }));
3325
- return {
3326
- sessionStats,
3327
- sourceFailures: params.sourceFailures,
3328
- skippedRows,
3329
- pricingOrigin: params.pricingOrigin,
3330
- pricingWarning: params.pricingWarning,
3331
- activeEnvOverrides: params.activeEnvOverrides,
3332
- timezone: params.timezone
3333
- };
3334
- }
3335
- function assembleUsageDataResult(events, rows, diagnostics) {
3336
- return {
3337
- events,
3338
- rows,
3339
- diagnostics
3340
- };
3341
- }
3342
-
3343
3383
  // src/cli/build-usage-data-inputs.ts
3344
3384
  function validateDateInput(value, flagName) {
3345
3385
  if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
@@ -3359,11 +3399,7 @@ function validateTimezone(timezone) {
3359
3399
  }
3360
3400
  }
3361
3401
  function normalizeProviderFilter(provider) {
3362
- if (!provider) {
3363
- return void 0;
3364
- }
3365
- const normalized = provider.trim().toLowerCase();
3366
- return normalized || void 0;
3402
+ return normalizeProviderToBillingEntity(provider);
3367
3403
  }
3368
3404
  function normalizeSourceFilter(source) {
3369
3405
  if (!source || Array.isArray(source) && source.length === 0) {
@@ -3540,7 +3576,7 @@ function normalizeSkippedRowReasons(value) {
3540
3576
  // src/cli/parse-file-cache.ts
3541
3577
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
3542
3578
  import path11 from "path";
3543
- var PARSE_FILE_CACHE_VERSION = 2;
3579
+ var PARSE_FILE_CACHE_VERSION = 3;
3544
3580
  var CACHE_KEY_SEPARATOR = "\0";
3545
3581
  function createCacheKey(source, filePath) {
3546
3582
  return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
@@ -3599,7 +3635,9 @@ function normalizeCachedUsageEvent(value) {
3599
3635
  if (inputTokens === void 0 || outputTokens === void 0 || reasoningTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0 || totalTokens === void 0) {
3600
3636
  return void 0;
3601
3637
  }
3602
- const provider = typeof record.provider === "string" ? record.provider.trim() : "";
3638
+ const provider = normalizeProviderToBillingEntity(
3639
+ typeof record.provider === "string" ? record.provider : void 0
3640
+ );
3603
3641
  const model = typeof record.model === "string" ? record.model.trim().toLowerCase() : "";
3604
3642
  const costUsd = toNonNegativeNumber2(record.costUsd);
3605
3643
  if (costMode === "explicit" && costUsd === void 0) {
@@ -3610,7 +3648,7 @@ function normalizeCachedUsageEvent(value) {
3610
3648
  sessionId,
3611
3649
  timestamp,
3612
3650
  repoRoot: repoRoot || void 0,
3613
- provider: provider || void 0,
3651
+ provider,
3614
3652
  model: model || void 0,
3615
3653
  inputTokens,
3616
3654
  outputTokens,
@@ -4745,9 +4783,20 @@ function shouldLoadPricingSource(events) {
4745
4783
  }
4746
4784
  return events.some((event) => eventNeedsPricingLookup(event));
4747
4785
  }
4748
- async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource) {
4786
+ function hasAnyBillableTokenBuckets(events) {
4787
+ return events.some(
4788
+ (event) => event.inputTokens > 0 || event.outputTokens > 0 || event.reasoningTokens > 0 || event.cacheReadTokens > 0 || event.cacheWriteTokens > 0
4789
+ );
4790
+ }
4791
+ function shouldLoadPricingSourceForMode(events, pricingLoadMode) {
4792
+ if (pricingLoadMode === "force") {
4793
+ return hasAnyBillableTokenBuckets(events);
4794
+ }
4795
+ return shouldLoadPricingSource(events);
4796
+ }
4797
+ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource, pricingLoadMode = "auto") {
4749
4798
  let pricingOrigin = "none";
4750
- if (!shouldLoadPricingSource(events)) {
4799
+ if (!shouldLoadPricingSourceForMode(events, pricingLoadMode)) {
4751
4800
  return {
4752
4801
  pricedEvents: events,
4753
4802
  pricingOrigin
@@ -4771,11 +4820,12 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
4771
4820
  pricingOrigin = pricingResult.origin;
4772
4821
  return {
4773
4822
  pricedEvents: applyPricingToEvents(events, pricingResult.source),
4774
- pricingOrigin
4823
+ pricingOrigin,
4824
+ pricingSource: pricingResult.source
4775
4825
  };
4776
4826
  }
4777
4827
 
4778
- // src/cli/build-usage-data.ts
4828
+ // src/cli/build-usage-event-dataset.ts
4779
4829
  function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4780
4830
  if (options.pricingUrl === normalizedPricingUrl) {
4781
4831
  return options;
@@ -4785,13 +4835,11 @@ function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4785
4835
  pricingUrl: normalizedPricingUrl
4786
4836
  };
4787
4837
  }
4788
- async function buildUsageData(granularity, options, deps = {}) {
4838
+ async function buildUsageEventDataset(options, deps = {}) {
4789
4839
  const normalizedInputs = normalizeBuildUsageInputs(options);
4790
4840
  const readParsingRuntimeConfig = deps.getParsingRuntimeConfig ?? getParsingRuntimeConfig;
4791
4841
  const readPricingRuntimeConfig = deps.getPricingFetcherRuntimeConfig ?? getPricingFetcherRuntimeConfig;
4792
4842
  const makeAdapters = deps.createAdapters ?? createDefaultAdapters;
4793
- const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4794
- const readEnvVarOverrides = deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides;
4795
4843
  const parsingRuntimeConfig = readParsingRuntimeConfig();
4796
4844
  const pricingRuntimeConfig = readPricingRuntimeConfig();
4797
4845
  const adapters = makeAdapters(options);
@@ -4816,26 +4864,53 @@ async function buildUsageData(granularity, options, deps = {}) {
4816
4864
  providerFilter: normalizedInputs.providerFilter,
4817
4865
  modelFilter: normalizedInputs.modelFilter
4818
4866
  });
4819
- const pricingOptions = withNormalizedPricingUrl(options, normalizedInputs.pricingUrl);
4820
- const { pricedEvents, pricingOrigin, pricingWarning } = await resolveAndApplyPricingToEvents(
4867
+ return {
4868
+ options,
4869
+ normalizedInputs,
4870
+ adaptersToParse,
4871
+ successfulParseResults,
4872
+ sourceFailures,
4821
4873
  filteredEvents,
4822
- pricingOptions,
4823
4874
  pricingRuntimeConfig,
4824
- loadPricingSource
4875
+ readEnvVarOverrides: deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides
4876
+ };
4877
+ }
4878
+ async function applyPricingToUsageEventDataset(dataset, deps = {}, pricingLoadMode = "auto") {
4879
+ const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4880
+ const pricingOptions = withNormalizedPricingUrl(
4881
+ dataset.options,
4882
+ dataset.normalizedInputs.pricingUrl
4883
+ );
4884
+ return resolveAndApplyPricingToEvents(
4885
+ dataset.filteredEvents,
4886
+ pricingOptions,
4887
+ dataset.pricingRuntimeConfig,
4888
+ loadPricingSource,
4889
+ pricingLoadMode
4890
+ );
4891
+ }
4892
+
4893
+ // src/cli/build-usage-data.ts
4894
+ async function buildUsageData(granularity, options, deps = {}) {
4895
+ const dataset = await buildUsageEventDataset(options, deps);
4896
+ const { pricedEvents, pricingOrigin, pricingWarning } = await applyPricingToUsageEventDataset(
4897
+ dataset,
4898
+ deps,
4899
+ "auto"
4825
4900
  );
4826
4901
  const rows = aggregateUsage(pricedEvents, {
4827
4902
  granularity,
4828
- timezone: normalizedInputs.timezone,
4829
- sourceOrder: adaptersToParse.map((adapter) => adapter.id)
4903
+ timezone: dataset.normalizedInputs.timezone,
4904
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
4830
4905
  });
4831
4906
  const diagnostics = buildUsageDiagnostics({
4832
- adaptersToParse,
4833
- successfulParseResults,
4834
- sourceFailures,
4907
+ adaptersToParse: dataset.adaptersToParse,
4908
+ successfulParseResults: dataset.successfulParseResults,
4909
+ sourceFailures: dataset.sourceFailures,
4835
4910
  pricingOrigin,
4836
4911
  pricingWarning,
4837
- activeEnvOverrides: readEnvVarOverrides(),
4838
- timezone: normalizedInputs.timezone
4912
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
4913
+ timezone: dataset.normalizedInputs.timezone
4839
4914
  });
4840
4915
  return assembleUsageDataResult(pricedEvents, rows, diagnostics);
4841
4916
  }
@@ -5252,6 +5327,7 @@ function warnIfTerminalTableOverflows(reportOutput, warn, stdoutState = process.
5252
5327
 
5253
5328
  // src/render/render-efficiency-report.ts
5254
5329
  import { markdownTable } from "markdown-table";
5330
+ import pc5 from "picocolors";
5255
5331
 
5256
5332
  // src/render/report-header.ts
5257
5333
  import pc2 from "picocolors";
@@ -5397,8 +5473,8 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
5397
5473
  var rowTypeStylePolicies = {
5398
5474
  period_source: (cells, palette) => {
5399
5475
  const styledCells = [...cells];
5400
- const costColumnIndex = styledCells.length - 1;
5401
- styledCells[costColumnIndex] = styleCellLines(styledCells[costColumnIndex], palette.yellow);
5476
+ const costColumnIndex2 = styledCells.length - 1;
5477
+ styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
5402
5478
  return styledCells;
5403
5479
  },
5404
5480
  period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
@@ -5843,6 +5919,14 @@ function renderTerminalTable(rows, options = {}) {
5843
5919
  // src/render/render-efficiency-report.ts
5844
5920
  var periodColumnIndex = 0;
5845
5921
  var minimumEfficiencyColumnWidth = 1;
5922
+ var commitsColumnIndex = 1;
5923
+ var linesAddedColumnIndex = 2;
5924
+ var linesDeletedColumnIndex = 3;
5925
+ var linesChangedColumnIndex = 4;
5926
+ var costColumnIndex = 11;
5927
+ var usdPerCommitColumnIndex = 12;
5928
+ var usdPer1kLinesChangedColumnIndex = 13;
5929
+ var commitsPerUsdColumnIndex = 16;
5846
5930
  function getReportTitle(granularity) {
5847
5931
  switch (granularity) {
5848
5932
  case "daily":
@@ -5981,9 +6065,67 @@ function fitTableCellsToTerminal(headerCells, bodyRows) {
5981
6065
  widths: constrainedWidths
5982
6066
  };
5983
6067
  }
5984
- function renderTerminalEfficiencyTable(rows) {
6068
+ function styleDeltaCell(value, formattedValue, options) {
6069
+ if (!options.useColor) {
6070
+ return formattedValue;
6071
+ }
6072
+ if (value > 0) {
6073
+ return pc5.green(formattedValue);
6074
+ }
6075
+ if (value < 0) {
6076
+ return pc5.red(formattedValue);
6077
+ }
6078
+ return pc5.dim(formattedValue);
6079
+ }
6080
+ function styleEfficiencyTerminalRows(rows, bodyRows, options) {
6081
+ return bodyRows.map((cells, rowIndex) => {
6082
+ if (!options.useColor) {
6083
+ return [...cells];
6084
+ }
6085
+ const row = rows[rowIndex];
6086
+ const styledCells = [...cells];
6087
+ const periodCell = styledCells[periodColumnIndex];
6088
+ styledCells[periodColumnIndex] = row.rowType === "grand_total" ? pc5.bold(pc5.cyan(periodCell)) : pc5.bold(periodCell);
6089
+ styledCells[commitsColumnIndex] = pc5.bold(styledCells[commitsColumnIndex]);
6090
+ styledCells[linesAddedColumnIndex] = styleDeltaCell(
6091
+ row.linesAdded,
6092
+ styledCells[linesAddedColumnIndex],
6093
+ options
6094
+ );
6095
+ styledCells[linesDeletedColumnIndex] = styleDeltaCell(
6096
+ row.linesDeleted * -1,
6097
+ styledCells[linesDeletedColumnIndex],
6098
+ options
6099
+ );
6100
+ styledCells[linesChangedColumnIndex] = styleDeltaCell(
6101
+ row.linesChanged,
6102
+ styledCells[linesChangedColumnIndex],
6103
+ options
6104
+ );
6105
+ const costValue = row.costUsd;
6106
+ if (costValue !== void 0 && costValue > 0) {
6107
+ styledCells[costColumnIndex] = pc5.yellow(styledCells[costColumnIndex]);
6108
+ }
6109
+ const usdPerCommitValue = row.usdPerCommit;
6110
+ if (usdPerCommitValue !== void 0 && usdPerCommitValue > 0) {
6111
+ styledCells[usdPerCommitColumnIndex] = pc5.yellow(styledCells[usdPerCommitColumnIndex]);
6112
+ }
6113
+ const usdPer1kLinesChangedValue = row.usdPer1kLinesChanged;
6114
+ if (usdPer1kLinesChangedValue !== void 0 && usdPer1kLinesChangedValue > 0) {
6115
+ styledCells[usdPer1kLinesChangedColumnIndex] = pc5.yellow(
6116
+ styledCells[usdPer1kLinesChangedColumnIndex]
6117
+ );
6118
+ }
6119
+ const commitsPerUsdValue = row.commitsPerUsd;
6120
+ if (commitsPerUsdValue !== void 0 && commitsPerUsdValue > 0) {
6121
+ styledCells[commitsPerUsdColumnIndex] = pc5.green(styledCells[commitsPerUsdColumnIndex]);
6122
+ }
6123
+ return styledCells;
6124
+ });
6125
+ }
6126
+ function renderTerminalEfficiencyTable(rows, options) {
5985
6127
  const headerCells = Array.from(efficiencyTableHeaders);
5986
- const bodyRows = toEfficiencyTableCells(rows);
6128
+ const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
5987
6129
  const tableSortRows = rows.map((row) => toTableSortRow(row));
5988
6130
  const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
5989
6131
  return renderUnicodeTable({
@@ -6020,7 +6162,7 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
6020
6162
  })
6021
6163
  );
6022
6164
  outputLines.push("");
6023
- outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows));
6165
+ outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
6024
6166
  return outputLines.join("\n");
6025
6167
  }
6026
6168
  function renderEfficiencyReport(efficiencyData, format, options) {
@@ -6083,25 +6225,656 @@ async function runEfficiencyReport(granularity, options) {
6083
6225
  console.log(preparedReport.output);
6084
6226
  }
6085
6227
 
6086
- // src/render/markdown-table.ts
6228
+ // src/render/render-optimize-report.ts
6087
6229
  import { markdownTable as markdownTable2 } from "markdown-table";
6088
- var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6230
+ import pc6 from "picocolors";
6231
+ var optimizeTableHeadersWithNotes = [
6232
+ "Period",
6233
+ "Candidate",
6234
+ "Hypothetical Cost",
6235
+ "Baseline Cost",
6236
+ "Savings",
6237
+ "Savings %",
6238
+ "Notes"
6239
+ ];
6240
+ var optimizeTableHeadersWithoutNotes = [
6241
+ "Period",
6242
+ "Candidate",
6243
+ "Hypothetical Cost",
6244
+ "Baseline Cost",
6245
+ "Savings",
6246
+ "Savings %"
6247
+ ];
6248
+ var usdFormatter3 = new Intl.NumberFormat("en-US", {
6249
+ style: "currency",
6250
+ currency: "USD",
6251
+ minimumFractionDigits: 2,
6252
+ maximumFractionDigits: 2
6253
+ });
6254
+ function getReportTitle2(granularity) {
6255
+ switch (granularity) {
6256
+ case "daily":
6257
+ return "Daily Optimize Report";
6258
+ case "weekly":
6259
+ return "Weekly Optimize Report";
6260
+ case "monthly":
6261
+ return "Monthly Optimize Report";
6262
+ }
6263
+ }
6264
+ function formatUsd3(value, options = {}) {
6265
+ if (value === void 0) {
6266
+ return "-";
6267
+ }
6268
+ const formatted = usdFormatter3.format(value);
6269
+ return options.approximate ? `~${formatted}` : formatted;
6270
+ }
6271
+ function formatPercent(value) {
6272
+ if (value === void 0) {
6273
+ return "-";
6274
+ }
6275
+ return `${(value * 100).toFixed(2)}%`;
6276
+ }
6277
+ function formatNotes(notes) {
6278
+ if (!notes || notes.length === 0) {
6279
+ return "-";
6280
+ }
6281
+ return notes.join(", ");
6282
+ }
6283
+ function styleCandidateCell(candidateValue, rowType, useColor) {
6284
+ if (!useColor) {
6285
+ return candidateValue;
6286
+ }
6287
+ if (rowType === "baseline") {
6288
+ return pc6.bold(pc6.cyan(candidateValue));
6289
+ }
6290
+ return pc6.bold(candidateValue);
6291
+ }
6292
+ function styleDeltaCell2(value, formattedValue, useColor) {
6293
+ if (!useColor || value === void 0) {
6294
+ return formattedValue;
6295
+ }
6296
+ if (value > 0) {
6297
+ return pc6.green(formattedValue);
6298
+ }
6299
+ if (value < 0) {
6300
+ return pc6.red(formattedValue);
6301
+ }
6302
+ return pc6.dim(formattedValue);
6303
+ }
6304
+ function styleNotesCell(notes, formattedNotes, useColor) {
6305
+ if (!useColor || !notes || notes.length === 0) {
6306
+ return formattedNotes;
6307
+ }
6308
+ return pc6.yellow(formattedNotes);
6309
+ }
6310
+ function formatAbsoluteUsd(value) {
6311
+ return usdFormatter3.format(Math.abs(value));
6312
+ }
6313
+ function resolveTerminalContextLines(optimizeData, options) {
6314
+ const allBaselineRow = optimizeData.rows.find(
6315
+ (row) => row.rowType === "baseline" && row.periodKey === "ALL"
6316
+ );
6317
+ const allCandidateRows = optimizeData.rows.filter(
6318
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6319
+ );
6320
+ const lines = [];
6321
+ const providerLine = `Provider scope: ${optimizeData.diagnostics.provider}`;
6322
+ lines.push(options.useColor ? pc6.cyan(providerLine) : providerLine);
6323
+ if (allBaselineRow) {
6324
+ lines.push(
6325
+ `ALL baseline cost: ${formatUsd3(allBaselineRow.baselineCostUsd, { approximate: allBaselineRow.baselineCostIncomplete })}`
6326
+ );
6327
+ }
6328
+ if (allCandidateRows.length > 0) {
6329
+ const rowsWithSavings = allCandidateRows.filter((row) => row.savingsUsd !== void 0);
6330
+ const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
6331
+ (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
6332
+ ) : void 0;
6333
+ if (!bestRow || bestRow.savingsUsd === void 0) {
6334
+ lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
6335
+ } else if (bestRow.savingsUsd > 0) {
6336
+ lines.push(
6337
+ `ALL best candidate: ${bestRow.candidateModel} saves ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6338
+ );
6339
+ } else if (bestRow.savingsUsd < 0) {
6340
+ lines.push(
6341
+ `ALL best candidate: ${bestRow.candidateModel} increases cost by ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6342
+ );
6343
+ } else {
6344
+ lines.push(`ALL best candidate: ${bestRow.candidateModel} matches baseline cost`);
6345
+ }
6346
+ }
6347
+ if (optimizeData.diagnostics.candidatesWithMissingPricing.length > 0) {
6348
+ const missingLine = `Missing candidate pricing: ${optimizeData.diagnostics.candidatesWithMissingPricing.join(", ")}`;
6349
+ lines.push(options.useColor ? pc6.yellow(missingLine) : missingLine);
6350
+ }
6351
+ const legendLine = "Savings = Baseline - Hypothetical (positive means cheaper candidate)";
6352
+ lines.push(options.useColor ? pc6.dim(legendLine) : legendLine);
6353
+ return lines;
6354
+ }
6355
+ function toTableCells(optimizeData, options) {
6356
+ const baselineByPeriod = new Map(
6357
+ optimizeData.rows.filter((row) => row.rowType === "baseline").map((row) => [row.periodKey, row])
6358
+ );
6359
+ return optimizeData.rows.map((row) => {
6360
+ const baselineRow = baselineByPeriod.get(row.periodKey);
6361
+ const periodCell = options.useColor && row.periodKey === "ALL" ? pc6.bold(row.periodKey) : row.periodKey;
6362
+ if (row.rowType === "baseline") {
6363
+ const baselineCells = [
6364
+ periodCell,
6365
+ styleCandidateCell("BASELINE", "baseline", options.useColor),
6366
+ "-",
6367
+ formatUsd3(row.baselineCostUsd, { approximate: row.baselineCostIncomplete }),
6368
+ "-",
6369
+ "-"
6370
+ ];
6371
+ return options.includeNotesColumn ? [...baselineCells, "-"] : baselineCells;
6372
+ }
6373
+ const savingsCell = formatUsd3(row.savingsUsd);
6374
+ const savingsPctCell = formatPercent(row.savingsPct);
6375
+ const notesCell = formatNotes(row.notes);
6376
+ const candidateCells = [
6377
+ periodCell,
6378
+ styleCandidateCell(row.candidateModel, "candidate", options.useColor),
6379
+ formatUsd3(row.hypotheticalCostUsd, { approximate: row.hypotheticalCostIncomplete }),
6380
+ formatUsd3(baselineRow?.baselineCostUsd, {
6381
+ approximate: baselineRow?.baselineCostIncomplete === true
6382
+ }),
6383
+ styleDeltaCell2(row.savingsUsd, savingsCell, options.useColor),
6384
+ styleDeltaCell2(row.savingsPct, savingsPctCell, options.useColor)
6385
+ ];
6386
+ return options.includeNotesColumn ? [...candidateCells, styleNotesCell(row.notes, notesCell, options.useColor)] : candidateCells;
6387
+ });
6388
+ }
6089
6389
  function toMarkdownSafeCell2(value) {
6090
6390
  return value.replace(/\r?\n/gu, "<br>");
6091
6391
  }
6392
+ function toSortingUsageRows(optimizeData) {
6393
+ return optimizeData.rows.map((row) => {
6394
+ return {
6395
+ rowType: "period_source",
6396
+ periodKey: row.periodKey,
6397
+ source: "combined",
6398
+ models: [],
6399
+ modelBreakdown: [],
6400
+ inputTokens: row.inputTokens,
6401
+ outputTokens: row.outputTokens,
6402
+ reasoningTokens: row.reasoningTokens,
6403
+ cacheReadTokens: row.cacheReadTokens,
6404
+ cacheWriteTokens: row.cacheWriteTokens,
6405
+ totalTokens: row.totalTokens,
6406
+ costUsd: row.rowType === "baseline" ? row.baselineCostUsd : row.hypotheticalCostUsd,
6407
+ costIncomplete: row.rowType === "baseline" ? row.baselineCostIncomplete : row.hypotheticalCostIncomplete
6408
+ };
6409
+ });
6410
+ }
6411
+ function resolveCandidateColumnWidth(tableCells) {
6412
+ return tableCells.reduce((maxWidth, row) => {
6413
+ const candidateValue = row[1] ?? "";
6414
+ return Math.max(maxWidth, visibleWidth(candidateValue));
6415
+ }, visibleWidth(optimizeTableHeadersWithNotes[1]));
6416
+ }
6417
+ function resolveIncludeNotesColumn(optimizeData) {
6418
+ return optimizeData.rows.some(
6419
+ (row) => row.rowType === "candidate" && row.notes !== void 0 && row.notes.length > 0
6420
+ );
6421
+ }
6422
+ function renderTerminalOptimizeReport(optimizeData, options) {
6423
+ const useColor = options.useColor ?? shouldUseColorByDefault();
6424
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6425
+ const tableCells = toTableCells(optimizeData, { useColor, includeNotesColumn });
6426
+ const candidateColumnWidth = resolveCandidateColumnWidth(tableCells);
6427
+ const contextLines = resolveTerminalContextLines(optimizeData, { useColor });
6428
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6429
+ const outputLines = [];
6430
+ outputLines.push(
6431
+ renderReportHeader({
6432
+ title: getReportTitle2(options.granularity),
6433
+ useColor
6434
+ })
6435
+ );
6436
+ outputLines.push("");
6437
+ outputLines.push(...contextLines);
6438
+ outputLines.push("");
6439
+ outputLines.push(
6440
+ renderUnicodeTable({
6441
+ headerCells,
6442
+ bodyRows: tableCells,
6443
+ measureHeaderCells: headerCells,
6444
+ measureBodyRows: tableCells,
6445
+ usageRows: toSortingUsageRows(optimizeData),
6446
+ tableLayout: "compact",
6447
+ modelsColumnIndex: 1,
6448
+ modelsColumnWidth: candidateColumnWidth
6449
+ })
6450
+ );
6451
+ return outputLines.join("\n");
6452
+ }
6453
+ function renderMarkdownOptimizeReport(optimizeData) {
6454
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6455
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6456
+ const bodyRows = toTableCells(optimizeData, {
6457
+ useColor: false,
6458
+ includeNotesColumn
6459
+ }).map((row) => row.map((cell) => toMarkdownSafeCell2(cell)));
6460
+ const tableRows = [headerCells, ...bodyRows];
6461
+ const alignment2 = headerCells.map((_, index) => index <= 1 ? "l" : "r");
6462
+ return markdownTable2(tableRows, { align: alignment2 });
6463
+ }
6464
+ function renderOptimizeReport(optimizeData, format, options) {
6465
+ switch (format) {
6466
+ case "json":
6467
+ return JSON.stringify(optimizeData.rows, null, 2);
6468
+ case "markdown":
6469
+ return renderMarkdownOptimizeReport(optimizeData);
6470
+ case "terminal":
6471
+ return renderTerminalOptimizeReport(optimizeData, options);
6472
+ }
6473
+ }
6474
+
6475
+ // src/optimize/aggregate-counterfactual.ts
6476
+ var USD_PRECISION_SCALE3 = 1e12;
6477
+ function roundUsd(value) {
6478
+ return Math.round(value * USD_PRECISION_SCALE3) / USD_PRECISION_SCALE3;
6479
+ }
6480
+ function hasZeroBillableTokenBuckets(totals) {
6481
+ return totals.inputTokens === 0 && totals.outputTokens === 0 && totals.reasoningTokens === 0 && totals.cacheReadTokens === 0 && totals.cacheWriteTokens === 0;
6482
+ }
6483
+ function parseCandidateModelsRaw(candidateModel) {
6484
+ if (!candidateModel || Array.isArray(candidateModel) && candidateModel.length === 0) {
6485
+ throw new Error("At least one --candidate-model is required");
6486
+ }
6487
+ const normalizedCandidates = (Array.isArray(candidateModel) ? candidateModel : [candidateModel]).flatMap((candidate) => candidate.split(",")).map((candidate) => candidate.trim().toLowerCase()).filter((candidate) => candidate.length > 0);
6488
+ if (normalizedCandidates.length === 0) {
6489
+ throw new Error("--candidate-model must contain at least one non-empty model name");
6490
+ }
6491
+ return [...new Set(normalizedCandidates)];
6492
+ }
6493
+ function normalizeCandidateModels(candidateModel) {
6494
+ return parseCandidateModelsRaw(candidateModel);
6495
+ }
6496
+ function parseTopOption(top) {
6497
+ if (top === void 0) {
6498
+ return void 0;
6499
+ }
6500
+ const normalized = top.trim();
6501
+ const parsed = Number.parseInt(normalized, 10);
6502
+ if (!/^\d+$/u.test(normalized) || Number.isNaN(parsed) || parsed < 1) {
6503
+ throw new Error("--top must be a positive integer");
6504
+ }
6505
+ return parsed;
6506
+ }
6507
+ function toBaselineRow(period, provider) {
6508
+ return {
6509
+ rowType: "baseline",
6510
+ periodKey: period.periodKey,
6511
+ provider,
6512
+ inputTokens: period.inputTokens,
6513
+ outputTokens: period.outputTokens,
6514
+ reasoningTokens: period.reasoningTokens,
6515
+ cacheReadTokens: period.cacheReadTokens,
6516
+ cacheWriteTokens: period.cacheWriteTokens,
6517
+ totalTokens: period.totalTokens,
6518
+ baselineCostUsd: period.baselineCostUsd,
6519
+ baselineCostIncomplete: period.baselineCostIncomplete
6520
+ };
6521
+ }
6522
+ function createSyntheticEvent(period) {
6523
+ return {
6524
+ source: "pi",
6525
+ sessionId: "optimize-period",
6526
+ timestamp: "1970-01-01T00:00:00.000Z",
6527
+ provider: void 0,
6528
+ model: "synthetic",
6529
+ inputTokens: period.inputTokens,
6530
+ outputTokens: period.outputTokens,
6531
+ reasoningTokens: period.reasoningTokens,
6532
+ cacheReadTokens: period.cacheReadTokens,
6533
+ cacheWriteTokens: period.cacheWriteTokens,
6534
+ totalTokens: period.totalTokens,
6535
+ costMode: "estimated",
6536
+ costUsd: void 0
6537
+ };
6538
+ }
6539
+ function withNotes(notes) {
6540
+ if (notes.size === 0) {
6541
+ return void 0;
6542
+ }
6543
+ return [...notes].sort(compareByCodePoint);
6544
+ }
6545
+ function evaluateCandidateForPeriod(period, provider, candidateModel, pricingSource) {
6546
+ const notes = /* @__PURE__ */ new Set();
6547
+ const zeroBillableTokens = hasZeroBillableTokenBuckets(period);
6548
+ const candidateResolvedModel = pricingSource ? pricingSource.resolveModelAlias(candidateModel) : candidateModel;
6549
+ const pricing = pricingSource ? pricingSource.getPricing(candidateResolvedModel) : void 0;
6550
+ let hypotheticalCostUsd;
6551
+ let hypotheticalCostIncomplete = false;
6552
+ if (!pricing) {
6553
+ if (zeroBillableTokens) {
6554
+ hypotheticalCostUsd = 0;
6555
+ } else {
6556
+ hypotheticalCostUsd = void 0;
6557
+ hypotheticalCostIncomplete = true;
6558
+ notes.add("missing_pricing");
6559
+ }
6560
+ } else {
6561
+ hypotheticalCostUsd = roundUsd(
6562
+ calculateEstimatedCostUsd(createSyntheticEvent(period), pricing)
6563
+ );
6564
+ }
6565
+ let savingsUsd;
6566
+ let savingsPct;
6567
+ let hasBaselineTokenMismatch = false;
6568
+ if (period.baselineCostIncomplete || period.baselineCostUsd === void 0) {
6569
+ notes.add("baseline_incomplete");
6570
+ } else if (zeroBillableTokens && period.baselineCostUsd > 0) {
6571
+ notes.add("baseline_tokens_missing");
6572
+ hasBaselineTokenMismatch = true;
6573
+ } else if (hypotheticalCostUsd !== void 0) {
6574
+ savingsUsd = roundUsd(period.baselineCostUsd - hypotheticalCostUsd);
6575
+ savingsPct = period.baselineCostUsd === 0 ? void 0 : savingsUsd / period.baselineCostUsd;
6576
+ }
6577
+ return {
6578
+ candidateRow: {
6579
+ rowType: "candidate",
6580
+ periodKey: period.periodKey,
6581
+ provider,
6582
+ inputTokens: period.inputTokens,
6583
+ outputTokens: period.outputTokens,
6584
+ reasoningTokens: period.reasoningTokens,
6585
+ cacheReadTokens: period.cacheReadTokens,
6586
+ cacheWriteTokens: period.cacheWriteTokens,
6587
+ totalTokens: period.totalTokens,
6588
+ candidateModel,
6589
+ candidateResolvedModel,
6590
+ hypotheticalCostUsd,
6591
+ hypotheticalCostIncomplete,
6592
+ savingsUsd,
6593
+ savingsPct,
6594
+ notes: withNotes(notes)
6595
+ },
6596
+ missingPricing: notes.has("missing_pricing"),
6597
+ hasBaselineTokenMismatch
6598
+ };
6599
+ }
6600
+ function compareCandidateRank(left, right) {
6601
+ if (left.hypotheticalCostUsd === void 0 && right.hypotheticalCostUsd !== void 0) {
6602
+ return 1;
6603
+ }
6604
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd === void 0) {
6605
+ return -1;
6606
+ }
6607
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd !== void 0) {
6608
+ if (left.hypotheticalCostUsd !== right.hypotheticalCostUsd) {
6609
+ return left.hypotheticalCostUsd - right.hypotheticalCostUsd;
6610
+ }
6611
+ }
6612
+ return compareByCodePoint(left.candidateModel, right.candidateModel);
6613
+ }
6614
+ function resolveBaselinePeriods(usageRows) {
6615
+ const periodRows = /* @__PURE__ */ new Map();
6616
+ let grandTotalRow;
6617
+ for (const row of usageRows) {
6618
+ if (row.rowType === "grand_total") {
6619
+ grandTotalRow = row;
6620
+ continue;
6621
+ }
6622
+ if (row.rowType === "period_combined") {
6623
+ periodRows.set(row.periodKey, row);
6624
+ continue;
6625
+ }
6626
+ if (!periodRows.has(row.periodKey)) {
6627
+ periodRows.set(row.periodKey, row);
6628
+ }
6629
+ }
6630
+ const sortedPeriodKeys = [...periodRows.keys()].sort(compareByCodePoint);
6631
+ const periods = sortedPeriodKeys.map((periodKey) => {
6632
+ const row = periodRows.get(periodKey);
6633
+ if (!row) {
6634
+ throw new Error(`Missing baseline row for period ${periodKey}`);
6635
+ }
6636
+ return {
6637
+ periodKey,
6638
+ inputTokens: row.inputTokens,
6639
+ outputTokens: row.outputTokens,
6640
+ reasoningTokens: row.reasoningTokens,
6641
+ cacheReadTokens: row.cacheReadTokens,
6642
+ cacheWriteTokens: row.cacheWriteTokens,
6643
+ totalTokens: row.totalTokens,
6644
+ baselineCostUsd: row.costUsd,
6645
+ baselineCostIncomplete: row.costIncomplete === true
6646
+ };
6647
+ });
6648
+ const allRow = grandTotalRow;
6649
+ if (allRow) {
6650
+ periods.push({
6651
+ periodKey: "ALL",
6652
+ inputTokens: allRow.inputTokens,
6653
+ outputTokens: allRow.outputTokens,
6654
+ reasoningTokens: allRow.reasoningTokens,
6655
+ cacheReadTokens: allRow.cacheReadTokens,
6656
+ cacheWriteTokens: allRow.cacheWriteTokens,
6657
+ totalTokens: allRow.totalTokens,
6658
+ baselineCostUsd: allRow.costUsd,
6659
+ baselineCostIncomplete: allRow.costIncomplete === true
6660
+ });
6661
+ } else {
6662
+ periods.push({
6663
+ periodKey: "ALL",
6664
+ inputTokens: 0,
6665
+ outputTokens: 0,
6666
+ reasoningTokens: 0,
6667
+ cacheReadTokens: 0,
6668
+ cacheWriteTokens: 0,
6669
+ totalTokens: 0,
6670
+ baselineCostUsd: 0,
6671
+ baselineCostIncomplete: false
6672
+ });
6673
+ }
6674
+ return periods;
6675
+ }
6676
+ function buildWarning(periodKeys) {
6677
+ if (periodKeys.length === 0) {
6678
+ return void 0;
6679
+ }
6680
+ const sortedPeriodKeys = [...new Set(periodKeys)].sort(compareByCodePoint);
6681
+ return `Baseline cost exists for zero-token periods (${sortedPeriodKeys.join(", ")}); savings were omitted.`;
6682
+ }
6683
+ function buildCounterfactualRows(input) {
6684
+ const baselinePeriods = resolveBaselinePeriods(input.usageRows);
6685
+ const allPeriod = baselinePeriods.find((period) => period.periodKey === "ALL");
6686
+ if (!allPeriod) {
6687
+ throw new Error("Missing ALL baseline totals");
6688
+ }
6689
+ const allPeriodEvaluations = input.candidateModels.map(
6690
+ (candidateModel) => evaluateCandidateForPeriod(allPeriod, input.provider, candidateModel, input.pricingSource)
6691
+ );
6692
+ const rankedCandidates = allPeriodEvaluations.map(({ candidateRow }) => ({
6693
+ candidateModel: candidateRow.candidateModel,
6694
+ hypotheticalCostUsd: candidateRow.hypotheticalCostUsd
6695
+ })).sort(compareCandidateRank).map((candidate) => candidate.candidateModel);
6696
+ const selectedCandidates = input.top === void 0 ? rankedCandidates : rankedCandidates.slice(0, input.top);
6697
+ const allEvaluationByCandidate = new Map(
6698
+ allPeriodEvaluations.map((evaluation) => [evaluation.candidateRow.candidateModel, evaluation])
6699
+ );
6700
+ const candidatesWithMissingPricing = input.candidateModels.filter(
6701
+ (candidateModel) => allEvaluationByCandidate.get(candidateModel)?.missingPricing === true
6702
+ ).sort(compareByCodePoint);
6703
+ const warningPeriods = [];
6704
+ const rows = [];
6705
+ for (const period of baselinePeriods) {
6706
+ rows.push(toBaselineRow(period, input.provider));
6707
+ for (const candidateModel of selectedCandidates) {
6708
+ const resolvedEvaluation = period.periodKey === "ALL" ? allEvaluationByCandidate.get(candidateModel) : evaluateCandidateForPeriod(period, input.provider, candidateModel, input.pricingSource);
6709
+ if (!resolvedEvaluation) {
6710
+ continue;
6711
+ }
6712
+ if (resolvedEvaluation.hasBaselineTokenMismatch) {
6713
+ warningPeriods.push(period.periodKey);
6714
+ }
6715
+ rows.push(resolvedEvaluation.candidateRow);
6716
+ }
6717
+ }
6718
+ return {
6719
+ rows,
6720
+ candidatesWithMissingPricing,
6721
+ baselineCostIncomplete: allPeriod.baselineCostIncomplete,
6722
+ warning: buildWarning(warningPeriods)
6723
+ };
6724
+ }
6725
+
6726
+ // src/cli/build-optimize-data.ts
6727
+ function resolveOptimizeProvider(providers, providerFilter) {
6728
+ const distinctProviders = [...providers].sort(compareByCodePoint);
6729
+ const normalizedProviderFilter = normalizeProviderFilter(providerFilter);
6730
+ if (distinctProviders.length > 1) {
6731
+ if (normalizedProviderFilter) {
6732
+ const matchingProviders = distinctProviders.filter(
6733
+ (provider) => provider.includes(normalizedProviderFilter)
6734
+ );
6735
+ if (matchingProviders.includes(normalizedProviderFilter)) {
6736
+ return normalizedProviderFilter;
6737
+ }
6738
+ if (matchingProviders.length === 1) {
6739
+ return matchingProviders[0];
6740
+ }
6741
+ if (matchingProviders.length === 0) {
6742
+ throw new Error(
6743
+ `Optimize --provider "${normalizedProviderFilter}" matched no providers. Available providers: ${distinctProviders.join(", ")}.`
6744
+ );
6745
+ }
6746
+ if (matchingProviders.length > 1) {
6747
+ throw new Error(
6748
+ `Optimize matched multiple providers for --provider "${normalizedProviderFilter}": ${matchingProviders.join(", ")}. Supply a more specific --provider value.`
6749
+ );
6750
+ }
6751
+ }
6752
+ throw new Error(
6753
+ `Optimize requires a single provider; found providers: ${distinctProviders.join(", ")}. Narrow with --provider.`
6754
+ );
6755
+ }
6756
+ if (distinctProviders.length === 1) {
6757
+ return distinctProviders[0];
6758
+ }
6759
+ return normalizedProviderFilter ?? "unknown";
6760
+ }
6761
+ async function buildOptimizeData(granularity, options, deps = {}) {
6762
+ const candidateModels = normalizeCandidateModels(options.candidateModel);
6763
+ const top = parseTopOption(options.top);
6764
+ const dataset = await buildUsageEventDataset(options, deps);
6765
+ const detectedProviders = new Set(
6766
+ dataset.filteredEvents.map((event) => normalizeProviderFilter(event.provider)).filter((provider2) => provider2 !== void 0)
6767
+ );
6768
+ const provider = resolveOptimizeProvider(
6769
+ detectedProviders,
6770
+ dataset.normalizedInputs.providerFilter
6771
+ );
6772
+ const { pricedEvents, pricingOrigin, pricingWarning, pricingSource } = await applyPricingToUsageEventDataset(dataset, deps, "force");
6773
+ const usageRows = aggregateUsage(pricedEvents, {
6774
+ granularity,
6775
+ timezone: dataset.normalizedInputs.timezone,
6776
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
6777
+ });
6778
+ const counterfactual = buildCounterfactualRows({
6779
+ usageRows,
6780
+ provider,
6781
+ candidateModels,
6782
+ pricingSource,
6783
+ top
6784
+ });
6785
+ const usageDiagnostics = buildUsageDiagnostics({
6786
+ adaptersToParse: dataset.adaptersToParse,
6787
+ successfulParseResults: dataset.successfulParseResults,
6788
+ sourceFailures: dataset.sourceFailures,
6789
+ pricingOrigin,
6790
+ pricingWarning,
6791
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
6792
+ timezone: dataset.normalizedInputs.timezone
6793
+ });
6794
+ return {
6795
+ rows: counterfactual.rows,
6796
+ diagnostics: {
6797
+ usage: usageDiagnostics,
6798
+ provider,
6799
+ baselineCostIncomplete: counterfactual.baselineCostIncomplete,
6800
+ candidatesWithMissingPricing: counterfactual.candidatesWithMissingPricing,
6801
+ warning: counterfactual.warning
6802
+ }
6803
+ };
6804
+ }
6805
+
6806
+ // src/cli/run-optimize-report.ts
6807
+ function validateOutputFormatOptions2(options) {
6808
+ if (options.markdown && options.json) {
6809
+ throw new Error("Choose either --markdown or --json, not both");
6810
+ }
6811
+ }
6812
+ function resolveReportFormat2(options) {
6813
+ if (options.json) {
6814
+ return "json";
6815
+ }
6816
+ if (options.markdown) {
6817
+ return "markdown";
6818
+ }
6819
+ return "terminal";
6820
+ }
6821
+ async function prepareOptimizeReport(granularity, options) {
6822
+ validateOutputFormatOptions2(options);
6823
+ const optimizeData = await buildOptimizeData(granularity, options);
6824
+ const format = resolveReportFormat2(options);
6825
+ return {
6826
+ format,
6827
+ diagnostics: optimizeData.diagnostics,
6828
+ candidateCount: optimizeData.rows.filter(
6829
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6830
+ ).length,
6831
+ output: renderOptimizeReport(optimizeData, format, {
6832
+ granularity
6833
+ })
6834
+ };
6835
+ }
6836
+ async function runOptimizeReport(granularity, options) {
6837
+ const preparedReport = await prepareOptimizeReport(granularity, options);
6838
+ emitDiagnostics(preparedReport.diagnostics.usage, logger);
6839
+ emitEnvVarOverrides(preparedReport.diagnostics.usage.activeEnvOverrides, logger);
6840
+ logger.info(
6841
+ `Optimize provider scope: ${preparedReport.diagnostics.provider}; candidate(s): ${preparedReport.candidateCount}`
6842
+ );
6843
+ if (preparedReport.diagnostics.candidatesWithMissingPricing.length > 0) {
6844
+ logger.warn(
6845
+ `Missing pricing for candidate model(s): ${preparedReport.diagnostics.candidatesWithMissingPricing.join(", ")}`
6846
+ );
6847
+ }
6848
+ if (preparedReport.diagnostics.warning) {
6849
+ logger.warn(preparedReport.diagnostics.warning);
6850
+ }
6851
+ if (preparedReport.format === "terminal") {
6852
+ warnIfTerminalTableOverflows(preparedReport.output, (message) => {
6853
+ logger.warn(message);
6854
+ });
6855
+ }
6856
+ console.log(preparedReport.output);
6857
+ }
6858
+
6859
+ // src/render/markdown-table.ts
6860
+ import { markdownTable as markdownTable3 } from "markdown-table";
6861
+ var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6862
+ function toMarkdownSafeCell3(value) {
6863
+ return value.replace(/\r?\n/gu, "<br>");
6864
+ }
6092
6865
  function renderMarkdownTable(rows, options = {}) {
6093
6866
  const tableLayout = options.tableLayout ?? "compact";
6094
6867
  const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
6095
- (row) => row.map((cell) => toMarkdownSafeCell2(cell))
6868
+ (row) => row.map((cell) => toMarkdownSafeCell3(cell))
6096
6869
  );
6097
6870
  const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
6098
- return markdownTable2(tableRows, {
6871
+ return markdownTable3(tableRows, {
6099
6872
  align: alignment
6100
6873
  });
6101
6874
  }
6102
6875
 
6103
6876
  // src/render/render-usage-report.ts
6104
- function getReportTitle2(granularity) {
6877
+ function getReportTitle3(granularity) {
6105
6878
  switch (granularity) {
6106
6879
  case "daily":
6107
6880
  return "Daily Token Usage Report";
@@ -6117,7 +6890,7 @@ function renderTerminalUsageReport(usageData, options) {
6117
6890
  const tableLayout = options.tableLayout ?? "compact";
6118
6891
  outputLines.push(
6119
6892
  renderReportHeader({
6120
- title: getReportTitle2(options.granularity),
6893
+ title: getReportTitle3(options.granularity),
6121
6894
  useColor
6122
6895
  })
6123
6896
  );
@@ -6138,12 +6911,12 @@ function renderUsageReport(usageData, format, options) {
6138
6911
  }
6139
6912
 
6140
6913
  // src/cli/run-usage-report.ts
6141
- function validateOutputFormatOptions2(options) {
6914
+ function validateOutputFormatOptions3(options) {
6142
6915
  if (options.markdown && options.json) {
6143
6916
  throw new Error("Choose either --markdown or --json, not both");
6144
6917
  }
6145
6918
  }
6146
- function resolveReportFormat2(options) {
6919
+ function resolveReportFormat3(options) {
6147
6920
  if (options.json) {
6148
6921
  return "json";
6149
6922
  }
@@ -6156,9 +6929,9 @@ function resolveTableLayout(options) {
6156
6929
  return options.perModelColumns ? "per_model_columns" : "compact";
6157
6930
  }
6158
6931
  async function prepareUsageReport(granularity, options) {
6159
- validateOutputFormatOptions2(options);
6932
+ validateOutputFormatOptions3(options);
6160
6933
  const usageData = await buildUsageData(granularity, options);
6161
- const format = resolveReportFormat2(options);
6934
+ const format = resolveReportFormat3(options);
6162
6935
  return {
6163
6936
  format,
6164
6937
  diagnostics: usageData.diagnostics,
@@ -6206,7 +6979,10 @@ function addSharedOptions(command, options = {}) {
6206
6979
  `Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
6207
6980
  collectRepeatedOption,
6208
6981
  []
6209
- ).option("--since <YYYY-MM-DD>", "Inclusive start date filter").option("--until <YYYY-MM-DD>", "Inclusive end date filter").option("--timezone <iana>", "Timezone for bucketing", defaultTimezone).option("--provider <name>", "Provider filter (substring match, optional)").option(
6982
+ ).option("--since <YYYY-MM-DD>", "Inclusive start date filter").option("--until <YYYY-MM-DD>", "Inclusive end date filter").option("--timezone <iana>", "Timezone for bucketing", defaultTimezone).option(
6983
+ "--provider <name>",
6984
+ "Billing-provider filter (substring match, optional; e.g. openai, anthropic, google)"
6985
+ ).option(
6210
6986
  "--model <name>",
6211
6987
  "Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
6212
6988
  collectRepeatedOption,
@@ -6254,6 +7030,18 @@ function createEfficiencyCommand() {
6254
7030
  });
6255
7031
  return command;
6256
7032
  }
7033
+ function createOptimizeCommand() {
7034
+ const command = new Command("optimize");
7035
+ addSharedOptions(command, { includePerModelColumns: false }).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
7036
+ "--candidate-model <name>",
7037
+ "Candidate model for counterfactual pricing (repeatable or comma-separated)",
7038
+ collectRepeatedOption,
7039
+ []
7040
+ ).option("--top <n>", "Show only the top N cheapest candidates (positive integer)").description("Show counterfactual pricing report for candidate model(s)").action(async (granularity, options) => {
7041
+ await runOptimizeReport(granularity, options);
7042
+ });
7043
+ return command;
7044
+ }
6257
7045
  function rootDescription() {
6258
7046
  const supportedSourceIds = getSupportedSourceIds();
6259
7047
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
@@ -6273,12 +7061,13 @@ function rootDescription() {
6273
7061
  " $ llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
6274
7062
  " $ llm-usage daily --pi-dir /tmp/pi-sessions --gemini-dir /tmp/.gemini --droid-dir /tmp/droid-sessions",
6275
7063
  " $ llm-usage efficiency weekly --repo-dir /path/to/repo --json",
7064
+ " $ llm-usage optimize monthly --provider openai --candidate-model gpt-4.1 --candidate-model gpt-5-codex --json",
6276
7065
  " $ npx --yes llm-usage-metrics@latest daily"
6277
7066
  ].join("\n");
6278
7067
  }
6279
7068
  function createCli(options = {}) {
6280
7069
  const program = new Command();
6281
- program.name("llm-usage").description(rootDescription()).version(options.version ?? "0.0.0").showHelpAfterError().addCommand(createCommand("daily")).addCommand(createCommand("weekly")).addCommand(createCommand("monthly")).addCommand(createEfficiencyCommand());
7070
+ program.name("llm-usage").description(rootDescription()).version(options.version ?? "0.0.0").showHelpAfterError().addCommand(createCommand("daily")).addCommand(createCommand("weekly")).addCommand(createCommand("monthly")).addCommand(createEfficiencyCommand()).addCommand(createOptimizeCommand());
6282
7071
  return program;
6283
7072
  }
6284
7073