1use async_trait::async_trait;
19use serde::{Deserialize, Serialize};
20use serde_json::Value;
21use std::collections::HashMap;
22
23use crate::{ExecutionConfig, Skill, SkillError, SkillResult, SkillStep};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct StepResult {
28 pub step_name: String,
30
31 pub success: bool,
33
34 pub output: Option<Value>,
36
37 pub error: Option<String>,
39
40 pub duration_ms: u64,
42
43 pub retry_attempts: usize,
45}
46
47impl StepResult {
48 pub fn success(step_name: impl Into<String>, output: Option<Value>, duration_ms: u64) -> Self {
50 Self {
51 step_name: step_name.into(),
52 success: true,
53 output,
54 error: None,
55 duration_ms,
56 retry_attempts: 0,
57 }
58 }
59
60 pub fn failure(
62 step_name: impl Into<String>,
63 error: impl Into<String>,
64 duration_ms: u64,
65 ) -> Self {
66 Self {
67 step_name: step_name.into(),
68 success: false,
69 output: None,
70 error: Some(error.into()),
71 duration_ms,
72 retry_attempts: 0,
73 }
74 }
75
76 pub fn with_retry_attempts(mut self, attempts: usize) -> Self {
78 self.retry_attempts = attempts;
79 self
80 }
81}
82
83#[derive(Debug, Clone)]
88pub struct ExecutionContext {
89 inputs: HashMap<String, Value>,
91
92 outputs: HashMap<String, Value>,
94
95 config: ExecutionConfig,
97
98 metadata: HashMap<String, Value>,
100}
101
102impl Default for ExecutionContext {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl ExecutionContext {
109 pub fn new() -> Self {
111 Self {
112 inputs: HashMap::new(),
113 outputs: HashMap::new(),
114 config: ExecutionConfig::default(),
115 metadata: HashMap::new(),
116 }
117 }
118
119 pub fn from_inputs(inputs: HashMap<String, Value>) -> Self {
121 Self {
122 inputs,
123 outputs: HashMap::new(),
124 config: ExecutionConfig::default(),
125 metadata: HashMap::new(),
126 }
127 }
128
129 pub fn with_input(mut self, key: impl Into<String>, value: Value) -> Self {
131 self.inputs.insert(key.into(), value);
132 self
133 }
134
135 pub fn with_config(mut self, config: ExecutionConfig) -> Self {
137 self.config = config;
138 self
139 }
140
141 pub fn with_metadata(mut self, key: impl Into<String>, value: Value) -> Self {
143 self.metadata.insert(key.into(), value);
144 self
145 }
146
147 pub fn get_input(&self, key: &str) -> Option<&Value> {
149 self.inputs.get(key)
150 }
151
152 pub fn inputs(&self) -> &HashMap<String, Value> {
154 &self.inputs
155 }
156
157 pub fn get_output(&self, step_name: &str) -> Option<&Value> {
159 self.outputs.get(step_name)
160 }
161
162 pub fn outputs(&self) -> &HashMap<String, Value> {
164 &self.outputs
165 }
166
167 pub fn set_output(&mut self, step_name: impl Into<String>, value: Value) {
169 self.outputs.insert(step_name.into(), value);
170 }
171
172 pub fn config(&self) -> &ExecutionConfig {
174 &self.config
175 }
176
177 pub fn config_mut(&mut self) -> &mut ExecutionConfig {
179 &mut self.config
180 }
181
182 pub fn get_metadata(&self, key: &str) -> Option<&Value> {
184 self.metadata.get(key)
185 }
186
187 pub fn metadata(&self) -> &HashMap<String, Value> {
189 &self.metadata
190 }
191
192 pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
194 self.metadata.insert(key.into(), value);
195 }
196
197 pub fn variables(&self) -> HashMap<String, Value> {
201 let mut vars = self.inputs.clone();
202 vars.extend(self.outputs.clone());
203 vars
204 }
205
206 pub fn clear_outputs(&mut self) {
208 self.outputs.clear();
209 }
210}
211
212#[async_trait]
245pub trait SkillExecutor: Send + Sync {
246 async fn execute(
260 &self,
261 skill: &Skill,
262 context: &mut ExecutionContext,
263 ) -> Result<SkillResult, SkillError>;
264
265 async fn execute_step(
278 &self,
279 step: &SkillStep,
280 context: &mut ExecutionContext,
281 ) -> Result<StepResult, SkillError>;
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_step_result_success() {
290 let result = StepResult::success("step1", Some(serde_json::json!({"data": "test"})), 100);
291
292 assert!(result.success);
293 assert_eq!(result.step_name, "step1");
294 assert!(result.output.is_some());
295 assert!(result.error.is_none());
296 assert_eq!(result.duration_ms, 100);
297 }
298
299 #[test]
300 fn test_step_result_failure() {
301 let result = StepResult::failure("step1", "something went wrong", 50);
302
303 assert!(!result.success);
304 assert_eq!(result.step_name, "step1");
305 assert!(result.output.is_none());
306 assert_eq!(result.error, Some("something went wrong".to_string()));
307 }
308
309 #[test]
310 fn test_step_result_with_retry_attempts() {
311 let result = StepResult::success("step1", None, 100).with_retry_attempts(3);
312
313 assert_eq!(result.retry_attempts, 3);
314 }
315
316 #[test]
317 fn test_execution_context_new() {
318 let context = ExecutionContext::new();
319
320 assert!(context.inputs().is_empty());
321 assert!(context.outputs().is_empty());
322 assert!(context.metadata().is_empty());
323 }
324
325 #[test]
326 fn test_execution_context_with_inputs() {
327 let context = ExecutionContext::new()
328 .with_input("query", serde_json::json!("test"))
329 .with_input("limit", serde_json::json!(10));
330
331 assert_eq!(context.inputs().len(), 2);
332 assert_eq!(context.get_input("query"), Some(&serde_json::json!("test")));
333 assert_eq!(context.get_input("limit"), Some(&serde_json::json!(10)));
334 }
335
336 #[test]
337 fn test_execution_context_from_inputs() {
338 let mut inputs = HashMap::new();
339 inputs.insert("key".to_string(), serde_json::json!("value"));
340
341 let context = ExecutionContext::from_inputs(inputs);
342
343 assert_eq!(context.inputs().len(), 1);
344 assert_eq!(context.get_input("key"), Some(&serde_json::json!("value")));
345 }
346
347 #[test]
348 fn test_execution_context_outputs() {
349 let mut context = ExecutionContext::new();
350
351 context.set_output("step1", serde_json::json!({"result": 42}));
352
353 assert_eq!(
354 context.get_output("step1"),
355 Some(&serde_json::json!({"result": 42}))
356 );
357 }
358
359 #[test]
360 fn test_execution_context_variables() {
361 let mut context = ExecutionContext::new()
362 .with_input("input1", serde_json::json!("in"))
363 .with_input("shared", serde_json::json!("from_input"));
364
365 context.set_output("step1", serde_json::json!("out"));
366 context.set_output("shared", serde_json::json!("from_output"));
367
368 let vars = context.variables();
369
370 assert_eq!(vars.len(), 3);
372 assert_eq!(vars.get("shared"), Some(&serde_json::json!("from_output")));
374 }
375
376 #[test]
377 fn test_execution_context_metadata() {
378 let context = ExecutionContext::new()
379 .with_metadata("trace_id", serde_json::json!("abc123"))
380 .with_metadata("user_id", serde_json::json!(42));
381
382 assert_eq!(context.metadata().len(), 2);
383 assert_eq!(
384 context.get_metadata("trace_id"),
385 Some(&serde_json::json!("abc123"))
386 );
387 }
388
389 #[test]
390 fn test_execution_context_clear_outputs() {
391 let mut context = ExecutionContext::new();
392 context.set_output("step1", serde_json::json!("result"));
393
394 assert_eq!(context.outputs().len(), 1);
395
396 context.clear_outputs();
397
398 assert!(context.outputs().is_empty());
399 }
400
401 #[test]
402 fn test_execution_context_with_config() {
403 use crate::TimeoutConfig;
404 use std::time::Duration;
405
406 let config = ExecutionConfig::new()
407 .with_timeout(TimeoutConfig::new().with_step_timeout(Duration::from_secs(60)));
408
409 let context = ExecutionContext::new().with_config(config);
410
411 assert_eq!(
412 context.config().timeout.step_timeout,
413 Duration::from_secs(60)
414 );
415 }
416}