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.
- package/dist/continuity/matcher.d.ts +18 -0
- package/dist/continuity/matcher.d.ts.map +1 -1
- package/dist/continuity/matcher.js +34 -1
- package/dist/continuity/matcher.js.map +1 -1
- package/dist/continuity/restore.d.ts +8 -1
- package/dist/continuity/restore.d.ts.map +1 -1
- package/dist/continuity/restore.js +43 -1
- package/dist/continuity/restore.js.map +1 -1
- package/dist/continuity/resume-lean.d.ts +126 -0
- package/dist/continuity/resume-lean.d.ts.map +1 -0
- package/dist/continuity/resume-lean.js +437 -0
- package/dist/continuity/resume-lean.js.map +1 -0
- package/dist/dashboard/generator.d.ts.map +1 -1
- package/dist/dashboard/generator.js +232 -36
- package/dist/dashboard/generator.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -4
- package/dist/index.js.map +1 -1
- package/dist/nudges/fresh-session-nudge.d.ts +72 -0
- package/dist/nudges/fresh-session-nudge.d.ts.map +1 -0
- package/dist/nudges/fresh-session-nudge.js +190 -0
- package/dist/nudges/fresh-session-nudge.js.map +1 -0
- package/dist/nudges/verbosity-steer.d.ts +28 -0
- package/dist/nudges/verbosity-steer.d.ts.map +1 -0
- package/dist/nudges/verbosity-steer.js +61 -0
- package/dist/nudges/verbosity-steer.js.map +1 -0
- package/dist/pricing.d.ts +58 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +307 -0
- package/dist/pricing.js.map +1 -0
- package/dist/savings.baseline.test.d.ts +2 -0
- package/dist/savings.baseline.test.d.ts.map +1 -0
- package/dist/savings.baseline.test.js +100 -0
- package/dist/savings.baseline.test.js.map +1 -0
- package/dist/savings.d.ts +41 -3
- package/dist/savings.d.ts.map +1 -1
- package/dist/savings.js +296 -86
- package/dist/savings.js.map +1 -1
- package/dist/storage/trends.d.ts +74 -0
- package/dist/storage/trends.d.ts.map +1 -1
- package/dist/storage/trends.js +199 -0
- package/dist/storage/trends.js.map +1 -1
- package/dist/util/context-window.d.ts.map +1 -1
- package/dist/util/context-window.js +2 -1
- package/dist/util/context-window.js.map +1 -1
- package/dist/util/env.d.ts +2 -0
- package/dist/util/env.d.ts.map +1 -1
- package/dist/util/env.js +4 -0
- package/dist/util/env.js.map +1 -1
- package/package.json +1 -1
- package/dist/nudges/tool-call-warn.d.ts +0 -7
- package/dist/nudges/tool-call-warn.d.ts.map +0 -1
- package/dist/nudges/tool-call-warn.js +0 -20
- 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
|
|
2
|
+
* Realized savings engine for OpenCode — the CURRENT-VOLUME COUNTERFACTUAL.
|
|
3
3
|
*
|
|
4
|
-
* Parity with
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
*
|
|
19
|
-
*
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
|
74
|
+
let total = 0;
|
|
70
75
|
for (const r of recs) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
106
|
-
*
|
|
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
|
-
//
|
|
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
|
-
|
|
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.
|
|
149
|
-
|
|
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
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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: "
|
|
167
|
-
{ key: "
|
|
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:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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(
|
|
178
|
-
afterMixLabel: mixLabel(
|
|
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
|
package/dist/savings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savings.js","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,2DAA2D;AAC3D,MAAM,wBAAwB,GAAG,CAAC,CAAC;AACnC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AACtC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AACxC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,UAAU,GAAG,IAAI,CAAC;AACxB,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,MAAM,GAAG,UAAU,CAAC;AAS1B,SAAS,GAAG,CAAC,CAAU;IACrB,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,KAAK,CAAC,GAA4B;IACzC,MAAM,EAAE,GACN,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QACrB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI;QAC5B,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO;QACL,EAAE;QACF,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,SAAS,CAAC;QACrC,MAAM,EACJ,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC;YACtB,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC;YAC1B,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAC7B,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;KACxB,CAAC;AACJ,CAAC;AA+BD,SAAS,QAAQ,CAAC,MAA8B;IAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,CAAC;AAED;4DAC4D;AAC5D,SAAS,UAAU,CAAC,IAAkB;IACpC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;IACtB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAEvF,IAAI,GAAG,GAAG,QAAQ,CAAC;IACnB,IAAI,CAAC,IAAI,iBAAiB,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5D,4EAA4E;QAC5E,yEAAyE;QACzE,yEAAyE;QACzE,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;QAC1B,MAAM,IAAI,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC;QAC3B,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;QACtD,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC;IACzB,CAAC;IACD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACnB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;YAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC;IAC3E,CAAC;IACD,OAAO;QACL,CAAC;QACD,cAAc,EAAE,OAAO,GAAG,CAAC;QAC3B,UAAU,EAAE,MAAM,GAAG,CAAC;QACtB,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM;KACP,CAAC;AACJ,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,MAAc,EAAmB,EAAE,CAAC,CAAC;IACtD,KAAK,EAAE,KAAK;IACZ,MAAM;IACN,iBAAiB,EAAE,CAAC;IACpB,iBAAiB,EAAE,CAAC;IACpB,oBAAoB,EAAE,CAAC;IACvB,mBAAmB,EAAE,CAAC;IACtB,gBAAgB,EAAE,CAAC;IACnB,cAAc,EAAE,KAAK;IACrB,aAAa,EAAE,KAAK;IACpB,kBAAkB,EAAE,CAAC;IACrB,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,EAAE;CACd,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAe,EACf,OAAe,EAAE,EACjB,MAAc,IAAI,CAAC,GAAG,EAAE,EACxB,YAA6C;IAE7C,IAAI,IAAI,GAAmC,YAAY,IAAI,EAAE,CAAC;IAC9D,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC;YACH,IAAI,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,EAAE,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACpF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAE9D,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChC,MAAM,WAAW,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnE,MAAM,WAAW,GAAG,SAAS,GAAG,wBAAwB,GAAG,MAAM,CAAC;IAClE,MAAM,SAAS,GAAG,WAAW,GAAG,0BAA0B,GAAG,MAAM,CAAC;IACpE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,WAAW,IAAI,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC;IAE9E,IAAI,MAAM,CAAC,MAAM,GAAG,4BAA4B,EAAE,CAAC;QACjD,MAAM,CAAC,GAAG,SAAS,CAAC,sBAAsB,MAAM,CAAC,MAAM,IAAI,4BAA4B,kBAAkB,CAAC,CAAC;QAC3G,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,GAAG,GAAG,SAAS,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,SAAS,CAAC,sBAAsB,QAAQ,yBAAyB,CAAC,CAAC;QAC7E,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC;QAC5B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,2EAA2E;IAC3E,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,UAAU,CAAC,CAAC;IACxD,MAAM,EAAE,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAI,KAAK,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,SAAS,CAAC,wBAAwB,KAAK,CAAC,MAAM,IAAI,kBAAkB,mBAAmB,CAAC,CAAC;QACnG,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC;QAC5B,CAAC,CAAC,oBAAoB,GAAG,EAAE,CAAC,cAAc,CAAC;QAC3C,CAAC,CAAC,cAAc,GAAG,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,EAAE,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,cAAc,CAAC;IACzD,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,GAAG,UAAU,CAAC,GAAG,MAAM,CAAC,CAAC;IACjE,MAAM,gBAAgB,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,eAAe,CAAC,GAAG,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAG,UAAU,GAAG,gBAAgB,CAAC;IAE9C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,SAAS,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEhD,wDAAwD;IACxD,uEAAuE;IACvE,yEAAyE;IACzE,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,GAAG,gBAAgB,CAAC;IACrD,MAAM,OAAO,GAAG,CAAC,EAAE,CAAC,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC;IAC1D,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IAC5D,MAAM,SAAS,GAA2B;QACxC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,yBAAyB,EAAE,UAAU,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;QACnF,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE;KACvE,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;QACZ,iBAAiB,EAAE,OAAO;QAC1B,iBAAiB,EAAE,UAAU;QAC7B,oBAAoB,EAAE,EAAE,CAAC,cAAc;QACvC,mBAAmB,EAAE,EAAE,CAAC,cAAc;QACtC,gBAAgB;QAChB,cAAc,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC;QACnC,aAAa,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC;QAClC,kBAAkB,EAAE,UAAU;QAC9B,WAAW;QACX,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"savings.js","sourceRoot":"","sources":["../src/savings.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/trends.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|