1use std::collections::BTreeMap;
17
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use time::format_description::well_known::Rfc3339;
21use time::OffsetDateTime;
22
23pub type EventId = u64;
26
27#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
32#[serde(tag = "kind", rename_all = "snake_case")]
33pub enum SessionEventKind {
34 Message,
35 ToolCall,
36 ToolResult,
37 Plan,
38 Compaction,
39 SystemReminder,
40 Hypothesis,
41 Receipt,
42 Reminder,
43 PermissionDecision,
44 Custom {
45 #[serde(rename = "type")]
46 custom_type: String,
47 },
48}
49
50impl SessionEventKind {
51 pub fn discriminator(&self) -> &str {
53 match self {
54 Self::Message => "message",
55 Self::ToolCall => "tool_call",
56 Self::ToolResult => "tool_result",
57 Self::Plan => "plan",
58 Self::Compaction => "compaction",
59 Self::SystemReminder => "system_reminder",
60 Self::Hypothesis => "hypothesis",
61 Self::Receipt => "receipt",
62 Self::Reminder => "reminder",
63 Self::PermissionDecision => "permission_decision",
64 Self::Custom { custom_type } => custom_type.as_str(),
65 }
66 }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
71pub struct AppendEvent {
72 pub kind: SessionEventKind,
73 #[serde(default)]
76 pub payload: Value,
77 #[serde(default)]
80 pub parent_event_id: Option<EventId>,
81 #[serde(default)]
83 pub actor: Option<String>,
84 #[serde(default)]
86 pub tags: Vec<String>,
87 #[serde(default)]
89 pub headers: BTreeMap<String, String>,
90}
91
92impl AppendEvent {
93 pub fn new(kind: SessionEventKind, payload: Value) -> Self {
94 Self {
95 kind,
96 payload,
97 parent_event_id: None,
98 actor: None,
99 tags: Vec::new(),
100 headers: BTreeMap::new(),
101 }
102 }
103
104 pub fn with_actor(mut self, actor: impl Into<String>) -> Self {
105 self.actor = Some(actor.into());
106 self
107 }
108
109 pub fn with_parent(mut self, parent_event_id: EventId) -> Self {
110 self.parent_event_id = Some(parent_event_id);
111 self
112 }
113
114 pub fn with_tags<I, S>(mut self, tags: I) -> Self
115 where
116 I: IntoIterator<Item = S>,
117 S: Into<String>,
118 {
119 self.tags = tags.into_iter().map(Into::into).collect();
120 self
121 }
122}
123
124#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
127pub struct StoredEvent {
128 pub event_id: EventId,
129 pub session_id: String,
130 pub tenant_id: Option<String>,
131 pub parent_event_id: Option<EventId>,
132 pub actor: Option<String>,
133 pub kind: SessionEventKind,
134 pub payload: Value,
135 pub tags: Vec<String>,
136 pub headers: BTreeMap<String, String>,
137 pub ts_ms: i64,
138 pub ts: String,
139 pub record_hash: String,
143 pub prev_hash: Option<String>,
146 pub signed_by: Option<EventSignature>,
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
153pub struct EventSignature {
154 pub algorithm: String,
156 pub key_id: String,
158 pub signature: String,
160}
161
162pub fn canonical_event_bytes(event: &StoredEvent) -> Vec<u8> {
165 let canonical = serde_json::json!({
166 "schema": "harn.session.event.v1",
167 "session_id": event.session_id,
168 "event_id": event.event_id,
169 "tenant_id": event.tenant_id,
170 "parent_event_id": event.parent_event_id,
171 "actor": event.actor,
172 "kind": event.kind.discriminator(),
173 "payload": event.payload,
174 "tags": event.tags,
175 "headers": event.headers,
176 "ts_ms": event.ts_ms,
177 "prev_hash": event.prev_hash,
178 });
179 canonical_json_bytes(&canonical)
180}
181
182pub fn canonical_json_bytes(value: &Value) -> Vec<u8> {
184 let mut buf = Vec::new();
185 write_canonical(&mut buf, value);
186 buf
187}
188
189fn write_canonical(buf: &mut Vec<u8>, value: &Value) {
190 match value {
191 Value::Null => buf.extend_from_slice(b"null"),
192 Value::Bool(b) => buf.extend_from_slice(if *b { b"true" } else { b"false" }),
193 Value::Number(n) => buf.extend_from_slice(n.to_string().as_bytes()),
194 Value::String(s) => {
195 let encoded = serde_json::to_string(s).expect("string round-trip");
196 buf.extend_from_slice(encoded.as_bytes());
197 }
198 Value::Array(items) => {
199 buf.push(b'[');
200 for (i, item) in items.iter().enumerate() {
201 if i > 0 {
202 buf.push(b',');
203 }
204 write_canonical(buf, item);
205 }
206 buf.push(b']');
207 }
208 Value::Object(map) => {
209 let mut keys: Vec<&String> = map.keys().collect();
210 keys.sort();
211 buf.push(b'{');
212 for (i, key) in keys.iter().enumerate() {
213 if i > 0 {
214 buf.push(b',');
215 }
216 let encoded_key = serde_json::to_string(key).expect("key round-trip");
217 buf.extend_from_slice(encoded_key.as_bytes());
218 buf.push(b':');
219 write_canonical(buf, &map[*key]);
220 }
221 buf.push(b'}');
222 }
223 }
224}
225
226pub(crate) fn now_ms_and_rfc3339() -> (i64, String) {
227 let now = OffsetDateTime::now_utc();
228 let ms = (now.unix_timestamp_nanos() / 1_000_000) as i64;
229 let text = now.format(&Rfc3339).unwrap_or_else(|_| now.to_string());
230 (ms, text)
231}
232
233pub(crate) fn ms_to_rfc3339(ms: i64) -> String {
237 let nanos = (ms as i128).saturating_mul(1_000_000);
238 OffsetDateTime::from_unix_timestamp_nanos(nanos)
239 .ok()
240 .and_then(|dt| dt.format(&Rfc3339).ok())
241 .unwrap_or_default()
242}