From f3af6f9f225f73ab7467b61a91b1f5186b204952 Mon Sep 17 00:00:00 2001 From: charlesnutter Date: Thu, 24 Sep 2026 21:28:34 -0700 Subject: [PATCH 1/3] Count tool-call argument streaming as generation time --- CHANGELOG.md | 8 ++++++++ tui.tsx | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 245fdc9..8755394 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [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. + ## [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..9b39f0c 100644 --- a/tui.tsx +++ b/tui.tsx @@ -1105,6 +1105,12 @@ 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, streamed as their + // own deltas and 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. + off.push(ctx.data.on("session.tool.input.delta", mark)) // Diagnostics only (OPENCODE_HUD_DEBUG): the per-step events, to design // reading the engine once per step instead of once per turn. On From 9e1b7ae4b5d0faebba9c1445af77ccd402518e96 Mon Sep 17 00:00:00 2001 From: charlesnutter Date: Thu, 24 Sep 2026 21:36:45 -0700 Subject: [PATCH 2/3] Mark tool-call argument start and end; log each stream window against its end --- tui.tsx | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/tui.tsx b/tui.tsx index 9b39f0c..35fcaae 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,12 +1115,24 @@ 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, streamed as their - // own deltas and 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. - off.push(ctx.data.on("session.tool.input.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. Watching the argument deltas + // alone still left the session at 37.7 against the engine's 32.4, so + // the start and end of the arguments are marks too; whether the deltas + // reach a plugin at all is logged below. + 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 From 94fa2768e90ffa781fb17a8b99b1df5de2afdeca Mon Sep 17 00:00:00 2001 From: charlesnutter Date: Thu, 24 Sep 2026 21:50:03 -0700 Subject: [PATCH 3/3] Record what the tool-input marks measured --- CHANGELOG.md | 4 +++- tui.tsx | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8755394..c7f0916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ 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. + 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 diff --git a/tui.tsx b/tui.tsx index 35fcaae..73cc6bd 100644 --- a/tui.tsx +++ b/tui.tsx @@ -1119,10 +1119,12 @@ export default Plugin.define({ // 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. Watching the argument deltas - // alone still left the session at 37.7 against the engine's 32.4, so - // the start and end of the arguments are marks too; whether the deltas - // reach a plugin at all is logged below. + // 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) => {