tor-browser

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

commit ff8afcd8c4b4fea7f1affb9ebbe38cab23caf8cb
parent 959cf1a54bfd8f05692f90a3cd7e1ff6f7722611
Author: Sandor Molnar <smolnar@mozilla.com>
Date:   Thu, 11 Dec 2025 00:24:39 +0200

Revert "Bug 2003303 - Implement Title Generation r=tzhang,ai-models-reviewers,ngrato" for causing bc failures @ TitleGeneration.sys

This reverts commit 53df932c3d25e57cb8bb2eed1118b891c2aa78e3.

Diffstat:
Dbrowser/components/aiwindow/models/TitleGeneration.sys.mjs | 75---------------------------------------------------------------------------
Mbrowser/components/aiwindow/models/moz.build | 1-
Dbrowser/components/aiwindow/models/prompts/TitleGenerationPrompts.sys.mjs | 16----------------
Mbrowser/components/aiwindow/models/prompts/moz.build | 1-
Dbrowser/components/aiwindow/models/tests/xpcshell/test_TitleGeneration.js | 434-------------------------------------------------------------------------------
Mbrowser/components/aiwindow/models/tests/xpcshell/xpcshell.toml | 2--
6 files changed, 0 insertions(+), 529 deletions(-)

diff --git a/browser/components/aiwindow/models/TitleGeneration.sys.mjs b/browser/components/aiwindow/models/TitleGeneration.sys.mjs @@ -1,75 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { - openAIEngine, - renderPrompt, -} from "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"; -import { titleGenerationPrompt } from "moz-src:///browser/components/aiwindow/models/prompts/TitleGenerationPrompts.sys.mjs"; - -/** - * Generats a default title from the first four words of a message. - * - * @param {string} message - The user's message - * @returns {string} The default title - */ -function generateDefaultTitle(message) { - if (!message || typeof message !== "string") { - return "New Chat"; - } - - const words = message - .trim() - .split(/\s+/) - .filter(word => !!word.length); - - if (words.length === 0) { - return "New Chat"; - } - - const titleWords = words.slice(0, 4); - return titleWords.join(" ") + "..."; -} - -/** - * Generate a chat title based on the user's message and current tab information. - * - * @param {string} message - The user's message - * @param {object} current_tab - Object containing current tab information - * @returns {Promise<string>} The generated chat title - */ -export async function generateChatTitle(message, current_tab) { - try { - // Build the OpenAI engines - const engine = await openAIEngine.build(); - - const tabInfo = current_tab || { url: "", title: "", description: "" }; - - // Render the prompt with actual values - const systemPrompt = await renderPrompt(titleGenerationPrompt, { - current_tab: JSON.stringify(tabInfo), - }); - - // Prepare messages for the LLM - const messages = [ - { role: "system", content: systemPrompt }, - { role: "user", content: message }, - ]; - - // Call the LLM - const response = await engine.run({ messages }); - - // Extract the generated title from the response - const title = - response?.choices?.[0]?.message?.content?.trim() || - generateDefaultTitle(message); - - return title; - } catch (error) { - console.error("Failed to generate chat title:", error); - return generateDefaultTitle(message); - } -} diff --git a/browser/components/aiwindow/models/moz.build b/browser/components/aiwindow/models/moz.build @@ -19,7 +19,6 @@ MOZ_SRC_FILES += [ "InsightsSchemas.sys.mjs", "IntentClassifier.sys.mjs", "SearchBrowsingHistory.sys.mjs", - "TitleGeneration.sys.mjs", "Tools.sys.mjs", "Utils.sys.mjs", ] diff --git a/browser/components/aiwindow/models/prompts/TitleGenerationPrompts.sys.mjs b/browser/components/aiwindow/models/prompts/TitleGenerationPrompts.sys.mjs @@ -1,16 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -export const titleGenerationPrompt = `Generate a concise chat title using only the current user message and the current context. - -Rules: -- Fewer than 6 words; reflect the main topic/intent -- Do not end with punctuation -- Do not write questions -- No quotes, brackets, or emojis - -Inputs: -The user is currently viewing this tab page: {current_tab} - -Output: Only the title.`; diff --git a/browser/components/aiwindow/models/prompts/moz.build b/browser/components/aiwindow/models/prompts/moz.build @@ -8,5 +8,4 @@ with Files("**"): MOZ_SRC_FILES += [ "AssistantPrompts.sys.mjs", "insightsPrompts.sys.mjs", - "TitleGenerationPrompts.sys.mjs", ] diff --git a/browser/components/aiwindow/models/tests/xpcshell/test_TitleGeneration.js b/browser/components/aiwindow/models/tests/xpcshell/test_TitleGeneration.js @@ -1,434 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const { generateChatTitle } = ChromeUtils.importESModule( - "moz-src:///browser/components/aiwindow/models/TitleGeneration.sys.mjs" -); - -const { openAIEngine } = ChromeUtils.importESModule( - "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs" -); - -const { sinon } = ChromeUtils.importESModule( - "resource://testing-common/Sinon.sys.mjs" -); - -/** - * Constants for preference keys and test values - */ -const PREF_API_KEY = "browser.aiwindow.apiKey"; -const PREF_ENDPOINT = "browser.aiwindow.endpoint"; -const PREF_MODEL = "browser.aiwindow.model"; - -const API_KEY = "test-api-key"; -const ENDPOINT = "https://api.test-endpoint.com/v1"; -const MODEL = "test-model"; - -/** - * Cleans up preferences after testing - */ -registerCleanupFunction(() => { - for (let pref of [PREF_API_KEY, PREF_ENDPOINT, PREF_MODEL]) { - if (Services.prefs.prefHasUserValue(pref)) { - Services.prefs.clearUserPref(pref); - } - } -}); - -/** - * Test that generateChatTitle successfully generates a title - */ -add_task(async function test_generateChatTitle_success() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - // Mock the engine response - const mockResponse = { - choices: [ - { - message: { - content: "Weather Forecast Query", - }, - }, - ], - }; - - const fakeEngineInstance = { - run: sb.stub().resolves(mockResponse), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const message = "What's the weather like today?"; - const currentTab = { - url: "https://weather.example.com", - title: "Weather Forecast", - description: "Get current weather conditions", - }; - - const title = await generateChatTitle(message, currentTab); - - Assert.equal( - title, - "Weather Forecast Query", - "Should return the generated title from the LLM" - ); - - Assert.ok( - fakeEngineInstance.run.calledOnce, - "Engine run should be called once" - ); - - // Verify the messages structure passed to the engine - const callArgs = fakeEngineInstance.run.firstCall.args[0]; - Assert.ok(callArgs.messages, "Should pass messages to the engine"); - Assert.equal( - callArgs.messages.length, - 2, - "Should have system and user messages" - ); - Assert.equal( - callArgs.messages[0].role, - "system", - "First message should be system" - ); - Assert.equal( - callArgs.messages[1].role, - "user", - "Second message should be user" - ); - Assert.equal( - callArgs.messages[1].content, - message, - "User message should contain the input message" - ); - - // Verify the system prompt contains the tab information - const systemContent = callArgs.messages[0].content; - Assert.ok( - systemContent.includes(currentTab.url), - "System prompt should include tab URL" - ); - Assert.ok( - systemContent.includes(currentTab.title), - "System prompt should include tab title" - ); - Assert.ok( - systemContent.includes(currentTab.description), - "System prompt should include tab description" - ); - } finally { - sb.restore(); - } -}); - -/** - * Test that generateChatTitle handles missing tab information - */ -add_task(async function test_generateChatTitle_no_tab_info() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const mockResponse = { - choices: [ - { - message: { - content: "General Question", - }, - }, - ], - }; - - const fakeEngineInstance = { - run: sb.stub().resolves(mockResponse), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const message = "Tell me about AI"; - const currentTab = null; - - const title = await generateChatTitle(message, currentTab); - - Assert.equal( - title, - "General Question", - "Should return the generated title even without tab info" - ); - - // Verify the system prompt handles null tab - const callArgs = fakeEngineInstance.run.firstCall.args[0]; - Assert.ok(callArgs.messages, "Should pass messages even with null tab"); - } finally { - sb.restore(); - } -}); - -/** - * Test that generateChatTitle handles empty tab fields - */ -add_task(async function test_generateChatTitle_empty_tab_fields() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const mockResponse = { - choices: [ - { - message: { - content: "Untitled Chat", - }, - }, - ], - }; - - const fakeEngineInstance = { - run: sb.stub().resolves(mockResponse), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const message = "Hello"; - const currentTab = { - url: "", - title: "", - description: "", - }; - - const title = await generateChatTitle(message, currentTab); - - Assert.equal(title, "Untitled Chat", "Should handle empty tab fields"); - - // Verify the system prompt includes the empty tab object - const callArgs = fakeEngineInstance.run.firstCall.args[0]; - Assert.ok( - callArgs.messages, - "Should pass messages even with empty tab fields" - ); - } finally { - sb.restore(); - } -}); - -/** - * Test that generateChatTitle handles engine errors gracefully - */ -add_task(async function test_generateChatTitle_engine_error() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const fakeEngineInstance = { - run: sb.stub().rejects(new Error("Engine failed")), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const message = "Test message for error handling"; - const currentTab = { - url: "https://example.com", - title: "Example", - description: "Test", - }; - - const title = await generateChatTitle(message, currentTab); - - Assert.equal( - title, - "Test message for error...", - "Should return first four words when engine fails" - ); - } finally { - sb.restore(); - } -}); - -/** - * Test that generateChatTitle handles malformed engine responses - */ -add_task(async function test_generateChatTitle_malformed_response() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - // Test with missing choices - const mockResponse1 = {}; - let fakeEngineInstance = { - run: sb.stub().resolves(mockResponse1), - }; - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - let title = await generateChatTitle("test message one two", null); - Assert.equal( - title, - "test message one two...", - "Should return first four words for missing choices" - ); - - // Test with empty choices array - sb.restore(); - const sb2 = sinon.createSandbox(); - const mockResponse2 = { choices: [] }; - fakeEngineInstance = { - run: sb2.stub().resolves(mockResponse2), - }; - sb2.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - title = await generateChatTitle("another test message here", null); - Assert.equal( - title, - "another test message here...", - "Should return first four words for empty choices" - ); - - // Test with null content - sb2.restore(); - const sb3 = sinon.createSandbox(); - const mockResponse3 = { - choices: [{ message: { content: null } }], - }; - fakeEngineInstance = { - run: sb3.stub().resolves(mockResponse3), - }; - sb3.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - title = await generateChatTitle("short test here", null); - Assert.equal( - title, - "short test here...", - "Should return first four words for null content" - ); - - sb3.restore(); - } finally { - sb.restore(); - } -}); - -/** - * Test that generateChatTitle trims whitespace from response - */ -add_task(async function test_generateChatTitle_trim_whitespace() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const mockResponse = { - choices: [ - { - message: { - content: " Title With Spaces \n\n", - }, - }, - ], - }; - - const fakeEngineInstance = { - run: sb.stub().resolves(mockResponse), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const title = await generateChatTitle("test", null); - - Assert.equal( - title, - "Title With Spaces", - "Should trim whitespace from generated title" - ); - } finally { - sb.restore(); - } -}); - -/** - * Test default title generation with fewer than four words - */ -add_task(async function test_generateChatTitle_short_message() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const fakeEngineInstance = { - run: sb.stub().rejects(new Error("Engine failed")), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - // Test with three words - let title = await generateChatTitle("Hello there friend", null); - Assert.equal( - title, - "Hello there friend...", - "Should return three words with ellipsis" - ); - - // Test with one word - title = await generateChatTitle("Hello", null); - Assert.equal(title, "Hello...", "Should return one word with ellipsis"); - - // Test with empty message - title = await generateChatTitle("", null); - Assert.equal( - title, - "New Chat", - "Should return 'New Chat' for empty message" - ); - - // Test with whitespace only - title = await generateChatTitle(" ", null); - Assert.equal( - title, - "New Chat", - "Should return 'New Chat' for whitespace-only message" - ); - } finally { - sb.restore(); - } -}); - -/** - * Test default title generation with more than four words - */ -add_task(async function test_generateChatTitle_long_message() { - Services.prefs.setStringPref(PREF_API_KEY, API_KEY); - Services.prefs.setStringPref(PREF_ENDPOINT, ENDPOINT); - Services.prefs.setStringPref(PREF_MODEL, MODEL); - - const sb = sinon.createSandbox(); - try { - const fakeEngineInstance = { - run: sb.stub().rejects(new Error("Engine failed")), - }; - - sb.stub(openAIEngine, "_createEngine").resolves(fakeEngineInstance); - - const message = "This is a very long message with many words"; - const title = await generateChatTitle(message, null); - - Assert.equal( - title, - "This is a very...", - "Should return only first four words with ellipsis" - ); - } finally { - sb.restore(); - } -}); diff --git a/browser/components/aiwindow/models/tests/xpcshell/xpcshell.toml b/browser/components/aiwindow/models/tests/xpcshell/xpcshell.toml @@ -18,8 +18,6 @@ support-files = [] ["test_SearchBrowsingHistory.js"] -["test_TitleGeneration.js"] - ["test_Tools_GetOpenTabs.js"] ["test_Tools_SearchBrowsingHistory.js"]