1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct AgentSpec {
14 pub name: String,
15 pub system_prompt: String,
16 pub tools: Vec<String>,
18 pub max_turns: u32,
19 pub metadata: HashMap<String, Value>,
21 #[serde(default)]
24 pub cache_control: bool,
25}
26
27impl AgentSpec {
28 pub fn new(name: &str, system_prompt: &str) -> Self {
29 Self {
30 name: name.to_string(),
31 system_prompt: system_prompt.to_string(),
32 tools: Vec::new(),
33 max_turns: 10,
34 metadata: HashMap::new(),
35 cache_control: false,
36 }
37 }
38
39 pub fn with_tools(mut self, tools: Vec<String>) -> Self {
40 self.tools = tools;
41 self
42 }
43
44 pub fn with_max_turns(mut self, max_turns: u32) -> Self {
45 self.max_turns = max_turns;
46 self
47 }
48
49 pub fn with_metadata(mut self, key: &str, value: Value) -> Self {
50 self.metadata.insert(key.to_string(), value);
51 self
52 }
53
54 pub fn with_cache_control(mut self) -> Self {
55 self.cache_control = true;
56 self
57 }
58}
59
60#[derive(Debug, Clone, Default, Serialize, Deserialize)]
66pub struct TokenAccounting {
67 #[serde(default)]
68 pub input_tokens: u64,
69 #[serde(default)]
70 pub output_tokens: u64,
71 #[serde(default)]
72 pub cost_usd: f64,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub latency: Option<RunLatency>,
80}
81
82#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct RunLatency {
86 #[serde(default)]
91 pub generation_ms: f64,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
96 pub ttft_ms: Option<u64>,
97}
98
99impl TokenAccounting {
100 pub fn new(input_tokens: u64, output_tokens: u64, cost_usd: f64) -> Self {
101 debug_assert!(cost_usd.is_finite(), "cost_usd must be finite");
102 debug_assert!(cost_usd >= 0.0, "cost_usd must be non-negative");
103 Self {
104 input_tokens,
105 output_tokens,
106 cost_usd,
107 latency: None,
108 }
109 }
110
111 pub fn with_latency(mut self, generation_ms: f64, ttft_ms: Option<u64>) -> Self {
114 self.latency = Some(RunLatency {
115 generation_ms,
116 ttft_ms,
117 });
118 self
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AgentOutput {
125 pub name: String,
126 pub answer: String,
127 pub turns: u32,
128 pub tool_calls: u32,
129 pub duration_ms: f64,
130 pub error: Option<String>,
131 #[serde(default, skip_serializing_if = "Option::is_none")]
134 pub outcome: Option<car_ir::AgentOutcome>,
135 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub tokens: Option<TokenAccounting>,
137 #[serde(default, skip_serializing_if = "Vec::is_empty")]
142 pub tools_used: Vec<String>,
143}
144
145impl AgentOutput {
146 pub fn succeeded(&self) -> bool {
147 self.error.is_none() && !self.answer.is_empty()
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct Message {
154 pub from: String,
155 pub to: String,
156 pub kind: MessageKind,
157 pub payload: Value,
158 pub timestamp: DateTime<Utc>,
159}
160
161impl Message {
162 pub fn new(from: &str, to: &str, kind: MessageKind, payload: Value) -> Self {
163 Self {
164 from: from.to_string(),
165 to: to.to_string(),
166 kind,
167 payload,
168 timestamp: Utc::now(),
169 }
170 }
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
174#[serde(rename_all = "snake_case")]
175pub enum MessageKind {
176 TaskAssignment,
178 Result,
180 Feedback,
182 DelegateRequest,
184 DelegateResponse,
186 Custom,
188}