fix(cli): emit text_delta events in run --format json#38670
Conversation
|
The following comment was made by an LLM, it may be inaccurate: I found a potentially related PR: PR #38504: feat(cli): stream incremental reasoning/text deltas in run --format json This appears to be closely related as it addresses streaming reasoning/text deltas in the |
|
Closing as duplicate of #38504, which I missed when searching before opening this. #38504 landed the same core idea a day earlier — the same partTypes map (partID mapped to part.type via message.part.updated) to attribute message.part.delta events — and is more complete (streams reasoning + text as part_delta; mine only emitted text). I did add subprocess integration tests (test/cli/run/run-process.test.ts: delta ordering, multi-chunk concatenation, reasoning-leak guard) — happy to contribute them to #38504 if useful. Apologies for the noise. |
Issue for this PR
Closes #38638
Type of change
What does this PR do?
opencode run --format jsononly emitted the completetextevent after generation finished, and silently dropped everymessage.part.deltaevent the server already produces in real time. Consumers parsing the JSON stream could not show streaming/progress output — for a long-running prompt they'd seestep_start, then nothing for tens of seconds, then the whole response andstep_finishat once (#38638).The
runevent loop inpackages/opencode/src/cli/cmd/run.tshandledmessage.part.updatedbut had no case formessage.part.delta. This adds one: under--format json, each streaming text chunk is mirrored as atext_deltaevent ({ messageID, partID, delta }) ahead of the final completetextevent, which is still emitted for backward compatibility. Non-json mode is unchanged — its text rendering is driven by the complete part.Worth flagging: a
message.part.deltaevent carriesfieldbut not the part type, and reasoning deltas also usefield: "text"—processor.tsupdates the reasoning part's text via the sameupdatePartDelta({ field: "text", ... })call. Filtering onfield === "text"alone would therefore leak reasoning content intotext_delta. The fix tracks each part's type frommessage.part.updatedin apartTypesmap and only emitstext_deltafor parts whose type istext, mirroring howrun/session-data.tsresolves a delta's kind before rendering it.How did you verify your code works?
bun run typecheck(tsgo --noEmit) on theopencodepackage: 0 errors.test/cli/run/run-process.test.ts(real CLI binary against an in-processTestLLMServer): updated the three existing--format jsoncases to asserttext_deltanow precedestext, and added a regression case for opencode run --format json does not emit message.part.delta events in real time #38638 that sends two text chunks on one reply and asserts thetext_deltaevents concatenate to the full text, carrymessageID/partID, precede the finaltext, and that the completetextevent is still emitted.bun test test/cli/run/run-process.test.ts --test-name-pattern "format json": 4/5 of the affected cases pass locally. The remaining case (records partial output for an unknown stream finish) fails on Windows before this PR as well — confirmed bygit stash-ing the changes and re-running on unmodifieddev, where it fails the same way (the secondstep_start/step_finishis not emitted). It is a Windowscross-spawnexit-timing issue with the mid-streamllm.fail, not a regression; the Linux CI run is the source of truth.Screenshots / recordings
N/A — pure CLI/JSON output change.
Checklist