Skip to main content

orchestrator_collab/
output.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use super::artifact::Artifact;
6
7/// Structured output emitted by an agent run.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct AgentOutput {
10    /// Run identifier associated with the output.
11    pub run_id: Uuid,
12    /// Agent identifier that produced the output.
13    pub agent_id: String,
14    /// Phase name for the run.
15    pub phase: String,
16    /// Process exit code.
17    pub exit_code: i64,
18    /// Captured stdout text.
19    pub stdout: String,
20    /// Captured stderr text.
21    pub stderr: String,
22    /// Structured artifacts parsed from the run.
23    pub artifacts: Vec<Artifact>,
24    /// Execution metrics collected for the run.
25    pub metrics: ExecutionMetrics,
26    /// Confidence score normalized to `[0.0, 1.0]`.
27    pub confidence: f32,
28    /// Quality score normalized to `[0.0, 1.0]`.
29    pub quality_score: f32,
30    /// Timestamp when the structured output was created.
31    pub created_at: DateTime<Utc>,
32    /// Structured build errors (populated for build/lint phases)
33    #[serde(default, skip_serializing_if = "Vec::is_empty")]
34    pub build_errors: Vec<orchestrator_config::config::BuildError>,
35    /// Structured test failures (populated for test phases)
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub test_failures: Vec<orchestrator_config::config::TestFailure>,
38}
39
40impl AgentOutput {
41    /// Creates a new output record with default metrics and scores.
42    pub fn new(
43        run_id: Uuid,
44        agent_id: String,
45        phase: String,
46        exit_code: i64,
47        stdout: String,
48        stderr: String,
49    ) -> Self {
50        Self {
51            run_id,
52            agent_id,
53            phase,
54            exit_code,
55            stdout,
56            stderr,
57            artifacts: Vec::new(),
58            metrics: ExecutionMetrics::default(),
59            confidence: 1.0,
60            quality_score: 1.0,
61            created_at: Utc::now(),
62            build_errors: Vec::new(),
63            test_failures: Vec::new(),
64        }
65    }
66
67    /// Replaces the artifact list on the output.
68    pub fn with_artifacts(mut self, artifacts: Vec<Artifact>) -> Self {
69        self.artifacts = artifacts;
70        self
71    }
72
73    /// Replaces execution metrics on the output.
74    pub fn with_metrics(mut self, metrics: ExecutionMetrics) -> Self {
75        self.metrics = metrics;
76        self
77    }
78
79    /// Sets the confidence score, clamping to `[0.0, 1.0]`.
80    pub fn with_confidence(mut self, confidence: f32) -> Self {
81        self.confidence = confidence.clamp(0.0, 1.0);
82        self
83    }
84
85    /// Sets the quality score, clamping to `[0.0, 1.0]`.
86    pub fn with_quality_score(mut self, score: f32) -> Self {
87        self.quality_score = score.clamp(0.0, 1.0);
88        self
89    }
90
91    /// Returns `true` when the run exited successfully.
92    pub fn is_success(&self) -> bool {
93        self.exit_code == 0
94    }
95}
96
97/// Execution metrics recorded for an agent run.
98#[derive(Debug, Clone, Default, Serialize, Deserialize)]
99pub struct ExecutionMetrics {
100    /// Total wall-clock duration in milliseconds.
101    pub duration_ms: u64,
102    /// Optional token count consumed by the agent backend.
103    pub tokens_consumed: Option<u64>,
104    /// Optional API call count issued by the agent backend.
105    pub api_calls: Option<u32>,
106    /// Number of retries performed before completion.
107    pub retry_count: u32,
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use crate::{ArtifactKind, ExecutionMetrics};
114
115    #[test]
116    fn test_agent_output_creation() {
117        let output = AgentOutput::new(
118            Uuid::new_v4(),
119            "qa_agent".to_string(),
120            "qa".to_string(),
121            0,
122            "test output".to_string(),
123            "".to_string(),
124        );
125
126        assert!(output.is_success());
127        assert_eq!(output.confidence, 1.0);
128    }
129
130    #[test]
131    fn test_agent_output_failure() {
132        let output = AgentOutput::new(
133            Uuid::new_v4(),
134            "impl_agent".to_string(),
135            "implement".to_string(),
136            1,
137            "".to_string(),
138            "error".to_string(),
139        );
140        assert!(!output.is_success());
141    }
142
143    #[test]
144    fn test_agent_output_builder_methods() {
145        let output = AgentOutput::new(
146            Uuid::new_v4(),
147            "agent".to_string(),
148            "qa".to_string(),
149            0,
150            "ok".to_string(),
151            "".to_string(),
152        )
153        .with_confidence(0.85)
154        .with_quality_score(0.9)
155        .with_metrics(ExecutionMetrics {
156            duration_ms: 1000,
157            tokens_consumed: Some(500),
158            api_calls: Some(3),
159            retry_count: 1,
160        })
161        .with_artifacts(vec![Artifact::new(ArtifactKind::Custom {
162            name: "test".to_string(),
163        })]);
164
165        assert_eq!(output.confidence, 0.85);
166        assert_eq!(output.quality_score, 0.9);
167        assert_eq!(output.metrics.duration_ms, 1000);
168        assert_eq!(output.artifacts.len(), 1);
169    }
170
171    #[test]
172    fn test_agent_output_confidence_clamped() {
173        let output = AgentOutput::new(
174            Uuid::new_v4(),
175            "a".to_string(),
176            "p".to_string(),
177            0,
178            "".to_string(),
179            "".to_string(),
180        )
181        .with_confidence(1.5)
182        .with_quality_score(-0.5);
183
184        assert_eq!(output.confidence, 1.0);
185        assert_eq!(output.quality_score, 0.0);
186    }
187}