Skip to main content

systemprompt_models/brain/
source.rs

1//! Where a fact came from, and the resource whose readability gates it.
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::{
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/// The passage of a document revision a fact cites: the node, the first
21/// eight hex characters of its content hash, the quoted text and which
22/// occurrence of it within the node.
23#[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/// The typed origin of a fact.
34#[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/// The class of a [`FactSource`], as importance weights name it.
69#[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}