systemprompt_models/ai/
execution_plan.rs1use regex::Regex;
13use serde::{Deserialize, Serialize};
14use serde_json::Value;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(tag = "type", rename_all = "snake_case")]
18pub enum PlanningResult {
19 DirectResponse {
20 content: String,
21 },
22 ToolCalls {
23 reasoning: String,
24 calls: Vec<PlannedToolCall>,
25 },
26}
27
28impl PlanningResult {
29 pub fn direct_response(content: impl Into<String>) -> Self {
30 Self::DirectResponse {
31 content: content.into(),
32 }
33 }
34
35 pub fn tool_calls(reasoning: impl Into<String>, calls: Vec<PlannedToolCall>) -> Self {
36 Self::ToolCalls {
37 reasoning: reasoning.into(),
38 calls,
39 }
40 }
41
42 pub const fn is_direct(&self) -> bool {
43 matches!(self, Self::DirectResponse { .. })
44 }
45
46 pub const fn is_tool_calls(&self) -> bool {
47 matches!(self, Self::ToolCalls { .. })
48 }
49
50 pub const fn tool_count(&self) -> usize {
51 match self {
52 Self::DirectResponse { .. } => 0,
53 Self::ToolCalls { calls, .. } => calls.len(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct PlannedToolCall {
60 pub tool_name: String,
61 pub arguments: Value,
62}
63
64impl PlannedToolCall {
65 pub fn new(tool_name: impl Into<String>, arguments: Value) -> Self {
66 Self {
67 tool_name: tool_name.into(),
68 arguments,
69 }
70 }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ToolCallResult {
75 pub tool_name: String,
76 pub arguments: Value,
77 pub success: bool,
78 pub output: Value,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub meta: Option<Value>,
81 pub error: Option<String>,
82 pub duration_ms: u64,
83}
84
85impl ToolCallResult {
86 pub const fn success(
87 tool_name: String,
88 arguments: Value,
89 output: Value,
90 duration_ms: u64,
91 ) -> Self {
92 Self {
93 tool_name,
94 arguments,
95 success: true,
96 output,
97 meta: None,
98 error: None,
99 duration_ms,
100 }
101 }
102
103 #[must_use]
104 pub fn with_meta(mut self, meta: Option<Value>) -> Self {
105 self.meta = meta;
106 self
107 }
108
109 pub fn failure(
110 tool_name: String,
111 arguments: Value,
112 error: impl Into<String>,
113 duration_ms: u64,
114 ) -> Self {
115 Self {
116 tool_name,
117 arguments,
118 success: false,
119 output: Value::Null,
120 meta: None,
121 error: Some(error.into()),
122 duration_ms,
123 }
124 }
125}
126
127#[derive(Debug, Clone, Default, Serialize, Deserialize)]
128pub struct ExecutionState {
129 pub results: Vec<ToolCallResult>,
130 pub halted: bool,
131 pub halt_reason: Option<String>,
132}
133
134impl ExecutionState {
135 pub fn new() -> Self {
136 Self::default()
137 }
138
139 pub fn add_result(&mut self, result: ToolCallResult) {
140 if !result.success && !self.halted {
141 self.halted = true;
142 self.halt_reason.clone_from(&result.error);
143 }
144 self.results.push(result);
145 }
146
147 pub fn successful_results(&self) -> Vec<&ToolCallResult> {
148 self.results.iter().filter(|r| r.success).collect()
149 }
150
151 pub fn failed_results(&self) -> Vec<&ToolCallResult> {
152 self.results.iter().filter(|r| !r.success).collect()
153 }
154
155 pub fn total_duration_ms(&self) -> u64 {
156 self.results.iter().map(|r| r.duration_ms).sum()
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct TemplateRef {
162 pub tool_index: usize,
163 pub field_path: Vec<String>,
164}
165
166impl TemplateRef {
167 pub fn parse(template: &str) -> Option<Self> {
168 let re = Regex::new(r"^\$(\d+)\.output\.(.+)$").ok()?;
169 let caps = re.captures(template)?;
170
171 let tool_index = caps.get(1)?.as_str().parse().ok()?;
172 let path = caps.get(2)?.as_str();
173 let field_path = path.split('.').map(String::from).collect();
174
175 Some(Self {
176 tool_index,
177 field_path,
178 })
179 }
180
181 pub fn format(&self) -> String {
182 format!("${}.output.{}", self.tool_index, self.field_path.join("."))
183 }
184}