feat: stream tool call inputs in real time#26678
Conversation
- Accumulate raw tool input via delta events - Update pending tool state with raw input - Filter SSE events using global bus - Remove v2 event system experimental flag Signed-off-by: jinlong <jinlong@tencent.com>
|
The following comment was made by an LLM, it may be inaccurate: Based on my search, I found several potentially related PRs that deal with streaming and tool inputs: Potentially Related PRs:
These PRs share overlapping concerns around tool call streaming and SSE event handling, though they appear to address different aspects. PR #26678 may build upon or be complementary to some of these changes. |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Issue for this PR
Closes #9737
Type of change
What does this PR do?
opencode already had v2 event definitions for streaming tool call inputs
(
session.next.tool.input.started/delta/ended) but none of the plumbing was connected,so clients received no streaming input events at all. This PR wires everything up so
frontends can show tool arguments appearing incrementally while the LLM is still
generating them — similar to how Cursor displays tool call inputs as they stream in.
Four concrete problems were fixed:
SessionProcessorsilently droppedtool-input-deltaevents — the handler was abare
returnwith no logic.session.next.tool.input.endedwas a no-op in bothSessionMessageUpdaterand theTUI's
sync-v2, so even the final accumulated text was never written back to thepending tool part.
EventV2.runwas gated behindOPENCODE_EXPERIMENTAL_EVENT_SYSTEM, so no v2 eventswere dispatched in normal operation regardless of the above.
/eventSSE route subscribed to the request-scopedBus.Service. Its lifetimeended when the HTTP handler returned the streaming response, causing the client event
stream to close immediately — so even if events had been published, clients would
never have received them.
Changes per file:
processor.ts: accumulate delta text per tool call, publishsession.next.tool.input.deltaon each chunk, write the full string tostate.rawon
tool-input-endand publishsession.next.tool.input.ended. Also fixed a schemabug: the pending→running transition was spreading
...match.state, which leaked therawfield (only valid onToolStatePending) intoToolStateRunning.v2/event.ts: removed the experimental flag guard — v2 events are now always on.session-message-updater.ts+sync-v2.tsx: implemented thesession.next.tool.input.endedhandler to write the accumulated text into the pendingtool part's input field.
httpapi/event.ts: replaced the request-scopedBus.Servicesubscription with aGlobalBuslistener filtered bydirectory/workspace, so the SSE stream staysalive for the full duration of the client connection.
How did you verify your code works?
session-message-updaterreducer(
test/session/llm.test.ts,test/v2/session-message-updater.test.ts).test/session/processor-effect.test.tsthat assertsToolStateRunningdoes not contain arawfield after the pending→running transition.session.next.tool.input.deltaevents arrive over SSE while the LLM is generating,followed by
session.next.tool.input.endedandsession.next.tool.called.Screenshots / recordings
If this is a UI change, please include a screenshot or recording.
Checklist