Skip to main content

tea_context/providers/
session.rs

1use std::str::FromStr;
2
3use tea_protocol::RecordId;
4
5use crate::{
6    BudgetBehavior, CacheScope, ConflictClaim, ConflictKey, ConflictMode, ContextError,
7    ContextErrorCode, ContextProvider, ContextProviderFuture, ContextProviderId, ContextRequest,
8    PromptAuthority, PromptModule, PromptModuleId, PromptPriority, PromptProvenance, PromptSegment,
9    PromptSegmentId, TrustLevel,
10};
11
12/// Explicit optional session-summary insertion point.
13#[derive(Debug, Clone)]
14pub struct SessionSummaryProvider {
15    id: ContextProviderId,
16    summary: Option<(String, RecordId, TrustLevel)>,
17}
18
19impl SessionSummaryProvider {
20    /// Creates an empty optional summary provider.
21    ///
22    /// # Errors
23    ///
24    /// Returns an error only when a static built-in identity is invalid.
25    pub fn empty() -> Result<Self, ContextError> {
26        Ok(Self {
27            id: ContextProviderId::from_str("builtin.session_summary").map_err(value_error)?,
28            summary: None,
29        })
30    }
31
32    /// Creates a provider from a caller-supplied durable summary snapshot.
33    ///
34    /// # Errors
35    ///
36    /// Returns an error when summary content violates prompt bounds.
37    pub fn new(
38        summary: impl Into<String>,
39        record_id: RecordId,
40        trust: TrustLevel,
41    ) -> Result<Self, ContextError> {
42        let summary = summary.into();
43        let id = ContextProviderId::from_str("builtin.session_summary").map_err(value_error)?;
44        validate(&id, &summary, record_id, trust)?;
45        Ok(Self {
46            id,
47            summary: Some((summary, record_id, trust)),
48        })
49    }
50}
51
52impl ContextProvider for SessionSummaryProvider {
53    fn id(&self) -> &ContextProviderId {
54        &self.id
55    }
56
57    fn provide(&self, _request: ContextRequest) -> ContextProviderFuture<'_> {
58        let id = self.id.clone();
59        let summary = self.summary.clone();
60        Box::pin(async move {
61            let Some((summary, record_id, trust)) = summary else {
62                return Ok(Vec::new());
63            };
64            let segment = PromptSegment::new(
65                PromptSegmentId::from_str("session.summary").map_err(value_error)?,
66                summary,
67                PromptProvenance::new(id, "session_record", Some(record_id.to_string()))
68                    .map_err(value_error)?,
69                trust,
70                CacheScope::Session,
71                BudgetBehavior::Omit,
72            )
73            .map_err(value_error)?
74            .with_conflict(ConflictClaim::new(
75                ConflictKey::from_str("session.summary").map_err(value_error)?,
76                ConflictMode::Replaceable,
77            ));
78            Ok(vec![
79                PromptModule::new(
80                    PromptModuleId::from_str("session.summary").map_err(value_error)?,
81                    PromptAuthority::Session,
82                    PromptPriority::new(0),
83                    vec![segment],
84                )
85                .map_err(value_error)?,
86            ])
87        })
88    }
89}
90
91fn validate(
92    id: &ContextProviderId,
93    summary: &str,
94    record_id: RecordId,
95    trust: TrustLevel,
96) -> Result<(), ContextError> {
97    PromptSegment::new(
98        PromptSegmentId::from_str("session.summary").map_err(value_error)?,
99        summary,
100        PromptProvenance::new(id.clone(), "session_record", Some(record_id.to_string()))
101            .map_err(value_error)?,
102        trust,
103        CacheScope::Session,
104        BudgetBehavior::Omit,
105    )
106    .map(|_| ())
107    .map_err(value_error)
108}
109
110fn value_error(error: impl std::fmt::Display) -> ContextError {
111    ContextError::new(ContextErrorCode::InvalidValue, error.to_string())
112}