Skip to main content

swink_agent_patterns/pipeline/
output.rs

1//! Pipeline output and error types.
2
3use std::time::Duration;
4
5use swink_agent::Usage;
6
7use super::types::PipelineId;
8
9// ─── StepResult ─────────────────────────────────────────────────────────────
10
11/// Per-step execution telemetry.
12#[non_exhaustive]
13#[derive(Clone, Debug)]
14pub struct StepResult {
15    /// Which agent ran this step.
16    pub agent_name: String,
17    /// The agent's text output.
18    pub response: String,
19    /// Wall-clock time for this step.
20    pub duration: Duration,
21    /// Token usage for this step.
22    pub usage: Usage,
23}
24
25impl StepResult {
26    /// Create a step result from its parts.
27    #[must_use]
28    pub fn new(
29        agent_name: impl Into<String>,
30        response: impl Into<String>,
31        duration: Duration,
32        usage: Usage,
33    ) -> Self {
34        Self {
35            agent_name: agent_name.into(),
36            response: response.into(),
37            duration,
38            usage,
39        }
40    }
41}
42
43// ─── PipelineOutput ─────────────────────────────────────────────────────────
44
45/// Structured result from pipeline execution.
46#[non_exhaustive]
47#[derive(Clone, Debug)]
48pub struct PipelineOutput {
49    /// Which pipeline produced this output.
50    pub pipeline_id: PipelineId,
51    /// The pipeline's final text output.
52    pub final_response: String,
53    /// Per-step telemetry.
54    pub steps: Vec<StepResult>,
55    /// Wall-clock time for the entire pipeline.
56    pub total_duration: Duration,
57    /// Aggregated token usage across all steps.
58    pub total_usage: Usage,
59}
60
61impl PipelineOutput {
62    /// Create a pipeline output with the identifying fields; step telemetry
63    /// and totals start empty and can be filled in with the `with_*` builders.
64    #[must_use]
65    pub fn new(pipeline_id: PipelineId, final_response: impl Into<String>) -> Self {
66        Self {
67            pipeline_id,
68            final_response: final_response.into(),
69            steps: Vec::new(),
70            total_duration: Duration::ZERO,
71            total_usage: Usage::default(),
72        }
73    }
74
75    /// Set the per-step telemetry.
76    #[must_use]
77    pub fn with_steps(mut self, steps: Vec<StepResult>) -> Self {
78        self.steps = steps;
79        self
80    }
81
82    /// Set the wall-clock time for the entire pipeline.
83    #[must_use]
84    pub fn with_total_duration(mut self, total_duration: Duration) -> Self {
85        self.total_duration = total_duration;
86        self
87    }
88
89    /// Set the aggregated token usage across all steps.
90    #[must_use]
91    pub fn with_total_usage(mut self, total_usage: Usage) -> Self {
92        self.total_usage = total_usage;
93        self
94    }
95}
96
97// ─── PipelineError ──────────────────────────────────────────────────────────
98
99/// Typed error variants for pipeline execution failures.
100#[non_exhaustive]
101#[derive(Debug, thiserror::Error)]
102pub enum PipelineError {
103    /// Named agent not found in factory.
104    #[error("agent not found: {name}")]
105    AgentNotFound { name: String },
106    /// Pipeline ID not in registry.
107    #[error("pipeline not found: {id}")]
108    PipelineNotFound { id: PipelineId },
109    /// A step errored during execution.
110    #[error("step {step_index} ({agent_name}) failed: {source}")]
111    StepFailed {
112        step_index: usize,
113        agent_name: String,
114        source: Box<dyn std::error::Error + Send + Sync>,
115    },
116    /// Loop hit safety cap without meeting exit condition.
117    #[error("max iterations reached: {iterations}")]
118    MaxIterationsReached { iterations: usize },
119    /// Cancellation token was triggered.
120    #[error("pipeline cancelled")]
121    Cancelled,
122    /// Regex compilation or other construction failure.
123    #[error("invalid exit condition: {message}")]
124    InvalidExitCondition { message: String },
125}