Repository navigation
dsh-replay-theater — replay a session at its original token cadence (the dt[] array finally has a reader) #5270
Replies: 2 comments
Compatibility notice: this plugin does not work on 0.2.0 yetRe-synced against The premise still holds. The implementation is reading a shape that no longer exists. What brokeThe plugin consumes Two upstream changes did it, both deliberate:
So the offline The good news: the cadence data was relocated, not removed// packages/llm/llm/src/assistant-stream.ts:20-47
export type AssistantStreamRecord =
| { readonly type: 'text-chunks'; readonly time0: number; readonly index: number
readonly dt: readonly number[]; readonly texts: readonly string[] }
| { readonly type: 'reasoning-chunks'; /* … */ }
| { readonly type: 'tool-call-chunks'; /* … */ readonly id: ToolCallId; readonly args: readonly string[] }
| { readonly type: 'chunk'; readonly time: number; readonly chunk: StreamChunk }
So the observation this plugin was built on — that the per-token inter-arrival gaps are deliberately preserved rather than joined away — is still true on 0.2.0. What changed is where you read it. What still works
One behavior note for anyone screenshotting: the Trajectory tab is now hidden behind the Developer Tools toggle ( Fix shape, for anyone who wants to fork before I get to it
I'd rather leave this notice up than let the README's install line mislead someone. Will update the repo and follow up here when the reader is ported. Incidentally, this is a good advertisement for the structural-mirror approach the post describes: because the mirrored types carried |
Ported. The reader now reads 0.2.0.Following up on the compatibility notice above: the repo is updated and the install line is safe again. First, a correction to my own notice. I wrote that What the port actually tookThree files, and the shape of the work was not "rename the event types":
A bug the port introduced, in case you hit the same oneMy stage accumulator grouped blocks by This cannot happen on 0.1.x — the old shape never put those two on comparable coordinates. It is specifically a hazard the fold introduces. Block identity now includes The snapshot corpus is far richer than I thought, and that matters for plugin authorsI had been asserting — in my README and in a fixture header — that the corpus cannot support stream tests, on the grounds that the longest same-block delta run in it is 2 members. I rescanned all of it on
So if you are writing anything that consumes What the corpus still cannot give you is cadence: 373 of the 526 runs have an all-zero Scope of verification, stated plainly165 tests, clean typecheck, clean build. I have not run this against a live 0.2.0 instance. The porting evidence is upstream source plus the test suite, nothing more. If you install it on a real 0.2.0 and something is off, that is genuinely new information to me — please say so here. Repo: https://github.com/ItBayMax/dsh-replay-theater (MIT) |
Uh oh!
There was an error while loading. Please reload this page.
Repo: https://github.com/ItBayMax/dsh-replay-theater · MIT ·
dsh plugin --profile web add dsh-replay-theaterThe observation that started it
Packed chunk rows keep every token's real inter-arrival gap, and the source says why:
Packed storage deliberately does not join a run's members into one string, so the original
cadence survives on disk. As far as I could tell, nothing in the harness read
dtback.This plugin is that reader: it turns the session log into a playable timeline.
What it does
A Theater tab appears beside Chat and Trajectory. The assistant's answer grows token by
token, spaced by the real millisecond gaps recorded when it was generated — play, pause,
single-step, scrub, 0.5×–8× speed.
Transport bar, plus scalar events (
step/start,tool/result, …) on their own marker rail:It also compares two runs side by side and reports where they stop matching — useful for
"why did the same prompt behave differently this time":
The replay core is framework-free and harness-free, so the same functions also work offline:
node scripts/replay-cli.mjs path/to/session.jsonl --frames 20 node scripts/replay-cli.mjs run-a.jsonl run-b.jsonl # compare two runsTwo design notes that might interest others
Real cadence is unwatchable verbatim. On a real 5,776-event session (54,626 recorded gaps)
the median gap is 0 ms — provider chunks routinely land in the same millisecond, and the
log clock is millisecond-resolution. Replaying that literally makes whole paragraphs appear
instantly. So gaps are clamped on both ends: a
minGapMsfloor restores visible typing, amaxGapMsceiling keeps a 30-second think from stalling playback. Whenever silence iscompressed, the transport bar says by how much — playback alters the data's timing, so it
should admit it.
The two shapes of a chunk row are not the same. A JSONL storage line carries
seq0/time0;the client wire record carries
seq/time. I read the wrong pair first, and only productiondata exposed it — totals were roughly 2× too large and sequences collided. Worth a heads-up for
anyone else parsing
session.jsonldirectly. (decodeStorageRecord,chunk-rows.ts:363, isthe upstream decoder for the storage form.)
Compatibility
Developed and validated against
0.1.2-alpha.2. It reads only the public client surface —session.eventSourceandsession.loadOlder()— deliberately, since 0.1.2 removed the wholeclient/runtimepackage; depending on internals is not survivable.@deepseek-ai/dsh-api-session-controllerisn't on npm, sosrc/core/wire.tscarries structuralmirrors of the upstream shapes, each annotated with the
file:lineit was read from. They'restructurally compatible, so swapping them for real imports later needs no call-site changes.
If those types ever get published, I'd happily drop the mirrors.
148 tests; the replay core runs without a browser or a harness checkout.
中文
仓库:https://github.com/ItBayMax/dsh-replay-theater · MIT
打包存储刻意不合并 run 的成员,把每个 token 的真实到达间隔完整留在了
dt数组里(
chunk-rows.ts:48:token 边界就是数据),但 harness 里似乎没有任何东西读回它。这个插件就是那个使用者:在 Chat / Trajectory 旁边加一个剧场标签页,
按原始节奏逐 token 重演一次会话,支持播放/暂停/单步/拖拽/0.5×–8× 倍速,
还能把两次运行并排对比、报出首个分歧点。
两个可能对他人有用的发现:
——同一毫秒内到达的 chunk 很常见。所以两端都做了钳制,并且只要压缩了静默就在播放条上明示。
seq0/time0,客户端记录是seq/time。我一开始读错了,只有真实生产数据才暴露出来(总时长约 2 倍偏大、序号冲突)。
直接解析
session.jsonl的同学可以留意。针对
0.1.2-alpha.2开发并验证,只依赖公开客户端面(session.eventSource/session.loadOlder())。All reactions