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 input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
93 pub keyboard_protocol: KeyboardProtocolSettings,
94 pub workspace_root: Option<PathBuf>,
95 pub slash_commands: Vec<SlashCommandItem>,
96 pub appearance: Option<AppearanceConfig>,
97 pub app_name: String,
98 pub non_interactive_hint: Option<String>,
99}
100
101impl Default for SessionOptions {
102 fn default() -> Self {
103 Self {
104 placeholder: None,
105 surface_preference: SessionSurface::Auto,
106 inline_rows: crate::config::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS,
107 event_callback: None,
108 focus_callback: None,
109 active_pty_sessions: None,
110 input_activity_counter: None,
111 keyboard_protocol: KeyboardProtocolSettings::default(),
112 workspace_root: None,
113 slash_commands: Vec::new(),
114 appearance: None,
115 app_name: "Agent TUI".to_string(),
116 non_interactive_hint: None,
117 }
118 }
119}
120
121impl SessionOptions {
122 pub fn from_host(host: &impl crate::host::HostAdapter) -> Self {
124 let defaults = host.session_defaults();
125 Self {
126 surface_preference: defaults.surface_preference,
127 inline_rows: defaults.inline_rows,
128 keyboard_protocol: defaults.keyboard_protocol,
129 workspace_root: host.workspace_root(),
130 slash_commands: host.slash_commands(),
131 app_name: host.app_name(),
132 non_interactive_hint: host.non_interactive_hint(),
133 ..Self::default()
134 }
135 }
136}
137
138pub fn spawn_session_with_options(
140 theme: InlineTheme,
141 options: SessionOptions,
142) -> anyhow::Result<InlineSession> {
143 crate::core_tui::spawn_session_with_prompts_and_options(
144 theme,
145 options.placeholder,
146 options.surface_preference.into(),
147 options.inline_rows,
148 options.event_callback,
149 options.focus_callback,
150 options.active_pty_sessions,
151 options.input_activity_counter,
152 options.keyboard_protocol.into(),
153 options.workspace_root,
154 options.slash_commands,
155 options.appearance,
156 options.app_name,
157 options.non_interactive_hint,
158 )
159}
160
161pub fn spawn_session_with_host(
163 theme: InlineTheme,
164 host: &impl crate::host::HostAdapter,
165) -> anyhow::Result<InlineSession> {
166 spawn_session_with_options(theme, SessionOptions::from_host(host))
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 struct DemoHost;
174
175 impl crate::host::WorkspaceInfoProvider for DemoHost {
176 fn workspace_name(&self) -> String {
177 "demo".to_string()
178 }
179
180 fn workspace_root(&self) -> Option<PathBuf> {
181 Some(PathBuf::from("/workspace/demo"))
182 }
183 }
184
185 impl crate::host::NotificationProvider for DemoHost {
186 fn set_terminal_focused(&self, _focused: bool) {}
187 }
188
189 impl crate::host::ThemeProvider for DemoHost {
190 fn available_themes(&self) -> Vec<String> {
191 vec!["default".to_string()]
192 }
193
194 fn active_theme_name(&self) -> Option<String> {
195 Some("default".to_string())
196 }
197 }
198
199 impl crate::host::HostAdapter for DemoHost {
200 fn session_defaults(&self) -> crate::host::HostSessionDefaults {
201 crate::host::HostSessionDefaults {
202 surface_preference: SessionSurface::Inline,
203 inline_rows: 24,
204 keyboard_protocol: KeyboardProtocolSettings::default(),
205 }
206 }
207 }
208
209 #[test]
210 fn session_surface_conversion_roundtrip() {
211 let variants = [
212 SessionSurface::Auto,
213 SessionSurface::Alternate,
214 SessionSurface::Inline,
215 ];
216
217 for variant in variants {
218 let converted: crate::config::UiSurfacePreference = variant.into();
219 let roundtrip = SessionSurface::from(converted);
220 assert_eq!(variant, roundtrip);
221 }
222 }
223
224 #[test]
225 fn keyboard_protocol_conversion_roundtrip() {
226 let settings = KeyboardProtocolSettings {
227 enabled: true,
228 mode: "custom".to_string(),
229 disambiguate_escape_codes: true,
230 report_event_types: false,
231 report_alternate_keys: true,
232 report_all_keys: false,
233 };
234
235 let config: KeyboardProtocolConfig = settings.clone().into();
236 let restored = KeyboardProtocolSettings::from(config);
237
238 assert_eq!(settings, restored);
239 }
240
241 #[test]
242 fn session_options_from_host_uses_defaults() {
243 let options = SessionOptions::from_host(&DemoHost);
244
245 assert_eq!(options.surface_preference, SessionSurface::Inline);
246 assert_eq!(options.inline_rows, 24);
247 assert_eq!(
248 options.workspace_root,
249 Some(PathBuf::from("/workspace/demo"))
250 );
251 }
252}