synwire_core/agents/
directive.rs1use crate::State;
4use crate::agents::streaming::AgentEvent;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::time::Duration;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type")]
14#[non_exhaustive]
15pub enum Directive {
16 #[serde(rename = "emit")]
18 Emit {
19 event: AgentEvent,
21 },
22
23 #[serde(rename = "spawn_agent")]
25 SpawnAgent {
26 name: String,
28 config: Value,
30 },
31
32 #[serde(rename = "stop_child")]
34 StopChild {
35 name: String,
37 },
38
39 #[serde(rename = "schedule")]
41 Schedule {
42 action: String,
44 #[serde(with = "humantime_serde")]
46 delay: Duration,
47 },
48
49 #[serde(rename = "run_instruction")]
51 RunInstruction {
52 instruction: String,
54 input: Value,
56 },
57
58 #[serde(rename = "cron")]
60 Cron {
61 expression: String,
63 action: String,
65 },
66
67 #[serde(rename = "stop")]
69 Stop {
70 reason: Option<String>,
72 },
73
74 #[serde(rename = "spawn_task")]
76 SpawnTask {
77 description: String,
79 input: Value,
81 },
82
83 #[serde(rename = "stop_task")]
85 StopTask {
86 task_id: String,
88 },
89
90 #[serde(rename = "custom")]
92 Custom {
93 #[serde(flatten)]
95 payload: Box<dyn DirectivePayload>,
96 },
97}
98
99#[typetag::serde(tag = "custom_type")]
103pub trait DirectivePayload: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {}
104
105dyn_clone::clone_trait_object!(DirectivePayload);
106
107#[derive(Debug, Clone)]
112pub struct DirectiveResult<S: State> {
113 pub state: S,
115 pub directives: Vec<Directive>,
117}
118
119impl<S: State> DirectiveResult<S> {
120 #[must_use]
122 pub const fn state_only(state: S) -> Self {
123 Self {
124 state,
125 directives: Vec::new(),
126 }
127 }
128
129 #[must_use]
131 pub fn with_directive(state: S, directive: Directive) -> Self {
132 Self {
133 state,
134 directives: vec![directive],
135 }
136 }
137
138 #[must_use]
140 pub const fn with_directives(state: S, directives: Vec<Directive>) -> Self {
141 Self { state, directives }
142 }
143}
144
145impl<S: State> From<S> for DirectiveResult<S> {
146 fn from(state: S) -> Self {
147 Self::state_only(state)
148 }
149}
150
151#[cfg(test)]
152#[allow(
153 clippy::unwrap_used,
154 clippy::expect_used,
155 clippy::panic,
156 clippy::redundant_clone
157)]
158mod tests {
159 use super::*;
160 use crate::agents::streaming::{AgentEvent, TerminationReason};
161
162 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
163 struct TestState {
164 count: u32,
165 }
166
167 impl State for TestState {
168 fn to_value(&self) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
169 Ok(serde_json::to_value(self)?)
170 }
171
172 fn from_value(value: Value) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
173 Ok(serde_json::from_value(value)?)
174 }
175 }
176
177 #[test]
178 fn test_directive_result_state_only() {
179 let state = TestState { count: 1 };
180 let result = DirectiveResult::state_only(state.clone());
181 assert_eq!(result.state, state);
182 assert!(result.directives.is_empty());
183 }
184
185 #[test]
186 fn test_directive_result_with_directive() {
187 let state = TestState { count: 1 };
188 let directive = Directive::Stop { reason: None };
189 let result = DirectiveResult::with_directive(state.clone(), directive.clone());
190 assert_eq!(result.state, state);
191 assert_eq!(result.directives.len(), 1);
192 }
193
194 #[test]
195 fn test_directive_result_from_state() {
196 let state = TestState { count: 1 };
197 let result: DirectiveResult<TestState> = state.clone().into();
198 assert_eq!(result.state, state);
199 assert!(result.directives.is_empty());
200 }
201
202 #[test]
203 fn test_directive_serde_emit() {
204 let directive = Directive::Emit {
205 event: AgentEvent::TurnComplete {
206 reason: TerminationReason::Complete,
207 },
208 };
209 let json = serde_json::to_string(&directive).expect("serialize");
210 let deserialized: Directive = serde_json::from_str(&json).expect("deserialize");
211 assert!(matches!(deserialized, Directive::Emit { .. }));
213 }
214
215 #[test]
216 fn test_directive_serde_spawn_agent() {
217 let directive = Directive::SpawnAgent {
218 name: "helper".to_string(),
219 config: serde_json::json!({"model": "gpt-4"}),
220 };
221 let json = serde_json::to_string(&directive).expect("serialize");
222 let deserialized: Directive = serde_json::from_str(&json).expect("deserialize");
223 assert!(matches!(deserialized, Directive::SpawnAgent { .. }));
224 }
225
226 #[test]
227 fn test_directive_serde_stop() {
228 let directive = Directive::Stop {
229 reason: Some("Task complete".to_string()),
230 };
231 let json = serde_json::to_string(&directive).expect("serialize");
232 let deserialized: Directive = serde_json::from_str(&json).expect("deserialize");
233 assert!(matches!(deserialized, Directive::Stop { .. }));
234 }
235}