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 pub error: Option<String>,
80 pub duration_ms: u64,
81}
82
83impl ToolCallResult {
84 pub const fn success(
85 tool_name: String,
86 arguments: Value,
87 output: Value,
88 duration_ms: u64,
89 ) -> Self {
90 Self {
91 tool_name,
92 arguments,
93 success: true,
94 output,
95 error: None,
96 duration_ms,
97 }
98 }
99
100 pub fn failure(
101 tool_name: String,
102 arguments: Value,
103 error: impl Into<String>,
104 duration_ms: u64,
105 ) -> Self {
106 Self {
107 tool_name,
108 arguments,
109 success: false,
110 output: Value::Null,
111 error: Some(error.into()),
112 duration_ms,
113 }
114 }
115}
116
117#[derive(Debug, Clone, Default, Serialize, Deserialize)]
118pub struct ExecutionState {
119 pub results: Vec<ToolCallResult>,
120 pub halted: bool,
121 pub halt_reason: Option<String>,
122}
123
124impl ExecutionState {
125 pub fn new() -> Self {
126 Self::default()
127 }
128
129 pub fn add_result(&mut self, result: ToolCallResult) {
130 if !result.success && !self.halted {
131 self.halted = true;
132 self.halt_reason.clone_from(&result.error);
133 }
134 self.results.push(result);
135 }
136
137 pub fn successful_results(&self) -> Vec<&ToolCallResult> {
138 self.results.iter().filter(|r| r.success).collect()
139 }
140
141 pub fn failed_results(&self) -> Vec<&ToolCallResult> {
142 self.results.iter().filter(|r| !r.success).collect()
143 }
144
145 pub fn total_duration_ms(&self) -> u64 {
146 self.results.iter().map(|r| r.duration_ms).sum()
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct TemplateRef {
152 pub tool_index: usize,
153 pub field_path: Vec<String>,
154}
155
156impl TemplateRef {
157 pub fn parse(template: &str) -> Option<Self> {
158 let re = Regex::new(r"^\$(\d+)\.output\.(.+)$").ok()?;
159 let caps = re.captures(template)?;
160
161 let tool_index = caps.get(1)?.as_str().parse().ok()?;
162 let path = caps.get(2)?.as_str();
163 let field_path = path.split('.').map(String::from).collect();
164
165 Some(Self {
166 tool_index,
167 field_path,
168 })
169 }
170
171 pub fn format(&self) -> String {
172 format!("${}.output.{}", self.tool_index, self.field_path.join("."))
173 }
174}