donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
699 lines • 32.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DonobuFlowsManager = void 0;
const GptClientFactory_1 = require("../clients/GptClientFactory");
const BrowserFramework_1 = require("../models/BrowserFramework");
const ChooseSelectOptionTool_1 = require("../tools/ChooseSelectOptionTool");
const ClickTool_1 = require("../tools/ClickTool");
const InputTextTool_1 = require("../tools/InputTextTool");
const InputRandomizedEmailAddressTool_1 = require("../tools/InputRandomizedEmailAddressTool");
const PressKeyTool_1 = require("../tools/PressKeyTool");
const DonobuFlow_1 = require("./DonobuFlow");
const FlowMetadata_1 = require("../models/FlowMetadata");
const GoToWebpageTool_1 = require("../tools/GoToWebpageTool");
const CustomToolRunnerTool_1 = require("../tools/CustomToolRunnerTool");
const ToolManager_1 = require("./ToolManager");
const ToolTipper_1 = require("./ToolTipper");
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
const ActiveFlowNotFoundException_1 = require("../exceptions/ActiveFlowNotFoundException");
const UnknownToolException_1 = require("../exceptions/UnknownToolException");
const CannotDeleteRunningFlowException_1 = require("../exceptions/CannotDeleteRunningFlowException");
const FlowNotFoundException_1 = require("../exceptions/FlowNotFoundException");
const ToolRequiresGptException_1 = require("../exceptions/ToolRequiresGptException");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os_1 = require("os");
const BrowserStateNotFoundException_1 = require("../exceptions/BrowserStateNotFoundException");
const MiscUtils_1 = require("../utils/MiscUtils");
const HoverOverElementTool_1 = require("../tools/HoverOverElementTool");
const DonobuDeploymentEnvironment_1 = require("../models/DonobuDeploymentEnvironment");
const Logger_1 = require("../utils/Logger");
const CodeGenerator_1 = require("./CodeGenerator");
const uuid_1 = require("uuid");
class DonobuFlowsManager {
constructor(deploymentEnvironment, gptConfigsManager, agentsManager, flowsPersistenceFactory) {
this.deploymentEnvironment = deploymentEnvironment;
this.gptConfigsManager = gptConfigsManager;
this.agentsManager = agentsManager;
this.flowsPersistenceFactory = flowsPersistenceFactory;
this.activeFlows = new Map();
this.codeGenerator = new CodeGenerator_1.CodeGenerator(gptConfigsManager, agentsManager);
}
/**
* Create a flow with the given parameters and invoke its `DonobuFlow#run`
* method, adding it to list of active flows.
*/
async createFlow(flowParams) {
const gptClientData = await this.createGptClient(flowParams.gptConfigNameOverride ?? null);
const initialRunMode = flowParams.initialRunMode ?? (gptClientData ? 'AUTONOMOUS' : 'INSTRUCT');
const browserConfig = flowParams.browser ?? DonobuFlowsManager.DEFAULT_BROWSER_CONFIG;
if (browserConfig.using.type === 'device' &&
!BrowserFramework_1.BrowserFramework.getSupportedDevices().has(browserConfig.using.deviceName ?? '')) {
throw new InvalidParamValueException_1.InvalidParamValueException('deviceName', browserConfig.using.deviceName);
}
this.validateFlowParams(flowParams, gptClientData.gptClient, initialRunMode);
const isControlPanelEnabled = !(browserConfig.using.type === 'device'
? browserConfig.using.headless
: false) &&
(flowParams.isControlPanelEnabled ?? true);
const allowedTools = this.setupAllowedTools(flowParams, gptClientData !== null);
const toolManager = new ToolManager_1.ToolManager(allowedTools);
const toolCallsOnStart = this.prepareInitialToolCalls(flowParams);
const toolTipDuration = flowParams.defaultToolTipDurationMilliseconds ??
DonobuFlowsManager.DEFAULT_TOOL_TIP_DURATION;
const tooltipper = new ToolTipper_1.ToolTipper(toolTipDuration);
const maxIterations = flowParams.maxIterations ?? DonobuFlowsManager.DEFAULT_MAX_ITERATIONS;
if (maxIterations < 0) {
throw new InvalidParamValueException_1.InvalidParamValueException('maxIterations', String(flowParams.maxIterations));
}
const flowMetadata = {
id: (0, uuid_1.v4)(),
createdWithDonobuVersion: MiscUtils_1.MiscUtils.DONOBU_VERSION,
name: flowParams.name || null,
browser: browserConfig,
runMode: initialRunMode,
isControlPanelEnabled: isControlPanelEnabled,
gptConfigName: gptClientData.gptConfigName,
hasGptConfigNameOverride: !gptClientData.agentName,
customTools: flowParams.customTools ?? null,
defaultToolTipDurationMilliseconds: tooltipper.defaultDuration,
callbackUrl: flowParams.callbackUrl || null,
targetWebsite: flowParams.targetWebsite,
overallObjective: flowParams.overallObjective ?? null,
allowedTools: allowedTools.map((tool) => tool.name),
resultJsonSchema: flowParams.resultJsonSchema || null,
maxIterations: maxIterations,
result: null,
inputTokensUsed: 0,
completionTokensUsed: 0,
iterations: 0,
startedAt: null,
completedAt: null,
state: 'UNSTARTED',
nextState: null,
};
const tempDirectoryForVideos = this.createTempDirectoryForFlow(flowMetadata.id);
const flowsPersistence = await this.flowsPersistenceFactory.createPersistenceLayer();
const browserStorageState = flowParams.browser?.initialState
? await this.getBrowserStorageState(flowParams.browser.initialState)
: undefined;
const browserFramework = await BrowserFramework_1.BrowserFramework.create(browserConfig, tempDirectoryForVideos, browserStorageState);
try {
const donobuFlow = new DonobuFlow_1.DonobuFlow(this, browserFramework, flowsPersistence, gptClientData.gptClient, toolManager, tooltipper, toolCallsOnStart, [], [], {
current: null,
}, flowMetadata);
await flowsPersistence.saveMetadata(flowMetadata);
const job = this.runFlow(donobuFlow, tempDirectoryForVideos, flowsPersistence);
const flowHandle = { donobuFlow, job };
this.activeFlows.set(flowMetadata.id, flowHandle);
return flowHandle;
}
catch (error) {
await browserFramework.close();
throw error;
}
}
/**
* Loads the given flow by ID and returns a `CreateDonobuFlow` object that can be passed to `createFlow`
* as to execute the flow as a rerun (i.e. without agentic decisioning).
*
* @param flowId The ID of the flow to prepare as a rerun.
* @returns Parameters that can be passed to createFlow to execute the flow as a rerun.
*/
async getFlowAsRerun(flowId) {
const priorFlowMetadata = await this.getFlowMetadata(flowId);
const originalToolCalls = await this.getToolCalls(flowId);
const toolCallsOnStart = this.prepareToolCallsForRerun(originalToolCalls);
const allowedTools = priorFlowMetadata.allowedTools ?? [];
return {
targetWebsite: priorFlowMetadata.targetWebsite,
overallObjective: priorFlowMetadata.overallObjective ?? undefined,
browser: priorFlowMetadata.browser,
name: priorFlowMetadata.name ?? undefined,
callbackUrl: priorFlowMetadata.callbackUrl ?? undefined,
customTools: priorFlowMetadata.customTools ?? undefined,
maxIterations: priorFlowMetadata.maxIterations,
gptConfigNameOverride: priorFlowMetadata.gptConfigName ?? undefined,
defaultToolTipDurationMilliseconds: 0,
initialRunMode: 'DETERMINISTIC',
isControlPanelEnabled: false,
allowedTools: allowedTools,
toolCallsOnStart: toolCallsOnStart,
resultJsonSchema: priorFlowMetadata.resultJsonSchema ?? undefined,
};
}
/** Add a proposed tool call the tool call queue for the given flow by ID. */
async proposeToolCall(flowId, toolName, parameters) {
const activeFlowHandle = this.isLocallyRunning()
? this.activeFlows.get(flowId)
: null;
if (!activeFlowHandle) {
throw new ActiveFlowNotFoundException_1.ActiveFlowNotFoundException(flowId);
}
const tool = ToolManager_1.ToolManager.ALL_TOOLS.find((t) => t.name === toolName);
if (!tool) {
throw new UnknownToolException_1.UnknownToolException(toolName);
}
activeFlowHandle.donobuFlow.proposedToolCalls.push({
name: tool.name,
parameters: parameters,
});
}
/**
* If the application is running in a non-hosted context, returns a direct,
* raw, `DonobuFlow` object by ID. If there is no flow in an active state
* with the given ID, or the application is running on some far flung server,
* then `ActiveFlowNotFoundException` is thrown. Mutations made to the
* returned object will be reflected by the active flow, and vice versa.
*/
getActiveFlow(flowId) {
if (!this.isLocallyRunning()) {
throw new ActiveFlowNotFoundException_1.ActiveFlowNotFoundException(flowId);
}
const activeFlowHandle = this.activeFlows.get(flowId);
if (!activeFlowHandle) {
throw new ActiveFlowNotFoundException_1.ActiveFlowNotFoundException(flowId);
}
return activeFlowHandle.donobuFlow;
}
/**
* Get flows metadata across multiple persistence layers with pagination and filtering.
*/
async getFlowsMetadata(query) {
// Parse the composite page token or create initial state.
const paginationState = this.parseCompositePageToken(query.pageToken);
const requestedLimit = Math.min(Math.max(1, query.limit || 100), 100);
// Results container.
const combinedResults = [];
// Get all persistence layers.
const persistenceLayers = await this.flowsPersistenceFactory.createPersistenceLayers();
// Query each persistence layer with its own pagination.
for (let i = 0; i < persistenceLayers.length; i++) {
// Skip layers we've already exhausted.
if (paginationState.exhaustedSources.includes(i)) {
continue;
}
// Calculate per-source limit - we need more than requested to merge
// properly.
//
// Double the limit to ensure we have enough data for sorting/merging.
const sourceLimit = Math.min(requestedLimit * 2, 100);
// Create a source-specific query, preserving all filter params
const sourceQuery = {
...query,
limit: sourceLimit,
pageToken: paginationState.sourceTokens[i],
};
const sourceResult = await persistenceLayers[i].getFlows(sourceQuery);
// Wrap each item with its source index.
const itemsWithSource = sourceResult.items.map((flow) => ({
flow,
sourceIndex: i,
}));
// Add to combined results.
combinedResults.push(...itemsWithSource);
// Update pagination state.
if (sourceResult.nextPageToken) {
paginationState.sourceTokens[i] = sourceResult.nextPageToken;
}
else {
// Mark this source as exhausted.
paginationState.exhaustedSources.push(i);
}
}
// Sort all results by creation date (newest first).
combinedResults.sort((a, b) => {
return (b.flow.startedAt || 0) - (a.flow.startedAt || 0);
});
// Take only the requested limit.
const limitedResults = combinedResults.slice(0, requestedLimit);
// Get the last timestamp for cursor-based continuation.
const lastTimestamp = limitedResults.length > 0
? limitedResults[limitedResults.length - 1].flow.startedAt
: null;
// Update the cursor timestamp in pagination state.
paginationState.cursorTimestamp = lastTimestamp;
// Are there more results?
const hasMore = combinedResults.length > requestedLimit ||
paginationState.exhaustedSources.length < persistenceLayers.length;
// Extract just the flow metadata objects for return.
const cleanResults = limitedResults.map((item) => item.flow);
return {
items: cleanResults,
nextPageToken: hasMore
? this.createCompositePageToken(paginationState)
: undefined,
};
}
/**
* Returns the metadata for the given flow by ID. If the flow is active, the
* returned metadata object is shared with the underlying flow and changes
* made to this metadata object will be visible to the flow. If the flow is
* not active, a copy of the persisted metadata is returned.
*/
async getFlowMetadata(flowId) {
const activeFlowHandle = this.isLocallyRunning()
? this.activeFlows.get(flowId)
: null;
if (activeFlowHandle) {
return activeFlowHandle.donobuFlow.metadata;
}
for (const persistence of await this.flowsPersistenceFactory.createPersistenceLayers()) {
try {
return await persistence.getMetadataByFlowId(flowId);
}
catch (error) {
if (!(error instanceof FlowNotFoundException_1.FlowNotFoundException)) {
throw error;
}
}
}
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
/**
* Returns the metadata for the given flow by name.
*/
async getFlowByName(flowName) {
for (const persistence of await this.flowsPersistenceFactory.createPersistenceLayers()) {
try {
return await persistence.getMetadataByFlowName(flowName);
}
catch (error) {
if (!(error instanceof FlowNotFoundException_1.FlowNotFoundException)) {
throw error;
}
}
}
throw FlowNotFoundException_1.FlowNotFoundException.forName(flowName);
}
/** Returns all the tool calls made by the given flow by ID. */
async getToolCalls(flowId) {
const activeFlowHandle = this.isLocallyRunning()
? this.activeFlows.get(flowId)
: null;
if (activeFlowHandle) {
return [...activeFlowHandle.donobuFlow.invokedToolCalls].sort((a, b) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime());
}
const persistenceLayers = await this.flowsPersistenceFactory.createPersistenceLayers();
for (const persistence of persistenceLayers) {
try {
return await persistence.getToolCalls(flowId);
}
catch (error) {
if (!(error instanceof FlowNotFoundException_1.FlowNotFoundException)) {
throw error;
}
}
}
throw FlowNotFoundException_1.FlowNotFoundException.forId(flowId);
}
/**
* Attempts to delete a flow by ID. If the flow is active, then
* `CannotDeleteRunningFlowException` is thrown. If the flow is not active,
* then the flow's persisted data is deleted.
*/
async deleteFlowById(flowId) {
const activeFlow = this.isLocallyRunning()
? this.activeFlows.get(flowId)
: null;
if (activeFlow) {
throw new CannotDeleteRunningFlowException_1.CannotDeleteRunningFlowException(flowId);
}
for (const persistence of await this.flowsPersistenceFactory.createPersistenceLayers()) {
try {
const metadata = await persistence.getMetadataByFlowId(flowId);
if ((0, FlowMetadata_1.isComplete)(metadata.state)) {
await persistence.deleteFlow(flowId);
return;
}
}
catch (error) {
if (!(error instanceof FlowNotFoundException_1.FlowNotFoundException)) {
throw error;
}
}
}
}
/**
* Attempts to cancel a flow by ID. If the flow is active, the flow is ended
* with a state of `FAILED`. If the flow is not active, this method has no
* effect.
*/
async cancelFlow(flowId) {
const activeFlowHandle = this.isLocallyRunning()
? this.activeFlows.get(flowId)
: null;
if (activeFlowHandle) {
activeFlowHandle.donobuFlow.metadata.nextState = 'FAILED';
const currentPage = activeFlowHandle.donobuFlow.focusedPage.current;
if (currentPage) {
await activeFlowHandle.donobuFlow.browserFramework.close();
}
return activeFlowHandle.donobuFlow.metadata;
}
return await this.getFlowMetadata(flowId);
}
/** Creates a Node.js Microsoft Playwright script to replay the given flow. */
async getFlowAsPlaywrightScript(flowId) {
const flowMetadata = await this.getFlowMetadata(flowId);
const toolCalls = (await this.getFlowAsRerun(flowId)).toolCallsOnStart;
return this.codeGenerator.getFlowAsPlaywrightScript(flowMetadata, toolCalls);
}
/**
* Creates a GPT client using the provided GPT configuration by name. If the
* configuration name is null, or resolves to a missing configuration, then
* the default GPT configuration associated with the 'flow-runner' agent is
* used.
*
* This method is public for testing purposes only.
**/
async createGptClient(gptConfigName) {
if (gptConfigName) {
try {
const gptConfig = await this.gptConfigsManager.get(gptConfigName);
const gptClient = await GptClientFactory_1.GptClientFactory.createFromGptConfig(gptConfig);
return {
gptConfigName: gptConfigName,
agentName: null,
gptClient: gptClient,
};
}
catch (_error) {
Logger_1.appLogger.warn(`Failed to find GPT configuration: ${gptConfigName}, will default to the 'flow-runner' agent config (if it exists).`);
}
}
const defaultGptConfigName = await this.agentsManager.get('flow-runner');
if (!defaultGptConfigName) {
return {
gptConfigName: null,
agentName: null,
gptClient: null,
};
}
const gptConfig = await this.gptConfigsManager.get(defaultGptConfigName);
const gptClient = await GptClientFactory_1.GptClientFactory.createFromGptConfig(gptConfig);
return {
gptConfigName: defaultGptConfigName,
agentName: 'flow-runner',
gptClient: gptClient,
};
}
/**
* Loads the browser state associated with the given flow. Throws
* {@link BrowserStateNotFoundException} if it is not found.
*/
async getBrowserStorageState(browserStateRef) {
let flowMetadata;
switch (browserStateRef.type) {
case 'id':
flowMetadata = await this.getFlowMetadata(browserStateRef.value);
break;
case 'name':
flowMetadata = await this.getFlowByName(browserStateRef.value);
break;
case 'json':
return browserStateRef.value;
default:
throw new InvalidParamValueException_1.InvalidParamValueException('type', browserStateRef.type);
}
for (const persistence of await this.flowsPersistenceFactory.createPersistenceLayers()) {
try {
const browserState = await persistence.getBrowserState(flowMetadata.id);
if (browserState) {
return browserState;
}
else {
throw new BrowserStateNotFoundException_1.BrowserStateNotFoundException(flowMetadata.id);
}
}
catch (error) {
if (!(error instanceof FlowNotFoundException_1.FlowNotFoundException)) {
throw error;
}
}
}
throw new BrowserStateNotFoundException_1.BrowserStateNotFoundException(flowMetadata.id);
}
validateFlowParams(flowParams, gptClient, initialRunMode) {
const validTargetWebsiteProtocols = ['https:', 'http:', 'file:'];
const validCallbackUrlProtocols = ['https:', 'http:'];
const parsedTargetWebsite = DonobuFlowsManager.parseUrl(flowParams.targetWebsite);
const parsedCallbackUrl = DonobuFlowsManager.parseUrl(flowParams.callbackUrl);
if (parsedTargetWebsite &&
!validTargetWebsiteProtocols.some((protocol) => protocol === parsedTargetWebsite.protocol)) {
throw new InvalidParamValueException_1.InvalidParamValueException('targetWebsite', flowParams.targetWebsite, 'the URL must start with a supported protocol (example: "https://")');
}
else if (flowParams.targetWebsite && !parsedTargetWebsite) {
throw new InvalidParamValueException_1.InvalidParamValueException('targetWebsite', flowParams.targetWebsite, 'the URL is malformed');
}
if (parsedCallbackUrl &&
!validCallbackUrlProtocols.some((protocol) => protocol === parsedCallbackUrl.protocol)) {
throw new InvalidParamValueException_1.InvalidParamValueException('callbackUrl', flowParams.callbackUrl, 'the URL must start with a supported protocol (example: "https://")');
}
else if (flowParams.callbackUrl && !parsedCallbackUrl) {
throw new InvalidParamValueException_1.InvalidParamValueException('callbackUrl', flowParams.callbackUrl, 'the URL is malformed');
}
if (flowParams.name && flowParams.name.length > 255) {
throw new InvalidParamValueException_1.InvalidParamValueException('name', flowParams.name, 'the value cannot be longer than 255 characters');
}
switch (initialRunMode) {
case 'AUTONOMOUS':
if ((flowParams.overallObjective?.trim().length ?? 0) === 0) {
throw new InvalidParamValueException_1.InvalidParamValueException('overallObjective', flowParams.overallObjective, `'initialRunMode' has a value of '${initialRunMode}'`);
}
if (!gptClient) {
throw new InvalidParamValueException_1.InvalidParamValueException('initialRunMode', initialRunMode, `no GPT client is available`);
}
break;
case 'INSTRUCT':
break;
case 'DETERMINISTIC':
break;
default:
throw new InvalidParamValueException_1.InvalidParamValueException('initialRunMode', initialRunMode);
}
if (!gptClient) {
this.checkIfAnyToolsRequireGpt(flowParams.allowedTools ?? null, flowParams.toolCallsOnStart ?? null);
}
}
setupAllowedTools(flowParams, hasGptClient) {
const customTools = flowParams.customTools?.map((tool) => new CustomToolRunnerTool_1.CustomToolRunnerTool(tool)) ??
[];
const toolsNeededOnStart = flowParams.toolCallsOnStart?.map((t) => t.name) ?? [];
let prepackagedTools;
if (flowParams.allowedTools?.length) {
const allowedTools = [...toolsNeededOnStart, ...flowParams.allowedTools];
// The user has specified a list of tools to use.
prepackagedTools = ToolManager_1.ToolManager.ALL_TOOLS.filter((tool) => allowedTools.includes(tool.name));
}
else {
// The user has not specified a list of tools to use, so use the default.
const allowedTools = [
...toolsNeededOnStart,
...ToolManager_1.ToolManager.DEFAULT_TOOLS.map((t) => t.name),
];
prepackagedTools = ToolManager_1.ToolManager.ALL_TOOLS.filter((tool) => allowedTools.includes(tool.name));
}
// If there is no GPT client, only include tools that do not require it.
const prepackagedToolsWithGptFiltered = prepackagedTools.filter((tool) => {
return hasGptClient || !tool.requiresGpt;
});
return [...customTools, ...prepackagedToolsWithGptFiltered];
}
prepareInitialToolCalls(flowParams) {
if (!flowParams.toolCallsOnStart?.length && flowParams.targetWebsite) {
return [
{
name: GoToWebpageTool_1.GoToWebpageTool.NAME,
parameters: {
rationale: 'Initializing web navigation.',
url: flowParams.targetWebsite.toString(),
},
},
];
}
if (flowParams.toolCallsOnStart) {
return flowParams.toolCallsOnStart;
}
else {
return [];
}
}
createTempDirectoryForFlow(flowId) {
const tempDir = path.join((0, os_1.tmpdir)(), flowId);
fs.mkdirSync(tempDir);
return tempDir;
}
async runFlow(donobuFlow, tempDirectoryForVideos, flowsPersistence) {
try {
await donobuFlow.run();
this.activeFlows.delete(donobuFlow.metadata.id);
const videoPath = this.findLargestVideo(tempDirectoryForVideos);
if (videoPath) {
const videoBytes = fs.readFileSync(videoPath);
await flowsPersistence.setVideo(donobuFlow.metadata.id, videoBytes);
}
}
finally {
fs.rmSync(tempDirectoryForVideos, { recursive: true, force: true });
}
}
findLargestVideo(directory) {
const files = fs
.readdirSync(directory)
.filter((file) => file.endsWith('.webm'))
.map((file) => path.join(directory, file));
return files.length
? files.reduce((a, b) => fs.statSync(a).size > fs.statSync(b).size ? a : b)
: null;
}
isLocallyRunning() {
return this.deploymentEnvironment === DonobuDeploymentEnvironment_1.DonobuDeploymentEnvironment.LOCAL;
}
checkIfAnyToolsRequireGpt(requestedTools, toolCallsOnStart) {
const toolMap = new Map(ToolManager_1.ToolManager.ALL_TOOLS.map((tool) => [tool.name, tool]));
const requestedToolsRequiringGpt = [...(requestedTools ?? [])].filter((name) => toolMap.get(name)?.requiresGpt);
if (requestedToolsRequiringGpt.length) {
throw new ToolRequiresGptException_1.ToolRequiresGptException(requestedToolsRequiringGpt[0]);
}
const toolCallsRequiringGpt = toolCallsOnStart?.filter((call) => toolMap.get(call.name)?.requiresGpt);
if (toolCallsRequiringGpt?.length) {
throw new ToolRequiresGptException_1.ToolRequiresGptException(toolCallsRequiringGpt[0].name);
}
}
prepareToolCallsForRerun(toolCalls) {
// Tools that need special handling for their inputs during replay.
const toolsThatNeedRemappedInputs = new Set([
ChooseSelectOptionTool_1.ChooseSelectOptionTool.NAME,
ClickTool_1.ClickTool.NAME,
HoverOverElementTool_1.HoverOverElementTool.NAME,
InputRandomizedEmailAddressTool_1.InputRandomizedEmailAddressTool.NAME,
InputTextTool_1.InputTextTool.NAME,
PressKeyTool_1.PressKeyTool.NAME,
]);
const proposedToolCalls = [];
for (const toolCall of toolCalls) {
const needsRemappedInputs = toolsThatNeedRemappedInputs.has(toolCall.toolName);
if (needsRemappedInputs && toolCall.outcome.metadata) {
// These tools write special selector metadata to the outcome object
// so that it can be replayed easily. Though if the tool call
// originally failed, the metadata may not exist.
const revisedArgs = { ...toolCall.parameters };
revisedArgs.selector = toolCall.outcome.metadata;
// Unset the flow-variant annotation-related fields
revisedArgs.annotation = '';
revisedArgs.whyThisAnnotation = '';
proposedToolCalls.push({
name: toolCall.toolName,
parameters: revisedArgs,
});
}
else if (needsRemappedInputs && !toolCall.outcome.metadata) {
// This is not recoverable.
Logger_1.appLogger.warn(`Failed to prepre tool call for rerun due to result metadata not existing for: ${JSON.stringify(toolCall)}`);
}
else {
// This is just a normal tool then.
const tool = ToolManager_1.ToolManager.ALL_TOOLS.find((t) => t.name === toolCall.toolName);
if (!tool) {
throw new UnknownToolException_1.UnknownToolException(toolCall.toolName);
}
proposedToolCalls.push({
name: toolCall.toolName,
parameters: toolCall.parameters,
});
}
}
return proposedToolCalls;
}
/**
* Parses the given argument as a URL, returning null on failure.
*/
static parseUrl(url) {
if (!url) {
return null;
}
try {
return new URL(url);
}
catch (_) {
return null;
}
}
/**
* Parse a composite page token into pagination state
*/
parseCompositePageToken(token) {
if (!token) {
return {
sourceTokens: {},
exhaustedSources: [],
cursorTimestamp: null,
};
}
try {
return JSON.parse(Buffer.from(token, 'base64').toString('utf8'));
}
catch (_error) {
// If token parsing fails, start fresh
return {
sourceTokens: {},
exhaustedSources: [],
cursorTimestamp: null,
};
}
}
/**
* Create a composite page token from pagination state
*/
createCompositePageToken(state) {
return Buffer.from(JSON.stringify(state)).toString('base64');
}
}
exports.DonobuFlowsManager = DonobuFlowsManager;
DonobuFlowsManager.DEFAULT_TOOL_TIP_DURATION = 2247;
DonobuFlowsManager.DEFAULT_MAX_ITERATIONS = 10;
DonobuFlowsManager.DEFAULT_BROWSER_CONFIG = {
initialState: undefined,
persistState: false,
using: {
type: 'device',
deviceName: 'Desktop Chromium',
headless: false,
},
};
DonobuFlowsManager.DEFAULT_BROWSER_STATE_FILENAME = 'browserstate.json';
//# sourceMappingURL=DonobuFlowsManager.js.map