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<String>>, 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