aimx/context.rs
1//! Evaluation context for expression evaluation within AIM workflows.
2//!
3//! The context module provides the runtime environment for evaluating AIMX expressions
4//! within the context of AIM workflows. It handles:
5//!
6//! - Variable resolution across multi-level workflow hierarchies
7//! - Function and method dispatch through the global function registry
8//! - Closure parameter management for functional programming constructs
9//! - Caching of evaluation results within a single evaluation session
10//! - Circular reference detection to prevent infinite loops
11//! - Inference operations for agentic workflow execution
12//!
13//! # Context Architecture
14//!
15//! The module defines two primary components:
16//!
17//! 1. [`ContextLike`] - A trait that defines the interface for evaluation contexts
18//! 2. [`Context`] - A complete implementation that integrates with AIM workflows
19//!
20//! The `Context` struct implements the `ContextLike` trait and provides a full
21//! implementation for workflow-based expression evaluation with proper scoping,
22//! caching, and inference capabilities.
23//!
24//! # Workflow Integration
25//!
26//! The context maintains references to workflows at different levels:
27//! - `workflow`: The current read-only workflow context
28//! - `workflow_mut`: A mutable workflow for making changes during inference
29//! - `scope`: The current reference scope for resolving relative references
30//!
31//! When evaluating expressions within workflows, the context automatically handles:
32//! - Multi-part reference resolution (e.g., `parent.child.value`)
33//! - Type promotion and validation for assignments
34//! - Caching of computed values within a session
35//! - Circular reference detection to prevent stack overflows
36//!
37//! # Thread Safety
38//!
39//! Each thread should use its own context instance to evaluate a workflow .
40//! Context instances are not thread-safe and should not be shared between threads.
41//!
42use crate::{
43 Expression, ExpressionLike, FunctionRegistry, Node, Reference, Rule, Typedef, Value, Workflow, WorkflowLike, aim::{get_config, get_lock_manager}, get_workspace, inference::{generate_prompt, parse_response, send_request, validate_responses},
44};
45use anyhow::{anyhow, Result};
46use std::{
47 collections::{HashMap, HashSet},
48 mem::replace,
49 sync::{Arc, Mutex},
50};
51
52/// Trait for evaluation contexts that provide runtime environment for expression evaluation.
53///
54/// This trait defines the interface that evaluation contexts must implement.
55/// A context provides variable values, handles function calls, manages closure parameters,
56/// and provides the runtime environment for expression evaluation.
57///
58/// The ContextLike trait is designed to be implemented by context types that need to:
59/// - Resolve variable references during evaluation
60/// - Handle function and method calls
61/// - Manage closure parameters for functional programming constructs
62/// - Support inference operations for agentic workflows
63///
64/// # Implementation Requirements
65///
66/// Implementors must manage:
67/// - Variable resolution through [`get_referenced`](ContextLike::get_referenced) and [`set_referenced`](ContextLike::set_referenced)
68/// - Function dispatch through [`function_call`](ContextLike::function_call) and [`method_call`](ContextLike::method_call)
69/// - Closure parameter management through [`start_closure`](ContextLike::start_closure), [`set_key`](ContextLike::set_key), [`set_value`](ContextLike::set_value), and [`end_closure`](ContextLike::end_closure)
70/// - Inference operations through [`inference_call`](ContextLike::inference_call)
71///
72/// See the [`Context`] struct for a complete implementation that supports
73/// workflow-based evaluation with proper scoping and caching.
74pub trait ContextLike {
75
76 /// Start a new closure and save the current stack.
77 fn start_closure(&mut self) -> [(String, Value); 2];
78
79 /// Set the key of the indexed mapped variable used by element-wise functions like `map`.
80 ///
81 /// # Arguments
82 ///
83 /// * `index` - The index key to set (0 or 1)
84 /// * `identifier` - The key to set
85 fn set_key(&mut self, index: usize, identifier: &str);
86
87 /// Set the value of the index mapped variable used by element-wise functions like `map`.
88 ///
89 /// # Arguments
90 ///
91 /// * `index` - The index value to set (0 or 1)
92 /// * `value` - The value to set
93 fn set_value(&mut self, index: usize, value: &Value);
94
95 /// End the closure and restore the previous stack.
96 fn end_closure(&mut self, stack: [(String, Value); 2]);
97
98 /// Get the value of a referenced variable or expression.
99 ///
100 /// # Arguments
101 ///
102 /// * `reference` - The reference to resolve
103 ///
104 /// # Returns
105 ///
106 /// Returns the value of the referenced variable or an error if the reference
107 /// cannot be resolved.
108 fn get_referenced(&mut self, reference: &Reference) -> Result<Value>;
109
110 /// Set the value of a referenced variable.
111 ///
112 /// # Arguments
113 ///
114 /// * `reference` - The reference to set
115 /// * `value` - The value to assign
116 ///
117 /// # Returns
118 ///
119 /// Returns `Ok(())` on success or an error if the referenced variable cannot be set.
120 fn set_referenced(&mut self, reference: &Reference, value: Value) -> Result<()>;
121
122 /// Call a standalone function.
123 ///
124 /// # Arguments
125 /// * `name` - The name of the function to call
126 /// * `arg` - The argument(s) to pass to the function (Value can be Empty, Literal, or Array)
127 ///
128 /// # Returns
129 ///
130 /// Returns the result of the function call or an error if the function is not found
131 /// or if there's an error during execution.
132 fn function_call(&mut self, name: &str, arg: Value) -> Result<Value>;
133
134 /// Call a method on a value.
135 ///
136 /// # Arguments
137 /// * `name` - The name of the method to call
138 /// * `value` - The value on which to call the method
139 /// * `arg` - The argument(s) to pass to the method (Value can be Empty, Literal, or Array)
140 ///
141 /// # Returns
142 ///
143 /// Returns the result of the method call or an error if the method is not found
144 /// or if there's an error during execution.
145 fn method_call(&mut self, name: &str, value: Value, arg: Value) -> Result<Value>;
146
147 /// Run inference on a referenced workflow.
148 ///
149 /// # Arguments
150 /// * `reference` - The workflow reference
151 /// * `arg` - The argument(s) to pass to the inference call (Value can be Empty, Literal, or Array)
152 ///
153 /// # Returns
154 ///
155 /// Returns the result of the inference call or an error if the workflow is not found
156 /// or if there's an error during execution.
157 fn inference_call(&mut self, reference: &Reference, arg: Value) -> Result<Value>;
158}
159
160enum Stack {
161 Reader(Reference, Arc<Workflow>),
162 Writer(Reference, Arc<Workflow>, Workflow),
163}
164
165/// A default context for evaluating expressions within AIM workflows.
166///
167/// The `Context` struct provides a complete implementation of the [`ContextLike`] trait
168/// that integrates with the AIM workflow system. It supports:
169///
170/// - Variable resolution across multi-level workflow hierarchies
171/// - Function and method dispatch through the global function registry
172/// - Closure parameter management for functional programming constructs
173/// - Caching of evaluation results within a single evaluation session
174/// - Circular reference detection to prevent infinite loops
175/// - Inference operations for agentic workflow execution
176///
177/// # Workflow Integration
178///
179/// The context maintains references to workflows at different levels:
180/// - `workflow`: The current read-only workflow context
181/// - `workflow_mut`: A mutable workflow for making changes during inference
182/// - `scope`: The current reference scope for resolving relative references
183///
184/// When evaluating expressions within workflows, the context automatically handles:
185/// - Multi-part reference resolution (e.g., `parent.child.value`)
186/// - Type promotion and validation for assignments
187/// - Caching of computed values within a session
188/// - Circular reference detection to prevent stack overflows
189///
190/// # Thread Safety
191///
192/// Context instances are not thread-safe and should not be shared between threads.
193/// Each evaluation session should use its own context instance.
194///
195/// # Examples
196///
197/// Basic usage with predefined values:
198///
199/// ```rust
200/// use aimx::{Context, Literal, Value};
201///
202/// let context = Context::new()
203/// .with_value("x", Value::Literal(Literal::Number(42.0)))
204/// .with_value("name", Value::Literal(Literal::Text("Alice".to_string())));
205/// ```
206///
207/// Evaluating expressions within workflows:
208///
209/// ```rust
210/// use aimx::{Context, aimx_parse, ExpressionLike};
211///
212/// let mut context = Context::new();
213/// // Expressions are evaluated within the context of the workspace workflows
214/// let expression = aimx_parse("some_workflow_value + 10");
215/// let result = expression.evaluate(&mut context);
216/// ```
217#[derive(Debug)]
218pub struct Context {
219 scope: Reference,
220 workflow: Arc<Workflow>,
221 workflow_mut: Option<Workflow>,
222 lock_guard: Option<Arc<Mutex<()>>>,
223 parameters: [(String, Value); 2],
224 /// Rules that have already been entered on the current call‑stack.
225 visited: HashSet<Reference>,
226 /// Caches results for the current evaluation.
227 cache: HashMap<Reference, Value>,
228}
229
230impl Context {
231 pub fn new() -> Self {
232 // Convert WorkflowLike to Workflow
233 let workflow = get_workspace()
234 .as_any()
235 .downcast_ref::<Workflow>()
236 .map(|workspace| Arc::new(workspace.clone())).unwrap();
237 Self {
238 scope: Reference::new("_"),
239 workflow,
240 workflow_mut: None,
241 lock_guard: None,
242 parameters: [
243 (String::new(), Value::Empty),
244 (String::new(), Value::Empty)
245 ],
246 visited: HashSet::new(),
247 cache: HashMap::new(),
248 }
249 }
250
251 /// Gets read-only access to the current workflow.
252 ///
253 /// Returns a clone of the Arc reference to the current workflow, allowing
254 /// read-only access to workflow data without affecting the context's ownership.
255 pub fn workflow(&self) -> Arc<Workflow> {
256 self.workflow.clone()
257 }
258
259 /// Gets mutable access to the workflow if available.
260 ///
261 /// Returns a mutable reference to the workflow if one is currently being
262 /// modified, or None if only read-only access is available.
263 pub fn workflow_mut(&mut self) -> Option<&mut Workflow> {
264 self.workflow_mut.as_mut()
265 }
266
267 /// Sets mutable access to a workflow.
268 ///
269 /// This method is used to provide a workflow that can be modified during
270 /// evaluation, typically when performing inference operations that need
271 /// to update workflow values. Note that this requires appropriate write
272 /// permissions from the lock manager.
273 ///
274 /// # Arguments
275 ///
276 /// * `workflow` - The workflow to make mutable
277 pub fn set_workflow_mut(&mut self, workflow: Workflow) {
278 self.workflow_mut = Some(workflow);
279 }
280
281 /// Clears mutable workflow access.
282 ///
283 /// This method releases the mutable workflow reference, reverting to
284 /// read-only access for subsequent operations.
285 pub fn clear_workflow_mut(&mut self) {
286 self.workflow_mut = None;
287 }
288
289 /// Begin a writable session on the current workflow.
290 ///
291 /// This method acquires a write lock on the current workflow scope and
292 /// prepares the context for making modifications. It's typically used
293 /// when an inference operation needs to update workflow values.
294 pub fn writable_start(&mut self) {
295 if self.workflow_mut.is_none() {
296 let lock_manager = get_lock_manager();
297 self.lock_guard = Some(lock_manager.acquire_workflow_lock(self.scope.clone()));
298 let workflow = self.workflow.clone();
299 self.workflow_mut = Some((*workflow).clone());
300 }
301 }
302
303 /// End a writable session and commit changes.
304 ///
305 /// This method releases the write lock and commits any changes made to
306 /// the workflow during the writable session. The modified workflow is
307 /// atomically replaced with the new version.
308 pub fn writable_end(&mut self) {
309 // Atomically replace the old node workflow with the new workflow.
310 if let Ok((_, node, _)) = self.dereference(&self.scope) {
311 if let Some(workflow_mut) = self.workflow_mut.take() {
312 node.set_workflow(workflow_mut);
313 }
314 }
315 // Release the lock by taking ownership and letting it drop
316 let _ = self.lock_guard.take();
317 }
318
319
320 /// Start a new evaluation session.
321 ///
322 /// This method clears the visited set and cache, preparing the context
323 /// for a new evaluation session. It's important to call this method
324 /// when starting a new independent evaluation to avoid conflicts with
325 /// cached values from previous evaluations.
326 pub fn new_session(&mut self) {
327 self.visited.clear();
328 self.cache.clear();
329 }
330
331 fn inference_start(&mut self, scope: Reference) -> Stack {
332 let old_scope = replace(&mut self.scope, scope);
333 if let Some(workflow_mut) = self.workflow_mut.take() {
334 Stack::Writer(old_scope, self.workflow.clone(), workflow_mut)
335 }
336 else {
337 Stack::Reader(old_scope, self.workflow.clone())
338 }
339 }
340
341 fn inference_end(&mut self, stack: Stack) {
342 match stack {
343 Stack::Reader(scope, workflow) => {
344 self.scope = scope;
345 self.workflow = workflow
346 }
347 Stack::Writer(scope, workflow, workflow_mut) => {
348 self.scope = scope;
349 self.workflow = workflow;
350 self.workflow_mut = Some(workflow_mut)
351 }
352 }
353 }
354
355 /// Add a predefined value to the context for testing purposes.
356 ///
357 /// This method creates a rule with the given identifier and value and adds it
358 /// to the workspace. This is primarily used for testing expressions that reference
359 /// predefined variables.
360 ///
361 /// # Arguments
362 ///
363 /// * `identifier` - The identifier name for the value
364 /// * `value` - The value to associate with the identifier
365 ///
366 /// # Returns
367 ///
368 /// Returns the modified context for method chaining.
369 pub fn with_value(mut self, identifier: &str, value: Value) -> Self {
370 let typedef = value.get_type().unwrap_or(Typedef::Any);
371 let rule = Rule::new(identifier.to_string(), typedef, Expression::Empty, value);
372
373 // Try mutable workflow first
374 if let Some(ref mut workflow) = self.workflow_mut {
375 workflow.append_or_update(Some(rule));
376 }
377 // Otherwise try read-only workflow
378 else {
379 // Clone the workflow for modification
380 let mut workflow_clone = (*self.workflow).clone();
381 workflow_clone.append_or_update(Some(rule));
382 self.workflow = Arc::new(workflow_clone);
383 }
384 self
385 }
386
387 // Dereference the Rule and return a normalized reference to it.
388 fn normalize(&mut self, reference: &Reference) -> Result<Reference> {
389 match reference {
390 Reference::One(one) => {
391 // Try writable workflow
392 if let Some(workflow_mut) = &self.workflow_mut {
393 if workflow_mut.get_rule(one).is_some() {
394 return self.scope.normalize(reference)
395 }
396 }
397 // Try read-only workflow
398 if self.workflow.get_rule(one).is_some() {
399 return self.scope.normalize(reference)
400 }
401 // Try workspace
402 if get_workspace().get_rule(one).is_some() {
403 return Ok(reference.clone())
404 }
405 }
406 Reference::Two(one, two) => {
407 // Try writable workflow
408 if let Some(workflow_mut) = &self.workflow_mut {
409 if let Some(rule) = &workflow_mut.get_rule(one) {
410 if let Some(node) = rule.get_node() {
411 if node.get_workflow_like().get_rule(two).is_some() {
412 return self.scope.normalize(reference)
413 }
414 }
415 }
416 }
417 // Try read-only workflow
418 if let Some(rule) = self.workflow.get_rule(one) {
419 if let Some(node) = rule.get_node() {
420 if node.get_workflow_like().get_rule(two).is_some() {
421 return self.scope.normalize(reference)
422 }
423 }
424 }
425 // Try workspace
426 if let Some(rule) = &get_workspace().get_rule(one) {
427 if let Some(node) = rule.get_node() {
428 if node.get_workflow_like().get_rule(two).is_some() {
429 return Ok(reference.clone())
430 }
431 }
432 }
433 }
434 Reference::Three(one, two, three) => {
435 if let Some(rule) = &get_workspace().get_rule(one) {
436 if let Some(node) = rule.get_node() {
437 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
438 if let Some(node) = rule.get_node() {
439 if node.get_workflow_like().get_rule(three).is_some() {
440 return Ok(reference.clone())
441 }
442 }
443 }
444 }
445 }
446 }
447 }
448 Err(anyhow!("Undefined locator ~{}", reference))
449 }
450
451 fn dereference_and_evaluate(&mut self, reference: &Reference) -> Result<Value> {
452 match reference {
453 Reference::One(one) => {
454 // Try writable workflow
455 if let Some(workflow) = &self.workflow_mut {
456 if let Some(rule) = &workflow.get_rule(one) {
457 return rule.evaluate(self);
458 }
459 }
460 // Try read-only workflow
461 if let Some(rule) = self.workflow.get_rule(one) {
462 return rule.evaluate(self);
463 }
464 // Try workspace
465 if let Some(rule) = &get_workspace().get_rule(one) {
466 return rule.evaluate(self);
467 }
468 }
469 Reference::Two(one, two) => {
470 // Try writable workflow
471 if let Some(workflow) = &self.workflow_mut {
472 if let Some(rule) = &workflow.get_rule(one) {
473 if let Some(node) = rule.get_node() {
474 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
475 return rule.evaluate(self);
476 }
477 }
478 }
479 }
480 // Try read-only workflow
481 if let Some(rule) = self.workflow.get_rule(one) {
482 if let Some(node) = rule.get_node() {
483 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
484 return rule.evaluate(self);
485 }
486 }
487 }
488 // Try workspace
489 if let Some(rule) = &get_workspace().get_rule(one) {
490 if let Some(node) = rule.get_node() {
491 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
492 return rule.evaluate(self);
493 }
494 }
495 }
496 }
497 Reference::Three(one, two, three) => {
498 if let Some(rule) = &get_workspace().get_rule(one) {
499 if let Some(node) = rule.get_node() {
500 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
501 if let Some(node) = rule.get_node() {
502 if let Some(rule) = &node.get_workflow_like().get_rule(three) {
503 return rule.evaluate(self);
504 }
505 }
506 }
507 }
508 }
509 }
510 }
511 Err(anyhow!("Undefined locator ~{}", reference))
512 }
513
514 fn dereference_typedef(&mut self, reference: &Reference) -> Result<(Reference, Typedef)> {
515 match reference {
516 Reference::One(one) => {
517 // Try writable workflow
518 if let Some(workflow) = &self.workflow_mut {
519 if let Some(rule) = &workflow.get_rule(one) {
520 let normalized = self.scope.normalize(reference)?;
521 return Ok((normalized, rule.typedef().clone()));
522 }
523 }
524 // Try read-only workflow
525 if let Some(rule) = self.workflow.get_rule(one) {
526 let normalized = self.scope.normalize(reference)?;
527 return Ok((normalized, rule.typedef().clone()));
528 }
529 // Try workspace
530 if let Some(rule) = &get_workspace().get_rule(one) {
531 return Ok((reference.clone(), rule.typedef().clone()));
532 }
533 }
534 Reference::Two(one, two) => {
535 // Try writable workflow
536 if let Some(workflow) = &self.workflow_mut {
537 if let Some(rule) = &workflow.get_rule(one) {
538 if let Some(node) = rule.get_node() {
539 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
540 let normalized = self.scope.normalize(reference)?;
541 return Ok((normalized, rule.typedef().clone()));
542 }
543 }
544 }
545 }
546 // Try read-only workflow
547 if let Some(rule) = self.workflow.get_rule(one) {
548 if let Some(node) = rule.get_node() {
549 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
550 let normalized = self.scope.normalize(reference)?;
551 return Ok((normalized, rule.typedef().clone()));
552 }
553 }
554 }
555 // Try workspace
556 if let Some(rule) = &get_workspace().get_rule(one) {
557 if let Some(node) = rule.get_node() {
558 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
559 return Ok((reference.clone(), rule.typedef().clone()));
560 }
561 }
562 }
563 }
564 Reference::Three(one, two, three) => {
565 if let Some(rule) = &get_workspace().get_rule(one) {
566 if let Some(node) = rule.get_node() {
567 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
568 if let Some(node) = rule.get_node() {
569 if let Some(rule) = &node.get_workflow_like().get_rule(three) {
570 return Ok((reference.clone(), rule.typedef().clone()));
571 }
572 }
573 }
574 }
575 }
576 }
577 }
578 Err(anyhow!("Undefined locator ~{}", reference))
579 }
580
581 // Dereference the Node and return a normalized reference to it.
582 fn dereference(&self, reference: &Reference) -> Result<(Reference, Node, bool)> {
583 match reference {
584 Reference::One(one) => {
585 // Try writable workflow
586 if let Some(workflow) = &self.workflow_mut {
587 if let Some(rule) = &workflow.get_rule(one) {
588 if let Some(node) = rule.get_node() {
589 let normalized = self.scope.normalize(reference)?;
590 return Ok((normalized, node, true));
591 }
592 }
593 }
594 // Try read-only workflow
595 if let Some(rule) = self.workflow.get_rule(one) {
596 if let Some(node) = rule.get_node() {
597 let normalized = self.scope.normalize(reference)?;
598 return Ok((normalized, node, true));
599 }
600 }
601 // Try workspace
602 if let Some(rule) = &get_workspace().get_rule(one) {
603 if let Some(node) = rule.get_node() {
604 return Ok((reference.clone(), node, false));
605 }
606 }
607 }
608 Reference::Two(one, two) => {
609 // Try writable workflow
610 if let Some(workflow) = &self.workflow_mut {
611 if let Some(rule) = &workflow.get_rule(one) {
612 if let Some(node) = rule.get_node() {
613 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
614 if let Some(node) = rule.get_node() {
615 let normalized = self.scope.normalize(reference)?;
616 return Ok((normalized, node, true));
617 }
618 }
619 }
620 }
621 }
622 // Try read-only workflow
623 if let Some(rule) = self.workflow.get_rule(one) {
624 if let Some(node) = rule.get_node() {
625 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
626 if let Some(node) = rule.get_node() {
627 let normalized = self.scope.normalize(reference)?;
628 return Ok((normalized, node, true));
629 }
630 }
631 }
632 }
633 // Try workspace
634 if let Some(rule) = &get_workspace().get_rule(one) {
635 if let Some(node) = rule.get_node() {
636 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
637 if let Some(node) = rule.get_node() {
638 return Ok((reference.clone(), node, false));
639 }
640 }
641 }
642 }
643 }
644 Reference::Three(one, two, three) => {
645 if let Some(rule) = &get_workspace().get_rule(one) {
646 if let Some(node) = rule.get_node() {
647 if let Some(rule) = &node.get_workflow_like().get_rule(two) {
648 if let Some(node) = rule.get_node() {
649 if let Some(rule) = &node.get_workflow_like().get_rule(three) {
650 if let Some(node) = rule.get_node() {
651 return Ok((reference.clone(), node, false));
652 }
653 }
654 }
655 }
656 }
657 }
658 }
659 }
660 Err(anyhow!("Undefined locator {} within {} scope~{}", reference, &self.scope, reference))
661 }
662}
663
664impl ContextLike for Context {
665
666 /// Start a new closure and save the current parameter stack.
667 fn start_closure(&mut self) -> [(String, Value); 2] {
668 replace(&mut self.parameters, [
669 (String::new(), Value::Empty),
670 (String::new(), Value::Empty)
671 ])
672 }
673
674 /// Set the key of the indexed mapped variable used by element-wise functions like `map`.
675 ///
676 /// # Arguments
677 ///
678 /// * `index` - The index key to set (0 or 1)
679 /// * `identifier` - The key to set
680 fn set_key(&mut self, index: usize, identifier: &str) {
681 if self.parameters[index].0 != *identifier {
682 self.parameters[index].0 = identifier.to_string();
683 }
684 }
685
686 /// Set the value of the index mapped variable used by element-wise functions like `map`.
687 ///
688 /// # Arguments
689 ///
690 /// * `index` - The index value to set (0 or 1)
691 /// * `value` - The value to set
692 fn set_value(&mut self, index: usize, value: &Value) {
693 self.parameters[index].1 = value.clone();
694 }
695
696 /// End the closure and restore the previous parameter stack.
697 fn end_closure(&mut self, stack: [(String, Value); 2]) {
698 self.parameters = stack;
699 }
700 /// Get the value of a referenced variable.
701 ///
702 /// This implementation only supports single-part references (default identifiers).
703 /// Multi-part references (e.g., `object.property`) are not supported in this
704 /// simplified context.
705 ///
706 /// # Arguments
707 ///
708 /// * `reference` - The reference to resolve
709 ///
710 /// # Returns
711 ///
712 /// Returns a `Result<Value>` containing the referenced value or an error
713 /// if the reference cannot be resolved.
714 fn get_referenced(&mut self, reference: &Reference) -> Result<Value> {
715 // Check closure parameters first
716 if *reference == self.parameters[0].0 {
717 Ok(self.parameters[0].1.clone())
718 } else if *reference == self.parameters[1].0 {
719 Ok(self.parameters[1].1.clone())
720 } else {
721 // Get a normalized reference for the cache key
722 let normalized = self.normalize(reference)?;
723 // 1️⃣ Cycle detection
724 if !self.visited.insert(normalized.clone()) {
725 // The key was already present → we are revisiting this rule on the stack.
726 return Err(anyhow!("Circular reference detected ~{}", normalized));
727 }
728 // 2️⃣ Cache lookup – if the value has already been computed in this run,
729 // just return the cached copy.
730 if let Some(cached) = self.cache.get(&normalized) {
731 // Pop the visited entry before returning.
732 self.visited.remove(&normalized);
733 return Ok(cached.clone());
734 }
735 // 3️⃣ Dereference and evaluate the rule
736 match self.dereference_and_evaluate(reference) {
737 Ok(value) => {
738 // 4️⃣ Clean up the visited reference (pop stack frame)
739 self.visited.remove(&normalized);
740 // 5️⃣ Cache value for the rest of this evaluation.
741 self.cache.insert(normalized, value.clone());
742 Ok(value)
743 }
744 Err(e) => {
745 self.visited.remove(&normalized);
746 Err(e)
747 }
748 }
749 }
750 }
751
752 /// Set the value of a referenced variable.
753 ///
754 /// # Arguments
755 ///
756 /// * `reference` - The reference to set
757 /// * `literal` - The value to assign
758 ///
759 /// # Returns
760 ///
761 /// Returns `Ok(())` if the referenced value was set or an error if the
762 /// reference cannot be resolved.
763 fn set_referenced(&mut self, reference: &Reference, value: Value) -> Result<()> {
764 // Check closure parameters first
765 if *reference == self.parameters[0].0 {
766 self.parameters[0].1 = value;
767 Ok(())
768 } else if *reference == self.parameters[1].0 {
769 self.parameters[1].1 = value;
770 Ok(())
771 } else {
772 // Find the referenced rule's typedef
773 match self.dereference_typedef(reference) {
774 Ok((normalized, typedef)) => {
775 // Promote type
776 let value = match value.to_type(&typedef) {
777 Ok(v) => v,
778 Err(e) => return Err(anyhow!("{}~{}", e, reference))
779 };
780 match reference {
781 Reference::One(one) => {
782 // Try mutable workflow first
783 if let Some(workflow_mut) = self.workflow_mut() {
784 if let Some(rule) = workflow_mut.get_rule_mut(one) {
785 return rule.set_value(value);
786 }
787 }
788 /* NOTE: I don't think we want to modify other workflows here
789 Try read-only workflow (clone and modify)
790 else if let Some(workflow) = self.workflow.as_ref() {
791 let mut workflow_clone = (**workflow).clone();
792 if let Some(rule) = workflow_clone.get_rule_mut(one) {
793 rule.set_value(value)?;
794 // Update the workflow in context
795 self.workflow = Some(Arc::new(workflow_clone));
796 return Ok(());
797 }
798 } */
799 }
800 _ => {}
801 }
802 // Cache the referenced assignment instead
803 self.cache.insert(normalized, value);
804 Ok(())
805 }
806 Err(e) => Err(e),
807 }
808 }
809 }
810
811 /// Call a standalone function.
812 ///
813 /// Delegates function calls to the singleton function registry.
814 ///
815 /// # Arguments
816 ///
817 /// * `name` - The name of the function to call
818 /// * `arg` - The argument(s) to pass to the function
819 ///
820 /// # Returns
821 ///
822 /// Returns a `Result<Value>` containing the function result or an error
823 /// if the function is not found or execution fails.
824 fn function_call(&mut self, name: &str, arg: Value) -> Result<Value> {
825 let registry = FunctionRegistry::read_lock()?;
826 registry.function_call(self, name, arg)
827 }
828
829 /// Call a method on a value.
830 ///
831 /// Delegates method calls to the singleton function registry.
832 ///
833 /// # Arguments
834 ///
835 /// * `name` - The name of the method to call
836 /// * `value` - The value on which to call the method
837 /// * `arg` - The argument(s) to pass to the method
838 ///
839 /// # Returns
840 ///
841 /// Returns a `Result<Value>` containing the method result or an error
842 /// if the method is not found or execution fails.
843 fn method_call(&mut self, name: &str, value: Value, arg: Value) -> Result<Value> {
844 let registry = FunctionRegistry::read_lock()?;
845 registry.method_call(self, name, value, arg)
846 }
847
848 fn inference_call(&mut self, reference: &Reference, _arg: Value) -> Result<Value> {
849 let (scope, node, is_descendent) = self.dereference(reference)?;
850 if &self.scope == &scope {
851 return Err(anyhow!("Circular inference detected ~{}", scope));
852 }
853
854 // 1️⃣ Acquire file-specific write lock ONLY when this is a descendant workflow
855 let lock_manager = get_lock_manager();
856 let _lock_guard = if is_descendent {
857 Some(lock_manager.acquire_workflow_lock(scope.clone()))
858 } else {
859 None
860 };
861
862 // 2️⃣ Push current state onto stack and switch to the new node workflow
863 let stack = self.inference_start(scope);
864 // If this is a descendant workflow we will write to it
865 self.workflow_mut = if is_descendent {
866 // Clone a writable workflow.
867 Some(node.get_workflow_mut())
868 } else {
869 None
870 };
871 // Get a reference to the read-only workflow.
872 self.workflow = node.get_workflow().clone();
873
874 // 3️⃣ Assign arguments
875 // TODO add argument list to node?
876
877 // Create a provider for Ollama
878 let config = get_config().read().unwrap();
879 let provider = config.get_provider();
880
881 // 4️⃣ Generate prompt
882 if let Ok((system_prompt, user_prompt))
883 = generate_prompt(self.workflow.clone(), &provider.capability) {
884
885 // 5️⃣ Send inference request
886 match send_request(&provider, &system_prompt, &user_prompt) {
887 Ok(inference) => {
888 if let Some(responses) = parse_response(inference.text()) {
889 // 6️⃣ Validate response
890 let _ = validate_responses(self.workflow.clone(), self, responses);
891 }
892 //let input = inference.finish();
893 }
894 _ => {}
895 }
896 }
897
898 // 7️⃣ Evaluate workflow expressions
899 node.get_workflow().evaluate(self);
900
901 // 8️⃣ Save changes
902 if is_descendent {
903 if let Some(workflow_mut) = &mut self.workflow_mut {
904 let _ = workflow_mut.save();
905 }
906 // Atomically replace the old node workflow with the new workflow.
907 if let Some(workflow_mut) = self.workflow_mut.take() {
908 node.set_workflow(workflow_mut);
909 }
910 }
911
912 // 9️⃣ Pop previous state from stack
913 self.inference_end(stack);
914
915 // Return results
916 Ok(Value::Empty)
917 // _lock_guard is dropped here, releasing the lock if it was acquired.
918 }
919}
920