Skip to content

feat: stream tool call inputs in real time#26678

Open
fslongjin wants to merge 1 commit into
anomalyco:devfrom
fslongjin:feat-v2-tool-input-streaming
Open

feat: stream tool call inputs in real time#26678
fslongjin wants to merge 1 commit into
anomalyco:devfrom
fslongjin:feat-v2-tool-input-streaming

Conversation

@fslongjin
Copy link
Copy Markdown

@fslongjin fslongjin commented May 10, 2026

Issue for this PR

Closes #9737

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

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:

  1. SessionProcessor silently dropped tool-input-delta events — the handler was a
    bare return with no logic.
  2. session.next.tool.input.ended was a no-op in both SessionMessageUpdater and the
    TUI's sync-v2, so even the final accumulated text was never written back to the
    pending tool part.
  3. EventV2.run was gated behind OPENCODE_EXPERIMENTAL_EVENT_SYSTEM, so no v2 events
    were dispatched in normal operation regardless of the above.
  4. The /event SSE route subscribed to the request-scoped Bus.Service. Its lifetime
    ended 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, publish
    session.next.tool.input.delta on each chunk, write the full string to state.raw
    on tool-input-end and publish session.next.tool.input.ended. Also fixed a schema
    bug: the pending→running transition was spreading ...match.state, which leaked the
    raw field (only valid on ToolStatePending) into ToolStateRunning.
  • v2/event.ts: removed the experimental flag guard — v2 events are now always on.
  • session-message-updater.ts + sync-v2.tsx: implemented the
    session.next.tool.input.ended handler to write the accumulated text into the pending
    tool part's input field.
  • httpapi/event.ts: replaced the request-scoped Bus.Service subscription with a
    GlobalBus listener filtered by directory/workspace, so the SSE stream stays
    alive for the full duration of the client connection.

How did you verify your code works?

  • Added unit tests for delta accumulation and the session-message-updater reducer
    (test/session/llm.test.ts, test/v2/session-message-updater.test.ts).
  • Added a live integration test in test/session/processor-effect.test.ts that asserts
    ToolStateRunning does not contain a raw field after the pending→running transition.
  • Ran an end-to-end demo against a local opencode server and confirmed
    session.next.tool.input.delta events arrive over SSE while the LLM is generating,
    followed by session.next.tool.input.ended and session.next.tool.called.

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

- 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>
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label May 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

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:

  1. PR feat(processor): add plugin stream hooks for tool streaming lifecycle #25245 - feat(processor): add plugin stream hooks for tool streaming lifecycle

  2. PR feat(server): SSE replay buffer with Last-Event-ID support on /global/event #25658 - feat(server): SSE replay buffer with Last-Event-ID support on /global/event

  3. PR fix: show streaming progress for large tool inputs instead of stuck Preparing write #16024 - fix: show streaming progress for large tool inputs instead of stuck Preparing write

    • Addresses streaming progress for tool inputs, which complements the real-time input streaming feature
  4. PR feat(plugin): add stream.delta and stream.aborted hooks #14741 - feat(plugin): add stream.delta and stream.aborted hooks

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.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label May 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Expose partial tool arguments during streaming via state.raw

1 participant