Skip to main content

vv_agent/app_server/protocol/
item.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use ts_rs::TS;
5
6use crate::events::{RunEvent, RunEventPayload, ToolStatus};
7use crate::types::AgentStatus;
8
9use super::approval::{ApprovalDecision, ApprovalRequestParams};
10use super::turn::{AppTurn, TurnCompletedParams, TurnStatus};
11use super::ServerNotification;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
14#[serde(rename_all = "camelCase")]
15pub struct ItemStartedParams {
16    pub thread_id: String,
17    pub turn_id: String,
18    pub item: AppItem,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
22#[serde(rename_all = "camelCase")]
23pub struct AgentMessageDeltaParams {
24    pub thread_id: String,
25    pub turn_id: String,
26    pub item_id: String,
27    pub delta: String,
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
31#[serde(rename_all = "camelCase")]
32pub struct ToolCallDeltaParams {
33    pub thread_id: String,
34    pub turn_id: String,
35    pub item_id: String,
36    pub delta: Value,
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
40#[serde(rename_all = "camelCase")]
41pub struct ItemCompletedParams {
42    pub thread_id: String,
43    pub turn_id: String,
44    pub item: AppItem,
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)]
48#[serde(rename_all = "camelCase")]
49pub struct AppItem {
50    pub id: String,
51    pub run_event_id: String,
52    #[serde(rename = "type")]
53    pub kind: AppItemKind,
54    pub status: AppItemStatus,
55    pub created_at_ms: u128,
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub completed_at_ms: Option<u128>,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub content: Option<Value>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
63#[serde(rename_all = "camelCase")]
64pub enum AppItemKind {
65    UserMessage,
66    AgentMessage,
67    ToolCall,
68    ApprovalRequest,
69    ApprovalResolved,
70    MemoryCompact,
71    SubRun,
72    Handoff,
73    RunStatus,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, TS)]
77#[serde(rename_all = "camelCase")]
78pub enum AppItemStatus {
79    Queued,
80    InProgress,
81    Completed,
82    Failed,
83    Interrupted,
84}
85
86pub fn map_run_event_to_notifications(
87    thread_id: &str,
88    turn_id: &str,
89    event: &RunEvent,
90) -> Vec<ServerNotification> {
91    match event.payload() {
92        RunEventPayload::AssistantDelta { delta } => {
93            vec![ServerNotification::AgentMessageDelta(
94                AgentMessageDeltaParams {
95                    thread_id: thread_id.to_string(),
96                    turn_id: turn_id.to_string(),
97                    item_id: event.event_id().as_str().to_string(),
98                    delta: delta.clone(),
99                },
100            )]
101        }
102        RunEventPayload::ToolCallStarted {
103            tool_call_id,
104            tool_name,
105            arguments,
106        } => vec![ServerNotification::ItemStarted(ItemStartedParams {
107            thread_id: thread_id.to_string(),
108            turn_id: turn_id.to_string(),
109            item: AppItem {
110                id: event.event_id().as_str().to_string(),
111                run_event_id: event.event_id().as_str().to_string(),
112                kind: AppItemKind::ToolCall,
113                status: AppItemStatus::InProgress,
114                created_at_ms: event.created_at_ms(),
115                completed_at_ms: None,
116                content: Some(serde_json::json!({
117                    "toolCallId": tool_call_id,
118                    "toolName": tool_name,
119                    "arguments": arguments,
120                })),
121            },
122        })],
123        RunEventPayload::ToolCallCompleted {
124            tool_call_id,
125            tool_name,
126            status,
127        } => vec![ServerNotification::ItemCompleted(ItemCompletedParams {
128            thread_id: thread_id.to_string(),
129            turn_id: turn_id.to_string(),
130            item: AppItem {
131                id: event.event_id().as_str().to_string(),
132                run_event_id: event.event_id().as_str().to_string(),
133                kind: AppItemKind::ToolCall,
134                status: tool_status_to_item_status(*status),
135                created_at_ms: event.created_at_ms(),
136                completed_at_ms: Some(event.created_at_ms()),
137                content: Some(serde_json::json!({
138                    "toolCallId": tool_call_id,
139                    "toolName": tool_name,
140                    "status": status,
141                })),
142            },
143        })],
144        RunEventPayload::ApprovalRequested {
145            request_id,
146            tool_name,
147            preview,
148            ..
149        } => vec![ServerNotification::ApprovalRequested(
150            ApprovalRequestParams {
151                thread_id: thread_id.to_string(),
152                turn_id: turn_id.to_string(),
153                request_id: request_id.clone(),
154                tool_name: tool_name.clone(),
155                preview: preview.clone(),
156                choices: vec![ApprovalDecision::Allow, ApprovalDecision::Deny],
157            },
158        )],
159        RunEventPayload::RunCompleted { status } => {
160            vec![ServerNotification::TurnCompleted(TurnCompletedParams {
161                turn: completed_turn(
162                    thread_id,
163                    turn_id,
164                    event,
165                    agent_status_to_turn_status(status),
166                ),
167            })]
168        }
169        RunEventPayload::RunFailed { .. } => {
170            vec![ServerNotification::TurnCompleted(TurnCompletedParams {
171                turn: completed_turn(thread_id, turn_id, event, TurnStatus::Failed),
172            })]
173        }
174        RunEventPayload::RunCancelled { .. } => {
175            vec![ServerNotification::TurnCompleted(TurnCompletedParams {
176                turn: completed_turn(thread_id, turn_id, event, TurnStatus::Interrupted),
177            })]
178        }
179        _ => Vec::new(),
180    }
181}
182
183fn completed_turn(thread_id: &str, turn_id: &str, event: &RunEvent, status: TurnStatus) -> AppTurn {
184    AppTurn {
185        id: turn_id.to_string(),
186        thread_id: thread_id.to_string(),
187        run_id: event.run_id().to_string(),
188        status,
189        input: Vec::new(),
190        started_at_ms: None,
191        completed_at_ms: Some(event.created_at_ms()),
192        token_usage: None,
193    }
194}
195
196fn tool_status_to_item_status(status: ToolStatus) -> AppItemStatus {
197    match status {
198        ToolStatus::Started => AppItemStatus::InProgress,
199        ToolStatus::Success => AppItemStatus::Completed,
200        ToolStatus::Error => AppItemStatus::Failed,
201        ToolStatus::WaitResponse => AppItemStatus::InProgress,
202    }
203}
204
205fn agent_status_to_turn_status(status: &AgentStatus) -> TurnStatus {
206    match status {
207        AgentStatus::Completed => TurnStatus::Completed,
208        AgentStatus::Failed => TurnStatus::Failed,
209        AgentStatus::Pending => TurnStatus::Queued,
210        AgentStatus::Running => TurnStatus::Running,
211        AgentStatus::WaitUser => TurnStatus::Running,
212        AgentStatus::MaxCycles => TurnStatus::Failed,
213    }
214}