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