llm-usage-metrics 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +334 -192
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2302,8 +2302,9 @@ function addOutcomeTotals(left, right) {
|
|
|
2302
2302
|
};
|
|
2303
2303
|
}
|
|
2304
2304
|
function addUsageTotals(left, right) {
|
|
2305
|
+
const hasAnyBucketUsage = (value) => value.inputTokens > 0 || value.outputTokens > 0 || value.reasoningTokens > 0 || value.cacheReadTokens > 0 || value.cacheWriteTokens > 0;
|
|
2305
2306
|
const hasUnknownCost = left.costIncomplete === true && left.costUsd === void 0 || right.costIncomplete === true && right.costUsd === void 0;
|
|
2306
|
-
const isNeutralZeroCost = (value) => value.totalTokens === 0 && value.costUsd === 0 && value.costIncomplete !== true;
|
|
2307
|
+
const isNeutralZeroCost = (value) => !hasAnyBucketUsage(value) && value.totalTokens === 0 && value.costUsd === 0 && value.costIncomplete !== true;
|
|
2307
2308
|
const leftKnownCost = left.costUsd !== void 0 && !isNeutralZeroCost(left) ? left.costUsd : void 0;
|
|
2308
2309
|
const rightKnownCost = right.costUsd !== void 0 && !isNeutralZeroCost(right) ? right.costUsd : void 0;
|
|
2309
2310
|
let costUsd = leftKnownCost !== void 0 && rightKnownCost !== void 0 ? addUsd2(leftKnownCost, rightKnownCost) : leftKnownCost ?? rightKnownCost;
|
|
@@ -2321,6 +2322,9 @@ function addUsageTotals(left, right) {
|
|
|
2321
2322
|
costIncomplete: left.costIncomplete || right.costIncomplete ? true : void 0
|
|
2322
2323
|
};
|
|
2323
2324
|
}
|
|
2325
|
+
function hasMeaningfulUsageSignal(usageTotals) {
|
|
2326
|
+
return usageTotals.totalTokens > 0 || usageTotals.inputTokens > 0 || usageTotals.outputTokens > 0 || usageTotals.reasoningTokens > 0 || usageTotals.cacheReadTokens > 0 || usageTotals.cacheWriteTokens > 0 || usageTotals.costUsd !== void 0 || usageTotals.costIncomplete === true;
|
|
2327
|
+
}
|
|
2324
2328
|
function computeDerivedMetrics(usage, outcomes) {
|
|
2325
2329
|
const costUsd = usage.costUsd;
|
|
2326
2330
|
const nonCacheTotalTokens = usage.inputTokens + usage.outputTokens + usage.reasoningTokens;
|
|
@@ -2343,7 +2347,7 @@ function aggregateEfficiency(options) {
|
|
|
2343
2347
|
const usageTotals = usageTotalsByPeriod.get(periodKey) ?? createEmptyEfficiencyUsageTotals();
|
|
2344
2348
|
const outcomeTotals = options.periodOutcomes.get(periodKey) ?? createEmptyEfficiencyOutcomeTotals();
|
|
2345
2349
|
const hasUsageRow = usageTotalsByPeriod.has(periodKey);
|
|
2346
|
-
const hasUsageSignal3 = hasUsageRow && (usageTotals
|
|
2350
|
+
const hasUsageSignal3 = hasUsageRow && hasMeaningfulUsageSignal(usageTotals);
|
|
2347
2351
|
if (outcomeTotals.commitCount === 0 || !hasUsageSignal3) {
|
|
2348
2352
|
continue;
|
|
2349
2353
|
}
|
|
@@ -2854,88 +2858,6 @@ async function attributeUsageEventsToRepo(events, repoDir, resolveRepoRoot3 = re
|
|
|
2854
2858
|
};
|
|
2855
2859
|
}
|
|
2856
2860
|
|
|
2857
|
-
// src/cli/build-usage-data-diagnostics.ts
|
|
2858
|
-
function buildUsageDiagnostics(params) {
|
|
2859
|
-
const parseResultBySource = new Map(
|
|
2860
|
-
params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
|
|
2861
|
-
);
|
|
2862
|
-
const sessionStats = params.adaptersToParse.map((adapter) => {
|
|
2863
|
-
const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
|
|
2864
|
-
return {
|
|
2865
|
-
source: adapter.id,
|
|
2866
|
-
filesFound: parseResult?.filesFound ?? 0,
|
|
2867
|
-
eventsParsed: parseResult?.events.length ?? 0
|
|
2868
|
-
};
|
|
2869
|
-
});
|
|
2870
|
-
const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
|
|
2871
|
-
source: result.source,
|
|
2872
|
-
skippedRows: result.skippedRows,
|
|
2873
|
-
reasons: result.skippedRowReasons
|
|
2874
|
-
}));
|
|
2875
|
-
return {
|
|
2876
|
-
sessionStats,
|
|
2877
|
-
sourceFailures: params.sourceFailures,
|
|
2878
|
-
skippedRows,
|
|
2879
|
-
pricingOrigin: params.pricingOrigin,
|
|
2880
|
-
pricingWarning: params.pricingWarning,
|
|
2881
|
-
activeEnvOverrides: params.activeEnvOverrides,
|
|
2882
|
-
timezone: params.timezone,
|
|
2883
|
-
runtimeProfile: params.runtimeProfile
|
|
2884
|
-
};
|
|
2885
|
-
}
|
|
2886
|
-
function assembleUsageDataResult(events, rows, diagnostics) {
|
|
2887
|
-
return {
|
|
2888
|
-
events,
|
|
2889
|
-
rows,
|
|
2890
|
-
diagnostics
|
|
2891
|
-
};
|
|
2892
|
-
}
|
|
2893
|
-
|
|
2894
|
-
// src/config/env-var-display.ts
|
|
2895
|
-
var ENV_VARS_TO_DISPLAY = [
|
|
2896
|
-
{ name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
|
|
2897
|
-
{
|
|
2898
|
-
name: "LLM_USAGE_UPDATE_CACHE_SCOPE",
|
|
2899
|
-
description: "update-check cache scope (global/session)"
|
|
2900
|
-
},
|
|
2901
|
-
{ name: "LLM_USAGE_UPDATE_CACHE_SESSION_KEY", description: "update-check session cache key" },
|
|
2902
|
-
{ name: "LLM_USAGE_UPDATE_CACHE_TTL_MS", description: "update-check cache TTL" },
|
|
2903
|
-
{ name: "LLM_USAGE_UPDATE_FETCH_TIMEOUT_MS", description: "update-check fetch timeout" },
|
|
2904
|
-
{ name: "LLM_USAGE_PRICING_CACHE_TTL_MS", description: "pricing cache TTL" },
|
|
2905
|
-
{ name: "LLM_USAGE_PRICING_FETCH_TIMEOUT_MS", description: "pricing fetch timeout" },
|
|
2906
|
-
{ name: "LLM_USAGE_PARSE_MAX_PARALLEL", description: "max parallel file parsing" },
|
|
2907
|
-
{ name: "LLM_USAGE_PARSE_CACHE_ENABLED", description: "enable file parse cache" },
|
|
2908
|
-
{ name: "LLM_USAGE_PARSE_CACHE_TTL_MS", description: "file parse cache TTL" },
|
|
2909
|
-
{ name: "LLM_USAGE_PARSE_CACHE_MAX_ENTRIES", description: "file parse cache max entries" },
|
|
2910
|
-
{ name: "LLM_USAGE_PARSE_CACHE_MAX_BYTES", description: "file parse cache max bytes" },
|
|
2911
|
-
{ name: "LLM_USAGE_PROFILE_RUNTIME", description: "emit runtime profiling diagnostics" }
|
|
2912
|
-
];
|
|
2913
|
-
function getActiveEnvVarOverrides() {
|
|
2914
|
-
const overrides = [];
|
|
2915
|
-
for (const { name, description } of ENV_VARS_TO_DISPLAY) {
|
|
2916
|
-
const value = process.env[name];
|
|
2917
|
-
if (value !== void 0 && value !== "") {
|
|
2918
|
-
overrides.push({ name, value, description });
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
return overrides;
|
|
2922
|
-
}
|
|
2923
|
-
function formatEnvVarOverrides(overrides) {
|
|
2924
|
-
if (overrides.length === 0) {
|
|
2925
|
-
return [];
|
|
2926
|
-
}
|
|
2927
|
-
const lines = [];
|
|
2928
|
-
lines.push("Active environment overrides:");
|
|
2929
|
-
for (const { name, value, description } of overrides) {
|
|
2930
|
-
lines.push(` ${name}=${value} (${description})`);
|
|
2931
|
-
}
|
|
2932
|
-
return lines;
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
// src/sources/codex/codex-source-adapter.ts
|
|
2936
|
-
import os2 from "os";
|
|
2937
|
-
import path6 from "path";
|
|
2938
|
-
|
|
2939
2861
|
// src/domain/provider-normalization.ts
|
|
2940
2862
|
var billingProviderAliases = /* @__PURE__ */ new Map([
|
|
2941
2863
|
["openai-codex", "openai"],
|
|
@@ -3095,7 +3017,7 @@ function createUsageEvent(input) {
|
|
|
3095
3017
|
const cacheWriteTokens = normalizeNonNegativeInteger(input.cacheWriteTokens);
|
|
3096
3018
|
const declaredTotalTokens = normalizeNonNegativeInteger(input.totalTokens);
|
|
3097
3019
|
const componentTotalTokens = inputTokens + outputTokens + reasoningTokens + cacheReadTokens + cacheWriteTokens;
|
|
3098
|
-
const totalTokens =
|
|
3020
|
+
const totalTokens = input.totalTokens === void 0 ? componentTotalTokens : declaredTotalTokens;
|
|
3099
3021
|
const costUsd = normalizeUsdCost(input.costUsd);
|
|
3100
3022
|
const costMode = resolveCostMode(input.costMode, costUsd);
|
|
3101
3023
|
return {
|
|
@@ -3122,8 +3044,90 @@ function isPriceableEvent(event) {
|
|
|
3122
3044
|
return hasBillableTokenBuckets(event);
|
|
3123
3045
|
}
|
|
3124
3046
|
|
|
3047
|
+
// src/cli/build-usage-data-diagnostics.ts
|
|
3048
|
+
function buildUsageDiagnostics(params) {
|
|
3049
|
+
const parseResultBySource = new Map(
|
|
3050
|
+
params.successfulParseResults.map((result) => [result.source.toLowerCase(), result])
|
|
3051
|
+
);
|
|
3052
|
+
const sessionStats = params.adaptersToParse.map((adapter) => {
|
|
3053
|
+
const parseResult = parseResultBySource.get(adapter.id.toLowerCase());
|
|
3054
|
+
return {
|
|
3055
|
+
source: adapter.id,
|
|
3056
|
+
filesFound: parseResult?.filesFound ?? 0,
|
|
3057
|
+
eventsParsed: parseResult?.events.length ?? 0
|
|
3058
|
+
};
|
|
3059
|
+
});
|
|
3060
|
+
const skippedRows = params.successfulParseResults.filter((result) => result.skippedRows > 0).map((result) => ({
|
|
3061
|
+
source: result.source,
|
|
3062
|
+
skippedRows: result.skippedRows,
|
|
3063
|
+
reasons: result.skippedRowReasons
|
|
3064
|
+
}));
|
|
3065
|
+
return {
|
|
3066
|
+
sessionStats,
|
|
3067
|
+
sourceFailures: params.sourceFailures,
|
|
3068
|
+
skippedRows,
|
|
3069
|
+
pricingOrigin: params.pricingOrigin,
|
|
3070
|
+
pricingWarning: params.pricingWarning,
|
|
3071
|
+
activeEnvOverrides: params.activeEnvOverrides,
|
|
3072
|
+
timezone: params.timezone,
|
|
3073
|
+
runtimeProfile: params.runtimeProfile
|
|
3074
|
+
};
|
|
3075
|
+
}
|
|
3076
|
+
function assembleUsageDataResult(events, rows, diagnostics) {
|
|
3077
|
+
return {
|
|
3078
|
+
events,
|
|
3079
|
+
rows,
|
|
3080
|
+
diagnostics
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
// src/config/env-var-display.ts
|
|
3085
|
+
var ENV_VARS_TO_DISPLAY = [
|
|
3086
|
+
{ name: "LLM_USAGE_SKIP_UPDATE_CHECK", description: "skip startup update check" },
|
|
3087
|
+
{
|
|
3088
|
+
name: "LLM_USAGE_UPDATE_CACHE_SCOPE",
|
|
3089
|
+
description: "update-check cache scope (global/session)"
|
|
3090
|
+
},
|
|
3091
|
+
{ name: "LLM_USAGE_UPDATE_CACHE_SESSION_KEY", description: "update-check session cache key" },
|
|
3092
|
+
{ name: "LLM_USAGE_UPDATE_CACHE_TTL_MS", description: "update-check cache TTL" },
|
|
3093
|
+
{ name: "LLM_USAGE_UPDATE_FETCH_TIMEOUT_MS", description: "update-check fetch timeout" },
|
|
3094
|
+
{ name: "LLM_USAGE_PRICING_CACHE_TTL_MS", description: "pricing cache TTL" },
|
|
3095
|
+
{ name: "LLM_USAGE_PRICING_FETCH_TIMEOUT_MS", description: "pricing fetch timeout" },
|
|
3096
|
+
{ name: "LLM_USAGE_PARSE_MAX_PARALLEL", description: "max parallel file parsing" },
|
|
3097
|
+
{ name: "LLM_USAGE_PARSE_CACHE_ENABLED", description: "enable file parse cache" },
|
|
3098
|
+
{ name: "LLM_USAGE_PARSE_CACHE_TTL_MS", description: "file parse cache TTL" },
|
|
3099
|
+
{ name: "LLM_USAGE_PARSE_CACHE_MAX_ENTRIES", description: "file parse cache max entries" },
|
|
3100
|
+
{ name: "LLM_USAGE_PARSE_CACHE_MAX_BYTES", description: "file parse cache max bytes" },
|
|
3101
|
+
{ name: "LLM_USAGE_PROFILE_RUNTIME", description: "emit runtime profiling diagnostics" }
|
|
3102
|
+
];
|
|
3103
|
+
function getActiveEnvVarOverrides() {
|
|
3104
|
+
const overrides = [];
|
|
3105
|
+
for (const { name, description } of ENV_VARS_TO_DISPLAY) {
|
|
3106
|
+
const value = process.env[name];
|
|
3107
|
+
if (value !== void 0 && value !== "") {
|
|
3108
|
+
overrides.push({ name, value, description });
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
return overrides;
|
|
3112
|
+
}
|
|
3113
|
+
function formatEnvVarOverrides(overrides) {
|
|
3114
|
+
if (overrides.length === 0) {
|
|
3115
|
+
return [];
|
|
3116
|
+
}
|
|
3117
|
+
const lines = [];
|
|
3118
|
+
lines.push("Active environment overrides:");
|
|
3119
|
+
for (const { name, value, description } of overrides) {
|
|
3120
|
+
lines.push(` ${name}=${value} (${description})`);
|
|
3121
|
+
}
|
|
3122
|
+
return lines;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
// src/sources/codex/codex-source-adapter.ts
|
|
3126
|
+
import os2 from "os";
|
|
3127
|
+
import path6 from "path";
|
|
3128
|
+
|
|
3125
3129
|
// src/utils/discover-files.ts
|
|
3126
|
-
import { readdir } from "fs/promises";
|
|
3130
|
+
import { readdir, realpath as realpath2, stat as stat2 } from "fs/promises";
|
|
3127
3131
|
import path5 from "path";
|
|
3128
3132
|
function getNodeErrorCode2(error) {
|
|
3129
3133
|
const record = asRecord(error);
|
|
@@ -3148,7 +3152,22 @@ function normalizeExtension(extension) {
|
|
|
3148
3152
|
}
|
|
3149
3153
|
return normalized;
|
|
3150
3154
|
}
|
|
3151
|
-
async function walkDirectory(rootDir, acc, options) {
|
|
3155
|
+
async function walkDirectory(rootDir, acc, options, ancestryRealPaths) {
|
|
3156
|
+
let resolvedRootDir;
|
|
3157
|
+
try {
|
|
3158
|
+
resolvedRootDir = await realpath2(rootDir);
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
if (getNodeErrorCode2(error) === "ENOENT") {
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
3163
|
+
if (options.allowPermissionSkip && isSkippableDirectoryReadError(error)) {
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
throw error;
|
|
3167
|
+
}
|
|
3168
|
+
if (ancestryRealPaths.has(resolvedRootDir)) {
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3152
3171
|
let entries;
|
|
3153
3172
|
try {
|
|
3154
3173
|
entries = await readdir(rootDir, { withFileTypes: true, encoding: "utf8" });
|
|
@@ -3161,13 +3180,37 @@ async function walkDirectory(rootDir, acc, options) {
|
|
|
3161
3180
|
}
|
|
3162
3181
|
throw error;
|
|
3163
3182
|
}
|
|
3183
|
+
const nextAncestryRealPaths = new Set(ancestryRealPaths);
|
|
3184
|
+
nextAncestryRealPaths.add(resolvedRootDir);
|
|
3164
3185
|
if (options.sort) {
|
|
3165
3186
|
entries.sort((left, right) => compareByCodePoint(left.name, right.name));
|
|
3166
3187
|
}
|
|
3167
3188
|
for (const entry of entries) {
|
|
3168
3189
|
const entryPath = path5.join(rootDir, entry.name);
|
|
3190
|
+
if (entry.isSymbolicLink()) {
|
|
3191
|
+
try {
|
|
3192
|
+
const entryStats = await stat2(entryPath);
|
|
3193
|
+
const resolvedEntryPath = await realpath2(entryPath);
|
|
3194
|
+
if (entryStats.isDirectory() && options.recursive) {
|
|
3195
|
+
await walkDirectory(entryPath, acc, options, nextAncestryRealPaths);
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
if (entryStats.isFile() && (matchesExtension(entry.name, options.extension) || matchesExtension(path5.basename(resolvedEntryPath), options.extension))) {
|
|
3199
|
+
acc.push(entryPath);
|
|
3200
|
+
}
|
|
3201
|
+
} catch (error) {
|
|
3202
|
+
if (getNodeErrorCode2(error) === "ENOENT") {
|
|
3203
|
+
continue;
|
|
3204
|
+
}
|
|
3205
|
+
if (options.allowPermissionSkip && isSkippableDirectoryReadError(error)) {
|
|
3206
|
+
continue;
|
|
3207
|
+
}
|
|
3208
|
+
throw error;
|
|
3209
|
+
}
|
|
3210
|
+
continue;
|
|
3211
|
+
}
|
|
3169
3212
|
if (entry.isDirectory() && options.recursive) {
|
|
3170
|
-
await walkDirectory(entryPath, acc, options);
|
|
3213
|
+
await walkDirectory(entryPath, acc, options, nextAncestryRealPaths);
|
|
3171
3214
|
continue;
|
|
3172
3215
|
}
|
|
3173
3216
|
if (entry.isFile() && matchesExtension(entry.name, options.extension)) {
|
|
@@ -3175,6 +3218,33 @@ async function walkDirectory(rootDir, acc, options) {
|
|
|
3175
3218
|
}
|
|
3176
3219
|
}
|
|
3177
3220
|
}
|
|
3221
|
+
async function toCanonicalFiles(files, options) {
|
|
3222
|
+
const canonicalFiles = [];
|
|
3223
|
+
const seenRealPaths = /* @__PURE__ */ new Set();
|
|
3224
|
+
for (const filePath of files) {
|
|
3225
|
+
let resolvedFilePath;
|
|
3226
|
+
try {
|
|
3227
|
+
resolvedFilePath = await realpath2(filePath);
|
|
3228
|
+
} catch (error) {
|
|
3229
|
+
if (getNodeErrorCode2(error) === "ENOENT") {
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
3232
|
+
if (options.allowPermissionSkip && isSkippableDirectoryReadError(error)) {
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
throw error;
|
|
3236
|
+
}
|
|
3237
|
+
if (seenRealPaths.has(resolvedFilePath)) {
|
|
3238
|
+
continue;
|
|
3239
|
+
}
|
|
3240
|
+
seenRealPaths.add(resolvedFilePath);
|
|
3241
|
+
canonicalFiles.push(resolvedFilePath);
|
|
3242
|
+
}
|
|
3243
|
+
if (options.sort) {
|
|
3244
|
+
canonicalFiles.sort(compareByCodePoint);
|
|
3245
|
+
}
|
|
3246
|
+
return canonicalFiles;
|
|
3247
|
+
}
|
|
3178
3248
|
async function discoverFiles(rootDir, options) {
|
|
3179
3249
|
const files = [];
|
|
3180
3250
|
const resolvedOptions = {
|
|
@@ -3183,8 +3253,8 @@ async function discoverFiles(rootDir, options) {
|
|
|
3183
3253
|
allowPermissionSkip: options.allowPermissionSkip ?? true,
|
|
3184
3254
|
sort: options.sort ?? true
|
|
3185
3255
|
};
|
|
3186
|
-
await walkDirectory(rootDir, files, resolvedOptions);
|
|
3187
|
-
return files;
|
|
3256
|
+
await walkDirectory(rootDir, files, resolvedOptions, /* @__PURE__ */ new Set());
|
|
3257
|
+
return toCanonicalFiles(files, resolvedOptions);
|
|
3188
3258
|
}
|
|
3189
3259
|
|
|
3190
3260
|
// src/utils/discover-jsonl-files.ts
|
|
@@ -3193,7 +3263,7 @@ async function discoverJsonlFiles(rootDir) {
|
|
|
3193
3263
|
}
|
|
3194
3264
|
|
|
3195
3265
|
// src/utils/fs-helpers.ts
|
|
3196
|
-
import { access as access2, constants as constants2, stat as
|
|
3266
|
+
import { access as access2, constants as constants2, stat as stat3 } from "fs/promises";
|
|
3197
3267
|
async function pathExists(filePath) {
|
|
3198
3268
|
try {
|
|
3199
3269
|
await access2(filePath, constants2.F_OK);
|
|
@@ -3212,21 +3282,21 @@ async function pathReadable(filePath) {
|
|
|
3212
3282
|
}
|
|
3213
3283
|
async function pathIsDirectory(filePath) {
|
|
3214
3284
|
try {
|
|
3215
|
-
return (await
|
|
3285
|
+
return (await stat3(filePath)).isDirectory();
|
|
3216
3286
|
} catch {
|
|
3217
3287
|
return false;
|
|
3218
3288
|
}
|
|
3219
3289
|
}
|
|
3220
3290
|
async function pathIsFile(filePath) {
|
|
3221
3291
|
try {
|
|
3222
|
-
return (await
|
|
3292
|
+
return (await stat3(filePath)).isFile();
|
|
3223
3293
|
} catch {
|
|
3224
3294
|
return false;
|
|
3225
3295
|
}
|
|
3226
3296
|
}
|
|
3227
3297
|
async function pathStat(filePath) {
|
|
3228
3298
|
try {
|
|
3229
|
-
return await
|
|
3299
|
+
return await stat3(filePath);
|
|
3230
3300
|
} catch {
|
|
3231
3301
|
return void 0;
|
|
3232
3302
|
}
|
|
@@ -3274,6 +3344,8 @@ async function* readJsonlObjects(filePath, options = {}) {
|
|
|
3274
3344
|
}
|
|
3275
3345
|
|
|
3276
3346
|
// src/sources/parsing-utils.ts
|
|
3347
|
+
var MIN_PLAUSIBLE_UNIX_SECONDS_ABS = 1e8;
|
|
3348
|
+
var UNIX_SECONDS_ABS_CUTOFF = 1e10;
|
|
3277
3349
|
function asTrimmedText(value) {
|
|
3278
3350
|
if (typeof value !== "string") {
|
|
3279
3351
|
return void 0;
|
|
@@ -3290,6 +3362,38 @@ function toNumberLike(value) {
|
|
|
3290
3362
|
}
|
|
3291
3363
|
return void 0;
|
|
3292
3364
|
}
|
|
3365
|
+
function normalizeTimestampCandidate(candidate) {
|
|
3366
|
+
if (candidate instanceof Date) {
|
|
3367
|
+
return Number.isNaN(candidate.getTime()) ? void 0 : candidate.toISOString();
|
|
3368
|
+
}
|
|
3369
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
3370
|
+
if (Math.abs(candidate) < MIN_PLAUSIBLE_UNIX_SECONDS_ABS) {
|
|
3371
|
+
return void 0;
|
|
3372
|
+
}
|
|
3373
|
+
const timestampMs = Math.abs(candidate) <= UNIX_SECONDS_ABS_CUTOFF ? candidate * 1e3 : candidate;
|
|
3374
|
+
const date2 = new Date(timestampMs);
|
|
3375
|
+
if (Number.isNaN(date2.getTime())) {
|
|
3376
|
+
return void 0;
|
|
3377
|
+
}
|
|
3378
|
+
return date2.toISOString();
|
|
3379
|
+
}
|
|
3380
|
+
const normalizedText = asTrimmedText(candidate);
|
|
3381
|
+
if (!normalizedText) {
|
|
3382
|
+
return void 0;
|
|
3383
|
+
}
|
|
3384
|
+
const numericTimestamp = /^-?\d+$/u.test(normalizedText) && normalizedText.length >= 9 ? Number(normalizedText) : NaN;
|
|
3385
|
+
if (Number.isFinite(numericTimestamp)) {
|
|
3386
|
+
return normalizeTimestampCandidate(numericTimestamp);
|
|
3387
|
+
}
|
|
3388
|
+
if (/^-?\d+$/u.test(normalizedText)) {
|
|
3389
|
+
return void 0;
|
|
3390
|
+
}
|
|
3391
|
+
const date = new Date(normalizedText);
|
|
3392
|
+
if (Number.isNaN(date.getTime())) {
|
|
3393
|
+
return void 0;
|
|
3394
|
+
}
|
|
3395
|
+
return date.toISOString();
|
|
3396
|
+
}
|
|
3293
3397
|
|
|
3294
3398
|
// src/sources/codex/codex-source-adapter.ts
|
|
3295
3399
|
var defaultSessionsDir = path6.join(os2.homedir(), ".codex", "sessions");
|
|
@@ -3345,10 +3449,22 @@ function addUsage(left, right) {
|
|
|
3345
3449
|
function hasUsageSignal(usage) {
|
|
3346
3450
|
return usage.inputTokens > 0 || usage.cacheReadTokens > 0 || usage.outputTokens > 0 || usage.reasoningTokens > 0 || usage.totalTokens > 0;
|
|
3347
3451
|
}
|
|
3452
|
+
function hasUsageRollback(current, previous) {
|
|
3453
|
+
return current.inputTokens < previous.inputTokens || current.cacheReadTokens < previous.cacheReadTokens || current.outputTokens < previous.outputTokens || current.reasoningTokens < previous.reasoningTokens || current.totalTokens < previous.totalTokens;
|
|
3454
|
+
}
|
|
3348
3455
|
function deriveDeltaUsage(info, previousTotalUsage) {
|
|
3349
3456
|
const totalUsage = toUsage(info.total_token_usage);
|
|
3350
3457
|
const lastUsage = toUsage(info.last_token_usage);
|
|
3351
3458
|
if (totalUsage && previousTotalUsage) {
|
|
3459
|
+
if (hasUsageRollback(totalUsage, previousTotalUsage)) {
|
|
3460
|
+
if (lastUsage && hasUsageSignal(lastUsage)) {
|
|
3461
|
+
return { deltaUsage: lastUsage, latestTotalUsage: totalUsage };
|
|
3462
|
+
}
|
|
3463
|
+
return {
|
|
3464
|
+
deltaUsage: hasUsageSignal(totalUsage) ? totalUsage : void 0,
|
|
3465
|
+
latestTotalUsage: totalUsage
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3352
3468
|
const deltaFromTotals = subtractUsage(totalUsage, previousTotalUsage);
|
|
3353
3469
|
if (hasUsageSignal(deltaFromTotals)) {
|
|
3354
3470
|
return { deltaUsage: deltaFromTotals, latestTotalUsage: totalUsage };
|
|
@@ -3356,7 +3472,7 @@ function deriveDeltaUsage(info, previousTotalUsage) {
|
|
|
3356
3472
|
return { latestTotalUsage: totalUsage };
|
|
3357
3473
|
}
|
|
3358
3474
|
if (lastUsage) {
|
|
3359
|
-
return { deltaUsage: lastUsage, latestTotalUsage: totalUsage };
|
|
3475
|
+
return { deltaUsage: lastUsage, latestTotalUsage: totalUsage, fromLastUsageOnly: true };
|
|
3360
3476
|
}
|
|
3361
3477
|
if (!totalUsage) {
|
|
3362
3478
|
return {};
|
|
@@ -3364,6 +3480,16 @@ function deriveDeltaUsage(info, previousTotalUsage) {
|
|
|
3364
3480
|
const deltaUsage = previousTotalUsage ? subtractUsage(totalUsage, previousTotalUsage) : totalUsage;
|
|
3365
3481
|
return { deltaUsage, latestTotalUsage: totalUsage };
|
|
3366
3482
|
}
|
|
3483
|
+
function createLastUsageOnlyKey(timestamp, usage) {
|
|
3484
|
+
return [
|
|
3485
|
+
timestamp,
|
|
3486
|
+
usage.inputTokens,
|
|
3487
|
+
usage.cacheReadTokens,
|
|
3488
|
+
usage.outputTokens,
|
|
3489
|
+
usage.reasoningTokens,
|
|
3490
|
+
usage.totalTokens
|
|
3491
|
+
].join(":");
|
|
3492
|
+
}
|
|
3367
3493
|
function getFallbackSessionId(filePath) {
|
|
3368
3494
|
return path6.basename(filePath, ".jsonl");
|
|
3369
3495
|
}
|
|
@@ -3435,16 +3561,33 @@ var CodexSourceAdapter = class {
|
|
|
3435
3561
|
if (!info) {
|
|
3436
3562
|
continue;
|
|
3437
3563
|
}
|
|
3438
|
-
const { deltaUsage, latestTotalUsage } = deriveDeltaUsage(
|
|
3564
|
+
const { deltaUsage, latestTotalUsage, fromLastUsageOnly } = deriveDeltaUsage(
|
|
3565
|
+
info,
|
|
3566
|
+
state.previousTotalUsage
|
|
3567
|
+
);
|
|
3439
3568
|
if (!deltaUsage || !hasUsageSignal(deltaUsage)) {
|
|
3569
|
+
if (!fromLastUsageOnly) {
|
|
3570
|
+
state.previousLastUsageOnlyKey = void 0;
|
|
3571
|
+
}
|
|
3440
3572
|
state.previousTotalUsage = latestTotalUsage ?? state.previousTotalUsage;
|
|
3441
3573
|
continue;
|
|
3442
3574
|
}
|
|
3443
|
-
const timestamp =
|
|
3575
|
+
const timestamp = normalizeTimestampCandidate(line.timestamp);
|
|
3444
3576
|
if (!timestamp) {
|
|
3445
|
-
|
|
3577
|
+
if (!fromLastUsageOnly) {
|
|
3578
|
+
state.previousLastUsageOnlyKey = void 0;
|
|
3579
|
+
}
|
|
3446
3580
|
continue;
|
|
3447
3581
|
}
|
|
3582
|
+
if (fromLastUsageOnly) {
|
|
3583
|
+
const currentLastUsageOnlyKey = createLastUsageOnlyKey(timestamp, deltaUsage);
|
|
3584
|
+
if (state.previousLastUsageOnlyKey === currentLastUsageOnlyKey) {
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
state.previousLastUsageOnlyKey = currentLastUsageOnlyKey;
|
|
3588
|
+
} else {
|
|
3589
|
+
state.previousLastUsageOnlyKey = void 0;
|
|
3590
|
+
}
|
|
3448
3591
|
const model = state.model ?? LEGACY_CODEX_MODEL_FALLBACK;
|
|
3449
3592
|
try {
|
|
3450
3593
|
events.push(
|
|
@@ -3506,24 +3649,6 @@ var DROID_MESSAGE_LINE_PATTERN = /"type"\s*:\s*"message"/u;
|
|
|
3506
3649
|
function shouldParseDroidJsonlLine(lineText) {
|
|
3507
3650
|
return DROID_SESSION_START_LINE_PATTERN.test(lineText) || DROID_MESSAGE_LINE_PATTERN.test(lineText);
|
|
3508
3651
|
}
|
|
3509
|
-
var UNIX_SECONDS_ABS_CUTOFF = 1e10;
|
|
3510
|
-
function normalizeTimestampCandidate(candidate) {
|
|
3511
|
-
let date;
|
|
3512
|
-
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
3513
|
-
const timestampMs = Math.abs(candidate) <= UNIX_SECONDS_ABS_CUTOFF ? candidate * 1e3 : candidate;
|
|
3514
|
-
date = new Date(timestampMs);
|
|
3515
|
-
} else {
|
|
3516
|
-
const normalizedText = asTrimmedText(candidate);
|
|
3517
|
-
if (!normalizedText) {
|
|
3518
|
-
return void 0;
|
|
3519
|
-
}
|
|
3520
|
-
date = new Date(normalizedText);
|
|
3521
|
-
}
|
|
3522
|
-
if (Number.isNaN(date.getTime())) {
|
|
3523
|
-
return void 0;
|
|
3524
|
-
}
|
|
3525
|
-
return date.toISOString();
|
|
3526
|
-
}
|
|
3527
3652
|
function getSettingsSessionId(filePath) {
|
|
3528
3653
|
return path7.basename(filePath, ".settings.json");
|
|
3529
3654
|
}
|
|
@@ -3604,7 +3729,8 @@ var DroidSourceAdapter = class {
|
|
|
3604
3729
|
toNumberLike(tokenUsage.cacheCreationTokens)
|
|
3605
3730
|
);
|
|
3606
3731
|
const billableTokens = inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens;
|
|
3607
|
-
|
|
3732
|
+
const hasUsageSignal3 = billableTokens > 0 || reasoningTokens > 0;
|
|
3733
|
+
if (!hasUsageSignal3) {
|
|
3608
3734
|
skippedRows++;
|
|
3609
3735
|
incrementSkippedReason(skippedRowReasons, "no_token_usage");
|
|
3610
3736
|
return toParseDiagnostics(events, skippedRows, skippedRowReasons);
|
|
@@ -3724,7 +3850,7 @@ async function discoverSessionFiles(geminiDir) {
|
|
|
3724
3850
|
for (const projectEntry of projectEntries.sort(
|
|
3725
3851
|
(left, right) => compareByCodePoint(left.name, right.name)
|
|
3726
3852
|
)) {
|
|
3727
|
-
if (!projectEntry.isDirectory()) {
|
|
3853
|
+
if (!projectEntry.isDirectory() && !projectEntry.isSymbolicLink()) {
|
|
3728
3854
|
continue;
|
|
3729
3855
|
}
|
|
3730
3856
|
const chatsDir = path8.join(tmpDir, projectEntry.name, "chats");
|
|
@@ -3790,16 +3916,6 @@ function extractTokenUsage(tokens) {
|
|
|
3790
3916
|
totalTokens
|
|
3791
3917
|
};
|
|
3792
3918
|
}
|
|
3793
|
-
function normalizeTimestamp2(candidate) {
|
|
3794
|
-
if (typeof candidate !== "string" || isBlankText(candidate)) {
|
|
3795
|
-
return void 0;
|
|
3796
|
-
}
|
|
3797
|
-
const date = new Date(candidate.trim());
|
|
3798
|
-
if (Number.isNaN(date.getTime())) {
|
|
3799
|
-
return void 0;
|
|
3800
|
-
}
|
|
3801
|
-
return date.toISOString();
|
|
3802
|
-
}
|
|
3803
3919
|
var GeminiSourceAdapter = class {
|
|
3804
3920
|
id = "gemini";
|
|
3805
3921
|
capabilities = {
|
|
@@ -3889,7 +4005,7 @@ var GeminiSourceAdapter = class {
|
|
|
3889
4005
|
incrementSkippedReason(skippedRowReasons, "no_token_usage");
|
|
3890
4006
|
continue;
|
|
3891
4007
|
}
|
|
3892
|
-
const timestamp =
|
|
4008
|
+
const timestamp = normalizeTimestampCandidate(message.timestamp);
|
|
3893
4009
|
if (!timestamp) {
|
|
3894
4010
|
skippedRows++;
|
|
3895
4011
|
incrementSkippedReason(skippedRowReasons, "invalid_timestamp");
|
|
@@ -3924,8 +4040,15 @@ import path9 from "path";
|
|
|
3924
4040
|
function deduplicate(paths) {
|
|
3925
4041
|
return [...new Set(paths)];
|
|
3926
4042
|
}
|
|
4043
|
+
function normalizeEnvPath(value) {
|
|
4044
|
+
if (value === void 0) {
|
|
4045
|
+
return void 0;
|
|
4046
|
+
}
|
|
4047
|
+
const normalized = value.trim();
|
|
4048
|
+
return normalized || void 0;
|
|
4049
|
+
}
|
|
3927
4050
|
function getLinuxLikeCandidates(homeDir, env) {
|
|
3928
|
-
const xdgDataHome = env.XDG_DATA_HOME ?? path9.join(homeDir, ".local", "share");
|
|
4051
|
+
const xdgDataHome = normalizeEnvPath(env.XDG_DATA_HOME) ?? path9.join(homeDir, ".local", "share");
|
|
3929
4052
|
return [
|
|
3930
4053
|
path9.join(xdgDataHome, "opencode", "opencode.db"),
|
|
3931
4054
|
path9.join(xdgDataHome, "opencode", "db.sqlite"),
|
|
@@ -3943,7 +4066,8 @@ function getMacOsCandidates(homeDir) {
|
|
|
3943
4066
|
];
|
|
3944
4067
|
}
|
|
3945
4068
|
function getWindowsCandidates(homeDir, env) {
|
|
3946
|
-
const
|
|
4069
|
+
const userProfile = normalizeEnvPath(env.USERPROFILE);
|
|
4070
|
+
const roamingBase = normalizeEnvPath(env.APPDATA) ?? normalizeEnvPath(env.LOCALAPPDATA) ?? (userProfile ? path9.join(userProfile, "AppData", "Roaming") : void 0);
|
|
3947
4071
|
const roamingCandidates = roamingBase ? [
|
|
3948
4072
|
path9.join(roamingBase, "opencode", "opencode.db"),
|
|
3949
4073
|
path9.join(roamingBase, "opencode", "db.sqlite")
|
|
@@ -4022,33 +4146,6 @@ async function loadNodeSqliteModule() {
|
|
|
4022
4146
|
}
|
|
4023
4147
|
|
|
4024
4148
|
// src/sources/opencode/opencode-row-parser.ts
|
|
4025
|
-
var UNIX_SECONDS_ABS_CUTOFF2 = 1e10;
|
|
4026
|
-
function normalizeTimestampCandidate2(candidate) {
|
|
4027
|
-
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
4028
|
-
const timestampMs = Math.abs(candidate) <= UNIX_SECONDS_ABS_CUTOFF2 ? candidate * 1e3 : candidate;
|
|
4029
|
-
const date = new Date(timestampMs);
|
|
4030
|
-
if (Number.isNaN(date.getTime())) {
|
|
4031
|
-
return void 0;
|
|
4032
|
-
}
|
|
4033
|
-
return date.toISOString();
|
|
4034
|
-
}
|
|
4035
|
-
if (typeof candidate === "string") {
|
|
4036
|
-
const trimmed = candidate.trim();
|
|
4037
|
-
if (!trimmed) {
|
|
4038
|
-
return void 0;
|
|
4039
|
-
}
|
|
4040
|
-
const numericTimestamp = Number(trimmed);
|
|
4041
|
-
if (Number.isFinite(numericTimestamp)) {
|
|
4042
|
-
return normalizeTimestampCandidate2(numericTimestamp);
|
|
4043
|
-
}
|
|
4044
|
-
const date = new Date(trimmed);
|
|
4045
|
-
if (Number.isNaN(date.getTime())) {
|
|
4046
|
-
return void 0;
|
|
4047
|
-
}
|
|
4048
|
-
return date.toISOString();
|
|
4049
|
-
}
|
|
4050
|
-
return void 0;
|
|
4051
|
-
}
|
|
4052
4149
|
function resolveTimestamp(rowTimestamp, messagePayload) {
|
|
4053
4150
|
const timestampCandidates = [
|
|
4054
4151
|
rowTimestamp,
|
|
@@ -4057,7 +4154,7 @@ function resolveTimestamp(rowTimestamp, messagePayload) {
|
|
|
4057
4154
|
messagePayload.time_created
|
|
4058
4155
|
];
|
|
4059
4156
|
for (const candidate of timestampCandidates) {
|
|
4060
|
-
const resolved =
|
|
4157
|
+
const resolved = normalizeTimestampCandidate(candidate);
|
|
4061
4158
|
if (resolved) {
|
|
4062
4159
|
return resolved;
|
|
4063
4160
|
}
|
|
@@ -4481,28 +4578,10 @@ var PI_MODEL_CHANGE_LINE_PATTERN = /"type"\s*:\s*"model_change"/u;
|
|
|
4481
4578
|
function shouldParsePiJsonlLine(lineText) {
|
|
4482
4579
|
return PI_MESSAGE_LINE_PATTERN.test(lineText) || PI_SESSION_LINE_PATTERN.test(lineText) || PI_MODEL_CHANGE_LINE_PATTERN.test(lineText);
|
|
4483
4580
|
}
|
|
4484
|
-
var UNIX_SECONDS_ABS_CUTOFF3 = 1e10;
|
|
4485
|
-
function normalizeTimestampCandidate3(candidate) {
|
|
4486
|
-
let date;
|
|
4487
|
-
if (typeof candidate === "number" && Number.isFinite(candidate)) {
|
|
4488
|
-
const timestampMs = Math.abs(candidate) <= UNIX_SECONDS_ABS_CUTOFF3 ? candidate * 1e3 : candidate;
|
|
4489
|
-
date = new Date(timestampMs);
|
|
4490
|
-
} else {
|
|
4491
|
-
const normalizedText = asTrimmedText(candidate);
|
|
4492
|
-
if (!normalizedText) {
|
|
4493
|
-
return void 0;
|
|
4494
|
-
}
|
|
4495
|
-
date = new Date(normalizedText);
|
|
4496
|
-
}
|
|
4497
|
-
if (Number.isNaN(date.getTime())) {
|
|
4498
|
-
return void 0;
|
|
4499
|
-
}
|
|
4500
|
-
return date.toISOString();
|
|
4501
|
-
}
|
|
4502
4581
|
function resolveTimestamp2(line, message, state) {
|
|
4503
4582
|
const candidates = [line.timestamp, message?.timestamp, state.sessionTimestamp];
|
|
4504
4583
|
for (const candidate of candidates) {
|
|
4505
|
-
const normalizedTimestamp =
|
|
4584
|
+
const normalizedTimestamp = normalizeTimestampCandidate(candidate);
|
|
4506
4585
|
if (normalizedTimestamp) {
|
|
4507
4586
|
return normalizedTimestamp;
|
|
4508
4587
|
}
|
|
@@ -5027,7 +5106,7 @@ function throwOnExplicitSourceScopeConflicts(adapters, selectedAdapters, options
|
|
|
5027
5106
|
}
|
|
5028
5107
|
|
|
5029
5108
|
// src/cli/build-usage-data-parsing.ts
|
|
5030
|
-
import { stat as
|
|
5109
|
+
import { stat as stat5 } from "fs/promises";
|
|
5031
5110
|
|
|
5032
5111
|
// src/cli/normalize-skipped-row-reasons.ts
|
|
5033
5112
|
function toPositiveInteger(value) {
|
|
@@ -5055,7 +5134,7 @@ function normalizeSkippedRowReasons(value) {
|
|
|
5055
5134
|
}
|
|
5056
5135
|
|
|
5057
5136
|
// src/cli/parse-file-cache.ts
|
|
5058
|
-
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as
|
|
5137
|
+
import { mkdir as mkdir2, readFile as readFile4, rename, rm, stat as stat4, writeFile as writeFile2 } from "fs/promises";
|
|
5059
5138
|
import path11 from "path";
|
|
5060
5139
|
var PARSE_FILE_CACHE_VERSION = 5;
|
|
5061
5140
|
var CACHE_KEY_SEPARATOR = "\0";
|
|
@@ -5148,7 +5227,7 @@ function cloneUsageEvents(events) {
|
|
|
5148
5227
|
return events.map((event) => cloneUsageEvent(event));
|
|
5149
5228
|
}
|
|
5150
5229
|
function cloneSkippedRowReasons(skippedRowReasons) {
|
|
5151
|
-
return (skippedRowReasons ?? []).map((
|
|
5230
|
+
return (skippedRowReasons ?? []).map((stat6) => ({ reason: stat6.reason, count: stat6.count }));
|
|
5152
5231
|
}
|
|
5153
5232
|
function normalizeCachedEvents(value) {
|
|
5154
5233
|
if (!Array.isArray(value)) {
|
|
@@ -5429,7 +5508,7 @@ var ParseFileCache = class _ParseFileCache {
|
|
|
5429
5508
|
async loadFromDisk() {
|
|
5430
5509
|
let cacheFileSizeBytes;
|
|
5431
5510
|
try {
|
|
5432
|
-
const cacheStat = await
|
|
5511
|
+
const cacheStat = await stat4(this.cacheFilePath);
|
|
5433
5512
|
cacheFileSizeBytes = cacheStat.size;
|
|
5434
5513
|
} catch {
|
|
5435
5514
|
return;
|
|
@@ -5504,7 +5583,7 @@ function isMissingPathError(error) {
|
|
|
5504
5583
|
}
|
|
5505
5584
|
async function createParseDependencyFingerprint(filePath, options) {
|
|
5506
5585
|
try {
|
|
5507
|
-
const fileStat = await
|
|
5586
|
+
const fileStat = await stat5(filePath);
|
|
5508
5587
|
return {
|
|
5509
5588
|
path: filePath,
|
|
5510
5589
|
exists: true,
|
|
@@ -6884,7 +6963,7 @@ function resolveScopeNote(options) {
|
|
|
6884
6963
|
return `Usage filters (${activeFilters.join(", ")}) affect commit attribution too: only commit days with matching repo-attributed usage events are counted.`;
|
|
6885
6964
|
}
|
|
6886
6965
|
function hasMeaningfulEfficiencyUsageSignal(event) {
|
|
6887
|
-
return event.totalTokens > 0 || event.costUsd !== void 0
|
|
6966
|
+
return event.totalTokens > 0 || hasBillableTokenBuckets(event) || event.costUsd !== void 0;
|
|
6888
6967
|
}
|
|
6889
6968
|
async function buildEfficiencyData(granularity, options, deps = {}) {
|
|
6890
6969
|
const buildDataset = deps.buildUsageEventDataset ?? buildUsageEventDataset;
|
|
@@ -7654,6 +7733,9 @@ var USD_PRECISION_SCALE3 = 1e12;
|
|
|
7654
7733
|
function roundUsd(value) {
|
|
7655
7734
|
return Math.round(value * USD_PRECISION_SCALE3) / USD_PRECISION_SCALE3;
|
|
7656
7735
|
}
|
|
7736
|
+
function addUsd3(left, right) {
|
|
7737
|
+
return roundUsd(left + right);
|
|
7738
|
+
}
|
|
7657
7739
|
function hasAnyUsageSignal(period) {
|
|
7658
7740
|
return period.totalTokens > 0 || period.baselineCostIncomplete || (period.baselineCostUsd ?? 0) > 0;
|
|
7659
7741
|
}
|
|
@@ -7811,9 +7893,22 @@ function resolveBaselinePeriods(usageRows) {
|
|
|
7811
7893
|
periodRows.set(row.periodKey, row);
|
|
7812
7894
|
continue;
|
|
7813
7895
|
}
|
|
7814
|
-
|
|
7896
|
+
const existingRow = periodRows.get(row.periodKey);
|
|
7897
|
+
if (!existingRow) {
|
|
7815
7898
|
periodRows.set(row.periodKey, row);
|
|
7899
|
+
continue;
|
|
7816
7900
|
}
|
|
7901
|
+
periodRows.set(row.periodKey, {
|
|
7902
|
+
...existingRow,
|
|
7903
|
+
inputTokens: existingRow.inputTokens + row.inputTokens,
|
|
7904
|
+
outputTokens: existingRow.outputTokens + row.outputTokens,
|
|
7905
|
+
reasoningTokens: existingRow.reasoningTokens + row.reasoningTokens,
|
|
7906
|
+
cacheReadTokens: existingRow.cacheReadTokens + row.cacheReadTokens,
|
|
7907
|
+
cacheWriteTokens: existingRow.cacheWriteTokens + row.cacheWriteTokens,
|
|
7908
|
+
totalTokens: existingRow.totalTokens + row.totalTokens,
|
|
7909
|
+
costUsd: row.costUsd !== void 0 ? addUsd3(existingRow.costUsd ?? 0, row.costUsd) : existingRow.costUsd,
|
|
7910
|
+
costIncomplete: existingRow.costIncomplete === true || row.costIncomplete === true ? true : void 0
|
|
7911
|
+
});
|
|
7817
7912
|
}
|
|
7818
7913
|
const sortedPeriodKeys = [...periodRows.keys()].sort(compareByCodePoint);
|
|
7819
7914
|
const periods = sortedPeriodKeys.map((periodKey) => {
|
|
@@ -8403,6 +8498,13 @@ function renderTrendsReport(trendsData, format, options = {}) {
|
|
|
8403
8498
|
}
|
|
8404
8499
|
|
|
8405
8500
|
// src/trends/aggregate-trends.ts
|
|
8501
|
+
var VALUE_PRECISION_SCALE = 1e12;
|
|
8502
|
+
function addValue(left, right) {
|
|
8503
|
+
return Math.round((left + right) * VALUE_PRECISION_SCALE) / VALUE_PRECISION_SCALE;
|
|
8504
|
+
}
|
|
8505
|
+
function divideValue(value, divisor) {
|
|
8506
|
+
return Math.round(value / divisor * VALUE_PRECISION_SCALE) / VALUE_PRECISION_SCALE;
|
|
8507
|
+
}
|
|
8406
8508
|
function toTrendBucket(row, metric) {
|
|
8407
8509
|
return {
|
|
8408
8510
|
date: row.periodKey,
|
|
@@ -8431,12 +8533,12 @@ function buildTrendSummary(buckets) {
|
|
|
8431
8533
|
observedDayCount: 0
|
|
8432
8534
|
};
|
|
8433
8535
|
}
|
|
8434
|
-
const total = buckets.reduce((sum, bucket) => sum
|
|
8536
|
+
const total = buckets.reduce((sum, bucket) => addValue(sum, bucket.value), 0);
|
|
8435
8537
|
const observedBuckets = buckets.filter((bucket) => bucket.observed);
|
|
8436
8538
|
if (observedBuckets.length === 0) {
|
|
8437
8539
|
return {
|
|
8438
8540
|
total,
|
|
8439
|
-
average: buckets.length > 0 ? total
|
|
8541
|
+
average: buckets.length > 0 ? divideValue(total, buckets.length) : 0,
|
|
8440
8542
|
peak: {
|
|
8441
8543
|
date: "",
|
|
8442
8544
|
value: 0
|
|
@@ -8452,7 +8554,7 @@ function buildTrendSummary(buckets) {
|
|
|
8452
8554
|
);
|
|
8453
8555
|
return {
|
|
8454
8556
|
total,
|
|
8455
|
-
average: buckets.length > 0 ? total
|
|
8557
|
+
average: buckets.length > 0 ? divideValue(total, buckets.length) : 0,
|
|
8456
8558
|
peak: {
|
|
8457
8559
|
date: peak.date,
|
|
8458
8560
|
value: peak.value
|
|
@@ -8472,6 +8574,46 @@ function buildSeries(source, rowsByDate, dateKeys, metric) {
|
|
|
8472
8574
|
summary: buildTrendSummary(buckets)
|
|
8473
8575
|
};
|
|
8474
8576
|
}
|
|
8577
|
+
function createEmptyUsageRow(periodKey, rowType, source) {
|
|
8578
|
+
return rowType === "period_combined" ? {
|
|
8579
|
+
rowType,
|
|
8580
|
+
periodKey,
|
|
8581
|
+
source: "combined",
|
|
8582
|
+
models: [],
|
|
8583
|
+
modelBreakdown: [],
|
|
8584
|
+
inputTokens: 0,
|
|
8585
|
+
outputTokens: 0,
|
|
8586
|
+
reasoningTokens: 0,
|
|
8587
|
+
cacheReadTokens: 0,
|
|
8588
|
+
cacheWriteTokens: 0,
|
|
8589
|
+
totalTokens: 0
|
|
8590
|
+
} : {
|
|
8591
|
+
rowType,
|
|
8592
|
+
periodKey,
|
|
8593
|
+
source,
|
|
8594
|
+
models: [],
|
|
8595
|
+
modelBreakdown: [],
|
|
8596
|
+
inputTokens: 0,
|
|
8597
|
+
outputTokens: 0,
|
|
8598
|
+
reasoningTokens: 0,
|
|
8599
|
+
cacheReadTokens: 0,
|
|
8600
|
+
cacheWriteTokens: 0,
|
|
8601
|
+
totalTokens: 0
|
|
8602
|
+
};
|
|
8603
|
+
}
|
|
8604
|
+
function addRowTotals(target, row) {
|
|
8605
|
+
return {
|
|
8606
|
+
...target,
|
|
8607
|
+
inputTokens: target.inputTokens + row.inputTokens,
|
|
8608
|
+
outputTokens: target.outputTokens + row.outputTokens,
|
|
8609
|
+
reasoningTokens: target.reasoningTokens + row.reasoningTokens,
|
|
8610
|
+
cacheReadTokens: target.cacheReadTokens + row.cacheReadTokens,
|
|
8611
|
+
cacheWriteTokens: target.cacheWriteTokens + row.cacheWriteTokens,
|
|
8612
|
+
totalTokens: target.totalTokens + row.totalTokens,
|
|
8613
|
+
costUsd: row.costUsd !== void 0 ? addValue(target.costUsd ?? 0, row.costUsd) : target.costUsd,
|
|
8614
|
+
costIncomplete: target.costIncomplete === true || row.costIncomplete === true ? true : void 0
|
|
8615
|
+
};
|
|
8616
|
+
}
|
|
8475
8617
|
function toCombinedRowsByDate(rows) {
|
|
8476
8618
|
const combinedByDate = /* @__PURE__ */ new Map();
|
|
8477
8619
|
const sourceOnlyByDate = /* @__PURE__ */ new Map();
|
|
@@ -8483,9 +8625,8 @@ function toCombinedRowsByDate(rows) {
|
|
|
8483
8625
|
combinedByDate.set(row.periodKey, row);
|
|
8484
8626
|
continue;
|
|
8485
8627
|
}
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
}
|
|
8628
|
+
const existingSourceOnlyRow = sourceOnlyByDate.get(row.periodKey) ?? createEmptyUsageRow(row.periodKey, "period_combined", "combined");
|
|
8629
|
+
sourceOnlyByDate.set(row.periodKey, addRowTotals(existingSourceOnlyRow, row));
|
|
8489
8630
|
}
|
|
8490
8631
|
const resolved = /* @__PURE__ */ new Map();
|
|
8491
8632
|
for (const [date, row] of sourceOnlyByDate) {
|
|
@@ -8506,7 +8647,8 @@ function toSourceSeries(rows, dateKeys, options) {
|
|
|
8506
8647
|
continue;
|
|
8507
8648
|
}
|
|
8508
8649
|
const sourceRows = rowsBySource.get(row.source) ?? /* @__PURE__ */ new Map();
|
|
8509
|
-
sourceRows.
|
|
8650
|
+
const existingSourceRow = sourceRows.get(row.periodKey) ?? createEmptyUsageRow(row.periodKey, "period_source", row.source);
|
|
8651
|
+
sourceRows.set(row.periodKey, addRowTotals(existingSourceRow, row));
|
|
8510
8652
|
rowsBySource.set(row.source, sourceRows);
|
|
8511
8653
|
}
|
|
8512
8654
|
const observedSources = [...rowsBySource.keys()].sort((left, right) => {
|