1use super::{App, ForegroundOperation};
2use crate::attachment::AttachmentOutcome;
3use crate::command::{AgentCommand, Command, FilesystemCommand, PromptRejection};
4use crate::request::RequestId;
5use crate::session::session_config_view::LocalConfigView;
6use crate::surfaces::composer::Submission;
7use acp_utils::config_option_id::ConfigOptionId;
8use agent_client_protocol::schema::v2 as acp;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct QueuedPrompt {
12 pub request_id: RequestId,
13 pub submission: Submission,
14}
15
16impl App {
17 pub(super) fn submit(&mut self) {
18 if self.composer.is_empty() || !self.foreground.is_idle() {
19 return;
20 }
21
22 if self.session.workspace_access() == crate::session::WorkspaceAccess::Remote
23 && (!self.composer.selected_mentions().is_empty() || !self.composer.pending_media().is_empty())
24 {
25 self.notify("Path attachments are unavailable for remote workspaces; remove attachments before sending");
26 return;
27 }
28 let submission = self.composer.take_submission();
29 let attachments = submission.attachments();
30
31 self.foreground = ForegroundOperation::PreparingPrompt(submission);
32 if attachments.is_empty() {
33 self.finish_submission(AttachmentOutcome { blocks: Vec::new(), warnings: Vec::new() });
34 } else {
35 self.queue(Command::Filesystem(FilesystemCommand::PrepareSubmission { attachments }));
36 }
37 }
38
39 pub(super) fn finish_submission(&mut self, outcome: AttachmentOutcome) {
40 let Some(submission) = self.foreground.take_prepared_prompt() else {
41 return;
42 };
43 let media_error = self.media_support_error(&outcome.blocks);
44 if media_error.is_some() {
45 self.composer.restore_submission(submission);
46 } else {
47 let content = (!outcome.blocks.is_empty()).then_some(outcome.blocks);
48 self.start_prompt(submission, content);
49 }
50 for warning in &outcome.warnings {
51 self.notify(warning);
52 }
53 if let Some(message) = media_error {
54 self.notify(&message);
55 }
56 }
57
58 pub(super) fn finish_prompt(&mut self, request_id: RequestId, result: Result<(), PromptRejection>) {
59 let Some(index) = self.queued_prompts.iter().position(|prompt| prompt.request_id == request_id) else {
60 return;
61 };
62 let prompt = self.queued_prompts.remove(index);
63 let Err(rejection) = result else { return };
64 self.composer.restore_submission(prompt.submission);
65 if let PromptRejection::Failed(error) = rejection {
66 self.notify(&format!("Failed to send prompt: {error}"));
67 }
68 }
69
70 fn media_support_error(&self, blocks: &[acp::ContentBlock]) -> Option<String> {
71 let requires_image = blocks.iter().any(|block| matches!(block, acp::ContentBlock::Image(_)));
72 let requires_audio = blocks.iter().any(|block| matches!(block, acp::ContentBlock::Audio(_)));
73
74 if !requires_image && !requires_audio {
75 return None;
76 }
77
78 if requires_image && self.session.prompt_capabilities().image.is_none() {
79 return Some("ACP agent does not support image input.".to_string());
80 }
81 if requires_audio && self.session.prompt_capabilities().audio.is_none() {
82 return Some("ACP agent does not support audio input.".to_string());
83 }
84
85 let config = LocalConfigView::new(self.session.config_options());
86 let values = config.current_values(ConfigOptionId::Model);
87 if values.is_empty() {
88 return None;
89 }
90 let selected_meta = config.selected_model_metadata();
91
92 if selected_meta.len() != values.len() {
93 return Some("Current model selection is missing prompt capability metadata.".into());
94 }
95
96 if requires_image && selected_meta.iter().any(|meta| !meta.supports_image) {
97 return Some("Current model selection does not support image input.".to_string());
98 }
99 if requires_audio && selected_meta.iter().any(|meta| !meta.supports_audio) {
100 return Some("Current model selection does not support audio input.".to_string());
101 }
102
103 None
104 }
105
106 pub(super) fn send_prompt_search_query(&mut self, query: String) {
107 if self.composer.prompt_search().is_none() {
108 return;
109 }
110 let params = acp_utils::notifications::PromptSearchParams { query, limit: None };
111 self.queue(Command::Agent(AgentCommand::SearchPrompts(params)));
112 }
113}