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    /// Explicit reasoning override used when this chat creates a session.
66    /// Existing sessions remain authoritative; later changes go through the
67    /// session PATCH contract.
68    pub reasoning_effort: Option<ReasoningEffort>,
69    /// Resolved initial session-scoped permission mode used when this chat
70    /// creates a session (explicit request value wins over the durable
71    /// permission-policy default, resolved by the caller). Existing sessions
72    /// remain authoritative; later changes go through the session PATCH
73    /// contract. `None` keeps the runtime default (Default).
74    pub permission_mode: Option<bamboo_domain::SessionPermissionMode>,
75    pub message: String,
76    pub system_prompt: Option<String>,
77    pub enhance_prompt: Option<String>,
78    /// Explicit, durable root delegation guidance selection. Omission keeps
79    /// the current Session value; `false` turns it off.
80    pub root_orchestration_prompt: Option<bool>,
81    /// Explicit Root tool authority selection. Omission retains the durable
82    /// choice; both enable and disable are rejected for Child sessions.
83    pub root_orchestration_only: Option<bool>,
84    pub workspace_path: Option<String>,
85    /// Caller-owned live-config default. This is distinct from an explicit
86    /// request field so an omitted workspace can still prefer the freshly
87    /// loaded durable session under the transaction lock.
88    pub default_workspace_path: Option<String>,
89    pub selected_skill_ids: Option<Vec<String>>,
90    pub workflow_selection: Option<bamboo_skills::WorkflowSelection>,
91    pub orchestration_opt_in: Option<bool>,
92    /// Optional data directory for workspace path fallback when neither request
93    /// nor metadata provides one.
94    pub data_dir: Option<std::path::PathBuf>,
95}
96
97/// Outcome of preparing a chat turn.
98pub struct PreparedChatTurn {
99    pub session: Session,
100}
101
102// ---- Execute types ----
103
104/// Input for the execute preparation use case.
105pub struct ExecuteInput {
106    pub session_id: String,
107    pub request_model: Option<String>,
108    pub request_model_ref: Option<ProviderModelRef>,
109    pub request_provider: Option<String>,
110    pub request_reasoning_effort: Option<ReasoningEffort>,
111    pub request_skill_mode: Option<String>,
112    pub client_sync: Option<ExecuteClientSync>,
113}
114
115/// Client-side sync state sent with execute requests.
116#[derive(Debug, Clone)]
117pub struct ExecuteClientSync {
118    pub client_message_count: usize,
119    pub client_last_message_id: Option<String>,
120    pub client_has_pending_question: bool,
121    pub client_pending_question_tool_call_id: Option<String>,
122}
123
124/// Reason for a sync mismatch between client and server.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub enum ExecuteSyncReason {
127    PendingQuestionMismatch,
128    MessageCountMismatch,
129    LastMessageIdMismatch,
130}
131
132impl ExecuteSyncReason {
133    pub fn as_str(&self) -> &'static str {
134        match self {
135            Self::PendingQuestionMismatch => "pending_question_mismatch",
136            Self::MessageCountMismatch => "message_count_mismatch",
137            Self::LastMessageIdMismatch => "last_message_id_mismatch",
138        }
139    }
140}
141
142/// Server-side snapshot of session state used for sync comparison.
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct ServerExecuteSnapshot {
145    pub message_count: usize,
146    pub last_message_id: Option<String>,
147    pub has_pending_question: bool,
148    pub pending_question_tool_call_id: Option<String>,
149    pub has_pending_user_message: bool,
150}
151
152/// Sync info to include in execute responses.
153#[derive(Debug, Clone)]
154pub struct ExecuteSyncInfo {
155    pub need_sync: bool,
156    pub reason: Option<ExecuteSyncReason>,
157    pub server_message_count: usize,
158    pub server_last_message_id: Option<String>,
159    pub has_pending_question: bool,
160    pub pending_question_tool_call_id: Option<String>,
161    pub has_pending_user_message: bool,
162}
163
164impl ServerExecuteSnapshot {
165    pub fn to_sync_info(&self, reason: Option<ExecuteSyncReason>) -> ExecuteSyncInfo {
166        ExecuteSyncInfo {
167            need_sync: reason.is_some(),
168            reason,
169            server_message_count: self.message_count,
170            server_last_message_id: self.last_message_id.clone(),
171            has_pending_question: self.has_pending_question,
172            pending_question_tool_call_id: self.pending_question_tool_call_id.clone(),
173            has_pending_user_message: self.has_pending_user_message,
174        }
175    }
176}
177
178/// Outcome of preparing an execute.
179pub enum ExecutePreparationOutcome {
180    /// Session is ready for agent execution.
181    Ready {
182        session: Box<Session>,
183        effective_model: String,
184        effective_reasoning_effort: Option<ReasoningEffort>,
185        model_source: &'static str,
186        reasoning_source: &'static str,
187        is_child_session: bool,
188    },
189    /// Agent is already running for this session.
190    AlreadyRunning {
191        server_snapshot: ServerExecuteSnapshot,
192    },
193    /// No pending user message, nothing to execute.
194    NoPendingMessage {
195        server_snapshot: ServerExecuteSnapshot,
196    },
197    /// Client/server state mismatch detected.
198    SyncMismatch {
199        reason: ExecuteSyncReason,
200        server_snapshot: ServerExecuteSnapshot,
201    },
202    /// No model could be resolved.
203    ModelRequired,
204    /// Image fallback validation failed.
205    ImageFallbackError(String),
206}
207
208// ---- Respond types ----
209
210/// Input for the respond use case.
211#[derive(Clone)]
212pub struct RespondInput {
213    pub session_id: String,
214    pub user_response: String,
215    pub model: Option<String>,
216    pub model_ref: Option<ProviderModelRef>,
217    pub provider: Option<String>,
218    pub reasoning_effort: Option<ReasoningEffort>,
219}
220
221/// Outcome of submitting a pending response.
222pub struct SubmitResponseOutcome {
223    pub session: Session,
224    pub user_response: String,
225}
226
227// ---- Resume types ----
228
229/// Resolved configuration snapshot for resume execution.
230///
231/// Captures the subset of config needed to spawn a resumed agent loop,
232/// decoupled from the full server config.
233#[derive(Clone)]
234pub struct ResumeConfigSnapshot {
235    pub provider_name: String,
236    pub provider_type: Option<String>,
237    pub fast_model: Option<String>,
238    pub fast_model_ref: Option<ProviderModelRef>,
239    pub background_model: Option<String>,
240    pub background_model_ref: Option<ProviderModelRef>,
241    pub background_model_provider: Option<Arc<dyn LLMProvider>>,
242    pub summarization_model: Option<String>,
243    pub summarization_model_ref: Option<ProviderModelRef>,
244    pub summarization_model_provider: Option<Arc<dyn LLMProvider>>,
245    pub disabled_tools: BTreeSet<String>,
246    pub disabled_skill_ids: BTreeSet<String>,
247    pub image_fallback: Option<ImageFallbackConfig>,
248    pub gold_config: Option<GoldConfig>,
249}
250
251/// Outcome of a resume attempt.
252#[derive(Debug, Clone, PartialEq, Eq)]
253pub enum ResumeOutcome {
254    /// Execution spawned successfully.
255    Started { run_id: String },
256    /// A runner is already active for this session.
257    AlreadyRunning { run_id: String },
258    /// No pending user message, nothing to execute.
259    Completed,
260    /// Session not found.
261    NotFound,
262}
263
264impl ResumeOutcome {
265    /// Returns the status string (for backward compatibility)
266    pub fn as_str(&self) -> &'static str {
267        self.status_str()
268    }
269
270    pub fn status_str(&self) -> &'static str {
271        match self {
272            Self::Started { .. } => "started",
273            Self::AlreadyRunning { .. } => "already_running",
274            Self::Completed => "completed",
275            Self::NotFound => "error: session not found",
276        }
277    }
278
279    pub fn run_id(&self) -> Option<&String> {
280        match self {
281            Self::Started { run_id } | Self::AlreadyRunning { run_id } => Some(run_id),
282            _ => None,
283        }
284    }
285}