use crate::tools::ToolResult;
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ExecutionContext {
pub bindings: HashMap<String, ToolResult>,
pub execution_log: Vec<ExecutionStep>,
parent: Option<Box<ExecutionContext>>,
pub variables: serde_json::Map<String, Value>,
pub last_result: Option<ToolResult>,
}
#[derive(Debug, Clone)]
pub struct ExecutionStep {
pub step_type: String,
pub result: Result<ToolResult, crate::tools::ToolError>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl ExecutionContext {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
execution_log: Vec::new(),
parent: None,
variables: serde_json::Map::new(),
last_result: None,
}
}
pub fn nested_scope(&self) -> Self {
Self {
bindings: HashMap::new(),
execution_log: Vec::new(),
parent: Some(Box::new(self.clone())),
variables: self.variables.clone(),
last_result: self.last_result.clone(),
}
}
pub fn set_variable(&mut self, key: impl Into<String>, value: Value) {
self.variables.insert(key.into(), value);
}
pub fn get_variable(&self, key: &str) -> Option<&Value> {
self.variables.get(key)
}
pub fn with_variable(mut self, key: impl Into<String>, value: Value) -> Self {
self.variables.insert(key.into(), value);
self
}
pub fn with_last_result(mut self, result: ToolResult) -> Self {
self.last_result = Some(result);
self
}
pub fn bind(&mut self, name: String, value: ToolResult) {
self.bindings.insert(name, value);
}
pub fn lookup(&self, name: &str) -> Option<&ToolResult> {
self.bindings
.get(name)
.or_else(|| self.parent.as_ref().and_then(|p| p.lookup(name)))
}
pub fn log_step(
&mut self,
step_type: String,
result: Result<ToolResult, crate::tools::ToolError>,
) {
self.execution_log.push(ExecutionStep {
step_type,
result,
timestamp: chrono::Utc::now(),
});
}
pub fn all_bindings(&self) -> HashMap<String, &ToolResult> {
let mut result = HashMap::new();
if let Some(ref parent) = self.parent {
for (k, v) in parent.all_bindings() {
result.insert(k, v);
}
}
for (k, v) in &self.bindings {
result.insert(k.clone(), v);
}
result
}
}
impl Default for ExecutionContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_bindings() {
let mut ctx = ExecutionContext::new();
let result = ToolResult {
success: true,
result: "test".to_string(),
display_preference: None,
};
ctx.bind("test_var".to_string(), result.clone());
assert!(ctx.lookup("test_var").is_some());
assert!(ctx.lookup("nonexistent").is_none());
}
#[test]
fn test_nested_scope() {
let mut parent = ExecutionContext::new();
let result = ToolResult {
success: true,
result: "parent_value".to_string(),
display_preference: None,
};
parent.bind("shared".to_string(), result);
let mut child = parent.nested_scope();
let child_result = ToolResult {
success: true,
result: "child_value".to_string(),
display_preference: None,
};
child.bind("child_only".to_string(), child_result);
assert!(child.lookup("shared").is_some());
assert!(parent.lookup("child_only").is_none());
}
}