Skip to content

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

@mklikushin

Description

@mklikushin

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.

Metadata

Metadata

Assignees

Labels

discussionUsed for feature requests, proposals, ideas, etc. Open discussion

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions