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

Open
opened 2026-02-16 18:05:53 -05:00 by yindo · 2 comments
Owner

Originally created by @mklikushin on GitHub (Jan 20, 2026).

Originally assigned to: @thdxr on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Current Problem:

When building UIs on top of Opencode, there is no way to access tool arguments until the LLM has finished streaming the entire tool call. When the LLM writes or edits large files, we have a 10-60s delay before any information is streamed. This leads to generic implementations like Writing file... as placeholders even though the file path is available in the first few bytes of the streamed JSON.

Current behavior:

  • Tool appears with status: "pending"
  • state.input is {}, state.raw is ""
  • 10-60+ seconds pass while LLM streams {"path": "/foo/bar.tsx", "content": "...hundreds of lines..."}
  • Tool transitions to status: "running" with fully parsed state.input

The file path is streamed early, but developers have no way to access it until the entire tool call is complete.

Root cause:

The streaming infrastructure exists but deltas are discarded.

In packages/opencode/src/session/processor.ts:

typescriptcase "tool-input-delta":
  break  // Delta discarded
case "tool-input-end":
  break  // Completion ignored

Meanwhile, state.raw is defined in the ToolStatePending schema but never populated.

Proposed solution:

Accumulate tool-input-delta events into state.raw and publish incremental updates.

typescriptcase "tool-input-delta":
  const match = toolcalls[value.id]
  if (match && match.state.status === "pending") {
    await Session.updatePart({
      ...match,
      state: {
        ...match.state,
        raw: (match.state.raw || "") + value.delta,
      },
    })
  }
  break

This would allow consumers/developers to parse partial JSON from state.raw to extract early fields (like path), and show meaningful UI feedback during long tool calls. Plus, this maintains backward compatibility (consumers ignoring state.raw are unaffected).

For my use case, I'm streaming generative UI into my chat (similar to cursor) and showing a generic event is super ugly and causes a big hit to UX.

Would love to open a PR on this if the change seems reasonable (for the sake of speed), no obligation to accept it if it conflicts with desired behavior.

Originally created by @mklikushin on GitHub (Jan 20, 2026). Originally assigned to: @thdxr on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request **Current Problem:** When building UIs on top of Opencode, there is no way to access tool arguments until the LLM has finished streaming the entire tool call. When the LLM writes or edits large files, we have a 10-60s delay before any information is streamed. This leads to generic implementations like Writing file... as placeholders even though the file path is available in the first few bytes of the streamed JSON. **Current behavior:** - Tool appears with status: "pending" - state.input is {}, state.raw is "" - 10-60+ seconds pass while LLM streams {"path": "/foo/bar.tsx", "content": "...hundreds of lines..."} - Tool transitions to status: "running" with fully parsed state.input The file path is streamed early, but developers have no way to access it until the entire tool call is complete. **Root cause:** The streaming infrastructure exists but deltas are discarded. In packages/opencode/src/session/processor.ts: ``` typescriptcase "tool-input-delta": break // Delta discarded case "tool-input-end": break // Completion ignored ``` Meanwhile, state.raw is defined in the ToolStatePending schema but never populated. **Proposed solution:** Accumulate tool-input-delta events into state.raw and publish incremental updates. ``` typescriptcase "tool-input-delta": const match = toolcalls[value.id] if (match && match.state.status === "pending") { await Session.updatePart({ ...match, state: { ...match.state, raw: (match.state.raw || "") + value.delta, }, }) } break ``` This would allow consumers/developers to parse partial JSON from state.raw to extract early fields (like path), and show meaningful UI feedback during long tool calls. Plus, this maintains backward compatibility (consumers ignoring state.raw are unaffected). For my use case, I'm streaming generative UI into my chat (similar to cursor) and showing a generic event is super ugly and causes a big hit to UX. Would love to open a PR on this if the change seems reasonable (for the sake of speed), no obligation to accept it if it conflicts with desired behavior.
yindo added the discussion label 2026-02-16 18:05:53 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Jan 20, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #5024: [FEATURE]: Bash tool call deltas - requests streaming output from tool calls while they're running rather than waiting for completion

Feel free to ignore if this doesn't address your specific case.

@github-actions[bot] commented on GitHub (Jan 20, 2026): This issue might be a duplicate of existing issues. Please check: - #5024: [FEATURE]: Bash tool call deltas - requests streaming output from tool calls while they're running rather than waiting for completion Feel free to ignore if this doesn't address your specific case.
Author
Owner

@mklikushin commented on GitHub (Jan 20, 2026):

Related to #5092, I see that bash tool output already streams via MessageV2.Event.PartUpdated with accumulated state. I'm requesting the same pattern be applied to tool arguments during the pending phase.

Currently, it looks like the bash output streams incrementally but the tool arguments are only available after full completion. The tool-input-delta events exist but are discarded in processor.ts. Applying the same accumulate-and-emit pattern would give developers early access to fields like path without waiting for the entire tool call to complete, improving UX for consumers who rely on downstream UI built off this.

@mklikushin commented on GitHub (Jan 20, 2026): Related to #5092, I see that bash tool output already streams via MessageV2.Event.PartUpdated with accumulated state. I'm requesting the same pattern be applied to tool arguments during the pending phase. Currently, it looks like the bash output streams incrementally but the tool arguments are only available after full completion. The tool-input-delta events exist but are discarded in processor.ts. Applying the same accumulate-and-emit pattern would give developers early access to fields like path without waiting for the entire tool call to complete, improving UX for consumers who rely on downstream UI built off this.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#7005