Chat.sys.mjs (5238B)
1 /** 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 */ 6 7 import { ToolRoleOpts } from "moz-src:///browser/components/aiwindow/ui/modules/ChatMessage.sys.mjs"; 8 import { 9 MODEL_FEATURES, 10 openAIEngine, 11 } from "moz-src:///browser/components/aiwindow/models/Utils.sys.mjs"; 12 import { 13 toolsConfig, 14 getOpenTabs, 15 searchBrowsingHistory, 16 GetPageContent, 17 } from "moz-src:///browser/components/aiwindow/models/Tools.sys.mjs"; 18 19 /** 20 * Chat 21 */ 22 export const Chat = { 23 toolMap: { 24 get_open_tabs: getOpenTabs, 25 search_browsing_history: searchBrowsingHistory, 26 get_page_content: GetPageContent.getPageContent.bind(GetPageContent), 27 }, 28 29 /** 30 * Stream assistant output with tool-call support. 31 * Yields assistant text chunks as they arrive. If the model issues tool calls, 32 * we execute them locally, append results to the conversation, and continue 33 * streaming the model’s follow-up answer. Repeats until no more tool calls. 34 * 35 * @param {ChatConversation} conversation 36 * @yields {string} Assistant text chunks 37 */ 38 async *fetchWithHistory(conversation) { 39 // Note FXA token fetching disabled for now - this is still in progress 40 // We can flip this switch on when more realiable 41 const fxAccountToken = await openAIEngine.getFxAccountToken(); 42 43 // @todo Bug 2007046 44 // Update this with correct model id 45 // Move engineInstance initialization up to access engineInstance.model 46 const modelId = "qwen3-235b-a22b-instruct-2507-maas"; 47 48 const toolRoleOpts = new ToolRoleOpts(modelId); 49 const currentTurn = conversation.currentTurnIndex(); 50 const engineInstance = await openAIEngine.build(MODEL_FEATURES.CHAT); 51 const config = engineInstance.getConfig(engineInstance.feature); 52 const inferenceParams = config?.parameters || {}; 53 54 // Helper to run the model once (streaming) on current convo 55 const streamModelResponse = () => 56 engineInstance.runWithGenerator({ 57 streamOptions: { enabled: true }, 58 fxAccountToken, 59 tool_choice: "auto", 60 tools: toolsConfig, 61 args: conversation.getMessagesInOpenAiFormat(), 62 ...inferenceParams, 63 }); 64 65 // Keep calling until the model finishes without requesting tools 66 while (true) { 67 let pendingToolCalls = null; 68 69 // 1) First pass: stream tokens; capture any toolCalls 70 for await (const chunk of streamModelResponse()) { 71 // Stream assistant text to the UI 72 if (chunk?.text) { 73 yield chunk.text; 74 } 75 76 // Capture tool calls (do not echo raw tool plumbing to the user) 77 if (chunk?.toolCalls?.length) { 78 pendingToolCalls = chunk.toolCalls; 79 } 80 } 81 82 // 2) Watch for tool calls; if none, we are done 83 if (!pendingToolCalls || pendingToolCalls.length === 0) { 84 return; 85 } 86 87 // 3) Build the assistant tool_calls message exactly as expected by the API 88 // 89 // @todo Bug 2006159 - Implement parallel tool calling 90 // Temporarily only include the first tool call due to quality issue 91 // with subsequent tool call responses, will include all later once above 92 // ticket is resolved. 93 const tool_calls = pendingToolCalls.slice(0, 1).map(toolCall => ({ 94 id: toolCall.id, 95 type: "function", 96 function: { 97 name: toolCall.function.name, 98 arguments: toolCall.function.arguments, 99 }, 100 })); 101 conversation.addAssistantMessage("function", { tool_calls }); 102 103 // 4) Execute each tool locally and create a tool message with the result 104 // TODO: Temporarily only execute the first tool call, will run all later 105 for (const toolCall of pendingToolCalls) { 106 const { id, function: functionSpec } = toolCall; 107 const name = functionSpec?.name || ""; 108 let toolParams = {}; 109 110 try { 111 toolParams = functionSpec?.arguments 112 ? JSON.parse(functionSpec.arguments) 113 : {}; 114 } catch { 115 const content = { 116 tool_call_id: id, 117 body: { error: "Invalid JSON arguments" }, 118 }; 119 conversation.addToolCallMessage(content, currentTurn, toolRoleOpts); 120 continue; 121 } 122 123 let result; 124 try { 125 // Call the appropriate tool by name 126 const toolFunc = this.toolMap[name]; 127 if (typeof toolFunc !== "function") { 128 throw new Error(`No such tool: ${name}`); 129 } 130 131 result = await toolFunc(toolParams); 132 133 // Create special tool call log message to show in the UI log panel 134 const content = { tool_call_id: id, body: result }; 135 conversation.addToolCallMessage(content, currentTurn, toolRoleOpts); 136 } catch (e) { 137 result = { error: `Tool execution failed: ${String(e)}` }; 138 const content = { tool_call_id: id, body: result }; 139 conversation.addToolCallMessage(content, currentTurn, toolRoleOpts); 140 } 141 142 // Bug 2006159 - Implement parallel tool calling, remove after implemented 143 break; 144 } 145 } 146 }, 147 };