1use std::path::PathBuf;
2use std::sync::Arc;
3
4use crate::config::KeyboardProtocolConfig;
5use crate::core_tui::session::config::AppearanceConfig;
6
7use crate::{
8 FocusChangeCallback, InlineEventCallback, InlineSession, InlineTheme, SlashCommandItem,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum SessionSurface {
14 #[default]
15 Auto,
16 Alternate,
17 Inline,
18}
19
20impl From<SessionSurface> for crate::config::UiSurfacePreference {
21 fn from(value: SessionSurface) -> Self {
22 match value {
23 SessionSurface::Auto => Self::Auto,
24 SessionSurface::Alternate => Self::Alternate,
25 SessionSurface::Inline => Self::Inline,
26 }
27 }
28}
29
30impl From<crate::config::UiSurfacePreference> for SessionSurface {
31 fn from(value: crate::config::UiSurfacePreference) -> Self {
32 match value {
33 crate::config::UiSurfacePreference::Auto => Self::Auto,
34 crate::config::UiSurfacePreference::Alternate => Self::Alternate,
35 crate::config::UiSurfacePreference::Inline => Self::Inline,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct KeyboardProtocolSettings {
43 pub enabled: bool,
44 pub mode: String,
45 pub disambiguate_escape_codes: bool,
46 pub report_event_types: bool,
47 pub report_alternate_keys: bool,
48 pub report_all_keys: bool,
49}
50
51impl Default for KeyboardProtocolSettings {
52 fn default() -> Self {
53 Self::from(KeyboardProtocolConfig::default())
54 }
55}
56
57impl From<KeyboardProtocolConfig> for KeyboardProtocolSettings {
58 fn from(value: KeyboardProtocolConfig) -> Self {
59 Self {
60 enabled: value.enabled,
61 mode: value.mode,
62 disambiguate_escape_codes: value.disambiguate_escape_codes,
63 report_event_types: value.report_event_types,
64 report_alternate_keys: value.report_alternate_keys,
65 report_all_keys: value.report_all_keys,
66 }
67 }
68}
69
70impl From<KeyboardProtocolSettings> for KeyboardProtocolConfig {
71 fn from(value: KeyboardProtocolSettings) -> Self {
72 Self {
73 enabled: value.enabled,
74 mode: value.mode,
75 disambiguate_escape_codes: value.disambiguate_escape_codes,
76 report_event_types: value.report_event_types,
77 report_alternate_keys: value.report_alternate_keys,
78 report_all_keys: value.report_all_keys,
79 }
80 }
81}
82
83#[derive(Clone)]
85pub struct SessionOptions {
86 pub placeholder: Option<String>,
87 pub surface_preference: SessionSurface,
88 pub inline_rows: u16,
89 pub event_callback: Option<InlineEventCallback>,
90 pub focus_callback: Option<FocusChangeCallback>,
91 pub active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
92 pub keyboard_protocol: KeyboardProtocolSettings,
93 pub workspace_root: Option<PathBuf>,
94 pub slash_commands: Vec<SlashCommandItem>,
95 pub appearance: Option<AppearanceConfig>,
96 pub app_name: String,
97 pub non_interactive_hint: Option<String>,
98}
99
100impl Default for SessionOptions {
101 fn default() -> Self {
102 Self {
103 placeholder: None,
104 surface_preference: SessionSurface::Auto,
105 inline_rows: crate::config::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS,
106 event_callback: None,
107 focus_callback: None,
108 active_pty_sessions: None,
109 keyboard_protocol: KeyboardProtocolSettings::default(),
110 workspace_root: None,
111 slash_commands: Vec::new(),
112 appearance: None,
113 app_name: "Agent TUI".to_string(),
114 non_interactive_hint: None,
115 }
116 }
117}
118
119impl SessionOptions {
120 pub fn from_host(host: &impl crate::host::HostAdapter) -> Self {
122 let defaults = host.session_defaults();
123 Self {
124 surface_preference: defaults.surface_preference,
125 inline_rows: defaults.inline_rows,
126 keyboard_protocol: defaults.keyboard_protocol,
127 workspace_root: host.workspace_root(),
128 slash_commands: host.slash_commands(),
129 app_name: host.app_name(),
130 non_interactive_hint: host.non_interactive_hint(),
131 ..Self::default()
132 }
133 }
134}
135
136pub fn spawn_session_with_options(
138 theme: InlineTheme,
139 options: SessionOptions,
140) -> anyhow::Result<InlineSession> {
141 crate::core_tui::spawn_session_with_prompts_and_options(
142 theme,
143 options.placeholder,
144 options.surface_preference.into(),
145 options.inline_rows,
146 options.event_callback,
147 options.focus_callback,
148 options.active_pty_sessions,
149 options.keyboard_protocol.into(),
150 options.workspace_root,
151 options.slash_commands,
152 options.appearance,
153 options.app_name,
154 options.non_interactive_hint,
155 )
156}
157
158pub fn spawn_session_with_host(
160 theme: InlineTheme,
161 host: &impl crate::host::HostAdapter,
162) -> anyhow::Result<InlineSession> {
163 spawn_session_with_options(theme, SessionOptions::from_host(host))
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169
170 struct DemoHost;
171
172 impl crate::host::WorkspaceInfoProvider for DemoHost {
173 fn workspace_name(&self) -> String {
174 "demo".to_string()
175 }
176
177 fn workspace_root(&self) -> Option<PathBuf> {
178 Some(PathBuf::from("/workspace/demo"))
179 }
180 }
181
182 impl crate::host::NotificationProvider for DemoHost {
183 fn set_terminal_focused(&self, _focused: bool) {}
184 }
185
186 impl crate::host::ThemeProvider for DemoHost {
187 fn available_themes(&self) -> Vec<String> {
188 vec!["default".to_string()]
189 }
190
191 fn active_theme_name(&self) -> Option<String> {
192 Some("default".to_string())
193 }
194 }
195
196 impl crate::host::HostAdapter for DemoHost {
197 fn session_defaults(&self) -> crate::host::HostSessionDefaults {
198 crate::host::HostSessionDefaults {
199 surface_preference: SessionSurface::Inline,
200 inline_rows: 24,
201 keyboard_protocol: KeyboardProtocolSettings::default(),
202 }
203 }
204 }
205
206 #[test]
207 fn session_surface_conversion_roundtrip() {
208 let variants = [
209 SessionSurface::Auto,
210 SessionSurface::Alternate,
211 SessionSurface::Inline,
212 ];
213
214 for variant in variants {
215 let converted: crate::config::UiSurfacePreference = variant.into();
216 let roundtrip = SessionSurface::from(converted);
217 assert_eq!(variant, roundtrip);
218 }
219 }
220
221 #[test]
222 fn keyboard_protocol_conversion_roundtrip() {
223 let settings = KeyboardProtocolSettings {
224 enabled: true,
225 mode: "custom".to_string(),
226 disambiguate_escape_codes: true,
227 report_event_types: false,
228 report_alternate_keys: true,
229 report_all_keys: false,
230 };
231
232 let config: KeyboardProtocolConfig = settings.clone().into();
233 let restored = KeyboardProtocolSettings::from(config);
234
235 assert_eq!(settings, restored);
236 }
237
238 #[test]
239 fn session_options_from_host_uses_defaults() {
240 let options = SessionOptions::from_host(&DemoHost);
241
242 assert_eq!(options.surface_preference, SessionSurface::Inline);
243 assert_eq!(options.inline_rows, 24);
244 assert_eq!(
245 options.workspace_root,
246 Some(PathBuf::from("/workspace/demo"))
247 );
248 }
249}