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