Skip to main content

systemprompt_models/ai/
template_resolver.rs

1//! Resolves `{{tool.result}}`-style templates against prior tool-call results.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use super::execution_plan::{TemplateRef, ToolCallResult};
10
11#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
12pub struct TemplateResolver;
13
14impl TemplateResolver {
15    pub fn resolve_arguments(arguments: &Value, results: &[ToolCallResult]) -> Value {
16        Self::resolve_value(arguments, results)
17    }
18
19    fn resolve_value(value: &Value, results: &[ToolCallResult]) -> Value {
20        match value {
21            Value::String(s) if s.starts_with('$') && s.contains(".output.") => {
22                Self::resolve_template(s, results)
23            },
24            Value::Array(arr) => Value::Array(
25                arr.iter()
26                    .map(|v| Self::resolve_value(v, results))
27                    .collect(),
28            ),
29            Value::Object(obj) => Value::Object(
30                obj.iter()
31                    .map(|(k, v)| (k.clone(), Self::resolve_value(v, results)))
32                    .collect(),
33            ),
34            Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => value.clone(),
35        }
36    }
37
38    fn resolve_template(template: &str, results: &[ToolCallResult]) -> Value {
39        let Some(template_ref) = TemplateRef::parse(template) else {
40            return Value::String(template.to_owned());
41        };
42
43        let Some(result) = results.get(template_ref.tool_index) else {
44            return Value::Null;
45        };
46
47        Self::get_nested_value(&result.output, &template_ref.field_path)
48    }
49
50    fn get_nested_value(value: &Value, path: &[String]) -> Value {
51        let mut current = value;
52        for segment in path {
53            match current.get(segment) {
54                Some(v) => current = v,
55                None => return Value::Null,
56            }
57        }
58        current.clone()
59    }
60}