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    // JSON: MCP tool-call arguments walked for `$N.output.path` templates.
16    pub fn resolve_arguments(arguments: &Value, results: &[ToolCallResult]) -> Value {
17        Self::resolve_value(arguments, results)
18    }
19
20    // JSON: MCP tool-call arguments walked for `$N.output.path` templates.
21    fn resolve_value(value: &Value, results: &[ToolCallResult]) -> Value {
22        match value {
23            Value::String(s) if s.starts_with('$') && s.contains(".output.") => {
24                Self::resolve_template(s, results)
25            },
26            Value::Array(arr) => Value::Array(
27                arr.iter()
28                    .map(|v| Self::resolve_value(v, results))
29                    .collect(),
30            ),
31            Value::Object(obj) => Value::Object(
32                obj.iter()
33                    .map(|(k, v)| (k.clone(), Self::resolve_value(v, results)))
34                    .collect(),
35            ),
36            Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_) => value.clone(),
37        }
38    }
39
40    // JSON: MCP tool-call arguments walked for `$N.output.path` templates.
41    fn resolve_template(template: &str, results: &[ToolCallResult]) -> Value {
42        let Some(template_ref) = TemplateRef::parse(template) else {
43            return Value::String(template.to_owned());
44        };
45
46        let Some(result) = results.get(template_ref.tool_index) else {
47            return Value::Null;
48        };
49
50        Self::get_nested_value(&result.output, &template_ref.field_path)
51    }
52
53    // JSON: MCP tool-call arguments walked for `$N.output.path` templates.
54    fn get_nested_value(value: &Value, path: &[String]) -> Value {
55        let mut current = value;
56        for segment in path {
57            match current.get(segment) {
58                Some(v) => current = v,
59                None => return Value::Null,
60            }
61        }
62        current.clone()
63    }
64}