Skip to content

opencode run --format json does not emit message.part.delta events in real time #38638

Description

@larrylian

Description

Description

opencode run --format json does not emit text delta events while the model is generating a response.

The underlying OpenCode event stream produces message.part.delta events in real time, but the run command ignores them. It only emits one complete text event after the text part has finished.

This makes opencode run --format json unsuitable for integrations that need streaming output, progress reporting, or long-running task observability.

Environment

  • OpenCode: 1.18.4
  • Also reproduced with: 1.17.15
  • Platform: macOS arm64
  • Output format: json
  • Provider: OpenAI-compatible third-party provider

The current dev branch appears to have the same behavior.

Reproduction

Run a prompt that takes several seconds to generate:

opencode run \
  --format json \
  --model provider/model \
  "Write exactly 50 numbered lines about remote software collaboration. Do not use tools."

Record the time at which each stdout line is received.

Actual behavior

The output looks approximately like this:

06:44:59.965  {"type":"step_start", ...}

# No stdout events while the model is generating

06:45:09.299  {"type":"text","part":{"type":"text","text":"<complete response>","time":{"start":...,"end":...}}}
06:45:09.299  {"type":"step_finish", ...}

The complete response is emitted as a single text event immediately before step_finish.

No token-level or chunk-level text events are written to stdout during generation.

Expected behavior

When --format json is used, opencode run should expose text deltas as they are received, for example:

{"type":"text_delta","sessionID":"...","partID":"...","delta":"First chunk"}
{"type":"text_delta","sessionID":"...","partID":"...","delta":" second chunk"}

The final complete text event may still be emitted for compatibility.

Alternatively, this behavior could be enabled explicitly with a flag such as:

opencode run --format json --stream

Investigation

OpenCode's server event stream already emits message.part.delta events in real time. This can be observed by:

  1. Starting opencode serve.
  2. Subscribing to the /event SSE endpoint.
  3. Sending a prompt.

The SSE stream delivers many message.part.delta events during generation, followed by the final message.part.updated event.

However, the run command only handles message.part.updated:

https://github.com/anomalyco/opencode/blob/v1.18.4/packages/opencode/src/cli/cmd/run.ts#L850-L878

In particular, text is emitted only after part.time?.end is present:

if (part.type === "text" && part.time?.end) {
  if (emit("text", { part })) continue
  // ...
}

The command does not appear to handle message.part.delta.

The current dev branch appears to contain the same logic:

https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/cli/cmd/run.ts#L850-L878

Impact

Applications that integrate OpenCode through opencode run --format json cannot observe generation progress.

For long-running prompts, consumers receive step_start, then no output for tens of seconds or minutes, followed by the entire response and step_finish at nearly the same time.

This prevents consumers from reliably:

  • displaying streaming responses;
  • reporting intermediate progress;
  • distinguishing active generation from a stalled process;
  • persisting incremental output;
  • implementing idle or health timeouts accurately.

The server SSE API contains the required information, but CLI-based integrations cannot access it through opencode run.

Suggested fix

Handle message.part.delta inside the run event loop and emit a JSON event containing at least:

  • session ID;
  • message ID or part ID;
  • field name;
  • delta text.

For example:

if (event.type === "message.part.delta") {
  const { sessionID, messageID, partID, field, delta } = event.properties
  if (sessionID !== activeSessionID) continue

  if (field === "text") {
    emit("text_delta", {
      messageID,
      partID,
      delta,
    })
  }
}

If changing the existing JSON event contract is a compatibility concern, this could be guarded by a new --stream or --include-deltas flag.



### Plugins

_No response_

### OpenCode version

latest

### Steps to reproduce

_No response_

### Screenshot and/or share link

_No response_

### Operating System

_No response_

### Terminal

_No response_

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions