token-optimizer-opencode 1.0.8 → 1.0.14

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 (55) hide show
  1. package/README.md +15 -23
  2. package/dist/continuity/matcher.d.ts +15 -0
  3. package/dist/continuity/matcher.d.ts.map +1 -1
  4. package/dist/continuity/matcher.js +30 -1
  5. package/dist/continuity/matcher.js.map +1 -1
  6. package/dist/continuity/restore.d.ts +8 -1
  7. package/dist/continuity/restore.d.ts.map +1 -1
  8. package/dist/continuity/restore.js +43 -1
  9. package/dist/continuity/restore.js.map +1 -1
  10. package/dist/continuity/resume-lean.d.ts +126 -0
  11. package/dist/continuity/resume-lean.d.ts.map +1 -0
  12. package/dist/continuity/resume-lean.js +437 -0
  13. package/dist/continuity/resume-lean.js.map +1 -0
  14. package/dist/dashboard/generator.d.ts.map +1 -1
  15. package/dist/dashboard/generator.js +232 -36
  16. package/dist/dashboard/generator.js.map +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +58 -4
  19. package/dist/index.js.map +1 -1
  20. package/dist/nudges/fresh-session-nudge.d.ts +72 -0
  21. package/dist/nudges/fresh-session-nudge.d.ts.map +1 -0
  22. package/dist/nudges/fresh-session-nudge.js +190 -0
  23. package/dist/nudges/fresh-session-nudge.js.map +1 -0
  24. package/dist/nudges/verbosity-steer.d.ts +28 -0
  25. package/dist/nudges/verbosity-steer.d.ts.map +1 -0
  26. package/dist/nudges/verbosity-steer.js +61 -0
  27. package/dist/nudges/verbosity-steer.js.map +1 -0
  28. package/dist/pricing.d.ts +58 -0
  29. package/dist/pricing.d.ts.map +1 -0
  30. package/dist/pricing.js +307 -0
  31. package/dist/pricing.js.map +1 -0
  32. package/dist/savings.baseline.test.d.ts +2 -0
  33. package/dist/savings.baseline.test.d.ts.map +1 -0
  34. package/dist/savings.baseline.test.js +100 -0
  35. package/dist/savings.baseline.test.js.map +1 -0
  36. package/dist/savings.d.ts +41 -3
  37. package/dist/savings.d.ts.map +1 -1
  38. package/dist/savings.js +296 -86
  39. package/dist/savings.js.map +1 -1
  40. package/dist/storage/trends.d.ts +61 -0
  41. package/dist/storage/trends.d.ts.map +1 -1
  42. package/dist/storage/trends.js +149 -0
  43. package/dist/storage/trends.js.map +1 -1
  44. package/dist/util/context-window.d.ts.map +1 -1
  45. package/dist/util/context-window.js +2 -1
  46. package/dist/util/context-window.js.map +1 -1
  47. package/dist/util/env.d.ts +2 -0
  48. package/dist/util/env.d.ts.map +1 -1
  49. package/dist/util/env.js +4 -0
  50. package/dist/util/env.js.map +1 -1
  51. package/package.json +1 -1
  52. package/dist/nudges/tool-call-warn.d.ts +0 -7
  53. package/dist/nudges/tool-call-warn.d.ts.map +0 -1
  54. package/dist/nudges/tool-call-warn.js +0 -20
  55. package/dist/nudges/tool-call-warn.js.map +0 -1
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Fresh-session nudge: fires once per session when context is BOTH long
3
+ * (fill >= FRESH_NUDGE_MIN_FILL_PCT) AND degraded (quality < FRESH_NUDGE_QUALITY_THRESHOLD).
4
+ *
5
+ * Confidently reassures the user that Token Optimizer has checkpointed their
6
+ * active task so a fresh session resumes exactly where they stopped, and shows
7
+ * the concrete tokens they would reclaim by starting fresh now.
8
+ *
9
+ * Takes PRECEDENCE over the ordinary quality/compact nudge (the caller skips
10
+ * that when this fires — both messages would be noise).
11
+ *
12
+ * Ported from Python _maybe_fresh_session_nudge / _fresh_session_savings_estimate
13
+ * in skills/token-optimizer/scripts/measure.py.
14
+ */
15
+ /**
16
+ * Look up the API input rate ($/M tokens) for the given model.
17
+ * Substring-matches the lowercase model id against the table — same
18
+ * strategy as contextWindowForModel. Falls back to Sonnet ($3.00/M).
19
+ */
20
+ export declare function modelInputRatePer1M(model?: string): number;
21
+ /**
22
+ * API-equivalent dollar value of the reclaimed tokens, priced at the session's
23
+ * own model input rate. Returns 0 on any error (best-effort).
24
+ * Mirrors Python _fresh_session_savings_usd.
25
+ */
26
+ export declare function freshSessionSavingsUsd(savedTokens: number, model?: string): number;
27
+ export declare const FRESH_NUDGE_QUALITY_THRESHOLD: number;
28
+ export declare const FRESH_NUDGE_MIN_FILL_PCT: number;
29
+ export interface FreshNudgeResult {
30
+ shouldNudge: boolean;
31
+ message: string | null;
32
+ }
33
+ /**
34
+ * Estimate tokens reclaimed by starting a fresh session now.
35
+ * current context size = (fillPct / 100) * contextWindow
36
+ * savings = current context - lean block re-injection overhead
37
+ *
38
+ * @param fillPct 0-100 (percentage, not fraction)
39
+ * @param model optional model id — used only as a last-resort fallback
40
+ * when sessionWindow is unavailable
41
+ * @param sessionWindow the EXACT context-window value the fill% was measured
42
+ * against (pass the same value used in computeQualityScore).
43
+ * When provided this takes priority over re-deriving from the
44
+ * model, which guarantees token count == fill% of that window
45
+ * (e.g. 54% of 1_000_000 ≈ 540K, never ~107K on a 200K fallback).
46
+ * @returns [savedTokens, contextWindow]
47
+ */
48
+ export declare function freshSessionSavingsEstimate(fillPct: number, model?: string, sessionWindow?: number): [number, number];
49
+ /**
50
+ * Check whether the fresh-session nudge should fire for this turn.
51
+ *
52
+ * @param currentScore current quality/resource-health score (0-100)
53
+ * @param fillPct current context fill as 0-100 (percentage, not fraction)
54
+ * @param previousScore score from the previous turn (null = no prior score yet)
55
+ * @param freshNudgeFired whether the nudge already fired this session
56
+ * @param nudgesEnabled whether quality nudges are enabled in config
57
+ * @param continuityEnabled whether checkpoint continuity is enabled. The nudge's
58
+ * whole pitch ("start fresh, your place is saved") only
59
+ * holds when continuity actually restores the checkpoint
60
+ * in the new session. With continuity off, suppress the
61
+ * nudge so the ordinary quality nudge (/compact) takes
62
+ * over instead of promising a restore that never happens.
63
+ * @param model optional model id — fallback for context-window lookup
64
+ * @param sessionWindow the EXACT context-window value the fill% was measured
65
+ * against; threads through to freshSessionSavingsEstimate
66
+ * so the token count is consistent with the fill% display
67
+ * @param qualityThreshold score below which (with fill) the nudge may fire; defaults
68
+ * to the env-tunable module constant, overridable via config
69
+ * @param minFillPct fill% at/above which the nudge may fire; same default rule
70
+ */
71
+ export declare function checkFreshSessionNudge(currentScore: number, fillPct: number, previousScore: number | null, freshNudgeFired: boolean, nudgesEnabled: boolean, continuityEnabled: boolean, model?: string, sessionWindow?: number, qualityThreshold?: number, minFillPct?: number): FreshNudgeResult;
72
+ //# sourceMappingURL=fresh-session-nudge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fresh-session-nudge.d.ts","sourceRoot":"","sources":["../../src/nudges/fresh-session-nudge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAoDH;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAc1D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAOlF;AAUD,eAAO,MAAM,6BAA6B,QAAoD,CAAC;AAC/F,eAAO,MAAM,wBAAwB,QAAqD,CAAC;AAK3F,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrH;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,EACtB,iBAAiB,EAAE,OAAO,EAC1B,KAAK,CAAC,EAAE,MAAM,EACd,aAAa,CAAC,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAsC,EACxD,UAAU,GAAE,MAAiC,GAC5C,gBAAgB,CAoClB"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Fresh-session nudge: fires once per session when context is BOTH long
3
+ * (fill >= FRESH_NUDGE_MIN_FILL_PCT) AND degraded (quality < FRESH_NUDGE_QUALITY_THRESHOLD).
4
+ *
5
+ * Confidently reassures the user that Token Optimizer has checkpointed their
6
+ * active task so a fresh session resumes exactly where they stopped, and shows
7
+ * the concrete tokens they would reclaim by starting fresh now.
8
+ *
9
+ * Takes PRECEDENCE over the ordinary quality/compact nudge (the caller skips
10
+ * that when this fires — both messages would be noise).
11
+ *
12
+ * Ported from Python _maybe_fresh_session_nudge / _fresh_session_savings_estimate
13
+ * in skills/token-optimizer/scripts/measure.py.
14
+ */
15
+ import { contextWindowForModel } from "../util/context-window.js";
16
+ // ---------------------------------------------------------------------------
17
+ // Per-model input rates ($/M tokens). Mirrors Python PRICING_TIERS["anthropic"]
18
+ // and the non-Claude model table. Fallback is Sonnet at $3.00/M.
19
+ // ---------------------------------------------------------------------------
20
+ const MODEL_INPUT_RATES = {
21
+ // Anthropic Claude
22
+ fable: 10.0,
23
+ opus: 5.0,
24
+ sonnet: 3.0,
25
+ haiku: 1.0,
26
+ // GPT-5 family
27
+ "gpt-5.5-pro": 30.0,
28
+ "gpt-5.5": 5.0,
29
+ "gpt-5.4": 2.5,
30
+ "gpt-5.4-mini": 0.75,
31
+ "gpt-5.4-nano": 0.2,
32
+ "gpt-5.3-codex": 1.75,
33
+ "gpt-5.2-codex": 1.75,
34
+ "gpt-5.2": 1.75,
35
+ "gpt-5.1-codex-mini": 0.25,
36
+ "gpt-5.1-codex": 1.25,
37
+ "gpt-5.1": 1.25,
38
+ "gpt-5-codex": 1.25,
39
+ "gpt-5": 1.25,
40
+ "gpt-5-mini": 0.25,
41
+ "gpt-5-nano": 0.05,
42
+ // GPT-4 family
43
+ "gpt-4.1": 2.0,
44
+ "gpt-4.1-mini": 0.4,
45
+ "gpt-4.1-nano": 0.1,
46
+ "gpt-4o": 2.5,
47
+ "gpt-4o-mini": 0.15,
48
+ // OpenAI reasoning
49
+ "o3-pro": 20.0,
50
+ o3: 2.0,
51
+ "o3-mini": 1.1,
52
+ "o4-mini": 1.1,
53
+ // Google Gemini
54
+ "gemini-2.5-pro": 1.25,
55
+ "gemini-2.5-flash": 0.3,
56
+ "gemini-2.5-flash-lite": 0.1,
57
+ "gemini-2.0-flash": 0.1,
58
+ "gemini-2.0-flash-lite": 0.075,
59
+ };
60
+ /** Sonnet input rate used as the default fallback ($/M tokens). */
61
+ const FALLBACK_INPUT_RATE_PER_MTOK = 3.0;
62
+ /**
63
+ * Look up the API input rate ($/M tokens) for the given model.
64
+ * Substring-matches the lowercase model id against the table — same
65
+ * strategy as contextWindowForModel. Falls back to Sonnet ($3.00/M).
66
+ */
67
+ export function modelInputRatePer1M(model) {
68
+ if (!model)
69
+ return FALLBACK_INPUT_RATE_PER_MTOK;
70
+ const lower = model.toLowerCase();
71
+ const direct = MODEL_INPUT_RATES[lower];
72
+ if (direct !== undefined)
73
+ return direct;
74
+ // Substring scan (longest key wins via insertion order — most specific first
75
+ // in the table above). Mirrors Python's model-tier resolution.
76
+ for (const [key, rate] of Object.entries(MODEL_INPUT_RATES)) {
77
+ if (lower.includes(key))
78
+ return rate;
79
+ }
80
+ return FALLBACK_INPUT_RATE_PER_MTOK;
81
+ }
82
+ /**
83
+ * API-equivalent dollar value of the reclaimed tokens, priced at the session's
84
+ * own model input rate. Returns 0 on any error (best-effort).
85
+ * Mirrors Python _fresh_session_savings_usd.
86
+ */
87
+ export function freshSessionSavingsUsd(savedTokens, model) {
88
+ try {
89
+ const rate = modelInputRatePer1M(model);
90
+ return Math.max(0, savedTokens * rate / 1_000_000);
91
+ }
92
+ catch {
93
+ return 0;
94
+ }
95
+ }
96
+ function intEnv(key, fallback) {
97
+ const raw = process.env[key]?.trim();
98
+ if (!raw)
99
+ return fallback;
100
+ const parsed = parseInt(raw, 10);
101
+ return isNaN(parsed) ? fallback : parsed;
102
+ }
103
+ // Env-tunable thresholds, matching Python constants.
104
+ export const FRESH_NUDGE_QUALITY_THRESHOLD = intEnv("TOKEN_OPTIMIZER_FRESH_NUDGE_QUALITY", 70);
105
+ export const FRESH_NUDGE_MIN_FILL_PCT = intEnv("TOKEN_OPTIMIZER_FRESH_NUDGE_MIN_FILL", 50);
106
+ /** Tokens re-injected by a fresh lean-resume (the small overhead the new session pays). */
107
+ const FRESH_NUDGE_LEAN_BLOCK_TOKENS = 1000;
108
+ /**
109
+ * Estimate tokens reclaimed by starting a fresh session now.
110
+ * current context size = (fillPct / 100) * contextWindow
111
+ * savings = current context - lean block re-injection overhead
112
+ *
113
+ * @param fillPct 0-100 (percentage, not fraction)
114
+ * @param model optional model id — used only as a last-resort fallback
115
+ * when sessionWindow is unavailable
116
+ * @param sessionWindow the EXACT context-window value the fill% was measured
117
+ * against (pass the same value used in computeQualityScore).
118
+ * When provided this takes priority over re-deriving from the
119
+ * model, which guarantees token count == fill% of that window
120
+ * (e.g. 54% of 1_000_000 ≈ 540K, never ~107K on a 200K fallback).
121
+ * @returns [savedTokens, contextWindow]
122
+ */
123
+ export function freshSessionSavingsEstimate(fillPct, model, sessionWindow) {
124
+ // Priority: explicit session window > model-based lookup > default fallback.
125
+ // Re-deriving from the model is a last resort: it can silently disagree with
126
+ // the window the fill % was actually computed against (e.g. 200K fallback on a
127
+ // 1M session makes the token estimate 5x too low).
128
+ const contextWindow = (sessionWindow && sessionWindow > 0)
129
+ ? sessionWindow
130
+ : contextWindowForModel(model ?? "");
131
+ const clampedFill = Math.max(0, Math.min(100, fillPct));
132
+ const currentCtx = Math.round((clampedFill / 100) * contextWindow);
133
+ const saved = Math.max(0, currentCtx - FRESH_NUDGE_LEAN_BLOCK_TOKENS);
134
+ return [saved, contextWindow];
135
+ }
136
+ /**
137
+ * Check whether the fresh-session nudge should fire for this turn.
138
+ *
139
+ * @param currentScore current quality/resource-health score (0-100)
140
+ * @param fillPct current context fill as 0-100 (percentage, not fraction)
141
+ * @param previousScore score from the previous turn (null = no prior score yet)
142
+ * @param freshNudgeFired whether the nudge already fired this session
143
+ * @param nudgesEnabled whether quality nudges are enabled in config
144
+ * @param continuityEnabled whether checkpoint continuity is enabled. The nudge's
145
+ * whole pitch ("start fresh, your place is saved") only
146
+ * holds when continuity actually restores the checkpoint
147
+ * in the new session. With continuity off, suppress the
148
+ * nudge so the ordinary quality nudge (/compact) takes
149
+ * over instead of promising a restore that never happens.
150
+ * @param model optional model id — fallback for context-window lookup
151
+ * @param sessionWindow the EXACT context-window value the fill% was measured
152
+ * against; threads through to freshSessionSavingsEstimate
153
+ * so the token count is consistent with the fill% display
154
+ * @param qualityThreshold score below which (with fill) the nudge may fire; defaults
155
+ * to the env-tunable module constant, overridable via config
156
+ * @param minFillPct fill% at/above which the nudge may fire; same default rule
157
+ */
158
+ export function checkFreshSessionNudge(currentScore, fillPct, previousScore, freshNudgeFired, nudgesEnabled, continuityEnabled, model, sessionWindow, qualityThreshold = FRESH_NUDGE_QUALITY_THRESHOLD, minFillPct = FRESH_NUDGE_MIN_FILL_PCT) {
159
+ if (!nudgesEnabled)
160
+ return { shouldNudge: false, message: null };
161
+ // The nudge promises "Token Optimizer has checkpointed your task, so a new
162
+ // session picks up where you stopped." That is only true when continuity is on.
163
+ // If the user disabled it, do not inject that promise into the system prompt --
164
+ // bail so the ordinary quality nudge handles the long+degraded session instead.
165
+ if (!continuityEnabled)
166
+ return { shouldNudge: false, message: null };
167
+ // Post-compaction suppression: no prior score means this is a fresh/just-compacted
168
+ // session. Let the ordinary nudge seed the baseline first.
169
+ if (previousScore === null)
170
+ return { shouldNudge: false, message: null };
171
+ // Once per session.
172
+ if (freshNudgeFired)
173
+ return { shouldNudge: false, message: null };
174
+ // Both conditions must hold: long session AND degraded quality.
175
+ if (!(currentScore < qualityThreshold && fillPct >= minFillPct)) {
176
+ return { shouldNudge: false, message: null };
177
+ }
178
+ const [saved] = freshSessionSavingsEstimate(fillPct, model, sessionWindow);
179
+ const savedStr = saved >= 1000 ? `~${Math.floor(saved / 1000)}K` : `~${saved}`;
180
+ const fillRounded = Math.round(fillPct);
181
+ const scoreRounded = Math.round(currentScore);
182
+ const usd = freshSessionSavingsUsd(saved, model);
183
+ const costStr = usd >= 0.01 ? `, about $${usd.toFixed(2)} in API-equivalent cost` : "";
184
+ const message = `[Token Optimizer] This session is long (${fillRounded}% full) and context quality has fallen to ${scoreRounded}. ` +
185
+ `Starting a fresh session now would reclaim ${savedStr} tokens (~${fillRounded}% of your window)${costStr}. ` +
186
+ `You won't lose your place: Token Optimizer has checkpointed your active task, key decisions, files, and tool results, ` +
187
+ `so a new session picks up exactly where you stopped. Just open one and say "continue this" — the context is rebuilt for free.`;
188
+ return { shouldNudge: true, message };
189
+ }
190
+ //# sourceMappingURL=fresh-session-nudge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fresh-session-nudge.js","sourceRoot":"","sources":["../../src/nudges/fresh-session-nudge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Verbosity-steer nudge: inject a tiered conciseness nudge when context is
3
+ * under pressure. Mirrors the Python `run_verbosity_steer` function in
4
+ * measure.py.
5
+ *
6
+ * Tiered messaging:
7
+ * 25-74% fill + degraded quality (<75) → gentle nudge
8
+ * 75-89% fill → strong nudge with specific directives
9
+ * 90%+ fill → suppressed (adding tokens makes it worse)
10
+ *
11
+ * Cooldown: max 3 nudges per session, 5 min between nudges.
12
+ * Shares the same nudge_count / last_nudge_time fields as the quality nudge
13
+ * so the two features share a single cooldown counter.
14
+ */
15
+ import type { SessionStore } from "../storage/session-store.js";
16
+ export interface VerbositySteerResult {
17
+ shouldNudge: boolean;
18
+ message: string | null;
19
+ tier: "gentle" | "strong" | "suppressed" | "none";
20
+ }
21
+ export declare function checkVerbositySteer(store: SessionStore, fillPct: number, qualityScore: number): VerbositySteerResult;
22
+ /**
23
+ * Estimate output token savings from a verbosity-steer nudge.
24
+ * The nudge causes the model to produce ~10-15% fewer output tokens
25
+ * on affected responses. Returns [tokensSaved, tier] for logging.
26
+ */
27
+ export declare function verbositySteerSavingsEstimate(fillPct: number): [number, string];
28
+ //# sourceMappingURL=verbosity-steer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verbosity-steer.d.ts","sourceRoot":"","sources":["../../src/nudges/verbosity-steer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAShE,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;CACnD;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GACnB,oBAAoB,CAkCtB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,MAAM,GACd,CAAC,MAAM,EAAE,MAAM,CAAC,CAIlB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Verbosity-steer nudge: inject a tiered conciseness nudge when context is
3
+ * under pressure. Mirrors the Python `run_verbosity_steer` function in
4
+ * measure.py.
5
+ *
6
+ * Tiered messaging:
7
+ * 25-74% fill + degraded quality (<75) → gentle nudge
8
+ * 75-89% fill → strong nudge with specific directives
9
+ * 90%+ fill → suppressed (adding tokens makes it worse)
10
+ *
11
+ * Cooldown: max 3 nudges per session, 5 min between nudges.
12
+ * Shares the same nudge_count / last_nudge_time fields as the quality nudge
13
+ * so the two features share a single cooldown counter.
14
+ */
15
+ const COOLDOWN_SEC = 300; // 5 minutes
16
+ const SESSION_CAP = 3;
17
+ const GENTLE_FILL_THRESHOLD = 25;
18
+ const STRONG_FILL_THRESHOLD = 75;
19
+ const CRITICAL_FILL_THRESHOLD = 90;
20
+ const QUALITY_THRESHOLD = 75;
21
+ export function checkVerbositySteer(store, fillPct, qualityScore) {
22
+ const cache = store.getQualityCache();
23
+ const nudgeCount = cache?.nudge_count ?? 0;
24
+ const lastNudgeTime = cache?.last_nudge_time ?? 0;
25
+ const now = Date.now() / 1000;
26
+ // Cooldown and session cap (shared with quality nudge)
27
+ if (nudgeCount >= SESSION_CAP)
28
+ return { shouldNudge: false, message: null, tier: "none" };
29
+ if (now - lastNudgeTime < COOLDOWN_SEC)
30
+ return { shouldNudge: false, message: null, tier: "none" };
31
+ // At 90%+ fill, don't add more tokens — suppress entirely
32
+ if (fillPct >= CRITICAL_FILL_THRESHOLD) {
33
+ return { shouldNudge: false, message: null, tier: "suppressed" };
34
+ }
35
+ // Strong tier: 75%+ fill regardless of quality
36
+ if (fillPct >= STRONG_FILL_THRESHOLD) {
37
+ const message = `[Token Optimizer] Context at ${Math.round(fillPct)}% capacity, quality ${Math.round(qualityScore)}/100. ` +
38
+ "Reason as deeply as you need — but keep your visible output lean: no preamble, " +
39
+ "no restating the request, no explanations unless asked. Every token saved extends the session.";
40
+ return { shouldNudge: true, message, tier: "strong" };
41
+ }
42
+ // Gentle tier: 25%+ fill with degraded quality
43
+ if (fillPct >= GENTLE_FILL_THRESHOLD && qualityScore < QUALITY_THRESHOLD) {
44
+ const message = `[Token Optimizer] Context at ${Math.round(fillPct)}% capacity, quality ${Math.round(qualityScore)}/100. ` +
45
+ "Reason fully, then keep your output lean — skip restating the request and " +
46
+ "omit unnecessary preamble. Every token saved extends the session.";
47
+ return { shouldNudge: true, message, tier: "gentle" };
48
+ }
49
+ return { shouldNudge: false, message: null, tier: "none" };
50
+ }
51
+ /**
52
+ * Estimate output token savings from a verbosity-steer nudge.
53
+ * The nudge causes the model to produce ~10-15% fewer output tokens
54
+ * on affected responses. Returns [tokensSaved, tier] for logging.
55
+ */
56
+ export function verbositySteerSavingsEstimate(fillPct) {
57
+ const avgResponseTokens = 800;
58
+ const reduction = fillPct >= STRONG_FILL_THRESHOLD ? 0.15 : 0.10;
59
+ return [Math.round(avgResponseTokens * reduction), fillPct >= STRONG_FILL_THRESHOLD ? "strong" : "gentle"];
60
+ }
61
+ //# sourceMappingURL=verbosity-steer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verbosity-steer.js","sourceRoot":"","sources":["../../src/nudges/verbosity-steer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,YAAY,GAAG,GAAG,CAAC,CAAC,YAAY;AACtC,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACjC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AACnC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAQ7B,MAAM,UAAU,mBAAmB,CACjC,KAAmB,EACnB,OAAe,EACf,YAAoB;IAEpB,MAAM,KAAK,GAAG,KAAK,CAAC,eAAe,EAAE,CAAC;IACtC,MAAM,UAAU,GAAG,KAAK,EAAE,WAAW,IAAI,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,KAAK,EAAE,eAAe,IAAI,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAE9B,uDAAuD;IACvD,IAAI,UAAU,IAAI,WAAW;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1F,IAAI,GAAG,GAAG,aAAa,GAAG,YAAY;QAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAEnG,0DAA0D;IAC1D,IAAI,OAAO,IAAI,uBAAuB,EAAE,CAAC;QACvC,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IACnE,CAAC;IAED,+CAA+C;IAC/C,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAAC;QACrC,MAAM,OAAO,GACX,gCAAgC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,uBAAuB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ;YAC1G,iFAAiF;YACjF,gGAAgG,CAAC;QACnG,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACxD,CAAC;IAED,+CAA+C;IAC/C,IAAI,OAAO,IAAI,qBAAqB,IAAI,YAAY,GAAG,iBAAiB,EAAE,CAAC;QACzE,MAAM,OAAO,GACX,gCAAgC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,uBAAuB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ;YAC1G,4EAA4E;YAC5E,mEAAmE,CAAC;QACtE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACxD,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC3C,OAAe;IAEf,MAAM,iBAAiB,GAAG,GAAG,CAAC;IAC9B,MAAM,SAAS,GAAG,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,GAAG,SAAS,CAAC,EAAE,OAAO,IAAI,qBAAqB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC7G,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Minimal per-MTok rate card for OpenCode's realized-savings counterfactual.
3
+ *
4
+ * OpenCode persists a pre-computed `cost_usd` per session (priced at its own
5
+ * model when recorded), so the ACTUAL arm needs no rate card. The
6
+ * COUNTERFACTUAL arm, however, reprices the same token VOLUME at a DIFFERENT
7
+ * (baseline) model mix, which requires a per-class rate card the stored cost
8
+ * cannot supply. This module is that card.
9
+ *
10
+ * Rates mirror openclaw/src/pricing.ts exactly (USD per token; verified
11
+ * May 30, 2026). cacheWrite = 5-minute-TTL rate (1.25x input); cacheWrite1h =
12
+ * 1-hour-TTL rate (2x input), only set for Claude models that support it.
13
+ *
14
+ * A "mix" is a {modelKey -> share} map (shares sum to ~1). price() blends the
15
+ * per-token cost across the mix; unpriced models fall back to a proxy rate.
16
+ */
17
+ export interface ModelPricing {
18
+ input: number;
19
+ output: number;
20
+ cacheRead: number;
21
+ /** 5-minute cache-write rate (1.25x input). Used when TTL is unknown. */
22
+ cacheWrite: number;
23
+ /** 1-hour cache-write rate (2x input). Only set for Claude models. */
24
+ cacheWrite1h?: number;
25
+ }
26
+ /** Default pricing (USD per token). Mirrors openclaw/src/pricing.ts. */
27
+ export declare const DEFAULT_PRICING: Record<string, ModelPricing>;
28
+ /** Price the proxy rate card uses when a model is unpriced. */
29
+ export declare const PROXY_MODEL = "sonnet";
30
+ /**
31
+ * Normalize a model ID into a pricing key. Mirrors openclaw/src/pricing.ts.
32
+ * Handles provider prefixes (anthropic/claude-sonnet-4-6 -> sonnet) and version
33
+ * suffixes (gpt-5.2-2026-03 -> gpt-5.2). Returns lowercased raw on no match.
34
+ */
35
+ export declare function normalizeModelName(modelId: string): string;
36
+ /** A model mix: modelKey (or display name) -> token share. Shares sum to ~1. */
37
+ export type ModelMix = Record<string, number>;
38
+ /**
39
+ * Price the fresh+cache_read POOL and OUTPUT at a model mix (NO cache-write).
40
+ * Linear in tokens, so aggregate window totals price the whole window directly.
41
+ * Mirrors measure.py's `price(fi, cr, out, shares)`.
42
+ */
43
+ export declare function price(F: number, CR: number, O: number, mix: ModelMix): number;
44
+ /**
45
+ * Price cache-write at a model mix, TTL-aware: 1h writes bill at 2x input, 5m
46
+ * at 1.25x. Cache-write IS a routing lever (billed at the writing model's
47
+ * rate), so each arm prices CW at its OWN mix. Mirrors measure.py's `price_cw`.
48
+ * OpenCode's session_log has no 5m/1h split, so all writes are treated as 5m
49
+ * (conservative) unless cw1h is supplied.
50
+ */
51
+ export declare function price_cw(CW: number, mix: ModelMix, CW_5m?: number, CW_1h?: number): number;
52
+ /**
53
+ * Cost of 1M fresh-input tokens at a mix. Used to reprice the compression
54
+ * add-back: baseline_input_rate / current_input_rate. Mirrors measure.py's
55
+ * `price(1_000_000, 0, 0, shares)`.
56
+ */
57
+ export declare function inputRatePerMTok(mix: ModelMix): number;
58
+ //# sourceMappingURL=pricing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wEAAwE;AACxE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAqExD,CAAC;AAEF,+DAA+D;AAC/D,eAAO,MAAM,WAAW,WAAW,CAAC;AA2BpC;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAuE1D;AAQD,gFAAgF;AAChF,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA8B9C;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,MAAM,CAM7E;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAO1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,CAEtD"}