main
rs 282 lines 12.3 KB
Raw
1 use serde::{Deserialize, Serialize};
2
3 use crate::common::{BlockId, ServerConversationToken};
4
5 /// The active base model selection for agent mode.
6 /// This represents the UI state of which model is selected in the model picker chip.
7 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8 pub struct SelectedAgentModel(String);
9
10 impl SelectedAgentModel {
11 pub fn new(model_id: impl Into<String>) -> Self {
12 Self(model_id.into())
13 }
14
15 pub fn model_id(&self) -> &str {
16 &self.0
17 }
18 }
19
20 /// The selected conversation for agent mode.
21 /// When agent view is enabled, this represents the conversation that is currently expanded.
22 /// When agent view is disabled, this represents the conversation the next query will follow up in.
23 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
24 pub enum SelectedConversation {
25 /// An existing conversation identified by a server token
26 ExistingConversation(ServerConversationToken),
27 /// The next query will start a new conversation
28 /// (when agent view is enabled, this looks like an empty expanded view).
29 #[default]
30 NewConversation,
31 /// No conversation selected
32 /// (when agent view is enabled, this means that no agent view is expanded).
33 NoConversation,
34 }
35
36 impl SelectedConversation {
37 pub fn new(server_token: Option<ServerConversationToken>) -> Self {
38 match server_token {
39 Some(token) => Self::ExistingConversation(token),
40 None => Self::NewConversation,
41 }
42 }
43
44 pub fn server_token(&self) -> Option<&ServerConversationToken> {
45 match self {
46 Self::ExistingConversation(token) => Some(token),
47 Self::NewConversation | Self::NoConversation => None,
48 }
49 }
50
51 pub fn is_new_conversation(&self) -> bool {
52 matches!(self, Self::NewConversation)
53 }
54
55 pub fn is_no_conversation(&self) -> bool {
56 matches!(self, Self::NoConversation)
57 }
58 }
59
60 /// The input type for the universal developer input.
61 #[derive(Clone, Default, Deserialize, Serialize, PartialEq, Eq, Debug, Copy)]
62 pub enum InputType {
63 /// The user input is a shell command.
64 #[default]
65 Shell,
66
67 /// The user input is a natural language query to AI.
68 AI,
69 }
70
71 /// The input mode for the universal developer input
72 /// (i.e. the input type and whether the input is locked in said type)
73 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
74 pub struct InputMode {
75 pub input_type: InputType,
76 pub is_locked: bool,
77 }
78
79 impl InputMode {
80 pub fn new(input_type: InputType, is_locked: bool) -> Self {
81 Self {
82 input_type,
83 is_locked,
84 }
85 }
86 }
87
88 /// Whether a CLI agent (e.g. Claude Code, Gemini CLI) is active in the
89 /// terminal. Synced during shared sessions so viewers see the agent footer.
90 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
91 pub enum CLIAgentSessionState {
92 /// A CLI agent is running.
93 Active {
94 /// Serialized `CLIAgent` enum value (e.g. "Claude", "Gemini", "Codex").
95 cli_agent: String,
96 /// Whether the CLI agent rich input composer is open.
97 is_rich_input_open: bool,
98 },
99 /// No CLI agent is running (or the previous one ended).
100 #[default]
101 Inactive,
102 }
103
104 /// How the agent is interacting with the current long running command (if at all).
105 #[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
106 pub enum LongRunningCommandAgentInteractionState {
107 /// The agent is not interacting with any long running command.
108 NotInteracting,
109 /// The user started a long running command and tagged the agent into it.
110 TaggedIn,
111 /// The agent started and is controlling a long running command.
112 InControl,
113 }
114
115 /// How the agent is interacting with a specific long running command block.
116 #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
117 pub struct LongRunningCommandAgentInteraction {
118 pub block_id: BlockId,
119 pub state: LongRunningCommandAgentInteractionState,
120 }
121
122 /// The combined state container for universal developer input context.
123 /// This includes model selection, input mode, and selected conversation.
124 #[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
125 pub struct UniversalDeveloperInputContext {
126 /// Which agent model is selected as the primary model.
127 pub selected_model: Option<SelectedAgentModel>,
128
129 /// The input mode for the universal developer input.
130 pub input_mode: Option<InputMode>,
131
132 /// The selected conversation (identified by server token) for the next agent query.
133 pub selected_conversation: Option<SelectedConversation>,
134
135 /// How the agent is interacting with the current long running command (if at all).
136 /// Deprecated in favor of long_running_command_agent_interaction.
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub long_running_command_agent_interaction_state:
139 Option<LongRunningCommandAgentInteractionState>,
140
141 /// How the agent is interacting with a specific long running command block, if any.
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub long_running_command_agent_interaction: Option<LongRunningCommandAgentInteraction>,
144
145 /// Whether auto-approve is enabled for agent actions.
146 pub auto_approve_agent_actions: Option<bool>,
147
148 /// Whether a CLI agent is active in this terminal.
149 #[serde(default)]
150 pub cli_agent_session: CLIAgentSessionState,
151 }
152
153 /// Update message for universal developer input context - only contains fields that changed.
154 #[derive(Clone, Debug, Deserialize, Serialize, Default)]
155 pub struct UniversalDeveloperInputContextUpdate {
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub selected_model: Option<SelectedAgentModel>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub input_mode: Option<InputMode>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub selected_conversation: Option<SelectedConversation>,
164
165 /// How the agent is interacting with the current long running command (if at all).
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub long_running_command_agent_interaction_state:
168 Option<LongRunningCommandAgentInteractionState>,
169
170 /// How the agent is interacting with a specific long running command block, if any.
171 #[serde(skip_serializing_if = "Option::is_none")]
172 pub long_running_command_agent_interaction: Option<LongRunningCommandAgentInteraction>,
173
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub auto_approve_agent_actions: Option<bool>,
176
177 /// Whether a CLI agent is active. `None` = no change.
178 #[serde(skip_serializing_if = "Option::is_none")]
179 pub cli_agent_session: Option<CLIAgentSessionState>,
180 }
181
182 impl UniversalDeveloperInputContextUpdate {
183 /// Returns true if this update would actually change the given cached context.
184 pub fn changes_cached_context(&self, cached: &UniversalDeveloperInputContext) -> bool {
185 // We destructure here to ensure that, when new fields are added, we check said fields.
186 let UniversalDeveloperInputContextUpdate {
187 selected_model: updated_selected_model,
188 input_mode: updated_input_mode,
189 selected_conversation: updated_selected_conversation,
190 auto_approve_agent_actions: updated_auto_approve_agent_actions,
191 long_running_command_agent_interaction_state:
192 updated_long_running_command_agent_interaction_state,
193 long_running_command_agent_interaction: updated_long_running_command_agent_interaction,
194 cli_agent_session: updated_cli_agent_session,
195 } = self;
196 let UniversalDeveloperInputContext {
197 selected_model: cached_selected_model,
198 input_mode: cached_input_mode,
199 selected_conversation: cached_selected_conversation,
200 auto_approve_agent_actions: cached_auto_approve_agent_actions,
201 long_running_command_agent_interaction_state:
202 cached_long_running_command_agent_interaction_state,
203 long_running_command_agent_interaction: cached_long_running_command_agent_interaction,
204 cli_agent_session: cached_cli_agent_session,
205 } = cached;
206
207 // If any of the fields are present and different from the cached context, return true
208 // (as the update will change the cached context)
209 (updated_selected_model.is_some()
210 && updated_selected_model.as_ref() != cached_selected_model.as_ref())
211 || (updated_input_mode.is_some()
212 && updated_input_mode.as_ref() != cached_input_mode.as_ref())
213 || (updated_selected_conversation.is_some()
214 && updated_selected_conversation != cached_selected_conversation)
215 || (updated_auto_approve_agent_actions.is_some()
216 && updated_auto_approve_agent_actions != cached_auto_approve_agent_actions)
217 || (updated_long_running_command_agent_interaction_state.is_some()
218 && updated_long_running_command_agent_interaction_state
219 != cached_long_running_command_agent_interaction_state)
220 || (updated_long_running_command_agent_interaction.is_some()
221 && updated_long_running_command_agent_interaction.as_ref()
222 != cached_long_running_command_agent_interaction.as_ref())
223 || (updated_cli_agent_session.is_some()
224 && updated_cli_agent_session.as_ref() != Some(cached_cli_agent_session))
225 }
226
227 /// Merges this update into the current context, returning the new merged state.
228 pub fn merge_into(
229 self,
230 current: UniversalDeveloperInputContext,
231 ) -> UniversalDeveloperInputContext {
232 let UniversalDeveloperInputContextUpdate {
233 selected_model: updated_selected_model,
234 input_mode: updated_input_mode,
235 selected_conversation: updated_selected_conversation,
236 auto_approve_agent_actions: updated_auto_approve_agent_actions,
237 long_running_command_agent_interaction_state:
238 updated_long_running_command_agent_interaction_state,
239 long_running_command_agent_interaction: updated_long_running_command_agent_interaction,
240 cli_agent_session: updated_cli_agent_session,
241 } = self;
242 let UniversalDeveloperInputContext {
243 selected_model: current_selected_model,
244 input_mode: current_input_mode,
245 selected_conversation: current_selected_conversation,
246 auto_approve_agent_actions: current_auto_approve_agent_actions,
247 long_running_command_agent_interaction_state:
248 current_long_running_command_agent_interaction_state,
249 long_running_command_agent_interaction: current_long_running_command_agent_interaction,
250 cli_agent_session: current_cli_agent_session,
251 } = current;
252
253 UniversalDeveloperInputContext {
254 selected_model: updated_selected_model.or(current_selected_model),
255 input_mode: updated_input_mode.or(current_input_mode),
256 selected_conversation: updated_selected_conversation.or(current_selected_conversation),
257 auto_approve_agent_actions: updated_auto_approve_agent_actions
258 .or(current_auto_approve_agent_actions),
259 long_running_command_agent_interaction_state:
260 updated_long_running_command_agent_interaction_state
261 .or(current_long_running_command_agent_interaction_state),
262 long_running_command_agent_interaction: updated_long_running_command_agent_interaction
263 .or(current_long_running_command_agent_interaction),
264 cli_agent_session: updated_cli_agent_session.unwrap_or(current_cli_agent_session),
265 }
266 }
267 }
268
269 impl From<UniversalDeveloperInputContext> for UniversalDeveloperInputContextUpdate {
270 fn from(context: UniversalDeveloperInputContext) -> Self {
271 Self {
272 selected_model: context.selected_model,
273 input_mode: context.input_mode,
274 selected_conversation: context.selected_conversation,
275 auto_approve_agent_actions: context.auto_approve_agent_actions,
276 long_running_command_agent_interaction_state: context
277 .long_running_command_agent_interaction_state,
278 long_running_command_agent_interaction: context.long_running_command_agent_interaction,
279 cli_agent_session: Some(context.cli_agent_session),
280 }
281 }
282 }