Source code
Revision control
Copy as Markdown
Other Tools
/**
* 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
*/
import { ToolRoleOpts } from "moz-src:///browser/components/aiwindow/ui/modules/ChatMessage.sys.mjs";
import {
MODEL_FEATURES,
openAIEngine,
} from "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs";
import {
toolsConfig,
getOpenTabs,
searchBrowsingHistory,
GetPageContent,
} from "moz-src:///browser/components/aiwindow/models/Tools.sys.mjs";
/**
* Chat
*/
export const Chat = {
toolMap: {
get_open_tabs: getOpenTabs,
search_browsing_history: searchBrowsingHistory,
get_page_content: GetPageContent.getPageContent.bind(GetPageContent),
},
/**
* 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 {ChatConversation} conversation
* @yields {string} Assistant text chunks
*/
async *fetchWithHistory(conversation) {
// Note FXA token fetching disabled for now - this is still in progress
// We can flip this switch on when more realiable
const fxAccountToken = await openAIEngine.getFxAccountToken();
// Update this with correct model id
// Move engineInstance initialization up to access engineInstance.model
const modelId = "qwen3-235b-a22b-instruct-2507-maas";
const toolRoleOpts = new ToolRoleOpts(modelId);
const currentTurn = conversation.currentTurnIndex();
const engineInstance = await openAIEngine.build(MODEL_FEATURES.CHAT);
const config = engineInstance.getConfig(engineInstance.feature);
const inferenceParams = config?.parameters || {};
// Helper to run the model once (streaming) on current convo
const streamModelResponse = () =>
engineInstance.runWithGenerator({
streamOptions: { enabled: true },
fxAccountToken,
tool_choice: "auto",
tools: toolsConfig,
args: conversation.getMessagesInOpenAiFormat(),
...inferenceParams,
});
// 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
//
// Temporarily only include the first tool call due to quality issue
// with subsequent tool call responses, will include all later once above
// ticket is resolved.
const tool_calls = pendingToolCalls.slice(0, 1).map(toolCall => ({
id: toolCall.id,
type: "function",
function: {
name: toolCall.function.name,
arguments: toolCall.function.arguments,
},
}));
conversation.addAssistantMessage("function", { tool_calls });
// 4) Execute each tool locally and create a tool message with the result
// TODO: Temporarily only execute the first tool call, will run all later
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 {
const content = {
tool_call_id: id,
body: { error: "Invalid JSON arguments" },
};
conversation.addToolCallMessage(content, currentTurn, toolRoleOpts);
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 content = { tool_call_id: id, body: result };
conversation.addToolCallMessage(content, currentTurn, toolRoleOpts);
} catch (e) {
result = { error: `Tool execution failed: ${String(e)}` };
const content = { tool_call_id: id, body: result };
conversation.addToolCallMessage(content, currentTurn, toolRoleOpts);
}
break;
}
}
},
};