Skip to main content

walrus_core/protocol/message/
server.rs

1//! Messages sent by the gateway to the client.
2
3use compact_str::CompactString;
4use serde::{Deserialize, Serialize};
5
6/// Complete response from an agent.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SendResponse {
9    /// Source agent identifier.
10    pub agent: CompactString,
11    /// Response content.
12    pub content: String,
13    /// Session ID used for this request.
14    pub session: u64,
15}
16
17/// Lightweight tool call info for streaming events.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct ToolCallInfo {
20    /// Tool name.
21    pub name: CompactString,
22    /// Tool arguments (JSON string).
23    pub arguments: String,
24}
25
26/// Events emitted during a streamed agent response.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum StreamEvent {
29    /// Stream has started.
30    Start {
31        /// Source agent identifier.
32        agent: CompactString,
33        /// Session ID used for this stream.
34        session: u64,
35    },
36    /// A chunk of streamed content.
37    Chunk {
38        /// Chunk content.
39        content: String,
40    },
41    /// A chunk of thinking/reasoning content.
42    Thinking {
43        /// Thinking content.
44        content: String,
45    },
46    /// Agent started executing tool calls.
47    ToolStart {
48        /// Tool calls being executed.
49        calls: Vec<ToolCallInfo>,
50    },
51    /// A single tool call completed.
52    ToolResult {
53        /// The tool call ID.
54        call_id: CompactString,
55        /// Tool output.
56        output: String,
57    },
58    /// All tool calls completed.
59    ToolsComplete,
60    /// Stream has ended.
61    End {
62        /// Source agent identifier.
63        agent: CompactString,
64    },
65}
66
67/// Kind of download operation.
68#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum DownloadKind {
71    /// Local model download from HuggingFace.
72    Model,
73    /// Hub package install/uninstall.
74    Hub,
75    /// Embeddings model pre-download.
76    Embeddings,
77    /// Skill download (future).
78    Skill,
79}
80
81impl std::fmt::Display for DownloadKind {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            Self::Model => write!(f, "model"),
85            Self::Hub => write!(f, "hub"),
86            Self::Embeddings => write!(f, "embeddings"),
87            Self::Skill => write!(f, "skill"),
88        }
89    }
90}
91
92/// Unified download lifecycle events.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub enum DownloadEvent {
95    /// A new download was registered.
96    Created {
97        /// Download identifier.
98        id: u64,
99        /// Kind of download.
100        kind: DownloadKind,
101        /// Human-readable label (model ID, package name, etc.).
102        label: String,
103    },
104    /// Byte-level progress (delta, not cumulative).
105    Progress {
106        /// Download identifier.
107        id: u64,
108        /// Bytes downloaded in this chunk.
109        bytes: u64,
110        /// Total expected bytes (0 if unknown).
111        total_bytes: u64,
112    },
113    /// Human-readable progress step.
114    Step {
115        /// Download identifier.
116        id: u64,
117        /// Step description.
118        message: String,
119    },
120    /// Download completed successfully.
121    Completed {
122        /// Download identifier.
123        id: u64,
124    },
125    /// Download failed.
126    Failed {
127        /// Download identifier.
128        id: u64,
129        /// Error message.
130        error: String,
131    },
132}
133
134/// Summary of a download in the registry.
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct DownloadInfo {
137    /// Download identifier.
138    pub id: u64,
139    /// Kind of download.
140    pub kind: DownloadKind,
141    /// Human-readable label.
142    pub label: String,
143    /// Current status.
144    pub status: String,
145    /// Bytes downloaded so far.
146    pub bytes_downloaded: u64,
147    /// Total expected bytes (0 if unknown).
148    pub total_bytes: u64,
149    /// Error message (if failed).
150    #[serde(default, skip_serializing_if = "Option::is_none")]
151    pub error: Option<String>,
152    /// Seconds since download started.
153    pub alive_secs: u64,
154}
155
156/// Summary of an active session.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct SessionInfo {
159    /// Session identifier.
160    pub id: u64,
161    /// Agent this session is bound to.
162    pub agent: CompactString,
163    /// Origin of this session.
164    pub created_by: CompactString,
165    /// Number of messages in history.
166    pub message_count: usize,
167    /// Seconds since session was created.
168    pub alive_secs: u64,
169}
170
171/// Summary of a task in the task registry.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct TaskInfo {
174    /// Task identifier.
175    pub id: u64,
176    /// Parent task ID (if sub-task).
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub parent_id: Option<u64>,
179    /// Agent assigned to this task.
180    pub agent: CompactString,
181    /// Current status.
182    pub status: String,
183    /// Task description / message.
184    pub description: String,
185    /// Result content (if finished).
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub result: Option<String>,
188    /// Error message (if failed).
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub error: Option<String>,
191    /// Origin of this task.
192    pub created_by: CompactString,
193    /// Cumulative prompt tokens.
194    pub prompt_tokens: u64,
195    /// Cumulative completion tokens.
196    pub completion_tokens: u64,
197    /// Seconds since task was created.
198    pub alive_secs: u64,
199    /// Pending inbox question (if blocked).
200    #[serde(default, skip_serializing_if = "Option::is_none")]
201    pub blocked_on: Option<String>,
202}
203
204/// Task lifecycle events emitted by the subscription stream.
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub enum TaskEvent {
207    /// A new task was created.
208    Created {
209        /// Full task snapshot at creation time.
210        task: TaskInfo,
211    },
212    /// Task status changed (non-terminal).
213    StatusChanged {
214        /// Task identifier.
215        task_id: u64,
216        /// New status.
217        status: String,
218        /// Pending inbox question (if blocked).
219        #[serde(default, skip_serializing_if = "Option::is_none")]
220        blocked_on: Option<String>,
221    },
222    /// Task reached a terminal state (finished or failed).
223    Completed {
224        /// Task identifier.
225        task_id: u64,
226        /// Terminal status ("finished" or "failed").
227        status: String,
228        /// Result content (if finished).
229        #[serde(default, skip_serializing_if = "Option::is_none")]
230        result: Option<String>,
231        /// Error message (if failed).
232        #[serde(default, skip_serializing_if = "Option::is_none")]
233        error: Option<String>,
234    },
235}
236
237/// Summary of a memory entity.
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct EntityInfo {
240    /// Entity type (e.g. "person", "fact").
241    pub entity_type: CompactString,
242    /// Human-readable key.
243    pub key: CompactString,
244    /// Entity value/content.
245    pub value: String,
246    /// Unix timestamp of creation.
247    pub created_at: u64,
248}
249
250/// Summary of a memory relation.
251#[derive(Debug, Clone, Serialize, Deserialize)]
252pub struct RelationInfo {
253    /// Source entity ID (`{type}:{key}`).
254    pub source_id: CompactString,
255    /// Relation type.
256    pub relation: CompactString,
257    /// Target entity ID (`{type}:{key}`).
258    pub target_id: CompactString,
259    /// Unix timestamp of creation.
260    pub created_at: u64,
261}
262
263/// Summary of a memory journal entry.
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct JournalInfo {
266    /// Compaction summary text.
267    pub summary: String,
268    /// Agent that produced this journal.
269    pub agent: CompactString,
270    /// Unix timestamp of creation.
271    pub created_at: u64,
272}
273
274/// Result of a memory graph query.
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(rename_all = "snake_case")]
277pub enum MemoryResult {
278    /// Entity list.
279    Entities(Vec<EntityInfo>),
280    /// Relation list.
281    Relations(Vec<RelationInfo>),
282    /// Journal list.
283    Journals(Vec<JournalInfo>),
284}
285
286/// Messages sent by the gateway to the client.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(tag = "type", rename_all = "snake_case")]
289pub enum ServerMessage {
290    /// Complete response from an agent.
291    Response(SendResponse),
292    /// A streamed response event.
293    Stream(StreamEvent),
294    /// A download lifecycle event.
295    Download(DownloadEvent),
296    /// Error response.
297    Error {
298        /// Error code.
299        code: u16,
300        /// Error message.
301        message: String,
302    },
303    /// Pong response to client ping.
304    Pong,
305    /// Active session list.
306    Sessions(Vec<SessionInfo>),
307    /// Download registry list.
308    Downloads(Vec<DownloadInfo>),
309    /// Task registry list.
310    Tasks(Vec<TaskInfo>),
311    /// A task lifecycle event (subscription stream).
312    Task(TaskEvent),
313    /// Evaluation result — whether the agent should respond (DD#39).
314    Evaluation {
315        /// Whether the agent decided to respond.
316        respond: bool,
317    },
318    /// Full daemon config as JSON.
319    Config {
320        /// JSON-serialized `DaemonConfig`.
321        config: String,
322    },
323    /// Memory graph query result.
324    Memory(MemoryResult),
325}
326
327impl From<SendResponse> for ServerMessage {
328    fn from(r: SendResponse) -> Self {
329        Self::Response(r)
330    }
331}
332
333impl From<StreamEvent> for ServerMessage {
334    fn from(e: StreamEvent) -> Self {
335        Self::Stream(e)
336    }
337}
338
339impl From<DownloadEvent> for ServerMessage {
340    fn from(e: DownloadEvent) -> Self {
341        Self::Download(e)
342    }
343}
344
345impl From<TaskEvent> for ServerMessage {
346    fn from(e: TaskEvent) -> Self {
347        Self::Task(e)
348    }
349}
350
351fn error_or_unexpected(msg: ServerMessage) -> anyhow::Error {
352    match msg {
353        ServerMessage::Error { code, message } => {
354            anyhow::anyhow!("server error ({code}): {message}")
355        }
356        other => anyhow::anyhow!("unexpected response: {other:?}"),
357    }
358}
359
360impl TryFrom<ServerMessage> for SendResponse {
361    type Error = anyhow::Error;
362    fn try_from(msg: ServerMessage) -> anyhow::Result<Self> {
363        match msg {
364            ServerMessage::Response(r) => Ok(r),
365            other => Err(error_or_unexpected(other)),
366        }
367    }
368}
369
370impl TryFrom<ServerMessage> for StreamEvent {
371    type Error = anyhow::Error;
372    fn try_from(msg: ServerMessage) -> anyhow::Result<Self> {
373        match msg {
374            ServerMessage::Stream(e) => Ok(e),
375            other => Err(error_or_unexpected(other)),
376        }
377    }
378}
379
380impl TryFrom<ServerMessage> for DownloadEvent {
381    type Error = anyhow::Error;
382    fn try_from(msg: ServerMessage) -> anyhow::Result<Self> {
383        match msg {
384            ServerMessage::Download(e) => Ok(e),
385            other => Err(error_or_unexpected(other)),
386        }
387    }
388}
389
390impl TryFrom<ServerMessage> for TaskEvent {
391    type Error = anyhow::Error;
392    fn try_from(msg: ServerMessage) -> anyhow::Result<Self> {
393        match msg {
394            ServerMessage::Task(e) => Ok(e),
395            other => Err(error_or_unexpected(other)),
396        }
397    }
398}
399
400impl TryFrom<ServerMessage> for MemoryResult {
401    type Error = anyhow::Error;
402    fn try_from(msg: ServerMessage) -> anyhow::Result<Self> {
403        match msg {
404            ServerMessage::Memory(r) => Ok(r),
405            other => Err(error_or_unexpected(other)),
406        }
407    }
408}