1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
6#[serde(rename_all = "snake_case")]
7pub enum Role {
8 System,
9 User,
10 Assistant,
11 Tool,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
15pub struct ChatMessage {
16 pub role: Role,
17 pub content: String,
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub tool_call_id: Option<String>,
21}
22
23impl ChatMessage {
24 pub fn system(content: impl Into<String>) -> Self {
25 Self {
26 role: Role::System,
27 content: content.into(),
28 tool_call_id: None,
29 }
30 }
31
32 pub fn user(content: impl Into<String>) -> Self {
33 Self {
34 role: Role::User,
35 content: content.into(),
36 tool_call_id: None,
37 }
38 }
39
40 pub fn assistant(content: impl Into<String>) -> Self {
41 Self {
42 role: Role::Assistant,
43 content: content.into(),
44 tool_call_id: None,
45 }
46 }
47
48 pub fn tool(content: impl Into<String>) -> Self {
49 Self {
50 role: Role::Tool,
51 content: content.into(),
52 tool_call_id: None,
53 }
54 }
55
56 pub fn tool_result(call_id: impl Into<String>, content: impl Into<String>) -> Self {
57 Self {
58 role: Role::Tool,
59 content: content.into(),
60 tool_call_id: Some(call_id.into()),
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
67pub struct ToolAnnotations {
68 #[serde(default)]
70 pub read_only: bool,
71 #[serde(default)]
73 pub destructive: bool,
74 #[serde(default)]
76 pub idempotent: bool,
77 #[serde(default)]
79 pub open_world: bool,
80 #[serde(default)]
82 pub requires_confirmation: bool,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
86pub struct ToolDefinition {
87 pub name: String,
88 pub description: String,
89 pub input_schema: Value,
90
91 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub title: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub output_schema: Option<Value>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub annotations: Option<ToolAnnotations>,
101
102 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub category: Option<String>,
106 #[serde(default, skip_serializing_if = "Vec::is_empty")]
108 pub tags: Vec<String>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub timeout_secs: Option<u32>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
116#[serde(tag = "type", rename_all = "snake_case")]
117pub enum ToolContent {
118 Text { text: String },
119 Image { data: String, mime_type: String },
120 Json { value: Value },
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
124pub struct ToolCall {
125 pub call_id: String,
126 pub tool_name: String,
127 #[serde(default)]
128 pub input: Value,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
132pub struct ToolResult {
133 pub call_id: String,
134 pub tool_name: String,
135 #[serde(default)]
136 pub output: Value,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
139 pub content: Option<Vec<ToolContent>>,
140 #[serde(default)]
142 pub is_error: bool,
143 pub state_patch: Option<StatePatch>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
147pub struct ToolResultSummary {
148 pub call_id: String,
149 pub tool_name: String,
150 #[serde(default)]
151 pub output: Value,
152}
153
154impl From<&ToolResult> for ToolResultSummary {
155 fn from(value: &ToolResult) -> Self {
156 Self {
157 call_id: value.call_id.clone(),
158 tool_name: value.tool_name.clone(),
159 output: value.output.clone(),
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
165#[serde(rename_all = "snake_case")]
166pub enum StatePatchFormat {
167 JsonPatch,
168 MergePatch,
169}
170
171#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
172#[serde(rename_all = "snake_case")]
173pub enum StatePatchSource {
174 Model,
175 Tool,
176 System,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
180pub struct StatePatch {
181 pub format: StatePatchFormat,
182 #[serde(default)]
183 pub patch: Value,
184 pub source: StatePatchSource,
185}
186
187#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
188#[serde(rename_all = "snake_case")]
189pub enum ModelStopReason {
190 EndTurn,
191 ToolUse,
192 NeedsUser,
193 MaxTokens,
194 Safety,
195 Unknown,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
199#[serde(tag = "kind", rename_all = "snake_case")]
200pub enum ModelDirective {
201 Text { delta: String },
202 ToolCall { call: ToolCall },
203 StatePatch { patch: StatePatch },
204 FinalAnswer { text: String },
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
208pub struct ModelTurn {
209 pub directives: Vec<ModelDirective>,
210 pub stop_reason: ModelStopReason,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
213 pub usage: Option<TokenUsage>,
214}
215
216#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
217#[serde(rename_all = "snake_case")]
218pub struct TokenUsage {
219 #[serde(default)]
221 pub input_tokens: u64,
222 #[serde(default)]
224 pub output_tokens: u64,
225 #[serde(default)]
227 pub cache_read_tokens: u64,
228 #[serde(default)]
230 pub cache_creation_tokens: u64,
231}
232
233impl TokenUsage {
234 pub fn accumulate(&mut self, other: &TokenUsage) {
236 self.input_tokens += other.input_tokens;
237 self.output_tokens += other.output_tokens;
238 self.cache_read_tokens += other.cache_read_tokens;
239 self.cache_creation_tokens += other.cache_creation_tokens;
240 }
241
242 pub fn total(&self) -> u64 {
243 self.input_tokens + self.output_tokens
244 }
245}
246
247#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
248#[serde(rename_all = "snake_case")]
249pub enum RunStopReason {
250 Completed,
251 NeedsUser,
252 BlockedByPolicy,
253 BudgetExceeded,
254 Cancelled,
255 Error,
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
259#[serde(tag = "part_type", rename_all = "snake_case")]
260pub enum AgentEvent {
261 RunStarted {
262 run_id: String,
263 session_id: String,
264 provider: String,
265 max_iterations: u32,
266 },
267 IterationStarted {
268 run_id: String,
269 session_id: String,
270 iteration: u32,
271 },
272 ModelOutput {
273 run_id: String,
274 session_id: String,
275 iteration: u32,
276 stop_reason: ModelStopReason,
277 directive_count: usize,
278 #[serde(default, skip_serializing_if = "Option::is_none")]
279 usage: Option<TokenUsage>,
280 },
281 TextDelta {
282 run_id: String,
283 session_id: String,
284 iteration: u32,
285 delta: String,
286 },
287 ToolCallRequested {
288 run_id: String,
289 session_id: String,
290 iteration: u32,
291 call: ToolCall,
292 },
293 ToolCallCompleted {
294 run_id: String,
295 session_id: String,
296 iteration: u32,
297 result: ToolResultSummary,
298 },
299 ToolCallFailed {
300 run_id: String,
301 session_id: String,
302 iteration: u32,
303 call_id: String,
304 tool_name: String,
305 error: String,
306 },
307 StatePatched {
308 run_id: String,
309 session_id: String,
310 iteration: u32,
311 patch: StatePatch,
312 revision: u64,
313 },
314 RunErrored {
315 run_id: String,
316 session_id: String,
317 error: String,
318 },
319 RunFinished {
320 run_id: String,
321 session_id: String,
322 reason: RunStopReason,
323 total_iterations: u32,
324 final_answer: Option<String>,
325 },
326}
327
328impl AgentEvent {
329 pub fn as_sse_data(&self) -> Result<String, serde_json::Error> {
330 let payload = serde_json::to_string(self)?;
331 Ok(format!("data: {payload}\n\n"))
332 }
333}