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