1use indexmap::IndexMap;
7
8use crate::source::{Span, Spanned};
9
10#[derive(Debug, Clone)]
12pub enum RawTaskAction {
13 Infer(Spanned<RawInferAction>),
15
16 Exec(Spanned<RawExecAction>),
18
19 Fetch(Spanned<RawFetchAction>),
21
22 Invoke(Spanned<RawInvokeAction>),
24
25 Agent(Box<Spanned<RawAgentAction>>),
27}
28
29impl Default for RawTaskAction {
30 fn default() -> Self {
31 RawTaskAction::Infer(Spanned::dummy(RawInferAction::default()))
32 }
33}
34
35impl RawTaskAction {
36 pub fn verb_name(&self) -> &'static str {
38 match self {
39 RawTaskAction::Infer(_) => "infer",
40 RawTaskAction::Exec(_) => "exec",
41 RawTaskAction::Fetch(_) => "fetch",
42 RawTaskAction::Invoke(_) => "invoke",
43 RawTaskAction::Agent(_) => "agent",
44 }
45 }
46
47 pub fn span(&self) -> Span {
49 match self {
50 RawTaskAction::Infer(a) => a.span,
51 RawTaskAction::Exec(a) => a.span,
52 RawTaskAction::Fetch(a) => a.span,
53 RawTaskAction::Invoke(a) => a.span,
54 RawTaskAction::Agent(a) => a.span,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Default)]
61pub struct RawInferAction {
62 pub prompt: Spanned<String>,
64
65 pub system: Option<Spanned<String>>,
67
68 pub temperature: Option<Spanned<f64>>,
70
71 pub max_tokens: Option<Spanned<u32>>,
73
74 pub extended_thinking: Option<Spanned<bool>>,
76
77 pub thinking_budget: Option<Spanned<u32>>,
79
80 pub content: Option<Spanned<Vec<crate::ast::content::RawContentPart>>>,
82
83 pub response_format: Option<Spanned<String>>,
85
86 pub guardrails: Vec<crate::ast::guardrails::GuardrailConfig>,
88}
89
90#[derive(Debug, Clone, Default)]
92pub struct RawExecAction {
93 pub command: Spanned<String>,
95
96 pub shell: Option<Spanned<bool>>,
98
99 pub cwd: Option<Spanned<String>>,
101
102 pub env: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
104
105 pub timeout_ms: Option<Spanned<u64>>,
107}
108
109#[derive(Debug, Clone, Default)]
111pub struct RawFetchAction {
112 pub url: Spanned<String>,
114
115 pub method: Option<Spanned<String>>,
117
118 pub headers: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
120
121 pub body: Option<Spanned<String>>,
123
124 pub json: Option<Spanned<serde_json::Value>>,
126
127 pub timeout_ms: Option<Spanned<u64>>,
129
130 pub follow_redirects: Option<Spanned<bool>>,
132
133 pub response: Option<Spanned<String>>,
135
136 pub extract: Option<Spanned<String>>,
138
139 pub selector: Option<Spanned<String>>,
141}
142
143#[derive(Debug, Clone, Default)]
145pub struct RawInvokeAction {
146 pub tool: Option<Spanned<String>>,
149
150 pub resource: Option<Spanned<String>>,
152
153 pub params: Option<Spanned<serde_json::Value>>,
155
156 pub mcp: Option<Spanned<String>>,
158
159 pub timeout_ms: Option<Spanned<u64>>,
161}
162
163impl RawInvokeAction {
164 pub fn parse_tool_name(&self) -> Option<(Option<&str>, &str)> {
168 let tool = self.tool.as_ref()?;
169 let tool_str = &tool.value;
170 if let Some((server, name)) = tool_str.split_once("::") {
171 Some((Some(server), name))
172 } else {
173 Some((None, tool_str.as_str()))
174 }
175 }
176}
177
178#[derive(Debug, Clone, Default)]
180pub struct RawAgentAction {
181 pub prompt: Spanned<String>,
183
184 pub tools: Option<Spanned<Vec<Spanned<String>>>>,
186
187 pub max_turns: Option<Spanned<u32>>,
189
190 pub max_tokens: Option<Spanned<u32>>,
192
193 pub from: Option<Spanned<String>>,
195
196 pub skills: Option<Spanned<Vec<Spanned<String>>>>,
198
199 pub provider: Option<Spanned<String>>,
201
202 pub model: Option<Spanned<String>>,
204
205 pub mcp: Option<Spanned<Vec<Spanned<String>>>>,
207
208 pub temperature: Option<Spanned<f64>>,
210
211 pub token_budget: Option<Spanned<u32>>,
213
214 pub system: Option<Spanned<String>>,
216
217 pub extended_thinking: Option<Spanned<bool>>,
219
220 pub thinking_budget: Option<Spanned<u32>>,
222
223 pub depth_limit: Option<Spanned<u32>>,
225
226 pub tool_choice: Option<Spanned<String>>,
228
229 pub stop_sequences: Option<Spanned<Vec<Spanned<String>>>>,
231
232 pub scope: Option<Spanned<String>>,
234 pub guardrails: Vec<crate::ast::guardrails::GuardrailConfig>,
236 pub completion: Option<crate::ast::completion::CompletionConfig>,
238 pub limits: Option<crate::ast::limits::LimitsConfig>,
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245 use crate::source::FileId;
246
247 fn make_span(start: u32, end: u32) -> Span {
248 Span::new(FileId(0), start, end)
249 }
250
251 #[test]
252 fn test_action_verb_names() {
253 let infer = RawTaskAction::Infer(Spanned::dummy(RawInferAction::default()));
254 assert_eq!(infer.verb_name(), "infer");
255
256 let exec = RawTaskAction::Exec(Spanned::dummy(RawExecAction::default()));
257 assert_eq!(exec.verb_name(), "exec");
258
259 let fetch = RawTaskAction::Fetch(Spanned::dummy(RawFetchAction::default()));
260 assert_eq!(fetch.verb_name(), "fetch");
261
262 let invoke = RawTaskAction::Invoke(Spanned::dummy(RawInvokeAction::default()));
263 assert_eq!(invoke.verb_name(), "invoke");
264
265 let agent = RawTaskAction::Agent(Box::new(Spanned::dummy(RawAgentAction::default())));
266 assert_eq!(agent.verb_name(), "agent");
267 }
268
269 #[test]
270 fn test_invoke_parse_tool_name() {
271 let simple = RawInvokeAction {
273 tool: Some(Spanned::new("my_tool".to_string(), make_span(0, 7))),
274 ..Default::default()
275 };
276 let (server, name) = simple.parse_tool_name().unwrap();
277 assert_eq!(server, None);
278 assert_eq!(name, "my_tool");
279
280 let qualified = RawInvokeAction {
282 tool: Some(Spanned::new("novanet::query".to_string(), make_span(0, 14))),
283 ..Default::default()
284 };
285 let (server, name) = qualified.parse_tool_name().unwrap();
286 assert_eq!(server, Some("novanet"));
287 assert_eq!(name, "query");
288 }
289
290 #[test]
291 fn test_infer_action_fields() {
292 let infer = RawInferAction {
293 prompt: Spanned::new("Hello, world!".to_string(), make_span(0, 13)),
294 temperature: Some(Spanned::new(0.7, make_span(20, 23))),
295 max_tokens: Some(Spanned::new(1000, make_span(30, 34))),
296 ..Default::default()
297 };
298
299 assert_eq!(infer.prompt.value, "Hello, world!");
300 assert_eq!(infer.temperature.as_ref().unwrap().value, 0.7);
301 assert_eq!(infer.max_tokens.as_ref().unwrap().value, 1000);
302 }
303}