systemprompt_models/execution/context/
mod.rs1mod 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}