1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use thiserror::Error;
6use uuid::Uuid;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum EventType {
11 Message,
12 Memory,
13 Decision,
14 Task,
15 Handoff,
16 Observation,
17 Pattern,
18 Fact,
19 Bug,
20 Artifact,
21}
22
23impl EventType {
24 pub fn as_str(self) -> &'static str {
25 match self {
26 Self::Message => "message",
27 Self::Memory => "memory",
28 Self::Decision => "decision",
29 Self::Task => "task",
30 Self::Handoff => "handoff",
31 Self::Observation => "observation",
32 Self::Pattern => "pattern",
33 Self::Fact => "fact",
34 Self::Bug => "bug",
35 Self::Artifact => "artifact",
36 }
37 }
38}
39
40impl TryFrom<&str> for EventType {
41 type Error = ShuttleError;
42
43 fn try_from(value: &str) -> Result<Self> {
44 match value {
45 "message" => Ok(Self::Message),
46 "memory" => Ok(Self::Memory),
47 "decision" => Ok(Self::Decision),
48 "task" => Ok(Self::Task),
49 "handoff" => Ok(Self::Handoff),
50 "observation" => Ok(Self::Observation),
51 "pattern" => Ok(Self::Pattern),
52 "fact" => Ok(Self::Fact),
53 "bug" => Ok(Self::Bug),
54 "artifact" => Ok(Self::Artifact),
55 other => Err(ShuttleError::InvalidEventType(other.to_owned())),
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct Event {
62 pub id: Uuid,
63 pub event_type: EventType,
64 pub workspace_id: String,
65 pub repo_id: Option<String>,
66 pub repo_path: Option<String>,
67 pub git_remote: Option<String>,
68 pub bit_repo_id: Option<String>,
69 pub branch: Option<String>,
70 pub commit: Option<String>,
71 pub repo_dirty: Option<bool>,
72 pub agent: String,
73 pub session_id: String,
74 pub title: Option<String>,
75 pub content: String,
76 pub tags: Vec<String>,
77 pub metadata_json: Value,
78 pub created_at: DateTime<Utc>,
79}
80
81impl Event {
82 pub fn new(input: NewEvent) -> Self {
83 Self {
84 id: Uuid::new_v4(),
85 event_type: input.event_type,
86 workspace_id: input.workspace_id,
87 repo_id: input.repo_id,
88 repo_path: input.repo_path,
89 git_remote: input.git_remote,
90 bit_repo_id: input.bit_repo_id,
91 branch: input.branch,
92 commit: input.commit,
93 repo_dirty: input.repo_dirty,
94 agent: input.agent,
95 session_id: input.session_id,
96 title: input.title,
97 content: input.content,
98 tags: input.tags,
99 metadata_json: input.metadata_json,
100 created_at: Utc::now(),
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct NewEvent {
107 pub event_type: EventType,
108 pub workspace_id: String,
109 pub repo_id: Option<String>,
110 pub repo_path: Option<String>,
111 pub git_remote: Option<String>,
112 pub bit_repo_id: Option<String>,
113 pub branch: Option<String>,
114 pub commit: Option<String>,
115 pub repo_dirty: Option<bool>,
116 pub agent: String,
117 pub session_id: String,
118 pub title: Option<String>,
119 pub content: String,
120 pub tags: Vec<String>,
121 pub metadata_json: Value,
122}
123
124#[derive(Debug, Clone, Default, PartialEq)]
125pub struct EventFilter {
126 pub id: Option<Uuid>,
127 pub event_type: Option<EventType>,
128 pub workspace_id: Option<String>,
129 pub agent: Option<String>,
130 pub recipient: Option<String>,
131 pub tag: Option<String>,
132 pub tags: Vec<String>,
133 pub query: Option<String>,
134 pub limit: Option<u32>,
135}
136
137#[derive(Debug, Error)]
138pub enum ShuttleError {
139 #[error("invalid event type: {0}")]
140 InvalidEventType(String),
141 #[error("store error: {0}")]
142 Store(String),
143 #[error("serialization error: {0}")]
144 Serialization(String),
145}
146
147pub type Result<T> = std::result::Result<T, ShuttleError>;
148
149#[async_trait]
150pub trait EventStore: Send + Sync {
151 async fn append(&self, event: Event) -> Result<Event>;
152 async fn list(&self, filter: EventFilter) -> Result<Vec<Event>>;
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn event_type_round_trips_through_string_values() {
161 assert_eq!(EventType::try_from("memory").unwrap(), EventType::Memory);
162 assert_eq!(EventType::try_from("pattern").unwrap(), EventType::Pattern);
163 assert_eq!(EventType::Fact.as_str(), "fact");
164 assert_eq!(EventType::Bug.as_str(), "bug");
165 assert_eq!(EventType::Message.as_str(), "message");
166 assert!(EventType::try_from("unknown").is_err());
167 }
168}