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        /// The Schedule binding observed before the model cascade. The outer
189        /// option distinguishes a Schedule session from an ordinary one.
190        scheduled_binding: Option<Option<ProviderModelRef>>,
191        /// Original durable Schedule profile for the atomic startup save.
192        scheduled_startup_expected: Option<Box<Session>>,
193    },
194    /// Agent is already running for this session.
195    AlreadyRunning {
196        server_snapshot: ServerExecuteSnapshot,
197    },
198    /// No pending user message, nothing to execute.
199    NoPendingMessage {
200        server_snapshot: ServerExecuteSnapshot,
201    },
202    /// Client/server state mismatch detected.
203    SyncMismatch {
204        reason: ExecuteSyncReason,
205        server_snapshot: ServerExecuteSnapshot,
206    },
207    /// No model could be resolved.
208    ModelRequired,
209    /// Image fallback validation failed.
210    ImageFallbackError(String),
211}
212
213// ---- Respond types ----
214
215/// Input for the respond use case.
216#[derive(Clone)]
217pub struct RespondInput {
218    pub session_id: String,
219    pub user_response: String,
220    pub model: Option<String>,
221    pub model_ref: Option<ProviderModelRef>,
222    pub provider: Option<String>,
223    pub reasoning_effort: Option<ReasoningEffort>,
224}
225
226/// Outcome of submitting a pending response.
227pub struct SubmitResponseOutcome {
228    pub session: Session,
229    pub user_response: String,
230}
231
232// ---- Resume types ----
233
234/// Resolved configuration snapshot for resume execution.
235///
236/// Captures the subset of config needed to spawn a resumed agent loop,
237/// decoupled from the full server config.
238#[derive(Clone)]
239pub struct ResumeConfigSnapshot {
240    pub provider_name: String,
241    pub provider_type: Option<String>,
242    pub fast_model: Option<String>,
243    pub fast_model_ref: Option<ProviderModelRef>,
244    pub background_model: Option<String>,
245    pub background_model_ref: Option<ProviderModelRef>,
246    pub background_model_provider: Option<Arc<dyn LLMProvider>>,
247    pub summarization_model: Option<String>,
248    pub summarization_model_ref: Option<ProviderModelRef>,
249    pub summarization_model_provider: Option<Arc<dyn LLMProvider>>,
250    pub disabled_tools: BTreeSet<String>,
251    pub disabled_skill_ids: BTreeSet<String>,
252    pub image_fallback: Option<ImageFallbackConfig>,
253    pub gold_config: Option<GoldConfig>,
254}
255
256/// Outcome of a resume attempt.
257#[derive(Debug, Clone, PartialEq, Eq)]
258pub enum ResumeOutcome {
259    /// Execution spawned successfully.
260    Started { run_id: String },
261    /// A runner is already active for this session.
262    AlreadyRunning { run_id: String },
263    /// No pending user message, nothing to execute.
264    Completed,
265    /// Session not found.
266    NotFound,
267}
268
269impl ResumeOutcome {
270    /// Returns the status string (for backward compatibility)
271    pub fn as_str(&self) -> &'static str {
272        self.status_str()
273    }
274
275    pub fn status_str(&self) -> &'static str {
276        match self {
277            Self::Started { .. } => "started",
278            Self::AlreadyRunning { .. } => "already_running",
279            Self::Completed => "completed",
280            Self::NotFound => "error: session not found",
281        }
282    }
283
284    pub fn run_id(&self) -> Option<&String> {
285        match self {
286            Self::Started { run_id } | Self::AlreadyRunning { run_id } => Some(run_id),
287            _ => None,
288        }
289    }
290}