1use crate::{
   2    CacheStatus, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus, TextThread,
   3    TextThreadEvent, TextThreadId, TextThreadOperation, TextThreadSummary,
   4};
   5use anyhow::Result;
   6use assistant_slash_command::{
   7    ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
   8    SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
   9};
  10use assistant_slash_commands::FileSlashCommand;
  11use collections::{HashMap, HashSet};
  12use fs::FakeFs;
  13use futures::{
  14    channel::mpsc,
  15    stream::{self, StreamExt},
  16};
  17use gpui::{App, Entity, SharedString, Task, TestAppContext, WeakEntity, prelude::*};
  18use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
  19use language_model::{
  20    ConfiguredModel, LanguageModelCacheConfiguration, LanguageModelRegistry, Role,
  21    fake_provider::{FakeLanguageModel, FakeLanguageModelProvider},
  22};
  23use parking_lot::Mutex;
  24use pretty_assertions::assert_eq;
  25use project::Project;
  26use prompt_store::PromptBuilder;
  27use rand::prelude::*;
  28use serde_json::json;
  29use settings::SettingsStore;
  30use std::{
  31    cell::RefCell,
  32    env,
  33    ops::Range,
  34    path::Path,
  35    rc::Rc,
  36    sync::{Arc, atomic::AtomicBool},
  37};
  38use text::{ReplicaId, ToOffset, network::Network};
  39use ui::{IconName, Window};
  40use unindent::Unindent;
  41use util::RandomCharIter;
  42use workspace::Workspace;
  43
  44#[gpui::test]
  45fn test_inserting_and_removing_messages(cx: &mut App) {
  46    init_test(cx);
  47
  48    let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
  49    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
  50    let text_thread = cx.new(|cx| {
  51        TextThread::local(
  52            registry,
  53            None,
  54            None,
  55            prompt_builder.clone(),
  56            Arc::new(SlashCommandWorkingSet::default()),
  57            cx,
  58        )
  59    });
  60    let buffer = text_thread.read(cx).buffer().clone();
  61
  62    let message_1 = text_thread.read(cx).message_anchors[0].clone();
  63    assert_eq!(
  64        messages(&text_thread, cx),
  65        vec![(message_1.id, Role::User, 0..0)]
  66    );
  67
  68    let message_2 = text_thread.update(cx, |context, cx| {
  69        context
  70            .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
  71            .unwrap()
  72    });
  73    assert_eq!(
  74        messages(&text_thread, cx),
  75        vec![
  76            (message_1.id, Role::User, 0..1),
  77            (message_2.id, Role::Assistant, 1..1)
  78        ]
  79    );
  80
  81    buffer.update(cx, |buffer, cx| {
  82        buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
  83    });
  84    assert_eq!(
  85        messages(&text_thread, cx),
  86        vec![
  87            (message_1.id, Role::User, 0..2),
  88            (message_2.id, Role::Assistant, 2..3)
  89        ]
  90    );
  91
  92    let message_3 = text_thread.update(cx, |context, cx| {
  93        context
  94            .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
  95            .unwrap()
  96    });
  97    assert_eq!(
  98        messages(&text_thread, cx),
  99        vec![
 100            (message_1.id, Role::User, 0..2),
 101            (message_2.id, Role::Assistant, 2..4),
 102            (message_3.id, Role::User, 4..4)
 103        ]
 104    );
 105
 106    let message_4 = text_thread.update(cx, |context, cx| {
 107        context
 108            .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
 109            .unwrap()
 110    });
 111    assert_eq!(
 112        messages(&text_thread, cx),
 113        vec![
 114            (message_1.id, Role::User, 0..2),
 115            (message_2.id, Role::Assistant, 2..4),
 116            (message_4.id, Role::User, 4..5),
 117            (message_3.id, Role::User, 5..5),
 118        ]
 119    );
 120
 121    buffer.update(cx, |buffer, cx| {
 122        buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
 123    });
 124    assert_eq!(
 125        messages(&text_thread, cx),
 126        vec![
 127            (message_1.id, Role::User, 0..2),
 128            (message_2.id, Role::Assistant, 2..4),
 129            (message_4.id, Role::User, 4..6),
 130            (message_3.id, Role::User, 6..7),
 131        ]
 132    );
 133
 134    // Deleting across message boundaries merges the messages.
 135    buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
 136    assert_eq!(
 137        messages(&text_thread, cx),
 138        vec![
 139            (message_1.id, Role::User, 0..3),
 140            (message_3.id, Role::User, 3..4),
 141        ]
 142    );
 143
 144    // Undoing the deletion should also undo the merge.
 145    buffer.update(cx, |buffer, cx| buffer.undo(cx));
 146    assert_eq!(
 147        messages(&text_thread, cx),
 148        vec![
 149            (message_1.id, Role::User, 0..2),
 150            (message_2.id, Role::Assistant, 2..4),
 151            (message_4.id, Role::User, 4..6),
 152            (message_3.id, Role::User, 6..7),
 153        ]
 154    );
 155
 156    // Redoing the deletion should also redo the merge.
 157    buffer.update(cx, |buffer, cx| buffer.redo(cx));
 158    assert_eq!(
 159        messages(&text_thread, cx),
 160        vec![
 161            (message_1.id, Role::User, 0..3),
 162            (message_3.id, Role::User, 3..4),
 163        ]
 164    );
 165
 166    // Ensure we can still insert after a merged message.
 167    let message_5 = text_thread.update(cx, |context, cx| {
 168        context
 169            .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
 170            .unwrap()
 171    });
 172    assert_eq!(
 173        messages(&text_thread, cx),
 174        vec![
 175            (message_1.id, Role::User, 0..3),
 176            (message_5.id, Role::System, 3..4),
 177            (message_3.id, Role::User, 4..5)
 178        ]
 179    );
 180}
 181
 182#[gpui::test]
 183fn test_message_splitting(cx: &mut App) {
 184    init_test(cx);
 185
 186    let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
 187
 188    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
 189    let text_thread = cx.new(|cx| {
 190        TextThread::local(
 191            registry.clone(),
 192            None,
 193            None,
 194            prompt_builder.clone(),
 195            Arc::new(SlashCommandWorkingSet::default()),
 196            cx,
 197        )
 198    });
 199    let buffer = text_thread.read(cx).buffer().clone();
 200
 201    let message_1 = text_thread.read(cx).message_anchors[0].clone();
 202    assert_eq!(
 203        messages(&text_thread, cx),
 204        vec![(message_1.id, Role::User, 0..0)]
 205    );
 206
 207    buffer.update(cx, |buffer, cx| {
 208        buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
 209    });
 210
 211    let (_, message_2) =
 212        text_thread.update(cx, |text_thread, cx| text_thread.split_message(3..3, cx));
 213    let message_2 = message_2.unwrap();
 214
 215    // We recycle newlines in the middle of a split message
 216    assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
 217    assert_eq!(
 218        messages(&text_thread, cx),
 219        vec![
 220            (message_1.id, Role::User, 0..4),
 221            (message_2.id, Role::User, 4..16),
 222        ]
 223    );
 224
 225    let (_, message_3) =
 226        text_thread.update(cx, |text_thread, cx| text_thread.split_message(3..3, cx));
 227    let message_3 = message_3.unwrap();
 228
 229    // We don't recycle newlines at the end of a split message
 230    assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
 231    assert_eq!(
 232        messages(&text_thread, cx),
 233        vec![
 234            (message_1.id, Role::User, 0..4),
 235            (message_3.id, Role::User, 4..5),
 236            (message_2.id, Role::User, 5..17),
 237        ]
 238    );
 239
 240    let (_, message_4) =
 241        text_thread.update(cx, |text_thread, cx| text_thread.split_message(9..9, cx));
 242    let message_4 = message_4.unwrap();
 243    assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
 244    assert_eq!(
 245        messages(&text_thread, cx),
 246        vec![
 247            (message_1.id, Role::User, 0..4),
 248            (message_3.id, Role::User, 4..5),
 249            (message_2.id, Role::User, 5..9),
 250            (message_4.id, Role::User, 9..17),
 251        ]
 252    );
 253
 254    let (_, message_5) =
 255        text_thread.update(cx, |text_thread, cx| text_thread.split_message(9..9, cx));
 256    let message_5 = message_5.unwrap();
 257    assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
 258    assert_eq!(
 259        messages(&text_thread, cx),
 260        vec![
 261            (message_1.id, Role::User, 0..4),
 262            (message_3.id, Role::User, 4..5),
 263            (message_2.id, Role::User, 5..9),
 264            (message_4.id, Role::User, 9..10),
 265            (message_5.id, Role::User, 10..18),
 266        ]
 267    );
 268
 269    let (message_6, message_7) =
 270        text_thread.update(cx, |text_thread, cx| text_thread.split_message(14..16, cx));
 271    let message_6 = message_6.unwrap();
 272    let message_7 = message_7.unwrap();
 273    assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
 274    assert_eq!(
 275        messages(&text_thread, cx),
 276        vec![
 277            (message_1.id, Role::User, 0..4),
 278            (message_3.id, Role::User, 4..5),
 279            (message_2.id, Role::User, 5..9),
 280            (message_4.id, Role::User, 9..10),
 281            (message_5.id, Role::User, 10..14),
 282            (message_6.id, Role::User, 14..17),
 283            (message_7.id, Role::User, 17..19),
 284        ]
 285    );
 286}
 287
 288#[gpui::test]
 289fn test_messages_for_offsets(cx: &mut App) {
 290    init_test(cx);
 291
 292    let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
 293    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
 294    let text_thread = cx.new(|cx| {
 295        TextThread::local(
 296            registry,
 297            None,
 298            None,
 299            prompt_builder.clone(),
 300            Arc::new(SlashCommandWorkingSet::default()),
 301            cx,
 302        )
 303    });
 304    let buffer = text_thread.read(cx).buffer().clone();
 305
 306    let message_1 = text_thread.read(cx).message_anchors[0].clone();
 307    assert_eq!(
 308        messages(&text_thread, cx),
 309        vec![(message_1.id, Role::User, 0..0)]
 310    );
 311
 312    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
 313    let message_2 = text_thread
 314        .update(cx, |text_thread, cx| {
 315            text_thread.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
 316        })
 317        .unwrap();
 318    buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
 319
 320    let message_3 = text_thread
 321        .update(cx, |text_thread, cx| {
 322            text_thread.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
 323        })
 324        .unwrap();
 325    buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
 326
 327    assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
 328    assert_eq!(
 329        messages(&text_thread, cx),
 330        vec![
 331            (message_1.id, Role::User, 0..4),
 332            (message_2.id, Role::User, 4..8),
 333            (message_3.id, Role::User, 8..11)
 334        ]
 335    );
 336
 337    assert_eq!(
 338        message_ids_for_offsets(&text_thread, &[0, 4, 9], cx),
 339        [message_1.id, message_2.id, message_3.id]
 340    );
 341    assert_eq!(
 342        message_ids_for_offsets(&text_thread, &[0, 1, 11], cx),
 343        [message_1.id, message_3.id]
 344    );
 345
 346    let message_4 = text_thread
 347        .update(cx, |text_thread, cx| {
 348            text_thread.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
 349        })
 350        .unwrap();
 351    assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
 352    assert_eq!(
 353        messages(&text_thread, cx),
 354        vec![
 355            (message_1.id, Role::User, 0..4),
 356            (message_2.id, Role::User, 4..8),
 357            (message_3.id, Role::User, 8..12),
 358            (message_4.id, Role::User, 12..12)
 359        ]
 360    );
 361    assert_eq!(
 362        message_ids_for_offsets(&text_thread, &[0, 4, 8, 12], cx),
 363        [message_1.id, message_2.id, message_3.id, message_4.id]
 364    );
 365
 366    fn message_ids_for_offsets(
 367        context: &Entity<TextThread>,
 368        offsets: &[usize],
 369        cx: &App,
 370    ) -> Vec<MessageId> {
 371        context
 372            .read(cx)
 373            .messages_for_offsets(offsets.iter().copied(), cx)
 374            .into_iter()
 375            .map(|message| message.id)
 376            .collect()
 377    }
 378}
 379
 380#[gpui::test]
 381async fn test_slash_commands(cx: &mut TestAppContext) {
 382    cx.update(init_test);
 383
 384    let fs = FakeFs::new(cx.background_executor.clone());
 385
 386    fs.insert_tree(
 387        "/test",
 388        json!({
 389            "src": {
 390                "lib.rs": "fn one() -> usize { 1 }",
 391                "main.rs": "
 392                    use crate::one;
 393                    fn main() { one(); }
 394                ".unindent(),
 395            }
 396        }),
 397    )
 398    .await;
 399
 400    let slash_command_registry = cx.update(SlashCommandRegistry::default_global);
 401    slash_command_registry.register_command(FileSlashCommand, false);
 402
 403    let registry = Arc::new(LanguageRegistry::test(cx.executor()));
 404    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
 405    let text_thread = cx.new(|cx| {
 406        TextThread::local(
 407            registry.clone(),
 408            None,
 409            None,
 410            prompt_builder.clone(),
 411            Arc::new(SlashCommandWorkingSet::default()),
 412            cx,
 413        )
 414    });
 415
 416    #[derive(Default)]
 417    struct ContextRanges {
 418        parsed_commands: HashSet<Range<language::Anchor>>,
 419        command_outputs: HashMap<InvokedSlashCommandId, Range<language::Anchor>>,
 420        output_sections: HashSet<Range<language::Anchor>>,
 421    }
 422
 423    let context_ranges = Rc::new(RefCell::new(ContextRanges::default()));
 424    text_thread.update(cx, |_, cx| {
 425        cx.subscribe(&text_thread, {
 426            let context_ranges = context_ranges.clone();
 427            move |text_thread, _, event, _| {
 428                let mut context_ranges = context_ranges.borrow_mut();
 429                match event {
 430                    TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
 431                        let command = text_thread.invoked_slash_command(command_id).unwrap();
 432                        context_ranges
 433                            .command_outputs
 434                            .insert(*command_id, command.range.clone());
 435                    }
 436                    TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
 437                        for range in removed {
 438                            context_ranges.parsed_commands.remove(range);
 439                        }
 440                        for command in updated {
 441                            context_ranges
 442                                .parsed_commands
 443                                .insert(command.source_range.clone());
 444                        }
 445                    }
 446                    TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
 447                        context_ranges.output_sections.insert(section.range.clone());
 448                    }
 449                    _ => {}
 450                }
 451            }
 452        })
 453        .detach();
 454    });
 455
 456    let buffer = text_thread.read_with(cx, |text_thread, _| text_thread.buffer().clone());
 457
 458    // Insert a slash command
 459    buffer.update(cx, |buffer, cx| {
 460        buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
 461    });
 462    assert_text_and_context_ranges(
 463        &buffer,
 464        &context_ranges,
 465        &"
 466        «/file src/lib.rs»"
 467            .unindent(),
 468        cx,
 469    );
 470
 471    // Edit the argument of the slash command.
 472    buffer.update(cx, |buffer, cx| {
 473        let edit_offset = buffer.text().find("lib.rs").unwrap();
 474        buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
 475    });
 476    assert_text_and_context_ranges(
 477        &buffer,
 478        &context_ranges,
 479        &"
 480        «/file src/main.rs»"
 481            .unindent(),
 482        cx,
 483    );
 484
 485    // Edit the name of the slash command, using one that doesn't exist.
 486    buffer.update(cx, |buffer, cx| {
 487        let edit_offset = buffer.text().find("/file").unwrap();
 488        buffer.edit(
 489            [(edit_offset..edit_offset + "/file".len(), "/unknown")],
 490            None,
 491            cx,
 492        );
 493    });
 494    assert_text_and_context_ranges(
 495        &buffer,
 496        &context_ranges,
 497        &"
 498        /unknown src/main.rs"
 499            .unindent(),
 500        cx,
 501    );
 502
 503    // Undoing the insertion of an non-existent slash command resorts the previous one.
 504    buffer.update(cx, |buffer, cx| buffer.undo(cx));
 505    assert_text_and_context_ranges(
 506        &buffer,
 507        &context_ranges,
 508        &"
 509        «/file src/main.rs»"
 510            .unindent(),
 511        cx,
 512    );
 513
 514    let (command_output_tx, command_output_rx) = mpsc::unbounded();
 515    text_thread.update(cx, |text_thread, cx| {
 516        let command_source_range = text_thread.parsed_slash_commands[0].source_range.clone();
 517        text_thread.insert_command_output(
 518            command_source_range,
 519            "file",
 520            Task::ready(Ok(command_output_rx.boxed())),
 521            true,
 522            cx,
 523        );
 524    });
 525    assert_text_and_context_ranges(
 526        &buffer,
 527        &context_ranges,
 528        &"
 529        ⟦«/file src/main.rs»
 530        …⟧
 531        "
 532        .unindent(),
 533        cx,
 534    );
 535
 536    command_output_tx
 537        .unbounded_send(Ok(SlashCommandEvent::StartSection {
 538            icon: IconName::Ai,
 539            label: "src/main.rs".into(),
 540            metadata: None,
 541        }))
 542        .unwrap();
 543    command_output_tx
 544        .unbounded_send(Ok(SlashCommandEvent::Content("src/main.rs".into())))
 545        .unwrap();
 546    cx.run_until_parked();
 547    assert_text_and_context_ranges(
 548        &buffer,
 549        &context_ranges,
 550        &"
 551        ⟦«/file src/main.rs»
 552        src/main.rs…⟧
 553        "
 554        .unindent(),
 555        cx,
 556    );
 557
 558    command_output_tx
 559        .unbounded_send(Ok(SlashCommandEvent::Content("\nfn main() {}".into())))
 560        .unwrap();
 561    cx.run_until_parked();
 562    assert_text_and_context_ranges(
 563        &buffer,
 564        &context_ranges,
 565        &"
 566        ⟦«/file src/main.rs»
 567        src/main.rs
 568        fn main() {}…⟧
 569        "
 570        .unindent(),
 571        cx,
 572    );
 573
 574    command_output_tx
 575        .unbounded_send(Ok(SlashCommandEvent::EndSection))
 576        .unwrap();
 577    cx.run_until_parked();
 578    assert_text_and_context_ranges(
 579        &buffer,
 580        &context_ranges,
 581        &"
 582        ⟦«/file src/main.rs»
 583        ⟪src/main.rs
 584        fn main() {}⟫…⟧
 585        "
 586        .unindent(),
 587        cx,
 588    );
 589
 590    drop(command_output_tx);
 591    cx.run_until_parked();
 592    assert_text_and_context_ranges(
 593        &buffer,
 594        &context_ranges,
 595        &"
 596        ⟦⟪src/main.rs
 597        fn main() {}⟫⟧
 598        "
 599        .unindent(),
 600        cx,
 601    );
 602
 603    #[track_caller]
 604    fn assert_text_and_context_ranges(
 605        buffer: &Entity<Buffer>,
 606        ranges: &RefCell<ContextRanges>,
 607        expected_marked_text: &str,
 608        cx: &mut TestAppContext,
 609    ) {
 610        let mut actual_marked_text = String::new();
 611        buffer.update(cx, |buffer, _| {
 612            struct Endpoint {
 613                offset: usize,
 614                marker: char,
 615            }
 616
 617            let ranges = ranges.borrow();
 618            let mut endpoints = Vec::new();
 619            for range in ranges.command_outputs.values() {
 620                endpoints.push(Endpoint {
 621                    offset: range.start.to_offset(buffer),
 622                    marker: '⟦',
 623                });
 624            }
 625            for range in ranges.parsed_commands.iter() {
 626                endpoints.push(Endpoint {
 627                    offset: range.start.to_offset(buffer),
 628                    marker: '«',
 629                });
 630            }
 631            for range in ranges.output_sections.iter() {
 632                endpoints.push(Endpoint {
 633                    offset: range.start.to_offset(buffer),
 634                    marker: '⟪',
 635                });
 636            }
 637
 638            for range in ranges.output_sections.iter() {
 639                endpoints.push(Endpoint {
 640                    offset: range.end.to_offset(buffer),
 641                    marker: '⟫',
 642                });
 643            }
 644            for range in ranges.parsed_commands.iter() {
 645                endpoints.push(Endpoint {
 646                    offset: range.end.to_offset(buffer),
 647                    marker: '»',
 648                });
 649            }
 650            for range in ranges.command_outputs.values() {
 651                endpoints.push(Endpoint {
 652                    offset: range.end.to_offset(buffer),
 653                    marker: '⟧',
 654                });
 655            }
 656
 657            endpoints.sort_by_key(|endpoint| endpoint.offset);
 658            let mut offset = 0;
 659            for endpoint in endpoints {
 660                actual_marked_text.extend(buffer.text_for_range(offset..endpoint.offset));
 661                actual_marked_text.push(endpoint.marker);
 662                offset = endpoint.offset;
 663            }
 664            actual_marked_text.extend(buffer.text_for_range(offset..buffer.len()));
 665        });
 666
 667        assert_eq!(actual_marked_text, expected_marked_text);
 668    }
 669}
 670
 671#[gpui::test]
 672async fn test_serialization(cx: &mut TestAppContext) {
 673    cx.update(init_test);
 674
 675    let registry = Arc::new(LanguageRegistry::test(cx.executor()));
 676    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
 677    let text_thread = cx.new(|cx| {
 678        TextThread::local(
 679            registry.clone(),
 680            None,
 681            None,
 682            prompt_builder.clone(),
 683            Arc::new(SlashCommandWorkingSet::default()),
 684            cx,
 685        )
 686    });
 687    let buffer = text_thread.read_with(cx, |text_thread, _| text_thread.buffer().clone());
 688    let message_0 = text_thread.read_with(cx, |text_thread, _| text_thread.message_anchors[0].id);
 689    let message_1 = text_thread.update(cx, |text_thread, cx| {
 690        text_thread
 691            .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
 692            .unwrap()
 693    });
 694    let message_2 = text_thread.update(cx, |text_thread, cx| {
 695        text_thread
 696            .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
 697            .unwrap()
 698    });
 699    buffer.update(cx, |buffer, cx| {
 700        buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
 701        buffer.finalize_last_transaction();
 702    });
 703    let _message_3 = text_thread.update(cx, |text_thread, cx| {
 704        text_thread
 705            .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
 706            .unwrap()
 707    });
 708    buffer.update(cx, |buffer, cx| buffer.undo(cx));
 709    assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
 710    assert_eq!(
 711        cx.read(|cx| messages(&text_thread, cx)),
 712        [
 713            (message_0, Role::User, 0..2),
 714            (message_1.id, Role::Assistant, 2..6),
 715            (message_2.id, Role::System, 6..6),
 716        ]
 717    );
 718
 719    let serialized_context = text_thread.read_with(cx, |text_thread, cx| text_thread.serialize(cx));
 720    let deserialized_context = cx.new(|cx| {
 721        TextThread::deserialize(
 722            serialized_context,
 723            Path::new("").into(),
 724            registry.clone(),
 725            prompt_builder.clone(),
 726            Arc::new(SlashCommandWorkingSet::default()),
 727            None,
 728            None,
 729            cx,
 730        )
 731    });
 732    let deserialized_buffer =
 733        deserialized_context.read_with(cx, |text_thread, _| text_thread.buffer().clone());
 734    assert_eq!(
 735        deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
 736        "a\nb\nc\n"
 737    );
 738    assert_eq!(
 739        cx.read(|cx| messages(&deserialized_context, cx)),
 740        [
 741            (message_0, Role::User, 0..2),
 742            (message_1.id, Role::Assistant, 2..6),
 743            (message_2.id, Role::System, 6..6),
 744        ]
 745    );
 746}
 747
 748#[gpui::test(iterations = 25)]
 749async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: StdRng) {
 750    cx.update(init_test);
 751
 752    let min_peers = env::var("MIN_PEERS")
 753        .map(|i| i.parse().expect("invalid `MIN_PEERS` variable"))
 754        .unwrap_or(2);
 755    let max_peers = env::var("MAX_PEERS")
 756        .map(|i| i.parse().expect("invalid `MAX_PEERS` variable"))
 757        .unwrap_or(5);
 758    let operations = env::var("OPERATIONS")
 759        .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
 760        .unwrap_or(50);
 761
 762    let slash_commands = cx.update(SlashCommandRegistry::default_global);
 763    slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
 764    slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false);
 765    slash_commands.register_command(FakeSlashCommand("cmd-3".into()), false);
 766
 767    let registry = Arc::new(LanguageRegistry::test(cx.background_executor.clone()));
 768    let network = Arc::new(Mutex::new(Network::new(rng.clone())));
 769    let mut text_threads = Vec::new();
 770
 771    let num_peers = rng.random_range(min_peers..=max_peers);
 772    let context_id = TextThreadId::new();
 773    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
 774    for i in 0..num_peers {
 775        let context = cx.new(|cx| {
 776            TextThread::new(
 777                context_id.clone(),
 778                ReplicaId::new(i as u16),
 779                language::Capability::ReadWrite,
 780                registry.clone(),
 781                prompt_builder.clone(),
 782                Arc::new(SlashCommandWorkingSet::default()),
 783                None,
 784                None,
 785                cx,
 786            )
 787        });
 788
 789        cx.update(|cx| {
 790            cx.subscribe(&context, {
 791                let network = network.clone();
 792                move |_, event, _| {
 793                    if let TextThreadEvent::Operation(op) = event {
 794                        network
 795                            .lock()
 796                            .broadcast(ReplicaId::new(i as u16), vec![op.to_proto()]);
 797                    }
 798                }
 799            })
 800            .detach();
 801        });
 802
 803        text_threads.push(context);
 804        network.lock().add_peer(ReplicaId::new(i as u16));
 805    }
 806
 807    let mut mutation_count = operations;
 808
 809    while mutation_count > 0
 810        || !network.lock().is_idle()
 811        || network.lock().contains_disconnected_peers()
 812    {
 813        let context_index = rng.random_range(0..text_threads.len());
 814        let text_thread = &text_threads[context_index];
 815
 816        match rng.random_range(0..100) {
 817            0..=29 if mutation_count > 0 => {
 818                log::info!("Context {}: edit buffer", context_index);
 819                text_thread.update(cx, |text_thread, cx| {
 820                    text_thread
 821                        .buffer()
 822                        .update(cx, |buffer, cx| buffer.randomly_edit(&mut rng, 1, cx));
 823                });
 824                mutation_count -= 1;
 825            }
 826            30..=44 if mutation_count > 0 => {
 827                text_thread.update(cx, |text_thread, cx| {
 828                    let range = text_thread.buffer().read(cx).random_byte_range(0, &mut rng);
 829                    log::info!("Context {}: split message at {:?}", context_index, range);
 830                    text_thread.split_message(range, cx);
 831                });
 832                mutation_count -= 1;
 833            }
 834            45..=59 if mutation_count > 0 => {
 835                text_thread.update(cx, |text_thread, cx| {
 836                    if let Some(message) = text_thread.messages(cx).choose(&mut rng) {
 837                        let role = *[Role::User, Role::Assistant, Role::System]
 838                            .choose(&mut rng)
 839                            .unwrap();
 840                        log::info!(
 841                            "Context {}: insert message after {:?} with {:?}",
 842                            context_index,
 843                            message.id,
 844                            role
 845                        );
 846                        text_thread.insert_message_after(message.id, role, MessageStatus::Done, cx);
 847                    }
 848                });
 849                mutation_count -= 1;
 850            }
 851            60..=74 if mutation_count > 0 => {
 852                text_thread.update(cx, |text_thread, cx| {
 853                    let command_text = "/".to_string()
 854                        + slash_commands
 855                            .command_names()
 856                            .choose(&mut rng)
 857                            .unwrap()
 858                            .clone()
 859                            .as_ref();
 860
 861                    let command_range = text_thread.buffer().update(cx, |buffer, cx| {
 862                        let offset = buffer.random_byte_range(0, &mut rng).start;
 863                        buffer.edit(
 864                            [(offset..offset, format!("\n{}\n", command_text))],
 865                            None,
 866                            cx,
 867                        );
 868                        offset + 1..offset + 1 + command_text.len()
 869                    });
 870
 871                    let output_text = RandomCharIter::new(&mut rng)
 872                        .filter(|c| *c != '\r')
 873                        .take(10)
 874                        .collect::<String>();
 875
 876                    let mut events = vec![Ok(SlashCommandEvent::StartMessage {
 877                        role: Role::User,
 878                        merge_same_roles: true,
 879                    })];
 880
 881                    let num_sections = rng.random_range(0..=3);
 882                    let mut section_start = 0;
 883                    for _ in 0..num_sections {
 884                        let mut section_end = rng.random_range(section_start..=output_text.len());
 885                        while !output_text.is_char_boundary(section_end) {
 886                            section_end += 1;
 887                        }
 888                        events.push(Ok(SlashCommandEvent::StartSection {
 889                            icon: IconName::Ai,
 890                            label: "section".into(),
 891                            metadata: None,
 892                        }));
 893                        events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
 894                            text: output_text[section_start..section_end].to_string(),
 895                            run_commands_in_text: false,
 896                        })));
 897                        events.push(Ok(SlashCommandEvent::EndSection));
 898                        section_start = section_end;
 899                    }
 900
 901                    if section_start < output_text.len() {
 902                        events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
 903                            text: output_text[section_start..].to_string(),
 904                            run_commands_in_text: false,
 905                        })));
 906                    }
 907
 908                    log::info!(
 909                        "Context {}: insert slash command output at {:?} with {:?} events",
 910                        context_index,
 911                        command_range,
 912                        events.len()
 913                    );
 914
 915                    let command_range = text_thread
 916                        .buffer()
 917                        .read(cx)
 918                        .anchor_after(command_range.start)
 919                        ..text_thread
 920                            .buffer()
 921                            .read(cx)
 922                            .anchor_after(command_range.end);
 923                    text_thread.insert_command_output(
 924                        command_range,
 925                        "/command",
 926                        Task::ready(Ok(stream::iter(events).boxed())),
 927                        true,
 928                        cx,
 929                    );
 930                });
 931                cx.run_until_parked();
 932                mutation_count -= 1;
 933            }
 934            75..=84 if mutation_count > 0 => {
 935                text_thread.update(cx, |text_thread, cx| {
 936                    if let Some(message) = text_thread.messages(cx).choose(&mut rng) {
 937                        let new_status = match rng.random_range(0..3) {
 938                            0 => MessageStatus::Done,
 939                            1 => MessageStatus::Pending,
 940                            _ => MessageStatus::Error(SharedString::from("Random error")),
 941                        };
 942                        log::info!(
 943                            "Context {}: update message {:?} status to {:?}",
 944                            context_index,
 945                            message.id,
 946                            new_status
 947                        );
 948                        text_thread.update_metadata(message.id, cx, |metadata| {
 949                            metadata.status = new_status;
 950                        });
 951                    }
 952                });
 953                mutation_count -= 1;
 954            }
 955            _ => {
 956                let replica_id = ReplicaId::new(context_index as u16);
 957                if network.lock().is_disconnected(replica_id) {
 958                    network.lock().reconnect_peer(replica_id, ReplicaId::new(0));
 959
 960                    let (ops_to_send, ops_to_receive) = cx.read(|cx| {
 961                        let host_context = &text_threads[0].read(cx);
 962                        let guest_context = text_thread.read(cx);
 963                        (
 964                            guest_context.serialize_ops(&host_context.version(cx), cx),
 965                            host_context.serialize_ops(&guest_context.version(cx), cx),
 966                        )
 967                    });
 968                    let ops_to_send = ops_to_send.await;
 969                    let ops_to_receive = ops_to_receive
 970                        .await
 971                        .into_iter()
 972                        .map(TextThreadOperation::from_proto)
 973                        .collect::<Result<Vec<_>>>()
 974                        .unwrap();
 975                    log::info!(
 976                        "Context {}: reconnecting. Sent {} operations, received {} operations",
 977                        context_index,
 978                        ops_to_send.len(),
 979                        ops_to_receive.len()
 980                    );
 981
 982                    network.lock().broadcast(replica_id, ops_to_send);
 983                    text_thread.update(cx, |text_thread, cx| {
 984                        text_thread.apply_ops(ops_to_receive, cx)
 985                    });
 986                } else if rng.random_bool(0.1) && replica_id != ReplicaId::new(0) {
 987                    log::info!("Context {}: disconnecting", context_index);
 988                    network.lock().disconnect_peer(replica_id);
 989                } else if network.lock().has_unreceived(replica_id) {
 990                    log::info!("Context {}: applying operations", context_index);
 991                    let ops = network.lock().receive(replica_id);
 992                    let ops = ops
 993                        .into_iter()
 994                        .map(TextThreadOperation::from_proto)
 995                        .collect::<Result<Vec<_>>>()
 996                        .unwrap();
 997                    text_thread.update(cx, |text_thread, cx| text_thread.apply_ops(ops, cx));
 998                }
 999            }
1000        }
1001    }
1002
1003    cx.read(|cx| {
1004        let first_context = text_threads[0].read(cx);
1005        for text_thread in &text_threads[1..] {
1006            let text_thread = text_thread.read(cx);
1007            assert!(text_thread.pending_ops.is_empty(), "pending ops: {:?}", text_thread.pending_ops);
1008            assert_eq!(
1009                text_thread.buffer().read(cx).text(),
1010                first_context.buffer().read(cx).text(),
1011                "Context {:?} text != Context 0 text",
1012                text_thread.buffer().read(cx).replica_id()
1013            );
1014            assert_eq!(
1015                text_thread.message_anchors,
1016                first_context.message_anchors,
1017                "Context {:?} messages != Context 0 messages",
1018                text_thread.buffer().read(cx).replica_id()
1019            );
1020            assert_eq!(
1021                text_thread.messages_metadata,
1022                first_context.messages_metadata,
1023                "Context {:?} message metadata != Context 0 message metadata",
1024                text_thread.buffer().read(cx).replica_id()
1025            );
1026            assert_eq!(
1027                text_thread.slash_command_output_sections,
1028                first_context.slash_command_output_sections,
1029                "Context {:?} slash command output sections != Context 0 slash command output sections",
1030                text_thread.buffer().read(cx).replica_id()
1031            );
1032        }
1033    });
1034}
1035
1036#[gpui::test]
1037fn test_mark_cache_anchors(cx: &mut App) {
1038    init_test(cx);
1039
1040    let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
1041    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
1042    let text_thread = cx.new(|cx| {
1043        TextThread::local(
1044            registry,
1045            None,
1046            None,
1047            prompt_builder.clone(),
1048            Arc::new(SlashCommandWorkingSet::default()),
1049            cx,
1050        )
1051    });
1052    let buffer = text_thread.read(cx).buffer().clone();
1053
1054    // Create a test cache configuration
1055    let cache_configuration = &Some(LanguageModelCacheConfiguration {
1056        max_cache_anchors: 3,
1057        should_speculate: true,
1058        min_total_token: 10,
1059    });
1060
1061    let message_1 = text_thread.read(cx).message_anchors[0].clone();
1062
1063    text_thread.update(cx, |text_thread, cx| {
1064        text_thread.mark_cache_anchors(cache_configuration, false, cx)
1065    });
1066
1067    assert_eq!(
1068        messages_cache(&text_thread, cx)
1069            .iter()
1070            .filter(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor))
1071            .count(),
1072        0,
1073        "Empty messages should not have any cache anchors."
1074    );
1075
1076    buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
1077    let message_2 = text_thread
1078        .update(cx, |text_thread, cx| {
1079            text_thread.insert_message_after(message_1.id, Role::User, MessageStatus::Pending, cx)
1080        })
1081        .unwrap();
1082
1083    buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbbbbbb")], None, cx));
1084    let message_3 = text_thread
1085        .update(cx, |text_thread, cx| {
1086            text_thread.insert_message_after(message_2.id, Role::User, MessageStatus::Pending, cx)
1087        })
1088        .unwrap();
1089    buffer.update(cx, |buffer, cx| buffer.edit([(12..12, "cccccc")], None, cx));
1090
1091    text_thread.update(cx, |text_thread, cx| {
1092        text_thread.mark_cache_anchors(cache_configuration, false, cx)
1093    });
1094    assert_eq!(buffer.read(cx).text(), "aaa\nbbbbbbb\ncccccc");
1095    assert_eq!(
1096        messages_cache(&text_thread, cx)
1097            .iter()
1098            .filter(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor))
1099            .count(),
1100        0,
1101        "Messages should not be marked for cache before going over the token minimum."
1102    );
1103    text_thread.update(cx, |text_thread, _| {
1104        text_thread.token_count = Some(20);
1105    });
1106
1107    text_thread.update(cx, |text_thread, cx| {
1108        text_thread.mark_cache_anchors(cache_configuration, true, cx)
1109    });
1110    assert_eq!(
1111        messages_cache(&text_thread, cx)
1112            .iter()
1113            .map(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor))
1114            .collect::<Vec<bool>>(),
1115        vec![true, true, false],
1116        "Last message should not be an anchor on speculative request."
1117    );
1118
1119    text_thread
1120        .update(cx, |text_thread, cx| {
1121            text_thread.insert_message_after(
1122                message_3.id,
1123                Role::Assistant,
1124                MessageStatus::Pending,
1125                cx,
1126            )
1127        })
1128        .unwrap();
1129
1130    text_thread.update(cx, |text_thread, cx| {
1131        text_thread.mark_cache_anchors(cache_configuration, false, cx)
1132    });
1133    assert_eq!(
1134        messages_cache(&text_thread, cx)
1135            .iter()
1136            .map(|(_, cache)| cache.as_ref().is_some_and(|cache| cache.is_anchor))
1137            .collect::<Vec<bool>>(),
1138        vec![false, true, true, false],
1139        "Most recent message should also be cached if not a speculative request."
1140    );
1141    text_thread.update(cx, |text_thread, cx| {
1142        text_thread.update_cache_status_for_completion(cx)
1143    });
1144    assert_eq!(
1145        messages_cache(&text_thread, cx)
1146            .iter()
1147            .map(|(_, cache)| cache
1148                .as_ref()
1149                .map_or(None, |cache| Some(cache.status.clone())))
1150            .collect::<Vec<Option<CacheStatus>>>(),
1151        vec![
1152            Some(CacheStatus::Cached),
1153            Some(CacheStatus::Cached),
1154            Some(CacheStatus::Cached),
1155            None
1156        ],
1157        "All user messages prior to anchor should be marked as cached."
1158    );
1159
1160    buffer.update(cx, |buffer, cx| buffer.edit([(14..14, "d")], None, cx));
1161    text_thread.update(cx, |text_thread, cx| {
1162        text_thread.mark_cache_anchors(cache_configuration, false, cx)
1163    });
1164    assert_eq!(
1165        messages_cache(&text_thread, cx)
1166            .iter()
1167            .map(|(_, cache)| cache
1168                .as_ref()
1169                .map_or(None, |cache| Some(cache.status.clone())))
1170            .collect::<Vec<Option<CacheStatus>>>(),
1171        vec![
1172            Some(CacheStatus::Cached),
1173            Some(CacheStatus::Cached),
1174            Some(CacheStatus::Pending),
1175            None
1176        ],
1177        "Modifying a message should invalidate it's cache but leave previous messages."
1178    );
1179    buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "e")], None, cx));
1180    text_thread.update(cx, |text_thread, cx| {
1181        text_thread.mark_cache_anchors(cache_configuration, false, cx)
1182    });
1183    assert_eq!(
1184        messages_cache(&text_thread, cx)
1185            .iter()
1186            .map(|(_, cache)| cache
1187                .as_ref()
1188                .map_or(None, |cache| Some(cache.status.clone())))
1189            .collect::<Vec<Option<CacheStatus>>>(),
1190        vec![
1191            Some(CacheStatus::Pending),
1192            Some(CacheStatus::Pending),
1193            Some(CacheStatus::Pending),
1194            None
1195        ],
1196        "Modifying a message should invalidate all future messages."
1197    );
1198}
1199
1200#[gpui::test]
1201async fn test_summarization(cx: &mut TestAppContext) {
1202    let (text_thread, fake_model) = setup_context_editor_with_fake_model(cx);
1203
1204    // Initial state should be pending
1205    text_thread.read_with(cx, |text_thread, _| {
1206        assert!(matches!(text_thread.summary(), TextThreadSummary::Pending));
1207        assert_eq!(
1208            text_thread.summary().or_default(),
1209            TextThreadSummary::DEFAULT
1210        );
1211    });
1212
1213    let message_1 = text_thread.read_with(cx, |text_thread, _cx| {
1214        text_thread.message_anchors[0].clone()
1215    });
1216    text_thread.update(cx, |context, cx| {
1217        context
1218            .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
1219            .unwrap();
1220    });
1221
1222    // Send a message
1223    text_thread.update(cx, |text_thread, cx| {
1224        text_thread.assist(cx);
1225    });
1226
1227    simulate_successful_response(&fake_model, cx);
1228
1229    // Should start generating summary when there are >= 2 messages
1230    text_thread.read_with(cx, |text_thread, _| {
1231        assert!(!text_thread.summary().content().unwrap().done);
1232    });
1233
1234    cx.run_until_parked();
1235    fake_model.send_last_completion_stream_text_chunk("Brief");
1236    fake_model.send_last_completion_stream_text_chunk(" Introduction");
1237    fake_model.end_last_completion_stream();
1238    cx.run_until_parked();
1239
1240    // Summary should be set
1241    text_thread.read_with(cx, |text_thread, _| {
1242        assert_eq!(text_thread.summary().or_default(), "Brief Introduction");
1243    });
1244
1245    // We should be able to manually set a summary
1246    text_thread.update(cx, |text_thread, cx| {
1247        text_thread.set_custom_summary("Brief Intro".into(), cx);
1248    });
1249
1250    text_thread.read_with(cx, |text_thread, _| {
1251        assert_eq!(text_thread.summary().or_default(), "Brief Intro");
1252    });
1253}
1254
1255#[gpui::test]
1256async fn test_thread_summary_error_set_manually(cx: &mut TestAppContext) {
1257    let (text_thread, fake_model) = setup_context_editor_with_fake_model(cx);
1258
1259    test_summarize_error(&fake_model, &text_thread, cx);
1260
1261    // Now we should be able to set a summary
1262    text_thread.update(cx, |text_thread, cx| {
1263        text_thread.set_custom_summary("Brief Intro".into(), cx);
1264    });
1265
1266    text_thread.read_with(cx, |text_thread, _| {
1267        assert_eq!(text_thread.summary().or_default(), "Brief Intro");
1268    });
1269}
1270
1271#[gpui::test]
1272async fn test_thread_summary_error_retry(cx: &mut TestAppContext) {
1273    let (text_thread, fake_model) = setup_context_editor_with_fake_model(cx);
1274
1275    test_summarize_error(&fake_model, &text_thread, cx);
1276
1277    // Sending another message should not trigger another summarize request
1278    text_thread.update(cx, |text_thread, cx| {
1279        text_thread.assist(cx);
1280    });
1281
1282    simulate_successful_response(&fake_model, cx);
1283
1284    text_thread.read_with(cx, |text_thread, _| {
1285        // State is still Error, not Generating
1286        assert!(matches!(text_thread.summary(), TextThreadSummary::Error));
1287    });
1288
1289    // But the summarize request can be invoked manually
1290    text_thread.update(cx, |text_thread, cx| {
1291        text_thread.summarize(true, cx);
1292    });
1293
1294    text_thread.read_with(cx, |text_thread, _| {
1295        assert!(!text_thread.summary().content().unwrap().done);
1296    });
1297
1298    cx.run_until_parked();
1299    fake_model.send_last_completion_stream_text_chunk("A successful summary");
1300    fake_model.end_last_completion_stream();
1301    cx.run_until_parked();
1302
1303    text_thread.read_with(cx, |text_thread, _| {
1304        assert_eq!(text_thread.summary().or_default(), "A successful summary");
1305    });
1306}
1307
1308fn test_summarize_error(
1309    model: &Arc<FakeLanguageModel>,
1310    text_thread: &Entity<TextThread>,
1311    cx: &mut TestAppContext,
1312) {
1313    let message_1 = text_thread.read_with(cx, |text_thread, _cx| {
1314        text_thread.message_anchors[0].clone()
1315    });
1316    text_thread.update(cx, |text_thread, cx| {
1317        text_thread
1318            .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
1319            .unwrap();
1320    });
1321
1322    // Send a message
1323    text_thread.update(cx, |text_thread, cx| {
1324        text_thread.assist(cx);
1325    });
1326
1327    simulate_successful_response(model, cx);
1328
1329    text_thread.read_with(cx, |text_thread, _| {
1330        assert!(!text_thread.summary().content().unwrap().done);
1331    });
1332
1333    // Simulate summary request ending
1334    cx.run_until_parked();
1335    model.end_last_completion_stream();
1336    cx.run_until_parked();
1337
1338    // State is set to Error and default message
1339    text_thread.read_with(cx, |text_thread, _| {
1340        assert_eq!(*text_thread.summary(), TextThreadSummary::Error);
1341        assert_eq!(
1342            text_thread.summary().or_default(),
1343            TextThreadSummary::DEFAULT
1344        );
1345    });
1346}
1347
1348fn setup_context_editor_with_fake_model(
1349    cx: &mut TestAppContext,
1350) -> (Entity<TextThread>, Arc<FakeLanguageModel>) {
1351    let registry = Arc::new(LanguageRegistry::test(cx.executor()));
1352
1353    let fake_provider = Arc::new(FakeLanguageModelProvider::default());
1354    let fake_model = Arc::new(fake_provider.test_model());
1355
1356    cx.update(|cx| {
1357        init_test(cx);
1358        LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
1359            let configured_model = ConfiguredModel {
1360                provider: fake_provider.clone(),
1361                model: fake_model.clone(),
1362            };
1363            registry.set_default_model(Some(configured_model.clone()), cx);
1364            registry.set_thread_summary_model(Some(configured_model), cx);
1365        })
1366    });
1367
1368    let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
1369    let context = cx.new(|cx| {
1370        TextThread::local(
1371            registry,
1372            None,
1373            None,
1374            prompt_builder.clone(),
1375            Arc::new(SlashCommandWorkingSet::default()),
1376            cx,
1377        )
1378    });
1379
1380    (context, fake_model)
1381}
1382
1383fn simulate_successful_response(fake_model: &FakeLanguageModel, cx: &mut TestAppContext) {
1384    cx.run_until_parked();
1385    fake_model.send_last_completion_stream_text_chunk("Assistant response");
1386    fake_model.end_last_completion_stream();
1387    cx.run_until_parked();
1388}
1389
1390fn messages(context: &Entity<TextThread>, cx: &App) -> Vec<(MessageId, Role, Range<usize>)> {
1391    context
1392        .read(cx)
1393        .messages(cx)
1394        .map(|message| (message.id, message.role, message.offset_range))
1395        .collect()
1396}
1397
1398fn messages_cache(
1399    context: &Entity<TextThread>,
1400    cx: &App,
1401) -> Vec<(MessageId, Option<MessageCacheMetadata>)> {
1402    context
1403        .read(cx)
1404        .messages(cx)
1405        .map(|message| (message.id, message.cache))
1406        .collect()
1407}
1408
1409fn init_test(cx: &mut App) {
1410    let settings_store = SettingsStore::test(cx);
1411    prompt_store::init(cx);
1412    LanguageModelRegistry::test(cx);
1413    cx.set_global(settings_store);
1414    language::init(cx);
1415    agent_settings::init(cx);
1416    Project::init_settings(cx);
1417}
1418
1419#[derive(Clone)]
1420struct FakeSlashCommand(String);
1421
1422impl SlashCommand for FakeSlashCommand {
1423    fn name(&self) -> String {
1424        self.0.clone()
1425    }
1426
1427    fn description(&self) -> String {
1428        format!("Fake slash command: {}", self.0)
1429    }
1430
1431    fn menu_text(&self) -> String {
1432        format!("Run fake command: {}", self.0)
1433    }
1434
1435    fn complete_argument(
1436        self: Arc<Self>,
1437        _arguments: &[String],
1438        _cancel: Arc<AtomicBool>,
1439        _workspace: Option<WeakEntity<Workspace>>,
1440        _window: &mut Window,
1441        _cx: &mut App,
1442    ) -> Task<Result<Vec<ArgumentCompletion>>> {
1443        Task::ready(Ok(vec![]))
1444    }
1445
1446    fn requires_argument(&self) -> bool {
1447        false
1448    }
1449
1450    fn run(
1451        self: Arc<Self>,
1452        _arguments: &[String],
1453        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
1454        _context_buffer: BufferSnapshot,
1455        _workspace: WeakEntity<Workspace>,
1456        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
1457        _window: &mut Window,
1458        _cx: &mut App,
1459    ) -> Task<SlashCommandResult> {
1460        Task::ready(Ok(SlashCommandOutput {
1461            text: format!("Executed fake command: {}", self.0),
1462            sections: vec![],
1463            run_commands_in_text: false,
1464        }
1465        .into_event_stream()))
1466    }
1467}