UNPKG

donobu

Version:

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

699 lines 32.7 kB
"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