Skip to main content

systemprompt_models/execution/context/
mod.rs

1//! Request context for execution tracking.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6mod call_source;
7mod caller_purpose;
8mod context_error;
9mod context_types;
10mod credential;
11mod propagation;
12
13pub use call_source::CallSource;
14pub use context_error::{ContextExtractionError, ContextIdSource, RequestContextValidationError};
15pub use context_types::{
16    AuthContext, ExecutionContext, ExecutionSettings, RequestMetadata, UserInteractionMode,
17};
18
19use crate::ai::ToolModelConfig;
20use crate::auth::{AuthenticatedUser, RateLimitTier, UserType};
21use serde::{Deserialize, Serialize};
22use std::time::{Duration, Instant};
23use systemprompt_identifiers::{
24    AccessTokenId, Actor, AgentName, AiToolCallId, ClientId, ContextId, JwtToken, McpExecutionId,
25    SessionId, TaskId, TraceId, UserId,
26};
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct RequestContext {
30    pub auth: AuthContext,
31    pub request: RequestMetadata,
32    pub execution: ExecutionContext,
33    pub settings: ExecutionSettings,
34
35    #[serde(skip)]
36    pub user: Option<AuthenticatedUser>,
37
38    #[serde(skip, default = "Instant::now")]
39    pub start_time: Instant,
40}
41
42impl RequestContext {
43    pub fn new(
44        session_id: SessionId,
45        trace_id: TraceId,
46        context_id: ContextId,
47        agent_name: AgentName,
48        actor: Actor,
49    ) -> Self {
50        Self {
51            auth: AuthContext {
52                auth_token: None,
53                actor,
54                user_type: UserType::Anon,
55                act_chain: Vec::new(),
56                jti: None,
57                token_exp: None,
58                browser_session: false,
59                cookie_transport: false,
60            },
61            request: RequestMetadata {
62                session_id,
63                timestamp: Instant::now(),
64                client_id: None,
65                is_tracked: true,
66                fingerprint_hash: None,
67            },
68            execution: ExecutionContext {
69                trace_id,
70                context_id,
71                task_id: None,
72                ai_tool_call_id: None,
73                mcp_execution_id: None,
74                call_source: None,
75                agent_name,
76                tool_model_config: None,
77                caller_purpose: None,
78            },
79            settings: ExecutionSettings::default(),
80            user: None,
81            start_time: Instant::now(),
82        }
83    }
84
85    pub fn with_user(mut self, user: AuthenticatedUser) -> Self {
86        self.auth.actor = Actor::user(user.id.clone());
87        self.user = Some(user);
88        self
89    }
90
91    pub fn with_actor(mut self, actor: Actor) -> Self {
92        self.auth.actor = actor;
93        self
94    }
95
96    pub fn with_agent_name(mut self, agent_name: AgentName) -> Self {
97        self.execution.agent_name = agent_name;
98        self
99    }
100
101    pub fn with_context_id(mut self, context_id: ContextId) -> Self {
102        self.execution.context_id = context_id;
103        self
104    }
105
106    pub fn with_task_id(mut self, task_id: TaskId) -> Self {
107        self.execution.task_id = Some(task_id);
108        self
109    }
110
111    pub fn with_task(mut self, task_id: TaskId, call_source: CallSource) -> Self {
112        self.execution.task_id = Some(task_id);
113        self.execution.call_source = Some(call_source);
114        self
115    }
116
117    pub fn with_ai_tool_call_id(mut self, ai_tool_call_id: AiToolCallId) -> Self {
118        self.execution.ai_tool_call_id = Some(ai_tool_call_id);
119        self
120    }
121
122    pub fn with_mcp_execution_id(mut self, mcp_execution_id: McpExecutionId) -> Self {
123        self.execution.mcp_execution_id = Some(mcp_execution_id);
124        self
125    }
126
127    pub fn with_client_id(mut self, client_id: ClientId) -> Self {
128        self.request.client_id = Some(client_id);
129        self
130    }
131
132    pub const fn with_user_type(mut self, user_type: UserType) -> Self {
133        self.auth.user_type = user_type;
134        self
135    }
136
137    pub fn with_auth_token(mut self, token: JwtToken) -> Self {
138        self.auth.auth_token = Some(token);
139        self
140    }
141
142    #[must_use]
143    pub fn with_jti(mut self, jti: AccessTokenId) -> Self {
144        self.auth.jti = Some(jti);
145        self
146    }
147
148    #[must_use]
149    pub const fn jti(&self) -> Option<&AccessTokenId> {
150        self.auth.jti.as_ref()
151    }
152
153    #[must_use]
154    pub fn with_act_chain(mut self, act_chain: Vec<Actor>) -> Self {
155        self.auth.act_chain = act_chain;
156        self
157    }
158
159    #[must_use]
160    pub fn act_chain(&self) -> &[Actor] {
161        &self.auth.act_chain
162    }
163
164    pub const fn with_call_source(mut self, call_source: CallSource) -> Self {
165        self.execution.call_source = Some(call_source);
166        self
167    }
168
169    pub const fn with_budget(mut self, cents: i32) -> Self {
170        self.settings.max_budget_cents = Some(cents);
171        self
172    }
173
174    pub const fn with_interaction_mode(mut self, mode: UserInteractionMode) -> Self {
175        self.settings.user_interaction_mode = Some(mode);
176        self
177    }
178
179    pub const fn with_tracked(mut self, is_tracked: bool) -> Self {
180        self.request.is_tracked = is_tracked;
181        self
182    }
183
184    pub fn with_fingerprint_hash(mut self, hash: impl Into<String>) -> Self {
185        self.request.fingerprint_hash = Some(hash.into());
186        self
187    }
188
189    pub fn fingerprint_hash(&self) -> Option<&str> {
190        self.request.fingerprint_hash.as_deref()
191    }
192
193    pub fn with_tool_model_config(mut self, config: ToolModelConfig) -> Self {
194        self.execution.tool_model_config = Some(config);
195        self
196    }
197
198    pub const fn tool_model_config(&self) -> Option<&ToolModelConfig> {
199        self.execution.tool_model_config.as_ref()
200    }
201
202    pub const fn session_id(&self) -> &SessionId {
203        &self.request.session_id
204    }
205
206    pub const fn user_id(&self) -> &UserId {
207        &self.auth.actor.user_id
208    }
209
210    pub const fn actor(&self) -> &Actor {
211        &self.auth.actor
212    }
213
214    pub const fn trace_id(&self) -> &TraceId {
215        &self.execution.trace_id
216    }
217
218    pub const fn context_id(&self) -> &ContextId {
219        &self.execution.context_id
220    }
221
222    pub const fn agent_name(&self) -> &AgentName {
223        &self.execution.agent_name
224    }
225
226    pub const fn auth_token(&self) -> Option<&JwtToken> {
227        self.auth.auth_token.as_ref()
228    }
229
230    pub const fn user_type(&self) -> UserType {
231        self.auth.user_type
232    }
233
234    pub const fn rate_limit_tier(&self) -> RateLimitTier {
235        self.auth.user_type.rate_tier()
236    }
237
238    pub const fn task_id(&self) -> Option<&TaskId> {
239        self.execution.task_id.as_ref()
240    }
241
242    pub const fn client_id(&self) -> Option<&ClientId> {
243        self.request.client_id.as_ref()
244    }
245
246    pub const fn ai_tool_call_id(&self) -> Option<&AiToolCallId> {
247        self.execution.ai_tool_call_id.as_ref()
248    }
249
250    pub const fn mcp_execution_id(&self) -> Option<&McpExecutionId> {
251        self.execution.mcp_execution_id.as_ref()
252    }
253
254    pub const fn call_source(&self) -> Option<CallSource> {
255        self.execution.call_source
256    }
257
258    pub const fn is_authenticated(&self) -> bool {
259        self.user.is_some()
260    }
261
262    pub const fn is_system(&self) -> bool {
263        matches!(self.auth.user_type, UserType::Service)
264    }
265
266    pub const fn is_anonymous(&self) -> bool {
267        matches!(self.auth.user_type, UserType::Anon)
268    }
269
270    pub fn elapsed(&self) -> Duration {
271        self.start_time.elapsed()
272    }
273
274    pub const fn validate_task_execution(&self) -> Result<(), RequestContextValidationError> {
275        if self.execution.task_id.is_none() {
276            return Err(RequestContextValidationError::MissingTaskId);
277        }
278        Ok(())
279    }
280
281    pub const fn validate_authenticated(&self) -> Result<(), RequestContextValidationError> {
282        if self.auth.auth_token.is_none() {
283            return Err(RequestContextValidationError::MissingAuthToken);
284        }
285        if matches!(self.auth.user_type, UserType::Anon) {
286            return Err(RequestContextValidationError::Unauthenticated);
287        }
288        Ok(())
289    }
290}