release/1.3.1
rs 82 lines 3 KB
Raw
1 //! Where Ed reports what just happened.
2
3 use onde::inference::EngineStatus;
4
5 use crate::{AgentReply, ApprovalRequest, ChatReply, ToolCall, ToolExecutionResult};
6
7 /// Receives lifecycle transitions and completed replies.
8 ///
9 /// `onde` reports status by polling
10 /// [`info()`](onde::inference::ChatEngine::info), which is the wrong shape for
11 /// a UI: nothing tells the view that a load finished, so hosts end up either
12 /// timing a poll loop or threading their own event emission through every call
13 /// site. Ed pushes instead, and a host implements this once to bridge into
14 /// whatever its UI listens on.
15 ///
16 /// Both methods are synchronous and should stay cheap — they are called while
17 /// Ed is mid-operation. Hand off to a channel or an event bus rather than
18 /// blocking. Implementations must be `Send + Sync` because a load or a send
19 /// may be driven from any task.
20 pub trait EventSink: Send + Sync + 'static {
21 /// The engine moved to a new lifecycle state.
22 ///
23 /// `model_name` is the model involved where one is known, and `error`
24 /// carries the reason when `status` is [`EngineStatus::Error`].
25 fn status_changed(
26 &self,
27 _status: EngineStatus,
28 _model_name: Option<&str>,
29 _error: Option<&str>,
30 ) {
31 }
32
33 /// An inference turn finished, successfully or not.
34 ///
35 /// Delivered as a notification rather than only as a return value because
36 /// inference is slow: a host that awaits the call from a UI callback can
37 /// have that callback collected before it resolves. Emitting lets the view
38 /// pick the result up independently.
39 fn replied(&self, _reply: &ChatReply) {}
40
41 /// A piece of the assistant's reply, as it is generated.
42 ///
43 /// Called zero or more times during [`Ed::run`](crate::Ed::run) before
44 /// the final reply is reported. Concatenating the deltas gives the reply
45 /// text. The default does nothing, so sinks written before streaming
46 /// existed keep compiling and keep seeing only the finished reply.
47 fn text_delta(&self, _delta: &str) {}
48
49 fn tool_requested(&self, _call: &ToolCall) {}
50 fn approval_requested(&self, _request: &ApprovalRequest) {}
51 fn tool_started(&self, _call: &ToolCall) {}
52 fn tool_finished(&self, _result: &ToolExecutionResult) {}
53 fn agent_replied(&self, _reply: &AgentReply) {}
54 fn warning(&self, _message: &str) {}
55 }
56
57 /// Compatibility name retained for the original Ed API.
58 pub use EventSink as StatusSink;
59
60 /// A [`StatusSink`] that drops everything.
61 ///
62 /// The default for [`Ed::new`](crate::Ed::new), and what you want in tests or
63 /// a host that only ever polls [`Ed::info`](crate::Ed::info).
64 #[derive(Debug, Clone, Copy, Default)]
65 pub struct NoopSink;
66
67 impl EventSink for NoopSink {}
68
69 #[cfg(test)]
70 mod tests {
71 use super::*;
72
73 struct Bare;
74 impl EventSink for Bare {}
75
76 #[test]
77 fn text_delta_defaults_to_a_no_op() {
78 // A sink that predates streaming must still compile and not panic.
79 Bare.text_delta("hello");
80 NoopSink.text_delta("hello");
81 }
82 }