1use std::sync::atomic::{AtomicUsize, Ordering};
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::AgentError;
6use crate::value::AgentValue;
7
8#[derive(Clone, Debug, Default, Serialize, Deserialize)]
18pub struct AgentContext {
19 id: usize,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
23 vars: Option<im::HashMap<String, AgentValue>>,
24
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 frames: Option<im::Vector<Frame>>,
27}
28
29pub const FRAME_MAP: &str = "map";
30pub const FRAME_KEY_INDEX: &str = "index";
31pub const FRAME_KEY_LENGTH: &str = "length";
32
33impl AgentContext {
34 pub fn new() -> Self {
36 Self {
37 id: new_id(),
38 vars: None,
39 frames: None,
40 }
41 }
42
43 pub fn id(&self) -> usize {
45 self.id
46 }
47
48 pub fn get_var(&self, key: &str) -> Option<&AgentValue> {
52 self.vars.as_ref().and_then(|vars| vars.get(key))
53 }
54
55 pub fn with_var(&self, key: String, value: AgentValue) -> Self {
57 let mut vars = if let Some(vars) = &self.vars {
58 vars.clone()
59 } else {
60 im::HashMap::new()
61 };
62 vars.insert(key, value);
63 Self {
64 id: self.id,
65 vars: Some(vars),
66 frames: self.frames.clone(),
67 }
68 }
69}
70
71static CONTEXT_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
73
74fn new_id() -> usize {
76 CONTEXT_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
77}
78
79#[derive(Clone, Debug, Serialize, Deserialize)]
83pub struct Frame {
84 pub name: String,
85 pub data: AgentValue,
86}
87
88fn map_frame_data(index: usize, len: usize) -> AgentValue {
89 let mut data = AgentValue::object_default();
90 let _ = data.set(
91 FRAME_KEY_INDEX.to_string(),
92 AgentValue::integer(index as i64),
93 );
94 let _ = data.set(
95 FRAME_KEY_LENGTH.to_string(),
96 AgentValue::integer(len as i64),
97 );
98 data
99}
100
101fn read_map_frame(frame: &Frame) -> Result<(usize, usize), AgentError> {
102 let idx = frame
103 .data
104 .get(FRAME_KEY_INDEX)
105 .and_then(|v| v.as_i64())
106 .ok_or_else(|| AgentError::InvalidValue("map frame missing integer index".into()))?;
107 let len = frame
108 .data
109 .get(FRAME_KEY_LENGTH)
110 .and_then(|v| v.as_i64())
111 .ok_or_else(|| AgentError::InvalidValue("map frame missing integer length".into()))?;
112 if idx < 0 || len < 1 {
113 return Err(AgentError::InvalidValue("Invalid map frame values".into()));
114 }
115 let (idx, len) = (idx as usize, len as usize);
116 if idx >= len {
117 return Err(AgentError::InvalidValue(
118 "map frame index is out of bounds".into(),
119 ));
120 }
121 Ok((idx, len))
122}
123
124impl AgentContext {
125 pub fn frames(&self) -> Option<&im::Vector<Frame>> {
127 self.frames.as_ref()
128 }
129
130 pub fn push_frame(&self, name: String, data: AgentValue) -> Self {
132 let mut frames = if let Some(frames) = &self.frames {
133 frames.clone()
134 } else {
135 im::Vector::new()
136 };
137 frames.push_back(Frame { name, data });
138 Self {
139 id: self.id,
140 vars: self.vars.clone(),
141 frames: Some(frames),
142 }
143 }
144
145 pub fn push_map_frame(&self, index: usize, len: usize) -> Result<Self, AgentError> {
147 if len == 0 {
148 return Err(AgentError::InvalidValue(
149 "map frame length must be positive".into(),
150 ));
151 }
152 if index >= len {
153 return Err(AgentError::InvalidValue(
154 "map frame index is out of bounds".into(),
155 ));
156 }
157 Ok(self.push_frame(FRAME_MAP.to_string(), map_frame_data(index, len)))
158 }
159
160 pub fn current_map_frame(&self) -> Result<Option<(usize, usize)>, AgentError> {
162 let frames = match self.frames() {
163 Some(frames) => frames,
164 None => return Ok(None),
165 };
166 let Some(last_index) = frames.len().checked_sub(1) else {
167 return Ok(None);
168 };
169 let Some(frame) = frames.get(last_index) else {
170 return Ok(None);
171 };
172 if frame.name != FRAME_MAP {
173 return Ok(None);
174 }
175 read_map_frame(frame).map(Some)
176 }
177
178 pub fn pop_map_frame(&self) -> Result<AgentContext, AgentError> {
180 let (frame, next_ctx) = self.pop_frame();
181 match frame {
182 Some(f) if f.name == FRAME_MAP => Ok(next_ctx),
183 Some(f) => Err(AgentError::InvalidValue(format!(
184 "Unexpected frame '{}', expected map",
185 f.name
186 ))),
187 None => Err(AgentError::InvalidValue(
188 "Missing map frame in context".into(),
189 )),
190 }
191 }
192
193 pub fn map_frame_indices(&self) -> Result<Vec<(usize, usize)>, AgentError> {
195 let mut indices = Vec::new();
196 let Some(frames) = self.frames() else {
197 return Ok(indices);
198 };
199 for frame in frames.iter() {
200 if frame.name != FRAME_MAP {
201 continue;
202 }
203 let (idx, len) = read_map_frame(frame)?;
204 indices.push((idx, len));
205 }
206 Ok(indices)
207 }
208
209 pub fn ctx_key(&self) -> Result<String, AgentError> {
211 let map_frames = self.map_frame_indices()?;
212 if map_frames.is_empty() {
213 return Ok(self.id().to_string());
214 }
215 let parts: Vec<String> = map_frames
216 .iter()
217 .map(|(idx, len)| format!("{}:{}", idx, len))
218 .collect();
219 Ok(format!("{}:{}", self.id(), parts.join(",")))
220 }
221
222 pub fn pop_frame(&self) -> (Option<Frame>, Self) {
225 if let Some(frames) = &self.frames {
226 if frames.is_empty() {
227 return (None, self.clone());
228 }
229 let mut frames = frames.clone();
230 let last = frames.pop_back().unwrap(); let new_frames = if frames.is_empty() {
233 None
234 } else {
235 Some(frames)
236 };
237 return (
238 Some(last),
239 Self {
240 id: self.id,
241 vars: self.vars.clone(),
242 frames: new_frames,
243 },
244 );
245 }
246 (None, self.clone())
247 }
248}
249
250#[cfg(test)]
252mod tests {
253 use super::*;
254 use serde_json::json;
255
256 #[test]
257 fn new_assigns_unique_ids() {
258 let ctx1 = AgentContext::new();
259 let ctx2 = AgentContext::new();
260
261 assert_ne!(ctx1.id(), 0);
262 assert_ne!(ctx2.id(), 0);
263 assert_ne!(ctx1.id(), ctx2.id());
264 assert_eq!(ctx1.id(), ctx1.clone().id());
265 }
266
267 #[test]
268 fn with_var_sets_value_without_mutating_original() {
269 let ctx = AgentContext::new();
270 assert!(ctx.get_var("answer").is_none());
271
272 let updated = ctx.with_var("answer".into(), AgentValue::integer(42));
273
274 assert!(ctx.get_var("answer").is_none());
275 assert_eq!(updated.get_var("answer"), Some(&AgentValue::integer(42)));
276 assert_eq!(ctx.id(), updated.id());
277 }
278
279 #[test]
280 fn push_and_pop_frames() {
281 let ctx = AgentContext::new();
282 assert!(ctx.frames().is_none());
283
284 let ctx = ctx
285 .push_frame("first".into(), AgentValue::string("a"))
286 .push_frame("second".into(), AgentValue::integer(2));
287
288 let frames = ctx.frames().expect("frames should be present");
289 assert_eq!(frames.len(), 2);
290 assert_eq!(frames[0].name, "first");
291 assert_eq!(frames[1].name, "second");
292 assert_eq!(frames[1].data, AgentValue::integer(2));
293
294 let (popped_second, ctx) = ctx.pop_frame();
295 let popped_second = popped_second.expect("second frame should exist");
296 assert_eq!(popped_second.name, "second");
297 assert_eq!(ctx.frames().unwrap().len(), 1);
298 assert_eq!(ctx.frames().unwrap()[0].name, "first");
299
300 let (popped_first, ctx) = ctx.pop_frame();
301 assert_eq!(popped_first.unwrap().name, "first");
302 assert!(ctx.frames().is_none());
303
304 let (no_frame, ctx_after_empty) = ctx.pop_frame();
305 assert!(no_frame.is_none());
306 assert!(ctx_after_empty.frames().is_none());
307 }
308
309 #[test]
310 fn clone_preserves_vars() {
311 let ctx = AgentContext::new().with_var("key".into(), AgentValue::integer(1));
312 let cloned = ctx.clone();
313
314 assert_eq!(cloned.get_var("key"), Some(&AgentValue::integer(1)));
315 assert_eq!(cloned.id(), ctx.id());
316 }
317
318 #[test]
319 fn clone_preserves_frames() {
320 let ctx = AgentContext::new().push_frame("frame".into(), AgentValue::string("data"));
321 let cloned = ctx.clone();
322
323 let frames = cloned.frames().expect("cloned frames should exist");
324 assert_eq!(frames.len(), 1);
325 assert_eq!(frames[0].name, "frame");
326 assert_eq!(frames[0].data, AgentValue::string("data"));
327 assert_eq!(cloned.id(), ctx.id());
328 }
329
330 #[test]
331 fn serialization_skips_empty_optional_fields() {
332 let ctx = AgentContext::new();
333 let json_ctx = serde_json::to_value(&ctx).unwrap();
334
335 assert!(json_ctx.get("id").and_then(|v| v.as_u64()).is_some());
336 assert!(json_ctx.get("vars").is_none());
337 assert!(json_ctx.get("frames").is_none());
338
339 let populated = ctx
340 .with_var("key".into(), AgentValue::string("value"))
341 .push_frame("frame".into(), AgentValue::integer(1));
342 let json_populated = serde_json::to_value(&populated).unwrap();
343
344 assert_eq!(json_populated["vars"]["key"], json!("value"));
345 let frames = json_populated["frames"]
346 .as_array()
347 .expect("frames should serialize as array");
348 assert_eq!(frames.len(), 1);
349 assert_eq!(frames[0]["name"], json!("frame"));
350 assert_eq!(frames[0]["data"], json!(1));
351 }
352
353 #[test]
354 fn map_frame_helpers_validate_and_track_indices() -> Result<(), AgentError> {
355 let ctx = AgentContext::new();
356 let ctx = ctx.push_map_frame(0, 2)?;
357 let ctx = ctx.push_map_frame(1, 3)?;
358
359 let indices = ctx.map_frame_indices()?;
360 assert_eq!(indices, vec![(0, 2), (1, 3)]);
361
362 let current = ctx.current_map_frame()?.expect("map frame should exist");
363 assert_eq!(current, (1, 3));
364
365 let key = ctx.ctx_key()?;
366 assert_eq!(key, format!("{}:0:2,1:3", ctx.id()));
367
368 let ctx = ctx.pop_map_frame()?;
369 let current_after_pop = ctx.current_map_frame()?.expect("map frame should remain");
370 assert_eq!(current_after_pop, (0, 2));
371
372 Ok(())
373 }
374
375 #[test]
376 fn pop_map_frame_errors_when_missing_or_wrong_kind() {
377 let ctx = AgentContext::new();
378 assert!(ctx.pop_map_frame().is_err());
379
380 let ctx = ctx.push_frame("other".into(), AgentValue::unit());
381 assert!(ctx.pop_map_frame().is_err());
382 }
383
384 #[test]
385 fn push_map_frame_rejects_invalid_bounds() {
386 let ctx = AgentContext::new();
387 assert!(ctx.push_map_frame(0, 0).is_err());
388 assert!(ctx.push_map_frame(2, 1).is_err());
389 }
390}