token-optimizer-opencode 1.0.6 → 1.0.13

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.
Files changed (54) hide show
  1. package/dist/continuity/matcher.d.ts +18 -0
  2. package/dist/continuity/matcher.d.ts.map +1 -1
  3. package/dist/continuity/matcher.js +34 -1
  4. package/dist/continuity/matcher.js.map +1 -1
  5. package/dist/continuity/restore.d.ts +8 -1
  6. package/dist/continuity/restore.d.ts.map +1 -1
  7. package/dist/continuity/restore.js +43 -1
  8. package/dist/continuity/restore.js.map +1 -1
  9. package/dist/continuity/resume-lean.d.ts +126 -0
  10. package/dist/continuity/resume-lean.d.ts.map +1 -0
  11. package/dist/continuity/resume-lean.js +437 -0
  12. package/dist/continuity/resume-lean.js.map +1 -0
  13. package/dist/dashboard/generator.d.ts.map +1 -1
  14. package/dist/dashboard/generator.js +232 -36
  15. package/dist/dashboard/generator.js.map +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +90 -4
  18. package/dist/index.js.map +1 -1
  19. package/dist/nudges/fresh-session-nudge.d.ts +72 -0
  20. package/dist/nudges/fresh-session-nudge.d.ts.map +1 -0
  21. package/dist/nudges/fresh-session-nudge.js +190 -0
  22. package/dist/nudges/fresh-session-nudge.js.map +1 -0
  23. package/dist/nudges/verbosity-steer.d.ts +28 -0
  24. package/dist/nudges/verbosity-steer.d.ts.map +1 -0
  25. package/dist/nudges/verbosity-steer.js +61 -0
  26. package/dist/nudges/verbosity-steer.js.map +1 -0
  27. package/dist/pricing.d.ts +58 -0
  28. package/dist/pricing.d.ts.map +1 -0
  29. package/dist/pricing.js +307 -0
  30. package/dist/pricing.js.map +1 -0
  31. package/dist/savings.baseline.test.d.ts +2 -0
  32. package/dist/savings.baseline.test.d.ts.map +1 -0
  33. package/dist/savings.baseline.test.js +100 -0
  34. package/dist/savings.baseline.test.js.map +1 -0
  35. package/dist/savings.d.ts +41 -3
  36. package/dist/savings.d.ts.map +1 -1
  37. package/dist/savings.js +296 -86
  38. package/dist/savings.js.map +1 -1
  39. package/dist/storage/trends.d.ts +74 -0
  40. package/dist/storage/trends.d.ts.map +1 -1
  41. package/dist/storage/trends.js +199 -0
  42. package/dist/storage/trends.js.map +1 -1
  43. package/dist/util/context-window.d.ts.map +1 -1
  44. package/dist/util/context-window.js +2 -1
  45. package/dist/util/context-window.js.map +1 -1
  46. package/dist/util/env.d.ts +2 -0
  47. package/dist/util/env.d.ts.map +1 -1
  48. package/dist/util/env.js +4 -0
  49. package/dist/util/env.js.map +1 -1
  50. package/package.json +1 -1
  51. package/dist/nudges/tool-call-warn.d.ts +0 -7
  52. package/dist/nudges/tool-call-warn.d.ts.map +0 -1
  53. package/dist/nudges/tool-call-warn.js +0 -20
  54. package/dist/nudges/tool-call-warn.js.map +0 -1
package/dist/savings.js CHANGED
@@ -1,31 +1,51 @@
1
1
  /**
2
- * Realized savings engine for OpenCode (the "$11/session -> $3/session" delta).
2
+ * Realized savings engine for OpenCode the CURRENT-VOLUME COUNTERFACTUAL.
3
3
  *
4
- * Parity with the Claude Code / Codex / OpenClaw before/after methodology:
5
- * freeze an early-usage baseline, compare it to current usage, report the
6
- * per-session cost delta. OpenCode already persists every session to trends.db
7
- * (session_log), so this reads that durable store directly -- no extra
8
- * persistence, and the baseline is recomputed deterministically from the fixed
9
- * historical window (no freeze needed; the early window never changes once past).
4
+ * Parity with measure.py `_estimate_before_after_savings` and the OpenClaw port.
5
+ * Prices the FROZEN pre-TO typical session (the per-session mean of the early
6
+ * `before` window) two ways:
7
+ * OLD WAY / session = baseline mix + the typical session's own cache pattern.
8
+ * Depends only on frozen inputs + prices -> a STABLE baseline
9
+ * that does NOT re-roll with this period's workload.
10
+ * NOW / session = the SAME frozen tokens, pool redistributed at the CURRENT
11
+ * cache-hit and priced at the CURRENT mix. Moves ONLY with
12
+ * efficiency (model routing + caching).
13
+ * Per-session transformation = old_cps − now_cps. Monthly = that x the session count,
14
+ * so volume re-enters ONLY as a multiplier — never as a confounder of the baseline.
10
15
  *
11
- * MULTI-MODEL CORRECTNESS: OpenCode runs many models with very different pricing.
12
- * Each session_log row already carries cost_usd computed at ITS OWN model when
13
- * recorded, so the per-session mean is exact regardless of model mix -- no
14
- * blended-average-vector approximation. The waterfall uses the era's effective
15
- * $/token (real cost / real tokens), which encodes the mix, and telescopes
16
- * exactly to the headline delta.
16
+ * OpenCode persists every session to trends.db (session_log) with a pre-computed
17
+ * cost_usd, but the counterfactual reprices the SAME volume at a DIFFERENT mix,
18
+ * which the stored cost cannot supply. So we add a minimal per-class rate card
19
+ * (pricing.ts) and price both arms from token volume directly.
17
20
  *
18
- * Distinct from forward-looking waste detection: this measures what you HAVE
19
- * saved, against an early baseline, using your actual recorded spend.
21
+ * Three non-overlapping pools sum to the headline:
22
+ * 1. Main routing + caching (billed session_log volume). NO winsorization /
23
+ * outlier drop — heavy sessions are real volume that genuinely cost more
24
+ * at the baseline mix; dropping them is a pure definitional undercount.
25
+ * 2. Subagent (sidechain) routing — Claude-only. OpenCode has no Claude-style
26
+ * sidechains (no is_sidechain column, no subagent transcripts), so this
27
+ * pool is 0. DOCUMENTED GAP.
28
+ * 3. Compression add-back — tokens TO removed from context (tool_archive,
29
+ * structure_map, resume_lean, checkpoint_restore, delta_read). Real volume
30
+ * the old way would have re-read. Directly-metered (savings_events),
31
+ * repriced to the baseline input mix. Disjoint from the billed pool.
32
+ * 4. Verbosity-steer add-back — estimated output tokens never produced due
33
+ * to lean-output conciseness nudges, repriced at the baseline output mix. The
34
+ * main counterfactual holds output volume constant, so this is a separate
35
+ * lever. Estimated tier (trigger observed, magnitude not metered).
36
+ *
37
+ * BASELINE = the frozen EARLY-window mix + pool cache-hit, used ONLY for
38
+ * efficiency anchors (never volume). NO 95% Opus floor: OpenCode is a
39
+ * non-Anthropic runtime, so the before-arm is priced at the user's OWN measured
40
+ * baseline mix — never fabricated Opus they never ran.
20
41
  */
21
42
  import { TrendsStore } from "./storage/trends.js";
43
+ import { price, price_cw, inputRatePerMTok, normalizeModelName } from "./pricing.js";
22
44
  // Constants mirror the other platforms' baseline tunables.
23
45
  const BASELINE_ONBOARDING_DAYS = 1;
24
46
  const BASELINE_EARLY_WINDOW_DAYS = 30;
25
47
  const BASELINE_MIN_STABLE_SESSIONS = 30;
26
48
  const AFTER_MIN_SESSIONS = 10;
27
- const WINSOR_PCT = 0.99;
28
- const WINSOR_MIN_SAMPLE = 10;
29
49
  const DAY_MS = 86_400_000;
30
50
  function num(v) {
31
51
  const n = typeof v === "number" ? v : parseFloat(String(v));
@@ -35,62 +55,49 @@ function toRec(row) {
35
55
  const ts = num(row.created_at) > 0
36
56
  ? num(row.created_at) * 1000
37
57
  : Date.parse(String(row.date ?? "")) || 0;
58
+ // Clamp every class >= 0 (corrupt-row protection, like measure.py's
59
+ // _session_token_vector clamps). cache-write is a separate billed column in
60
+ // session_log, kept distinct from fresh input.
38
61
  return {
39
62
  ts,
40
- model: String(row.model ?? "unknown"),
41
- tokens: num(row.tokens_input) +
42
- num(row.tokens_output) +
43
- num(row.tokens_cache_read) +
44
- num(row.tokens_cache_write),
63
+ model: normalizeModelName(String(row.model ?? "unknown")),
64
+ fi: Math.max(0, num(row.tokens_input)),
65
+ cr: Math.max(0, num(row.tokens_cache_read)),
66
+ cw: Math.max(0, num(row.tokens_cache_write)),
67
+ out: Math.max(0, num(row.tokens_output)),
45
68
  cost: num(row.cost_usd),
46
69
  };
47
70
  }
48
- function mixLabel(shares) {
49
- const top = Object.entries(shares).sort((a, b) => b[1] - a[1])[0];
50
- return top ? `${Math.round(top[1] * 100)}% ${top[0]}` : "n/a";
51
- }
52
- /** Winsorize the top 1% of sessions by cost (scale cost AND tokens together so
53
- * the effective rate stays consistent), then aggregate. */
54
- function computeEra(recs) {
55
- const n = recs.length;
56
- if (n === 0)
57
- return { n: 0, costPerSession: 0, meanTokens: 0, effRate: 0, shares: {} };
58
- let cap = Infinity;
59
- if (n >= WINSOR_MIN_SAMPLE) {
60
- const costs = recs.map((r) => r.cost).sort((a, b) => a - b);
61
- // floor (not round): round((n-1)*0.99) == n-1 for n<=51, making the cap the
62
- // max element and winsorization a no-op for every minimum-sample window.
63
- // Clamp to <= n-2 so the single largest session is always above the cap.
64
- cap = costs[Math.min(n - 2, Math.floor((n - 1) * WINSOR_PCT))];
65
- }
66
- let costSum = 0;
67
- let tokSum = 0;
71
+ /** Token-weighted model mix over a set of sessions (normalized pricing keys). */
72
+ function modelMix(recs) {
68
73
  const byModel = {};
69
- let modelTotal = 0;
74
+ let total = 0;
70
75
  for (const r of recs) {
71
- const scale = r.cost > cap && r.cost > 0 ? cap / r.cost : 1;
72
- costSum += r.cost * scale;
73
- tokSum += r.tokens * scale;
74
- byModel[r.model] = (byModel[r.model] ?? 0) + r.tokens;
75
- modelTotal += r.tokens;
76
- }
77
- const shares = {};
78
- if (modelTotal > 0) {
79
- for (const [m, t] of Object.entries(byModel))
80
- shares[m] = t / modelTotal;
76
+ const t = r.fi + r.cr + r.cw + r.out;
77
+ byModel[r.model] = (byModel[r.model] ?? 0) + t;
78
+ total += t;
81
79
  }
82
- return {
83
- n,
84
- costPerSession: costSum / n,
85
- meanTokens: tokSum / n,
86
- effRate: tokSum > 0 ? costSum / tokSum : 0,
87
- shares,
88
- };
80
+ if (total <= 0)
81
+ return {};
82
+ const mix = {};
83
+ for (const [m, t] of Object.entries(byModel))
84
+ mix[m] = t / total;
85
+ return mix;
86
+ }
87
+ function mixLabel(mix) {
88
+ const top = Object.entries(mix).sort((a, b) => b[1] - a[1])[0];
89
+ return top ? `${Math.round(top[1] * 100)}% ${top[0]}` : "n/a";
89
90
  }
90
91
  const NOT_READY = (status) => ({
91
92
  ready: false,
92
93
  status,
93
94
  monthlySavingsUsd: 0,
95
+ actualMonthlyUsd: 0,
96
+ counterfactualMonthlyUsd: 0,
97
+ transformationPct: 0,
98
+ compressionMeasuredUsd: 0,
99
+ verbosityMeasuredUsd: 0,
100
+ verbosityTransformationUsd: 0,
94
101
  savingsPerSession: 0,
95
102
  beforeCostPerSession: 0,
96
103
  afterCostPerSession: 0,
@@ -100,20 +107,32 @@ const NOT_READY = (status) => ({
100
107
  cumulativeSavedUsd: 0,
101
108
  installDate: null,
102
109
  breakdown: [],
110
+ baselineBuilding: null,
103
111
  });
104
112
  /**
105
- * Compute realized before/after savings from trends.db. `now` is injectable for
106
- * testing; `rowsOverride` lets tests bypass the DB entirely.
113
+ * Compute realized savings via the current-volume counterfactual.
114
+ * `now` injectable for testing.
115
+ * `rowsOverride` bypasses the DB (raw session_log rows) for tests.
116
+ * `compressionOverride` injects the measured compression dollars when the DB
117
+ * is bypassed (rowsOverride present); default 0.
107
118
  */
108
- export function computeRealizedSavings(dataDir, days = 30, now = Date.now(), rowsOverride) {
119
+ export function computeRealizedSavings(dataDir, days = 30, now = Date.now(), rowsOverride, compressionOverride) {
109
120
  let rows = rowsOverride ?? [];
121
+ let measuredCompression = compressionOverride ?? 0;
122
+ let measuredVerbosity = 0;
110
123
  if (!rowsOverride) {
111
124
  const store = new TrendsStore(dataDir);
112
125
  try {
113
126
  rows = store.getAllSessions();
127
+ // Compression add-back (pool #3) reads metered savings_events for the window.
128
+ measuredCompression = store.getCompressionSavings(days, now).totalCostSavedUsd;
129
+ // Verbosity-steer add-back (pool #4) reads estimated savings_events.
130
+ measuredVerbosity = store.getVerbositySavings(days, now);
114
131
  }
115
132
  catch {
116
133
  rows = [];
134
+ measuredCompression = 0;
135
+ measuredVerbosity = 0;
117
136
  }
118
137
  finally {
119
138
  store.close();
@@ -128,57 +147,248 @@ export function computeRealizedSavings(dataDir, days = 30, now = Date.now(), row
128
147
  const windowEnd = windowStart + BASELINE_EARLY_WINDOW_DAYS * DAY_MS;
129
148
  const before = history.filter((r) => r.ts >= windowStart && r.ts < windowEnd);
130
149
  if (before.length < BASELINE_MIN_STABLE_SESSIONS) {
150
+ const daysLeft = Math.max(0, Math.ceil((windowEnd - now) / DAY_MS));
131
151
  const r = NOT_READY(`building baseline (${before.length}/${BASELINE_MIN_STABLE_SESSIONS} early sessions)`);
132
152
  r.installDate = installDate;
153
+ r.baselineBuilding = {
154
+ sessionsInWindow: before.length,
155
+ sessionsNeeded: BASELINE_MIN_STABLE_SESSIONS,
156
+ earlyWindowDays: BASELINE_EARLY_WINDOW_DAYS,
157
+ daysLeft,
158
+ firstDate: installDate,
159
+ };
133
160
  return r;
134
161
  }
135
162
  if (now < windowEnd) {
136
163
  const daysLeft = Math.ceil((windowEnd - now) / DAY_MS);
137
164
  const r = NOT_READY(`building baseline (${daysLeft}d of early window left)`);
138
165
  r.installDate = installDate;
166
+ r.baselineBuilding = {
167
+ sessionsInWindow: before.length,
168
+ sessionsNeeded: BASELINE_MIN_STABLE_SESSIONS,
169
+ earlyWindowDays: BASELINE_EARLY_WINDOW_DAYS,
170
+ daysLeft,
171
+ firstDate: installDate,
172
+ };
139
173
  return r;
140
174
  }
141
- // After = recent sessions in lookback, strictly after the baseline window.
175
+ // CURRENT window = recent sessions in lookback, strictly after the baseline
176
+ // window (cohort separation). This is the EXACT volume held constant on both arms.
142
177
  const afterStart = Math.max(windowEnd, now - days * DAY_MS);
143
178
  const after = history.filter((r) => r.ts >= afterStart);
144
- const bs = computeEra(before);
179
+ // BASELINE efficiency anchors (mix + pool cache-hit), from the early window.
180
+ const beforeMix = modelMix(before);
145
181
  if (after.length < AFTER_MIN_SESSIONS) {
146
182
  const r = NOT_READY(`building comparison (${after.length}/${AFTER_MIN_SESSIONS} recent sessions)`);
147
183
  r.installDate = installDate;
148
- r.beforeCostPerSession = bs.costPerSession;
149
- r.beforeMixLabel = mixLabel(bs.shares);
184
+ r.beforeMixLabel = mixLabel(beforeMix);
185
+ // Baseline window is closed and frozen — daysLeft=0 here.
186
+ r.baselineBuilding = {
187
+ sessionsInWindow: before.length,
188
+ sessionsNeeded: BASELINE_MIN_STABLE_SESSIONS,
189
+ earlyWindowDays: BASELINE_EARLY_WINDOW_DAYS,
190
+ daysLeft: 0,
191
+ firstDate: installDate,
192
+ };
150
193
  return r;
151
194
  }
152
- const as = computeEra(after);
153
- const perSession = bs.costPerSession - as.costPerSession;
195
+ // Aggregate the CURRENT window billed token classes (SUM, not mean). NO
196
+ // winsorization / outlier drop: a heavy session's tokens are real volume that
197
+ // genuinely cost more at the baseline mix.
198
+ let F = 0, CR = 0, CW = 0;
199
+ for (const r of after) {
200
+ F += r.fi;
201
+ CR += r.cr;
202
+ CW += r.cw;
203
+ }
204
+ // Numeric safety: clamp the aggregated totals to finite, non-negative values
205
+ // (mirrors measure.py's _session_token_vector clamps). Per-row reads are already
206
+ // clamped in toRec, but a corrupt/overflowing row could still poison the SUM
207
+ // (e.g. a row injected via rowsOverride bypassing the DB type system). A single
208
+ // bad total must never NaN/negative the headline.
209
+ const clampTotal = (x) => (Number.isFinite(x) && x > 0 ? x : 0);
210
+ F = clampTotal(F);
211
+ CR = clampTotal(CR);
212
+ CW = clampTotal(CW);
213
+ const totalIn = F + CR + CW;
214
+ if (totalIn <= 0) {
215
+ const r = NOT_READY("no recent volume");
216
+ r.installDate = installDate;
217
+ r.beforeMixLabel = mixLabel(beforeMix);
218
+ r.baselineBuilding = {
219
+ sessionsInWindow: before.length,
220
+ sessionsNeeded: BASELINE_MIN_STABLE_SESSIONS,
221
+ earlyWindowDays: BASELINE_EARLY_WINDOW_DAYS,
222
+ daysLeft: 0,
223
+ firstDate: installDate,
224
+ };
225
+ return r;
226
+ }
227
+ const afterMix = modelMix(after);
228
+ // CURRENT pool cache-hit over FRESH+CACHE_READ (cache-write excluded) — the one
229
+ // efficiency signal the "now" arm needs from the current window.
230
+ const curPool = F + CR;
231
+ const curHit = curPool > 0 ? CR / curPool : 0;
232
+ // FROZEN typical session = the per-session MEAN of the early (`before`) window, a
233
+ // fixed install-anchored cohort -> a stable, factual baseline. Both arms price THIS
234
+ // session, so per-session figures move ONLY with efficiency, never with the current
235
+ // period's workload volume. Mirrors measure.py _estimate_before_after_savings.
236
+ const nBefore = Math.max(1, before.length);
237
+ const tFi = before.reduce((s, r) => s + r.fi, 0) / nBefore;
238
+ const tCr = before.reduce((s, r) => s + r.cr, 0) / nBefore;
239
+ const tCw = before.reduce((s, r) => s + r.cw, 0) / nBefore;
240
+ const tOut = before.reduce((s, r) => s + r.out, 0) / nBefore;
241
+ const tPool = tFi + tCr; // frozen fresh + cache_read pool
154
242
  const afterWindowDays = Math.max(1, (now - afterStart) / DAY_MS);
155
243
  const sessionsPerMonth = (after.length / afterWindowDays) * 30;
156
- const monthly = perSession * sessionsPerMonth;
244
+ // MONTHLY SCALING: the token classes above are summed over the `days` window,
245
+ // so every dollar figure derived from them is a WINDOW total, not a monthly one.
246
+ // `days` is user-controllable (dashboard.ts clamps to [1,365]); at days=7 the raw
247
+ // window sum is ~1/4 of the true monthly figure and at days=90 ~3x. Scale every
248
+ // dollar OUTPUT by 30/days so the headline is monthly regardless of `days`,
249
+ // mirroring openclaw/src/savings.ts (`monthlyScale = 30/max(1,days)`) and
250
+ // measure.py's monthly conversion. `sessionsPerMonth` already annualizes; the
251
+ // dollar aggregates did NOT — this closes that gap. At days=30, scale == 1, so
252
+ // the locked days=30 worked example is byte-identical (no regression).
253
+ const monthlyScale = 30 / Math.max(1, days);
254
+ const m = (x) => x * monthlyScale;
255
+ // OLD WAY / session (counterfactual) = the typical session's native fresh/cache_read
256
+ // split priced at the baseline mix + cache-write (5m, conservative) at the baseline
257
+ // mix. Depends only on the frozen early window + prices -> stable across runs.
258
+ const oldCps = price(tFi, tCr, tOut, beforeMix) + price_cw(tCw, beforeMix);
259
+ // NOW / session (actual) = the SAME frozen tokens, pool redistributed at the CURRENT
260
+ // pool-hit and priced at the CURRENT mix. Cache-write priced at the actual mix.
261
+ const curCRs = curHit * tPool;
262
+ const curFs = tPool - curCRs;
263
+ const nowCps = price(curFs, curCRs, tOut, afterMix) + price_cw(tCw, afterMix);
264
+ if (!Number.isFinite(oldCps) || !Number.isFinite(nowCps) ||
265
+ oldCps <= 0 || nowCps <= 0) {
266
+ const r = NOT_READY("insufficient pricing data");
267
+ r.installDate = installDate;
268
+ r.beforeMixLabel = mixLabel(beforeMix);
269
+ r.baselineBuilding = {
270
+ sessionsInWindow: before.length,
271
+ sessionsNeeded: BASELINE_MIN_STABLE_SESSIONS,
272
+ earlyWindowDays: BASELINE_EARLY_WINDOW_DAYS,
273
+ daysLeft: 0,
274
+ firstDate: installDate,
275
+ };
276
+ return r;
277
+ }
278
+ // Monthly main arms = the per-session anchor x the monthly session count.
279
+ const cfMonthlyMain = oldCps * sessionsPerMonth;
280
+ const actualMonthlyMain = nowCps * sessionsPerMonth;
281
+ // COMPRESSION ADD-BACK (#3): the measured removed-token dollars repriced to the
282
+ // baseline input mix (baseline_input_rate / current_input_rate). actual = 0 for
283
+ // this pool (the tokens were never billed), so the whole repriced value is
284
+ // transformation. Disjoint from the billed pool and the caching lever.
285
+ // `measuredCompression` is already WINDOWED to `days` by getCompressionSavings
286
+ // (cutoff = now - days*DAY), so it scales by the same 30/days as the billed arms.
287
+ const inAfter = inputRatePerMTok(afterMix);
288
+ const inBefore = inputRatePerMTok(beforeMix);
289
+ const compReprice = inAfter > 0 ? inBefore / inAfter : 1;
290
+ const compressionAddbackWindow = Math.max(0, measuredCompression * compReprice);
291
+ // VERBOSITY-STEER ADD-BACK (#4): estimated output tokens never produced due
292
+ // to lean-output conciseness nudges. The main counterfactual holds output volume
293
+ // constant (both arms price the same O), so the output reduction from nudges
294
+ // is NOT captured by mainTransformation. These are estimated savings logged
295
+ // to savings_events but excluded from measuredCompression. Reprice to the
296
+ // baseline OUTPUT rate: the old way would have produced verbose output at the
297
+ // baseline model mix's output rate. Actual for this pool is 0, so the whole
298
+ // repriced value is transformation. Mirrors measure.py verbosity_addback.
299
+ const outAfter = price(0, 0, 1_000_000, afterMix);
300
+ const outBefore = price(0, 0, 1_000_000, beforeMix);
301
+ const vsReprice = outAfter > 0 ? outBefore / outAfter : 1;
302
+ const verbosityAddbackWindow = Math.max(0, measuredVerbosity * vsReprice);
303
+ // Monthly arms. Main arms come from the frozen per-session anchors x session count;
304
+ // the add-back pools are still window sums scaled to monthly via m().
305
+ const actualMonthly = actualMonthlyMain;
306
+ const counterfactualMonthly = cfMonthlyMain;
307
+ const compressionAddback = m(compressionAddbackWindow);
308
+ const verbosityAddback = m(verbosityAddbackWindow);
309
+ // MAIN transformation = counterfactual − actual (clamped >= 0), monthly.
310
+ const mainTransformation = Math.max(0, counterfactualMonthly - actualMonthly);
311
+ // SUBAGENT pool (#2) = 0. OpenCode has NO Claude-style sidechains: no
312
+ // is_sidechain column on session_log, no separate subagent transcripts to read.
313
+ // This pool is a DOCUMENTED GAP — when OpenCode exposes delegated/subagent
314
+ // sessions distinctly, port _subagent_pool_savings here.
315
+ const subagentTransformation = 0;
316
+ // Headline = main + subagent (0) + compression add-back + verbosity add-back
317
+ // (four disjoint pools), monthly.
318
+ const transformation = mainTransformation + subagentTransformation + compressionAddback + verbosityAddback;
319
+ // Per-session keys (dashboard reads them) = the FROZEN typical-session anchors
320
+ // directly. before_* = "the old way" (stable across runs); after_* = "now" (moves
321
+ // only with efficiency). recentN drives the cumulative figure below.
322
+ const recentN = after.length;
323
+ const beforeCps = oldCps;
324
+ const afterCps = nowCps;
325
+ // Cumulative: per-session transformation across every post-baseline session.
326
+ // Uses the full transformation (all 4 pools: main + subagent + compression +
327
+ // verbosity) divided by current session count, times all post-baseline sessions.
328
+ // Matches OpenClaw's approach.
157
329
  const allAfter = history.filter((r) => r.ts >= windowEnd);
158
- const cumulative = perSession * allAfter.length;
159
- // 2-lever waterfall (telescopes exactly to perSession):
160
- // routing/mix = (effRate_before - effRate_after) * meanTokens_before
161
- // volume = effRate_after * (meanTokens_before - meanTokens_after)
162
- const perMonth = (x) => x * sessionsPerMonth;
163
- const routing = (bs.effRate - as.effRate) * bs.meanTokens;
164
- const volume = as.effRate * (bs.meanTokens - as.meanTokens);
330
+ const perSessionTransformation = transformation / Math.max(1, recentN);
331
+ const cumulative = perSessionTransformation * allAfter.length;
332
+ // --- Attribution breakdown: morph the counterfactual into the actual one lever
333
+ // at a time so the UNROUNDED steps telescope to the headline. ---
334
+ // routing = counterfactual (cf footprint repriced at today's mix, incl CW)
335
+ // caching = (cf footprint at today's mix) actual
336
+ // Routing carries the cache-write reprice (#2). Compression is the third lever.
337
+ // Levers are computed on the WINDOW arms then scaled by m() so they telescope to
338
+ // the (monthly) main transformation.
339
+ let sRoute = 0, sCache = 0;
340
+ if (mainTransformation > 0) {
341
+ // Per-session: typical baseline cache split repriced at the actual mix, then x count.
342
+ const vRouteS = price(tFi, tCr, tOut, afterMix) + price_cw(tCw, afterMix);
343
+ sRoute = (oldCps - vRouteS) * sessionsPerMonth; // before->after mix (incl CW)
344
+ sCache = (vRouteS - nowCps) * sessionsPerMonth; // remaining gap = caching
345
+ }
165
346
  const breakdown = [
166
- { key: "routing", label: "Model routing & pricing", monthlyUsd: perMonth(routing) },
167
- { key: "volume", label: "Token volume", monthlyUsd: perMonth(volume) },
168
- ];
347
+ { key: "routing", label: "Smarter model routing (lighter mix)", monthlyUsd: sRoute },
348
+ { key: "context_rereads", label: "Lighter sessions (better cache reuse)", monthlyUsd: sCache },
349
+ { key: "subagent_routing", label: "Cheaper subagents (no sidechains on OpenCode)", monthlyUsd: subagentTransformation },
350
+ { key: "context_compression", label: "Lighter context (fewer re-reads, metered)", monthlyUsd: compressionAddback },
351
+ { key: "verbosity_steer", label: "Lean output nudges (less output, estimated)", monthlyUsd: verbosityAddback },
352
+ ]
353
+ .filter((b) => b.key !== "subagent_routing" || b.monthlyUsd !== 0) // drop the always-0 sidechain lever
354
+ .sort((a, b) => Math.abs(b.monthlyUsd) - Math.abs(a.monthlyUsd));
355
+ // transformationPct: fraction of combined counterfactual spend eliminated.
356
+ // The combined counterfactual = main cf + compression add-back + verbosity
357
+ // add-back (both add-back pools' actual is 0, so their cf == their contribution).
358
+ // Clamped [0,1].
359
+ const combinedCf = counterfactualMonthly + compressionAddback + verbosityAddback;
360
+ const transformationPct = combinedCf > 0
361
+ ? Math.max(0, Math.min(1, transformation / combinedCf))
362
+ : 0;
363
+ // compressionMeasuredUsd: the RAW metered floor (before repricing to baseline mix).
364
+ // This is the proven, event-by-event subset of compressionAddback. It is returned
365
+ // as a SEPARATE field so the dashboard can render it in its own card, kept OUT of
366
+ // the transformation headline. INVARIANT: never summed into monthlySavingsUsd.
367
+ const compressionMeasuredUsd = m(Math.max(0, measuredCompression));
368
+ // verbosityMeasuredUsd: the RAW estimated verbosity $ (before repricing to
369
+ // baseline output mix). Also a separate field for the dashboard, kept OUT of
370
+ // the transformation headline.
371
+ const verbosityMeasuredUsd = m(Math.max(0, measuredVerbosity));
169
372
  return {
170
373
  ready: true,
171
374
  status: "ok",
172
- monthlySavingsUsd: monthly,
173
- savingsPerSession: perSession,
174
- beforeCostPerSession: bs.costPerSession,
175
- afterCostPerSession: as.costPerSession,
375
+ monthlySavingsUsd: transformation,
376
+ actualMonthlyUsd: actualMonthly,
377
+ counterfactualMonthlyUsd: combinedCf,
378
+ transformationPct,
379
+ compressionMeasuredUsd,
380
+ verbosityMeasuredUsd,
381
+ verbosityTransformationUsd: verbosityAddback,
382
+ savingsPerSession: beforeCps - afterCps,
383
+ beforeCostPerSession: beforeCps,
384
+ afterCostPerSession: afterCps,
176
385
  sessionsPerMonth,
177
- beforeMixLabel: mixLabel(bs.shares),
178
- afterMixLabel: mixLabel(as.shares),
386
+ beforeMixLabel: mixLabel(beforeMix),
387
+ afterMixLabel: mixLabel(afterMix),
179
388
  cumulativeSavedUsd: cumulative,
180
389
  installDate,
181
390
  breakdown,
391
+ baselineBuilding: null, // not needed when ready=true
182
392
  };
183
393
  }
184
394
  //# sourceMappingURL=savings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"savings.js","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"savings.js","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,80 @@ export declare class TrendsStore {
19
19
  private dbPath;
20
20
  constructor(dataDir: string);
21
21
  private connect;
22
+ /**
23
+ * Log a realized-savings event to the savings_events table.
24
+ *
25
+ * This is the TypeScript equivalent of Python's `_log_savings_event`.
26
+ * Model is not known at checkpoint-inject time, so cost_saved_usd is
27
+ * priced at the Sonnet input fallback rate — same behaviour as Python's
28
+ * resolver when the session model cannot be determined.
29
+ *
30
+ * Guards:
31
+ * - tokensSaved <= 0 → no-op (never credit zero or negative)
32
+ * - Any exception → silently swallowed (must never break the caller)
33
+ */
34
+ logSavingsEvent(eventType: string, tokensSaved: number, sessionId: string | null, detail: string | null, model?: string | null): void;
35
+ /**
36
+ * True if a savings event of the given type for the given target session was
37
+ * already credited within the specified window.
38
+ *
39
+ * Prevents double-counting when a user opens the same cold session from two
40
+ * different fresh sessions (cross-session dedup). Mirrors Python's
41
+ * `_resume_lean_already_credited` which dedups on the TARGET session_uuid.
42
+ *
43
+ * Best-effort: returns false on any error so we never block savings accounting.
44
+ */
45
+ hasRecentSavingsEvent(eventType: string, sessionId: string, withinMs: number): boolean;
46
+ /**
47
+ * Returns the tokens_cache_write value from session_log for the given session,
48
+ * or 0 if not found / unavailable.
49
+ *
50
+ * This is the closest opencode equivalent to Python's
51
+ * `cache_create_1h_tokens + cache_create_5m_tokens` — the real cold-rewrite
52
+ * cost that a lean resume avoids. Used by logResumeLeanSavings for the
53
+ * primary avoided-cost estimate.
54
+ *
55
+ * OVERCOUNT VERIFICATION (Fix 7): tokens_cache_write is populated from
56
+ * `t?.cache?.write` in the message.updated handler, which maps directly to
57
+ * OpenCode SDK's `Message.tokens.cache.write` field. Per the SDK type definition
58
+ * (types.gen.d.ts L120), `cache.write` is the cache CREATION count only —
59
+ * distinct from `cache.read` (cheap hits). A `--resume` cold rewrite ONLY incurs
60
+ * write (creation) cost; read hits on an established cache do NOT re-pay write cost.
61
+ * Therefore tokens_cache_write is write-only and does NOT conflate cache-read tokens.
62
+ * No discount needed: this column is the correct, non-overcounting avoided-cost metric.
63
+ *
64
+ * Best-effort: returns 0 on any error.
65
+ */
66
+ getSessionCacheWrite(sessionId: string): number;
67
+ /**
68
+ * Summarize compression/volume-reduction savings_events over the window for
69
+ * the realized-savings compression add-back (pool #3).
70
+ *
71
+ * Mirrors Python's `_get_savings_summary`: the returned `totalCostSavedUsd` is
72
+ * the MEASURED compression floor with estimated-tier categories relocated OUT
73
+ * (setup_optimization, mcp_cap, hint_followed) and tool_archive re-expansions
74
+ * NETTED against tool_archive (a re-popped result didn't stay collapsed, so its
75
+ * eager credit is reversed, floored at 0). What remains is the directly-metered
76
+ * removed-token dollars the old way would have re-read.
77
+ *
78
+ * Best-effort: returns zeros on any error (never throws). `now` is injectable
79
+ * for testing.
80
+ */
81
+ getCompressionSavings(days?: number, now?: number): {
82
+ totalTokensSaved: number;
83
+ totalCostSavedUsd: number;
84
+ totalEvents: number;
85
+ };
86
+ /**
87
+ * Sum the ESTIMATED verbosity_steer savings (cost_saved_usd) over the window.
88
+ * These are estimated output-token reductions from lean-output conciseness nudges —
89
+ * the trigger is observed but the magnitude is not metered. Mirrors measure.py
90
+ * `_get_savings_summary` which relocates verbosity_steer to the estimated tier.
91
+ * The caller reprices to the baseline OUTPUT rate and adds as a separate pool.
92
+ *
93
+ * Best-effort: returns 0 on any error (never throws). `now` is injectable.
94
+ */
95
+ getVerbositySavings(days?: number, now?: number): number;
22
96
  close(): void;
23
97
  recordSession(data: SessionTrendData): void;
24
98
  getRecentSessions(days?: number): Array<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"trends.d.ts","sourceRoot":"","sources":["../../src/storage/trends.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,MAAM;IAQ3B,OAAO,CAAC,OAAO;IAUf,KAAK,IAAI,IAAI;IAOb,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAwC3C,iBAAiB,CAAC,IAAI,GAAE,MAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAWpE;+DAC2D;IAC3D,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAOhD,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAqBjE"}
1
+ {"version":3,"file":"trends.d.ts","sourceRoot":"","sources":["../../src/storage/trends.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,MAAM;IAQ3B,OAAO,CAAC,OAAO;IAWf;;;;;;;;;;;OAWG;IACH,eAAe,CACb,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,GAAE,MAAM,GAAG,IAAW,GAC1B,IAAI;IAuBP;;;;;;;;;OASG;IACH,qBAAqB,CACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO;IAkBV;;;;;;;;;;;;;;;;;;;OAmBG;IACH,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAgB/C;;;;;;;;;;;;;OAaG;IACH,qBAAqB,CAAC,IAAI,GAAE,MAAW,EAAE,GAAG,GAAE,MAAmB,GAAG;QAClE,gBAAgB,EAAE,MAAM,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC;KACrB;IAwDD;;;;;;;;OAQG;IACH,mBAAmB,CAAC,IAAI,GAAE,MAAW,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM;IAgBxE,KAAK,IAAI,IAAI;IAOb,aAAa,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAwC3C,iBAAiB,CAAC,IAAI,GAAE,MAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAWpE;+DAC2D;IAC3D,cAAc,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAOhD,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAqBjE"}