Module: Opencode::ReplyObserver

Included in:
StreamBlockObserver
Defined in:
lib/opencode/reply_observer.rb

Overview

The canonical observer protocol for Opencode::Reply — every event Reply dispatches, documented in one place, with safe no-op defaults.

Include this module in a reply-stream class to get two things:

  1. Compile-time checklist. Override only the callbacks you care about; the rest inherit a no-op. Forgetting to handle a new event never crashes the stream.
  2. Protocol documentation that can't rot. The signatures here are the contract. If Reply's dispatch shape ever drifts, every observer using this module updates in lockstep.

Callbacks are duck-typed in Reply — features may choose not to include this module and implement the methods directly, but then they lose the two benefits above.

Every callback takes keyword arguments, so adding a new keyword later only requires existing observers to add **_ if they want to opt out of breakage.

Instance Method Summary collapse

Instance Method Details

#message_updated(info:) ⇒ Object

The authoritative message.info was updated (cost, tokens, provider error metadata). Fires late in the stream after the agent closes.



63
64
# File 'lib/opencode/reply_observer.rb', line 63

def message_updated(info:)
end

#part_added(part:, index:) ⇒ Object

A new part was appended to the reply's parts list.



25
26
# File 'lib/opencode/reply_observer.rb', line 25

def part_added(part:, index:)
end

#part_changed(part:, index:, delta:) ⇒ Object

An existing part's content grew by a delta (streaming text or reasoning).



30
31
# File 'lib/opencode/reply_observer.rb', line 30

def part_changed(part:, index:, delta:)
end

#part_finalized(part:, index:) ⇒ Object

An existing part's content was rewritten to the authoritative value from part.updated. Fires unconditionally when a part closes so throttled observers can flush, regardless of whether content actually diverged from what deltas accumulated.



37
38
# File 'lib/opencode/reply_observer.rb', line 37

def part_finalized(part:, index:)
end

#permission_asked(request:, raw:) ⇒ Object

opencode emitted a permission.asked event — a tool is requesting user permission to proceed. request is the PermissionRequest hash (sessionID, permission, patterns, metadata, always, tool?).



92
93
# File 'lib/opencode/reply_observer.rb', line 92

def permission_asked(request:, raw:)
end

#permission_replied(request_id:, reply:, raw:, asked_at:) ⇒ Object

opencode emitted a permission.replied event — the user chose once/always/reject. reply is the string. asked_at per question_replied semantics.



98
99
# File 'lib/opencode/reply_observer.rb', line 98

def permission_replied(request_id:, reply:, raw:, asked_at:)
end

#question_asked(request:, raw:) ⇒ Object

opencode emitted a question.asked event — the agent's question tool is suspended waiting for the user's reply. request is the full QuestionRequest hash (sessionID, questions, tool?).



74
75
# File 'lib/opencode/reply_observer.rb', line 74

def question_asked(request:, raw:)
end

#question_rejected(request_id:, raw:, asked_at:) ⇒ Object

opencode emitted a question.rejected event — the user dismissed the prompt, or it was cancelled (e.g., container shutdown).



86
87
# File 'lib/opencode/reply_observer.rb', line 86

def question_rejected(request_id:, raw:, asked_at:)
end

#question_replied(request_id:, answers:, raw:, asked_at:) ⇒ Object

opencode emitted a question.replied event — the user submitted answers (Array<Array>, one inner array per question). asked_at is the monotonic clock value when question.asked was observed, for latency telemetry; nil if asked never arrived.



81
82
# File 'lib/opencode/reply_observer.rb', line 81

def question_replied(request_id:, answers:, raw:, asked_at:)
end

#session_errored(text:, raw:) ⇒ Object

A session-level error surfaced. Text is a human-readable summary ("ErrorName: details"); raw is the full error hash.



58
59
# File 'lib/opencode/reply_observer.rb', line 58

def session_errored(text:, raw:)
end

#session_retried(attempt:, message:) ⇒ Object

The upstream session is retrying an LLM call (e.g., provider rate-limit backoff). Attempt is nullable; message is a short reason string.



53
54
# File 'lib/opencode/reply_observer.rb', line 53

def session_retried(attempt:, message:)
end

#step_finished(cost:, tokens:) ⇒ Object

A step boundary with usage info. tokens is the raw tokens hash from the step-finish part (keys: :input, :output, :reasoning, :cache).



47
48
# File 'lib/opencode/reply_observer.rb', line 47

def step_finished(cost:, tokens:)
end

#todos_changed(todos:) ⇒ Object

Agent's internal todo list changed. Todos are whatever shape the agent's task tool uses.



68
69
# File 'lib/opencode/reply_observer.rb', line 68

def todos_changed(todos:)
end

#tool_progressed(part:, index:, status:, raw:) ⇒ Object

A tool part transitioned status (pending → running → completed/error), or its state payload (title/input/error) changed.



42
43
# File 'lib/opencode/reply_observer.rb', line 42

def tool_progressed(part:, index:, status:, raw:)
end