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