Skip to main content

tea_context/
provider.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use tea_protocol::{ProfileId, ProtocolMetadata, RunId, SessionId};
5use tea_tools::ToolSpec;
6
7use crate::{ContextError, ContextProviderId, PromptModule};
8
9/// Maximum active tools visible to a context snapshot.
10pub const MAX_CONTEXT_TOOLS: usize = 256;
11
12/// Immutable inputs visible to context providers for one turn snapshot.
13#[derive(Debug, Clone, PartialEq)]
14pub struct ContextRequest {
15    profile_id: ProfileId,
16    session_id: SessionId,
17    run_id: Option<RunId>,
18    active_tools: Vec<ToolSpec>,
19    metadata: ProtocolMetadata,
20}
21
22impl ContextRequest {
23    /// Creates a bounded request and canonicalizes active tool order.
24    ///
25    /// # Errors
26    ///
27    /// Returns an error for too many tools or duplicate active tool names.
28    pub fn new(
29        profile_id: ProfileId,
30        session_id: SessionId,
31        run_id: Option<RunId>,
32        mut active_tools: Vec<ToolSpec>,
33        metadata: ProtocolMetadata,
34    ) -> Result<Self, ContextError> {
35        if active_tools.len() > MAX_CONTEXT_TOOLS {
36            return Err(ContextError::new(
37                crate::ContextErrorCode::BoundsExceeded,
38                "context request contains too many active tools",
39            ));
40        }
41        active_tools.sort_by(|left, right| left.name().cmp(right.name()));
42        if active_tools
43            .windows(2)
44            .any(|tools| tools[0].name() == tools[1].name())
45        {
46            return Err(ContextError::new(
47                crate::ContextErrorCode::DuplicateIdentity,
48                "context request contains duplicate active tool names",
49            ));
50        }
51        Ok(Self {
52            profile_id,
53            session_id,
54            run_id,
55            active_tools,
56            metadata,
57        })
58    }
59
60    /// Returns active profile.
61    #[must_use]
62    pub const fn profile_id(&self) -> &ProfileId {
63        &self.profile_id
64    }
65    /// Returns active session.
66    #[must_use]
67    pub const fn session_id(&self) -> SessionId {
68        self.session_id
69    }
70    /// Returns active run when present.
71    #[must_use]
72    pub const fn run_id(&self) -> Option<RunId> {
73        self.run_id
74    }
75    /// Returns canonical active tools.
76    #[must_use]
77    pub fn active_tools(&self) -> &[ToolSpec] {
78        &self.active_tools
79    }
80    /// Returns bounded request metadata.
81    #[must_use]
82    pub const fn metadata(&self) -> &ProtocolMetadata {
83        &self.metadata
84    }
85}
86
87/// Runtime-neutral boxed context-provider future.
88pub type ContextProviderFuture<'a> =
89    Pin<Box<dyn Future<Output = Result<Vec<PromptModule>, ContextError>> + Send + 'a>>;
90
91/// Object-safe context source evaluated before prompt compilation.
92pub trait ContextProvider: std::fmt::Debug + Send + Sync {
93    /// Returns stable provider identity.
94    fn id(&self) -> &ContextProviderId;
95    /// Produces bounded modules from one immutable request.
96    fn provide(&self, request: ContextRequest) -> ContextProviderFuture<'_>;
97}
98
99/// Deterministic provider returning an immutable module snapshot.
100#[derive(Debug, Clone)]
101pub struct StaticContextProvider {
102    id: ContextProviderId,
103    modules: Vec<PromptModule>,
104}
105
106impl StaticContextProvider {
107    /// Creates one static provider.
108    #[must_use]
109    pub const fn new(id: ContextProviderId, modules: Vec<PromptModule>) -> Self {
110        Self { id, modules }
111    }
112}
113
114impl ContextProvider for StaticContextProvider {
115    fn id(&self) -> &ContextProviderId {
116        &self.id
117    }
118    fn provide(&self, _request: ContextRequest) -> ContextProviderFuture<'_> {
119        let modules = self.modules.clone();
120        Box::pin(async move { Ok(modules) })
121    }
122}