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