llm-usage-metrics 0.3.6 → 0.4.0

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,
@@ -974,6 +1014,13 @@ function hasUsageSignal(usage) {
974
1014
  function deriveDeltaUsage(info, previousTotalUsage) {
975
1015
  const totalUsage = toUsage(info.total_token_usage);
976
1016
  const lastUsage = toUsage(info.last_token_usage);
1017
+ if (totalUsage && previousTotalUsage) {
1018
+ const deltaFromTotals = subtractUsage(totalUsage, previousTotalUsage);
1019
+ if (hasUsageSignal(deltaFromTotals)) {
1020
+ return { deltaUsage: deltaFromTotals, latestTotalUsage: totalUsage };
1021
+ }
1022
+ return { latestTotalUsage: totalUsage };
1023
+ }
977
1024
  if (lastUsage) {
978
1025
  return { deltaUsage: lastUsage, latestTotalUsage: totalUsage };
979
1026
  }
@@ -3264,6 +3311,42 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
3264
3311
  };
3265
3312
  }
3266
3313
 
3314
+ // src/cli/build-usage-data-diagnostics.ts
3315
+ function buildUsageDiagnostics(params) {
3316
+ const parseResultBySource = new Map(
3317
+ params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
3318
+ );
3319
+ const sessionStats = params.adaptersToParse.map((adapter) => {
3320
+ const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
3321
+ return {
3322
+ source: adapter.id,
3323
+ filesFound: parseResult?.filesFound ?? 0,
3324
+ eventsParsed: parseResult?.events.length ?? 0
3325
+ };
3326
+ });
3327
+ const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
3328
+ source: result.source,
3329
+ skippedRows: result.skippedRows,
3330
+ reasons: result.skippedRowReasons
3331
+ }));
3332
+ return {
3333
+ sessionStats,
3334
+ sourceFailures: params.sourceFailures,
3335
+ skippedRows,
3336
+ pricingOrigin: params.pricingOrigin,
3337
+ pricingWarning: params.pricingWarning,
3338
+ activeEnvOverrides: params.activeEnvOverrides,
3339
+ timezone: params.timezone
3340
+ };
3341
+ }
3342
+ function assembleUsageDataResult(events, rows, diagnostics) {
3343
+ return {
3344
+ events,
3345
+ rows,
3346
+ diagnostics
3347
+ };
3348
+ }
3349
+
3267
3350
  // src/config/env-var-display.ts
3268
3351
  var ENV_VARS_TO_DISPLAY = [
3269
3352
  { name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
@@ -3304,42 +3387,6 @@ function formatEnvVarOverrides(overrides) {
3304
3387
  return lines;
3305
3388
  }
3306
3389
 
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
3390
  // src/cli/build-usage-data-inputs.ts
3344
3391
  function validateDateInput(value, flagName) {
3345
3392
  if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
@@ -3359,11 +3406,7 @@ function validateTimezone(timezone) {
3359
3406
  }
3360
3407
  }
3361
3408
  function normalizeProviderFilter(provider) {
3362
- if (!provider) {
3363
- return void 0;
3364
- }
3365
- const normalized = provider.trim().toLowerCase();
3366
- return normalized || void 0;
3409
+ return normalizeProviderToBillingEntity(provider);
3367
3410
  }
3368
3411
  function normalizeSourceFilter(source) {
3369
3412
  if (!source || Array.isArray(source) && source.length === 0) {
@@ -3540,7 +3583,7 @@ function normalizeSkippedRowReasons(value) {
3540
3583
  // src/cli/parse-file-cache.ts
3541
3584
  import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
3542
3585
  import path11 from "path";
3543
- var PARSE_FILE_CACHE_VERSION = 2;
3586
+ var PARSE_FILE_CACHE_VERSION = 4;
3544
3587
  var CACHE_KEY_SEPARATOR = "\0";
3545
3588
  function createCacheKey(source, filePath) {
3546
3589
  return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
@@ -3599,7 +3642,9 @@ function normalizeCachedUsageEvent(value) {
3599
3642
  if (inputTokens === void 0 || outputTokens === void 0 || reasoningTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0 || totalTokens === void 0) {
3600
3643
  return void 0;
3601
3644
  }
3602
- const provider = typeof record.provider === "string" ? record.provider.trim() : "";
3645
+ const provider = normalizeProviderToBillingEntity(
3646
+ typeof record.provider === "string" ? record.provider : void 0
3647
+ );
3603
3648
  const model = typeof record.model === "string" ? record.model.trim().toLowerCase() : "";
3604
3649
  const costUsd = toNonNegativeNumber2(record.costUsd);
3605
3650
  if (costMode === "explicit" && costUsd === void 0) {
@@ -3610,7 +3655,7 @@ function normalizeCachedUsageEvent(value) {
3610
3655
  sessionId,
3611
3656
  timestamp,
3612
3657
  repoRoot: repoRoot || void 0,
3613
- provider: provider || void 0,
3658
+ provider,
3614
3659
  model: model || void 0,
3615
3660
  inputTokens,
3616
3661
  outputTokens,
@@ -4745,9 +4790,20 @@ function shouldLoadPricingSource(events) {
4745
4790
  }
4746
4791
  return events.some((event) => eventNeedsPricingLookup(event));
4747
4792
  }
4748
- async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource) {
4793
+ function hasAnyBillableTokenBuckets(events) {
4794
+ return events.some(
4795
+ (event) => event.inputTokens > 0 || event.outputTokens > 0 || event.reasoningTokens > 0 || event.cacheReadTokens > 0 || event.cacheWriteTokens > 0
4796
+ );
4797
+ }
4798
+ function shouldLoadPricingSourceForMode(events, pricingLoadMode) {
4799
+ if (pricingLoadMode === "force") {
4800
+ return hasAnyBillableTokenBuckets(events);
4801
+ }
4802
+ return shouldLoadPricingSource(events);
4803
+ }
4804
+ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = getPricingFetcherRuntimeConfig(), loadPricingSource = resolvePricingSource, pricingLoadMode = "auto") {
4749
4805
  let pricingOrigin = "none";
4750
- if (!shouldLoadPricingSource(events)) {
4806
+ if (!shouldLoadPricingSourceForMode(events, pricingLoadMode)) {
4751
4807
  return {
4752
4808
  pricedEvents: events,
4753
4809
  pricingOrigin
@@ -4771,11 +4827,12 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
4771
4827
  pricingOrigin = pricingResult.origin;
4772
4828
  return {
4773
4829
  pricedEvents: applyPricingToEvents(events, pricingResult.source),
4774
- pricingOrigin
4830
+ pricingOrigin,
4831
+ pricingSource: pricingResult.source
4775
4832
  };
4776
4833
  }
4777
4834
 
4778
- // src/cli/build-usage-data.ts
4835
+ // src/cli/build-usage-event-dataset.ts
4779
4836
  function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4780
4837
  if (options.pricingUrl === normalizedPricingUrl) {
4781
4838
  return options;
@@ -4785,13 +4842,11 @@ function withNormalizedPricingUrl(options, normalizedPricingUrl) {
4785
4842
  pricingUrl: normalizedPricingUrl
4786
4843
  };
4787
4844
  }
4788
- async function buildUsageData(granularity, options, deps = {}) {
4845
+ async function buildUsageEventDataset(options, deps = {}) {
4789
4846
  const normalizedInputs = normalizeBuildUsageInputs(options);
4790
4847
  const readParsingRuntimeConfig = deps.getParsingRuntimeConfig ?? getParsingRuntimeConfig;
4791
4848
  const readPricingRuntimeConfig = deps.getPricingFetcherRuntimeConfig ?? getPricingFetcherRuntimeConfig;
4792
4849
  const makeAdapters = deps.createAdapters ?? createDefaultAdapters;
4793
- const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4794
- const readEnvVarOverrides = deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides;
4795
4850
  const parsingRuntimeConfig = readParsingRuntimeConfig();
4796
4851
  const pricingRuntimeConfig = readPricingRuntimeConfig();
4797
4852
  const adapters = makeAdapters(options);
@@ -4816,26 +4871,53 @@ async function buildUsageData(granularity, options, deps = {}) {
4816
4871
  providerFilter: normalizedInputs.providerFilter,
4817
4872
  modelFilter: normalizedInputs.modelFilter
4818
4873
  });
4819
- const pricingOptions = withNormalizedPricingUrl(options, normalizedInputs.pricingUrl);
4820
- const { pricedEvents, pricingOrigin, pricingWarning } = await resolveAndApplyPricingToEvents(
4874
+ return {
4875
+ options,
4876
+ normalizedInputs,
4877
+ adaptersToParse,
4878
+ successfulParseResults,
4879
+ sourceFailures,
4821
4880
  filteredEvents,
4822
- pricingOptions,
4823
4881
  pricingRuntimeConfig,
4824
- loadPricingSource
4882
+ readEnvVarOverrides: deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides
4883
+ };
4884
+ }
4885
+ async function applyPricingToUsageEventDataset(dataset, deps = {}, pricingLoadMode = "auto") {
4886
+ const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
4887
+ const pricingOptions = withNormalizedPricingUrl(
4888
+ dataset.options,
4889
+ dataset.normalizedInputs.pricingUrl
4890
+ );
4891
+ return resolveAndApplyPricingToEvents(
4892
+ dataset.filteredEvents,
4893
+ pricingOptions,
4894
+ dataset.pricingRuntimeConfig,
4895
+ loadPricingSource,
4896
+ pricingLoadMode
4897
+ );
4898
+ }
4899
+
4900
+ // src/cli/build-usage-data.ts
4901
+ async function buildUsageData(granularity, options, deps = {}) {
4902
+ const dataset = await buildUsageEventDataset(options, deps);
4903
+ const { pricedEvents, pricingOrigin, pricingWarning } = await applyPricingToUsageEventDataset(
4904
+ dataset,
4905
+ deps,
4906
+ "auto"
4825
4907
  );
4826
4908
  const rows = aggregateUsage(pricedEvents, {
4827
4909
  granularity,
4828
- timezone: normalizedInputs.timezone,
4829
- sourceOrder: adaptersToParse.map((adapter) => adapter.id)
4910
+ timezone: dataset.normalizedInputs.timezone,
4911
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
4830
4912
  });
4831
4913
  const diagnostics = buildUsageDiagnostics({
4832
- adaptersToParse,
4833
- successfulParseResults,
4834
- sourceFailures,
4914
+ adaptersToParse: dataset.adaptersToParse,
4915
+ successfulParseResults: dataset.successfulParseResults,
4916
+ sourceFailures: dataset.sourceFailures,
4835
4917
  pricingOrigin,
4836
4918
  pricingWarning,
4837
- activeEnvOverrides: readEnvVarOverrides(),
4838
- timezone: normalizedInputs.timezone
4919
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
4920
+ timezone: dataset.normalizedInputs.timezone
4839
4921
  });
4840
4922
  return assembleUsageDataResult(pricedEvents, rows, diagnostics);
4841
4923
  }
@@ -5252,6 +5334,7 @@ function warnIfTerminalTableOverflows(reportOutput, warn, stdoutState = process.
5252
5334
 
5253
5335
  // src/render/render-efficiency-report.ts
5254
5336
  import { markdownTable } from "markdown-table";
5337
+ import pc5 from "picocolors";
5255
5338
 
5256
5339
  // src/render/report-header.ts
5257
5340
  import pc2 from "picocolors";
@@ -5397,8 +5480,8 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
5397
5480
  var rowTypeStylePolicies = {
5398
5481
  period_source: (cells, palette) => {
5399
5482
  const styledCells = [...cells];
5400
- const costColumnIndex = styledCells.length - 1;
5401
- styledCells[costColumnIndex] = styleCellLines(styledCells[costColumnIndex], palette.yellow);
5483
+ const costColumnIndex2 = styledCells.length - 1;
5484
+ styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
5402
5485
  return styledCells;
5403
5486
  },
5404
5487
  period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
@@ -5843,6 +5926,14 @@ function renderTerminalTable(rows, options = {}) {
5843
5926
  // src/render/render-efficiency-report.ts
5844
5927
  var periodColumnIndex = 0;
5845
5928
  var minimumEfficiencyColumnWidth = 1;
5929
+ var commitsColumnIndex = 1;
5930
+ var linesAddedColumnIndex = 2;
5931
+ var linesDeletedColumnIndex = 3;
5932
+ var linesChangedColumnIndex = 4;
5933
+ var costColumnIndex = 11;
5934
+ var usdPerCommitColumnIndex = 12;
5935
+ var usdPer1kLinesChangedColumnIndex = 13;
5936
+ var commitsPerUsdColumnIndex = 16;
5846
5937
  function getReportTitle(granularity) {
5847
5938
  switch (granularity) {
5848
5939
  case "daily":
@@ -5981,9 +6072,67 @@ function fitTableCellsToTerminal(headerCells, bodyRows) {
5981
6072
  widths: constrainedWidths
5982
6073
  };
5983
6074
  }
5984
- function renderTerminalEfficiencyTable(rows) {
6075
+ function styleDeltaCell(value, formattedValue, options) {
6076
+ if (!options.useColor) {
6077
+ return formattedValue;
6078
+ }
6079
+ if (value > 0) {
6080
+ return pc5.green(formattedValue);
6081
+ }
6082
+ if (value < 0) {
6083
+ return pc5.red(formattedValue);
6084
+ }
6085
+ return pc5.dim(formattedValue);
6086
+ }
6087
+ function styleEfficiencyTerminalRows(rows, bodyRows, options) {
6088
+ return bodyRows.map((cells, rowIndex) => {
6089
+ if (!options.useColor) {
6090
+ return [...cells];
6091
+ }
6092
+ const row = rows[rowIndex];
6093
+ const styledCells = [...cells];
6094
+ const periodCell = styledCells[periodColumnIndex];
6095
+ styledCells[periodColumnIndex] = row.rowType === "grand_total" ? pc5.bold(pc5.cyan(periodCell)) : pc5.bold(periodCell);
6096
+ styledCells[commitsColumnIndex] = pc5.bold(styledCells[commitsColumnIndex]);
6097
+ styledCells[linesAddedColumnIndex] = styleDeltaCell(
6098
+ row.linesAdded,
6099
+ styledCells[linesAddedColumnIndex],
6100
+ options
6101
+ );
6102
+ styledCells[linesDeletedColumnIndex] = styleDeltaCell(
6103
+ row.linesDeleted * -1,
6104
+ styledCells[linesDeletedColumnIndex],
6105
+ options
6106
+ );
6107
+ styledCells[linesChangedColumnIndex] = styleDeltaCell(
6108
+ row.linesChanged,
6109
+ styledCells[linesChangedColumnIndex],
6110
+ options
6111
+ );
6112
+ const costValue = row.costUsd;
6113
+ if (costValue !== void 0 && costValue > 0) {
6114
+ styledCells[costColumnIndex] = pc5.yellow(styledCells[costColumnIndex]);
6115
+ }
6116
+ const usdPerCommitValue = row.usdPerCommit;
6117
+ if (usdPerCommitValue !== void 0 && usdPerCommitValue > 0) {
6118
+ styledCells[usdPerCommitColumnIndex] = pc5.yellow(styledCells[usdPerCommitColumnIndex]);
6119
+ }
6120
+ const usdPer1kLinesChangedValue = row.usdPer1kLinesChanged;
6121
+ if (usdPer1kLinesChangedValue !== void 0 && usdPer1kLinesChangedValue > 0) {
6122
+ styledCells[usdPer1kLinesChangedColumnIndex] = pc5.yellow(
6123
+ styledCells[usdPer1kLinesChangedColumnIndex]
6124
+ );
6125
+ }
6126
+ const commitsPerUsdValue = row.commitsPerUsd;
6127
+ if (commitsPerUsdValue !== void 0 && commitsPerUsdValue > 0) {
6128
+ styledCells[commitsPerUsdColumnIndex] = pc5.green(styledCells[commitsPerUsdColumnIndex]);
6129
+ }
6130
+ return styledCells;
6131
+ });
6132
+ }
6133
+ function renderTerminalEfficiencyTable(rows, options) {
5985
6134
  const headerCells = Array.from(efficiencyTableHeaders);
5986
- const bodyRows = toEfficiencyTableCells(rows);
6135
+ const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
5987
6136
  const tableSortRows = rows.map((row) => toTableSortRow(row));
5988
6137
  const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
5989
6138
  return renderUnicodeTable({
@@ -6020,7 +6169,7 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
6020
6169
  })
6021
6170
  );
6022
6171
  outputLines.push("");
6023
- outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows));
6172
+ outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
6024
6173
  return outputLines.join("\n");
6025
6174
  }
6026
6175
  function renderEfficiencyReport(efficiencyData, format, options) {
@@ -6083,25 +6232,656 @@ async function runEfficiencyReport(granularity, options) {
6083
6232
  console.log(preparedReport.output);
6084
6233
  }
6085
6234
 
6086
- // src/render/markdown-table.ts
6235
+ // src/render/render-optimize-report.ts
6087
6236
  import { markdownTable as markdownTable2 } from "markdown-table";
6088
- var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6237
+ import pc6 from "picocolors";
6238
+ var optimizeTableHeadersWithNotes = [
6239
+ "Period",
6240
+ "Candidate",
6241
+ "Hypothetical Cost",
6242
+ "Baseline Cost",
6243
+ "Savings",
6244
+ "Savings %",
6245
+ "Notes"
6246
+ ];
6247
+ var optimizeTableHeadersWithoutNotes = [
6248
+ "Period",
6249
+ "Candidate",
6250
+ "Hypothetical Cost",
6251
+ "Baseline Cost",
6252
+ "Savings",
6253
+ "Savings %"
6254
+ ];
6255
+ var usdFormatter3 = new Intl.NumberFormat("en-US", {
6256
+ style: "currency",
6257
+ currency: "USD",
6258
+ minimumFractionDigits: 2,
6259
+ maximumFractionDigits: 2
6260
+ });
6261
+ function getReportTitle2(granularity) {
6262
+ switch (granularity) {
6263
+ case "daily":
6264
+ return "Daily Optimize Report";
6265
+ case "weekly":
6266
+ return "Weekly Optimize Report";
6267
+ case "monthly":
6268
+ return "Monthly Optimize Report";
6269
+ }
6270
+ }
6271
+ function formatUsd3(value, options = {}) {
6272
+ if (value === void 0) {
6273
+ return "-";
6274
+ }
6275
+ const formatted = usdFormatter3.format(value);
6276
+ return options.approximate ? `~${formatted}` : formatted;
6277
+ }
6278
+ function formatPercent(value) {
6279
+ if (value === void 0) {
6280
+ return "-";
6281
+ }
6282
+ return `${(value * 100).toFixed(2)}%`;
6283
+ }
6284
+ function formatNotes(notes) {
6285
+ if (!notes || notes.length === 0) {
6286
+ return "-";
6287
+ }
6288
+ return notes.join(", ");
6289
+ }
6290
+ function styleCandidateCell(candidateValue, rowType, useColor) {
6291
+ if (!useColor) {
6292
+ return candidateValue;
6293
+ }
6294
+ if (rowType === "baseline") {
6295
+ return pc6.bold(pc6.cyan(candidateValue));
6296
+ }
6297
+ return pc6.bold(candidateValue);
6298
+ }
6299
+ function styleDeltaCell2(value, formattedValue, useColor) {
6300
+ if (!useColor || value === void 0) {
6301
+ return formattedValue;
6302
+ }
6303
+ if (value > 0) {
6304
+ return pc6.green(formattedValue);
6305
+ }
6306
+ if (value < 0) {
6307
+ return pc6.red(formattedValue);
6308
+ }
6309
+ return pc6.dim(formattedValue);
6310
+ }
6311
+ function styleNotesCell(notes, formattedNotes, useColor) {
6312
+ if (!useColor || !notes || notes.length === 0) {
6313
+ return formattedNotes;
6314
+ }
6315
+ return pc6.yellow(formattedNotes);
6316
+ }
6317
+ function formatAbsoluteUsd(value) {
6318
+ return usdFormatter3.format(Math.abs(value));
6319
+ }
6320
+ function resolveTerminalContextLines(optimizeData, options) {
6321
+ const allBaselineRow = optimizeData.rows.find(
6322
+ (row) => row.rowType === "baseline" && row.periodKey === "ALL"
6323
+ );
6324
+ const allCandidateRows = optimizeData.rows.filter(
6325
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6326
+ );
6327
+ const lines = [];
6328
+ const providerLine = `Provider scope: ${optimizeData.diagnostics.provider}`;
6329
+ lines.push(options.useColor ? pc6.cyan(providerLine) : providerLine);
6330
+ if (allBaselineRow) {
6331
+ lines.push(
6332
+ `ALL baseline cost: ${formatUsd3(allBaselineRow.baselineCostUsd, { approximate: allBaselineRow.baselineCostIncomplete })}`
6333
+ );
6334
+ }
6335
+ if (allCandidateRows.length > 0) {
6336
+ const rowsWithSavings = allCandidateRows.filter((row) => row.savingsUsd !== void 0);
6337
+ const bestRow = rowsWithSavings.length > 0 ? rowsWithSavings.reduce(
6338
+ (best, current) => (current.savingsUsd ?? Number.NEGATIVE_INFINITY) > (best.savingsUsd ?? Number.NEGATIVE_INFINITY) ? current : best
6339
+ ) : void 0;
6340
+ if (!bestRow || bestRow.savingsUsd === void 0) {
6341
+ lines.push("ALL best candidate: unavailable (missing baseline or candidate pricing)");
6342
+ } else if (bestRow.savingsUsd > 0) {
6343
+ lines.push(
6344
+ `ALL best candidate: ${bestRow.candidateModel} saves ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6345
+ );
6346
+ } else if (bestRow.savingsUsd < 0) {
6347
+ lines.push(
6348
+ `ALL best candidate: ${bestRow.candidateModel} increases cost by ${formatAbsoluteUsd(bestRow.savingsUsd)} (${formatPercent(bestRow.savingsPct)})`
6349
+ );
6350
+ } else {
6351
+ lines.push(`ALL best candidate: ${bestRow.candidateModel} matches baseline cost`);
6352
+ }
6353
+ }
6354
+ if (optimizeData.diagnostics.candidatesWithMissingPricing.length > 0) {
6355
+ const missingLine = `Missing candidate pricing: ${optimizeData.diagnostics.candidatesWithMissingPricing.join(", ")}`;
6356
+ lines.push(options.useColor ? pc6.yellow(missingLine) : missingLine);
6357
+ }
6358
+ const legendLine = "Savings = Baseline - Hypothetical (positive means cheaper candidate)";
6359
+ lines.push(options.useColor ? pc6.dim(legendLine) : legendLine);
6360
+ return lines;
6361
+ }
6362
+ function toTableCells(optimizeData, options) {
6363
+ const baselineByPeriod = new Map(
6364
+ optimizeData.rows.filter((row) => row.rowType === "baseline").map((row) => [row.periodKey, row])
6365
+ );
6366
+ return optimizeData.rows.map((row) => {
6367
+ const baselineRow = baselineByPeriod.get(row.periodKey);
6368
+ const periodCell = options.useColor && row.periodKey === "ALL" ? pc6.bold(row.periodKey) : row.periodKey;
6369
+ if (row.rowType === "baseline") {
6370
+ const baselineCells = [
6371
+ periodCell,
6372
+ styleCandidateCell("BASELINE", "baseline", options.useColor),
6373
+ "-",
6374
+ formatUsd3(row.baselineCostUsd, { approximate: row.baselineCostIncomplete }),
6375
+ "-",
6376
+ "-"
6377
+ ];
6378
+ return options.includeNotesColumn ? [...baselineCells, "-"] : baselineCells;
6379
+ }
6380
+ const savingsCell = formatUsd3(row.savingsUsd);
6381
+ const savingsPctCell = formatPercent(row.savingsPct);
6382
+ const notesCell = formatNotes(row.notes);
6383
+ const candidateCells = [
6384
+ periodCell,
6385
+ styleCandidateCell(row.candidateModel, "candidate", options.useColor),
6386
+ formatUsd3(row.hypotheticalCostUsd, { approximate: row.hypotheticalCostIncomplete }),
6387
+ formatUsd3(baselineRow?.baselineCostUsd, {
6388
+ approximate: baselineRow?.baselineCostIncomplete === true
6389
+ }),
6390
+ styleDeltaCell2(row.savingsUsd, savingsCell, options.useColor),
6391
+ styleDeltaCell2(row.savingsPct, savingsPctCell, options.useColor)
6392
+ ];
6393
+ return options.includeNotesColumn ? [...candidateCells, styleNotesCell(row.notes, notesCell, options.useColor)] : candidateCells;
6394
+ });
6395
+ }
6089
6396
  function toMarkdownSafeCell2(value) {
6090
6397
  return value.replace(/\r?\n/gu, "<br>");
6091
6398
  }
6399
+ function toSortingUsageRows(optimizeData) {
6400
+ return optimizeData.rows.map((row) => {
6401
+ return {
6402
+ rowType: "period_source",
6403
+ periodKey: row.periodKey,
6404
+ source: "combined",
6405
+ models: [],
6406
+ modelBreakdown: [],
6407
+ inputTokens: row.inputTokens,
6408
+ outputTokens: row.outputTokens,
6409
+ reasoningTokens: row.reasoningTokens,
6410
+ cacheReadTokens: row.cacheReadTokens,
6411
+ cacheWriteTokens: row.cacheWriteTokens,
6412
+ totalTokens: row.totalTokens,
6413
+ costUsd: row.rowType === "baseline" ? row.baselineCostUsd : row.hypotheticalCostUsd,
6414
+ costIncomplete: row.rowType === "baseline" ? row.baselineCostIncomplete : row.hypotheticalCostIncomplete
6415
+ };
6416
+ });
6417
+ }
6418
+ function resolveCandidateColumnWidth(tableCells) {
6419
+ return tableCells.reduce((maxWidth, row) => {
6420
+ const candidateValue = row[1] ?? "";
6421
+ return Math.max(maxWidth, visibleWidth(candidateValue));
6422
+ }, visibleWidth(optimizeTableHeadersWithNotes[1]));
6423
+ }
6424
+ function resolveIncludeNotesColumn(optimizeData) {
6425
+ return optimizeData.rows.some(
6426
+ (row) => row.rowType === "candidate" && row.notes !== void 0 && row.notes.length > 0
6427
+ );
6428
+ }
6429
+ function renderTerminalOptimizeReport(optimizeData, options) {
6430
+ const useColor = options.useColor ?? shouldUseColorByDefault();
6431
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6432
+ const tableCells = toTableCells(optimizeData, { useColor, includeNotesColumn });
6433
+ const candidateColumnWidth = resolveCandidateColumnWidth(tableCells);
6434
+ const contextLines = resolveTerminalContextLines(optimizeData, { useColor });
6435
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6436
+ const outputLines = [];
6437
+ outputLines.push(
6438
+ renderReportHeader({
6439
+ title: getReportTitle2(options.granularity),
6440
+ useColor
6441
+ })
6442
+ );
6443
+ outputLines.push("");
6444
+ outputLines.push(...contextLines);
6445
+ outputLines.push("");
6446
+ outputLines.push(
6447
+ renderUnicodeTable({
6448
+ headerCells,
6449
+ bodyRows: tableCells,
6450
+ measureHeaderCells: headerCells,
6451
+ measureBodyRows: tableCells,
6452
+ usageRows: toSortingUsageRows(optimizeData),
6453
+ tableLayout: "compact",
6454
+ modelsColumnIndex: 1,
6455
+ modelsColumnWidth: candidateColumnWidth
6456
+ })
6457
+ );
6458
+ return outputLines.join("\n");
6459
+ }
6460
+ function renderMarkdownOptimizeReport(optimizeData) {
6461
+ const includeNotesColumn = resolveIncludeNotesColumn(optimizeData);
6462
+ const headerCells = includeNotesColumn ? [...optimizeTableHeadersWithNotes] : [...optimizeTableHeadersWithoutNotes];
6463
+ const bodyRows = toTableCells(optimizeData, {
6464
+ useColor: false,
6465
+ includeNotesColumn
6466
+ }).map((row) => row.map((cell) => toMarkdownSafeCell2(cell)));
6467
+ const tableRows = [headerCells, ...bodyRows];
6468
+ const alignment2 = headerCells.map((_, index) => index <= 1 ? "l" : "r");
6469
+ return markdownTable2(tableRows, { align: alignment2 });
6470
+ }
6471
+ function renderOptimizeReport(optimizeData, format, options) {
6472
+ switch (format) {
6473
+ case "json":
6474
+ return JSON.stringify(optimizeData.rows, null, 2);
6475
+ case "markdown":
6476
+ return renderMarkdownOptimizeReport(optimizeData);
6477
+ case "terminal":
6478
+ return renderTerminalOptimizeReport(optimizeData, options);
6479
+ }
6480
+ }
6481
+
6482
+ // src/optimize/aggregate-counterfactual.ts
6483
+ var USD_PRECISION_SCALE3 = 1e12;
6484
+ function roundUsd(value) {
6485
+ return Math.round(value * USD_PRECISION_SCALE3) / USD_PRECISION_SCALE3;
6486
+ }
6487
+ function hasZeroBillableTokenBuckets(totals) {
6488
+ return totals.inputTokens === 0 && totals.outputTokens === 0 && totals.reasoningTokens === 0 && totals.cacheReadTokens === 0 && totals.cacheWriteTokens === 0;
6489
+ }
6490
+ function parseCandidateModelsRaw(candidateModel) {
6491
+ if (!candidateModel || Array.isArray(candidateModel) && candidateModel.length === 0) {
6492
+ throw new Error("At least one --candidate-model is required");
6493
+ }
6494
+ const normalizedCandidates = (Array.isArray(candidateModel) ? candidateModel : [candidateModel]).flatMap((candidate) => candidate.split(",")).map((candidate) => candidate.trim().toLowerCase()).filter((candidate) => candidate.length > 0);
6495
+ if (normalizedCandidates.length === 0) {
6496
+ throw new Error("--candidate-model must contain at least one non-empty model name");
6497
+ }
6498
+ return [...new Set(normalizedCandidates)];
6499
+ }
6500
+ function normalizeCandidateModels(candidateModel) {
6501
+ return parseCandidateModelsRaw(candidateModel);
6502
+ }
6503
+ function parseTopOption(top) {
6504
+ if (top === void 0) {
6505
+ return void 0;
6506
+ }
6507
+ const normalized = top.trim();
6508
+ const parsed = Number.parseInt(normalized, 10);
6509
+ if (!/^\d+$/u.test(normalized) || Number.isNaN(parsed) || parsed < 1) {
6510
+ throw new Error("--top must be a positive integer");
6511
+ }
6512
+ return parsed;
6513
+ }
6514
+ function toBaselineRow(period, provider) {
6515
+ return {
6516
+ rowType: "baseline",
6517
+ periodKey: period.periodKey,
6518
+ provider,
6519
+ inputTokens: period.inputTokens,
6520
+ outputTokens: period.outputTokens,
6521
+ reasoningTokens: period.reasoningTokens,
6522
+ cacheReadTokens: period.cacheReadTokens,
6523
+ cacheWriteTokens: period.cacheWriteTokens,
6524
+ totalTokens: period.totalTokens,
6525
+ baselineCostUsd: period.baselineCostUsd,
6526
+ baselineCostIncomplete: period.baselineCostIncomplete
6527
+ };
6528
+ }
6529
+ function createSyntheticEvent(period) {
6530
+ return {
6531
+ source: "pi",
6532
+ sessionId: "optimize-period",
6533
+ timestamp: "1970-01-01T00:00:00.000Z",
6534
+ provider: void 0,
6535
+ model: "synthetic",
6536
+ inputTokens: period.inputTokens,
6537
+ outputTokens: period.outputTokens,
6538
+ reasoningTokens: period.reasoningTokens,
6539
+ cacheReadTokens: period.cacheReadTokens,
6540
+ cacheWriteTokens: period.cacheWriteTokens,
6541
+ totalTokens: period.totalTokens,
6542
+ costMode: "estimated",
6543
+ costUsd: void 0
6544
+ };
6545
+ }
6546
+ function withNotes(notes) {
6547
+ if (notes.size === 0) {
6548
+ return void 0;
6549
+ }
6550
+ return [...notes].sort(compareByCodePoint);
6551
+ }
6552
+ function evaluateCandidateForPeriod(period, provider, candidateModel, pricingSource) {
6553
+ const notes = /* @__PURE__ */ new Set();
6554
+ const zeroBillableTokens = hasZeroBillableTokenBuckets(period);
6555
+ const candidateResolvedModel = pricingSource ? pricingSource.resolveModelAlias(candidateModel) : candidateModel;
6556
+ const pricing = pricingSource ? pricingSource.getPricing(candidateResolvedModel) : void 0;
6557
+ let hypotheticalCostUsd;
6558
+ let hypotheticalCostIncomplete = false;
6559
+ if (!pricing) {
6560
+ if (zeroBillableTokens) {
6561
+ hypotheticalCostUsd = 0;
6562
+ } else {
6563
+ hypotheticalCostUsd = void 0;
6564
+ hypotheticalCostIncomplete = true;
6565
+ notes.add("missing_pricing");
6566
+ }
6567
+ } else {
6568
+ hypotheticalCostUsd = roundUsd(
6569
+ calculateEstimatedCostUsd(createSyntheticEvent(period), pricing)
6570
+ );
6571
+ }
6572
+ let savingsUsd;
6573
+ let savingsPct;
6574
+ let hasBaselineTokenMismatch = false;
6575
+ if (period.baselineCostIncomplete || period.baselineCostUsd === void 0) {
6576
+ notes.add("baseline_incomplete");
6577
+ } else if (zeroBillableTokens && period.baselineCostUsd > 0) {
6578
+ notes.add("baseline_tokens_missing");
6579
+ hasBaselineTokenMismatch = true;
6580
+ } else if (hypotheticalCostUsd !== void 0) {
6581
+ savingsUsd = roundUsd(period.baselineCostUsd - hypotheticalCostUsd);
6582
+ savingsPct = period.baselineCostUsd === 0 ? void 0 : savingsUsd / period.baselineCostUsd;
6583
+ }
6584
+ return {
6585
+ candidateRow: {
6586
+ rowType: "candidate",
6587
+ periodKey: period.periodKey,
6588
+ provider,
6589
+ inputTokens: period.inputTokens,
6590
+ outputTokens: period.outputTokens,
6591
+ reasoningTokens: period.reasoningTokens,
6592
+ cacheReadTokens: period.cacheReadTokens,
6593
+ cacheWriteTokens: period.cacheWriteTokens,
6594
+ totalTokens: period.totalTokens,
6595
+ candidateModel,
6596
+ candidateResolvedModel,
6597
+ hypotheticalCostUsd,
6598
+ hypotheticalCostIncomplete,
6599
+ savingsUsd,
6600
+ savingsPct,
6601
+ notes: withNotes(notes)
6602
+ },
6603
+ missingPricing: notes.has("missing_pricing"),
6604
+ hasBaselineTokenMismatch
6605
+ };
6606
+ }
6607
+ function compareCandidateRank(left, right) {
6608
+ if (left.hypotheticalCostUsd === void 0 && right.hypotheticalCostUsd !== void 0) {
6609
+ return 1;
6610
+ }
6611
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd === void 0) {
6612
+ return -1;
6613
+ }
6614
+ if (left.hypotheticalCostUsd !== void 0 && right.hypotheticalCostUsd !== void 0) {
6615
+ if (left.hypotheticalCostUsd !== right.hypotheticalCostUsd) {
6616
+ return left.hypotheticalCostUsd - right.hypotheticalCostUsd;
6617
+ }
6618
+ }
6619
+ return compareByCodePoint(left.candidateModel, right.candidateModel);
6620
+ }
6621
+ function resolveBaselinePeriods(usageRows) {
6622
+ const periodRows = /* @__PURE__ */ new Map();
6623
+ let grandTotalRow;
6624
+ for (const row of usageRows) {
6625
+ if (row.rowType === "grand_total") {
6626
+ grandTotalRow = row;
6627
+ continue;
6628
+ }
6629
+ if (row.rowType === "period_combined") {
6630
+ periodRows.set(row.periodKey, row);
6631
+ continue;
6632
+ }
6633
+ if (!periodRows.has(row.periodKey)) {
6634
+ periodRows.set(row.periodKey, row);
6635
+ }
6636
+ }
6637
+ const sortedPeriodKeys = [...periodRows.keys()].sort(compareByCodePoint);
6638
+ const periods = sortedPeriodKeys.map((periodKey) => {
6639
+ const row = periodRows.get(periodKey);
6640
+ if (!row) {
6641
+ throw new Error(`Missing baseline row for period ${periodKey}`);
6642
+ }
6643
+ return {
6644
+ periodKey,
6645
+ inputTokens: row.inputTokens,
6646
+ outputTokens: row.outputTokens,
6647
+ reasoningTokens: row.reasoningTokens,
6648
+ cacheReadTokens: row.cacheReadTokens,
6649
+ cacheWriteTokens: row.cacheWriteTokens,
6650
+ totalTokens: row.totalTokens,
6651
+ baselineCostUsd: row.costUsd,
6652
+ baselineCostIncomplete: row.costIncomplete === true
6653
+ };
6654
+ });
6655
+ const allRow = grandTotalRow;
6656
+ if (allRow) {
6657
+ periods.push({
6658
+ periodKey: "ALL",
6659
+ inputTokens: allRow.inputTokens,
6660
+ outputTokens: allRow.outputTokens,
6661
+ reasoningTokens: allRow.reasoningTokens,
6662
+ cacheReadTokens: allRow.cacheReadTokens,
6663
+ cacheWriteTokens: allRow.cacheWriteTokens,
6664
+ totalTokens: allRow.totalTokens,
6665
+ baselineCostUsd: allRow.costUsd,
6666
+ baselineCostIncomplete: allRow.costIncomplete === true
6667
+ });
6668
+ } else {
6669
+ periods.push({
6670
+ periodKey: "ALL",
6671
+ inputTokens: 0,
6672
+ outputTokens: 0,
6673
+ reasoningTokens: 0,
6674
+ cacheReadTokens: 0,
6675
+ cacheWriteTokens: 0,
6676
+ totalTokens: 0,
6677
+ baselineCostUsd: 0,
6678
+ baselineCostIncomplete: false
6679
+ });
6680
+ }
6681
+ return periods;
6682
+ }
6683
+ function buildWarning(periodKeys) {
6684
+ if (periodKeys.length === 0) {
6685
+ return void 0;
6686
+ }
6687
+ const sortedPeriodKeys = [...new Set(periodKeys)].sort(compareByCodePoint);
6688
+ return `Baseline cost exists for zero-token periods (${sortedPeriodKeys.join(", ")}); savings were omitted.`;
6689
+ }
6690
+ function buildCounterfactualRows(input) {
6691
+ const baselinePeriods = resolveBaselinePeriods(input.usageRows);
6692
+ const allPeriod = baselinePeriods.find((period) => period.periodKey === "ALL");
6693
+ if (!allPeriod) {
6694
+ throw new Error("Missing ALL baseline totals");
6695
+ }
6696
+ const allPeriodEvaluations = input.candidateModels.map(
6697
+ (candidateModel) => evaluateCandidateForPeriod(allPeriod, input.provider, candidateModel, input.pricingSource)
6698
+ );
6699
+ const rankedCandidates = allPeriodEvaluations.map(({ candidateRow }) => ({
6700
+ candidateModel: candidateRow.candidateModel,
6701
+ hypotheticalCostUsd: candidateRow.hypotheticalCostUsd
6702
+ })).sort(compareCandidateRank).map((candidate) => candidate.candidateModel);
6703
+ const selectedCandidates = input.top === void 0 ? rankedCandidates : rankedCandidates.slice(0, input.top);
6704
+ const allEvaluationByCandidate = new Map(
6705
+ allPeriodEvaluations.map((evaluation) => [evaluation.candidateRow.candidateModel, evaluation])
6706
+ );
6707
+ const candidatesWithMissingPricing = input.candidateModels.filter(
6708
+ (candidateModel) => allEvaluationByCandidate.get(candidateModel)?.missingPricing === true
6709
+ ).sort(compareByCodePoint);
6710
+ const warningPeriods = [];
6711
+ const rows = [];
6712
+ for (const period of baselinePeriods) {
6713
+ rows.push(toBaselineRow(period, input.provider));
6714
+ for (const candidateModel of selectedCandidates) {
6715
+ const resolvedEvaluation = period.periodKey === "ALL" ? allEvaluationByCandidate.get(candidateModel) : evaluateCandidateForPeriod(period, input.provider, candidateModel, input.pricingSource);
6716
+ if (!resolvedEvaluation) {
6717
+ continue;
6718
+ }
6719
+ if (resolvedEvaluation.hasBaselineTokenMismatch) {
6720
+ warningPeriods.push(period.periodKey);
6721
+ }
6722
+ rows.push(resolvedEvaluation.candidateRow);
6723
+ }
6724
+ }
6725
+ return {
6726
+ rows,
6727
+ candidatesWithMissingPricing,
6728
+ baselineCostIncomplete: allPeriod.baselineCostIncomplete,
6729
+ warning: buildWarning(warningPeriods)
6730
+ };
6731
+ }
6732
+
6733
+ // src/cli/build-optimize-data.ts
6734
+ function resolveOptimizeProvider(providers, providerFilter) {
6735
+ const distinctProviders = [...providers].sort(compareByCodePoint);
6736
+ const normalizedProviderFilter = normalizeProviderFilter(providerFilter);
6737
+ if (distinctProviders.length > 1) {
6738
+ if (normalizedProviderFilter) {
6739
+ const matchingProviders = distinctProviders.filter(
6740
+ (provider) => provider.includes(normalizedProviderFilter)
6741
+ );
6742
+ if (matchingProviders.includes(normalizedProviderFilter)) {
6743
+ return normalizedProviderFilter;
6744
+ }
6745
+ if (matchingProviders.length === 1) {
6746
+ return matchingProviders[0];
6747
+ }
6748
+ if (matchingProviders.length === 0) {
6749
+ throw new Error(
6750
+ `Optimize --provider "${normalizedProviderFilter}" matched no providers. Available providers: ${distinctProviders.join(", ")}.`
6751
+ );
6752
+ }
6753
+ if (matchingProviders.length > 1) {
6754
+ throw new Error(
6755
+ `Optimize matched multiple providers for --provider "${normalizedProviderFilter}": ${matchingProviders.join(", ")}. Supply a more specific --provider value.`
6756
+ );
6757
+ }
6758
+ }
6759
+ throw new Error(
6760
+ `Optimize requires a single provider; found providers: ${distinctProviders.join(", ")}. Narrow with --provider.`
6761
+ );
6762
+ }
6763
+ if (distinctProviders.length === 1) {
6764
+ return distinctProviders[0];
6765
+ }
6766
+ return normalizedProviderFilter ?? "unknown";
6767
+ }
6768
+ async function buildOptimizeData(granularity, options, deps = {}) {
6769
+ const candidateModels = normalizeCandidateModels(options.candidateModel);
6770
+ const top = parseTopOption(options.top);
6771
+ const dataset = await buildUsageEventDataset(options, deps);
6772
+ const detectedProviders = new Set(
6773
+ dataset.filteredEvents.map((event) => normalizeProviderFilter(event.provider)).filter((provider2) => provider2 !== void 0)
6774
+ );
6775
+ const provider = resolveOptimizeProvider(
6776
+ detectedProviders,
6777
+ dataset.normalizedInputs.providerFilter
6778
+ );
6779
+ const { pricedEvents, pricingOrigin, pricingWarning, pricingSource } = await applyPricingToUsageEventDataset(dataset, deps, "force");
6780
+ const usageRows = aggregateUsage(pricedEvents, {
6781
+ granularity,
6782
+ timezone: dataset.normalizedInputs.timezone,
6783
+ sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
6784
+ });
6785
+ const counterfactual = buildCounterfactualRows({
6786
+ usageRows,
6787
+ provider,
6788
+ candidateModels,
6789
+ pricingSource,
6790
+ top
6791
+ });
6792
+ const usageDiagnostics = buildUsageDiagnostics({
6793
+ adaptersToParse: dataset.adaptersToParse,
6794
+ successfulParseResults: dataset.successfulParseResults,
6795
+ sourceFailures: dataset.sourceFailures,
6796
+ pricingOrigin,
6797
+ pricingWarning,
6798
+ activeEnvOverrides: dataset.readEnvVarOverrides(),
6799
+ timezone: dataset.normalizedInputs.timezone
6800
+ });
6801
+ return {
6802
+ rows: counterfactual.rows,
6803
+ diagnostics: {
6804
+ usage: usageDiagnostics,
6805
+ provider,
6806
+ baselineCostIncomplete: counterfactual.baselineCostIncomplete,
6807
+ candidatesWithMissingPricing: counterfactual.candidatesWithMissingPricing,
6808
+ warning: counterfactual.warning
6809
+ }
6810
+ };
6811
+ }
6812
+
6813
+ // src/cli/run-optimize-report.ts
6814
+ function validateOutputFormatOptions2(options) {
6815
+ if (options.markdown && options.json) {
6816
+ throw new Error("Choose either --markdown or --json, not both");
6817
+ }
6818
+ }
6819
+ function resolveReportFormat2(options) {
6820
+ if (options.json) {
6821
+ return "json";
6822
+ }
6823
+ if (options.markdown) {
6824
+ return "markdown";
6825
+ }
6826
+ return "terminal";
6827
+ }
6828
+ async function prepareOptimizeReport(granularity, options) {
6829
+ validateOutputFormatOptions2(options);
6830
+ const optimizeData = await buildOptimizeData(granularity, options);
6831
+ const format = resolveReportFormat2(options);
6832
+ return {
6833
+ format,
6834
+ diagnostics: optimizeData.diagnostics,
6835
+ candidateCount: optimizeData.rows.filter(
6836
+ (row) => row.rowType === "candidate" && row.periodKey === "ALL"
6837
+ ).length,
6838
+ output: renderOptimizeReport(optimizeData, format, {
6839
+ granularity
6840
+ })
6841
+ };
6842
+ }
6843
+ async function runOptimizeReport(granularity, options) {
6844
+ const preparedReport = await prepareOptimizeReport(granularity, options);
6845
+ emitDiagnostics(preparedReport.diagnostics.usage, logger);
6846
+ emitEnvVarOverrides(preparedReport.diagnostics.usage.activeEnvOverrides, logger);
6847
+ logger.info(
6848
+ `Optimize provider scope: ${preparedReport.diagnostics.provider}; candidate(s): ${preparedReport.candidateCount}`
6849
+ );
6850
+ if (preparedReport.diagnostics.candidatesWithMissingPricing.length > 0) {
6851
+ logger.warn(
6852
+ `Missing pricing for candidate model(s): ${preparedReport.diagnostics.candidatesWithMissingPricing.join(", ")}`
6853
+ );
6854
+ }
6855
+ if (preparedReport.diagnostics.warning) {
6856
+ logger.warn(preparedReport.diagnostics.warning);
6857
+ }
6858
+ if (preparedReport.format === "terminal") {
6859
+ warnIfTerminalTableOverflows(preparedReport.output, (message) => {
6860
+ logger.warn(message);
6861
+ });
6862
+ }
6863
+ console.log(preparedReport.output);
6864
+ }
6865
+
6866
+ // src/render/markdown-table.ts
6867
+ import { markdownTable as markdownTable3 } from "markdown-table";
6868
+ var alignment = ["l", "l", "l", "r", "r", "r", "r", "r", "r", "r"];
6869
+ function toMarkdownSafeCell3(value) {
6870
+ return value.replace(/\r?\n/gu, "<br>");
6871
+ }
6092
6872
  function renderMarkdownTable(rows, options = {}) {
6093
6873
  const tableLayout = options.tableLayout ?? "compact";
6094
6874
  const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
6095
- (row) => row.map((cell) => toMarkdownSafeCell2(cell))
6875
+ (row) => row.map((cell) => toMarkdownSafeCell3(cell))
6096
6876
  );
6097
6877
  const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
6098
- return markdownTable2(tableRows, {
6878
+ return markdownTable3(tableRows, {
6099
6879
  align: alignment
6100
6880
  });
6101
6881
  }
6102
6882
 
6103
6883
  // src/render/render-usage-report.ts
6104
- function getReportTitle2(granularity) {
6884
+ function getReportTitle3(granularity) {
6105
6885
  switch (granularity) {
6106
6886
  case "daily":
6107
6887
  return "Daily Token Usage Report";
@@ -6117,7 +6897,7 @@ function renderTerminalUsageReport(usageData, options) {
6117
6897
  const tableLayout = options.tableLayout ?? "compact";
6118
6898
  outputLines.push(
6119
6899
  renderReportHeader({
6120
- title: getReportTitle2(options.granularity),
6900
+ title: getReportTitle3(options.granularity),
6121
6901
  useColor
6122
6902
  })
6123
6903
  );
@@ -6138,12 +6918,12 @@ function renderUsageReport(usageData, format, options) {
6138
6918
  }
6139
6919
 
6140
6920
  // src/cli/run-usage-report.ts
6141
- function validateOutputFormatOptions2(options) {
6921
+ function validateOutputFormatOptions3(options) {
6142
6922
  if (options.markdown && options.json) {
6143
6923
  throw new Error("Choose either --markdown or --json, not both");
6144
6924
  }
6145
6925
  }
6146
- function resolveReportFormat2(options) {
6926
+ function resolveReportFormat3(options) {
6147
6927
  if (options.json) {
6148
6928
  return "json";
6149
6929
  }
@@ -6156,9 +6936,9 @@ function resolveTableLayout(options) {
6156
6936
  return options.perModelColumns ? "per_model_columns" : "compact";
6157
6937
  }
6158
6938
  async function prepareUsageReport(granularity, options) {
6159
- validateOutputFormatOptions2(options);
6939
+ validateOutputFormatOptions3(options);
6160
6940
  const usageData = await buildUsageData(granularity, options);
6161
- const format = resolveReportFormat2(options);
6941
+ const format = resolveReportFormat3(options);
6162
6942
  return {
6163
6943
  format,
6164
6944
  diagnostics: usageData.diagnostics,
@@ -6206,7 +6986,10 @@ function addSharedOptions(command, options = {}) {
6206
6986
  `Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
6207
6987
  collectRepeatedOption,
6208
6988
  []
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(
6989
+ ).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(
6990
+ "--provider <name>",
6991
+ "Billing-provider filter (substring match, optional; e.g. openai, anthropic, google)"
6992
+ ).option(
6210
6993
  "--model <name>",
6211
6994
  "Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
6212
6995
  collectRepeatedOption,
@@ -6254,6 +7037,18 @@ function createEfficiencyCommand() {
6254
7037
  });
6255
7038
  return command;
6256
7039
  }
7040
+ function createOptimizeCommand() {
7041
+ const command = new Command("optimize");
7042
+ addSharedOptions(command, { includePerModelColumns: false }).argument("<granularity>", "Granularity: daily | weekly | monthly", parseGranularityArgument).option(
7043
+ "--candidate-model <name>",
7044
+ "Candidate model for counterfactual pricing (repeatable or comma-separated)",
7045
+ collectRepeatedOption,
7046
+ []
7047
+ ).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) => {
7048
+ await runOptimizeReport(granularity, options);
7049
+ });
7050
+ return command;
7051
+ }
6257
7052
  function rootDescription() {
6258
7053
  const supportedSourceIds = getSupportedSourceIds();
6259
7054
  const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
@@ -6273,12 +7068,13 @@ function rootDescription() {
6273
7068
  " $ llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
6274
7069
  " $ llm-usage daily --pi-dir /tmp/pi-sessions --gemini-dir /tmp/.gemini --droid-dir /tmp/droid-sessions",
6275
7070
  " $ llm-usage efficiency weekly --repo-dir /path/to/repo --json",
7071
+ " $ llm-usage optimize monthly --provider openai --candidate-model gpt-4.1 --candidate-model gpt-5-codex --json",
6276
7072
  " $ npx --yes llm-usage-metrics@latest daily"
6277
7073
  ].join("\n");
6278
7074
  }
6279
7075
  function createCli(options = {}) {
6280
7076
  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());
7077
+ 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
7078
  return program;
6283
7079
  }
6284
7080