bamboo_engine/session_app/execute/
sync.rs1use bamboo_agent_core::Message;
4use bamboo_domain::Session;
5
6use super::super::types::{ExecuteClientSync, ExecuteSyncReason, ServerExecuteSnapshot};
7use super::resume_markers::has_pending_user_message;
8
9pub fn evaluate_client_sync(
10 client_sync: Option<&ExecuteClientSync>,
11 server_snapshot: &ServerExecuteSnapshot,
12) -> Option<ExecuteSyncReason> {
13 let client_sync = client_sync?;
14
15 let client_pending_question_tool_call_id = client_sync
16 .client_pending_question_tool_call_id
17 .as_deref()
18 .map(str::trim)
19 .filter(|value| !value.is_empty());
20 let server_pending_question_tool_call_id = server_snapshot
21 .pending_question_tool_call_id
22 .as_deref()
23 .map(str::trim)
24 .filter(|value| !value.is_empty());
25
26 if client_sync.client_has_pending_question != server_snapshot.has_pending_question {
27 return Some(ExecuteSyncReason::PendingQuestionMismatch);
28 }
29
30 if client_sync.client_has_pending_question
31 && client_pending_question_tool_call_id.is_some()
32 && client_pending_question_tool_call_id != server_pending_question_tool_call_id
33 {
34 return Some(ExecuteSyncReason::PendingQuestionMismatch);
35 }
36
37 if client_sync.client_message_count != server_snapshot.message_count {
38 return Some(ExecuteSyncReason::MessageCountMismatch);
39 }
40
41 let client_last_message_id = client_sync
42 .client_last_message_id
43 .as_deref()
44 .map(str::trim)
45 .filter(|value| !value.is_empty());
46 let server_last_message_id = server_snapshot
47 .last_message_id
48 .as_deref()
49 .map(str::trim)
50 .filter(|value| !value.is_empty());
51
52 if client_last_message_id != server_last_message_id {
53 return Some(ExecuteSyncReason::LastMessageIdMismatch);
54 }
55
56 None
57}
58
59pub fn is_hidden_from_ui(message: &Message) -> bool {
67 message
68 .metadata
69 .as_ref()
70 .and_then(|metadata| metadata.get("hidden_from_ui"))
71 .and_then(|value| value.as_bool())
72 .unwrap_or(false)
73}
74
75impl ServerExecuteSnapshot {
76 pub fn from_session(session: &Session) -> Self {
77 let visible: Vec<&Message> = session
82 .messages
83 .iter()
84 .filter(|message| !is_hidden_from_ui(message))
85 .collect();
86 Self {
87 message_count: visible.len(),
88 last_message_id: visible.last().map(|message| message.id.clone()),
89 has_pending_question: session.pending_question.is_some(),
90 pending_question_tool_call_id: session
91 .pending_question
92 .as_ref()
93 .map(|pending| pending.tool_call_id.clone()),
94 has_pending_user_message: has_pending_user_message(session),
95 }
96 }
97}