dsh-fast-mode 1.6.2 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/usage.js CHANGED
@@ -122,3 +122,26 @@ export function findUsage(source) {
122
122
  export function readOutputTokens(source) {
123
123
  return readUsage(findUsage(source)).output
124
124
  }
125
+
126
+ /**
127
+ * Reasoning characters in one assistant message, summed over its blocks.
128
+ *
129
+ * This is the *observed* size of the model's thinking, read from the transcript;
130
+ * it is never written back into a request. Routes that do not persist reasoning
131
+ * blocks return 0, and a switch that reads this stays silent there — the correct
132
+ * failure mode for something that is off by default.
133
+ *
134
+ * @param {any} source an assistant message, or an event carrying one under `message`
135
+ * @returns {number}
136
+ */
137
+ export function readReasoningChars(source) {
138
+ const message = source && typeof source === 'object' ? (source.message ?? source) : undefined
139
+ const content = message && Array.isArray(message.content) ? message.content : []
140
+ let total = 0
141
+ for (const block of content) {
142
+ if (block && typeof block === 'object' && block.type === 'reasoning' && typeof block.text === 'string') {
143
+ total += block.text.length
144
+ }
145
+ }
146
+ return total
147
+ }
package/src/lazy.js DELETED
@@ -1,245 +0,0 @@
1
- /**
2
- * Lazy mode: the one switch in this plugin that is allowed to touch how much the
3
- * model deliberates.
4
- *
5
- * Everything else here buys speed while holding effort still — the promise is
6
- * "same depth, sooner". This module is the deliberate exception, and it is the
7
- * only place in the package that rewrites `reasoningEffort`. It exists because
8
- * a long turn loses time in a way parallelism cannot fix: the model keeps
9
- * expanding a problem it has already understood. The idea is borrowed from
10
- * `ponytail` — a lazy senior developer solves the whole problem with the least
11
- * new code, and is lazy about the solution but never about the reading.
12
- *
13
- * Three mechanisms, cheapest first:
14
- *
15
- * 1. **The ladder, in the prompt.** One short text asks for the first rung that
16
- * works and a one-line "what I skipped". Cost is input tokens once per turn;
17
- * it changes nothing about the harness.
18
- * 2. **The nudge.** When a turn's streamed reasoning passes a threshold without
19
- * a new tool result, one notice says so. It is observation, not policy: it
20
- * repeats what the transcript already proves.
21
- * 3. **The effort step-down.** Only when a step visibly over-thought (the
22
- * streamed reasoner block is large) does the next request ask for one rung
23
- * less. It never goes below `FLOOR` (the user's own floor, `low`), never
24
- * fires twice in a row after an error, and is capped per turn.
25
- *
26
- * What is never cut, in this module or its text: validation at a trust boundary,
27
- * error handling that protects data, security, accessibility, and the
28
- * deliverable itself. `DIRECTIVE` says so explicitly, because a model told
29
- * "write less" drops the wrong things first.
30
- *
31
- * ## Why the step-down keeps its own ledger
32
- *
33
- * The 0.9.x release lowered effort from step two of *every* turn. Measured
34
- * later, that bought no wall clock on this route and did real damage: the
35
- * rewrite reached the provider as a request header, so the composer showed an
36
- * effort the user never chose, and it stayed there across restarts. So this
37
- * module lowers effort only after evidence, restores the resolved value on the
38
- * next request, and is off unless `lazy: true`.
39
- */
40
-
41
- /** The prompt section. Short on purpose: it is paid on every request of a turn. */
42
- export const DIRECTIVE = `Work like a lazy senior developer: the best step is the one not taken.
43
-
44
- - Read before deciding. Trace the real flow of the code the change touches, then decide. Being lazy about the solution never means being lazy about the reading.
45
- - Once a step fully does the job, stop planning it. No option, abstraction, or "for later" code nobody asked for.
46
- - Take the first rung that works: what this repo already has, then the platform or a tool you already hold, then one line, then the minimum that works.
47
- - Never cut these to save code or time: validation where input crosses a trust boundary, error handling that protects data, security, accessibility, and the deliverable the user asked for.
48
- - End the turn with one line: what you skipped, and the risk that carries.`
49
-
50
- /**
51
- * Bench variants of the ladder text, selected by `lazyVariant`. The measured
52
- * block compares them in one run, so the control stays the released text.
53
- */
54
- export const DIRECTIVES = Object.freeze({
55
- v1: DIRECTIVE,
56
- v2: `Work like a lazy senior developer: the best step is the one not taken.
57
-
58
- - Read before deciding. Trace the real flow of the code the change touches, then decide. Being lazy about the solution never means being lazy about the reading.
59
- - Once a step fully does the job, stop planning it. No option, abstraction, or "for later" code nobody asked for.
60
- - Take the first rung that works: what this repo already has, then the platform or a tool you already hold, then one line, then the minimum that works.
61
- - Never cut these to save code or time: validation where input crosses a trust boundary, error handling that protects data, security, accessibility, and the deliverable the user asked for.
62
- - End the turn with one line: what you skipped, and the risk that carries.
63
- - Do not restate the task or re-explain the plan before acting: the reader already has it.
64
- - If the next action is already clear, take it now. Do not spend a step weighing alternatives you are not going to use.`,
65
- v3: `Work like a lazy senior developer: the best step is the one not taken.
66
-
67
- - Read the flow the change touches before you decide; never be lazy about the reading.
68
- - Take the first rung that works: what the repo already has, the platform, a tool you hold, one line, then the minimum.
69
- - Once the step fully does the job, stop planning it: no option, abstraction, or code nobody asked for.
70
- - Never cut these: validation at a trust boundary, error handling that protects data, security, accessibility, and the deliverable.
71
- - Do not restate the task. If the next action is clear, take it. End with one line: what you skipped and the risk.`,
72
- })
73
-
74
-
75
- const DIRECTIVE_SUMMARY = 'lazy mode: first rung that works, no scope beyond the task'
76
-
77
- /** Reasoning rungs a route may name, weakest first. */
78
- export const EFFORT_RUNGS = Object.freeze(['minimal', 'low', 'medium', 'high', 'xhigh', 'max'])
79
-
80
- /**
81
- * The lowest rung this module will ask for. Effort is the user's contract; a
82
- * watchdog may spend a little of the headroom a high setting leaves, and stops
83
- * where the default would have been.
84
- */
85
- const FLOOR = 'low'
86
-
87
- /**
88
- * Streamed reasoning characters in one step that count as over-thinking. On the
89
- * measured route a normal step streams a few hundred characters and a step stuck
90
- * on scope streams thousands, so this is deliberately not a tight bound.
91
- */
92
- const HOT_CHARS = 1600
93
-
94
- /** Reasoning characters a turn may spend before the first nudge. */
95
- const NUDGE_CHARS = 4000
96
-
97
- /** How many notices one turn may receive. Two: one early, one late. */
98
- const MAX_NUDGES = 2
99
-
100
- /** How many effort reductions one turn may use. */
101
- const MAX_REDUCTIONS = 2
102
-
103
- /** Steps a reduction is off limits for after an error result (the step it fired on plus two). */
104
- const ERROR_COOLDOWN = 2
105
-
106
- /**
107
- * The next rung down, or `undefined` when the value is not a rung this module
108
- * understands (a route may use its own vocabulary — never guess at it) or is
109
- * already at `FLOOR`.
110
- *
111
- * @param {string} value
112
- * @returns {string | undefined}
113
- */
114
- export function cheaperEffort(value) {
115
- const at = EFFORT_RUNGS.indexOf(value)
116
- if (at <= 0) return undefined
117
- const floor = EFFORT_RUNGS.indexOf(FLOOR)
118
- if (at <= floor) return undefined
119
- return EFFORT_RUNGS[at - 1]
120
- }
121
-
122
- /** Per-session bookkeeping; one turn at a time is all a session runs. */
123
- const newTurn = () => ({
124
- step: 0,
125
- hot: false,
126
- stepChars: 0,
127
- turnChars: 0,
128
- nudges: 0,
129
- reductions: 0,
130
- coolUntil: 0,
131
- nudgedAt: 0,
132
- reducedStep: -1,
133
- })
134
-
135
- /**
136
- * Create the per-session ledger. Pure and dependency-free so the policy can be
137
- * tested without a harness.
138
- */
139
- export function createLazyWatch() {
140
- return { runs: new Map() }
141
- }
142
-
143
- /** @param {{runs: Map<string, any>}} watch @param {string} sid */
144
- const runOf = (watch, sid) => {
145
- let run = watch.runs.get(sid)
146
- if (run === undefined) {
147
- run = newTurn()
148
- watch.runs.set(sid, run)
149
- if (watch.runs.size > 64) watch.runs.delete(watch.runs.keys().next().value)
150
- }
151
- return run
152
- }
153
-
154
- /**
155
- * Count streamed reasoning characters. Called from the stream tap, i.e. before
156
- * the step ends, which is what makes the effort decision possible at all: the
157
- * decision has to be made for the *next* request.
158
- *
159
- * @param {{runs: Map<string, any>}} watch @param {string} sid @param {number} chars
160
- */
161
- export function noteReasoning(watch, sid, chars) {
162
- if (!Number.isFinite(chars) || chars <= 0) return
163
- const run = runOf(watch, sid)
164
- run.stepChars += chars
165
- run.turnChars += chars
166
- }
167
-
168
- /**
169
- * Open a step: close the previous one's verdict, and say whether this step is
170
- * the one that carries the ladder text.
171
- *
172
- * @param {{runs: Map<string, any>}} watch @param {string} sid
173
- * @param {{turn?: number, step?: number, firstStep?: boolean}} where
174
- * @returns {{directive?: string, nudge?: string} | undefined}
175
- */
176
- export function beginStep(watch, sid, where) {
177
- const run = runOf(watch, sid)
178
- const step = Number.isFinite(where?.step) ? Math.floor(where.step) : run.step + 1
179
- const newTurnStart = where?.firstStep === true || step <= 1
180
- if (newTurnStart) {
181
- const fresh = newTurn()
182
- fresh.step = step
183
- watch.runs.set(sid, fresh)
184
- return { directive: DIRECTIVES[where?.variant] || DIRECTIVE }
185
- }
186
- // The previous step's streamed volume is the evidence the next request acts on.
187
- run.hot = run.stepChars >= HOT_CHARS
188
- run.stepChars = 0
189
- run.step = step
190
- const plan = {}
191
- if (run.nudges < MAX_NUDGES && run.turnChars >= NUDGE_CHARS * (run.nudges + 1) && run.turnChars > run.nudgedAt) {
192
- run.nudges += 1
193
- run.nudgedAt = run.turnChars
194
- run.hot = true
195
- plan.nudge = `You have streamed about ${run.turnChars} characters of reasoning this turn. Use what you already have: decide, take the next rung, and say in one line what you skipped. Do not open a new line of investigation unless the last result contradicted an assumption.`
196
- }
197
- return Object.keys(plan).length === 0 ? undefined : plan
198
- }
199
-
200
- /**
201
- * A tool result came back. An error is the one signal that the model may need
202
- * *more* thinking, so it cancels any pending step-down for a couple of steps.
203
- *
204
- * @param {{runs: Map<string, any>}} watch @param {string} sid @param {{isError?: boolean}} observation
205
- */
206
- export function noteToolResult(watch, sid, observation) {
207
- if (observation?.isError !== true) return
208
- const run = runOf(watch, sid)
209
- run.hot = false
210
- run.coolUntil = run.step + ERROR_COOLDOWN + 1
211
- }
212
-
213
- /**
214
- * The effort this request should carry.
215
- *
216
- * Returns `base` untouched unless the ledger holds evidence: a hot previous
217
- * step, no pending cooldown, budget left, and a base that is a rung above
218
- * `FLOOR`. The caller must treat `effort === base` as "nothing happened".
219
- *
220
- * @param {{runs: Map<string, any>}} watch @param {string} sid
221
- * @param {string | undefined} base - the effort the harness resolved.
222
- * @returns {{effort: string | undefined, reduced: boolean}}
223
- */
224
- export function planEffort(watch, sid, base) {
225
- if (typeof base !== 'string') return { effort: base, reduced: false }
226
- const run = runOf(watch, sid)
227
- if (run.reducedStep === run.step) {
228
- // One step, one reduction: a retry inside the same step must not walk the
229
- // effort ladder down twice before anything has been measured again.
230
- return { effort: base, reduced: false }
231
- }
232
- if (!run.hot || run.step < run.coolUntil || run.reductions >= MAX_REDUCTIONS) {
233
- return { effort: base, reduced: false }
234
- }
235
- const cheaper = cheaperEffort(base)
236
- if (cheaper === undefined) return { effort: base, reduced: false }
237
- run.reductions += 1
238
- run.reducedStep = run.step
239
- return { effort: cheaper, reduced: true }
240
- }
241
-
242
- /** The notice body for the nudge, built where it is sent (see `index.js`). */
243
- export const NUDGE_SUMMARY = 'lazy mode: decide with what you have'
244
-
245
- export { DIRECTIVE_SUMMARY }