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}
74
75impl TokenAccounting {
76 pub fn new(input_tokens: u64, output_tokens: u64, cost_usd: f64) -> Self {
77 debug_assert!(cost_usd.is_finite(), "cost_usd must be finite");
78 debug_assert!(cost_usd >= 0.0, "cost_usd must be non-negative");
79 Self { input_tokens, output_tokens, cost_usd }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct AgentOutput {
86 pub name: String,
87 pub answer: String,
88 pub turns: u32,
89 pub tool_calls: u32,
90 pub duration_ms: f64,
91 pub error: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub outcome: Option<car_ir::AgentOutcome>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub tokens: Option<TokenAccounting>,
98}
99
100impl AgentOutput {
101 pub fn succeeded(&self) -> bool {
102 self.error.is_none() && !self.answer.is_empty()
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Message {
109 pub from: String,
110 pub to: String,
111 pub kind: MessageKind,
112 pub payload: Value,
113 pub timestamp: DateTime<Utc>,
114}
115
116impl Message {
117 pub fn new(from: &str, to: &str, kind: MessageKind, payload: Value) -> Self {
118 Self {
119 from: from.to_string(),
120 to: to.to_string(),
121 kind,
122 payload,
123 timestamp: Utc::now(),
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129#[serde(rename_all = "snake_case")]
130pub enum MessageKind {
131 TaskAssignment,
133 Result,
135 Feedback,
137 DelegateRequest,
139 DelegateResponse,
141 Custom,
143}