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