Skip to main content

Module opencode

Module opencode 

Source
Expand description

OpenCode event parser implementation

This module handles parsing and displaying OpenCode NDJSON event streams.

§Source Code Reference

This parser is based on analysis of the OpenCode source code from:

  • Repository: https://github.com/anomalyco/opencode
  • Key source files:
    • /packages/opencode/src/cli/cmd/run.ts - NDJSON output generation
    • /packages/opencode/src/session/message-v2.ts - Message part type definitions

§NDJSON Event Format

OpenCode outputs NDJSON (newline-delimited JSON) events via --format json. Each event has the structure:

{
  "type": "step_start" | "step_finish" | "tool_use" | "text" | "error",
  "timestamp": 1768191337567,
  "sessionID": "ses_44f9562d4ffe",
  ...event-specific data (usually in "part" field)
}

From run.ts lines 146-201, the event types are generated as:

outputJsonEvent("tool_use", { part })    // Tool invocations
outputJsonEvent("step_start", { part })  // Step initialization
outputJsonEvent("step_finish", { part }) // Step completion
outputJsonEvent("text", { part })        // Streaming text content
outputJsonEvent("error", { error })      // Error events

§Part Type Definitions

§StepStartPart (message-v2.ts lines 194-200)

{
  id: string,
  sessionID: string,
  messageID: string,
  type: "step-start",
  snapshot: string | undefined  // Git commit hash for state snapshot
}

§StepFinishPart (message-v2.ts lines 202-219)

{
  id: string,
  sessionID: string,
  messageID: string,
  type: "step-finish",
  reason: string,               // "tool-calls", "end_turn", etc.
  snapshot: string | undefined,
  cost: number,                 // Cost in USD
  tokens: {
    input: number,
    output: number,
    reasoning: number,
    cache: { read: number, write: number }
  }
}

§TextPart (message-v2.ts lines 62-77)

{
  id: string,
  sessionID: string,
  messageID: string,
  type: "text",
  text: string,
  synthetic?: boolean,
  ignored?: boolean,
  time?: { start: number, end?: number },
  metadata?: Record<string, any>
}

§ToolPart (message-v2.ts lines 289-298)

{
  id: string,
  sessionID: string,
  messageID: string,
  type: "tool",
  callID: string,
  tool: string,  // Tool name: "read", "bash", "write", "edit", "glob", "grep", etc.
  state: ToolState,
  metadata?: Record<string, any>
}

§ToolState Variants (message-v2.ts lines 221-287)

The state field is a discriminated union based on status:

§Pending (status: "pending")

{ status: "pending", input: Record<string, any>, raw: string }

§Running (status: "running")

{
  status: "running",
  input: Record<string, any>,
  title?: string,
  metadata?: Record<string, any>,
  time: { start: number }
}

§Completed (status: "completed")

{
  status: "completed",
  input: Record<string, any>,
  output: string,
  title: string,
  metadata: Record<string, any>,
  time: { start: number, end: number, compacted?: number },
  attachments?: FilePart[]
}

§Error (status: "error")

{
  status: "error",
  input: Record<string, any>,
  error: string,
  metadata?: Record<string, any>,
  time: { start: number, end: number }
}

§Tool Input Parameters

The state.input object contains tool-specific parameters:

ToolInput Fields
read{ filePath: string, offset?: number, limit?: number }
bash{ command: string, timeout?: number }
write{ filePath: string, content: string }
edit{ filePath: string, ... }
glob{ pattern: string, path?: string }
grep{ pattern: string, path?: string, include?: string }
fetch{ url: string, format?: string, timeout?: number }

From run.ts line 168, the title fallback is:

const title = part.state.title ||
  (Object.keys(part.state.input).length > 0 ? JSON.stringify(part.state.input) : "Unknown")

§Streaming Output Behavior

This parser implements real-time streaming output for text deltas. When content arrives in multiple chunks (via text events), the parser:

  1. Accumulates text deltas from each chunk into a buffer
  2. Displays the accumulated text after each chunk
  3. Uses carriage return (\r) and line clearing (\x1b[2K) to rewrite the entire line, creating an updating effect that shows the content building up in real-time
  4. Shows prefix on every delta, rewriting the entire line each time (industry standard)

Example output sequence for streaming “Hello World” in two chunks:

[OpenCode] Hello\r       (first text event with prefix, no newline)
\x1b[2K\r[OpenCode] Hello World\r  (second text event clears line, rewrites with accumulated)
[OpenCode] ✓ Step finished... (step_finish shows prefix with newline)

§Single-Line Pattern

The renderer uses a single-line pattern with carriage return for in-place updates. This is the industry standard for streaming CLIs (used by Rich, Ink, Bubble Tea).

Each delta rewrites the entire line with prefix, ensuring that:

  • The user always sees the prefix
  • Content updates in-place without visual artifacts
  • Terminal state is clean and predictable

Structs§

OpenCodeCache
Cache statistics
OpenCodeError
Error information from error events
OpenCodeEvent
OpenCode event types
OpenCodeParser
OpenCode event parser
OpenCodePart
Nested part object containing the actual event data
OpenCodeTime
Time information
OpenCodeTokens
Token statistics from step_finish events
OpenCodeToolState
Tool state containing status, input, and output