1use crate::{DisplayEntry, DisplayEntryUpdate};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(tag = "kind", rename_all = "snake_case")]
7pub enum ControlMessage {
8 Entry { v: u8, entry: DisplayEntry },
10 Update { v: u8, update: DisplayEntryUpdate },
12 AgentDestroyed { v: u8, agent_key: String },
15}
16
17impl ControlMessage {
18 pub fn entry(entry: DisplayEntry) -> Self {
19 Self::Entry { v: 1, entry }
20 }
21 pub fn update(update: DisplayEntryUpdate) -> Self {
22 Self::Update { v: 1, update }
23 }
24 pub fn agent_destroyed(agent_key: impl Into<String>) -> Self {
25 Self::AgentDestroyed {
26 v: 1,
27 agent_key: agent_key.into(),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct HeaderLine {
35 pub v: u8,
36 pub tool_use_id: String,
37 pub stream: String,
39 pub started_at: String,
41}
42
43impl HeaderLine {
44 pub fn new(tool_use_id: impl Into<String>, stream: impl Into<String>) -> Self {
45 use std::time::{SystemTime, UNIX_EPOCH};
46 let now = SystemTime::now()
47 .duration_since(UNIX_EPOCH)
48 .unwrap_or_default();
49 let started_at = format!("@{}", now.as_secs());
51 Self {
52 v: 1,
53 tool_use_id: tool_use_id.into(),
54 stream: stream.into(),
55 started_at,
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::{EntryBody, EntryStatus};
64 use std::time::SystemTime;
65
66 #[test]
67 fn control_entry_roundtrips() {
68 let msg = ControlMessage::entry(DisplayEntry {
69 agent_key: "main".into(),
70 tool_use_id: "t1".into(),
71 tool: "bash".into(),
72 timestamp: SystemTime::UNIX_EPOCH,
73 headline: "ls".into(),
74 body: EntryBody::None,
75 status: EntryStatus::Pending,
76 });
77 let s = serde_json::to_string(&msg).unwrap();
78 assert!(s.contains("\"kind\":\"entry\""));
79 let parsed: ControlMessage = serde_json::from_str(&s).unwrap();
80 match parsed {
81 ControlMessage::Entry { v, entry } => {
82 assert_eq!(v, 1);
83 assert_eq!(entry.tool_use_id, "t1");
84 }
85 _ => panic!("expected Entry"),
86 }
87 }
88
89 #[test]
90 fn header_line_roundtrips() {
91 let h = HeaderLine::new("t1", "stderr-discarded");
92 let s = serde_json::to_string(&h).unwrap();
93 let parsed: HeaderLine = serde_json::from_str(&s).unwrap();
94 assert_eq!(parsed.tool_use_id, "t1");
95 assert_eq!(parsed.stream, "stderr-discarded");
96 }
97}