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