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:
-
**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.
-
**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
-
#message_updated(info:) ⇒ Object
The authoritative message.info was updated (cost, tokens, provider error metadata).
-
#part_added(part:, index:) ⇒ Object
A new part was appended to the reply’s parts list.
-
#part_changed(part:, index:, delta:) ⇒ Object
An existing part’s content grew by a delta (streaming text or reasoning).
-
#part_finalized(part:, index:) ⇒ Object
An existing part’s content was rewritten to the authoritative value from part.updated.
-
#permission_asked(request:, raw:) ⇒ Object
opencode emitted a permission.asked event — a tool is requesting user permission to proceed.
-
#permission_replied(request_id:, reply:, raw:, asked_at:) ⇒ Object
opencode emitted a permission.replied event — the user chose once/always/reject.
-
#question_asked(request:, raw:) ⇒ Object
opencode emitted a question.asked event — the agent’s ‘question` tool is suspended waiting for the user’s reply.
-
#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).
-
#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).
-
#session_errored(text:, raw:) ⇒ Object
A session-level error surfaced.
-
#session_retried(attempt:, message:) ⇒ Object
The upstream session is retrying an LLM call (e.g., provider rate-limit backoff).
-
#step_finished(cost:, tokens:) ⇒ Object
A step boundary with usage info.
-
#todos_changed(todos:) ⇒ Object
Agent’s internal todo list changed.
-
#tool_progressed(part:, index:, status:, raw:) ⇒ Object
A tool part transitioned status (pending → running → completed/error), or its state payload (title/input/error) changed.
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 (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 (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 (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 |