llm-usage-metrics 0.3.5 → 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/README.md +21 -1
- package/dist/index.js +1050 -112
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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([
|
|
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:
|
|
778
|
+
provider: normalizeOptionalProvider(input.provider),
|
|
739
779
|
model: normalizeOptionalModel(input.model),
|
|
740
780
|
inputTokens,
|
|
741
781
|
outputTokens,
|
|
@@ -1217,7 +1257,6 @@ var DroidSourceAdapter = class {
|
|
|
1217
1257
|
toNumberLike(tokenUsage.cacheCreationTokens)
|
|
1218
1258
|
);
|
|
1219
1259
|
const billableTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
|
|
1220
|
-
const totalTokens = billableTokens + reasoningTokens;
|
|
1221
1260
|
if (billableTokens === 0) {
|
|
1222
1261
|
skippedRows++;
|
|
1223
1262
|
incrementSkippedReason(skippedRowReasons, "no_token_usage");
|
|
@@ -1225,6 +1264,7 @@ var DroidSourceAdapter = class {
|
|
|
1225
1264
|
}
|
|
1226
1265
|
const provider = asTrimmedText(settings.providerLock);
|
|
1227
1266
|
const model = asTrimmedText(settings.model);
|
|
1267
|
+
const totalTokens = billableTokens;
|
|
1228
1268
|
const primaryTimestamp = normalizeTimestampCandidate(settings.providerLockTimestamp);
|
|
1229
1269
|
const hasValidPrimaryTimestamp = Boolean(primaryTimestamp);
|
|
1230
1270
|
const jsonlPath = getSiblingJsonlPath(filePath);
|
|
@@ -1243,7 +1283,9 @@ var DroidSourceAdapter = class {
|
|
|
1243
1283
|
}
|
|
1244
1284
|
if (!hasValidPrimaryTimestamp && isMessageRecord(line)) {
|
|
1245
1285
|
fallbackMessageTimestamp = normalizeTimestampCandidate(line.timestamp);
|
|
1246
|
-
|
|
1286
|
+
if (fallbackMessageTimestamp) {
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1247
1289
|
}
|
|
1248
1290
|
}
|
|
1249
1291
|
} catch {
|
|
@@ -3262,6 +3304,42 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
|
|
|
3262
3304
|
};
|
|
3263
3305
|
}
|
|
3264
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
|
+
|
|
3265
3343
|
// src/config/env-var-display.ts
|
|
3266
3344
|
var ENV_VARS_TO_DISPLAY = [
|
|
3267
3345
|
{ name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
|
|
@@ -3302,42 +3380,6 @@ function formatEnvVarOverrides(overrides) {
|
|
|
3302
3380
|
return lines;
|
|
3303
3381
|
}
|
|
3304
3382
|
|
|
3305
|
-
// src/cli/build-usage-data-diagnostics.ts
|
|
3306
|
-
function buildUsageDiagnostics(params) {
|
|
3307
|
-
const parseResultBySource = new Map(
|
|
3308
|
-
params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
|
|
3309
|
-
);
|
|
3310
|
-
const sessionStats = params.adaptersToParse.map((adapter) => {
|
|
3311
|
-
const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
|
|
3312
|
-
return {
|
|
3313
|
-
source: adapter.id,
|
|
3314
|
-
filesFound: parseResult?.filesFound ?? 0,
|
|
3315
|
-
eventsParsed: parseResult?.events.length ?? 0
|
|
3316
|
-
};
|
|
3317
|
-
});
|
|
3318
|
-
const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
|
|
3319
|
-
source: result.source,
|
|
3320
|
-
skippedRows: result.skippedRows,
|
|
3321
|
-
reasons: result.skippedRowReasons
|
|
3322
|
-
}));
|
|
3323
|
-
return {
|
|
3324
|
-
sessionStats,
|
|
3325
|
-
sourceFailures: params.sourceFailures,
|
|
3326
|
-
skippedRows,
|
|
3327
|
-
pricingOrigin: params.pricingOrigin,
|
|
3328
|
-
pricingWarning: params.pricingWarning,
|
|
3329
|
-
activeEnvOverrides: params.activeEnvOverrides,
|
|
3330
|
-
timezone: params.timezone
|
|
3331
|
-
};
|
|
3332
|
-
}
|
|
3333
|
-
function assembleUsageDataResult(events, rows, diagnostics) {
|
|
3334
|
-
return {
|
|
3335
|
-
events,
|
|
3336
|
-
rows,
|
|
3337
|
-
diagnostics
|
|
3338
|
-
};
|
|
3339
|
-
}
|
|
3340
|
-
|
|
3341
3383
|
// src/cli/build-usage-data-inputs.ts
|
|
3342
3384
|
function validateDateInput(value, flagName) {
|
|
3343
3385
|
if (!/^\d{4}-\d{2}-\d{2}$/u.test(value)) {
|
|
@@ -3357,11 +3399,7 @@ function validateTimezone(timezone) {
|
|
|
3357
3399
|
}
|
|
3358
3400
|
}
|
|
3359
3401
|
function normalizeProviderFilter(provider) {
|
|
3360
|
-
|
|
3361
|
-
return void 0;
|
|
3362
|
-
}
|
|
3363
|
-
const normalized = provider.trim().toLowerCase();
|
|
3364
|
-
return normalized || void 0;
|
|
3402
|
+
return normalizeProviderToBillingEntity(provider);
|
|
3365
3403
|
}
|
|
3366
3404
|
function normalizeSourceFilter(source) {
|
|
3367
3405
|
if (!source || Array.isArray(source) && source.length === 0) {
|
|
@@ -3508,7 +3546,7 @@ function selectAdaptersForParsing(adapters, sourceFilter) {
|
|
|
3508
3546
|
}
|
|
3509
3547
|
|
|
3510
3548
|
// src/cli/build-usage-data-parsing.ts
|
|
3511
|
-
import { stat as
|
|
3549
|
+
import { stat as stat4 } from "fs/promises";
|
|
3512
3550
|
|
|
3513
3551
|
// src/cli/normalize-skipped-row-reasons.ts
|
|
3514
3552
|
function toPositiveInteger(value) {
|
|
@@ -3536,9 +3574,9 @@ function normalizeSkippedRowReasons(value) {
|
|
|
3536
3574
|
}
|
|
3537
3575
|
|
|
3538
3576
|
// src/cli/parse-file-cache.ts
|
|
3539
|
-
import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
3577
|
+
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat3, writeFile as writeFile2 } from "fs/promises";
|
|
3540
3578
|
import path11 from "path";
|
|
3541
|
-
var PARSE_FILE_CACHE_VERSION =
|
|
3579
|
+
var PARSE_FILE_CACHE_VERSION = 3;
|
|
3542
3580
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
3543
3581
|
function createCacheKey(source, filePath) {
|
|
3544
3582
|
return `${source}${CACHE_KEY_SEPARATOR}${filePath}`;
|
|
@@ -3597,7 +3635,9 @@ function normalizeCachedUsageEvent(value) {
|
|
|
3597
3635
|
if (inputTokens === void 0 || outputTokens === void 0 || reasoningTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0 || totalTokens === void 0) {
|
|
3598
3636
|
return void 0;
|
|
3599
3637
|
}
|
|
3600
|
-
const provider =
|
|
3638
|
+
const provider = normalizeProviderToBillingEntity(
|
|
3639
|
+
typeof record.provider === "string" ? record.provider : void 0
|
|
3640
|
+
);
|
|
3601
3641
|
const model = typeof record.model === "string" ? record.model.trim().toLowerCase() : "";
|
|
3602
3642
|
const costUsd = toNonNegativeNumber2(record.costUsd);
|
|
3603
3643
|
if (costMode === "explicit" && costUsd === void 0) {
|
|
@@ -3608,7 +3648,7 @@ function normalizeCachedUsageEvent(value) {
|
|
|
3608
3648
|
sessionId,
|
|
3609
3649
|
timestamp,
|
|
3610
3650
|
repoRoot: repoRoot || void 0,
|
|
3611
|
-
provider
|
|
3651
|
+
provider,
|
|
3612
3652
|
model: model || void 0,
|
|
3613
3653
|
inputTokens,
|
|
3614
3654
|
outputTokens,
|
|
@@ -3627,7 +3667,7 @@ function cloneUsageEvents(events) {
|
|
|
3627
3667
|
return events.map((event) => cloneUsageEvent(event));
|
|
3628
3668
|
}
|
|
3629
3669
|
function cloneSkippedRowReasons(skippedRowReasons) {
|
|
3630
|
-
return (skippedRowReasons ?? []).map((
|
|
3670
|
+
return (skippedRowReasons ?? []).map((stat5) => ({ reason: stat5.reason, count: stat5.count }));
|
|
3631
3671
|
}
|
|
3632
3672
|
function normalizeCachedEvents(value) {
|
|
3633
3673
|
if (!Array.isArray(value)) {
|
|
@@ -3678,6 +3718,21 @@ function normalizeCacheEntry(value) {
|
|
|
3678
3718
|
function getDefaultParseFileCachePath() {
|
|
3679
3719
|
return path11.join(getUserCacheRootDir(), "llm-usage-metrics", "parse-file-cache.json");
|
|
3680
3720
|
}
|
|
3721
|
+
function normalizeCacheShardSource(source) {
|
|
3722
|
+
const normalizedSource = normalizeCacheSource(source);
|
|
3723
|
+
if (!normalizedSource) {
|
|
3724
|
+
return "unknown";
|
|
3725
|
+
}
|
|
3726
|
+
return normalizedSource.replace(/[^a-z0-9._-]/gu, "_");
|
|
3727
|
+
}
|
|
3728
|
+
function getSourceShardedParseFileCachePath(cacheFilePath, source) {
|
|
3729
|
+
const parsedPath = path11.parse(cacheFilePath);
|
|
3730
|
+
const sourceShard = normalizeCacheShardSource(source);
|
|
3731
|
+
if (parsedPath.ext.length > 0) {
|
|
3732
|
+
return path11.join(parsedPath.dir, `${parsedPath.name}.${sourceShard}${parsedPath.ext}`);
|
|
3733
|
+
}
|
|
3734
|
+
return path11.join(parsedPath.dir, `${parsedPath.base}.${sourceShard}`);
|
|
3735
|
+
}
|
|
3681
3736
|
var ParseFileCache = class _ParseFileCache {
|
|
3682
3737
|
constructor(cacheFilePath, limits, now) {
|
|
3683
3738
|
this.cacheFilePath = cacheFilePath;
|
|
@@ -3763,8 +3818,15 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
3763
3818
|
payloadText = bestPayloadText;
|
|
3764
3819
|
}
|
|
3765
3820
|
await mkdir2(path11.dirname(this.cacheFilePath), { recursive: true });
|
|
3766
|
-
|
|
3767
|
-
|
|
3821
|
+
const temporaryPath = `${this.cacheFilePath}.${process.pid}.${this.now()}.tmp`;
|
|
3822
|
+
try {
|
|
3823
|
+
await writeFile2(temporaryPath, payloadText, "utf8");
|
|
3824
|
+
await rename(temporaryPath, this.cacheFilePath);
|
|
3825
|
+
this.dirty = false;
|
|
3826
|
+
} catch (error) {
|
|
3827
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
3828
|
+
throw error;
|
|
3829
|
+
}
|
|
3768
3830
|
}
|
|
3769
3831
|
toPayload(entries) {
|
|
3770
3832
|
return {
|
|
@@ -3783,6 +3845,17 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
3783
3845
|
};
|
|
3784
3846
|
}
|
|
3785
3847
|
async loadFromDisk() {
|
|
3848
|
+
let cacheFileSizeBytes;
|
|
3849
|
+
try {
|
|
3850
|
+
const cacheStat = await stat3(this.cacheFilePath);
|
|
3851
|
+
cacheFileSizeBytes = cacheStat.size;
|
|
3852
|
+
} catch {
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
if (cacheFileSizeBytes > this.limits.maxBytes) {
|
|
3856
|
+
this.dirty = true;
|
|
3857
|
+
return;
|
|
3858
|
+
}
|
|
3786
3859
|
let content;
|
|
3787
3860
|
try {
|
|
3788
3861
|
content = await readFile4(this.cacheFilePath, "utf8");
|
|
@@ -3808,6 +3881,7 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
3808
3881
|
}
|
|
3809
3882
|
if (Buffer.byteLength(content, "utf8") > this.limits.maxBytes) {
|
|
3810
3883
|
this.dirty = true;
|
|
3884
|
+
return;
|
|
3811
3885
|
}
|
|
3812
3886
|
const entries = Array.isArray(payloadRecord.entries) ? payloadRecord.entries : [];
|
|
3813
3887
|
for (const rawEntry of entries) {
|
|
@@ -3867,7 +3941,7 @@ async function parseAdapterEvents(adapter, maxParallelFileParsing, parseFileCach
|
|
|
3867
3941
|
let parseFileDiagnostics;
|
|
3868
3942
|
if (parseFileCache) {
|
|
3869
3943
|
try {
|
|
3870
|
-
const fileStat = await
|
|
3944
|
+
const fileStat = await stat4(filePath);
|
|
3871
3945
|
fileFingerprint = {
|
|
3872
3946
|
size: fileStat.size,
|
|
3873
3947
|
mtimeMs: fileStat.mtimeMs
|
|
@@ -3908,25 +3982,44 @@ function getErrorReason(error) {
|
|
|
3908
3982
|
return String(error);
|
|
3909
3983
|
}
|
|
3910
3984
|
async function parseSelectedAdapters(adaptersToParse, maxParallelFileParsing, options = {}) {
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3985
|
+
const parseCacheBySource = /* @__PURE__ */ new Map();
|
|
3986
|
+
if (options.parseCache?.enabled) {
|
|
3987
|
+
const parseCacheLimits = {
|
|
3914
3988
|
ttlMs: options.parseCache.ttlMs,
|
|
3915
3989
|
maxEntries: options.parseCache.maxEntries,
|
|
3916
3990
|
maxBytes: options.parseCache.maxBytes
|
|
3917
|
-
}
|
|
3918
|
-
|
|
3919
|
-
|
|
3991
|
+
};
|
|
3992
|
+
const cacheFilePath = options.parseCacheFilePath ?? getDefaultParseFileCachePath();
|
|
3993
|
+
await Promise.all(
|
|
3994
|
+
adaptersToParse.map(async (adapter) => {
|
|
3995
|
+
const sourceId = adapter.id.toLowerCase();
|
|
3996
|
+
if (parseCacheBySource.has(sourceId)) {
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3999
|
+
parseCacheBySource.set(
|
|
4000
|
+
sourceId,
|
|
4001
|
+
await ParseFileCache.load({
|
|
4002
|
+
cacheFilePath: getSourceShardedParseFileCachePath(cacheFilePath, sourceId),
|
|
4003
|
+
limits: parseCacheLimits,
|
|
4004
|
+
now: options.now
|
|
4005
|
+
})
|
|
4006
|
+
);
|
|
4007
|
+
})
|
|
4008
|
+
);
|
|
4009
|
+
}
|
|
3920
4010
|
const parseResults = await Promise.allSettled(
|
|
3921
4011
|
adaptersToParse.map(
|
|
3922
|
-
(adapter) => parseAdapterEvents(
|
|
4012
|
+
(adapter) => parseAdapterEvents(
|
|
4013
|
+
adapter,
|
|
4014
|
+
maxParallelFileParsing,
|
|
4015
|
+
parseCacheBySource.get(adapter.id.toLowerCase())
|
|
4016
|
+
)
|
|
3923
4017
|
)
|
|
3924
4018
|
);
|
|
3925
|
-
if (
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
}
|
|
4019
|
+
if (parseCacheBySource.size > 0) {
|
|
4020
|
+
await Promise.allSettled(
|
|
4021
|
+
[...parseCacheBySource.values()].map(async (parseCache) => parseCache.persist())
|
|
4022
|
+
);
|
|
3930
4023
|
}
|
|
3931
4024
|
const sourceFailures = [];
|
|
3932
4025
|
const successfulParseResults = [];
|
|
@@ -4200,7 +4293,7 @@ function normalizeModelPricing(rawModelPricing) {
|
|
|
4200
4293
|
return void 0;
|
|
4201
4294
|
}
|
|
4202
4295
|
const cacheReadPerToken = toNonNegativeNumber3(rawModelPricing.cache_read_input_token_cost) ?? toNonNegativeNumber3(rawModelPricing.cache_read_input_token_cost_priority);
|
|
4203
|
-
const cacheWritePerToken = toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost);
|
|
4296
|
+
const cacheWritePerToken = toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost) ?? toNonNegativeNumber3(rawModelPricing.cache_creation_input_token_cost_priority);
|
|
4204
4297
|
const reasoningPerToken = toNonNegativeNumber3(rawModelPricing.output_cost_per_reasoning_token);
|
|
4205
4298
|
const modelPricing = {
|
|
4206
4299
|
inputPer1MUsd: inputPerToken * ONE_MILLION2,
|
|
@@ -4690,9 +4783,20 @@ function shouldLoadPricingSource(events) {
|
|
|
4690
4783
|
}
|
|
4691
4784
|
return events.some((event) => eventNeedsPricingLookup(event));
|
|
4692
4785
|
}
|
|
4693
|
-
|
|
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") {
|
|
4694
4798
|
let pricingOrigin = "none";
|
|
4695
|
-
if (!
|
|
4799
|
+
if (!shouldLoadPricingSourceForMode(events, pricingLoadMode)) {
|
|
4696
4800
|
return {
|
|
4697
4801
|
pricedEvents: events,
|
|
4698
4802
|
pricingOrigin
|
|
@@ -4716,11 +4820,12 @@ async function resolveAndApplyPricingToEvents(events, options, runtimeConfig = g
|
|
|
4716
4820
|
pricingOrigin = pricingResult.origin;
|
|
4717
4821
|
return {
|
|
4718
4822
|
pricedEvents: applyPricingToEvents(events, pricingResult.source),
|
|
4719
|
-
pricingOrigin
|
|
4823
|
+
pricingOrigin,
|
|
4824
|
+
pricingSource: pricingResult.source
|
|
4720
4825
|
};
|
|
4721
4826
|
}
|
|
4722
4827
|
|
|
4723
|
-
// src/cli/build-usage-
|
|
4828
|
+
// src/cli/build-usage-event-dataset.ts
|
|
4724
4829
|
function withNormalizedPricingUrl(options, normalizedPricingUrl) {
|
|
4725
4830
|
if (options.pricingUrl === normalizedPricingUrl) {
|
|
4726
4831
|
return options;
|
|
@@ -4730,13 +4835,11 @@ function withNormalizedPricingUrl(options, normalizedPricingUrl) {
|
|
|
4730
4835
|
pricingUrl: normalizedPricingUrl
|
|
4731
4836
|
};
|
|
4732
4837
|
}
|
|
4733
|
-
async function
|
|
4838
|
+
async function buildUsageEventDataset(options, deps = {}) {
|
|
4734
4839
|
const normalizedInputs = normalizeBuildUsageInputs(options);
|
|
4735
4840
|
const readParsingRuntimeConfig = deps.getParsingRuntimeConfig ?? getParsingRuntimeConfig;
|
|
4736
4841
|
const readPricingRuntimeConfig = deps.getPricingFetcherRuntimeConfig ?? getPricingFetcherRuntimeConfig;
|
|
4737
4842
|
const makeAdapters = deps.createAdapters ?? createDefaultAdapters;
|
|
4738
|
-
const loadPricingSource = deps.resolvePricingSource ?? resolvePricingSource;
|
|
4739
|
-
const readEnvVarOverrides = deps.getActiveEnvVarOverrides ?? getActiveEnvVarOverrides;
|
|
4740
4843
|
const parsingRuntimeConfig = readParsingRuntimeConfig();
|
|
4741
4844
|
const pricingRuntimeConfig = readPricingRuntimeConfig();
|
|
4742
4845
|
const adapters = makeAdapters(options);
|
|
@@ -4761,26 +4864,53 @@ async function buildUsageData(granularity, options, deps = {}) {
|
|
|
4761
4864
|
providerFilter: normalizedInputs.providerFilter,
|
|
4762
4865
|
modelFilter: normalizedInputs.modelFilter
|
|
4763
4866
|
});
|
|
4764
|
-
|
|
4765
|
-
|
|
4867
|
+
return {
|
|
4868
|
+
options,
|
|
4869
|
+
normalizedInputs,
|
|
4870
|
+
adaptersToParse,
|
|
4871
|
+
successfulParseResults,
|
|
4872
|
+
sourceFailures,
|
|
4766
4873
|
filteredEvents,
|
|
4767
|
-
pricingOptions,
|
|
4768
4874
|
pricingRuntimeConfig,
|
|
4769
|
-
|
|
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"
|
|
4770
4900
|
);
|
|
4771
4901
|
const rows = aggregateUsage(pricedEvents, {
|
|
4772
4902
|
granularity,
|
|
4773
|
-
timezone: normalizedInputs.timezone,
|
|
4774
|
-
sourceOrder: adaptersToParse.map((adapter) => adapter.id)
|
|
4903
|
+
timezone: dataset.normalizedInputs.timezone,
|
|
4904
|
+
sourceOrder: dataset.adaptersToParse.map((adapter) => adapter.id)
|
|
4775
4905
|
});
|
|
4776
4906
|
const diagnostics = buildUsageDiagnostics({
|
|
4777
|
-
adaptersToParse,
|
|
4778
|
-
successfulParseResults,
|
|
4779
|
-
sourceFailures,
|
|
4907
|
+
adaptersToParse: dataset.adaptersToParse,
|
|
4908
|
+
successfulParseResults: dataset.successfulParseResults,
|
|
4909
|
+
sourceFailures: dataset.sourceFailures,
|
|
4780
4910
|
pricingOrigin,
|
|
4781
4911
|
pricingWarning,
|
|
4782
|
-
activeEnvOverrides: readEnvVarOverrides(),
|
|
4783
|
-
timezone: normalizedInputs.timezone
|
|
4912
|
+
activeEnvOverrides: dataset.readEnvVarOverrides(),
|
|
4913
|
+
timezone: dataset.normalizedInputs.timezone
|
|
4784
4914
|
});
|
|
4785
4915
|
return assembleUsageDataResult(pricedEvents, rows, diagnostics);
|
|
4786
4916
|
}
|
|
@@ -5197,6 +5327,7 @@ function warnIfTerminalTableOverflows(reportOutput, warn, stdoutState = process.
|
|
|
5197
5327
|
|
|
5198
5328
|
// src/render/render-efficiency-report.ts
|
|
5199
5329
|
import { markdownTable } from "markdown-table";
|
|
5330
|
+
import pc5 from "picocolors";
|
|
5200
5331
|
|
|
5201
5332
|
// src/render/report-header.ts
|
|
5202
5333
|
import pc2 from "picocolors";
|
|
@@ -5342,8 +5473,8 @@ function resolveSourceStyler(source, palette = defaultTerminalStylePalette) {
|
|
|
5342
5473
|
var rowTypeStylePolicies = {
|
|
5343
5474
|
period_source: (cells, palette) => {
|
|
5344
5475
|
const styledCells = [...cells];
|
|
5345
|
-
const
|
|
5346
|
-
styledCells[
|
|
5476
|
+
const costColumnIndex2 = styledCells.length - 1;
|
|
5477
|
+
styledCells[costColumnIndex2] = styleCellLines(styledCells[costColumnIndex2], palette.yellow);
|
|
5347
5478
|
return styledCells;
|
|
5348
5479
|
},
|
|
5349
5480
|
period_combined: (cells, palette) => cells.map((cell, cellIndex) => {
|
|
@@ -5786,6 +5917,16 @@ function renderTerminalTable(rows, options = {}) {
|
|
|
5786
5917
|
}
|
|
5787
5918
|
|
|
5788
5919
|
// src/render/render-efficiency-report.ts
|
|
5920
|
+
var periodColumnIndex = 0;
|
|
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;
|
|
5789
5930
|
function getReportTitle(granularity) {
|
|
5790
5931
|
switch (granularity) {
|
|
5791
5932
|
case "daily":
|
|
@@ -5830,22 +5971,172 @@ function toTableSortRow(row) {
|
|
|
5830
5971
|
costIncomplete: row.costIncomplete
|
|
5831
5972
|
};
|
|
5832
5973
|
}
|
|
5833
|
-
function
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5974
|
+
function measureRenderedTableWidth(columnWidths) {
|
|
5975
|
+
if (columnWidths.length === 0) {
|
|
5976
|
+
return 0;
|
|
5977
|
+
}
|
|
5978
|
+
return columnWidths.reduce((sum, width) => sum + width, 0) + columnWidths.length * 3 + 1;
|
|
5979
|
+
}
|
|
5980
|
+
function computeColumnWidths2(headerCells, bodyRows) {
|
|
5981
|
+
const columnCount = Math.max(
|
|
5982
|
+
headerCells.length,
|
|
5983
|
+
...bodyRows.map((row) => row.length),
|
|
5984
|
+
efficiencyTableHeaders.length
|
|
5839
5985
|
);
|
|
5840
|
-
|
|
5841
|
-
|
|
5986
|
+
const widths = Array.from({ length: columnCount }, () => 0);
|
|
5987
|
+
const measureRow = (row) => {
|
|
5988
|
+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex += 1) {
|
|
5989
|
+
for (const line of splitCellLines(row[columnIndex] ?? "")) {
|
|
5990
|
+
widths[columnIndex] = Math.max(widths[columnIndex], visibleWidth(line));
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
};
|
|
5994
|
+
measureRow(headerCells);
|
|
5995
|
+
for (const row of bodyRows) {
|
|
5996
|
+
measureRow(row);
|
|
5997
|
+
}
|
|
5998
|
+
return widths;
|
|
5999
|
+
}
|
|
6000
|
+
function resolveWrappedCells(headerCells, bodyRows, widths) {
|
|
6001
|
+
let wrappedHeaderCells = [...headerCells];
|
|
6002
|
+
let wrappedBodyRows = bodyRows.map((row) => [...row]);
|
|
6003
|
+
for (let columnIndex = 0; columnIndex < widths.length; columnIndex += 1) {
|
|
6004
|
+
const columnWidth = widths[columnIndex] ?? 0;
|
|
6005
|
+
if (columnWidth <= 0) {
|
|
6006
|
+
continue;
|
|
6007
|
+
}
|
|
6008
|
+
wrappedHeaderCells = wrapTableColumn([wrappedHeaderCells], {
|
|
6009
|
+
columnIndex,
|
|
6010
|
+
width: columnWidth
|
|
6011
|
+
})[0] ?? [];
|
|
6012
|
+
wrappedBodyRows = wrapTableColumn(wrappedBodyRows, {
|
|
6013
|
+
columnIndex,
|
|
6014
|
+
width: columnWidth
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
return {
|
|
6018
|
+
wrappedHeaderCells,
|
|
6019
|
+
wrappedBodyRows
|
|
6020
|
+
};
|
|
6021
|
+
}
|
|
6022
|
+
function fitTableCellsToTerminal(headerCells, bodyRows) {
|
|
6023
|
+
const naturalWidths = computeColumnWidths2(headerCells, bodyRows);
|
|
6024
|
+
const terminalWidth = resolveTtyColumns(process.stdout);
|
|
6025
|
+
if (terminalWidth === void 0 || measureRenderedTableWidth(naturalWidths) <= terminalWidth) {
|
|
6026
|
+
return {
|
|
6027
|
+
headerCells: [...headerCells],
|
|
6028
|
+
bodyRows: bodyRows.map((row) => [...row]),
|
|
6029
|
+
widths: naturalWidths
|
|
6030
|
+
};
|
|
6031
|
+
}
|
|
6032
|
+
const constrainedWidths = [...naturalWidths];
|
|
6033
|
+
let renderedTableWidth = measureRenderedTableWidth(constrainedWidths);
|
|
6034
|
+
while (renderedTableWidth > terminalWidth && constrainedWidths.some((width) => width > minimumEfficiencyColumnWidth)) {
|
|
6035
|
+
let widestIndex = -1;
|
|
6036
|
+
let widestWidth = -1;
|
|
6037
|
+
for (let columnIndex = 0; columnIndex < constrainedWidths.length; columnIndex += 1) {
|
|
6038
|
+
const columnWidth = constrainedWidths[columnIndex];
|
|
6039
|
+
if (columnWidth <= minimumEfficiencyColumnWidth || columnWidth <= widestWidth) {
|
|
6040
|
+
continue;
|
|
6041
|
+
}
|
|
6042
|
+
widestIndex = columnIndex;
|
|
6043
|
+
widestWidth = columnWidth;
|
|
6044
|
+
}
|
|
6045
|
+
if (widestIndex === -1) {
|
|
6046
|
+
break;
|
|
6047
|
+
}
|
|
6048
|
+
const overflowColumns = renderedTableWidth - terminalWidth;
|
|
6049
|
+
const maxReducibleWidth = widestWidth - minimumEfficiencyColumnWidth;
|
|
6050
|
+
const reduction = Math.min(overflowColumns, maxReducibleWidth);
|
|
6051
|
+
if (reduction <= 0) {
|
|
6052
|
+
break;
|
|
6053
|
+
}
|
|
6054
|
+
constrainedWidths[widestIndex] -= reduction;
|
|
6055
|
+
renderedTableWidth -= reduction;
|
|
6056
|
+
}
|
|
6057
|
+
const { wrappedHeaderCells, wrappedBodyRows } = resolveWrappedCells(
|
|
6058
|
+
headerCells,
|
|
5842
6059
|
bodyRows,
|
|
5843
|
-
|
|
5844
|
-
|
|
6060
|
+
constrainedWidths
|
|
6061
|
+
);
|
|
6062
|
+
return {
|
|
6063
|
+
headerCells: wrappedHeaderCells,
|
|
6064
|
+
bodyRows: wrappedBodyRows,
|
|
6065
|
+
widths: constrainedWidths
|
|
6066
|
+
};
|
|
6067
|
+
}
|
|
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) {
|
|
6127
|
+
const headerCells = Array.from(efficiencyTableHeaders);
|
|
6128
|
+
const bodyRows = styleEfficiencyTerminalRows(rows, toEfficiencyTableCells(rows), options);
|
|
6129
|
+
const tableSortRows = rows.map((row) => toTableSortRow(row));
|
|
6130
|
+
const fittedCells = fitTableCellsToTerminal(headerCells, bodyRows);
|
|
6131
|
+
return renderUnicodeTable({
|
|
6132
|
+
headerCells: fittedCells.headerCells,
|
|
6133
|
+
bodyRows: fittedCells.bodyRows,
|
|
6134
|
+
measureHeaderCells: fittedCells.headerCells,
|
|
6135
|
+
measureBodyRows: fittedCells.bodyRows,
|
|
5845
6136
|
usageRows: tableSortRows,
|
|
5846
6137
|
tableLayout: "compact",
|
|
5847
|
-
modelsColumnIndex:
|
|
5848
|
-
modelsColumnWidth:
|
|
6138
|
+
modelsColumnIndex: periodColumnIndex,
|
|
6139
|
+
modelsColumnWidth: fittedCells.widths[periodColumnIndex] ?? efficiencyTableHeaders[periodColumnIndex].length
|
|
5849
6140
|
});
|
|
5850
6141
|
}
|
|
5851
6142
|
function toMarkdownSafeCell(value) {
|
|
@@ -5871,7 +6162,7 @@ function renderTerminalEfficiencyReport(efficiencyData, options) {
|
|
|
5871
6162
|
})
|
|
5872
6163
|
);
|
|
5873
6164
|
outputLines.push("");
|
|
5874
|
-
outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows));
|
|
6165
|
+
outputLines.push(renderTerminalEfficiencyTable(efficiencyData.rows, { useColor }));
|
|
5875
6166
|
return outputLines.join("\n");
|
|
5876
6167
|
}
|
|
5877
6168
|
function renderEfficiencyReport(efficiencyData, format, options) {
|
|
@@ -5934,25 +6225,656 @@ async function runEfficiencyReport(granularity, options) {
|
|
|
5934
6225
|
console.log(preparedReport.output);
|
|
5935
6226
|
}
|
|
5936
6227
|
|
|
5937
|
-
// src/render/
|
|
6228
|
+
// src/render/render-optimize-report.ts
|
|
5938
6229
|
import { markdownTable as markdownTable2 } from "markdown-table";
|
|
5939
|
-
|
|
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
|
+
}
|
|
5940
6389
|
function toMarkdownSafeCell2(value) {
|
|
5941
6390
|
return value.replace(/\r?\n/gu, "<br>");
|
|
5942
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
|
+
}
|
|
5943
6865
|
function renderMarkdownTable(rows, options = {}) {
|
|
5944
6866
|
const tableLayout = options.tableLayout ?? "compact";
|
|
5945
6867
|
const bodyRows = toUsageTableCells(rows, { layout: tableLayout }).map(
|
|
5946
|
-
(row) => row.map((cell) =>
|
|
6868
|
+
(row) => row.map((cell) => toMarkdownSafeCell3(cell))
|
|
5947
6869
|
);
|
|
5948
6870
|
const tableRows = [Array.from(usageTableHeaders), ...bodyRows];
|
|
5949
|
-
return
|
|
6871
|
+
return markdownTable3(tableRows, {
|
|
5950
6872
|
align: alignment
|
|
5951
6873
|
});
|
|
5952
6874
|
}
|
|
5953
6875
|
|
|
5954
6876
|
// src/render/render-usage-report.ts
|
|
5955
|
-
function
|
|
6877
|
+
function getReportTitle3(granularity) {
|
|
5956
6878
|
switch (granularity) {
|
|
5957
6879
|
case "daily":
|
|
5958
6880
|
return "Daily Token Usage Report";
|
|
@@ -5968,7 +6890,7 @@ function renderTerminalUsageReport(usageData, options) {
|
|
|
5968
6890
|
const tableLayout = options.tableLayout ?? "compact";
|
|
5969
6891
|
outputLines.push(
|
|
5970
6892
|
renderReportHeader({
|
|
5971
|
-
title:
|
|
6893
|
+
title: getReportTitle3(options.granularity),
|
|
5972
6894
|
useColor
|
|
5973
6895
|
})
|
|
5974
6896
|
);
|
|
@@ -5989,12 +6911,12 @@ function renderUsageReport(usageData, format, options) {
|
|
|
5989
6911
|
}
|
|
5990
6912
|
|
|
5991
6913
|
// src/cli/run-usage-report.ts
|
|
5992
|
-
function
|
|
6914
|
+
function validateOutputFormatOptions3(options) {
|
|
5993
6915
|
if (options.markdown && options.json) {
|
|
5994
6916
|
throw new Error("Choose either --markdown or --json, not both");
|
|
5995
6917
|
}
|
|
5996
6918
|
}
|
|
5997
|
-
function
|
|
6919
|
+
function resolveReportFormat3(options) {
|
|
5998
6920
|
if (options.json) {
|
|
5999
6921
|
return "json";
|
|
6000
6922
|
}
|
|
@@ -6007,9 +6929,9 @@ function resolveTableLayout(options) {
|
|
|
6007
6929
|
return options.perModelColumns ? "per_model_columns" : "compact";
|
|
6008
6930
|
}
|
|
6009
6931
|
async function prepareUsageReport(granularity, options) {
|
|
6010
|
-
|
|
6932
|
+
validateOutputFormatOptions3(options);
|
|
6011
6933
|
const usageData = await buildUsageData(granularity, options);
|
|
6012
|
-
const format =
|
|
6934
|
+
const format = resolveReportFormat3(options);
|
|
6013
6935
|
return {
|
|
6014
6936
|
format,
|
|
6015
6937
|
diagnostics: usageData.diagnostics,
|
|
@@ -6057,7 +6979,10 @@ function addSharedOptions(command, options = {}) {
|
|
|
6057
6979
|
`Filter by source id (repeatable or comma-separated, supported sources ${supportedSourcesSummary})`,
|
|
6058
6980
|
collectRepeatedOption,
|
|
6059
6981
|
[]
|
|
6060
|
-
).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(
|
|
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(
|
|
6061
6986
|
"--model <name>",
|
|
6062
6987
|
"Filter by model (repeatable/comma-separated; exact when exact match exists after source/provider/date filters, otherwise substring)",
|
|
6063
6988
|
collectRepeatedOption,
|
|
@@ -6105,6 +7030,18 @@ function createEfficiencyCommand() {
|
|
|
6105
7030
|
});
|
|
6106
7031
|
return command;
|
|
6107
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
|
+
}
|
|
6108
7045
|
function rootDescription() {
|
|
6109
7046
|
const supportedSourceIds = getSupportedSourceIds();
|
|
6110
7047
|
const allowedSourcesLabel = getAllowedSourcesLabel(supportedSourceIds);
|
|
@@ -6124,12 +7061,13 @@ function rootDescription() {
|
|
|
6124
7061
|
" $ llm-usage daily --source-dir pi=/tmp/pi-sessions --source-dir gemini=/tmp/.gemini --source-dir droid=/tmp/droid-sessions",
|
|
6125
7062
|
" $ llm-usage daily --pi-dir /tmp/pi-sessions --gemini-dir /tmp/.gemini --droid-dir /tmp/droid-sessions",
|
|
6126
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",
|
|
6127
7065
|
" $ npx --yes llm-usage-metrics@latest daily"
|
|
6128
7066
|
].join("\n");
|
|
6129
7067
|
}
|
|
6130
7068
|
function createCli(options = {}) {
|
|
6131
7069
|
const program = new Command();
|
|
6132
|
-
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());
|
|
6133
7071
|
return program;
|
|
6134
7072
|
}
|
|
6135
7073
|
|