Skip to main content

bamboo_server/session_app/
types.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3
4use bamboo_domain::reasoning::ReasoningEffort;
5use bamboo_domain::ProviderModelRef;
6use bamboo_domain::Session;
7use bamboo_engine::ImageFallbackConfig;
8use bamboo_infrastructure::LLMProvider;
9
10/// Resolved configuration snapshot for execution.
11///
12/// Built from `Config` in the handler layer and passed to use cases.
13/// This avoids leaking `bamboo-infrastructure-config` into the crate.
14#[derive(Clone, Default)]
15pub struct ExecutionConfigSnapshot {
16    pub default_model: Option<String>,
17    pub default_model_ref: Option<ProviderModelRef>,
18    pub default_reasoning_effort: Option<ReasoningEffort>,
19    pub disabled_tools: Vec<String>,
20    pub disabled_skill_ids: Vec<String>,
21    pub provider_name: String,
22    pub provider_type: Option<String>,
23    pub fast_model: Option<String>,
24    pub fast_model_ref: Option<ProviderModelRef>,
25    pub background_model: Option<String>,
26    pub background_model_ref: Option<ProviderModelRef>,
27    pub summarization_model: Option<String>,
28    pub summarization_model_ref: Option<ProviderModelRef>,
29    pub image_fallback: Option<ImageFallbackConfig>,
30    pub provider_model_ref_enabled: bool,
31}
32
33// ---- Chat types ----
34
35/// Input for the chat turn use case.
36pub struct ChatTurnInput {
37    pub session_id: String,
38    pub model: String,
39    pub model_ref: Option<ProviderModelRef>,
40    pub provider: Option<String>,
41    pub message: String,
42    pub system_prompt: Option<String>,
43    pub enhance_prompt: Option<String>,
44    pub workspace_path: Option<String>,
45    pub selected_skill_ids: Option<Vec<String>>,
46    pub copilot_conclusion_with_options_enhancement_enabled: Option<bool>,
47    /// Optional data directory for workspace path fallback when neither request
48    /// nor metadata provides one.
49    pub data_dir: Option<std::path::PathBuf>,
50}
51
52/// Outcome of preparing a chat turn.
53pub struct PreparedChatTurn {
54    pub session: Session,
55}
56
57// ---- Execute types ----
58
59/// Input for the execute preparation use case.
60pub struct ExecuteInput {
61    pub session_id: String,
62    pub request_model: Option<String>,
63    pub request_model_ref: Option<ProviderModelRef>,
64    pub request_provider: Option<String>,
65    pub request_reasoning_effort: Option<ReasoningEffort>,
66    pub request_skill_mode: Option<String>,
67    pub client_sync: Option<ExecuteClientSync>,
68}
69
70/// Client-side sync state sent with execute requests.
71#[derive(Debug, Clone)]
72pub struct ExecuteClientSync {
73    pub client_message_count: usize,
74    pub client_last_message_id: Option<String>,
75    pub client_has_pending_question: bool,
76    pub client_pending_question_tool_call_id: Option<String>,
77}
78
79/// Reason for a sync mismatch between client and server.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum ExecuteSyncReason {
82    PendingQuestionMismatch,
83    MessageCountMismatch,
84    LastMessageIdMismatch,
85}
86
87impl ExecuteSyncReason {
88    pub fn as_str(&self) -> &'static str {
89        match self {
90            Self::PendingQuestionMismatch => "pending_question_mismatch",
91            Self::MessageCountMismatch => "message_count_mismatch",
92            Self::LastMessageIdMismatch => "last_message_id_mismatch",
93        }
94    }
95}
96
97/// Server-side snapshot of session state used for sync comparison.
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct ServerExecuteSnapshot {
100    pub message_count: usize,
101    pub last_message_id: Option<String>,
102    pub has_pending_question: bool,
103    pub pending_question_tool_call_id: Option<String>,
104    pub has_pending_user_message: bool,
105}
106
107/// Sync info to include in execute responses.
108#[derive(Debug, Clone)]
109pub struct ExecuteSyncInfo {
110    pub need_sync: bool,
111    pub reason: Option<ExecuteSyncReason>,
112    pub server_message_count: usize,
113    pub server_last_message_id: Option<String>,
114    pub has_pending_question: bool,
115    pub pending_question_tool_call_id: Option<String>,
116    pub has_pending_user_message: bool,
117}
118
119impl ServerExecuteSnapshot {
120    pub fn to_sync_info(&self, reason: Option<ExecuteSyncReason>) -> ExecuteSyncInfo {
121        ExecuteSyncInfo {
122            need_sync: reason.is_some(),
123            reason,
124            server_message_count: self.message_count,
125            server_last_message_id: self.last_message_id.clone(),
126            has_pending_question: self.has_pending_question,
127            pending_question_tool_call_id: self.pending_question_tool_call_id.clone(),
128            has_pending_user_message: self.has_pending_user_message,
129        }
130    }
131}
132
133/// Outcome of preparing an execute.
134pub enum ExecutePreparationOutcome {
135    /// Session is ready for agent execution.
136    Ready {
137        session: Box<Session>,
138        effective_model: String,
139        effective_reasoning_effort: Option<ReasoningEffort>,
140        model_source: &'static str,
141        reasoning_source: &'static str,
142        is_child_session: bool,
143    },
144    /// Agent is already running for this session.
145    AlreadyRunning {
146        server_snapshot: ServerExecuteSnapshot,
147    },
148    /// No pending user message, nothing to execute.
149    NoPendingMessage {
150        server_snapshot: ServerExecuteSnapshot,
151    },
152    /// Client/server state mismatch detected.
153    SyncMismatch {
154        reason: ExecuteSyncReason,
155        server_snapshot: ServerExecuteSnapshot,
156    },
157    /// No model could be resolved.
158    ModelRequired,
159    /// Image fallback validation failed.
160    ImageFallbackError(String),
161}
162
163// ---- Respond types ----
164
165/// Input for the respond use case.
166pub struct RespondInput {
167    pub session_id: String,
168    pub user_response: String,
169    pub model: Option<String>,
170    pub model_ref: Option<ProviderModelRef>,
171    pub provider: Option<String>,
172    pub reasoning_effort: Option<ReasoningEffort>,
173}
174
175/// Outcome of submitting a pending response.
176pub struct SubmitResponseOutcome {
177    pub session: Session,
178    pub user_response: String,
179}
180
181// ---- Resume types ----
182
183/// Resolved configuration snapshot for resume execution.
184///
185/// Captures the subset of config needed to spawn a resumed agent loop,
186/// decoupled from the full server config.
187#[derive(Clone)]
188pub struct ResumeConfigSnapshot {
189    pub provider_name: String,
190    pub provider_type: Option<String>,
191    pub fast_model: Option<String>,
192    pub fast_model_ref: Option<ProviderModelRef>,
193    pub background_model: Option<String>,
194    pub background_model_ref: Option<ProviderModelRef>,
195    pub background_model_provider: Option<Arc<dyn LLMProvider>>,
196    pub summarization_model: Option<String>,
197    pub summarization_model_ref: Option<ProviderModelRef>,
198    pub summarization_model_provider: Option<Arc<dyn LLMProvider>>,
199    pub disabled_tools: BTreeSet<String>,
200    pub disabled_skill_ids: BTreeSet<String>,
201    pub image_fallback: Option<ImageFallbackConfig>,
202}
203
204/// Outcome of a resume attempt.
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub enum ResumeOutcome {
207    /// Execution spawned successfully.
208    Started { run_id: String },
209    /// A runner is already active for this session.
210    AlreadyRunning { run_id: String },
211    /// No pending user message, nothing to execute.
212    Completed,
213    /// Session not found.
214    NotFound,
215}
216
217impl ResumeOutcome {
218    /// Returns the status string (for backward compatibility)
219    pub fn as_str(&self) -> &'static str {
220        self.status_str()
221    }
222
223    pub fn status_str(&self) -> &'static str {
224        match self {
225            Self::Started { .. } => "started",
226            Self::AlreadyRunning { .. } => "already_running",
227            Self::Completed => "completed",
228            Self::NotFound => "error: session not found",
229        }
230    }
231
232    pub fn run_id(&self) -> Option<&String> {
233        match self {
234            Self::Started { run_id } | Self::AlreadyRunning { run_id } => Some(run_id),
235            _ => None,
236        }
237    }
238}