UNPKG

donobu

Version:

Create browser automations with an LLM agent and replay them as Playwright scripts.

192 lines 8.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ToolManager = void 0; exports.buildToolInterpolationDataSource = buildToolInterpolationDataSource; const v4_1 = require("zod/v4"); const PageClosedException_1 = require("../exceptions/PageClosedException"); const ToolRequiresGptException_1 = require("../exceptions/ToolRequiresGptException"); const JsonUtils_1 = require("../utils/JsonUtils"); const Logger_1 = require("../utils/Logger"); const PlaywrightUtils_1 = require("../utils/PlaywrightUtils"); const TemplateInterpolator_1 = require("../utils/TemplateInterpolator"); /** * Executes tool calls against a given list of tools. * * Construct with the specific tool set a flow is allowed to use. Tool * *discovery* (which tools exist, defaults, plugins) is handled by * {@link ToolRegistry} — this class only cares about *execution*. */ class ToolManager { constructor(tools) { this.tools = tools; this.tools = tools; } /** * Invokes a tool by name with the provided parameters. * * This method handles the entire tool execution lifecycle including: * - Finding the appropriate tool by name * - Processing any template strings in parameter values using the {{...}} syntax * - Executing the tool with the processed parameters * - Capturing screenshots after tool execution * - Error handling and logging * * @param context - The context object containing page, persistence, metadata and other execution context * @param toolName - The name of the tool to invoke * @param toolParameters - Parameters to pass to the tool (template strings will be interpolated) * @param isFromGpt - Whether this tool call is initiated from a GPT * * @returns A Promise resolving to a ToolCall object containing execution details and results */ async invokeTool(context, toolName, toolParameters, isFromGpt) { const target = context.targetInspector.target; const startedAt = new Date().getTime(); const initialPageUrl = target.type === 'web' ? target.current?.url() : undefined; Logger_1.appLogger.info(`Taking action: ${toolName}`, { toolName, parameters: toolParameters, }); let toolCallResult; let postCallImageId = null; try { const tool = this.tools.find((t) => t.name === toolName); if (!tool) { toolCallResult = { isSuccessful: false, forLlm: `FAILED! No tool named '${toolName}' found.`, metadata: null, }; } else if (tool.requiresGpt && !context.gptClient) { const exception = new ToolRequiresGptException_1.ToolRequiresGptException(tool.name); toolCallResult = { isSuccessful: false, forLlm: exception.userFacingMessage, metadata: JsonUtils_1.JsonUtils.objectToJson(exception), }; } else { context.controlPanel.update({ state: context.metadata.state, headline: tool.controlPanelMessage, }); const interpolatedDataSource = buildToolInterpolationDataSource(context.envData, context.invokedToolCalls); // Interpolate the parameters for this tool call using the interpolated data source. const interpolatedParameters = (0, TemplateInterpolator_1.interpolateObject)(toolParameters, interpolatedDataSource); Logger_1.appLogger.debug(`Non-interpolated parameters for ${toolName} tool: ${JSON.stringify(toolParameters, null, 2)}`); Logger_1.appLogger.debug(`Interpolated parameters for ${toolName} tool: ${JSON.stringify(interpolatedParameters, null, 2)}`); // Persist an in-progress record immediately so the UI can show the tool call // while it is executing. The final write below will replace this record. await context.persistence.setToolCall(context.metadata.id, { id: context.toolCallId, toolName: toolName, parameters: toolParameters, outcome: null, postCallImageId: null, page: initialPageUrl ?? '', startedAt: startedAt, completedAt: null, }); // Expose the un-interpolated parameters so tools that need to // preserve `{{...}}` references (e.g. AssertTool) can read them. context.rawParameters = toolParameters; // Use the interpolated parameters when calling the tool. toolCallResult = isFromGpt ? await tool.callFromGpt(context, tool.inputSchemaForGpt.parse(interpolatedParameters)) : await tool.call(context, tool.inputSchema.parse(interpolatedParameters)); } } catch (error) { Logger_1.appLogger.error(`Exception while calling the ${toolName} tool`, error, { toolName, parameters: toolParameters, }); if (PlaywrightUtils_1.PlaywrightUtils.isPageClosedError(error)) { toolCallResult = { isSuccessful: false, forLlm: `FAILED! This tool call failed because the webpage for this operation had closed.`, metadata: { exception: new PageClosedException_1.PageClosedException().constructor.name, }, }; } else if (error instanceof v4_1.ZodError) { toolCallResult = { isSuccessful: false, forLlm: `FAILED! Invalid arguments: ${error.message}`, metadata: null, }; } else { toolCallResult = { isSuccessful: false, forLlm: `FAILED! ${typeof error}: ${error.message}`, metadata: null, }; } } if (toolCallResult.metadata?.exception !== new PageClosedException_1.PageClosedException().name) { try { if (context.targetInspector.connected) { const postCallImage = await context.targetInspector.captureScreenshot(); postCallImageId = await context.persistence.saveScreenShot(context.metadata.id, postCallImage); } } catch { // This can happen if the page/device has closed after the tool call ended. } } const completedAt = new Date().getTime(); const durationMillis = completedAt - startedAt; Logger_1.appLogger.info(`The ${toolName} tool completed in ${durationMillis}ms`, { toolName, durationMillis, outcome: toolCallResult, }); const toolCall = { id: context.toolCallId, toolName: toolName, parameters: toolParameters, outcome: toolCallResult, postCallImageId: postCallImageId, page: initialPageUrl ?? '', startedAt: startedAt, completedAt: completedAt, }; await context.persistence.setToolCall(context.metadata.id, toolCall); await context.persistence.setFlowMetadata(context.metadata); context.invokedToolCalls.push(toolCall); return toolCall; } } exports.ToolManager = ToolManager; /** * Builds a data source for template interpolation that includes the * interpolated parameters of previous tool calls. * * The first tool call in the given history is assumed to require no * interpolation; each subsequent call can reference the results of the * ones before it via `{{$.calls[n].result}}` syntax. */ function buildToolInterpolationDataSource(envData, previousToolCalls) { const dataSource = { env: envData, calls: [], }; const interpolatedPastToolCalls = []; for (const toolCall of previousToolCalls) { const tempDataSource = { env: envData, calls: [...interpolatedPastToolCalls], }; const processedParameters = (0, TemplateInterpolator_1.interpolateObject)(toolCall.parameters, tempDataSource); interpolatedPastToolCalls.push({ name: toolCall.toolName, args: processedParameters, result: toolCall.outcome.forLlm, }); dataSource.calls = [...interpolatedPastToolCalls]; } return dataSource; } //# sourceMappingURL=ToolManager.js.map