systemprompt_models/execution/context/
propagation.rs1use super::{CallSource, RequestContext};
13use http::{HeaderMap, HeaderValue};
14use std::str::FromStr;
15use systemprompt_identifiers::{
16 Actor, AgentName, AiToolCallId, ClientId, ContextId, JwtToken, SessionId, TaskId, TraceId,
17 UserId, headers,
18};
19use systemprompt_traits::{
20 ContextPropagation, ContextPropagationError, ContextPropagationResult, InjectContextHeaders,
21};
22
23fn insert_header(headers: &mut HeaderMap, name: &'static str, value: &str) {
24 match HeaderValue::from_str(value) {
25 Ok(val) => {
26 headers.insert(name, val);
27 },
28 Err(e) => {
29 tracing::warn!(
30 header = %name,
31 value = %value,
32 error = %e,
33 "Invalid header value - header not inserted"
34 );
35 },
36 }
37}
38
39fn insert_header_if_present(headers: &mut HeaderMap, name: &'static str, value: Option<&str>) {
40 if let Some(v) = value {
41 insert_header(headers, name, v);
42 }
43}
44
45impl InjectContextHeaders for RequestContext {
46 fn inject_headers(&self, hdrs: &mut HeaderMap) {
47 insert_header(hdrs, headers::SESSION_ID, self.request.session_id.as_str());
48 insert_header(hdrs, headers::TRACE_ID, self.execution.trace_id.as_str());
49 insert_header(hdrs, headers::USER_ID, self.auth.actor.user_id.as_str());
50 insert_header(hdrs, headers::USER_TYPE, self.auth.user_type.as_str());
51 insert_header(
52 hdrs,
53 headers::AGENT_NAME,
54 self.execution.agent_name.as_str(),
55 );
56
57 insert_header(
58 hdrs,
59 headers::CONTEXT_ID,
60 self.execution.context_id.as_str(),
61 );
62
63 insert_header_if_present(
64 hdrs,
65 headers::TASK_ID,
66 self.execution.task_id.as_ref().map(TaskId::as_str),
67 );
68 insert_header_if_present(
69 hdrs,
70 headers::AI_TOOL_CALL_ID,
71 self.execution.ai_tool_call_id.as_ref().map(AsRef::as_ref),
72 );
73 insert_header_if_present(
74 hdrs,
75 headers::CALL_SOURCE,
76 self.execution.call_source.as_ref().map(CallSource::as_str),
77 );
78 insert_header_if_present(
79 hdrs,
80 headers::CLIENT_ID,
81 self.request.client_id.as_ref().map(ClientId::as_str),
82 );
83
84 match &self.auth.auth_token {
85 Some(auth_token) => {
86 let auth_value = format!("Bearer {}", auth_token.as_str());
87 insert_header(hdrs, headers::AUTHORIZATION, &auth_value);
88 tracing::trace!(user_id = %self.auth.actor.user_id, "Injected Authorization header for proxy");
89 },
90 None => {
91 tracing::trace!(user_id = %self.auth.actor.user_id, "No auth_token to inject - Authorization header not added");
92 },
93 }
94
95 if let Some(user) = &self.user {
96 insert_header(hdrs, headers::PROXY_VERIFIED, "true");
97 let perms = crate::auth::permissions_to_string(&user.permissions);
98 insert_header(hdrs, headers::USER_PERMISSIONS, &perms);
99 let roles = crate::auth::roles_to_string(&user.roles);
100 insert_header(hdrs, headers::USER_ROLES, &roles);
101 }
102 }
103}
104
105fn header_str<'h>(hdrs: &'h HeaderMap, name: &'static str) -> Option<&'h str> {
106 hdrs.get(name).and_then(|v| v.to_str().ok())
107}
108
109fn required_header<'h>(
110 hdrs: &'h HeaderMap,
111 name: &'static str,
112) -> ContextPropagationResult<&'h str> {
113 header_str(hdrs, name).ok_or_else(|| ContextPropagationError::MissingHeader(name.to_owned()))
114}
115
116fn invalid_header(
117 name: &'static str,
118 source: impl Into<systemprompt_traits::BoxedSource>,
119) -> ContextPropagationError {
120 ContextPropagationError::InvalidHeader {
121 name: name.to_owned(),
122 source: source.into(),
123 }
124}
125
126fn apply_optional_execution_fields(
127 mut ctx: RequestContext,
128 hdrs: &HeaderMap,
129) -> ContextPropagationResult<RequestContext> {
130 if let Some(s) = header_str(hdrs, headers::TASK_ID) {
131 ctx = ctx.with_task_id(TaskId::new(s.to_owned()));
132 }
133 if let Some(s) = header_str(hdrs, headers::AI_TOOL_CALL_ID) {
134 ctx = ctx.with_ai_tool_call_id(AiToolCallId::new(s.to_owned()));
135 }
136 if let Some(s) = header_str(hdrs, headers::CALL_SOURCE) {
137 let cs = CallSource::from_str(s).map_err(|e| invalid_header(headers::CALL_SOURCE, e))?;
138 ctx = ctx.with_call_source(cs);
139 }
140 if let Some(s) = header_str(hdrs, headers::CLIENT_ID) {
141 ctx = ctx.with_client_id(ClientId::new(s.to_owned()));
142 }
143 let auth_token = header_str(hdrs, headers::AUTHORIZATION)
144 .and_then(|s| s.strip_prefix("Bearer "))
145 .filter(|token| !token.is_empty());
146 if let Some(token) = auth_token {
147 ctx = ctx.with_auth_token(JwtToken::new(token));
148 }
149 Ok(ctx)
150}
151
152fn apply_proxy_verified_user(
153 mut ctx: RequestContext,
154 hdrs: &HeaderMap,
155 user_id: &UserId,
156) -> ContextPropagationResult<RequestContext> {
157 let proxy_verified = header_str(hdrs, headers::PROXY_VERIFIED).is_some_and(|v| v == "true");
158 if !proxy_verified {
159 return Ok(ctx);
160 }
161
162 let Some(raw_permissions) = header_str(hdrs, headers::USER_PERMISSIONS) else {
166 return Ok(ctx);
167 };
168 let permissions = crate::auth::parse_permissions(raw_permissions)
169 .map_err(|e| invalid_header(headers::USER_PERMISSIONS, e))?;
170
171 let roles = header_str(hdrs, headers::USER_ROLES)
172 .map(crate::auth::parse_roles)
173 .unwrap_or_default();
174 let user = crate::auth::AuthenticatedUser::new_with_roles(
175 user_id.clone(),
176 String::new(),
177 String::new(),
178 permissions,
179 roles,
180 );
181 ctx = ctx.with_user(user);
182 Ok(ctx)
183}
184
185impl ContextPropagation for RequestContext {
186 fn from_headers(hdrs: &HeaderMap) -> ContextPropagationResult<Self> {
187 let session_id = required_header(hdrs, headers::SESSION_ID)?;
188 let trace_id = required_header(hdrs, headers::TRACE_ID)?;
189 let user_id = UserId::try_new(required_header(hdrs, headers::USER_ID)?)
190 .map_err(|e| invalid_header(headers::USER_ID, e))?;
191 let agent_name = required_header(hdrs, headers::AGENT_NAME)?;
192
193 let session_id = SessionId::new(session_id.to_owned());
194 let context_id = match header_str(hdrs, headers::CONTEXT_ID).filter(|s| !s.is_empty()) {
195 Some(s) => ContextId::try_new(s).map_err(|e| invalid_header(headers::CONTEXT_ID, e))?,
196 None => ContextId::derived_from_session(&session_id),
197 };
198
199 let agent_name = AgentName::try_new(agent_name.to_owned())
200 .map_err(|e| invalid_header(headers::AGENT_NAME, e))?;
201
202 let ctx = Self::new(
203 session_id,
204 TraceId::new(trace_id.to_owned()),
205 context_id,
206 agent_name,
207 Actor::user(user_id.clone()),
208 );
209
210 let ctx = apply_optional_execution_fields(ctx, hdrs)?;
211 apply_proxy_verified_user(ctx, hdrs, &user_id)
212 }
213
214 fn to_headers(&self) -> HeaderMap {
215 let mut headers = HeaderMap::new();
216 self.inject_headers(&mut headers);
217 headers
218 }
219}