1use std::sync::Arc;
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4use serde::{Deserialize, Serialize};
5use tokio_util::sync::CancellationToken;
6
7use crate::error::AgentError;
8use crate::value::AgentValue;
9
10#[derive(Clone, Debug, Default, Serialize, Deserialize)]
20pub struct AgentContext {
21 id: usize,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 vars: Option<im::HashMap<String, AgentValue>>,
27
28 #[serde(default, skip_serializing_if = "Option::is_none")]
30 frames: Option<im::Vector<Frame>>,
31
32 #[serde(skip)]
42 cancel: Option<Arc<CancellationToken>>,
43}
44
45pub const FRAME_MAP: &str = "map";
47
48pub const FRAME_KEY_INDEX: &str = "index";
50
51pub const FRAME_KEY_LENGTH: &str = "length";
53
54impl AgentContext {
55 pub fn new() -> Self {
57 Self {
58 id: new_id(),
59 vars: None,
60 frames: None,
61 cancel: None,
62 }
63 }
64
65 pub fn id(&self) -> usize {
67 self.id
68 }
69
70 pub fn get_var(&self, key: &str) -> Option<&AgentValue> {
74 self.vars.as_ref().and_then(|vars| vars.get(key))
75 }
76
77 pub fn with_var(&self, key: String, value: AgentValue) -> Self {
79 let mut vars = if let Some(vars) = &self.vars {
80 vars.clone()
81 } else {
82 im::HashMap::new()
83 };
84 vars.insert(key, value);
85 Self {
86 id: self.id,
87 vars: Some(vars),
88 frames: self.frames.clone(),
89 cancel: self.cancel.clone(),
90 }
91 }
92
93 pub fn cancel_token(&self) -> Option<&CancellationToken> {
102 self.cancel.as_deref()
103 }
104
105 pub fn with_cancel_token(&self, token: impl Into<Arc<CancellationToken>>) -> Self {
107 Self {
108 id: self.id,
109 vars: self.vars.clone(),
110 frames: self.frames.clone(),
111 cancel: Some(token.into()),
112 }
113 }
114
115 pub fn is_cancelled(&self) -> bool {
117 self.cancel.as_ref().is_some_and(|t| t.is_cancelled())
118 }
119}
120
121static CONTEXT_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
123
124fn new_id() -> usize {
126 CONTEXT_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
127}
128
129#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct Frame {
134 pub name: String,
136
137 pub data: AgentValue,
139}
140
141fn map_frame_data(index: usize, len: usize) -> AgentValue {
142 let mut data = AgentValue::object_default();
143 let _ = data.set(
144 FRAME_KEY_INDEX.to_string(),
145 AgentValue::integer(index as i64),
146 );
147 let _ = data.set(
148 FRAME_KEY_LENGTH.to_string(),
149 AgentValue::integer(len as i64),
150 );
151 data
152}
153
154fn read_map_frame(frame: &Frame) -> Result<(usize, usize), AgentError> {
155 let idx = frame
156 .data
157 .get(FRAME_KEY_INDEX)
158 .and_then(|v| v.as_i64())
159 .ok_or_else(|| AgentError::InvalidValue("map frame missing integer index".into()))?;
160 let len = frame
161 .data
162 .get(FRAME_KEY_LENGTH)
163 .and_then(|v| v.as_i64())
164 .ok_or_else(|| AgentError::InvalidValue("map frame missing integer length".into()))?;
165 if idx < 0 || len < 1 {
166 return Err(AgentError::InvalidValue("Invalid map frame values".into()));
167 }
168 let (idx, len) = (idx as usize, len as usize);
169 if idx >= len {
170 return Err(AgentError::InvalidValue(
171 "map frame index is out of bounds".into(),
172 ));
173 }
174 Ok((idx, len))
175}
176
177impl AgentContext {
178 pub fn frames(&self) -> Option<&im::Vector<Frame>> {
180 self.frames.as_ref()
181 }
182
183 pub fn push_frame(&self, name: String, data: AgentValue) -> Self {
185 let mut frames = if let Some(frames) = &self.frames {
186 frames.clone()
187 } else {
188 im::Vector::new()
189 };
190 frames.push_back(Frame { name, data });
191 Self {
192 id: self.id,
193 vars: self.vars.clone(),
194 frames: Some(frames),
195 cancel: self.cancel.clone(),
196 }
197 }
198
199 pub fn push_map_frame(&self, index: usize, len: usize) -> Result<Self, AgentError> {
201 if len == 0 {
202 return Err(AgentError::InvalidValue(
203 "map frame length must be positive".into(),
204 ));
205 }
206 if index >= len {
207 return Err(AgentError::InvalidValue(
208 "map frame index is out of bounds".into(),
209 ));
210 }
211 Ok(self.push_frame(FRAME_MAP.to_string(), map_frame_data(index, len)))
212 }
213
214 pub fn current_map_frame(&self) -> Result<Option<(usize, usize)>, AgentError> {
216 let frames = match self.frames() {
217 Some(frames) => frames,
218 None => return Ok(None),
219 };
220 let Some(last_index) = frames.len().checked_sub(1) else {
221 return Ok(None);
222 };
223 let Some(frame) = frames.get(last_index) else {
224 return Ok(None);
225 };
226 if frame.name != FRAME_MAP {
227 return Ok(None);
228 }
229 read_map_frame(frame).map(Some)
230 }
231
232 pub fn pop_map_frame(&self) -> Result<AgentContext, AgentError> {
234 let (frame, next_ctx) = self.pop_frame();
235 match frame {
236 Some(f) if f.name == FRAME_MAP => Ok(next_ctx),
237 Some(f) => Err(AgentError::InvalidValue(format!(
238 "Unexpected frame '{}', expected map",
239 f.name
240 ))),
241 None => Err(AgentError::InvalidValue(
242 "Missing map frame in context".into(),
243 )),
244 }
245 }
246
247 pub fn map_frame_indices(&self) -> Result<Vec<(usize, usize)>, AgentError> {
249 let mut indices = Vec::new();
250 let Some(frames) = self.frames() else {
251 return Ok(indices);
252 };
253 for frame in frames.iter() {
254 if frame.name != FRAME_MAP {
255 continue;
256 }
257 let (idx, len) = read_map_frame(frame)?;
258 indices.push((idx, len));
259 }
260 Ok(indices)
261 }
262
263 pub fn ctx_key(&self) -> Result<String, AgentError> {
265 let map_frames = self.map_frame_indices()?;
266 if map_frames.is_empty() {
267 return Ok(self.id().to_string());
268 }
269 let parts: Vec<String> = map_frames
270 .iter()
271 .map(|(idx, len)| format!("{}:{}", idx, len))
272 .collect();
273 Ok(format!("{}:{}", self.id(), parts.join(",")))
274 }
275
276 pub fn pop_frame(&self) -> (Option<Frame>, Self) {
279 if let Some(frames) = &self.frames {
280 if frames.is_empty() {
281 return (None, self.clone());
282 }
283 let mut frames = frames.clone();
284 let last = frames.pop_back().unwrap(); let new_frames = if frames.is_empty() {
287 None
288 } else {
289 Some(frames)
290 };
291 return (
292 Some(last),
293 Self {
294 id: self.id,
295 vars: self.vars.clone(),
296 frames: new_frames,
297 cancel: self.cancel.clone(),
298 },
299 );
300 }
301 (None, self.clone())
302 }
303}
304
305#[cfg(test)]
307mod tests {
308 use super::*;
309 use serde_json::json;
310
311 #[test]
312 fn new_assigns_unique_ids() {
313 let ctx1 = AgentContext::new();
314 let ctx2 = AgentContext::new();
315
316 assert_ne!(ctx1.id(), 0);
317 assert_ne!(ctx2.id(), 0);
318 assert_ne!(ctx1.id(), ctx2.id());
319 assert_eq!(ctx1.id(), ctx1.clone().id());
320 }
321
322 #[test]
323 fn with_var_sets_value_without_mutating_original() {
324 let ctx = AgentContext::new();
325 assert!(ctx.get_var("answer").is_none());
326
327 let updated = ctx.with_var("answer".into(), AgentValue::integer(42));
328
329 assert!(ctx.get_var("answer").is_none());
330 assert_eq!(updated.get_var("answer"), Some(&AgentValue::integer(42)));
331 assert_eq!(ctx.id(), updated.id());
332 }
333
334 #[test]
335 fn push_and_pop_frames() {
336 let ctx = AgentContext::new();
337 assert!(ctx.frames().is_none());
338
339 let ctx = ctx
340 .push_frame("first".into(), AgentValue::string("a"))
341 .push_frame("second".into(), AgentValue::integer(2));
342
343 let frames = ctx.frames().expect("frames should be present");
344 assert_eq!(frames.len(), 2);
345 assert_eq!(frames[0].name, "first");
346 assert_eq!(frames[1].name, "second");
347 assert_eq!(frames[1].data, AgentValue::integer(2));
348
349 let (popped_second, ctx) = ctx.pop_frame();
350 let popped_second = popped_second.expect("second frame should exist");
351 assert_eq!(popped_second.name, "second");
352 assert_eq!(ctx.frames().unwrap().len(), 1);
353 assert_eq!(ctx.frames().unwrap()[0].name, "first");
354
355 let (popped_first, ctx) = ctx.pop_frame();
356 assert_eq!(popped_first.unwrap().name, "first");
357 assert!(ctx.frames().is_none());
358
359 let (no_frame, ctx_after_empty) = ctx.pop_frame();
360 assert!(no_frame.is_none());
361 assert!(ctx_after_empty.frames().is_none());
362 }
363
364 #[test]
365 fn clone_preserves_vars() {
366 let ctx = AgentContext::new().with_var("key".into(), AgentValue::integer(1));
367 let cloned = ctx.clone();
368
369 assert_eq!(cloned.get_var("key"), Some(&AgentValue::integer(1)));
370 assert_eq!(cloned.id(), ctx.id());
371 }
372
373 #[test]
374 fn clone_preserves_frames() {
375 let ctx = AgentContext::new().push_frame("frame".into(), AgentValue::string("data"));
376 let cloned = ctx.clone();
377
378 let frames = cloned.frames().expect("cloned frames should exist");
379 assert_eq!(frames.len(), 1);
380 assert_eq!(frames[0].name, "frame");
381 assert_eq!(frames[0].data, AgentValue::string("data"));
382 assert_eq!(cloned.id(), ctx.id());
383 }
384
385 #[test]
386 fn serialization_skips_empty_optional_fields() {
387 let ctx = AgentContext::new();
388 let json_ctx = serde_json::to_value(&ctx).unwrap();
389
390 assert!(json_ctx.get("id").and_then(|v| v.as_u64()).is_some());
391 assert!(json_ctx.get("vars").is_none());
392 assert!(json_ctx.get("frames").is_none());
393
394 let populated = ctx
395 .with_var("key".into(), AgentValue::string("value"))
396 .push_frame("frame".into(), AgentValue::integer(1));
397 let json_populated = serde_json::to_value(&populated).unwrap();
398
399 assert_eq!(json_populated["vars"]["key"], json!("value"));
400 let frames = json_populated["frames"]
401 .as_array()
402 .expect("frames should serialize as array");
403 assert_eq!(frames.len(), 1);
404 assert_eq!(frames[0]["name"], json!("frame"));
405 assert_eq!(frames[0]["data"], json!(1));
406 }
407
408 #[test]
409 fn map_frame_helpers_validate_and_track_indices() -> Result<(), AgentError> {
410 let ctx = AgentContext::new();
411 let ctx = ctx.push_map_frame(0, 2)?;
412 let ctx = ctx.push_map_frame(1, 3)?;
413
414 let indices = ctx.map_frame_indices()?;
415 assert_eq!(indices, vec![(0, 2), (1, 3)]);
416
417 let current = ctx.current_map_frame()?.expect("map frame should exist");
418 assert_eq!(current, (1, 3));
419
420 let key = ctx.ctx_key()?;
421 assert_eq!(key, format!("{}:0:2,1:3", ctx.id()));
422
423 let ctx = ctx.pop_map_frame()?;
424 let current_after_pop = ctx.current_map_frame()?.expect("map frame should remain");
425 assert_eq!(current_after_pop, (0, 2));
426
427 Ok(())
428 }
429
430 #[test]
431 fn pop_map_frame_errors_when_missing_or_wrong_kind() {
432 let ctx = AgentContext::new();
433 assert!(ctx.pop_map_frame().is_err());
434
435 let ctx = ctx.push_frame("other".into(), AgentValue::unit());
436 assert!(ctx.pop_map_frame().is_err());
437 }
438
439 #[test]
440 fn push_map_frame_rejects_invalid_bounds() {
441 let ctx = AgentContext::new();
442 assert!(ctx.push_map_frame(0, 0).is_err());
443 assert!(ctx.push_map_frame(2, 1).is_err());
444 }
445}