systemprompt_models/brain/
source.rs1use chrono::{DateTime, Utc};
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use systemprompt_identifiers::{
10 AgentRunId, DocumentCommentId, DocumentId, DocumentNodeId, DocumentRevisionId, SessionId,
11 UserId,
12};
13
14use super::envelope::Pathway;
15use crate::notifications::SubjectRef;
16
17pub const DOCUMENT_SUBJECT_KIND: &str = "document";
18pub const EXTERNAL_SUBJECT_PREFIX: &str = "external:";
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
24#[serde(deny_unknown_fields)]
25pub struct SourceSpan {
26 pub node_id: DocumentNodeId,
27 pub hash8: String,
28 pub find: String,
29 #[serde(default)]
30 pub nth: u32,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
35#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
36pub enum FactSource {
37 Document {
38 document_id: DocumentId,
39 revision_id: DocumentRevisionId,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 span: Option<SourceSpan>,
42 },
43 Comment {
44 document_id: DocumentId,
45 comment_id: DocumentCommentId,
46 },
47 Session {
48 session_id: SessionId,
49 owner: UserId,
50 },
51 EnteredBy {
52 user_id: UserId,
53 },
54 External {
55 system: String,
56 reference: String,
57 },
58 Run {
59 run_id: AgentRunId,
60 },
61 Aggregate {
62 pathway: Pathway,
63 window_start: DateTime<Utc>,
64 window_end: DateTime<Utc>,
65 },
66}
67
68#[derive(
70 Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
71)]
72#[serde(rename_all = "snake_case")]
73pub enum SourceClass {
74 Document,
75 Comment,
76 Session,
77 EnteredBy,
78 External,
79 Run,
80 Aggregate,
81}
82
83impl SourceClass {
84 #[must_use]
85 pub const fn as_str(self) -> &'static str {
86 match self {
87 Self::Document => "document",
88 Self::Comment => "comment",
89 Self::Session => "session",
90 Self::EnteredBy => "entered_by",
91 Self::External => "external",
92 Self::Run => "run",
93 Self::Aggregate => "aggregate",
94 }
95 }
96}
97
98impl FactSource {
99 #[must_use]
100 pub const fn class(&self) -> SourceClass {
101 match self {
102 Self::Document { .. } => SourceClass::Document,
103 Self::Comment { .. } => SourceClass::Comment,
104 Self::Session { .. } => SourceClass::Session,
105 Self::EnteredBy { .. } => SourceClass::EnteredBy,
106 Self::External { .. } => SourceClass::External,
107 Self::Run { .. } => SourceClass::Run,
108 Self::Aggregate { .. } => SourceClass::Aggregate,
109 }
110 }
111
112 #[must_use]
113 pub const fn document_id(&self) -> Option<&DocumentId> {
114 match self {
115 Self::Document { document_id, .. } | Self::Comment { document_id, .. } => {
116 Some(document_id)
117 },
118 _ => None,
119 }
120 }
121
122 #[must_use]
123 pub fn visibility_subject(&self) -> Option<SubjectRef> {
124 match self {
125 Self::Document { document_id, .. } | Self::Comment { document_id, .. } => {
126 Some(SubjectRef::new(DOCUMENT_SUBJECT_KIND, document_id.as_str()))
127 },
128 Self::External { system, reference } => Some(SubjectRef::new(
129 format!("{EXTERNAL_SUBJECT_PREFIX}{system}"),
130 reference.clone(),
131 )),
132 Self::Session { .. }
133 | Self::EnteredBy { .. }
134 | Self::Run { .. }
135 | Self::Aggregate { .. } => None,
136 }
137 }
138
139 #[must_use]
140 pub fn label(&self) -> String {
141 match self {
142 Self::Document {
143 document_id,
144 revision_id,
145 ..
146 } => format!("doc:{document_id}@{revision_id}"),
147 Self::Comment { comment_id, .. } => format!("comment:{comment_id}"),
148 Self::Session { session_id, .. } => format!("session:{session_id}"),
149 Self::EnteredBy { user_id } => format!("entered:{user_id}"),
150 Self::External { system, reference } => format!("{system}:{reference}"),
151 Self::Run { run_id } => format!("run:{run_id}"),
152 Self::Aggregate { pathway, .. } => format!("aggregate:{}", pathway.as_str()),
153 }
154 }
155}