Skip to main content

bamboo_engine/session_app/
types.rs

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