donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
192 lines • 8.84 kB
JavaScript
;
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