Skip to main content

systemprompt_models/notifications/
event.rs

1//! The event a producer appends to the outbox for the notifications consumer.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use chrono::{DateTime, Utc};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use systemprompt_identifiers::{NotificationStreamId, UserId};
10
11use super::attention::Attention;
12use super::kind::NotificationKind;
13use super::subject::SubjectRef;
14
15pub const NOTIFICATIONS_CONSUMER: &str = "notifications";
16pub const NOTIFICATION_EVENT_VERSION: u32 = 1;
17
18/// A user the producer addresses directly, optionally at a chosen attention.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
20pub struct DirectRecipient {
21    pub user: UserId,
22    pub attention: Option<Attention>,
23}
24
25/// One thing that happened, before routing decides who hears about it.
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
27pub struct NotificationEvent {
28    pub kind: NotificationKind,
29    pub actor: UserId,
30    pub actor_is_agent: bool,
31    pub via_agent: Option<String>,
32    pub subjects: Vec<SubjectRef>,
33    pub stream: Option<NotificationStreamId>,
34    pub direct_recipients: Vec<DirectRecipient>,
35    pub sender_attention: Option<Attention>,
36    pub excerpt: Option<String>,
37    // JSON: the producer's payload is the event's free-form contract field.
38    pub payload: serde_json::Value,
39    pub occurred_at: DateTime<Utc>,
40}
41
42impl NotificationEvent {
43    #[must_use]
44    pub const fn new(kind: NotificationKind, actor: UserId, occurred_at: DateTime<Utc>) -> Self {
45        Self {
46            kind,
47            actor,
48            actor_is_agent: false,
49            via_agent: None,
50            subjects: Vec::new(),
51            stream: None,
52            direct_recipients: Vec::new(),
53            sender_attention: None,
54            excerpt: None,
55            payload: serde_json::Value::Null,
56            occurred_at,
57        }
58    }
59
60    #[must_use]
61    pub const fn with_actor_is_agent(mut self, actor_is_agent: bool) -> Self {
62        self.actor_is_agent = actor_is_agent;
63        self
64    }
65
66    #[must_use]
67    pub fn with_via_agent(mut self, via_agent: impl Into<String>) -> Self {
68        self.via_agent = Some(via_agent.into());
69        self
70    }
71
72    #[must_use]
73    pub fn with_subject(mut self, subject: SubjectRef) -> Self {
74        self.subjects.push(subject);
75        self
76    }
77
78    #[must_use]
79    pub fn with_subjects(mut self, subjects: impl IntoIterator<Item = SubjectRef>) -> Self {
80        self.subjects.extend(subjects);
81        self
82    }
83
84    #[must_use]
85    pub fn with_stream(mut self, stream: NotificationStreamId) -> Self {
86        self.stream = Some(stream);
87        self
88    }
89
90    #[must_use]
91    pub fn with_direct_recipient(mut self, user: UserId, attention: Option<Attention>) -> Self {
92        self.direct_recipients
93            .push(DirectRecipient { user, attention });
94        self
95    }
96
97    #[must_use]
98    pub fn with_direct_recipients(
99        mut self,
100        recipients: impl IntoIterator<Item = DirectRecipient>,
101    ) -> Self {
102        self.direct_recipients.extend(recipients);
103        self
104    }
105
106    #[must_use]
107    pub const fn with_sender_attention(mut self, attention: Attention) -> Self {
108        self.sender_attention = Some(attention);
109        self
110    }
111
112    #[must_use]
113    pub fn with_excerpt(mut self, excerpt: impl Into<String>) -> Self {
114        self.excerpt = Some(excerpt.into());
115        self
116    }
117
118    #[must_use]
119    // JSON: the producer's payload is the event's free-form contract field.
120    pub fn with_payload(mut self, payload: serde_json::Value) -> Self {
121        self.payload = payload;
122        self
123    }
124}