Skip to main content

systemprompt_models/execution/context/
mod.rs

1//! Request context for execution tracking.
2
3mod call_source;
4mod context_error;
5mod context_types;
6mod propagation;
7
8pub use call_source::CallSource;
9pub use context_error::{ContextExtractionError, ContextIdSource, TASK_BASED_CONTEXT_MARKER};
10pub use context_types::{
11    AuthContext, ExecutionContext, ExecutionSettings, RequestMetadata, UserInteractionMode,
12};
13
14use crate::ai::ToolModelConfig;
15use crate::auth::{AuthenticatedUser, RateLimitTier, UserType};
16use serde::{Deserialize, Serialize};
17use std::time::{Duration, Instant};
18use systemprompt_identifiers::{
19    AgentName, AiToolCallId, ClientId, ContextId, JwtToken, McpExecutionId, SessionId, TaskId,
20    TraceId, UserId,
21};
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct RequestContext {
25    pub auth: AuthContext,
26    pub request: RequestMetadata,
27    pub execution: ExecutionContext,
28    pub settings: ExecutionSettings,
29
30    #[serde(skip)]
31    pub user: Option<AuthenticatedUser>,
32
33    #[serde(skip, default = "Instant::now")]
34    pub start_time: Instant,
35}
36
37impl RequestContext {
38    /// Creates a new `RequestContext` - the ONLY way to construct a context.
39    ///
40    /// This is the single constructor for `RequestContext`. All contexts must
41    /// be created through this method, ensuring consistent initialization.
42    ///
43    /// # Required Fields
44    /// - `session_id`: Identifies the user session
45    /// - `trace_id`: For distributed tracing
46    /// - `context_id`: Conversation/execution context (empty string for
47    ///   user-level contexts)
48    /// - `agent_name`: The agent handling this request (use
49    ///   `AgentName::system()` for system operations)
50    ///
51    /// # Optional Fields
52    /// Use builder methods to set optional fields:
53    /// - `.with_user_id()` - Set the authenticated user
54    /// - `.with_auth_token()` - Set the JWT token
55    /// - `.with_user_type()` - Set user type (Admin, Standard, Anon)
56    /// - `.with_task_id()` - Set task ID for AI operations
57    /// - `.with_client_id()` - Set client ID
58    /// - `.with_call_source()` - Set call source (Agentic, Direct, Ephemeral)
59    ///
60    /// # Example
61    /// ```
62    /// # use systemprompt_models::execution::context::RequestContext;
63    /// # use systemprompt_identifiers::{SessionId, TraceId, ContextId, AgentName, UserId};
64    /// # use systemprompt_models::auth::UserType;
65    /// let ctx = RequestContext::new(
66    ///     SessionId::new("sess_123".to_string()),
67    ///     TraceId::new("trace_456".to_string()),
68    ///     ContextId::new("ctx_789".to_string()),
69    ///     AgentName::new("my-agent".to_string()),
70    /// )
71    /// .with_user_id(UserId::new("user_123".to_string()))
72    /// .with_auth_token("jwt_token_here")
73    /// .with_user_type(UserType::User);
74    /// ```
75    pub fn new(
76        session_id: SessionId,
77        trace_id: TraceId,
78        context_id: ContextId,
79        agent_name: AgentName,
80    ) -> Self {
81        Self {
82            auth: AuthContext {
83                auth_token: JwtToken::new(""),
84                user_id: UserId::anonymous(),
85                user_type: UserType::Anon,
86            },
87            request: RequestMetadata {
88                session_id,
89                timestamp: Instant::now(),
90                client_id: None,
91                is_tracked: true,
92                fingerprint_hash: None,
93            },
94            execution: ExecutionContext {
95                trace_id,
96                context_id,
97                task_id: None,
98                ai_tool_call_id: None,
99                mcp_execution_id: None,
100                call_source: None,
101                agent_name,
102                tool_model_config: None,
103            },
104            settings: ExecutionSettings::default(),
105            user: None,
106            start_time: Instant::now(),
107        }
108    }
109
110    pub fn with_user(mut self, user: AuthenticatedUser) -> Self {
111        self.auth.user_id = UserId::new(user.id.to_string());
112        self.user = Some(user);
113        self
114    }
115
116    pub fn with_user_id(mut self, user_id: UserId) -> Self {
117        self.auth.user_id = user_id;
118        self
119    }
120
121    pub fn with_agent_name(mut self, agent_name: AgentName) -> Self {
122        self.execution.agent_name = agent_name;
123        self
124    }
125
126    pub fn with_context_id(mut self, context_id: ContextId) -> Self {
127        self.execution.context_id = context_id;
128        self
129    }
130
131    pub fn with_task_id(mut self, task_id: TaskId) -> Self {
132        self.execution.task_id = Some(task_id);
133        self
134    }
135
136    pub fn with_task(mut self, task_id: TaskId, call_source: CallSource) -> Self {
137        self.execution.task_id = Some(task_id);
138        self.execution.call_source = Some(call_source);
139        self
140    }
141
142    pub fn with_ai_tool_call_id(mut self, ai_tool_call_id: AiToolCallId) -> Self {
143        self.execution.ai_tool_call_id = Some(ai_tool_call_id);
144        self
145    }
146
147    pub fn with_mcp_execution_id(mut self, mcp_execution_id: McpExecutionId) -> Self {
148        self.execution.mcp_execution_id = Some(mcp_execution_id);
149        self
150    }
151
152    pub fn with_client_id(mut self, client_id: ClientId) -> Self {
153        self.request.client_id = Some(client_id);
154        self
155    }
156
157    pub const fn with_user_type(mut self, user_type: UserType) -> Self {
158        self.auth.user_type = user_type;
159        self
160    }
161
162    pub fn with_auth_token(mut self, token: impl Into<String>) -> Self {
163        self.auth.auth_token = JwtToken::new(token.into());
164        self
165    }
166
167    pub const fn with_call_source(mut self, call_source: CallSource) -> Self {
168        self.execution.call_source = Some(call_source);
169        self
170    }
171
172    pub const fn with_budget(mut self, cents: i32) -> Self {
173        self.settings.max_budget_cents = Some(cents);
174        self
175    }
176
177    pub const fn with_interaction_mode(mut self, mode: UserInteractionMode) -> Self {
178        self.settings.user_interaction_mode = Some(mode);
179        self
180    }
181
182    pub const fn with_tracked(mut self, is_tracked: bool) -> Self {
183        self.request.is_tracked = is_tracked;
184        self
185    }
186
187    pub fn with_fingerprint_hash(mut self, hash: impl Into<String>) -> Self {
188        self.request.fingerprint_hash = Some(hash.into());
189        self
190    }
191
192    pub fn fingerprint_hash(&self) -> Option<&str> {
193        self.request.fingerprint_hash.as_deref()
194    }
195
196    pub fn with_tool_model_config(mut self, config: ToolModelConfig) -> Self {
197        self.execution.tool_model_config = Some(config);
198        self
199    }
200
201    pub const fn tool_model_config(&self) -> Option<&ToolModelConfig> {
202        self.execution.tool_model_config.as_ref()
203    }
204
205    pub const fn session_id(&self) -> &SessionId {
206        &self.request.session_id
207    }
208
209    pub const fn user_id(&self) -> &UserId {
210        &self.auth.user_id
211    }
212
213    pub const fn trace_id(&self) -> &TraceId {
214        &self.execution.trace_id
215    }
216
217    pub const fn context_id(&self) -> &ContextId {
218        &self.execution.context_id
219    }
220
221    pub const fn agent_name(&self) -> &AgentName {
222        &self.execution.agent_name
223    }
224
225    pub const fn auth_token(&self) -> &JwtToken {
226        &self.auth.auth_token
227    }
228
229    pub const fn user_type(&self) -> UserType {
230        self.auth.user_type
231    }
232
233    pub const fn rate_limit_tier(&self) -> RateLimitTier {
234        self.auth.user_type.rate_tier()
235    }
236
237    pub const fn task_id(&self) -> Option<&TaskId> {
238        self.execution.task_id.as_ref()
239    }
240
241    pub const fn client_id(&self) -> Option<&ClientId> {
242        self.request.client_id.as_ref()
243    }
244
245    pub const fn ai_tool_call_id(&self) -> Option<&AiToolCallId> {
246        self.execution.ai_tool_call_id.as_ref()
247    }
248
249    pub const fn mcp_execution_id(&self) -> Option<&McpExecutionId> {
250        self.execution.mcp_execution_id.as_ref()
251    }
252
253    pub const fn call_source(&self) -> Option<CallSource> {
254        self.execution.call_source
255    }
256
257    pub const fn is_authenticated(&self) -> bool {
258        self.user.is_some()
259    }
260
261    pub fn is_system(&self) -> bool {
262        self.auth.user_id.is_system() && self.execution.context_id.is_system()
263    }
264
265    pub fn elapsed(&self) -> Duration {
266        self.start_time.elapsed()
267    }
268
269    pub fn validate_task_execution(&self) -> Result<(), String> {
270        if self.execution.task_id.is_none() {
271            return Err("Missing task_id for task execution".to_string());
272        }
273        if self.execution.context_id.as_str().is_empty() {
274            return Err("Missing context_id for task execution".to_string());
275        }
276        Ok(())
277    }
278
279    pub fn validate_authenticated(&self) -> Result<(), String> {
280        if self.auth.auth_token.as_str().is_empty() {
281            return Err("Missing authentication token".to_string());
282        }
283        if self.auth.user_id.is_anonymous() {
284            return Err("User is not authenticated".to_string());
285        }
286        Ok(())
287    }
288}