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