mod.rs

   1use super::*;
   2use acp_thread::{AgentConnection, AgentModelGroupName, AgentModelList, UserMessageId};
   3use action_log::ActionLog;
   4use agent_client_protocol::{self as acp};
   5use agent_settings::AgentProfileId;
   6use anyhow::Result;
   7use client::{Client, UserStore};
   8use fs::{FakeFs, Fs};
   9use futures::channel::mpsc::UnboundedReceiver;
  10use gpui::{
  11    App, AppContext, Entity, Task, TestAppContext, UpdateGlobal, http_client::FakeHttpClient,
  12};
  13use indoc::indoc;
  14use language_model::{
  15    LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelRegistry,
  16    LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse, MessageContent,
  17    Role, StopReason, fake_provider::FakeLanguageModel,
  18};
  19use pretty_assertions::assert_eq;
  20use project::Project;
  21use prompt_store::ProjectContext;
  22use reqwest_client::ReqwestClient;
  23use schemars::JsonSchema;
  24use serde::{Deserialize, Serialize};
  25use serde_json::json;
  26use settings::SettingsStore;
  27use smol::stream::StreamExt;
  28use std::{cell::RefCell, path::Path, rc::Rc, sync::Arc, time::Duration};
  29use util::path;
  30
  31mod test_tools;
  32use test_tools::*;
  33
  34#[gpui::test]
  35#[ignore = "can't run on CI yet"]
  36async fn test_echo(cx: &mut TestAppContext) {
  37    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4).await;
  38
  39    let events = thread
  40        .update(cx, |thread, cx| {
  41            thread.send(UserMessageId::new(), ["Testing: Reply with 'Hello'"], cx)
  42        })
  43        .unwrap()
  44        .collect()
  45        .await;
  46    thread.update(cx, |thread, _cx| {
  47        assert_eq!(
  48            thread.last_message().unwrap().to_markdown(),
  49            indoc! {"
  50                ## Assistant
  51
  52                Hello
  53            "}
  54        )
  55    });
  56    assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
  57}
  58
  59#[gpui::test]
  60#[ignore = "can't run on CI yet"]
  61async fn test_thinking(cx: &mut TestAppContext) {
  62    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4Thinking).await;
  63
  64    let events = thread
  65        .update(cx, |thread, cx| {
  66            thread.send(
  67                UserMessageId::new(),
  68                [indoc! {"
  69                    Testing:
  70
  71                    Generate a thinking step where you just think the word 'Think',
  72                    and have your final answer be 'Hello'
  73                "}],
  74                cx,
  75            )
  76        })
  77        .unwrap()
  78        .collect()
  79        .await;
  80    thread.update(cx, |thread, _cx| {
  81        assert_eq!(
  82            thread.last_message().unwrap().to_markdown(),
  83            indoc! {"
  84                ## Assistant
  85
  86                <think>Think</think>
  87                Hello
  88            "}
  89        )
  90    });
  91    assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
  92}
  93
  94#[gpui::test]
  95async fn test_system_prompt(cx: &mut TestAppContext) {
  96    let ThreadTest {
  97        model,
  98        thread,
  99        project_context,
 100        ..
 101    } = setup(cx, TestModel::Fake).await;
 102    let fake_model = model.as_fake();
 103
 104    project_context.borrow_mut().shell = "test-shell".into();
 105    thread.update(cx, |thread, _| thread.add_tool(EchoTool));
 106    thread
 107        .update(cx, |thread, cx| {
 108            thread.send(UserMessageId::new(), ["abc"], cx)
 109        })
 110        .unwrap();
 111    cx.run_until_parked();
 112    let mut pending_completions = fake_model.pending_completions();
 113    assert_eq!(
 114        pending_completions.len(),
 115        1,
 116        "unexpected pending completions: {:?}",
 117        pending_completions
 118    );
 119
 120    let pending_completion = pending_completions.pop().unwrap();
 121    assert_eq!(pending_completion.messages[0].role, Role::System);
 122
 123    let system_message = &pending_completion.messages[0];
 124    let system_prompt = system_message.content[0].to_str().unwrap();
 125    assert!(
 126        system_prompt.contains("test-shell"),
 127        "unexpected system message: {:?}",
 128        system_message
 129    );
 130    assert!(
 131        system_prompt.contains("## Fixing Diagnostics"),
 132        "unexpected system message: {:?}",
 133        system_message
 134    );
 135}
 136
 137#[gpui::test]
 138async fn test_prompt_caching(cx: &mut TestAppContext) {
 139    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
 140    let fake_model = model.as_fake();
 141
 142    // Send initial user message and verify it's cached
 143    thread
 144        .update(cx, |thread, cx| {
 145            thread.send(UserMessageId::new(), ["Message 1"], cx)
 146        })
 147        .unwrap();
 148    cx.run_until_parked();
 149
 150    let completion = fake_model.pending_completions().pop().unwrap();
 151    assert_eq!(
 152        completion.messages[1..],
 153        vec![LanguageModelRequestMessage {
 154            role: Role::User,
 155            content: vec!["Message 1".into()],
 156            cache: true
 157        }]
 158    );
 159    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text(
 160        "Response to Message 1".into(),
 161    ));
 162    fake_model.end_last_completion_stream();
 163    cx.run_until_parked();
 164
 165    // Send another user message and verify only the latest is cached
 166    thread
 167        .update(cx, |thread, cx| {
 168            thread.send(UserMessageId::new(), ["Message 2"], cx)
 169        })
 170        .unwrap();
 171    cx.run_until_parked();
 172
 173    let completion = fake_model.pending_completions().pop().unwrap();
 174    assert_eq!(
 175        completion.messages[1..],
 176        vec![
 177            LanguageModelRequestMessage {
 178                role: Role::User,
 179                content: vec!["Message 1".into()],
 180                cache: false
 181            },
 182            LanguageModelRequestMessage {
 183                role: Role::Assistant,
 184                content: vec!["Response to Message 1".into()],
 185                cache: false
 186            },
 187            LanguageModelRequestMessage {
 188                role: Role::User,
 189                content: vec!["Message 2".into()],
 190                cache: true
 191            }
 192        ]
 193    );
 194    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text(
 195        "Response to Message 2".into(),
 196    ));
 197    fake_model.end_last_completion_stream();
 198    cx.run_until_parked();
 199
 200    // Simulate a tool call and verify that the latest tool result is cached
 201    thread.update(cx, |thread, _| thread.add_tool(EchoTool));
 202    thread
 203        .update(cx, |thread, cx| {
 204            thread.send(UserMessageId::new(), ["Use the echo tool"], cx)
 205        })
 206        .unwrap();
 207    cx.run_until_parked();
 208
 209    let tool_use = LanguageModelToolUse {
 210        id: "tool_1".into(),
 211        name: EchoTool.name().into(),
 212        raw_input: json!({"text": "test"}).to_string(),
 213        input: json!({"text": "test"}),
 214        is_input_complete: true,
 215    };
 216    fake_model
 217        .send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(tool_use.clone()));
 218    fake_model.end_last_completion_stream();
 219    cx.run_until_parked();
 220
 221    let completion = fake_model.pending_completions().pop().unwrap();
 222    let tool_result = LanguageModelToolResult {
 223        tool_use_id: "tool_1".into(),
 224        tool_name: EchoTool.name().into(),
 225        is_error: false,
 226        content: "test".into(),
 227        output: Some("test".into()),
 228    };
 229    assert_eq!(
 230        completion.messages[1..],
 231        vec![
 232            LanguageModelRequestMessage {
 233                role: Role::User,
 234                content: vec!["Message 1".into()],
 235                cache: false
 236            },
 237            LanguageModelRequestMessage {
 238                role: Role::Assistant,
 239                content: vec!["Response to Message 1".into()],
 240                cache: false
 241            },
 242            LanguageModelRequestMessage {
 243                role: Role::User,
 244                content: vec!["Message 2".into()],
 245                cache: false
 246            },
 247            LanguageModelRequestMessage {
 248                role: Role::Assistant,
 249                content: vec!["Response to Message 2".into()],
 250                cache: false
 251            },
 252            LanguageModelRequestMessage {
 253                role: Role::User,
 254                content: vec!["Use the echo tool".into()],
 255                cache: false
 256            },
 257            LanguageModelRequestMessage {
 258                role: Role::Assistant,
 259                content: vec![MessageContent::ToolUse(tool_use)],
 260                cache: false
 261            },
 262            LanguageModelRequestMessage {
 263                role: Role::User,
 264                content: vec![MessageContent::ToolResult(tool_result)],
 265                cache: true
 266            }
 267        ]
 268    );
 269}
 270
 271#[gpui::test]
 272#[ignore = "can't run on CI yet"]
 273async fn test_basic_tool_calls(cx: &mut TestAppContext) {
 274    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4).await;
 275
 276    // Test a tool call that's likely to complete *before* streaming stops.
 277    let events = thread
 278        .update(cx, |thread, cx| {
 279            thread.add_tool(EchoTool);
 280            thread.send(
 281                UserMessageId::new(),
 282                ["Now test the echo tool with 'Hello'. Does it work? Say 'Yes' or 'No'."],
 283                cx,
 284            )
 285        })
 286        .unwrap()
 287        .collect()
 288        .await;
 289    assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
 290
 291    // Test a tool calls that's likely to complete *after* streaming stops.
 292    let events = thread
 293        .update(cx, |thread, cx| {
 294            thread.remove_tool(&AgentTool::name(&EchoTool));
 295            thread.add_tool(DelayTool);
 296            thread.send(
 297                UserMessageId::new(),
 298                [
 299                    "Now call the delay tool with 200ms.",
 300                    "When the timer goes off, then you echo the output of the tool.",
 301                ],
 302                cx,
 303            )
 304        })
 305        .unwrap()
 306        .collect()
 307        .await;
 308    assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
 309    thread.update(cx, |thread, _cx| {
 310        assert!(
 311            thread
 312                .last_message()
 313                .unwrap()
 314                .as_agent_message()
 315                .unwrap()
 316                .content
 317                .iter()
 318                .any(|content| {
 319                    if let AgentMessageContent::Text(text) = content {
 320                        text.contains("Ding")
 321                    } else {
 322                        false
 323                    }
 324                }),
 325            "{}",
 326            thread.to_markdown()
 327        );
 328    });
 329}
 330
 331#[gpui::test]
 332#[ignore = "can't run on CI yet"]
 333async fn test_streaming_tool_calls(cx: &mut TestAppContext) {
 334    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4).await;
 335
 336    // Test a tool call that's likely to complete *before* streaming stops.
 337    let mut events = thread
 338        .update(cx, |thread, cx| {
 339            thread.add_tool(WordListTool);
 340            thread.send(UserMessageId::new(), ["Test the word_list tool."], cx)
 341        })
 342        .unwrap();
 343
 344    let mut saw_partial_tool_use = false;
 345    while let Some(event) = events.next().await {
 346        if let Ok(AgentResponseEvent::ToolCall(tool_call)) = event {
 347            thread.update(cx, |thread, _cx| {
 348                // Look for a tool use in the thread's last message
 349                let message = thread.last_message().unwrap();
 350                let agent_message = message.as_agent_message().unwrap();
 351                let last_content = agent_message.content.last().unwrap();
 352                if let AgentMessageContent::ToolUse(last_tool_use) = last_content {
 353                    assert_eq!(last_tool_use.name.as_ref(), "word_list");
 354                    if tool_call.status == acp::ToolCallStatus::Pending {
 355                        if !last_tool_use.is_input_complete
 356                            && last_tool_use.input.get("g").is_none()
 357                        {
 358                            saw_partial_tool_use = true;
 359                        }
 360                    } else {
 361                        last_tool_use
 362                            .input
 363                            .get("a")
 364                            .expect("'a' has streamed because input is now complete");
 365                        last_tool_use
 366                            .input
 367                            .get("g")
 368                            .expect("'g' has streamed because input is now complete");
 369                    }
 370                } else {
 371                    panic!("last content should be a tool use");
 372                }
 373            });
 374        }
 375    }
 376
 377    assert!(
 378        saw_partial_tool_use,
 379        "should see at least one partially streamed tool use in the history"
 380    );
 381}
 382
 383#[gpui::test]
 384async fn test_tool_authorization(cx: &mut TestAppContext) {
 385    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
 386    let fake_model = model.as_fake();
 387
 388    let mut events = thread
 389        .update(cx, |thread, cx| {
 390            thread.add_tool(ToolRequiringPermission);
 391            thread.send(UserMessageId::new(), ["abc"], cx)
 392        })
 393        .unwrap();
 394    cx.run_until_parked();
 395    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
 396        LanguageModelToolUse {
 397            id: "tool_id_1".into(),
 398            name: ToolRequiringPermission.name().into(),
 399            raw_input: "{}".into(),
 400            input: json!({}),
 401            is_input_complete: true,
 402        },
 403    ));
 404    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
 405        LanguageModelToolUse {
 406            id: "tool_id_2".into(),
 407            name: ToolRequiringPermission.name().into(),
 408            raw_input: "{}".into(),
 409            input: json!({}),
 410            is_input_complete: true,
 411        },
 412    ));
 413    fake_model.end_last_completion_stream();
 414    let tool_call_auth_1 = next_tool_call_authorization(&mut events).await;
 415    let tool_call_auth_2 = next_tool_call_authorization(&mut events).await;
 416
 417    // Approve the first
 418    tool_call_auth_1
 419        .response
 420        .send(tool_call_auth_1.options[1].id.clone())
 421        .unwrap();
 422    cx.run_until_parked();
 423
 424    // Reject the second
 425    tool_call_auth_2
 426        .response
 427        .send(tool_call_auth_1.options[2].id.clone())
 428        .unwrap();
 429    cx.run_until_parked();
 430
 431    let completion = fake_model.pending_completions().pop().unwrap();
 432    let message = completion.messages.last().unwrap();
 433    assert_eq!(
 434        message.content,
 435        vec![
 436            language_model::MessageContent::ToolResult(LanguageModelToolResult {
 437                tool_use_id: tool_call_auth_1.tool_call.id.0.to_string().into(),
 438                tool_name: ToolRequiringPermission.name().into(),
 439                is_error: false,
 440                content: "Allowed".into(),
 441                output: Some("Allowed".into())
 442            }),
 443            language_model::MessageContent::ToolResult(LanguageModelToolResult {
 444                tool_use_id: tool_call_auth_2.tool_call.id.0.to_string().into(),
 445                tool_name: ToolRequiringPermission.name().into(),
 446                is_error: true,
 447                content: "Permission to run tool denied by user".into(),
 448                output: None
 449            })
 450        ]
 451    );
 452
 453    // Simulate yet another tool call.
 454    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
 455        LanguageModelToolUse {
 456            id: "tool_id_3".into(),
 457            name: ToolRequiringPermission.name().into(),
 458            raw_input: "{}".into(),
 459            input: json!({}),
 460            is_input_complete: true,
 461        },
 462    ));
 463    fake_model.end_last_completion_stream();
 464
 465    // Respond by always allowing tools.
 466    let tool_call_auth_3 = next_tool_call_authorization(&mut events).await;
 467    tool_call_auth_3
 468        .response
 469        .send(tool_call_auth_3.options[0].id.clone())
 470        .unwrap();
 471    cx.run_until_parked();
 472    let completion = fake_model.pending_completions().pop().unwrap();
 473    let message = completion.messages.last().unwrap();
 474    assert_eq!(
 475        message.content,
 476        vec![language_model::MessageContent::ToolResult(
 477            LanguageModelToolResult {
 478                tool_use_id: tool_call_auth_3.tool_call.id.0.to_string().into(),
 479                tool_name: ToolRequiringPermission.name().into(),
 480                is_error: false,
 481                content: "Allowed".into(),
 482                output: Some("Allowed".into())
 483            }
 484        )]
 485    );
 486
 487    // Simulate a final tool call, ensuring we don't trigger authorization.
 488    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
 489        LanguageModelToolUse {
 490            id: "tool_id_4".into(),
 491            name: ToolRequiringPermission.name().into(),
 492            raw_input: "{}".into(),
 493            input: json!({}),
 494            is_input_complete: true,
 495        },
 496    ));
 497    fake_model.end_last_completion_stream();
 498    cx.run_until_parked();
 499    let completion = fake_model.pending_completions().pop().unwrap();
 500    let message = completion.messages.last().unwrap();
 501    assert_eq!(
 502        message.content,
 503        vec![language_model::MessageContent::ToolResult(
 504            LanguageModelToolResult {
 505                tool_use_id: "tool_id_4".into(),
 506                tool_name: ToolRequiringPermission.name().into(),
 507                is_error: false,
 508                content: "Allowed".into(),
 509                output: Some("Allowed".into())
 510            }
 511        )]
 512    );
 513}
 514
 515#[gpui::test]
 516async fn test_tool_hallucination(cx: &mut TestAppContext) {
 517    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
 518    let fake_model = model.as_fake();
 519
 520    let mut events = thread
 521        .update(cx, |thread, cx| {
 522            thread.send(UserMessageId::new(), ["abc"], cx)
 523        })
 524        .unwrap();
 525    cx.run_until_parked();
 526    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
 527        LanguageModelToolUse {
 528            id: "tool_id_1".into(),
 529            name: "nonexistent_tool".into(),
 530            raw_input: "{}".into(),
 531            input: json!({}),
 532            is_input_complete: true,
 533        },
 534    ));
 535    fake_model.end_last_completion_stream();
 536
 537    let tool_call = expect_tool_call(&mut events).await;
 538    assert_eq!(tool_call.title, "nonexistent_tool");
 539    assert_eq!(tool_call.status, acp::ToolCallStatus::Pending);
 540    let update = expect_tool_call_update_fields(&mut events).await;
 541    assert_eq!(update.fields.status, Some(acp::ToolCallStatus::Failed));
 542}
 543
 544#[gpui::test]
 545async fn test_resume_after_tool_use_limit(cx: &mut TestAppContext) {
 546    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
 547    let fake_model = model.as_fake();
 548
 549    let events = thread
 550        .update(cx, |thread, cx| {
 551            thread.add_tool(EchoTool);
 552            thread.send(UserMessageId::new(), ["abc"], cx)
 553        })
 554        .unwrap();
 555    cx.run_until_parked();
 556    let tool_use = LanguageModelToolUse {
 557        id: "tool_id_1".into(),
 558        name: EchoTool.name().into(),
 559        raw_input: "{}".into(),
 560        input: serde_json::to_value(&EchoToolInput { text: "def".into() }).unwrap(),
 561        is_input_complete: true,
 562    };
 563    fake_model
 564        .send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(tool_use.clone()));
 565    fake_model.end_last_completion_stream();
 566
 567    cx.run_until_parked();
 568    let completion = fake_model.pending_completions().pop().unwrap();
 569    let tool_result = LanguageModelToolResult {
 570        tool_use_id: "tool_id_1".into(),
 571        tool_name: EchoTool.name().into(),
 572        is_error: false,
 573        content: "def".into(),
 574        output: Some("def".into()),
 575    };
 576    assert_eq!(
 577        completion.messages[1..],
 578        vec![
 579            LanguageModelRequestMessage {
 580                role: Role::User,
 581                content: vec!["abc".into()],
 582                cache: false
 583            },
 584            LanguageModelRequestMessage {
 585                role: Role::Assistant,
 586                content: vec![MessageContent::ToolUse(tool_use.clone())],
 587                cache: false
 588            },
 589            LanguageModelRequestMessage {
 590                role: Role::User,
 591                content: vec![MessageContent::ToolResult(tool_result.clone())],
 592                cache: true
 593            },
 594        ]
 595    );
 596
 597    // Simulate reaching tool use limit.
 598    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::StatusUpdate(
 599        cloud_llm_client::CompletionRequestStatus::ToolUseLimitReached,
 600    ));
 601    fake_model.end_last_completion_stream();
 602    let last_event = events.collect::<Vec<_>>().await.pop().unwrap();
 603    assert!(
 604        last_event
 605            .unwrap_err()
 606            .is::<language_model::ToolUseLimitReachedError>()
 607    );
 608
 609    let events = thread.update(cx, |thread, cx| thread.resume(cx)).unwrap();
 610    cx.run_until_parked();
 611    let completion = fake_model.pending_completions().pop().unwrap();
 612    assert_eq!(
 613        completion.messages[1..],
 614        vec![
 615            LanguageModelRequestMessage {
 616                role: Role::User,
 617                content: vec!["abc".into()],
 618                cache: false
 619            },
 620            LanguageModelRequestMessage {
 621                role: Role::Assistant,
 622                content: vec![MessageContent::ToolUse(tool_use)],
 623                cache: false
 624            },
 625            LanguageModelRequestMessage {
 626                role: Role::User,
 627                content: vec![MessageContent::ToolResult(tool_result)],
 628                cache: false
 629            },
 630            LanguageModelRequestMessage {
 631                role: Role::User,
 632                content: vec!["Continue where you left off".into()],
 633                cache: true
 634            }
 635        ]
 636    );
 637
 638    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::Text("Done".into()));
 639    fake_model.end_last_completion_stream();
 640    events.collect::<Vec<_>>().await;
 641    thread.read_with(cx, |thread, _cx| {
 642        assert_eq!(
 643            thread.last_message().unwrap().to_markdown(),
 644            indoc! {"
 645                ## Assistant
 646
 647                Done
 648            "}
 649        )
 650    });
 651
 652    // Ensure we error if calling resume when tool use limit was *not* reached.
 653    let error = thread
 654        .update(cx, |thread, cx| thread.resume(cx))
 655        .unwrap_err();
 656    assert_eq!(
 657        error.to_string(),
 658        "can only resume after tool use limit is reached"
 659    )
 660}
 661
 662#[gpui::test]
 663async fn test_send_after_tool_use_limit(cx: &mut TestAppContext) {
 664    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
 665    let fake_model = model.as_fake();
 666
 667    let events = thread
 668        .update(cx, |thread, cx| {
 669            thread.add_tool(EchoTool);
 670            thread.send(UserMessageId::new(), ["abc"], cx)
 671        })
 672        .unwrap();
 673    cx.run_until_parked();
 674
 675    let tool_use = LanguageModelToolUse {
 676        id: "tool_id_1".into(),
 677        name: EchoTool.name().into(),
 678        raw_input: "{}".into(),
 679        input: serde_json::to_value(&EchoToolInput { text: "def".into() }).unwrap(),
 680        is_input_complete: true,
 681    };
 682    let tool_result = LanguageModelToolResult {
 683        tool_use_id: "tool_id_1".into(),
 684        tool_name: EchoTool.name().into(),
 685        is_error: false,
 686        content: "def".into(),
 687        output: Some("def".into()),
 688    };
 689    fake_model
 690        .send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(tool_use.clone()));
 691    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::StatusUpdate(
 692        cloud_llm_client::CompletionRequestStatus::ToolUseLimitReached,
 693    ));
 694    fake_model.end_last_completion_stream();
 695    let last_event = events.collect::<Vec<_>>().await.pop().unwrap();
 696    assert!(
 697        last_event
 698            .unwrap_err()
 699            .is::<language_model::ToolUseLimitReachedError>()
 700    );
 701
 702    thread
 703        .update(cx, |thread, cx| {
 704            thread.send(UserMessageId::new(), vec!["ghi"], cx)
 705        })
 706        .unwrap();
 707    cx.run_until_parked();
 708    let completion = fake_model.pending_completions().pop().unwrap();
 709    assert_eq!(
 710        completion.messages[1..],
 711        vec![
 712            LanguageModelRequestMessage {
 713                role: Role::User,
 714                content: vec!["abc".into()],
 715                cache: false
 716            },
 717            LanguageModelRequestMessage {
 718                role: Role::Assistant,
 719                content: vec![MessageContent::ToolUse(tool_use)],
 720                cache: false
 721            },
 722            LanguageModelRequestMessage {
 723                role: Role::User,
 724                content: vec![MessageContent::ToolResult(tool_result)],
 725                cache: false
 726            },
 727            LanguageModelRequestMessage {
 728                role: Role::User,
 729                content: vec!["ghi".into()],
 730                cache: true
 731            }
 732        ]
 733    );
 734}
 735
 736async fn expect_tool_call(
 737    events: &mut UnboundedReceiver<Result<AgentResponseEvent>>,
 738) -> acp::ToolCall {
 739    let event = events
 740        .next()
 741        .await
 742        .expect("no tool call authorization event received")
 743        .unwrap();
 744    match event {
 745        AgentResponseEvent::ToolCall(tool_call) => return tool_call,
 746        event => {
 747            panic!("Unexpected event {event:?}");
 748        }
 749    }
 750}
 751
 752async fn expect_tool_call_update_fields(
 753    events: &mut UnboundedReceiver<Result<AgentResponseEvent>>,
 754) -> acp::ToolCallUpdate {
 755    let event = events
 756        .next()
 757        .await
 758        .expect("no tool call authorization event received")
 759        .unwrap();
 760    match event {
 761        AgentResponseEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields(update)) => {
 762            return update;
 763        }
 764        event => {
 765            panic!("Unexpected event {event:?}");
 766        }
 767    }
 768}
 769
 770async fn next_tool_call_authorization(
 771    events: &mut UnboundedReceiver<Result<AgentResponseEvent>>,
 772) -> ToolCallAuthorization {
 773    loop {
 774        let event = events
 775            .next()
 776            .await
 777            .expect("no tool call authorization event received")
 778            .unwrap();
 779        if let AgentResponseEvent::ToolCallAuthorization(tool_call_authorization) = event {
 780            let permission_kinds = tool_call_authorization
 781                .options
 782                .iter()
 783                .map(|o| o.kind)
 784                .collect::<Vec<_>>();
 785            assert_eq!(
 786                permission_kinds,
 787                vec![
 788                    acp::PermissionOptionKind::AllowAlways,
 789                    acp::PermissionOptionKind::AllowOnce,
 790                    acp::PermissionOptionKind::RejectOnce,
 791                ]
 792            );
 793            return tool_call_authorization;
 794        }
 795    }
 796}
 797
 798#[gpui::test]
 799#[ignore = "can't run on CI yet"]
 800async fn test_concurrent_tool_calls(cx: &mut TestAppContext) {
 801    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4).await;
 802
 803    // Test concurrent tool calls with different delay times
 804    let events = thread
 805        .update(cx, |thread, cx| {
 806            thread.add_tool(DelayTool);
 807            thread.send(
 808                UserMessageId::new(),
 809                [
 810                    "Call the delay tool twice in the same message.",
 811                    "Once with 100ms. Once with 300ms.",
 812                    "When both timers are complete, describe the outputs.",
 813                ],
 814                cx,
 815            )
 816        })
 817        .unwrap()
 818        .collect()
 819        .await;
 820
 821    let stop_reasons = stop_events(events);
 822    assert_eq!(stop_reasons, vec![acp::StopReason::EndTurn]);
 823
 824    thread.update(cx, |thread, _cx| {
 825        let last_message = thread.last_message().unwrap();
 826        let agent_message = last_message.as_agent_message().unwrap();
 827        let text = agent_message
 828            .content
 829            .iter()
 830            .filter_map(|content| {
 831                if let AgentMessageContent::Text(text) = content {
 832                    Some(text.as_str())
 833                } else {
 834                    None
 835                }
 836            })
 837            .collect::<String>();
 838
 839        assert!(text.contains("Ding"));
 840    });
 841}
 842
 843#[gpui::test]
 844async fn test_profiles(cx: &mut TestAppContext) {
 845    let ThreadTest {
 846        model, thread, fs, ..
 847    } = setup(cx, TestModel::Fake).await;
 848    let fake_model = model.as_fake();
 849
 850    thread.update(cx, |thread, _cx| {
 851        thread.add_tool(DelayTool);
 852        thread.add_tool(EchoTool);
 853        thread.add_tool(InfiniteTool);
 854    });
 855
 856    // Override profiles and wait for settings to be loaded.
 857    fs.insert_file(
 858        paths::settings_file(),
 859        json!({
 860            "agent": {
 861                "profiles": {
 862                    "test-1": {
 863                        "name": "Test Profile 1",
 864                        "tools": {
 865                            EchoTool.name(): true,
 866                            DelayTool.name(): true,
 867                        }
 868                    },
 869                    "test-2": {
 870                        "name": "Test Profile 2",
 871                        "tools": {
 872                            InfiniteTool.name(): true,
 873                        }
 874                    }
 875                }
 876            }
 877        })
 878        .to_string()
 879        .into_bytes(),
 880    )
 881    .await;
 882    cx.run_until_parked();
 883
 884    // Test that test-1 profile (default) has echo and delay tools
 885    thread
 886        .update(cx, |thread, cx| {
 887            thread.set_profile(AgentProfileId("test-1".into()));
 888            thread.send(UserMessageId::new(), ["test"], cx)
 889        })
 890        .unwrap();
 891    cx.run_until_parked();
 892
 893    let mut pending_completions = fake_model.pending_completions();
 894    assert_eq!(pending_completions.len(), 1);
 895    let completion = pending_completions.pop().unwrap();
 896    let tool_names: Vec<String> = completion
 897        .tools
 898        .iter()
 899        .map(|tool| tool.name.clone())
 900        .collect();
 901    assert_eq!(tool_names, vec![DelayTool.name(), EchoTool.name()]);
 902    fake_model.end_last_completion_stream();
 903
 904    // Switch to test-2 profile, and verify that it has only the infinite tool.
 905    thread
 906        .update(cx, |thread, cx| {
 907            thread.set_profile(AgentProfileId("test-2".into()));
 908            thread.send(UserMessageId::new(), ["test2"], cx)
 909        })
 910        .unwrap();
 911    cx.run_until_parked();
 912    let mut pending_completions = fake_model.pending_completions();
 913    assert_eq!(pending_completions.len(), 1);
 914    let completion = pending_completions.pop().unwrap();
 915    let tool_names: Vec<String> = completion
 916        .tools
 917        .iter()
 918        .map(|tool| tool.name.clone())
 919        .collect();
 920    assert_eq!(tool_names, vec![InfiniteTool.name()]);
 921}
 922
 923#[gpui::test]
 924#[ignore = "can't run on CI yet"]
 925async fn test_cancellation(cx: &mut TestAppContext) {
 926    let ThreadTest { thread, .. } = setup(cx, TestModel::Sonnet4).await;
 927
 928    let mut events = thread
 929        .update(cx, |thread, cx| {
 930            thread.add_tool(InfiniteTool);
 931            thread.add_tool(EchoTool);
 932            thread.send(
 933                UserMessageId::new(),
 934                ["Call the echo tool, then call the infinite tool, then explain their output"],
 935                cx,
 936            )
 937        })
 938        .unwrap();
 939
 940    // Wait until both tools are called.
 941    let mut expected_tools = vec!["Echo", "Infinite Tool"];
 942    let mut echo_id = None;
 943    let mut echo_completed = false;
 944    while let Some(event) = events.next().await {
 945        match event.unwrap() {
 946            AgentResponseEvent::ToolCall(tool_call) => {
 947                assert_eq!(tool_call.title, expected_tools.remove(0));
 948                if tool_call.title == "Echo" {
 949                    echo_id = Some(tool_call.id);
 950                }
 951            }
 952            AgentResponseEvent::ToolCallUpdate(acp_thread::ToolCallUpdate::UpdateFields(
 953                acp::ToolCallUpdate {
 954                    id,
 955                    fields:
 956                        acp::ToolCallUpdateFields {
 957                            status: Some(acp::ToolCallStatus::Completed),
 958                            ..
 959                        },
 960                },
 961            )) if Some(&id) == echo_id.as_ref() => {
 962                echo_completed = true;
 963            }
 964            _ => {}
 965        }
 966
 967        if expected_tools.is_empty() && echo_completed {
 968            break;
 969        }
 970    }
 971
 972    // Cancel the current send and ensure that the event stream is closed, even
 973    // if one of the tools is still running.
 974    thread.update(cx, |thread, _cx| thread.cancel());
 975    let events = events.collect::<Vec<_>>().await;
 976    let last_event = events.last();
 977    assert!(
 978        matches!(
 979            last_event,
 980            Some(Ok(AgentResponseEvent::Stop(acp::StopReason::Canceled)))
 981        ),
 982        "unexpected event {last_event:?}"
 983    );
 984
 985    // Ensure we can still send a new message after cancellation.
 986    let events = thread
 987        .update(cx, |thread, cx| {
 988            thread.send(
 989                UserMessageId::new(),
 990                ["Testing: reply with 'Hello' then stop."],
 991                cx,
 992            )
 993        })
 994        .unwrap()
 995        .collect::<Vec<_>>()
 996        .await;
 997    thread.update(cx, |thread, _cx| {
 998        let message = thread.last_message().unwrap();
 999        let agent_message = message.as_agent_message().unwrap();
1000        assert_eq!(
1001            agent_message.content,
1002            vec![AgentMessageContent::Text("Hello".to_string())]
1003        );
1004    });
1005    assert_eq!(stop_events(events), vec![acp::StopReason::EndTurn]);
1006}
1007
1008#[gpui::test]
1009async fn test_in_progress_send_canceled_by_next_send(cx: &mut TestAppContext) {
1010    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
1011    let fake_model = model.as_fake();
1012
1013    let events_1 = thread
1014        .update(cx, |thread, cx| {
1015            thread.send(UserMessageId::new(), ["Hello 1"], cx)
1016        })
1017        .unwrap();
1018    cx.run_until_parked();
1019    fake_model.send_last_completion_stream_text_chunk("Hey 1!");
1020    cx.run_until_parked();
1021
1022    let events_2 = thread
1023        .update(cx, |thread, cx| {
1024            thread.send(UserMessageId::new(), ["Hello 2"], cx)
1025        })
1026        .unwrap();
1027    cx.run_until_parked();
1028    fake_model.send_last_completion_stream_text_chunk("Hey 2!");
1029    fake_model
1030        .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
1031    fake_model.end_last_completion_stream();
1032
1033    let events_1 = events_1.collect::<Vec<_>>().await;
1034    assert_eq!(stop_events(events_1), vec![acp::StopReason::Canceled]);
1035    let events_2 = events_2.collect::<Vec<_>>().await;
1036    assert_eq!(stop_events(events_2), vec![acp::StopReason::EndTurn]);
1037}
1038
1039#[gpui::test]
1040async fn test_subsequent_successful_sends_dont_cancel(cx: &mut TestAppContext) {
1041    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
1042    let fake_model = model.as_fake();
1043
1044    let events_1 = thread
1045        .update(cx, |thread, cx| {
1046            thread.send(UserMessageId::new(), ["Hello 1"], cx)
1047        })
1048        .unwrap();
1049    cx.run_until_parked();
1050    fake_model.send_last_completion_stream_text_chunk("Hey 1!");
1051    fake_model
1052        .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
1053    fake_model.end_last_completion_stream();
1054    let events_1 = events_1.collect::<Vec<_>>().await;
1055
1056    let events_2 = thread
1057        .update(cx, |thread, cx| {
1058            thread.send(UserMessageId::new(), ["Hello 2"], cx)
1059        })
1060        .unwrap();
1061    cx.run_until_parked();
1062    fake_model.send_last_completion_stream_text_chunk("Hey 2!");
1063    fake_model
1064        .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::EndTurn));
1065    fake_model.end_last_completion_stream();
1066    let events_2 = events_2.collect::<Vec<_>>().await;
1067
1068    assert_eq!(stop_events(events_1), vec![acp::StopReason::EndTurn]);
1069    assert_eq!(stop_events(events_2), vec![acp::StopReason::EndTurn]);
1070}
1071
1072#[gpui::test]
1073async fn test_refusal(cx: &mut TestAppContext) {
1074    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
1075    let fake_model = model.as_fake();
1076
1077    let events = thread
1078        .update(cx, |thread, cx| {
1079            thread.send(UserMessageId::new(), ["Hello"], cx)
1080        })
1081        .unwrap();
1082    cx.run_until_parked();
1083    thread.read_with(cx, |thread, _| {
1084        assert_eq!(
1085            thread.to_markdown(),
1086            indoc! {"
1087                ## User
1088
1089                Hello
1090            "}
1091        );
1092    });
1093
1094    fake_model.send_last_completion_stream_text_chunk("Hey!");
1095    cx.run_until_parked();
1096    thread.read_with(cx, |thread, _| {
1097        assert_eq!(
1098            thread.to_markdown(),
1099            indoc! {"
1100                ## User
1101
1102                Hello
1103
1104                ## Assistant
1105
1106                Hey!
1107            "}
1108        );
1109    });
1110
1111    // If the model refuses to continue, the thread should remove all the messages after the last user message.
1112    fake_model
1113        .send_last_completion_stream_event(LanguageModelCompletionEvent::Stop(StopReason::Refusal));
1114    let events = events.collect::<Vec<_>>().await;
1115    assert_eq!(stop_events(events), vec![acp::StopReason::Refusal]);
1116    thread.read_with(cx, |thread, _| {
1117        assert_eq!(thread.to_markdown(), "");
1118    });
1119}
1120
1121#[gpui::test]
1122async fn test_truncate(cx: &mut TestAppContext) {
1123    let ThreadTest { model, thread, .. } = setup(cx, TestModel::Fake).await;
1124    let fake_model = model.as_fake();
1125
1126    let message_id = UserMessageId::new();
1127    thread
1128        .update(cx, |thread, cx| {
1129            thread.send(message_id.clone(), ["Hello"], cx)
1130        })
1131        .unwrap();
1132    cx.run_until_parked();
1133    thread.read_with(cx, |thread, _| {
1134        assert_eq!(
1135            thread.to_markdown(),
1136            indoc! {"
1137                ## User
1138
1139                Hello
1140            "}
1141        );
1142    });
1143
1144    fake_model.send_last_completion_stream_text_chunk("Hey!");
1145    cx.run_until_parked();
1146    thread.read_with(cx, |thread, _| {
1147        assert_eq!(
1148            thread.to_markdown(),
1149            indoc! {"
1150                ## User
1151
1152                Hello
1153
1154                ## Assistant
1155
1156                Hey!
1157            "}
1158        );
1159    });
1160
1161    thread
1162        .update(cx, |thread, _cx| thread.truncate(message_id))
1163        .unwrap();
1164    cx.run_until_parked();
1165    thread.read_with(cx, |thread, _| {
1166        assert_eq!(thread.to_markdown(), "");
1167    });
1168
1169    // Ensure we can still send a new message after truncation.
1170    thread
1171        .update(cx, |thread, cx| {
1172            thread.send(UserMessageId::new(), ["Hi"], cx)
1173        })
1174        .unwrap();
1175    thread.update(cx, |thread, _cx| {
1176        assert_eq!(
1177            thread.to_markdown(),
1178            indoc! {"
1179                ## User
1180
1181                Hi
1182            "}
1183        );
1184    });
1185    cx.run_until_parked();
1186    fake_model.send_last_completion_stream_text_chunk("Ahoy!");
1187    cx.run_until_parked();
1188    thread.read_with(cx, |thread, _| {
1189        assert_eq!(
1190            thread.to_markdown(),
1191            indoc! {"
1192                ## User
1193
1194                Hi
1195
1196                ## Assistant
1197
1198                Ahoy!
1199            "}
1200        );
1201    });
1202}
1203
1204#[gpui::test]
1205async fn test_agent_connection(cx: &mut TestAppContext) {
1206    cx.update(settings::init);
1207    let templates = Templates::new();
1208
1209    // Initialize language model system with test provider
1210    cx.update(|cx| {
1211        gpui_tokio::init(cx);
1212        client::init_settings(cx);
1213
1214        let http_client = FakeHttpClient::with_404_response();
1215        let clock = Arc::new(clock::FakeSystemClock::new());
1216        let client = Client::new(clock, http_client, cx);
1217        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
1218        language_model::init(client.clone(), cx);
1219        language_models::init(user_store.clone(), client.clone(), cx);
1220        Project::init_settings(cx);
1221        LanguageModelRegistry::test(cx);
1222        agent_settings::init(cx);
1223    });
1224    cx.executor().forbid_parking();
1225
1226    // Create a project for new_thread
1227    let fake_fs = cx.update(|cx| fs::FakeFs::new(cx.background_executor().clone()));
1228    fake_fs.insert_tree(path!("/test"), json!({})).await;
1229    let project = Project::test(fake_fs.clone(), [Path::new("/test")], cx).await;
1230    let cwd = Path::new("/test");
1231
1232    // Create agent and connection
1233    let agent = NativeAgent::new(
1234        project.clone(),
1235        templates.clone(),
1236        None,
1237        fake_fs.clone(),
1238        &mut cx.to_async(),
1239    )
1240    .await
1241    .unwrap();
1242    let connection = NativeAgentConnection(agent.clone());
1243
1244    // Test model_selector returns Some
1245    let selector_opt = connection.model_selector();
1246    assert!(
1247        selector_opt.is_some(),
1248        "agent2 should always support ModelSelector"
1249    );
1250    let selector = selector_opt.unwrap();
1251
1252    // Test list_models
1253    let listed_models = cx
1254        .update(|cx| selector.list_models(cx))
1255        .await
1256        .expect("list_models should succeed");
1257    let AgentModelList::Grouped(listed_models) = listed_models else {
1258        panic!("Unexpected model list type");
1259    };
1260    assert!(!listed_models.is_empty(), "should have at least one model");
1261    assert_eq!(
1262        listed_models[&AgentModelGroupName("Fake".into())][0].id.0,
1263        "fake/fake"
1264    );
1265
1266    // Create a thread using new_thread
1267    let connection_rc = Rc::new(connection.clone());
1268    let acp_thread = cx
1269        .update(|cx| connection_rc.new_thread(project, cwd, cx))
1270        .await
1271        .expect("new_thread should succeed");
1272
1273    // Get the session_id from the AcpThread
1274    let session_id = acp_thread.read_with(cx, |thread, _| thread.session_id().clone());
1275
1276    // Test selected_model returns the default
1277    let model = cx
1278        .update(|cx| selector.selected_model(&session_id, cx))
1279        .await
1280        .expect("selected_model should succeed");
1281    let model = cx
1282        .update(|cx| agent.read(cx).models().model_from_id(&model.id))
1283        .unwrap();
1284    let model = model.as_fake();
1285    assert_eq!(model.id().0, "fake", "should return default model");
1286
1287    let request = acp_thread.update(cx, |thread, cx| thread.send(vec!["abc".into()], cx));
1288    cx.run_until_parked();
1289    model.send_last_completion_stream_text_chunk("def");
1290    cx.run_until_parked();
1291    acp_thread.read_with(cx, |thread, cx| {
1292        assert_eq!(
1293            thread.to_markdown(cx),
1294            indoc! {"
1295                ## User
1296
1297                abc
1298
1299                ## Assistant
1300
1301                def
1302
1303            "}
1304        )
1305    });
1306
1307    // Test cancel
1308    cx.update(|cx| connection.cancel(&session_id, cx));
1309    request.await.expect("prompt should fail gracefully");
1310
1311    // Ensure that dropping the ACP thread causes the native thread to be
1312    // dropped as well.
1313    cx.update(|_| drop(acp_thread));
1314    let result = cx
1315        .update(|cx| {
1316            connection.prompt(
1317                Some(acp_thread::UserMessageId::new()),
1318                acp::PromptRequest {
1319                    session_id: session_id.clone(),
1320                    prompt: vec!["ghi".into()],
1321                },
1322                cx,
1323            )
1324        })
1325        .await;
1326    assert_eq!(
1327        result.as_ref().unwrap_err().to_string(),
1328        "Session not found",
1329        "unexpected result: {:?}",
1330        result
1331    );
1332}
1333
1334#[gpui::test]
1335async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
1336    let ThreadTest { thread, model, .. } = setup(cx, TestModel::Fake).await;
1337    thread.update(cx, |thread, _cx| thread.add_tool(ThinkingTool));
1338    let fake_model = model.as_fake();
1339
1340    let mut events = thread
1341        .update(cx, |thread, cx| {
1342            thread.send(UserMessageId::new(), ["Think"], cx)
1343        })
1344        .unwrap();
1345    cx.run_until_parked();
1346
1347    // Simulate streaming partial input.
1348    let input = json!({});
1349    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
1350        LanguageModelToolUse {
1351            id: "1".into(),
1352            name: ThinkingTool.name().into(),
1353            raw_input: input.to_string(),
1354            input,
1355            is_input_complete: false,
1356        },
1357    ));
1358
1359    // Input streaming completed
1360    let input = json!({ "content": "Thinking hard!" });
1361    fake_model.send_last_completion_stream_event(LanguageModelCompletionEvent::ToolUse(
1362        LanguageModelToolUse {
1363            id: "1".into(),
1364            name: "thinking".into(),
1365            raw_input: input.to_string(),
1366            input,
1367            is_input_complete: true,
1368        },
1369    ));
1370    fake_model.end_last_completion_stream();
1371    cx.run_until_parked();
1372
1373    let tool_call = expect_tool_call(&mut events).await;
1374    assert_eq!(
1375        tool_call,
1376        acp::ToolCall {
1377            id: acp::ToolCallId("1".into()),
1378            title: "Thinking".into(),
1379            kind: acp::ToolKind::Think,
1380            status: acp::ToolCallStatus::Pending,
1381            content: vec![],
1382            locations: vec![],
1383            raw_input: Some(json!({})),
1384            raw_output: None,
1385        }
1386    );
1387    let update = expect_tool_call_update_fields(&mut events).await;
1388    assert_eq!(
1389        update,
1390        acp::ToolCallUpdate {
1391            id: acp::ToolCallId("1".into()),
1392            fields: acp::ToolCallUpdateFields {
1393                title: Some("Thinking".into()),
1394                kind: Some(acp::ToolKind::Think),
1395                raw_input: Some(json!({ "content": "Thinking hard!" })),
1396                ..Default::default()
1397            },
1398        }
1399    );
1400    let update = expect_tool_call_update_fields(&mut events).await;
1401    assert_eq!(
1402        update,
1403        acp::ToolCallUpdate {
1404            id: acp::ToolCallId("1".into()),
1405            fields: acp::ToolCallUpdateFields {
1406                status: Some(acp::ToolCallStatus::InProgress),
1407                ..Default::default()
1408            },
1409        }
1410    );
1411    let update = expect_tool_call_update_fields(&mut events).await;
1412    assert_eq!(
1413        update,
1414        acp::ToolCallUpdate {
1415            id: acp::ToolCallId("1".into()),
1416            fields: acp::ToolCallUpdateFields {
1417                content: Some(vec!["Thinking hard!".into()]),
1418                ..Default::default()
1419            },
1420        }
1421    );
1422    let update = expect_tool_call_update_fields(&mut events).await;
1423    assert_eq!(
1424        update,
1425        acp::ToolCallUpdate {
1426            id: acp::ToolCallId("1".into()),
1427            fields: acp::ToolCallUpdateFields {
1428                status: Some(acp::ToolCallStatus::Completed),
1429                raw_output: Some("Finished thinking.".into()),
1430                ..Default::default()
1431            },
1432        }
1433    );
1434}
1435
1436/// Filters out the stop events for asserting against in tests
1437fn stop_events(result_events: Vec<Result<AgentResponseEvent>>) -> Vec<acp::StopReason> {
1438    result_events
1439        .into_iter()
1440        .filter_map(|event| match event.unwrap() {
1441            AgentResponseEvent::Stop(stop_reason) => Some(stop_reason),
1442            _ => None,
1443        })
1444        .collect()
1445}
1446
1447struct ThreadTest {
1448    model: Arc<dyn LanguageModel>,
1449    thread: Entity<Thread>,
1450    project_context: Rc<RefCell<ProjectContext>>,
1451    fs: Arc<FakeFs>,
1452}
1453
1454enum TestModel {
1455    Sonnet4,
1456    Sonnet4Thinking,
1457    Fake,
1458}
1459
1460impl TestModel {
1461    fn id(&self) -> LanguageModelId {
1462        match self {
1463            TestModel::Sonnet4 => LanguageModelId("claude-sonnet-4-latest".into()),
1464            TestModel::Sonnet4Thinking => LanguageModelId("claude-sonnet-4-thinking-latest".into()),
1465            TestModel::Fake => unreachable!(),
1466        }
1467    }
1468}
1469
1470async fn setup(cx: &mut TestAppContext, model: TestModel) -> ThreadTest {
1471    cx.executor().allow_parking();
1472
1473    let fs = FakeFs::new(cx.background_executor.clone());
1474    fs.create_dir(paths::settings_file().parent().unwrap())
1475        .await
1476        .unwrap();
1477    fs.insert_file(
1478        paths::settings_file(),
1479        json!({
1480            "agent": {
1481                "default_profile": "test-profile",
1482                "profiles": {
1483                    "test-profile": {
1484                        "name": "Test Profile",
1485                        "tools": {
1486                            EchoTool.name(): true,
1487                            DelayTool.name(): true,
1488                            WordListTool.name(): true,
1489                            ToolRequiringPermission.name(): true,
1490                            InfiniteTool.name(): true,
1491                        }
1492                    }
1493                }
1494            }
1495        })
1496        .to_string()
1497        .into_bytes(),
1498    )
1499    .await;
1500
1501    cx.update(|cx| {
1502        settings::init(cx);
1503        Project::init_settings(cx);
1504        agent_settings::init(cx);
1505        gpui_tokio::init(cx);
1506        let http_client = ReqwestClient::user_agent("agent tests").unwrap();
1507        cx.set_http_client(Arc::new(http_client));
1508
1509        client::init_settings(cx);
1510        let client = Client::production(cx);
1511        let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
1512        language_model::init(client.clone(), cx);
1513        language_models::init(user_store.clone(), client.clone(), cx);
1514
1515        watch_settings(fs.clone(), cx);
1516    });
1517
1518    let templates = Templates::new();
1519
1520    fs.insert_tree(path!("/test"), json!({})).await;
1521    let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
1522
1523    let model = cx
1524        .update(|cx| {
1525            if let TestModel::Fake = model {
1526                Task::ready(Arc::new(FakeLanguageModel::default()) as Arc<_>)
1527            } else {
1528                let model_id = model.id();
1529                let models = LanguageModelRegistry::read_global(cx);
1530                let model = models
1531                    .available_models(cx)
1532                    .find(|model| model.id() == model_id)
1533                    .unwrap();
1534
1535                let provider = models.provider(&model.provider_id()).unwrap();
1536                let authenticated = provider.authenticate(cx);
1537
1538                cx.spawn(async move |_cx| {
1539                    authenticated.await.unwrap();
1540                    model
1541                })
1542            }
1543        })
1544        .await;
1545
1546    let project_context = Rc::new(RefCell::new(ProjectContext::default()));
1547    let context_server_registry =
1548        cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
1549    let action_log = cx.new(|_| ActionLog::new(project.clone()));
1550    let thread = cx.new(|cx| {
1551        Thread::new(
1552            project,
1553            project_context.clone(),
1554            context_server_registry,
1555            action_log,
1556            templates,
1557            Some(model.clone()),
1558            cx,
1559        )
1560    });
1561    ThreadTest {
1562        model,
1563        thread,
1564        project_context,
1565        fs,
1566    }
1567}
1568
1569#[cfg(test)]
1570#[ctor::ctor]
1571fn init_logger() {
1572    if std::env::var("RUST_LOG").is_ok() {
1573        env_logger::init();
1574    }
1575}
1576
1577fn watch_settings(fs: Arc<dyn Fs>, cx: &mut App) {
1578    let fs = fs.clone();
1579    cx.spawn({
1580        async move |cx| {
1581            let mut new_settings_content_rx = settings::watch_config_file(
1582                cx.background_executor(),
1583                fs,
1584                paths::settings_file().clone(),
1585            );
1586
1587            while let Some(new_settings_content) = new_settings_content_rx.next().await {
1588                cx.update(|cx| {
1589                    SettingsStore::update_global(cx, |settings, cx| {
1590                        settings.set_user_settings(&new_settings_content, cx)
1591                    })
1592                })
1593                .ok();
1594            }
1595        }
1596    })
1597    .detach();
1598}