tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_ConversationSuggestions.js (43772B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 do_get_profile();
      6 
      7 const {
      8  NewTabStarterGenerator,
      9  trimConversation,
     10  addMemoriesToPrompt,
     11  cleanInferenceOutput,
     12  generateConversationStartersSidebar,
     13  generateFollowupPrompts,
     14  MemoriesGetterForSuggestionPrompts,
     15 } = ChromeUtils.importESModule(
     16  "moz-src:///browser/components/aiwindow/models/ConversationSuggestions.sys.mjs"
     17 );
     18 
     19 const { openAIEngine } = ChromeUtils.importESModule(
     20  "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"
     21 );
     22 const { MemoriesManager } = ChromeUtils.importESModule(
     23  "moz-src:///browser/components/aiwindow/models/memories/MemoriesManager.sys.mjs"
     24 );
     25 const { MESSAGE_ROLE } = ChromeUtils.importESModule(
     26  "moz-src:///browser/components/aiwindow/ui/modules/ChatConstants.sys.mjs"
     27 );
     28 const { sinon } = ChromeUtils.importESModule(
     29  "resource://testing-common/Sinon.sys.mjs"
     30 );
     31 
     32 /**
     33 * Constants for preference keys and test values
     34 */
     35 const PREF_API_KEY = "browser.aiwindow.apiKey";
     36 const PREF_ENDPOINT = "browser.aiwindow.endpoint";
     37 const PREF_MODEL = "browser.aiwindow.model";
     38 const PREF_HISTORY_ENABLED = "places.history.enabled";
     39 const PREF_PRIVATE_BROWSING = "browser.privatebrowsing.autostart";
     40 
     41 const API_KEY = "test-api-key";
     42 const ENDPOINT = "https://api.test-endpoint.com/v1";
     43 const MODEL = "test-model";
     44 
     45 async function loadRemoteSettingsSnapshot() {
     46  const file = do_get_file("ai-window-prompts-remote-settings-snapshot.json");
     47  const data = await IOUtils.readUTF8(file.path);
     48  return JSON.parse(data);
     49 }
     50 
     51 let REAL_REMOTE_SETTINGS_SNAPSHOT;
     52 
     53 add_setup(async function () {
     54  REAL_REMOTE_SETTINGS_SNAPSHOT = await loadRemoteSettingsSnapshot();
     55 });
     56 
     57 /**
     58 * Cleans up preferences after testing
     59 */
     60 registerCleanupFunction(() => {
     61  for (let pref of [
     62    PREF_API_KEY,
     63    PREF_ENDPOINT,
     64    PREF_MODEL,
     65    PREF_HISTORY_ENABLED,
     66    PREF_PRIVATE_BROWSING,
     67  ]) {
     68    if (Services.prefs.prefHasUserValue(pref)) {
     69      Services.prefs.clearUserPref(pref);
     70    }
     71  }
     72 });
     73 
     74 /**
     75 * Tests for trimConversation function
     76 */
     77 add_task(async function test_trimConversation() {
     78  const cases = [
     79    {
     80      input: [
     81        // empty case
     82      ],
     83      expected: [],
     84    },
     85    {
     86      input: [
     87        // more than 15 messages
     88        ...Array.from({ length: 20 }, (_, i) => ({
     89          role: i % 2 === 0 ? MESSAGE_ROLE.USER : MESSAGE_ROLE.ASSISTANT,
     90          content: `Message ${i + 1}`,
     91        })),
     92      ],
     93      expected: [
     94        ...Array.from({ length: 15 }, (_, i) => ({
     95          role: (i + 5) % 2 === 0 ? "user" : "assistant",
     96          content: `Message ${i + 6}`,
     97        })),
     98      ],
     99    },
    100    {
    101      input: [
    102        // should remove tool call/responses
    103        { role: MESSAGE_ROLE.USER, content: "What's the weather like?" },
    104        {
    105          role: MESSAGE_ROLE.ASSISTANT,
    106          content: "",
    107          tool_call: { name: "get_weather", arguments: "{}" },
    108        },
    109        {
    110          role: MESSAGE_ROLE.TOOL,
    111          content: "Here are the latest news articles.",
    112        },
    113      ],
    114      expected: [{ role: "user", content: "What's the weather like?" }],
    115    },
    116    {
    117      input: [
    118        // should remove system message
    119        { role: MESSAGE_ROLE.SYSTEM, content: "System message" },
    120        { role: MESSAGE_ROLE.USER, content: "Hello" },
    121        { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
    122      ],
    123      expected: [
    124        { role: "user", content: "Hello" },
    125        { role: "assistant", content: "Hi there!" },
    126      ],
    127    },
    128    {
    129      input: [
    130        // should remove messages with empty content
    131        { role: MESSAGE_ROLE.USER, content: "\n" },
    132        { role: MESSAGE_ROLE.ASSISTANT, content: "   " },
    133      ],
    134      expected: [],
    135    },
    136    {
    137      input: [
    138        // no valid messages
    139        { role: MESSAGE_ROLE.SYSTEM, content: "System message" },
    140        {
    141          role: MESSAGE_ROLE.ASSISTANT,
    142          content: "",
    143          tool_call: { name: "get_info", arguments: "{}" },
    144        },
    145      ],
    146      expected: [],
    147    },
    148    {
    149      input: [
    150        // should slice after filtering invalid messages
    151        ...Array(10)
    152          .fill(0)
    153          .flatMap((_, i) => [
    154            {
    155              role: MESSAGE_ROLE.USER,
    156              content: `User message ${i + 1}`,
    157            },
    158            { role: MESSAGE_ROLE.SYSTEM, content: "System message" },
    159          ]),
    160      ],
    161      expected: [
    162        ...Array.from({ length: 10 }, (_, i) => ({
    163          role: "user",
    164          content: `User message ${i + 1}`,
    165        })),
    166      ],
    167    },
    168  ];
    169 
    170  for (const { input, expected } of cases) {
    171    const result = trimConversation(input);
    172    Assert.deepEqual(
    173      result,
    174      expected,
    175      "trimConversation should return only user/assistant messages with content"
    176    );
    177  }
    178 });
    179 
    180 /**
    181 * Test for addMemoriesToPrompt function when there are memories
    182 */
    183 add_task(async function test_addMemoriesToPrompt_have_memories() {
    184  const sb = sinon.createSandbox();
    185  try {
    186    const basePrompt = "Base prompt content.";
    187    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    188    const memoriesStub = sb
    189      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    190      .resolves(fakeMemories);
    191    const conversationMemoriesPrompt = "Memories block:\n{memories}";
    192    const promptWithMemories = await addMemoriesToPrompt(
    193      basePrompt,
    194      conversationMemoriesPrompt
    195    );
    196 
    197    Assert.ok(
    198      memoriesStub.calledOnce,
    199      "getMemorySummariesForPrompt should be called"
    200    );
    201    Assert.ok(
    202      promptWithMemories.includes("- Memory summary 1") &&
    203        promptWithMemories.includes("- Memory summary 2"),
    204      "Prompt should include memories"
    205    );
    206  } finally {
    207    sb.restore();
    208  }
    209 });
    210 
    211 /**
    212 * Test for addMemoriesToPrompt function when there are no memories
    213 */
    214 add_task(async function test_addMemoriesToPrompt_dont_have_memories() {
    215  const sb = sinon.createSandbox();
    216  try {
    217    const basePrompt = "Base prompt content.";
    218    const fakeMemories = [];
    219    const memoriesStub = sb
    220      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    221      .resolves(fakeMemories);
    222    const conversationMemoriesPrompt = "Memories block:\n{memories}";
    223    const promptWithMemories = await addMemoriesToPrompt(
    224      basePrompt,
    225      conversationMemoriesPrompt
    226    );
    227 
    228    Assert.ok(
    229      memoriesStub.calledOnce,
    230      "getMemorySummariesForPrompt should be called"
    231    );
    232    Assert.equal(
    233      promptWithMemories,
    234      basePrompt,
    235      "Prompt should be unchanged when no memories"
    236    );
    237  } finally {
    238    sb.restore();
    239  }
    240 });
    241 
    242 /**
    243 * Tests for cleanInferenceOutput function
    244 */
    245 add_task(function test_cleanInferenceOutput() {
    246  const sb = sinon.createSandbox();
    247  try {
    248    const cases = [
    249      {
    250        input: {
    251          finalOutput: `- Suggestion 1\n\n* Suggestion 2\n1. Suggestion 3.\n2)Suggestion 4\n[5] Suggestion 5\n6.\nLabel: Suggestion 6\nSuggestion 7.\nSuggestion 8?`,
    252        },
    253        expected: [
    254          "Suggestion 1",
    255          "Suggestion 2",
    256          "Suggestion 3",
    257          "Suggestion 4",
    258          "Suggestion 5",
    259          "Suggestion 6",
    260          "Suggestion 7",
    261          "Suggestion 8?",
    262        ],
    263      },
    264      {
    265        input: { finalOutput: `Suggestion X\nSuggestion Y\nSuggestion Z` },
    266        expected: ["Suggestion X", "Suggestion Y", "Suggestion Z"],
    267      },
    268      {
    269        input: { finalOutput: "" },
    270        expected: [],
    271      },
    272    ];
    273 
    274    for (const { input, expected } of cases) {
    275      const result = cleanInferenceOutput(input);
    276      Assert.deepEqual(
    277        result,
    278        expected,
    279        "cleanInferenceOutput should return expected output"
    280      );
    281    }
    282  } finally {
    283    sb.restore();
    284  }
    285 });
    286 
    287 /**
    288 * Tests for createNewTabPromptGenerator generating prompts based on tab count
    289 */
    290 add_task(
    291  async function test_createNewTabPromptGenerator_with_history_enabled() {
    292    const sb = sinon.createSandbox();
    293    const writingPrompts = [
    294      "Write a first draft",
    295      "Improve writing",
    296      "Proofread a message",
    297    ];
    298 
    299    const planningPrompts = [
    300      "Simplify a topic",
    301      "Brainstorm ideas",
    302      "Help make a plan",
    303    ];
    304    try {
    305      const cases = [
    306        {
    307          input: { tabCount: -1 },
    308          expectedBrowsing: [],
    309        },
    310        {
    311          input: { tabCount: 0 },
    312          expectedBrowsing: ["Find tabs in history"],
    313        },
    314        {
    315          input: { tabCount: 1 },
    316          expectedBrowsing: ["Find tabs in history", "Summarize tabs"],
    317        },
    318        {
    319          input: { tabCount: 2 },
    320          expectedBrowsing: [
    321            "Find tabs in history",
    322            "Summarize tabs",
    323            "Compare tabs",
    324          ],
    325        },
    326        {
    327          input: { tabCount: 3 },
    328          expectedBrowsing: [
    329            "Find tabs in history",
    330            "Summarize tabs",
    331            "Compare tabs",
    332          ],
    333        },
    334      ];
    335      const promptGenerator = NewTabStarterGenerator;
    336      for (const { input, expectedBrowsing } of cases) {
    337        const results = await promptGenerator.getPrompts(input.tabCount);
    338        if (input.tabCount <= -1) {
    339          Assert.equal(results.length, 2, "Should return 2 suggestions");
    340        } else {
    341          Assert.equal(results.length, 3, "Should return 3 suggestions");
    342        }
    343        for (const result of results) {
    344          Assert.equal(
    345            result.type,
    346            "chat",
    347            "Each result should have type 'chat'"
    348          );
    349        }
    350        Assert.ok(
    351          writingPrompts.includes(results[0].text),
    352          "Results should include a valid writing prompt"
    353        );
    354        Assert.ok(
    355          planningPrompts.includes(results[1].text),
    356          "Results should include a valid planning prompt"
    357        );
    358        if (results[2]) {
    359          Assert.ok(
    360            expectedBrowsing.includes(results[2].text),
    361            "Results should include a valid browsing prompt"
    362          );
    363        }
    364      }
    365    } finally {
    366      sb.restore();
    367    }
    368  }
    369 );
    370 
    371 /**
    372 * Tests for createNewTabPromptGenerator generating prompts based on tab count, with history disabled
    373 */
    374 add_task(
    375  async function test_createNewTabPromptGenerator_with_history_disabled() {
    376    const sb = sinon.createSandbox();
    377    const writingPrompts = [
    378      "Write a first draft",
    379      "Improve writing",
    380      "Proofread a message",
    381    ];
    382 
    383    const planningPrompts = [
    384      "Simplify a topic",
    385      "Brainstorm ideas",
    386      "Help make a plan",
    387    ];
    388    try {
    389      const cases = [
    390        {
    391          input: { tabCount: -1 },
    392          expectedBrowsing: [],
    393        },
    394        {
    395          input: { tabCount: 0 },
    396          expectedBrowsing: [],
    397        },
    398        {
    399          input: { tabCount: 1 },
    400          expectedBrowsing: ["Summarize tabs"],
    401        },
    402        {
    403          input: { tabCount: 2 },
    404          expectedBrowsing: ["Summarize tabs", "Compare tabs"],
    405        },
    406        {
    407          input: { tabCount: 3 },
    408          expectedBrowsing: ["Summarize tabs", "Compare tabs"],
    409        },
    410      ];
    411      for (const pref of [
    412        [{ key: PREF_HISTORY_ENABLED, value: false }],
    413        [{ key: PREF_PRIVATE_BROWSING, value: true }],
    414        [
    415          { key: PREF_HISTORY_ENABLED, value: false },
    416          { key: PREF_PRIVATE_BROWSING, value: true },
    417        ],
    418      ]) {
    419        for (const p of pref) {
    420          Services.prefs.setBoolPref(p.key, p.value);
    421        }
    422        const promptGenerator = NewTabStarterGenerator;
    423        for (const { input, expectedBrowsing } of cases) {
    424          const results = await promptGenerator.getPrompts(input.tabCount);
    425          if (input.tabCount <= 0) {
    426            Assert.equal(results.length, 2, "Should return 2 suggestions");
    427          } else {
    428            Assert.equal(results.length, 3, "Should return 3 suggestions");
    429          }
    430          for (const result of results) {
    431            Assert.equal(
    432              result.type,
    433              "chat",
    434              "Each result should have type 'chat'"
    435            );
    436          }
    437          Assert.ok(
    438            writingPrompts.includes(results[0].text),
    439            "Results should include a valid writing prompt"
    440          );
    441          Assert.ok(
    442            planningPrompts.includes(results[1].text),
    443            "Results should include a valid planning prompt"
    444          );
    445          if (results[2]) {
    446            Assert.ok(
    447              expectedBrowsing.includes(results[2].text),
    448              "Results should include a valid browsing prompt"
    449            );
    450          }
    451        }
    452        for (const p of pref) {
    453          Services.prefs.clearUserPref(p.key);
    454        }
    455      }
    456    } finally {
    457      sb.restore();
    458    }
    459  }
    460 );
    461 
    462 /**
    463 * Tests for generateConversationStartersSidebar successfully generating suggestions
    464 */
    465 add_task(async function test_generateConversationStartersSidebar_happy_path() {
    466  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    467  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    468  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    469 
    470  const sb = sinon.createSandbox();
    471  try {
    472    // Mock the openAIEngine and memories response
    473    const fakeEngine = {
    474      run: sb.stub().resolves({
    475        finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    476      }),
    477    };
    478    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    479 
    480    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    481    const memoriesStub = sb
    482      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    483      .resolves(fakeMemories);
    484 
    485    const n = 3;
    486    const contextTabs = [
    487      { title: "Current Tab", url: "https://current.example.com" },
    488      { title: "Tab 2", url: "https://tab2.example.com" },
    489    ];
    490 
    491    const result = await generateConversationStartersSidebar(
    492      contextTabs,
    493      n,
    494      true
    495    );
    496    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    497    Assert.ok(
    498      memoriesStub.calledOnce,
    499      "getMemorySummariesForPrompt should be called once"
    500    );
    501 
    502    // Verify the prompt content
    503    const callArgs = fakeEngine.run.firstCall.args[0];
    504    Assert.equal(
    505      callArgs.messages.length,
    506      2,
    507      "run should be called with 2 messages"
    508    );
    509    Assert.ok(
    510      callArgs.messages[1].content.includes(
    511        '{"title":"Current Tab","url":"https://current.example.com"}'
    512      ),
    513      "Prompt should include current tab info"
    514    );
    515    Assert.ok(
    516      callArgs.messages[1].content.includes(
    517        '{"title":"Tab 2","url":"https://tab2.example.com"}'
    518      ),
    519      "Prompt should include other tab info"
    520    );
    521    Assert.ok(
    522      callArgs.messages[1].content.includes(
    523        "\n- Memory summary 1\n- Memory summary 2"
    524      ),
    525      "Prompt should include memory summaries"
    526    );
    527 
    528    Assert.deepEqual(
    529      result,
    530      [
    531        { text: "Suggestion 1", type: "chat" },
    532        { text: "Suggestion 2", type: "chat" },
    533        { text: "Suggestion 3", type: "chat" },
    534      ],
    535      "Suggestions should match expected values"
    536    );
    537  } finally {
    538    sb.restore();
    539  }
    540 });
    541 
    542 /**
    543 * Tests for generateConversationStartersSidebar without including memories
    544 */
    545 add_task(
    546  async function test_generateConversationStartersSidebar_without_memories() {
    547    Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    548    Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    549    Services.prefs.setStringPref(PREF_MODEL, MODEL);
    550 
    551    const sb = sinon.createSandbox();
    552    try {
    553      // Mock the openAIEngine and memories response
    554      const fakeEngine = {
    555        run: sb.stub().resolves({
    556          finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    557        }),
    558      };
    559      sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    560 
    561      const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    562      const memoriesStub = sb
    563        .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    564        .resolves(fakeMemories);
    565 
    566      const n = 3;
    567      const contextTabs = [
    568        { title: "Current Tab", url: "https://current.example.com" },
    569        { title: "Tab 2", url: "https://tab2.example.com" },
    570      ];
    571 
    572      const result = await generateConversationStartersSidebar(
    573        contextTabs,
    574        n,
    575        false
    576      );
    577      Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    578      Assert.ok(
    579        !memoriesStub.calledOnce,
    580        "getMemorySummariesForPrompt shouldn't be called"
    581      );
    582 
    583      // Verify the prompt content
    584      const callArgs = fakeEngine.run.firstCall.args[0];
    585      Assert.equal(
    586        callArgs.messages.length,
    587        2,
    588        "run should be called with 2 messages"
    589      );
    590      Assert.ok(
    591        callArgs.messages[1].content.includes(
    592          '{"title":"Current Tab","url":"https://current.example.com"}'
    593        ),
    594        "Prompt should include current tab info"
    595      );
    596      Assert.ok(
    597        callArgs.messages[1].content.includes(
    598          '{"title":"Tab 2","url":"https://tab2.example.com"}'
    599        ),
    600        "Prompt should include other tab info"
    601      );
    602      Assert.ok(
    603        !callArgs.messages[1].content.includes(
    604          "\n- Memory summary 1\n- Memory summary 2"
    605        ),
    606        "Prompt should not include memory summaries"
    607      );
    608 
    609      Assert.deepEqual(
    610        result,
    611        [
    612          { text: "Suggestion 1", type: "chat" },
    613          { text: "Suggestion 2", type: "chat" },
    614          { text: "Suggestion 3", type: "chat" },
    615        ],
    616        "Suggestions should match expected values"
    617      );
    618    } finally {
    619      sb.restore();
    620    }
    621  }
    622 );
    623 
    624 /**
    625 * Tests for generateConversationStartersSidebar when no memories are returned
    626 */
    627 add_task(
    628  async function test_generateConversationStartersSidebar_no_memories_returned() {
    629    Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    630    Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    631    Services.prefs.setStringPref(PREF_MODEL, MODEL);
    632 
    633    const sb = sinon.createSandbox();
    634    try {
    635      // Mock the openAIEngine and memories response
    636      const fakeEngine = {
    637        run: sb.stub().resolves({
    638          finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    639        }),
    640      };
    641      sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    642 
    643      const fakeMemories = [];
    644      const memoriesStub = sb
    645        .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    646        .resolves(fakeMemories);
    647 
    648      const n = 3;
    649      const contextTabs = [
    650        { title: "Current Tab", url: "https://current.example.com" },
    651        { title: "Tab 2", url: "https://tab2.example.com" },
    652      ];
    653 
    654      const result = await generateConversationStartersSidebar(
    655        contextTabs,
    656        n,
    657        true
    658      );
    659      Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    660      Assert.ok(
    661        memoriesStub.calledOnce,
    662        "getMemorySummariesForPrompt should be called once"
    663      );
    664 
    665      // Verify the prompt content
    666      const callArgs = fakeEngine.run.firstCall.args[0];
    667      Assert.equal(
    668        callArgs.messages.length,
    669        2,
    670        "run should be called with 2 messages"
    671      );
    672      Assert.ok(
    673        callArgs.messages[1].content.includes(
    674          '{"title":"Current Tab","url":"https://current.example.com"}'
    675        ),
    676        "Prompt should include current tab info"
    677      );
    678      Assert.ok(
    679        callArgs.messages[1].content.includes(
    680          '{"title":"Tab 2","url":"https://tab2.example.com"}'
    681        ),
    682        "Prompt should include other tab info"
    683      );
    684      Assert.ok(
    685        !callArgs.messages[1].content.includes("\nUser Memories:\n"),
    686        "Prompt shouldn't include user memories block"
    687      );
    688 
    689      Assert.deepEqual(
    690        result,
    691        [
    692          { text: "Suggestion 1", type: "chat" },
    693          { text: "Suggestion 2", type: "chat" },
    694          { text: "Suggestion 3", type: "chat" },
    695        ],
    696        "Suggestions should match expected values"
    697      );
    698    } finally {
    699      sb.restore();
    700    }
    701  }
    702 );
    703 
    704 /**
    705 * Tests for generateConversationStartersSidebar when no tabs are provided
    706 */
    707 add_task(async function test_generateConversationStartersSidebar_no_tabs() {
    708  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    709  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    710  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    711 
    712  const sb = sinon.createSandbox();
    713  try {
    714    // Mock the openAIEngine and memories response
    715    const fakeEngine = {
    716      run: sb.stub().resolves({
    717        finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    718      }),
    719    };
    720    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    721 
    722    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    723    const memoriesStub = sb
    724      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    725      .resolves(fakeMemories);
    726 
    727    const n = 3;
    728    const contextTabs = [];
    729 
    730    const result = await generateConversationStartersSidebar(
    731      contextTabs,
    732      n,
    733      true
    734    );
    735    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    736    Assert.ok(
    737      memoriesStub.calledOnce,
    738      "getMemorySummariesForPrompt should be called once"
    739    );
    740 
    741    // Verify the prompt content
    742    const callArgs = fakeEngine.run.firstCall.args[0];
    743    Assert.equal(
    744      callArgs.messages.length,
    745      2,
    746      "run should be called with 2 messages"
    747    );
    748    Assert.ok(
    749      callArgs.messages[1].content.includes("\nNo current tab\n"),
    750      "Prompt should indicate no current tab"
    751    );
    752    Assert.ok(
    753      callArgs.messages[1].content.includes("\nNo tabs available\n"),
    754      "Prompt should indicate no tabs available"
    755    );
    756    Assert.ok(
    757      callArgs.messages[1].content.includes(
    758        "\n- Memory summary 1\n- Memory summary 2"
    759      ),
    760      "Prompt should include memory summaries"
    761    );
    762 
    763    Assert.deepEqual(
    764      result,
    765      [
    766        { text: "Suggestion 1", type: "chat" },
    767        { text: "Suggestion 2", type: "chat" },
    768        { text: "Suggestion 3", type: "chat" },
    769      ],
    770      "Suggestions should match expected values"
    771    );
    772  } finally {
    773    sb.restore();
    774  }
    775 });
    776 
    777 /**
    778 * Tests for generateConversationStartersSidebar with one tab provided
    779 */
    780 add_task(async function test_generateConversationStartersSidebar_one_tab() {
    781  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    782  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    783  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    784 
    785  const sb = sinon.createSandbox();
    786  try {
    787    // Mock the openAIEngine and memories response
    788    const fakeEngine = {
    789      run: sb.stub().resolves({
    790        finalOutput: `1. Suggestion 1\n\n- Suggestion 2\nLabel: Suggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    791      }),
    792    };
    793    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    794 
    795    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    796    const memoriesStub = sb
    797      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    798      .resolves(fakeMemories);
    799 
    800    const n = 3;
    801    const contextTabs = [
    802      { title: "Only Tab", url: "https://only.example.com" },
    803    ];
    804 
    805    const result = await generateConversationStartersSidebar(
    806      contextTabs,
    807      n,
    808      true
    809    );
    810    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    811    Assert.ok(
    812      memoriesStub.calledOnce,
    813      "getMemorySummariesForPrompt should be called once"
    814    );
    815 
    816    // Verify the prompt content
    817    const callArgs = fakeEngine.run.firstCall.args[0];
    818    Assert.equal(
    819      callArgs.messages.length,
    820      2,
    821      "run should be called with 2 messages"
    822    );
    823    Assert.ok(
    824      callArgs.messages[1].content.includes(
    825        '\n{"title":"Only Tab","url":"https://only.example.com"}'
    826      ),
    827      "Prompt should include current tab info"
    828    );
    829    Assert.ok(
    830      callArgs.messages[1].content.includes("\nOnly current tab is open\n"),
    831      "Prompt should indicate only current tab is open"
    832    );
    833    Assert.ok(
    834      callArgs.messages[1].content.includes(
    835        "\n- Memory summary 1\n- Memory summary 2"
    836      ),
    837      "Prompt should include memory summaries"
    838    );
    839 
    840    Assert.deepEqual(
    841      result,
    842      [
    843        { text: "Suggestion 1", type: "chat" },
    844        { text: "Suggestion 2", type: "chat" },
    845        { text: "Suggestion 3", type: "chat" },
    846      ],
    847      "Suggestions should match expected values"
    848    );
    849  } finally {
    850    sb.restore();
    851  }
    852 });
    853 
    854 /**
    855 * Tests that generateConversationStartersSidebar handles engine errors gracefully
    856 */
    857 add_task(
    858  async function test_generateConversationStartersSidebar_engine_error() {
    859    Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    860    Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    861    Services.prefs.setStringPref(PREF_MODEL, MODEL);
    862 
    863    const sb = sinon.createSandbox();
    864    try {
    865      // Mock the openAIEngine and memories response
    866      const fakeEngine = {
    867        run: sb.stub().rejects(new Error("Engine failure")),
    868      };
    869      sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    870 
    871      const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    872      sb.stub(
    873        MemoriesGetterForSuggestionPrompts,
    874        "getMemorySummariesForPrompt"
    875      ).resolves(fakeMemories);
    876 
    877      const n = 3;
    878      const contextTabs = [
    879        { title: "Only Tab", url: "https://only.example.com" },
    880      ];
    881 
    882      const result = await generateConversationStartersSidebar(
    883        contextTabs,
    884        n,
    885        true
    886      );
    887      Assert.deepEqual(result, [], "Should return empty array on engine error");
    888    } finally {
    889      sb.restore();
    890    }
    891  }
    892 );
    893 
    894 /**
    895 * Tests that assistant limitations are included in conversation starter prompts
    896 */
    897 add_task(
    898  async function test_generateConversationStartersSidebar_includes_assistant_limitations() {
    899    Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    900    Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    901    Services.prefs.setStringPref(PREF_MODEL, MODEL);
    902 
    903    const sb = sinon.createSandbox();
    904    try {
    905      const fakeEngine = {
    906        run: sb.stub().resolves({
    907          finalOutput: `Suggestion 1\nSuggestion 2\nSuggestion 3`,
    908        }),
    909      };
    910      sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    911 
    912      sb.stub(openAIEngine, "getRemoteClient").returns({
    913        get: sb.stub().resolves(REAL_REMOTE_SETTINGS_SNAPSHOT),
    914      });
    915 
    916      sb.stub(
    917        MemoriesGetterForSuggestionPrompts,
    918        "getMemorySummariesForPrompt"
    919      ).resolves([]);
    920 
    921      const n = 3;
    922      const contextTabs = [
    923        { title: "Test Tab", url: "https://test.example.com" },
    924      ];
    925 
    926      await generateConversationStartersSidebar(contextTabs, n, false);
    927 
    928      Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    929 
    930      const callArgs = fakeEngine.run.firstCall.args[0];
    931      Assert.ok(
    932        callArgs.messages[1].content.includes(
    933          "You can do this and cannot do that."
    934        ),
    935        "Prompt should include assistant limitations from remote settings"
    936      );
    937    } finally {
    938      sb.restore();
    939    }
    940  }
    941 );
    942 
    943 /**
    944 * Tests for generateFollowupPrompts successfully generating suggestions
    945 */
    946 add_task(async function test_generateFollowupPrompts_happy_path() {
    947  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
    948  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
    949  Services.prefs.setStringPref(PREF_MODEL, MODEL);
    950 
    951  const sb = sinon.createSandbox();
    952  try {
    953    // Mock the openAIEngine and memories response
    954    const fakeEngine = {
    955      run: sb.stub().resolves({
    956        finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
    957      }),
    958    };
    959    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
    960 
    961    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
    962    const memoriesStub = sb
    963      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
    964      .resolves(fakeMemories);
    965 
    966    const n = 2;
    967    const conversationHistory = [
    968      { role: MESSAGE_ROLE.USER, content: "Hello" },
    969      { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
    970    ];
    971    const currentTab = {
    972      title: "Current Tab",
    973      url: "https://current.example.com",
    974    };
    975 
    976    // Using memories
    977    const result = await generateFollowupPrompts(
    978      conversationHistory,
    979      currentTab,
    980      n,
    981      true
    982    );
    983    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
    984    Assert.ok(
    985      memoriesStub.calledOnce,
    986      "getMemorySummariesForPrompt should be called once"
    987    );
    988 
    989    const callArgs = fakeEngine.run.firstCall.args[0];
    990    Assert.equal(
    991      callArgs.messages.length,
    992      2,
    993      "run should be called with 2 messages"
    994    );
    995    Assert.ok(
    996      callArgs.messages[1].content.includes(
    997        '{"title":"Current Tab","url":"https://current.example.com"}'
    998      ),
    999      "Prompt should include current tab info"
   1000    );
   1001    Assert.ok(
   1002      callArgs.messages[1].content.includes(
   1003        '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]'
   1004      ),
   1005      "Prompt should include conversation history"
   1006    );
   1007    Assert.ok(
   1008      callArgs.messages[1].content.includes(
   1009        "\n- Memory summary 1\n- Memory summary 2"
   1010      ),
   1011      "Prompt should include memory summaries"
   1012    );
   1013 
   1014    Assert.deepEqual(
   1015      result,
   1016      [
   1017        { text: "Suggestion 1", type: "chat" },
   1018        { text: "Suggestion 2", type: "chat" },
   1019      ],
   1020      "Suggestions should match"
   1021    );
   1022  } finally {
   1023    sb.restore();
   1024  }
   1025 });
   1026 
   1027 /**
   1028 * Tests for generateFollowupPrompts without including memories
   1029 */
   1030 add_task(async function test_generateFollowupPrompts_no_memories() {
   1031  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
   1032  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
   1033  Services.prefs.setStringPref(PREF_MODEL, MODEL);
   1034 
   1035  const sb = sinon.createSandbox();
   1036  try {
   1037    // Mock the openAIEngine and memories response
   1038    const fakeEngine = {
   1039      run: sb.stub().resolves({
   1040        finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
   1041      }),
   1042    };
   1043    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
   1044 
   1045    const fakeMemories = ["Memory summary 1", "Memory summary 2"];
   1046    const memoriesStub = sb
   1047      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
   1048      .resolves(fakeMemories);
   1049 
   1050    const n = 2;
   1051    const conversationHistory = [
   1052      { role: MESSAGE_ROLE.USER, content: "Hello" },
   1053      { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
   1054    ];
   1055    const currentTab = {
   1056      title: "Current Tab",
   1057      url: "https://current.example.com",
   1058    };
   1059 
   1060    const result = await generateFollowupPrompts(
   1061      conversationHistory,
   1062      currentTab,
   1063      n,
   1064      false
   1065    );
   1066    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
   1067    Assert.ok(
   1068      !memoriesStub.calledOnce,
   1069      "getMemorySummariesForPrompt shouldn't be called"
   1070    );
   1071 
   1072    const callArgs = fakeEngine.run.firstCall.args[0];
   1073    Assert.equal(
   1074      callArgs.messages.length,
   1075      2,
   1076      "run should be called with 2 messages"
   1077    );
   1078    Assert.ok(
   1079      callArgs.messages[1].content.includes(
   1080        '{"title":"Current Tab","url":"https://current.example.com"}'
   1081      ),
   1082      "Prompt should include current tab info"
   1083    );
   1084    Assert.ok(
   1085      callArgs.messages[1].content.includes(
   1086        '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]'
   1087      ),
   1088      "Prompt should include conversation history"
   1089    );
   1090    Assert.ok(
   1091      !callArgs.messages[1].content.includes(
   1092        "\n- Memory summary 1\n- Memory summary 2"
   1093      ),
   1094      "Prompt shouldn't include memory summaries"
   1095    );
   1096 
   1097    Assert.deepEqual(
   1098      result,
   1099      [
   1100        { text: "Suggestion 1", type: "chat" },
   1101        { text: "Suggestion 2", type: "chat" },
   1102      ],
   1103      "Suggestions should match"
   1104    );
   1105  } finally {
   1106    sb.restore();
   1107  }
   1108 });
   1109 
   1110 /**
   1111 * Tests for generateFollowupPrompts when no memories are returned
   1112 */
   1113 add_task(async function test_generateFollowupPrompts_no_memories_returned() {
   1114  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
   1115  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
   1116  Services.prefs.setStringPref(PREF_MODEL, MODEL);
   1117 
   1118  const sb = sinon.createSandbox();
   1119  try {
   1120    // Mock the openAIEngine and memories response
   1121    const fakeEngine = {
   1122      run: sb.stub().resolves({
   1123        finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
   1124      }),
   1125    };
   1126    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
   1127 
   1128    const fakeMemories = [];
   1129    const memoriesStub = sb
   1130      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
   1131      .resolves(fakeMemories);
   1132 
   1133    const n = 2;
   1134    const conversationHistory = [
   1135      { role: MESSAGE_ROLE.USER, content: "Hello" },
   1136      { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
   1137    ];
   1138    const currentTab = {
   1139      title: "Current Tab",
   1140      url: "https://current.example.com",
   1141    };
   1142 
   1143    // Using memories
   1144    const result = await generateFollowupPrompts(
   1145      conversationHistory,
   1146      currentTab,
   1147      n,
   1148      true
   1149    );
   1150    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
   1151    Assert.ok(
   1152      memoriesStub.calledOnce,
   1153      "getMemorySummariesForPrompt should be called once"
   1154    );
   1155 
   1156    const callArgs = fakeEngine.run.firstCall.args[0];
   1157    Assert.equal(
   1158      callArgs.messages.length,
   1159      2,
   1160      "run should be called with 2 messages"
   1161    );
   1162    Assert.ok(
   1163      callArgs.messages[1].content.includes(
   1164        '{"title":"Current Tab","url":"https://current.example.com"}'
   1165      ),
   1166      "Prompt should include current tab info"
   1167    );
   1168    Assert.ok(
   1169      callArgs.messages[1].content.includes(
   1170        '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]'
   1171      ),
   1172      "Prompt should include conversation history"
   1173    );
   1174    Assert.ok(
   1175      !callArgs.messages[1].content.includes("\nUser Memories:\n"),
   1176      "Prompt shouldn't include user memories block"
   1177    );
   1178 
   1179    Assert.deepEqual(
   1180      result,
   1181      [
   1182        { text: "Suggestion 1", type: "chat" },
   1183        { text: "Suggestion 2", type: "chat" },
   1184      ],
   1185      "Suggestions should match"
   1186    );
   1187  } finally {
   1188    sb.restore();
   1189  }
   1190 });
   1191 
   1192 /**
   1193 * Tests for generateFollowupPrompts without a current tab
   1194 */
   1195 add_task(async function test_generateFollowupPrompts_no_current_tab() {
   1196  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
   1197  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
   1198  Services.prefs.setStringPref(PREF_MODEL, MODEL);
   1199 
   1200  const sb = sinon.createSandbox();
   1201  try {
   1202    // Mock the openAIEngine and memories response
   1203    const fakeEngine = {
   1204      run: sb.stub().resolves({
   1205        finalOutput: `1. Suggestion 1\n\n- Suggestion 2.\nSuggestion 3.\nSuggestion 4\nSuggestion 5\nSuggestion 6`,
   1206      }),
   1207    };
   1208    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
   1209 
   1210    const fakeMemories = [];
   1211    const memoriesStub = sb
   1212      .stub(MemoriesGetterForSuggestionPrompts, "getMemorySummariesForPrompt")
   1213      .resolves(fakeMemories);
   1214 
   1215    const n = 2;
   1216    const conversationHistory = [
   1217      { role: MESSAGE_ROLE.USER, content: "Hello" },
   1218      { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
   1219    ];
   1220    const currentTab = {};
   1221 
   1222    const result = await generateFollowupPrompts(
   1223      conversationHistory,
   1224      currentTab,
   1225      n,
   1226      false
   1227    );
   1228    Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
   1229    Assert.ok(
   1230      !memoriesStub.calledOnce,
   1231      "getMemorySummariesForPrompt shouldn't be called"
   1232    );
   1233 
   1234    const callArgs = fakeEngine.run.firstCall.args[0];
   1235    Assert.equal(
   1236      callArgs.messages.length,
   1237      2,
   1238      "run should be called with 2 messages"
   1239    );
   1240    Assert.ok(
   1241      callArgs.messages[1].content.includes("\nNo tab\n"),
   1242      "Prompt shouldn't include any tab info"
   1243    );
   1244    Assert.ok(
   1245      callArgs.messages[1].content.includes(
   1246        '[{"role":"user","content":"Hello"},{"role":"assistant","content":"Hi there!"}]'
   1247      ),
   1248      "Prompt should include conversation history"
   1249    );
   1250    Assert.ok(
   1251      !callArgs.messages[1].content.includes("\nUser Memories:\n"),
   1252      "Prompt shouldn't include user memories block"
   1253    );
   1254 
   1255    Assert.deepEqual(
   1256      result,
   1257      [
   1258        { text: "Suggestion 1", type: "chat" },
   1259        { text: "Suggestion 2", type: "chat" },
   1260      ],
   1261      "Suggestions should match"
   1262    );
   1263  } finally {
   1264    sb.restore();
   1265  }
   1266 });
   1267 
   1268 /**
   1269 * Tests that generateFollowupPrompts handles engine errors gracefully
   1270 */
   1271 add_task(async function test_generateFollowupPrompts_engine_error() {
   1272  Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
   1273  Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
   1274  Services.prefs.setStringPref(PREF_MODEL, MODEL);
   1275 
   1276  const sb = sinon.createSandbox();
   1277  try {
   1278    // Mock the openAIEngine and memories response
   1279    const fakeEngine = {
   1280      run: sb.stub().rejects(new Error("Engine failure")),
   1281    };
   1282    sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
   1283 
   1284    const fakeMemories = [];
   1285    sb.stub(
   1286      MemoriesGetterForSuggestionPrompts,
   1287      "getMemorySummariesForPrompt"
   1288    ).resolves(fakeMemories);
   1289 
   1290    const n = 2;
   1291    const conversationHistory = [
   1292      { role: MESSAGE_ROLE.USER, content: "Hello" },
   1293      { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
   1294    ];
   1295    const currentTab = {};
   1296 
   1297    const result = await generateFollowupPrompts(
   1298      conversationHistory,
   1299      currentTab,
   1300      n,
   1301      false
   1302    );
   1303    Assert.deepEqual(result, [], "Should return empty array on engine error");
   1304  } finally {
   1305    sb.restore();
   1306  }
   1307 });
   1308 
   1309 /**
   1310 * Tests that assistant limitations are included in followup prompts
   1311 */
   1312 add_task(
   1313  async function test_generateFollowupPrompts_includes_assistant_limitations() {
   1314    Services.prefs.setStringPref(PREF_API_KEY, API_KEY);
   1315    Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT);
   1316    Services.prefs.setStringPref(PREF_MODEL, MODEL);
   1317 
   1318    const sb = sinon.createSandbox();
   1319    try {
   1320      const fakeEngine = {
   1321        run: sb.stub().resolves({
   1322          finalOutput: `Suggestion 1\nSuggestion 2`,
   1323        }),
   1324      };
   1325      sb.stub(openAIEngine, "_createEngine").resolves(fakeEngine);
   1326 
   1327      sb.stub(openAIEngine, "getRemoteClient").returns({
   1328        get: sb.stub().resolves(REAL_REMOTE_SETTINGS_SNAPSHOT),
   1329      });
   1330 
   1331      sb.stub(
   1332        MemoriesGetterForSuggestionPrompts,
   1333        "getMemorySummariesForPrompt"
   1334      ).resolves([]);
   1335 
   1336      const n = 2;
   1337      const conversationHistory = [
   1338        { role: MESSAGE_ROLE.USER, content: "Hello" },
   1339        { role: MESSAGE_ROLE.ASSISTANT, content: "Hi there!" },
   1340      ];
   1341      const currentTab = { title: "Test", url: "https://test.example.com" };
   1342 
   1343      await generateFollowupPrompts(conversationHistory, currentTab, n, false);
   1344 
   1345      Assert.ok(fakeEngine.run.calledOnce, "Engine run should be called once");
   1346 
   1347      const callArgs = fakeEngine.run.firstCall.args[0];
   1348      Assert.ok(
   1349        callArgs.messages[1].content.includes(
   1350          "You can do this and cannot do that."
   1351        ),
   1352        "Prompt should include assistant limitations from remote settings"
   1353      );
   1354    } finally {
   1355      sb.restore();
   1356    }
   1357  }
   1358 );
   1359 
   1360 /**
   1361 * Tests for getMemorySummariesForPrompt happy path
   1362 */
   1363 add_task(async function test_getMemorySummariesForPrompt_happy_path() {
   1364  const sb = sinon.createSandbox();
   1365  try {
   1366    // Mock the MemoryStore to return fixed memories
   1367    const fakeMemories = [
   1368      {
   1369        memory_summary: "Memory summary 1",
   1370      },
   1371      {
   1372        memory_summary: "Memory summary 2",
   1373      },
   1374      {
   1375        memory_summary: "Memory summary 3",
   1376      },
   1377    ];
   1378 
   1379    sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories);
   1380 
   1381    const maxMemories = 2;
   1382    const summaries =
   1383      await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt(
   1384        maxMemories
   1385      );
   1386 
   1387    Assert.deepEqual(
   1388      summaries,
   1389      ["Memory summary 1", "Memory summary 2"],
   1390      "Memory summaries should match expected values"
   1391    );
   1392  } finally {
   1393    sb.restore();
   1394  }
   1395 });
   1396 
   1397 /**
   1398 * Tests for getMemorySummariesForPrompt when no memories are returned
   1399 */
   1400 add_task(async function test_getMemorySummariesForPrompt_no_memories() {
   1401  const sb = sinon.createSandbox();
   1402  try {
   1403    // Mock the MemoryStore to return fixed memories
   1404    const fakeMemories = [];
   1405 
   1406    sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories);
   1407 
   1408    const maxMemories = 2;
   1409    const summaries =
   1410      await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt(
   1411        maxMemories
   1412      );
   1413 
   1414    Assert.equal(
   1415      summaries.length,
   1416      0,
   1417      `getMemorySummariesForPrompt(${maxMemories}) should return 0 summaries`
   1418    );
   1419  } finally {
   1420    sb.restore();
   1421  }
   1422 });
   1423 
   1424 /**
   1425 * Tests for getMemorySummariesForPrompt with fewer memories than maxMemories
   1426 */
   1427 add_task(async function test_getMemorySummariesForPrompt_too_few_memories() {
   1428  const sb = sinon.createSandbox();
   1429  try {
   1430    // Mock the MemoryStore to return fixed memories
   1431    const fakeMemories = [
   1432      {
   1433        memory_summary: "Memory summary 1",
   1434      },
   1435    ];
   1436 
   1437    sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories);
   1438 
   1439    const maxMemories = 2;
   1440    const summaries =
   1441      await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt(
   1442        maxMemories
   1443      );
   1444 
   1445    Assert.deepEqual(
   1446      summaries,
   1447      ["Memory summary 1"],
   1448      "Memory summaries should match expected values"
   1449    );
   1450  } finally {
   1451    sb.restore();
   1452  }
   1453 });
   1454 
   1455 /**
   1456 * Tests for getMemorySummariesForPrompt handling duplicate summaries
   1457 */
   1458 add_task(async function test_getMemorySummariesForPrompt_duplicates() {
   1459  const sb = sinon.createSandbox();
   1460  try {
   1461    // Mock the MemoryStore to return fixed memories
   1462    const fakeMemories = [
   1463      {
   1464        memory_summary: "Duplicate summary",
   1465      },
   1466      {
   1467        memory_summary: "duplicate summary",
   1468      },
   1469      {
   1470        memory_summary: "Unique summary",
   1471      },
   1472    ];
   1473 
   1474    sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories);
   1475 
   1476    const maxMemories = 2;
   1477    const summaries =
   1478      await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt(
   1479        maxMemories
   1480      );
   1481 
   1482    Assert.deepEqual(
   1483      summaries,
   1484      ["Duplicate summary", "Unique summary"],
   1485      "Memory summaries should match expected values"
   1486    );
   1487  } finally {
   1488    sb.restore();
   1489  }
   1490 });
   1491 
   1492 /**
   1493 * Tests for getMemorySummariesForPrompt handling empty and whitespace-only summaries
   1494 */
   1495 add_task(
   1496  async function test_getMemorySummariesForPrompt_empty_and_whitespace() {
   1497    const sb = sinon.createSandbox();
   1498    try {
   1499      // Mock the MemoryStore to return fixed memories
   1500      const fakeMemories = [
   1501        {
   1502          memory_summary: "   \n",
   1503        },
   1504        {
   1505          memory_summary: "",
   1506        },
   1507        {
   1508          memory_summary: "Valid summary",
   1509        },
   1510      ];
   1511 
   1512      sb.stub(MemoriesManager, "getAllMemories").resolves(fakeMemories);
   1513 
   1514      const maxMemories = 2;
   1515      const summaries =
   1516        await MemoriesGetterForSuggestionPrompts.getMemorySummariesForPrompt(
   1517          maxMemories
   1518        );
   1519 
   1520      Assert.deepEqual(
   1521        summaries,
   1522        ["Valid summary"],
   1523        "Memory summaries should match expected values"
   1524      );
   1525    } finally {
   1526      sb.restore();
   1527    }
   1528  }
   1529 );