@agent-finops/core 0.1.3 → 0.1.4
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/analyze.js +7 -3
- package/dist/cutList.d.ts +6 -0
- package/dist/cutList.js +26 -3
- package/dist/deadContext.js +1 -0
- package/dist/planMath.js +7 -3
- package/package.json +1 -1
package/dist/analyze.js
CHANGED
|
@@ -165,7 +165,11 @@ export function generateRecommendations(records) {
|
|
|
165
165
|
relatedKeys: unique(highInputTokenRecords.map((record) => record.model))
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
|
-
|
|
168
|
+
// Session aggregates from local agent logs share one operation label per
|
|
169
|
+
// agent — that's aggregation, not repetition, and a result cache is not a
|
|
170
|
+
// real lever for interactive coding sessions. Exclude them here.
|
|
171
|
+
const cacheableRecords = records.filter((record) => record.providerCostType !== "local_agent_logs");
|
|
172
|
+
const repeatedOperations = repeatedValues(cacheableRecords.map((record) => record.operation).filter(isPresent));
|
|
169
173
|
if (repeatedOperations.length > 0) {
|
|
170
174
|
recommendations.push({
|
|
171
175
|
id: "caching",
|
|
@@ -174,8 +178,8 @@ export function generateRecommendations(records) {
|
|
|
174
178
|
whyItMatters: "Repeated AI calls are the easiest spend to defend cutting because they usually do not change the customer experience.",
|
|
175
179
|
nextAction: "Add a local cache or memoization policy for repeated operation labels before expanding this workflow to more clients.",
|
|
176
180
|
priority: "medium",
|
|
177
|
-
estimatedImpactUsd: roundMoney(sumRecords(
|
|
178
|
-
confidence: combinedConfidence(
|
|
181
|
+
estimatedImpactUsd: roundMoney(sumRecords(cacheableRecords.filter((record) => repeatedOperations.includes(record.operation ?? ""))) * impactRatios.caching),
|
|
182
|
+
confidence: combinedConfidence(cacheableRecords.map((record) => record.costConfidence)),
|
|
179
183
|
relatedKeys: repeatedOperations
|
|
180
184
|
});
|
|
181
185
|
}
|
package/dist/cutList.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export type CutAction = {
|
|
|
19
19
|
affectedSpendUsd: number;
|
|
20
20
|
/** How many usage records this action is grounded in. */
|
|
21
21
|
recordCount: number;
|
|
22
|
+
/**
|
|
23
|
+
* What one record represents, for honest grounding lines. Local agent logs
|
|
24
|
+
* aggregate a day of sessions into one record, so calling those "calls"
|
|
25
|
+
* overstates precision to the exact audience that will check.
|
|
26
|
+
*/
|
|
27
|
+
recordUnit: "calls" | "session-days" | "tools";
|
|
22
28
|
/** Lowest confidence of the underlying records (drives how we caveat $). */
|
|
23
29
|
confidence: CostConfidence;
|
|
24
30
|
kind: "model_downgrade" | "context_trim" | "cache" | "batch";
|
package/dist/cutList.js
CHANGED
|
@@ -111,6 +111,7 @@ function modelDowngradeActions(records) {
|
|
|
111
111
|
estimatedMonthlySavingsUsd: monthlySavings,
|
|
112
112
|
affectedSpendUsd,
|
|
113
113
|
recordCount: groupRecords.length,
|
|
114
|
+
recordUnit: groupRecords.every(isLocalAgentRecord) ? "session-days" : "calls",
|
|
114
115
|
recordIds: groupRecords.map((record) => record.id),
|
|
115
116
|
confidence: combinedConfidence(groupRecords.map((record) => record.costConfidence)),
|
|
116
117
|
kind: "model_downgrade"
|
|
@@ -136,13 +137,22 @@ function contextTrimActions(records) {
|
|
|
136
137
|
// input-token cost on these large calls.
|
|
137
138
|
const windowSavings = affectedSpendUsd * 0.25;
|
|
138
139
|
const monthlySavings = roundMoney(toMonthly(windowSavings, window));
|
|
140
|
+
const sessionAggregates = groupRecords.every(isLocalAgentRecord);
|
|
141
|
+
const count = groupRecords.length;
|
|
139
142
|
actions.push({
|
|
140
143
|
id: `trim-${slug(operation)}`,
|
|
141
|
-
title:
|
|
142
|
-
|
|
144
|
+
title: sessionAggregates
|
|
145
|
+
? `Trim heavy context in ${operation}`
|
|
146
|
+
: `Trim oversized context on ${operation}`,
|
|
147
|
+
// Coding-agent sessions are aggregated per day — the honest levers are
|
|
148
|
+
// the context loaded every turn, not "prompt size" on a single call.
|
|
149
|
+
action: sessionAggregates
|
|
150
|
+
? `${count} session-day${count === 1 ? "" : "s"} averaged >=100k input tokens per record. Cut dead context first (unused MCP servers/skills — see above), keep CLAUDE.md/AGENTS.md lean, and avoid pulling whole directories into context.`
|
|
151
|
+
: `Cap retrieval/prompt size on ${count} large ${operation} call${count === 1 ? "" : "s"} (>=100k input tokens) before they fan out.`,
|
|
143
152
|
estimatedMonthlySavingsUsd: monthlySavings,
|
|
144
153
|
affectedSpendUsd,
|
|
145
|
-
recordCount:
|
|
154
|
+
recordCount: count,
|
|
155
|
+
recordUnit: sessionAggregates ? "session-days" : "calls",
|
|
146
156
|
recordIds: groupRecords.map((record) => record.id),
|
|
147
157
|
confidence: combinedConfidence(groupRecords.map((record) => record.costConfidence)),
|
|
148
158
|
kind: "context_trim"
|
|
@@ -157,6 +167,13 @@ function cacheActions(records) {
|
|
|
157
167
|
if (!record.operation) {
|
|
158
168
|
continue;
|
|
159
169
|
}
|
|
170
|
+
// Local agent logs aggregate interactive sessions under one operation
|
|
171
|
+
// label ("claude-code sessions"). Those are NOT repeated identical calls —
|
|
172
|
+
// a result cache is not a real lever there (prompt caching already applies
|
|
173
|
+
// and is priced into the estimate), so recommending one would be wrong.
|
|
174
|
+
if (isLocalAgentRecord(record)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
160
177
|
counts.set(record.operation, [...(counts.get(record.operation) ?? []), record]);
|
|
161
178
|
}
|
|
162
179
|
const actions = [];
|
|
@@ -175,6 +192,7 @@ function cacheActions(records) {
|
|
|
175
192
|
estimatedMonthlySavingsUsd: monthlySavings,
|
|
176
193
|
affectedSpendUsd,
|
|
177
194
|
recordCount: groupRecords.length,
|
|
195
|
+
recordUnit: "calls",
|
|
178
196
|
recordIds: groupRecords.map((record) => record.id),
|
|
179
197
|
confidence: combinedConfidence(groupRecords.map((record) => record.costConfidence)),
|
|
180
198
|
kind: "cache"
|
|
@@ -206,6 +224,7 @@ function batchActions(records) {
|
|
|
206
224
|
estimatedMonthlySavingsUsd: monthlySavings,
|
|
207
225
|
affectedSpendUsd,
|
|
208
226
|
recordCount: groupRecords.length,
|
|
227
|
+
recordUnit: "calls",
|
|
209
228
|
recordIds: groupRecords.map((record) => record.id),
|
|
210
229
|
confidence: combinedConfidence(groupRecords.map((record) => record.costConfidence)),
|
|
211
230
|
kind: "batch"
|
|
@@ -213,6 +232,10 @@ function batchActions(records) {
|
|
|
213
232
|
}
|
|
214
233
|
return actions;
|
|
215
234
|
}
|
|
235
|
+
/** Records ingested from local agent transcripts (day-level session aggregates). */
|
|
236
|
+
function isLocalAgentRecord(record) {
|
|
237
|
+
return record.providerCostType === "local_agent_logs";
|
|
238
|
+
}
|
|
216
239
|
/** Number of distinct calendar days the records span (min 1). */
|
|
217
240
|
function windowDays(records) {
|
|
218
241
|
const days = new Set(records.map((record) => record.timestamp.slice(0, 10)));
|
package/dist/deadContext.js
CHANGED
|
@@ -135,6 +135,7 @@ export function deadContextCutAction(result) {
|
|
|
135
135
|
estimatedMonthlySavingsUsd: result.monthlyUsd,
|
|
136
136
|
affectedSpendUsd: result.monthlyUsd,
|
|
137
137
|
recordCount: result.deadCount,
|
|
138
|
+
recordUnit: "tools",
|
|
138
139
|
// Dead-context savings come from inventory, not priced usage records, so
|
|
139
140
|
// there are no record IDs to dedupe against the spend-based cut actions.
|
|
140
141
|
recordIds: [],
|
package/dist/planMath.js
CHANGED
|
@@ -31,15 +31,19 @@ export function computePlanChecks(records) {
|
|
|
31
31
|
const candidates = subscriptionPlans.filter((plan) => plan.agent === agent);
|
|
32
32
|
const suggested = candidates.find((plan) => monthly <= plan.coversUpToUsd) ?? candidates[candidates.length - 1];
|
|
33
33
|
const savings = suggested ? roundMoney(monthly - suggested.monthlyUsd) : undefined;
|
|
34
|
+
// Always state the projection basis: this number divides by ACTIVE days
|
|
35
|
+
// (days with usage), which can differ from the calendar window shown
|
|
36
|
+
// elsewhere on the readout — a technical reader will divide and check.
|
|
37
|
+
const basis = `projected from ${windowDays} active day${windowDays === 1 ? "" : "s"}`;
|
|
34
38
|
let headline;
|
|
35
39
|
if (!suggested) {
|
|
36
|
-
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates.`;
|
|
40
|
+
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}).`;
|
|
37
41
|
}
|
|
38
42
|
else if (typeof savings === "number" && savings > 0) {
|
|
39
|
-
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates — ${suggested.name} ($${suggested.monthlyUsd}/mo) likely covers this, ~$${savings.toFixed(2)}/mo cheaper than paying per token.`;
|
|
43
|
+
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — ${suggested.name} ($${suggested.monthlyUsd}/mo) likely covers this, ~$${savings.toFixed(2)}/mo cheaper than paying per token.`;
|
|
40
44
|
}
|
|
41
45
|
else {
|
|
42
|
-
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates — within ${suggested.name} ($${suggested.monthlyUsd}/mo); pay-as-you-go API could be cheaper if you drop the subscription.`;
|
|
46
|
+
headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — within ${suggested.name} ($${suggested.monthlyUsd}/mo); pay-as-you-go API could be cheaper if you drop the subscription.`;
|
|
43
47
|
}
|
|
44
48
|
checks.push({
|
|
45
49
|
agent,
|