diff --git a/CHANGELOG.md b/CHANGELOG.md index 245fdc9..c7f0916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## [Unreleased] +### Fixed +- Generation speed counts the time a model spends writing a tool call's + arguments. Those tokens were counted but their streaming time was not, so + a step that wrote a file showed several times its real speed (3,672 tokens + "at 162.9 tok/s" against the engine's 36.4), and the Session average was + inflated with it. Affects OpenCode's figures, not an engine's own. On + MTPLX, a turn that wrote a file now gives 36.4 tok/s from OpenCode's + figures against the engine's 36.1. + ## [0.3.2] – 2026-09-25 ### Fixed - A reply is recorded when it ends, not when OpenCode's execution does. A diff --git a/tui.tsx b/tui.tsx index 88f9595..73cc6bd 100644 --- a/tui.tsx +++ b/tui.tsx @@ -1094,7 +1094,17 @@ export default Plugin.define({ off.push( ctx.data.on("session.step.streamed", (evt) => { const id = stepID(evt) - if (id) readStep(id, "step.streamed") + if (!id) return + if (HUD_DEBUG) { + const t = turns.get(id) + const now = Date.now() + dbg( + ` stream window ${id}: ${t?.firstAt !== undefined && t.lastAt !== undefined ? ((t.lastAt - t.firstAt) / 1000).toFixed(2) : "?"}s; ` + + `last mark ${t?.lastAt !== undefined ? now - t.lastAt : "?"}ms before step.streamed; tool deltas ${toolDeltas.get(id) ?? 0}` + ) + toolDeltas.delete(id) + } + readStep(id, "step.streamed") }) ) off.push( @@ -1105,6 +1115,26 @@ export default Plugin.define({ ) off.push(ctx.data.on("session.text.delta", mark)) off.push(ctx.data.on("session.reasoning.delta", mark)) + // A tool call's arguments are generated tokens too, counted in the + // step's output. Unwatched, a step that wrote a file had its tokens + // divided by only its text's streaming time (measured: 3,672 tokens "at + // 162.9 tok/s" on a `write` step, against the engine's 36.4 for the + // turn), inflating the session's speed. The argument deltas never + // reach a plugin (measured: 0 on every step of an 8-step turn that + // wrote a file); the start and end of the arguments do, and with them + // the window's last mark lands 0-1ms before the stream ends. Session + // 36.4 tok/s against the engine's 36.1, from 55.1 against 36.4. The + // delta subscription is kept in case a later OpenCode forwards them. + const toolDeltas = new Map() + off.push( + ctx.data.on("session.tool.input.delta", (evt) => { + mark(evt) + const id = stepID(evt) + if (id) toolDeltas.set(id, (toolDeltas.get(id) ?? 0) + 1) + }) + ) + off.push(ctx.data.on("session.tool.input.started", mark)) + off.push(ctx.data.on("session.tool.input.ended", mark)) // Diagnostics only (OPENCODE_HUD_DEBUG): the per-step events, to design // reading the engine once per step instead of once per turn. On