1use crate::{ContextServerRegistry, SystemPromptTemplate, Template, Templates};
2use acp_thread::{MentionUri, UserMessageId};
3use action_log::ActionLog;
4use agent_client_protocol as acp;
5use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
6use anyhow::{Context as _, Result, anyhow};
7use assistant_tool::adapt_schema_to_format;
8use cloud_llm_client::{CompletionIntent, CompletionRequestStatus};
9use collections::IndexMap;
10use fs::Fs;
11use futures::{
12 channel::{mpsc, oneshot},
13 stream::FuturesUnordered,
14};
15use gpui::{App, Context, Entity, SharedString, Task};
16use language_model::{
17 LanguageModel, LanguageModelCompletionEvent, LanguageModelImage, LanguageModelProviderId,
18 LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,
19 LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat,
20 LanguageModelToolUse, LanguageModelToolUseId, Role, StopReason,
21};
22use project::Project;
23use prompt_store::ProjectContext;
24use schemars::{JsonSchema, Schema};
25use serde::{Deserialize, Serialize};
26use settings::{Settings, update_settings_file};
27use smol::stream::StreamExt;
28use std::{cell::RefCell, collections::BTreeMap, path::Path, rc::Rc, sync::Arc};
29use std::{fmt::Write, ops::Range};
30use util::{ResultExt, markdown::MarkdownCodeBlock};
31use uuid::Uuid;
32
33#[derive(
34 Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
35)]
36pub struct ThreadId(Arc<str>);
37
38impl ThreadId {
39 pub fn new() -> Self {
40 Self(Uuid::new_v4().to_string().into())
41 }
42}
43
44impl std::fmt::Display for ThreadId {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 write!(f, "{}", self.0)
47 }
48}
49
50impl From<&str> for ThreadId {
51 fn from(value: &str) -> Self {
52 Self(value.into())
53 }
54}
55
56/// The ID of the user prompt that initiated a request.
57///
58/// This equates to the user physically submitting a message to the model (e.g., by pressing the Enter key).
59#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
60pub struct PromptId(Arc<str>);
61
62impl PromptId {
63 pub fn new() -> Self {
64 Self(Uuid::new_v4().to_string().into())
65 }
66}
67
68impl std::fmt::Display for PromptId {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 write!(f, "{}", self.0)
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum Message {
76 User(UserMessage),
77 Agent(AgentMessage),
78 Resume,
79}
80
81impl Message {
82 pub fn as_agent_message(&self) -> Option<&AgentMessage> {
83 match self {
84 Message::Agent(agent_message) => Some(agent_message),
85 _ => None,
86 }
87 }
88
89 pub fn to_markdown(&self) -> String {
90 match self {
91 Message::User(message) => message.to_markdown(),
92 Message::Agent(message) => message.to_markdown(),
93 Message::Resume => "[resumed after tool use limit was reached]".into(),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct UserMessage {
100 pub id: UserMessageId,
101 pub content: Vec<UserMessageContent>,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum UserMessageContent {
106 Text(String),
107 Mention { uri: MentionUri, content: String },
108 Image(LanguageModelImage),
109}
110
111impl UserMessage {
112 pub fn to_markdown(&self) -> String {
113 let mut markdown = String::from("## User\n\n");
114
115 for content in &self.content {
116 match content {
117 UserMessageContent::Text(text) => {
118 markdown.push_str(text);
119 markdown.push('\n');
120 }
121 UserMessageContent::Image(_) => {
122 markdown.push_str("<image />\n");
123 }
124 UserMessageContent::Mention { uri, content } => {
125 if !content.is_empty() {
126 let _ = write!(&mut markdown, "{}\n\n{}\n", uri.as_link(), content);
127 } else {
128 let _ = write!(&mut markdown, "{}\n", uri.as_link());
129 }
130 }
131 }
132 }
133
134 markdown
135 }
136
137 fn to_request(&self) -> LanguageModelRequestMessage {
138 let mut message = LanguageModelRequestMessage {
139 role: Role::User,
140 content: Vec::with_capacity(self.content.len()),
141 cache: false,
142 };
143
144 const OPEN_CONTEXT: &str = "<context>\n\
145 The following items were attached by the user. \
146 They are up-to-date and don't need to be re-read.\n\n";
147
148 const OPEN_FILES_TAG: &str = "<files>";
149 const OPEN_DIRECTORIES_TAG: &str = "<directories>";
150 const OPEN_SYMBOLS_TAG: &str = "<symbols>";
151 const OPEN_THREADS_TAG: &str = "<threads>";
152 const OPEN_FETCH_TAG: &str = "<fetched_urls>";
153 const OPEN_RULES_TAG: &str =
154 "<rules>\nThe user has specified the following rules that should be applied:\n";
155
156 let mut file_context = OPEN_FILES_TAG.to_string();
157 let mut directory_context = OPEN_DIRECTORIES_TAG.to_string();
158 let mut symbol_context = OPEN_SYMBOLS_TAG.to_string();
159 let mut thread_context = OPEN_THREADS_TAG.to_string();
160 let mut fetch_context = OPEN_FETCH_TAG.to_string();
161 let mut rules_context = OPEN_RULES_TAG.to_string();
162
163 for chunk in &self.content {
164 let chunk = match chunk {
165 UserMessageContent::Text(text) => {
166 language_model::MessageContent::Text(text.clone())
167 }
168 UserMessageContent::Image(value) => {
169 language_model::MessageContent::Image(value.clone())
170 }
171 UserMessageContent::Mention { uri, content } => {
172 match uri {
173 MentionUri::File { abs_path } => {
174 write!(
175 &mut symbol_context,
176 "\n{}",
177 MarkdownCodeBlock {
178 tag: &codeblock_tag(abs_path, None),
179 text: &content.to_string(),
180 }
181 )
182 .ok();
183 }
184 MentionUri::Directory { .. } => {
185 write!(&mut directory_context, "\n{}\n", content).ok();
186 }
187 MentionUri::Symbol {
188 path, line_range, ..
189 }
190 | MentionUri::Selection {
191 path, line_range, ..
192 } => {
193 write!(
194 &mut rules_context,
195 "\n{}",
196 MarkdownCodeBlock {
197 tag: &codeblock_tag(path, Some(line_range)),
198 text: content
199 }
200 )
201 .ok();
202 }
203 MentionUri::Thread { .. } => {
204 write!(&mut thread_context, "\n{}\n", content).ok();
205 }
206 MentionUri::TextThread { .. } => {
207 write!(&mut thread_context, "\n{}\n", content).ok();
208 }
209 MentionUri::Rule { .. } => {
210 write!(
211 &mut rules_context,
212 "\n{}",
213 MarkdownCodeBlock {
214 tag: "",
215 text: content
216 }
217 )
218 .ok();
219 }
220 MentionUri::Fetch { url } => {
221 write!(&mut fetch_context, "\nFetch: {}\n\n{}", url, content).ok();
222 }
223 }
224
225 language_model::MessageContent::Text(uri.as_link().to_string())
226 }
227 };
228
229 message.content.push(chunk);
230 }
231
232 let len_before_context = message.content.len();
233
234 if file_context.len() > OPEN_FILES_TAG.len() {
235 file_context.push_str("</files>\n");
236 message
237 .content
238 .push(language_model::MessageContent::Text(file_context));
239 }
240
241 if directory_context.len() > OPEN_DIRECTORIES_TAG.len() {
242 directory_context.push_str("</directories>\n");
243 message
244 .content
245 .push(language_model::MessageContent::Text(directory_context));
246 }
247
248 if symbol_context.len() > OPEN_SYMBOLS_TAG.len() {
249 symbol_context.push_str("</symbols>\n");
250 message
251 .content
252 .push(language_model::MessageContent::Text(symbol_context));
253 }
254
255 if thread_context.len() > OPEN_THREADS_TAG.len() {
256 thread_context.push_str("</threads>\n");
257 message
258 .content
259 .push(language_model::MessageContent::Text(thread_context));
260 }
261
262 if fetch_context.len() > OPEN_FETCH_TAG.len() {
263 fetch_context.push_str("</fetched_urls>\n");
264 message
265 .content
266 .push(language_model::MessageContent::Text(fetch_context));
267 }
268
269 if rules_context.len() > OPEN_RULES_TAG.len() {
270 rules_context.push_str("</user_rules>\n");
271 message
272 .content
273 .push(language_model::MessageContent::Text(rules_context));
274 }
275
276 if message.content.len() > len_before_context {
277 message.content.insert(
278 len_before_context,
279 language_model::MessageContent::Text(OPEN_CONTEXT.into()),
280 );
281 message
282 .content
283 .push(language_model::MessageContent::Text("</context>".into()));
284 }
285
286 message
287 }
288}
289
290fn codeblock_tag(full_path: &Path, line_range: Option<&Range<u32>>) -> String {
291 let mut result = String::new();
292
293 if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) {
294 let _ = write!(result, "{} ", extension);
295 }
296
297 let _ = write!(result, "{}", full_path.display());
298
299 if let Some(range) = line_range {
300 if range.start == range.end {
301 let _ = write!(result, ":{}", range.start + 1);
302 } else {
303 let _ = write!(result, ":{}-{}", range.start + 1, range.end + 1);
304 }
305 }
306
307 result
308}
309
310impl AgentMessage {
311 pub fn to_markdown(&self) -> String {
312 let mut markdown = String::from("## Assistant\n\n");
313
314 for content in &self.content {
315 match content {
316 AgentMessageContent::Text(text) => {
317 markdown.push_str(text);
318 markdown.push('\n');
319 }
320 AgentMessageContent::Thinking { text, .. } => {
321 markdown.push_str("<think>");
322 markdown.push_str(text);
323 markdown.push_str("</think>\n");
324 }
325 AgentMessageContent::RedactedThinking(_) => {
326 markdown.push_str("<redacted_thinking />\n")
327 }
328 AgentMessageContent::Image(_) => {
329 markdown.push_str("<image />\n");
330 }
331 AgentMessageContent::ToolUse(tool_use) => {
332 markdown.push_str(&format!(
333 "**Tool Use**: {} (ID: {})\n",
334 tool_use.name, tool_use.id
335 ));
336 markdown.push_str(&format!(
337 "{}\n",
338 MarkdownCodeBlock {
339 tag: "json",
340 text: &format!("{:#}", tool_use.input)
341 }
342 ));
343 }
344 }
345 }
346
347 for tool_result in self.tool_results.values() {
348 markdown.push_str(&format!(
349 "**Tool Result**: {} (ID: {})\n\n",
350 tool_result.tool_name, tool_result.tool_use_id
351 ));
352 if tool_result.is_error {
353 markdown.push_str("**ERROR:**\n");
354 }
355
356 match &tool_result.content {
357 LanguageModelToolResultContent::Text(text) => {
358 writeln!(markdown, "{text}\n").ok();
359 }
360 LanguageModelToolResultContent::Image(_) => {
361 writeln!(markdown, "<image />\n").ok();
362 }
363 }
364
365 if let Some(output) = tool_result.output.as_ref() {
366 writeln!(
367 markdown,
368 "**Debug Output**:\n\n```json\n{}\n```\n",
369 serde_json::to_string_pretty(output).unwrap()
370 )
371 .unwrap();
372 }
373 }
374
375 markdown
376 }
377
378 pub fn to_request(&self) -> Vec<LanguageModelRequestMessage> {
379 let mut assistant_message = LanguageModelRequestMessage {
380 role: Role::Assistant,
381 content: Vec::with_capacity(self.content.len()),
382 cache: false,
383 };
384 for chunk in &self.content {
385 let chunk = match chunk {
386 AgentMessageContent::Text(text) => {
387 language_model::MessageContent::Text(text.clone())
388 }
389 AgentMessageContent::Thinking { text, signature } => {
390 language_model::MessageContent::Thinking {
391 text: text.clone(),
392 signature: signature.clone(),
393 }
394 }
395 AgentMessageContent::RedactedThinking(value) => {
396 language_model::MessageContent::RedactedThinking(value.clone())
397 }
398 AgentMessageContent::ToolUse(value) => {
399 language_model::MessageContent::ToolUse(value.clone())
400 }
401 AgentMessageContent::Image(value) => {
402 language_model::MessageContent::Image(value.clone())
403 }
404 };
405 assistant_message.content.push(chunk);
406 }
407
408 let mut user_message = LanguageModelRequestMessage {
409 role: Role::User,
410 content: Vec::new(),
411 cache: false,
412 };
413
414 for tool_result in self.tool_results.values() {
415 user_message
416 .content
417 .push(language_model::MessageContent::ToolResult(
418 tool_result.clone(),
419 ));
420 }
421
422 let mut messages = Vec::new();
423 if !assistant_message.content.is_empty() {
424 messages.push(assistant_message);
425 }
426 if !user_message.content.is_empty() {
427 messages.push(user_message);
428 }
429 messages
430 }
431}
432
433#[derive(Default, Debug, Clone, PartialEq, Eq)]
434pub struct AgentMessage {
435 pub content: Vec<AgentMessageContent>,
436 pub tool_results: IndexMap<LanguageModelToolUseId, LanguageModelToolResult>,
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub enum AgentMessageContent {
441 Text(String),
442 Thinking {
443 text: String,
444 signature: Option<String>,
445 },
446 RedactedThinking(String),
447 Image(LanguageModelImage),
448 ToolUse(LanguageModelToolUse),
449}
450
451#[derive(Debug)]
452pub enum AgentResponseEvent {
453 Text(String),
454 Thinking(String),
455 ToolCall(acp::ToolCall),
456 ToolCallUpdate(acp_thread::ToolCallUpdate),
457 ToolCallAuthorization(ToolCallAuthorization),
458 Stop(acp::StopReason),
459}
460
461#[derive(Debug)]
462pub struct ToolCallAuthorization {
463 pub tool_call: acp::ToolCallUpdate,
464 pub options: Vec<acp::PermissionOption>,
465 pub response: oneshot::Sender<acp::PermissionOptionId>,
466}
467
468pub struct Thread {
469 id: ThreadId,
470 prompt_id: PromptId,
471 messages: Vec<Message>,
472 completion_mode: CompletionMode,
473 /// Holds the task that handles agent interaction until the end of the turn.
474 /// Survives across multiple requests as the model performs tool calls and
475 /// we run tools, report their results.
476 running_turn: Option<RunningTurn>,
477 pending_message: Option<AgentMessage>,
478 tools: BTreeMap<SharedString, Arc<dyn AnyAgentTool>>,
479 tool_use_limit_reached: bool,
480 context_server_registry: Entity<ContextServerRegistry>,
481 profile_id: AgentProfileId,
482 project_context: Rc<RefCell<ProjectContext>>,
483 templates: Arc<Templates>,
484 model: Option<Arc<dyn LanguageModel>>,
485 project: Entity<Project>,
486 action_log: Entity<ActionLog>,
487}
488
489impl Thread {
490 pub fn new(
491 project: Entity<Project>,
492 project_context: Rc<RefCell<ProjectContext>>,
493 context_server_registry: Entity<ContextServerRegistry>,
494 action_log: Entity<ActionLog>,
495 templates: Arc<Templates>,
496 model: Option<Arc<dyn LanguageModel>>,
497 cx: &mut Context<Self>,
498 ) -> Self {
499 let profile_id = AgentSettings::get_global(cx).default_profile.clone();
500 Self {
501 id: ThreadId::new(),
502 prompt_id: PromptId::new(),
503 messages: Vec::new(),
504 completion_mode: CompletionMode::Normal,
505 running_turn: None,
506 pending_message: None,
507 tools: BTreeMap::default(),
508 tool_use_limit_reached: false,
509 context_server_registry,
510 profile_id,
511 project_context,
512 templates,
513 model,
514 project,
515 action_log,
516 }
517 }
518
519 pub fn project(&self) -> &Entity<Project> {
520 &self.project
521 }
522
523 pub fn action_log(&self) -> &Entity<ActionLog> {
524 &self.action_log
525 }
526
527 pub fn model(&self) -> Option<&Arc<dyn LanguageModel>> {
528 self.model.as_ref()
529 }
530
531 pub fn set_model(&mut self, model: Arc<dyn LanguageModel>) {
532 self.model = Some(model);
533 }
534
535 pub fn completion_mode(&self) -> CompletionMode {
536 self.completion_mode
537 }
538
539 pub fn set_completion_mode(&mut self, mode: CompletionMode) {
540 self.completion_mode = mode;
541 }
542
543 #[cfg(any(test, feature = "test-support"))]
544 pub fn last_message(&self) -> Option<Message> {
545 if let Some(message) = self.pending_message.clone() {
546 Some(Message::Agent(message))
547 } else {
548 self.messages.last().cloned()
549 }
550 }
551
552 pub fn add_tool(&mut self, tool: impl AgentTool) {
553 self.tools.insert(tool.name(), tool.erase());
554 }
555
556 pub fn remove_tool(&mut self, name: &str) -> bool {
557 self.tools.remove(name).is_some()
558 }
559
560 pub fn profile(&self) -> &AgentProfileId {
561 &self.profile_id
562 }
563
564 pub fn set_profile(&mut self, profile_id: AgentProfileId) {
565 self.profile_id = profile_id;
566 }
567
568 pub fn cancel(&mut self) {
569 if let Some(running_turn) = self.running_turn.take() {
570 running_turn.cancel();
571 }
572 self.flush_pending_message();
573 }
574
575 pub fn truncate(&mut self, message_id: UserMessageId) -> Result<()> {
576 self.cancel();
577 let Some(position) = self.messages.iter().position(
578 |msg| matches!(msg, Message::User(UserMessage { id, .. }) if id == &message_id),
579 ) else {
580 return Err(anyhow!("Message not found"));
581 };
582 self.messages.truncate(position);
583 Ok(())
584 }
585
586 pub fn resume(
587 &mut self,
588 cx: &mut Context<Self>,
589 ) -> Result<mpsc::UnboundedReceiver<Result<AgentResponseEvent>>> {
590 anyhow::ensure!(self.model.is_some(), "Model not set");
591 anyhow::ensure!(
592 self.tool_use_limit_reached,
593 "can only resume after tool use limit is reached"
594 );
595
596 self.messages.push(Message::Resume);
597 cx.notify();
598
599 log::info!("Total messages in thread: {}", self.messages.len());
600 self.run_turn(cx)
601 }
602
603 /// Sending a message results in the model streaming a response, which could include tool calls.
604 /// After calling tools, the model will stops and waits for any outstanding tool calls to be completed and their results sent.
605 /// The returned channel will report all the occurrences in which the model stops before erroring or ending its turn.
606 pub fn send<T>(
607 &mut self,
608 id: UserMessageId,
609 content: impl IntoIterator<Item = T>,
610 cx: &mut Context<Self>,
611 ) -> Result<mpsc::UnboundedReceiver<Result<AgentResponseEvent>>>
612 where
613 T: Into<UserMessageContent>,
614 {
615 let model = self.model().context("No language model configured")?;
616
617 log::info!("Thread::send called with model: {:?}", model.name());
618 self.advance_prompt_id();
619
620 let content = content.into_iter().map(Into::into).collect::<Vec<_>>();
621 log::debug!("Thread::send content: {:?}", content);
622
623 self.messages
624 .push(Message::User(UserMessage { id, content }));
625 cx.notify();
626
627 log::info!("Total messages in thread: {}", self.messages.len());
628 self.run_turn(cx)
629 }
630
631 fn run_turn(
632 &mut self,
633 cx: &mut Context<Self>,
634 ) -> Result<mpsc::UnboundedReceiver<Result<AgentResponseEvent>>> {
635 self.cancel();
636
637 let model = self
638 .model()
639 .cloned()
640 .context("No language model configured")?;
641 let (events_tx, events_rx) = mpsc::unbounded::<Result<AgentResponseEvent>>();
642 let event_stream = AgentResponseEventStream(events_tx);
643 let message_ix = self.messages.len().saturating_sub(1);
644 self.tool_use_limit_reached = false;
645 self.running_turn = Some(RunningTurn {
646 event_stream: event_stream.clone(),
647 _task: cx.spawn(async move |this, cx| {
648 log::info!("Starting agent turn execution");
649 let turn_result: Result<()> = async {
650 let mut completion_intent = CompletionIntent::UserPrompt;
651 loop {
652 log::debug!(
653 "Building completion request with intent: {:?}",
654 completion_intent
655 );
656 let request = this.update(cx, |this, cx| {
657 this.build_completion_request(completion_intent, cx)
658 })??;
659
660 log::info!("Calling model.stream_completion");
661 let mut events = model.stream_completion(request, cx).await?;
662 log::debug!("Stream completion started successfully");
663
664 let mut tool_use_limit_reached = false;
665 let mut tool_uses = FuturesUnordered::new();
666 while let Some(event) = events.next().await {
667 match event? {
668 LanguageModelCompletionEvent::StatusUpdate(
669 CompletionRequestStatus::ToolUseLimitReached,
670 ) => {
671 tool_use_limit_reached = true;
672 }
673 LanguageModelCompletionEvent::Stop(reason) => {
674 event_stream.send_stop(reason);
675 if reason == StopReason::Refusal {
676 this.update(cx, |this, _cx| {
677 this.flush_pending_message();
678 this.messages.truncate(message_ix);
679 })?;
680 return Ok(());
681 }
682 }
683 event => {
684 log::trace!("Received completion event: {:?}", event);
685 this.update(cx, |this, cx| {
686 tool_uses.extend(this.handle_streamed_completion_event(
687 event,
688 &event_stream,
689 cx,
690 ));
691 })
692 .ok();
693 }
694 }
695 }
696
697 let used_tools = tool_uses.is_empty();
698 while let Some(tool_result) = tool_uses.next().await {
699 log::info!("Tool finished {:?}", tool_result);
700
701 event_stream.update_tool_call_fields(
702 &tool_result.tool_use_id,
703 acp::ToolCallUpdateFields {
704 status: Some(if tool_result.is_error {
705 acp::ToolCallStatus::Failed
706 } else {
707 acp::ToolCallStatus::Completed
708 }),
709 raw_output: tool_result.output.clone(),
710 ..Default::default()
711 },
712 );
713 this.update(cx, |this, _cx| {
714 this.pending_message()
715 .tool_results
716 .insert(tool_result.tool_use_id.clone(), tool_result);
717 })
718 .ok();
719 }
720
721 if tool_use_limit_reached {
722 log::info!("Tool use limit reached, completing turn");
723 this.update(cx, |this, _cx| this.tool_use_limit_reached = true)?;
724 return Err(language_model::ToolUseLimitReachedError.into());
725 } else if used_tools {
726 log::info!("No tool uses found, completing turn");
727 return Ok(());
728 } else {
729 this.update(cx, |this, _| this.flush_pending_message())?;
730 completion_intent = CompletionIntent::ToolResults;
731 }
732 }
733 }
734 .await;
735
736 if let Err(error) = turn_result {
737 log::error!("Turn execution failed: {:?}", error);
738 event_stream.send_error(error);
739 } else {
740 log::info!("Turn execution completed successfully");
741 }
742
743 this.update(cx, |this, _| {
744 this.flush_pending_message();
745 this.running_turn.take();
746 })
747 .ok();
748 }),
749 });
750 Ok(events_rx)
751 }
752
753 pub fn build_system_message(&self) -> LanguageModelRequestMessage {
754 log::debug!("Building system message");
755 let prompt = SystemPromptTemplate {
756 project: &self.project_context.borrow(),
757 available_tools: self.tools.keys().cloned().collect(),
758 }
759 .render(&self.templates)
760 .context("failed to build system prompt")
761 .expect("Invalid template");
762 log::debug!("System message built");
763 LanguageModelRequestMessage {
764 role: Role::System,
765 content: vec![prompt.into()],
766 cache: true,
767 }
768 }
769
770 /// A helper method that's called on every streamed completion event.
771 /// Returns an optional tool result task, which the main agentic loop in
772 /// send will send back to the model when it resolves.
773 fn handle_streamed_completion_event(
774 &mut self,
775 event: LanguageModelCompletionEvent,
776 event_stream: &AgentResponseEventStream,
777 cx: &mut Context<Self>,
778 ) -> Option<Task<LanguageModelToolResult>> {
779 log::trace!("Handling streamed completion event: {:?}", event);
780 use LanguageModelCompletionEvent::*;
781
782 match event {
783 StartMessage { .. } => {
784 self.flush_pending_message();
785 self.pending_message = Some(AgentMessage::default());
786 }
787 Text(new_text) => self.handle_text_event(new_text, event_stream, cx),
788 Thinking { text, signature } => {
789 self.handle_thinking_event(text, signature, event_stream, cx)
790 }
791 RedactedThinking { data } => self.handle_redacted_thinking_event(data, cx),
792 ToolUse(tool_use) => {
793 return self.handle_tool_use_event(tool_use, event_stream, cx);
794 }
795 ToolUseJsonParseError {
796 id,
797 tool_name,
798 raw_input,
799 json_parse_error,
800 } => {
801 return Some(Task::ready(self.handle_tool_use_json_parse_error_event(
802 id,
803 tool_name,
804 raw_input,
805 json_parse_error,
806 )));
807 }
808 UsageUpdate(_) | StatusUpdate(_) => {}
809 Stop(_) => unreachable!(),
810 }
811
812 None
813 }
814
815 fn handle_text_event(
816 &mut self,
817 new_text: String,
818 event_stream: &AgentResponseEventStream,
819 cx: &mut Context<Self>,
820 ) {
821 event_stream.send_text(&new_text);
822
823 let last_message = self.pending_message();
824 if let Some(AgentMessageContent::Text(text)) = last_message.content.last_mut() {
825 text.push_str(&new_text);
826 } else {
827 last_message
828 .content
829 .push(AgentMessageContent::Text(new_text));
830 }
831
832 cx.notify();
833 }
834
835 fn handle_thinking_event(
836 &mut self,
837 new_text: String,
838 new_signature: Option<String>,
839 event_stream: &AgentResponseEventStream,
840 cx: &mut Context<Self>,
841 ) {
842 event_stream.send_thinking(&new_text);
843
844 let last_message = self.pending_message();
845 if let Some(AgentMessageContent::Thinking { text, signature }) =
846 last_message.content.last_mut()
847 {
848 text.push_str(&new_text);
849 *signature = new_signature.or(signature.take());
850 } else {
851 last_message.content.push(AgentMessageContent::Thinking {
852 text: new_text,
853 signature: new_signature,
854 });
855 }
856
857 cx.notify();
858 }
859
860 fn handle_redacted_thinking_event(&mut self, data: String, cx: &mut Context<Self>) {
861 let last_message = self.pending_message();
862 last_message
863 .content
864 .push(AgentMessageContent::RedactedThinking(data));
865 cx.notify();
866 }
867
868 fn handle_tool_use_event(
869 &mut self,
870 tool_use: LanguageModelToolUse,
871 event_stream: &AgentResponseEventStream,
872 cx: &mut Context<Self>,
873 ) -> Option<Task<LanguageModelToolResult>> {
874 cx.notify();
875
876 let tool = self.tools.get(tool_use.name.as_ref()).cloned();
877 let mut title = SharedString::from(&tool_use.name);
878 let mut kind = acp::ToolKind::Other;
879 if let Some(tool) = tool.as_ref() {
880 title = tool.initial_title(tool_use.input.clone());
881 kind = tool.kind();
882 }
883
884 // Ensure the last message ends in the current tool use
885 let last_message = self.pending_message();
886 let push_new_tool_use = last_message.content.last_mut().map_or(true, |content| {
887 if let AgentMessageContent::ToolUse(last_tool_use) = content {
888 if last_tool_use.id == tool_use.id {
889 *last_tool_use = tool_use.clone();
890 false
891 } else {
892 true
893 }
894 } else {
895 true
896 }
897 });
898
899 if push_new_tool_use {
900 event_stream.send_tool_call(&tool_use.id, title, kind, tool_use.input.clone());
901 last_message
902 .content
903 .push(AgentMessageContent::ToolUse(tool_use.clone()));
904 } else {
905 event_stream.update_tool_call_fields(
906 &tool_use.id,
907 acp::ToolCallUpdateFields {
908 title: Some(title.into()),
909 kind: Some(kind),
910 raw_input: Some(tool_use.input.clone()),
911 ..Default::default()
912 },
913 );
914 }
915
916 if !tool_use.is_input_complete {
917 return None;
918 }
919
920 let Some(tool) = tool else {
921 let content = format!("No tool named {} exists", tool_use.name);
922 return Some(Task::ready(LanguageModelToolResult {
923 content: LanguageModelToolResultContent::Text(Arc::from(content)),
924 tool_use_id: tool_use.id,
925 tool_name: tool_use.name,
926 is_error: true,
927 output: None,
928 }));
929 };
930
931 let fs = self.project.read(cx).fs().clone();
932 let tool_event_stream =
933 ToolCallEventStream::new(tool_use.id.clone(), event_stream.clone(), Some(fs));
934 tool_event_stream.update_fields(acp::ToolCallUpdateFields {
935 status: Some(acp::ToolCallStatus::InProgress),
936 ..Default::default()
937 });
938 let supports_images = self.model().map_or(false, |model| model.supports_images());
939 let tool_result = tool.run(tool_use.input, tool_event_stream, cx);
940 log::info!("Running tool {}", tool_use.name);
941 Some(cx.foreground_executor().spawn(async move {
942 let tool_result = tool_result.await.and_then(|output| {
943 if let LanguageModelToolResultContent::Image(_) = &output.llm_output {
944 if !supports_images {
945 return Err(anyhow!(
946 "Attempted to read an image, but this model doesn't support it.",
947 ));
948 }
949 }
950 Ok(output)
951 });
952
953 match tool_result {
954 Ok(output) => LanguageModelToolResult {
955 tool_use_id: tool_use.id,
956 tool_name: tool_use.name,
957 is_error: false,
958 content: output.llm_output,
959 output: Some(output.raw_output),
960 },
961 Err(error) => LanguageModelToolResult {
962 tool_use_id: tool_use.id,
963 tool_name: tool_use.name,
964 is_error: true,
965 content: LanguageModelToolResultContent::Text(Arc::from(error.to_string())),
966 output: None,
967 },
968 }
969 }))
970 }
971
972 fn handle_tool_use_json_parse_error_event(
973 &mut self,
974 tool_use_id: LanguageModelToolUseId,
975 tool_name: Arc<str>,
976 raw_input: Arc<str>,
977 json_parse_error: String,
978 ) -> LanguageModelToolResult {
979 let tool_output = format!("Error parsing input JSON: {json_parse_error}");
980 LanguageModelToolResult {
981 tool_use_id,
982 tool_name,
983 is_error: true,
984 content: LanguageModelToolResultContent::Text(tool_output.into()),
985 output: Some(serde_json::Value::String(raw_input.to_string())),
986 }
987 }
988
989 fn pending_message(&mut self) -> &mut AgentMessage {
990 self.pending_message.get_or_insert_default()
991 }
992
993 fn flush_pending_message(&mut self) {
994 let Some(mut message) = self.pending_message.take() else {
995 return;
996 };
997
998 for content in &message.content {
999 let AgentMessageContent::ToolUse(tool_use) = content else {
1000 continue;
1001 };
1002
1003 if !message.tool_results.contains_key(&tool_use.id) {
1004 message.tool_results.insert(
1005 tool_use.id.clone(),
1006 LanguageModelToolResult {
1007 tool_use_id: tool_use.id.clone(),
1008 tool_name: tool_use.name.clone(),
1009 is_error: true,
1010 content: LanguageModelToolResultContent::Text(
1011 "Tool canceled by user".into(),
1012 ),
1013 output: None,
1014 },
1015 );
1016 }
1017 }
1018
1019 self.messages.push(Message::Agent(message));
1020 }
1021
1022 pub(crate) fn build_completion_request(
1023 &self,
1024 completion_intent: CompletionIntent,
1025 cx: &mut App,
1026 ) -> Result<LanguageModelRequest> {
1027 let model = self.model().context("No language model configured")?;
1028
1029 log::debug!("Building completion request");
1030 log::debug!("Completion intent: {:?}", completion_intent);
1031 log::debug!("Completion mode: {:?}", self.completion_mode);
1032
1033 let messages = self.build_request_messages();
1034 log::info!("Request will include {} messages", messages.len());
1035
1036 let tools = if let Some(tools) = self.tools(cx).log_err() {
1037 tools
1038 .filter_map(|tool| {
1039 let tool_name = tool.name().to_string();
1040 log::trace!("Including tool: {}", tool_name);
1041 Some(LanguageModelRequestTool {
1042 name: tool_name,
1043 description: tool.description().to_string(),
1044 input_schema: tool.input_schema(model.tool_input_format()).log_err()?,
1045 })
1046 })
1047 .collect()
1048 } else {
1049 Vec::new()
1050 };
1051
1052 log::info!("Request includes {} tools", tools.len());
1053
1054 let request = LanguageModelRequest {
1055 thread_id: Some(self.id.to_string()),
1056 prompt_id: Some(self.prompt_id.to_string()),
1057 intent: Some(completion_intent),
1058 mode: Some(self.completion_mode.into()),
1059 messages,
1060 tools,
1061 tool_choice: None,
1062 stop: Vec::new(),
1063 temperature: AgentSettings::temperature_for_model(model, cx),
1064 thinking_allowed: true,
1065 };
1066
1067 log::debug!("Completion request built successfully");
1068 Ok(request)
1069 }
1070
1071 fn tools<'a>(&'a self, cx: &'a App) -> Result<impl Iterator<Item = &'a Arc<dyn AnyAgentTool>>> {
1072 let model = self.model().context("No language model configured")?;
1073
1074 let profile = AgentSettings::get_global(cx)
1075 .profiles
1076 .get(&self.profile_id)
1077 .context("profile not found")?;
1078 let provider_id = model.provider_id();
1079
1080 Ok(self
1081 .tools
1082 .iter()
1083 .filter(move |(_, tool)| tool.supported_provider(&provider_id))
1084 .filter_map(|(tool_name, tool)| {
1085 if profile.is_tool_enabled(tool_name) {
1086 Some(tool)
1087 } else {
1088 None
1089 }
1090 })
1091 .chain(self.context_server_registry.read(cx).servers().flat_map(
1092 |(server_id, tools)| {
1093 tools.iter().filter_map(|(tool_name, tool)| {
1094 if profile.is_context_server_tool_enabled(&server_id.0, tool_name) {
1095 Some(tool)
1096 } else {
1097 None
1098 }
1099 })
1100 },
1101 )))
1102 }
1103
1104 fn build_request_messages(&self) -> Vec<LanguageModelRequestMessage> {
1105 log::trace!(
1106 "Building request messages from {} thread messages",
1107 self.messages.len()
1108 );
1109 let mut messages = vec![self.build_system_message()];
1110 for message in &self.messages {
1111 match message {
1112 Message::User(message) => messages.push(message.to_request()),
1113 Message::Agent(message) => messages.extend(message.to_request()),
1114 Message::Resume => messages.push(LanguageModelRequestMessage {
1115 role: Role::User,
1116 content: vec!["Continue where you left off".into()],
1117 cache: false,
1118 }),
1119 }
1120 }
1121
1122 if let Some(message) = self.pending_message.as_ref() {
1123 messages.extend(message.to_request());
1124 }
1125
1126 if let Some(last_user_message) = messages
1127 .iter_mut()
1128 .rev()
1129 .find(|message| message.role == Role::User)
1130 {
1131 last_user_message.cache = true;
1132 }
1133
1134 messages
1135 }
1136
1137 pub fn to_markdown(&self) -> String {
1138 let mut markdown = String::new();
1139 for (ix, message) in self.messages.iter().enumerate() {
1140 if ix > 0 {
1141 markdown.push('\n');
1142 }
1143 markdown.push_str(&message.to_markdown());
1144 }
1145
1146 if let Some(message) = self.pending_message.as_ref() {
1147 markdown.push('\n');
1148 markdown.push_str(&message.to_markdown());
1149 }
1150
1151 markdown
1152 }
1153
1154 fn advance_prompt_id(&mut self) {
1155 self.prompt_id = PromptId::new();
1156 }
1157}
1158
1159struct RunningTurn {
1160 /// Holds the task that handles agent interaction until the end of the turn.
1161 /// Survives across multiple requests as the model performs tool calls and
1162 /// we run tools, report their results.
1163 _task: Task<()>,
1164 /// The current event stream for the running turn. Used to report a final
1165 /// cancellation event if we cancel the turn.
1166 event_stream: AgentResponseEventStream,
1167}
1168
1169impl RunningTurn {
1170 fn cancel(self) {
1171 log::debug!("Cancelling in progress turn");
1172 self.event_stream.send_canceled();
1173 }
1174}
1175
1176pub trait AgentTool
1177where
1178 Self: 'static + Sized,
1179{
1180 type Input: for<'de> Deserialize<'de> + Serialize + JsonSchema;
1181 type Output: for<'de> Deserialize<'de> + Serialize + Into<LanguageModelToolResultContent>;
1182
1183 fn name(&self) -> SharedString;
1184
1185 fn description(&self) -> SharedString {
1186 let schema = schemars::schema_for!(Self::Input);
1187 SharedString::new(
1188 schema
1189 .get("description")
1190 .and_then(|description| description.as_str())
1191 .unwrap_or_default(),
1192 )
1193 }
1194
1195 fn kind(&self) -> acp::ToolKind;
1196
1197 /// The initial tool title to display. Can be updated during the tool run.
1198 fn initial_title(&self, input: Result<Self::Input, serde_json::Value>) -> SharedString;
1199
1200 /// Returns the JSON schema that describes the tool's input.
1201 fn input_schema(&self) -> Schema {
1202 schemars::schema_for!(Self::Input)
1203 }
1204
1205 /// Some tools rely on a provider for the underlying billing or other reasons.
1206 /// Allow the tool to check if they are compatible, or should be filtered out.
1207 fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
1208 true
1209 }
1210
1211 /// Runs the tool with the provided input.
1212 fn run(
1213 self: Arc<Self>,
1214 input: Self::Input,
1215 event_stream: ToolCallEventStream,
1216 cx: &mut App,
1217 ) -> Task<Result<Self::Output>>;
1218
1219 fn erase(self) -> Arc<dyn AnyAgentTool> {
1220 Arc::new(Erased(Arc::new(self)))
1221 }
1222}
1223
1224pub struct Erased<T>(T);
1225
1226pub struct AgentToolOutput {
1227 pub llm_output: LanguageModelToolResultContent,
1228 pub raw_output: serde_json::Value,
1229}
1230
1231pub trait AnyAgentTool {
1232 fn name(&self) -> SharedString;
1233 fn description(&self) -> SharedString;
1234 fn kind(&self) -> acp::ToolKind;
1235 fn initial_title(&self, input: serde_json::Value) -> SharedString;
1236 fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value>;
1237 fn supported_provider(&self, _provider: &LanguageModelProviderId) -> bool {
1238 true
1239 }
1240 fn run(
1241 self: Arc<Self>,
1242 input: serde_json::Value,
1243 event_stream: ToolCallEventStream,
1244 cx: &mut App,
1245 ) -> Task<Result<AgentToolOutput>>;
1246}
1247
1248impl<T> AnyAgentTool for Erased<Arc<T>>
1249where
1250 T: AgentTool,
1251{
1252 fn name(&self) -> SharedString {
1253 self.0.name()
1254 }
1255
1256 fn description(&self) -> SharedString {
1257 self.0.description()
1258 }
1259
1260 fn kind(&self) -> agent_client_protocol::ToolKind {
1261 self.0.kind()
1262 }
1263
1264 fn initial_title(&self, input: serde_json::Value) -> SharedString {
1265 let parsed_input = serde_json::from_value(input.clone()).map_err(|_| input);
1266 self.0.initial_title(parsed_input)
1267 }
1268
1269 fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
1270 let mut json = serde_json::to_value(self.0.input_schema())?;
1271 adapt_schema_to_format(&mut json, format)?;
1272 Ok(json)
1273 }
1274
1275 fn supported_provider(&self, provider: &LanguageModelProviderId) -> bool {
1276 self.0.supported_provider(provider)
1277 }
1278
1279 fn run(
1280 self: Arc<Self>,
1281 input: serde_json::Value,
1282 event_stream: ToolCallEventStream,
1283 cx: &mut App,
1284 ) -> Task<Result<AgentToolOutput>> {
1285 cx.spawn(async move |cx| {
1286 let input = serde_json::from_value(input)?;
1287 let output = cx
1288 .update(|cx| self.0.clone().run(input, event_stream, cx))?
1289 .await?;
1290 let raw_output = serde_json::to_value(&output)?;
1291 Ok(AgentToolOutput {
1292 llm_output: output.into(),
1293 raw_output,
1294 })
1295 })
1296 }
1297}
1298
1299#[derive(Clone)]
1300struct AgentResponseEventStream(mpsc::UnboundedSender<Result<AgentResponseEvent>>);
1301
1302impl AgentResponseEventStream {
1303 fn send_text(&self, text: &str) {
1304 self.0
1305 .unbounded_send(Ok(AgentResponseEvent::Text(text.to_string())))
1306 .ok();
1307 }
1308
1309 fn send_thinking(&self, text: &str) {
1310 self.0
1311 .unbounded_send(Ok(AgentResponseEvent::Thinking(text.to_string())))
1312 .ok();
1313 }
1314
1315 fn send_tool_call(
1316 &self,
1317 id: &LanguageModelToolUseId,
1318 title: SharedString,
1319 kind: acp::ToolKind,
1320 input: serde_json::Value,
1321 ) {
1322 self.0
1323 .unbounded_send(Ok(AgentResponseEvent::ToolCall(Self::initial_tool_call(
1324 id,
1325 title.to_string(),
1326 kind,
1327 input,
1328 ))))
1329 .ok();
1330 }
1331
1332 fn initial_tool_call(
1333 id: &LanguageModelToolUseId,
1334 title: String,
1335 kind: acp::ToolKind,
1336 input: serde_json::Value,
1337 ) -> acp::ToolCall {
1338 acp::ToolCall {
1339 id: acp::ToolCallId(id.to_string().into()),
1340 title,
1341 kind,
1342 status: acp::ToolCallStatus::Pending,
1343 content: vec![],
1344 locations: vec![],
1345 raw_input: Some(input),
1346 raw_output: None,
1347 }
1348 }
1349
1350 fn update_tool_call_fields(
1351 &self,
1352 tool_use_id: &LanguageModelToolUseId,
1353 fields: acp::ToolCallUpdateFields,
1354 ) {
1355 self.0
1356 .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
1357 acp::ToolCallUpdate {
1358 id: acp::ToolCallId(tool_use_id.to_string().into()),
1359 fields,
1360 }
1361 .into(),
1362 )))
1363 .ok();
1364 }
1365
1366 fn send_stop(&self, reason: StopReason) {
1367 match reason {
1368 StopReason::EndTurn => {
1369 self.0
1370 .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::EndTurn)))
1371 .ok();
1372 }
1373 StopReason::MaxTokens => {
1374 self.0
1375 .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::MaxTokens)))
1376 .ok();
1377 }
1378 StopReason::Refusal => {
1379 self.0
1380 .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::Refusal)))
1381 .ok();
1382 }
1383 StopReason::ToolUse => {}
1384 }
1385 }
1386
1387 fn send_canceled(&self) {
1388 self.0
1389 .unbounded_send(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled)))
1390 .ok();
1391 }
1392
1393 fn send_error(&self, error: impl Into<anyhow::Error>) {
1394 self.0.unbounded_send(Err(error.into())).ok();
1395 }
1396}
1397
1398#[derive(Clone)]
1399pub struct ToolCallEventStream {
1400 tool_use_id: LanguageModelToolUseId,
1401 stream: AgentResponseEventStream,
1402 fs: Option<Arc<dyn Fs>>,
1403}
1404
1405impl ToolCallEventStream {
1406 #[cfg(test)]
1407 pub fn test() -> (Self, ToolCallEventStreamReceiver) {
1408 let (events_tx, events_rx) = mpsc::unbounded::<Result<AgentResponseEvent>>();
1409
1410 let stream =
1411 ToolCallEventStream::new("test_id".into(), AgentResponseEventStream(events_tx), None);
1412
1413 (stream, ToolCallEventStreamReceiver(events_rx))
1414 }
1415
1416 fn new(
1417 tool_use_id: LanguageModelToolUseId,
1418 stream: AgentResponseEventStream,
1419 fs: Option<Arc<dyn Fs>>,
1420 ) -> Self {
1421 Self {
1422 tool_use_id,
1423 stream,
1424 fs,
1425 }
1426 }
1427
1428 pub fn update_fields(&self, fields: acp::ToolCallUpdateFields) {
1429 self.stream
1430 .update_tool_call_fields(&self.tool_use_id, fields);
1431 }
1432
1433 pub fn update_diff(&self, diff: Entity<acp_thread::Diff>) {
1434 self.stream
1435 .0
1436 .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
1437 acp_thread::ToolCallUpdateDiff {
1438 id: acp::ToolCallId(self.tool_use_id.to_string().into()),
1439 diff,
1440 }
1441 .into(),
1442 )))
1443 .ok();
1444 }
1445
1446 pub fn update_terminal(&self, terminal: Entity<acp_thread::Terminal>) {
1447 self.stream
1448 .0
1449 .unbounded_send(Ok(AgentResponseEvent::ToolCallUpdate(
1450 acp_thread::ToolCallUpdateTerminal {
1451 id: acp::ToolCallId(self.tool_use_id.to_string().into()),
1452 terminal,
1453 }
1454 .into(),
1455 )))
1456 .ok();
1457 }
1458
1459 pub fn authorize(&self, title: impl Into<String>, cx: &mut App) -> Task<Result<()>> {
1460 if agent_settings::AgentSettings::get_global(cx).always_allow_tool_actions {
1461 return Task::ready(Ok(()));
1462 }
1463
1464 let (response_tx, response_rx) = oneshot::channel();
1465 self.stream
1466 .0
1467 .unbounded_send(Ok(AgentResponseEvent::ToolCallAuthorization(
1468 ToolCallAuthorization {
1469 tool_call: acp::ToolCallUpdate {
1470 id: acp::ToolCallId(self.tool_use_id.to_string().into()),
1471 fields: acp::ToolCallUpdateFields {
1472 title: Some(title.into()),
1473 ..Default::default()
1474 },
1475 },
1476 options: vec![
1477 acp::PermissionOption {
1478 id: acp::PermissionOptionId("always_allow".into()),
1479 name: "Always Allow".into(),
1480 kind: acp::PermissionOptionKind::AllowAlways,
1481 },
1482 acp::PermissionOption {
1483 id: acp::PermissionOptionId("allow".into()),
1484 name: "Allow".into(),
1485 kind: acp::PermissionOptionKind::AllowOnce,
1486 },
1487 acp::PermissionOption {
1488 id: acp::PermissionOptionId("deny".into()),
1489 name: "Deny".into(),
1490 kind: acp::PermissionOptionKind::RejectOnce,
1491 },
1492 ],
1493 response: response_tx,
1494 },
1495 )))
1496 .ok();
1497 let fs = self.fs.clone();
1498 cx.spawn(async move |cx| match response_rx.await?.0.as_ref() {
1499 "always_allow" => {
1500 if let Some(fs) = fs.clone() {
1501 cx.update(|cx| {
1502 update_settings_file::<AgentSettings>(fs, cx, |settings, _| {
1503 settings.set_always_allow_tool_actions(true);
1504 });
1505 })?;
1506 }
1507
1508 Ok(())
1509 }
1510 "allow" => Ok(()),
1511 _ => Err(anyhow!("Permission to run tool denied by user")),
1512 })
1513 }
1514}
1515
1516#[cfg(test)]
1517pub struct ToolCallEventStreamReceiver(mpsc::UnboundedReceiver<Result<AgentResponseEvent>>);
1518
1519#[cfg(test)]
1520impl ToolCallEventStreamReceiver {
1521 pub async fn expect_authorization(&mut self) -> ToolCallAuthorization {
1522 let event = self.0.next().await;
1523 if let Some(Ok(AgentResponseEvent::ToolCallAuthorization(auth))) = event {
1524 auth
1525 } else {
1526 panic!("Expected ToolCallAuthorization but got: {:?}", event);
1527 }
1528 }
1529
1530 pub async fn expect_terminal(&mut self) -> Entity<acp_thread::Terminal> {
1531 let event = self.0.next().await;
1532 if let Some(Ok(AgentResponseEvent::ToolCallUpdate(
1533 acp_thread::ToolCallUpdate::UpdateTerminal(update),
1534 ))) = event
1535 {
1536 update.terminal
1537 } else {
1538 panic!("Expected terminal but got: {:?}", event);
1539 }
1540 }
1541}
1542
1543#[cfg(test)]
1544impl std::ops::Deref for ToolCallEventStreamReceiver {
1545 type Target = mpsc::UnboundedReceiver<Result<AgentResponseEvent>>;
1546
1547 fn deref(&self) -> &Self::Target {
1548 &self.0
1549 }
1550}
1551
1552#[cfg(test)]
1553impl std::ops::DerefMut for ToolCallEventStreamReceiver {
1554 fn deref_mut(&mut self) -> &mut Self::Target {
1555 &mut self.0
1556 }
1557}
1558
1559impl From<&str> for UserMessageContent {
1560 fn from(text: &str) -> Self {
1561 Self::Text(text.into())
1562 }
1563}
1564
1565impl From<acp::ContentBlock> for UserMessageContent {
1566 fn from(value: acp::ContentBlock) -> Self {
1567 match value {
1568 acp::ContentBlock::Text(text_content) => Self::Text(text_content.text),
1569 acp::ContentBlock::Image(image_content) => Self::Image(convert_image(image_content)),
1570 acp::ContentBlock::Audio(_) => {
1571 // TODO
1572 Self::Text("[audio]".to_string())
1573 }
1574 acp::ContentBlock::ResourceLink(resource_link) => {
1575 match MentionUri::parse(&resource_link.uri) {
1576 Ok(uri) => Self::Mention {
1577 uri,
1578 content: String::new(),
1579 },
1580 Err(err) => {
1581 log::error!("Failed to parse mention link: {}", err);
1582 Self::Text(format!("[{}]({})", resource_link.name, resource_link.uri))
1583 }
1584 }
1585 }
1586 acp::ContentBlock::Resource(resource) => match resource.resource {
1587 acp::EmbeddedResourceResource::TextResourceContents(resource) => {
1588 match MentionUri::parse(&resource.uri) {
1589 Ok(uri) => Self::Mention {
1590 uri,
1591 content: resource.text,
1592 },
1593 Err(err) => {
1594 log::error!("Failed to parse mention link: {}", err);
1595 Self::Text(
1596 MarkdownCodeBlock {
1597 tag: &resource.uri,
1598 text: &resource.text,
1599 }
1600 .to_string(),
1601 )
1602 }
1603 }
1604 }
1605 acp::EmbeddedResourceResource::BlobResourceContents(_) => {
1606 // TODO
1607 Self::Text("[blob]".to_string())
1608 }
1609 },
1610 }
1611 }
1612}
1613
1614fn convert_image(image_content: acp::ImageContent) -> LanguageModelImage {
1615 LanguageModelImage {
1616 source: image_content.data.into(),
1617 // TODO: make this optional?
1618 size: gpui::Size::new(0.into(), 0.into()),
1619 }
1620}