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