Skip to main content

agent_orchestrator/prehook/
mod.rs

1mod cel;
2mod context;
3/// Finalize-rule and prehook evaluation helpers.
4pub mod finalize;
5
6#[cfg(test)]
7mod tests;
8
9use crate::config::{StepHookEngine, StepPrehookConfig, WorkflowFinalizeRule};
10use anyhow::Result;
11use cel_interpreter::Program;
12
13// Public API re-exports
14pub use cel::{
15    evaluate_convergence_expression, evaluate_finalize_rule_expression,
16    evaluate_step_prehook_expression, evaluate_webhook_filter,
17};
18pub use finalize::{
19    emit_item_finalize_event, emit_step_prehook_event, evaluate_step_prehook,
20    resolve_workflow_finalize_outcome,
21};
22
23/// Validates all command rules on an agent: CEL syntax and `{prompt}` placeholder.
24pub fn validate_agent_command_rules(
25    agent_id: &str,
26    rules: &[crate::config::AgentCommandRule],
27) -> Result<()> {
28    for (i, rule) in rules.iter().enumerate() {
29        let expression = rule.when.trim();
30        if expression.is_empty() {
31            anyhow::bail!(
32                "agent '{}' command_rules[{}].when cannot be empty",
33                agent_id,
34                i
35            );
36        }
37        let compiled = std::panic::catch_unwind(|| Program::compile(expression)).map_err(|_| {
38            anyhow::anyhow!(
39                "agent '{}' command_rules[{}].when caused CEL parser panic",
40                agent_id,
41                i
42            )
43        })?;
44        compiled.map_err(|err| {
45            anyhow::anyhow!(
46                "agent '{}' command_rules[{}].when is invalid CEL: {}",
47                agent_id,
48                i,
49                err
50            )
51        })?;
52        if !rule.command.contains("{prompt}") {
53            anyhow::bail!(
54                "agent '{}' command_rules[{}].command must contain {{prompt}} placeholder",
55                agent_id,
56                i
57            );
58        }
59    }
60    Ok(())
61}
62
63/// Validates one step prehook expression and its engine configuration.
64pub fn validate_step_prehook(
65    prehook: &StepPrehookConfig,
66    workflow_id: &str,
67    step_type: &str,
68) -> Result<()> {
69    let expression = prehook.when.trim();
70    if expression.is_empty() {
71        anyhow::bail!(
72            "workflow '{}' step '{}' prehook.when cannot be empty",
73            workflow_id,
74            step_type
75        );
76    }
77    match prehook.engine {
78        StepHookEngine::Cel => {
79            let compiled =
80                std::panic::catch_unwind(|| Program::compile(expression)).map_err(|_| {
81                    anyhow::anyhow!(
82                        "workflow '{}' step '{}' prehook.when caused CEL parser panic",
83                        workflow_id,
84                        step_type
85                    )
86                })?;
87            compiled.map_err(|err| {
88                anyhow::anyhow!(
89                    "workflow '{}' step '{}' prehook.when is invalid CEL: {}",
90                    workflow_id,
91                    step_type,
92                    err
93                )
94            })?;
95        }
96    }
97    Ok(())
98}
99
100/// Validates one workflow finalize rule expression and metadata.
101pub fn validate_workflow_finalize_rule(
102    rule: &WorkflowFinalizeRule,
103    workflow_id: &str,
104) -> Result<()> {
105    if rule.id.trim().is_empty() {
106        anyhow::bail!("workflow '{}' has finalize rule with empty id", workflow_id);
107    }
108    if rule.status.trim().is_empty() {
109        anyhow::bail!(
110            "workflow '{}' finalize rule '{}' has empty status",
111            workflow_id,
112            rule.id
113        );
114    }
115    let expression = rule.when.trim();
116    if expression.is_empty() {
117        anyhow::bail!(
118            "workflow '{}' finalize rule '{}' has empty when",
119            workflow_id,
120            rule.id
121        );
122    }
123    match rule.engine {
124        StepHookEngine::Cel => {
125            let compiled =
126                std::panic::catch_unwind(|| Program::compile(expression)).map_err(|_| {
127                    anyhow::anyhow!(
128                        "workflow '{}' finalize rule '{}' caused CEL parser panic",
129                        workflow_id,
130                        rule.id
131                    )
132                })?;
133            compiled.map_err(|err| {
134                anyhow::anyhow!(
135                    "workflow '{}' finalize rule '{}' invalid CEL: {}",
136                    workflow_id,
137                    rule.id,
138                    err
139                )
140            })?;
141        }
142    }
143    Ok(())
144}