use indexmap::IndexMap;
use crate::source::{Span, Spanned};
#[derive(Debug, Clone)]
pub enum RawTaskAction {
Infer(Spanned<RawInferAction>),
Exec(Spanned<RawExecAction>),
Fetch(Spanned<RawFetchAction>),
Invoke(Spanned<RawInvokeAction>),
Agent(Spanned<RawAgentAction>),
}
impl Default for RawTaskAction {
fn default() -> Self {
RawTaskAction::Infer(Spanned::dummy(RawInferAction::default()))
}
}
impl RawTaskAction {
pub fn verb_name(&self) -> &'static str {
match self {
RawTaskAction::Infer(_) => "infer",
RawTaskAction::Exec(_) => "exec",
RawTaskAction::Fetch(_) => "fetch",
RawTaskAction::Invoke(_) => "invoke",
RawTaskAction::Agent(_) => "agent",
}
}
pub fn span(&self) -> Span {
match self {
RawTaskAction::Infer(a) => a.span,
RawTaskAction::Exec(a) => a.span,
RawTaskAction::Fetch(a) => a.span,
RawTaskAction::Invoke(a) => a.span,
RawTaskAction::Agent(a) => a.span,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RawInferAction {
pub prompt: Spanned<String>,
pub system: Option<Spanned<String>>,
pub temperature: Option<Spanned<f64>>,
pub max_tokens: Option<Spanned<u32>>,
pub thinking: Option<Spanned<bool>>,
pub thinking_budget: Option<Spanned<u32>>,
pub content: Option<Spanned<Vec<crate::ast::content::RawContentPart>>>,
pub response_format: Option<Spanned<String>>,
pub guardrails: Vec<crate::ast::guardrails::GuardrailConfig>,
}
#[derive(Debug, Clone, Default)]
pub struct RawExecAction {
pub command: Spanned<String>,
pub shell: Option<Spanned<bool>>,
pub working_dir: Option<Spanned<String>>,
pub env: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
pub timeout_ms: Option<Spanned<u64>>,
}
#[derive(Debug, Clone, Default)]
pub struct RawFetchAction {
pub url: Spanned<String>,
pub method: Option<Spanned<String>>,
pub headers: Option<Spanned<IndexMap<Spanned<String>, Spanned<String>>>>,
pub body: Option<Spanned<String>>,
pub json: Option<Spanned<serde_json::Value>>,
pub timeout_ms: Option<Spanned<u64>>,
pub follow_redirects: Option<Spanned<bool>>,
pub response: Option<Spanned<String>>,
pub extract: Option<Spanned<String>>,
pub selector: Option<Spanned<String>>,
}
#[derive(Debug, Clone, Default)]
pub struct RawInvokeAction {
pub tool: Spanned<String>,
pub params: Option<Spanned<serde_json::Value>>,
pub mcp: Option<Spanned<String>>,
pub timeout_ms: Option<Spanned<u64>>,
}
impl RawInvokeAction {
pub fn parse_tool_name(&self) -> (Option<&str>, &str) {
let tool = &self.tool.value;
if let Some((server, name)) = tool.split_once("::") {
(Some(server), name)
} else {
(None, tool.as_str())
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RawAgentAction {
pub prompt: Spanned<String>,
pub tools: Option<Spanned<Vec<Spanned<String>>>>,
pub max_iterations: Option<Spanned<u32>>,
pub max_tokens: Option<Spanned<u32>>,
pub from: Option<Spanned<String>>,
pub skills: Option<Spanned<Vec<Spanned<String>>>>,
pub provider: Option<Spanned<String>>,
pub model: Option<Spanned<String>>,
pub mcp: Option<Spanned<Vec<Spanned<String>>>>,
pub temperature: Option<Spanned<f64>>,
pub token_budget: Option<Spanned<u32>>,
pub system: Option<Spanned<String>>,
pub extended_thinking: Option<Spanned<bool>>,
pub thinking_budget: Option<Spanned<u32>>,
pub depth_limit: Option<Spanned<u32>>,
pub tool_choice: Option<Spanned<String>>,
pub stop_sequences: Option<Spanned<Vec<Spanned<String>>>>,
pub scope: Option<Spanned<String>>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::source::FileId;
fn make_span(start: u32, end: u32) -> Span {
Span::new(FileId(0), start, end)
}
#[test]
fn test_action_verb_names() {
let infer = RawTaskAction::Infer(Spanned::dummy(RawInferAction::default()));
assert_eq!(infer.verb_name(), "infer");
let exec = RawTaskAction::Exec(Spanned::dummy(RawExecAction::default()));
assert_eq!(exec.verb_name(), "exec");
let fetch = RawTaskAction::Fetch(Spanned::dummy(RawFetchAction::default()));
assert_eq!(fetch.verb_name(), "fetch");
let invoke = RawTaskAction::Invoke(Spanned::dummy(RawInvokeAction::default()));
assert_eq!(invoke.verb_name(), "invoke");
let agent = RawTaskAction::Agent(Spanned::dummy(RawAgentAction::default()));
assert_eq!(agent.verb_name(), "agent");
}
#[test]
fn test_invoke_parse_tool_name() {
let simple = RawInvokeAction {
tool: Spanned::new("my_tool".to_string(), make_span(0, 7)),
..Default::default()
};
let (server, name) = simple.parse_tool_name();
assert_eq!(server, None);
assert_eq!(name, "my_tool");
let qualified = RawInvokeAction {
tool: Spanned::new("novanet::query".to_string(), make_span(0, 14)),
..Default::default()
};
let (server, name) = qualified.parse_tool_name();
assert_eq!(server, Some("novanet"));
assert_eq!(name, "query");
}
#[test]
fn test_infer_action_fields() {
let infer = RawInferAction {
prompt: Spanned::new("Hello, world!".to_string(), make_span(0, 13)),
temperature: Some(Spanned::new(0.7, make_span(20, 23))),
max_tokens: Some(Spanned::new(1000, make_span(30, 34))),
..Default::default()
};
assert_eq!(infer.prompt.value, "Hello, world!");
assert_eq!(infer.temperature.as_ref().unwrap().value, 0.7);
assert_eq!(infer.max_tokens.as_ref().unwrap().value, 1000);
}
}