commit d7ac6710fca6548998d7b548ca6e7f4406cd90a7
parent 1d997225396ca03651cec8b7b4537db201ba2b25
Author: iulian moraru <imoraru@mozilla.com>
Date: Wed, 10 Dec 2025 23:39:11 +0200
Revert "Bug 2003598 - Add Chat service with fetch with history r=tzhang,ai-models-reviewers" for causing bc failures on browser_all_files_referenced.js.
This reverts commit 2f7bfd630e4eb2ce47db838f6c9fc2b166db4231.
Diffstat:
4 files changed, 0 insertions(+), 449 deletions(-)
diff --git a/browser/components/aiwindow/models/Chat.sys.mjs b/browser/components/aiwindow/models/Chat.sys.mjs
@@ -1,154 +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/.
- */
-
-/* eslint-disable-next-line mozilla/reject-import-system-module-from-non-system */
-import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";
-import { openAIEngine } from "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs";
-import {
- OAUTH_CLIENT_ID,
- SCOPE_PROFILE,
-} from "resource://gre/modules/FxAccountsCommon.sys.mjs";
-
-/**
- * Chat
- */
-export const Chat = {
- toolMap: {}, // TODO can import toolMap
-
- async _getFxAccountToken() {
- try {
- const fxAccounts = getFxAccountsSingleton();
- const token = await fxAccounts.getOAuthToken({
- // Scope needs to be updated in accordance with https://bugzilla.mozilla.org/show_bug.cgi?id=2005290
- scope: SCOPE_PROFILE,
- client_id: OAUTH_CLIENT_ID,
- });
- return token;
- } catch (error) {
- console.warn("Error obtaining FxA token:", error);
- return null;
- }
- },
-
- /**
- * Stream assistant output with tool-call support.
- * Yields assistant text chunks as they arrive. If the model issues tool calls,
- * we execute them locally, append results to the conversation, and continue
- * streaming the model’s follow-up answer. Repeats until no more tool calls.
- *
- * @param {Array<{role:string, content?:string, tool_call_id?:string, tool_calls?:any}>} messages
- * @yields {string} Assistant text chunks
- */
- async *fetchWithHistory(messages) {
- const engineInstance = await openAIEngine.build();
- // Note FXA token fetching disabled for now - this is still in progress
- // We can flip this switch on when more realiable
- // const fxAccountToken = await this._getFxAccountToken();
-
- // We'll mutate a local copy of the thread as we loop
- let convo = Array.isArray(messages) ? [...messages] : [];
-
- // Helper to run the model once (streaming) on current convo
- const streamModelResponse = () =>
- engineInstance.runWithGenerator({
- streamOptions: { enabled: true },
- // fxAccountToken,
- tool_choice: "auto",
- // tools: Add your tools configuration here,
- args: convo,
- });
-
- // Keep calling until the model finishes without requesting tools
- while (true) {
- let pendingToolCalls = null;
-
- // 1) First pass: stream tokens; capture any toolCalls
- for await (const chunk of streamModelResponse()) {
- // Stream assistant text to the UI
- if (chunk?.text) {
- yield chunk.text;
- }
-
- // Capture tool calls (do not echo raw tool plumbing to the user)
- if (chunk?.toolCalls?.length) {
- pendingToolCalls = chunk.toolCalls;
- }
- }
-
- // 2) Watch for tool calls; if none, we are done
- if (!pendingToolCalls || pendingToolCalls.length === 0) {
- return;
- }
-
- // 3) Build the assistant tool_calls message exactly as expected by the API
- const assistantToolMsg = {
- role: "assistant",
- tool_calls: pendingToolCalls.map(toolCall => ({
- id: toolCall.id,
- type: "function",
- function: {
- name: toolCall.function.name,
- arguments: toolCall.function.arguments,
- },
- })),
- };
-
- // 4) Execute each tool locally and create a tool message with the result
- const toolResultMessages = [];
- for (const toolCall of pendingToolCalls) {
- const { id, function: functionSpec } = toolCall;
- const name = functionSpec?.name || "";
- let toolParams = {};
-
- try {
- toolParams = functionSpec?.arguments
- ? JSON.parse(functionSpec.arguments)
- : {};
- } catch {
- toolResultMessages.push({
- role: "tool",
- tool_call_id: id,
- content: JSON.stringify({ error: "Invalid JSON arguments" }),
- });
- continue;
- }
-
- let result;
- try {
- // Call the appropriate tool by name
- const toolFunc = this.toolMap[name];
- if (typeof toolFunc !== "function") {
- throw new Error(`No such tool: ${name}`);
- }
-
- result = await toolFunc(toolParams);
-
- // Create special tool call log message to show in the UI log panel
- const assistantToolCallLogMsg = {
- role: "assistant",
- content: `Tool Call: ${name} with parameters: ${JSON.stringify(
- toolParams
- )}`,
- type: "tool_call_log",
- result,
- };
- convo.push(assistantToolCallLogMsg);
- yield assistantToolCallLogMsg;
- } catch (e) {
- result = { error: `Tool execution failed: ${String(e)}` };
- }
-
- toolResultMessages.push({
- role: "tool",
- tool_call_id: id,
- content: typeof result === "string" ? result : JSON.stringify(result),
- });
- }
-
- convo = [...convo, assistantToolMsg, ...toolResultMessages];
- }
- },
-};
diff --git a/browser/components/aiwindow/models/moz.build b/browser/components/aiwindow/models/moz.build
@@ -10,7 +10,6 @@ DIRS += [
]
MOZ_SRC_FILES += [
- "Chat.sys.mjs",
"ChatUtils.sys.mjs",
"Insights.sys.mjs",
"InsightsConstants.sys.mjs",
diff --git a/browser/components/aiwindow/models/tests/xpcshell/test_Chat.js b/browser/components/aiwindow/models/tests/xpcshell/test_Chat.js
@@ -1,292 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const { Chat } = ChromeUtils.importESModule(
- "moz-src:///browser/components/aiwindow/models/Chat.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"
-);
-
-// Prefs for aiwindow
-const PREF_API_KEY = "browser.aiwindow.apiKey";
-const PREF_ENDPOINT = "browser.aiwindow.endpoint";
-const PREF_MODEL = "browser.aiwindow.model";
-
-// Clean prefs after all tests
-registerCleanupFunction(() => {
- for (let pref of [PREF_API_KEY, PREF_ENDPOINT, PREF_MODEL]) {
- if (Services.prefs.prefHasUserValue(pref)) {
- Services.prefs.clearUserPref(pref);
- }
- }
-});
-
-add_task(async function test_openAIEngine_build_uses_prefs() {
- Services.prefs.setStringPref(PREF_API_KEY, "test-key-123");
- Services.prefs.setStringPref(PREF_ENDPOINT, "https://example.test/v1");
- Services.prefs.setStringPref(PREF_MODEL, "gpt-fake");
-
- const sb = sinon.createSandbox();
- try {
- const fakeEngineInstance = {
- runWithGenerator() {
- throw new Error("not used");
- },
- };
- const stub = sb
- .stub(openAIEngine, "_createEngine")
- .resolves(fakeEngineInstance);
-
- const engine = await openAIEngine.build();
-
- Assert.ok(
- engine instanceof openAIEngine,
- "Should return openAIEngine instance"
- );
- Assert.strictEqual(
- engine.engineInstance,
- fakeEngineInstance,
- "Should store engine instance"
- );
- Assert.ok(stub.calledOnce, "_createEngine should be called once");
-
- const opts = stub.firstCall.args[0];
- Assert.equal(opts.apiKey, "test-key-123", "apiKey should come from pref");
- Assert.equal(
- opts.baseURL,
- "https://example.test/v1",
- "baseURL should come from pref"
- );
- Assert.equal(opts.modelId, "gpt-fake", "modelId should come from pref");
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_Chat_fetchWithHistory_streams_and_forwards_args() {
- const sb = sinon.createSandbox();
- try {
- let capturedArgs = null;
- let capturedOptions = null;
-
- // Fake openAIEngine instance that directly has runWithGenerator method
- const fakeEngine = {
- runWithGenerator(options) {
- capturedArgs = options.args;
- capturedOptions = options;
- async function* gen() {
- yield { text: "Hello" };
- yield { text: " from" };
- yield { text: " fake engine!" };
- yield {}; // ignored by Chat
- // No toolCalls yielded, so loop will exit after first iteration
- }
- return gen();
- },
- };
-
- sb.stub(openAIEngine, "build").resolves(fakeEngine);
- // sb.stub(Chat, "_getFxAccountToken").resolves("mock_token");
-
- const messages = [
- { role: "system", content: "You are helpful" },
- { role: "user", content: "Hi there" },
- ];
-
- // Collect streamed output
- let acc = "";
- for await (const chunk of Chat.fetchWithHistory(messages)) {
- if (typeof chunk === "string") {
- acc += chunk;
- }
- }
-
- Assert.equal(
- acc,
- "Hello from fake engine!",
- "Should concatenate streamed chunks"
- );
- Assert.deepEqual(
- capturedArgs,
- messages,
- "Should forward messages as args to runWithGenerator()"
- );
- Assert.deepEqual(
- capturedOptions.streamOptions.enabled,
- true,
- "Should enable streaming in runWithGenerator()"
- );
- } finally {
- sb.restore();
- }
-});
-
-add_task(async function test_Chat_fetchWithHistory_handles_tool_calls() {
- const sb = sinon.createSandbox();
- try {
- let callCount = 0;
- const fakeEngine = {
- runWithGenerator(_options) {
- callCount++;
- async function* gen() {
- if (callCount === 1) {
- // First call: yield text and tool call
- yield { text: "I'll help you with that. " };
- yield {
- toolCalls: [
- {
- id: "call_123",
- function: {
- name: "test_tool",
- arguments: JSON.stringify({ param: "value" }),
- },
- },
- ],
- };
- } else {
- // Second call: after tool execution
- yield { text: "Tool executed successfully!" };
- }
- }
- return gen();
- },
- };
-
- // Mock tool function
- Chat.toolMap.test_tool = sb.stub().resolves("tool result");
-
- sb.stub(openAIEngine, "build").resolves(fakeEngine);
- // sb.stub(Chat, "_getFxAccountToken").resolves("mock_token");
-
- const messages = [{ role: "user", content: "Use the test tool" }];
-
- let textOutput = "";
- let toolCallLogs = [];
- for await (const chunk of Chat.fetchWithHistory(messages)) {
- if (typeof chunk === "string") {
- textOutput += chunk;
- } else if (chunk?.type === "tool_call_log") {
- toolCallLogs.push(chunk);
- }
- }
-
- Assert.equal(
- textOutput,
- "I'll help you with that. Tool executed successfully!",
- "Should yield text from both model calls"
- );
- Assert.equal(toolCallLogs.length, 1, "Should have one tool call log");
- Assert.ok(
- toolCallLogs[0].content.includes("test_tool"),
- "Tool call log should mention tool name"
- );
- Assert.ok(Chat.toolMap.test_tool.calledOnce, "Tool should be called once");
- Assert.deepEqual(
- Chat.toolMap.test_tool.firstCall.args[0],
- { param: "value" },
- "Tool should receive correct parameters"
- );
- Assert.equal(
- callCount,
- 2,
- "Engine should be called twice (initial + after tool)"
- );
- } finally {
- sb.restore();
- delete Chat.toolMap.test_tool;
- }
-});
-
-add_task(
- async function test_Chat_fetchWithHistory_propagates_engine_build_error() {
- const sb = sinon.createSandbox();
- try {
- const err = new Error("engine build failed");
- sb.stub(openAIEngine, "build").rejects(err);
- // sb.stub(Chat, "_getFxAccountToken").resolves("mock_token");
-
- const messages = [{ role: "user", content: "Hi" }];
-
- const consume = async () => {
- for await (const _chunk of Chat.fetchWithHistory(messages)) {
- void _chunk;
- }
- };
-
- await Assert.rejects(
- consume(),
- e => e === err,
- "Should propagate the same error thrown by openAIEngine.build"
- );
- } finally {
- sb.restore();
- }
- }
-);
-
-add_task(
- async function test_Chat_fetchWithHistory_handles_invalid_tool_arguments() {
- const sb = sinon.createSandbox();
- try {
- let callCount = 0;
- const fakeEngine = {
- runWithGenerator(_options) {
- callCount++;
- async function* gen() {
- if (callCount === 1) {
- // First call: yield text and invalid tool call
- yield { text: "Using tool with bad args: " };
- yield {
- toolCalls: [
- {
- id: "call_456",
- function: {
- name: "test_tool",
- arguments: "invalid json {",
- },
- },
- ],
- };
- } else {
- // Second call: no more tool calls, should exit loop
- yield { text: "Done." };
- }
- }
- return gen();
- },
- };
-
- Chat.toolMap.test_tool = sb.stub().resolves("should not be called");
-
- sb.stub(openAIEngine, "build").resolves(fakeEngine);
- // sb.stub(Chat, "_getFxAccountToken").resolves("mock_token");
-
- const messages = [{ role: "user", content: "Test bad JSON" }];
-
- let textOutput = "";
- for await (const chunk of Chat.fetchWithHistory(messages)) {
- if (typeof chunk === "string") {
- textOutput += chunk;
- }
- }
-
- Assert.equal(
- textOutput,
- "Using tool with bad args: Done.",
- "Should yield text from both calls"
- );
- Assert.ok(
- Chat.toolMap.test_tool.notCalled,
- "Tool should not be called with invalid JSON"
- );
- } finally {
- sb.restore();
- delete Chat.toolMap.test_tool;
- }
- }
-);
diff --git a/browser/components/aiwindow/models/tests/xpcshell/xpcshell.toml b/browser/components/aiwindow/models/tests/xpcshell/xpcshell.toml
@@ -6,8 +6,6 @@ head = "head.js"
firefox-appdir = "browser"
support-files = []
-["test_Chat.js"]
-
["test_ChatUtils.js"]
["test_Insights.js"]