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