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, SessionId, TaskId, TraceId, UserId,
17 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 let auth_token = self.auth.auth_token.as_str();
85 if auth_token.is_empty() {
86 tracing::trace!(user_id = %self.auth.actor.user_id, "No auth_token to inject - Authorization header not added");
87 } else {
88 let auth_value = format!("Bearer {}", auth_token);
89 insert_header(hdrs, headers::AUTHORIZATION, &auth_value);
90 tracing::trace!(user_id = %self.auth.actor.user_id, "Injected Authorization header for proxy");
91 }
92
93 if let Some(user) = &self.user {
94 insert_header(hdrs, headers::PROXY_VERIFIED, "true");
95 let perms = crate::auth::permissions_to_string(&user.permissions);
96 insert_header(hdrs, headers::USER_PERMISSIONS, &perms);
97 }
98 }
99}
100
101fn header_str<'h>(hdrs: &'h HeaderMap, name: &'static str) -> Option<&'h str> {
102 hdrs.get(name).and_then(|v| v.to_str().ok())
103}
104
105fn required_header<'h>(
106 hdrs: &'h HeaderMap,
107 name: &'static str,
108) -> ContextPropagationResult<&'h str> {
109 header_str(hdrs, name).ok_or_else(|| ContextPropagationError::MissingHeader(name.to_owned()))
110}
111
112fn apply_optional_execution_fields(mut ctx: RequestContext, hdrs: &HeaderMap) -> RequestContext {
113 if let Some(s) = header_str(hdrs, headers::TASK_ID) {
114 ctx = ctx.with_task_id(TaskId::new(s.to_owned()));
115 }
116 if let Some(s) = header_str(hdrs, headers::AI_TOOL_CALL_ID) {
117 ctx = ctx.with_ai_tool_call_id(AiToolCallId::new(s.to_owned()));
118 }
119 let call_source =
120 header_str(hdrs, headers::CALL_SOURCE).and_then(|s| CallSource::from_str(s).ok());
121 if let Some(cs) = call_source {
122 ctx = ctx.with_call_source(cs);
123 }
124 if let Some(s) = header_str(hdrs, headers::CLIENT_ID) {
125 ctx = ctx.with_client_id(ClientId::new(s.to_owned()));
126 }
127 let auth_token =
128 header_str(hdrs, headers::AUTHORIZATION).and_then(|s| s.strip_prefix("Bearer "));
129 if let Some(token) = auth_token {
130 ctx = ctx.with_auth_token(token.to_owned());
131 }
132 ctx
133}
134
135fn apply_proxy_verified_user(
136 mut ctx: RequestContext,
137 hdrs: &HeaderMap,
138 user_id: &str,
139) -> ContextPropagationResult<RequestContext> {
140 let proxy_verified = header_str(hdrs, headers::PROXY_VERIFIED).is_some_and(|v| v == "true");
141 if !proxy_verified {
142 return Ok(ctx);
143 }
144
145 let Some(permissions) = header_str(hdrs, headers::USER_PERMISSIONS)
146 .and_then(|s| crate::auth::parse_permissions(s).ok())
147 else {
148 return Ok(ctx);
149 };
150
151 let user_id_uuid =
152 user_id
153 .parse::<uuid::Uuid>()
154 .map_err(|e| ContextPropagationError::InvalidHeader {
155 name: headers::USER_ID.to_owned(),
156 message: format!("invalid UUID: {e}"),
157 })?;
158 let user = crate::auth::AuthenticatedUser::new(
159 user_id_uuid,
160 String::new(),
161 String::new(),
162 permissions,
163 );
164 ctx = ctx.with_user(user);
165 Ok(ctx)
166}
167
168impl ContextPropagation for RequestContext {
169 fn from_headers(hdrs: &HeaderMap) -> ContextPropagationResult<Self> {
170 let session_id = required_header(hdrs, headers::SESSION_ID)?;
171 let trace_id = required_header(hdrs, headers::TRACE_ID)?;
172 let user_id = required_header(hdrs, headers::USER_ID)?;
173 let agent_name = required_header(hdrs, headers::AGENT_NAME)?;
174
175 let context_id = header_str(hdrs, headers::CONTEXT_ID)
176 .filter(|s| !s.is_empty())
177 .and_then(|s| ContextId::try_new(s).ok())
178 .unwrap_or_else(ContextId::generate);
179
180 let ctx = Self::new(
181 SessionId::new(session_id.to_owned()),
182 TraceId::new(trace_id.to_owned()),
183 context_id,
184 AgentName::new(agent_name.to_owned()),
185 )
186 .with_actor(Actor::user(UserId::new(user_id.to_owned())));
187
188 let ctx = apply_optional_execution_fields(ctx, hdrs);
189 apply_proxy_verified_user(ctx, hdrs, user_id)
190 }
191
192 fn to_headers(&self) -> HeaderMap {
193 let mut headers = HeaderMap::new();
194 self.inject_headers(&mut headers);
195 headers
196 }
197}