1use crate::completion::Usage;
4use crate::ids::{ModelId, RunId};
5use crate::trajectory::TrajectoryHandle;
6use crate::MetadataMap;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::time::Duration;
10use time::OffsetDateTime;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct RunOutcome {
14 pub run_id: RunId,
15 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub task_id: Option<String>,
17
18 pub termination: Termination,
19
20 pub final_messages: Vec<crate::message::Message>,
22
23 pub trajectory: TrajectoryHandle,
25
26 pub usage: ResourceUsage,
27 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
28 pub per_model_usage: HashMap<ModelId, ResourceUsage>,
29
30 #[serde(with = "time::serde::rfc3339")]
31 pub started_at: OffsetDateTime,
32 #[serde(with = "time::serde::rfc3339")]
33 pub finished_at: OffsetDateTime,
34
35 #[serde(default, skip_serializing_if = "MetadataMap::is_empty")]
37 pub agent_state: MetadataMap,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(tag = "kind", rename_all = "snake_case")]
42pub enum Termination {
43 Completed { reason: CompletionReason },
44 Truncated { limit: TruncationLimit },
45 Failed { error: RunError, at_turn: u32 },
46 Interrupted { reason: InterruptionReason },
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum CompletionReason {
52 EndTurn,
53 StopSequence(String),
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum TruncationLimit {
59 MaxTurns(u32),
60 Budget(String),
61 Timeout,
62 MaxTokens,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(rename_all = "snake_case")]
67pub enum InterruptionReason {
68 User,
69 ApprovalDenied(String),
70 Cancellation,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct RunError {
77 pub category: RunErrorCategory,
78 pub message: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82#[serde(rename_all = "snake_case")]
83pub enum RunErrorCategory {
84 Llm,
85 Tool,
86 Memory,
87 Budget,
88 Critic,
89 UserSimulator,
90 Other,
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct ResourceUsage {
95 pub tokens_input: u64,
96 pub tokens_output: u64,
97 pub tokens_cache_read: u64,
98 pub tokens_cache_create: u64,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub cost_usd: Option<f64>,
101 #[serde(with = "duration_seconds")]
102 pub wall_clock: Duration,
103 pub turns: u32,
104 pub tool_calls: u32,
105}
106
107impl ResourceUsage {
108 pub fn add_usage(&mut self, u: &Usage) {
109 self.tokens_input += u.tokens_input;
110 self.tokens_output += u.tokens_output;
111 self.tokens_cache_read += u.tokens_cache_read;
112 self.tokens_cache_create += u.tokens_cache_create;
113 }
114}
115
116mod duration_seconds {
117 use serde::{Deserialize, Deserializer, Serializer};
118 use std::time::Duration;
119
120 pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
121 s.serialize_f64(d.as_secs_f64())
122 }
123
124 pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
125 let secs = f64::deserialize(d)?;
126 Ok(Duration::from_secs_f64(secs))
127 }
128}
129
130#[derive(Debug, thiserror::Error)]
133pub enum AgentError {
134 #[error("llm error: {0}")]
135 Llm(String),
136 #[error("tool error: {0}")]
137 Tool(String),
138 #[error("memory error: {0}")]
139 Memory(String),
140 #[error("budget exceeded: {0}")]
141 Budget(String),
142 #[error("configuration error: {0}")]
143 Configuration(String),
144 #[error("cancelled")]
145 Cancelled,
146 #[error(transparent)]
147 Io(#[from] std::io::Error),
148 #[error("{0}")]
149 Other(String),
150}