donobu
Version:
Create browser automations with an LLM agent and replay them as Playwright scripts.
256 lines • 10.8 kB
JavaScript
;
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.webTargetRuntimePlugin = exports.WebTargetRuntime = void 0;
const fs = __importStar(require("fs/promises"));
const os_1 = require("os");
const path = __importStar(require("path"));
const InvalidParamValueException_1 = require("../exceptions/InvalidParamValueException");
const WebTargetInspector_1 = require("../managers/WebTargetInspector");
const ControlPanel_1 = require("../models/ControlPanel");
const GoToWebpageTool_1 = require("../tools/GoToWebpageTool");
const BrowserUtils_1 = require("../utils/BrowserUtils");
const Logger_1 = require("../utils/Logger");
const PlaywrightUtils_1 = require("../utils/PlaywrightUtils");
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
* Returns a browser config that points to BrowserBase if the
* BROWSERBASE_PROJECT_ID and BROWSERBASE_API_KEY environment variables are
* present, otherwise, returns back a basic config that uses local Chromium.
*/
function getDefaultBrowserConfig(environ) {
const browserBaseProjectId = environ.data.BROWSERBASE_PROJECT_ID;
if (browserBaseProjectId && environ.data.BROWSERBASE_API_KEY) {
return {
initialState: undefined,
persistState: false,
using: {
type: 'browserBase',
sessionArgs: {
projectId: browserBaseProjectId,
browserSettings: {
advancedStealth: false,
},
keepAlive: false,
proxies: false,
},
},
};
}
else {
return {
initialState: undefined,
persistState: false,
using: {
type: 'device',
deviceName: 'Desktop Chromium',
headless: false,
},
};
}
}
async function createTempDirectoryForFlow(flowId) {
const tempDir = path.join((0, os_1.tmpdir)(), flowId);
await fs.mkdir(tempDir);
return tempDir;
}
// ---------------------------------------------------------------------------
// WebTargetRuntime
// ---------------------------------------------------------------------------
/**
* {@link TargetRuntime} implementation for web (Playwright browser) targets.
*
* Encapsulates browser config resolution, browser context creation, device
* name validation, control panel setup, video directory management, and the
* `GoToWebpage` initial tool call.
*/
class WebTargetRuntime {
constructor(browserContext, inspector, controlPanel, browserConfig, isControlPanelEnabled, targetWebsite, videoDir) {
this.targetType = 'web';
this.browserContext = browserContext;
this.inspector = inspector;
this.controlPanel = controlPanel;
this.browserConfig = browserConfig;
this.isControlPanelEnabled = isControlPanelEnabled;
this.targetWebsite = targetWebsite;
this.videoDir = videoDir;
}
/**
* Create a {@link WebTargetRuntime} from the given parameters.
*
* Resolves browser config, validates device name, creates the browser
* context (or uses the override), sets up the control panel, and prepares
* the video directory.
*/
static async create(params) {
const { flowParams, flowId, interactionVisualizer, controlPanelFactory } = params;
const webConfig = flowParams.web;
const targetWebsite = webConfig?.targetWebsite ?? '';
// --- Browser config resolution ---
const defaultBrowserConfig = getDefaultBrowserConfig(params.environ);
const defaultDeviceName = defaultBrowserConfig.using.type === 'device'
? defaultBrowserConfig.using.deviceName
: '';
const browserConfig = webConfig?.browser ?? defaultBrowserConfig;
if (browserConfig.using.type === 'device') {
if (!browserConfig.using.deviceName) {
browserConfig.using.deviceName = defaultDeviceName;
}
if (!BrowserUtils_1.BrowserUtils.getSupportedDevices().has(browserConfig.using.deviceName ?? defaultDeviceName ?? '')) {
throw new InvalidParamValueException_1.InvalidParamValueException('deviceName', browserConfig.using.deviceName);
}
}
// --- Control panel ---
const isControlPanelEnabled = !(browserConfig.using.type === 'device'
? browserConfig.using.headless
: false) &&
(flowParams.isControlPanelEnabled ?? true);
const controlPanel = isControlPanelEnabled
? await controlPanelFactory(flowId)
: new ControlPanel_1.NoOpControlPanel();
try {
// --- Video directory ---
const videoDir = flowParams.videoDisabled
? undefined
: await createTempDirectoryForFlow(flowId);
// --- Browser readiness (lazy install for non-Chromium browsers) ---
if (browserConfig.using.type === 'device' &&
browserConfig.using.deviceName) {
const browserType = BrowserUtils_1.BrowserUtils.getBrowserTypeForDeviceName(browserConfig.using.deviceName);
if (browserType !== 'chromium') {
const isReady = await PlaywrightUtils_1.PlaywrightUtils.isBrowserInstalled(browserType);
if (!isReady) {
Logger_1.appLogger.info(`Installing ${browserType} for first-time use — this may take a few minutes...`);
await PlaywrightUtils_1.PlaywrightUtils.ensureBrowserReady(browserType);
}
}
}
// --- Browser context ---
const browserContext = params.browserContextOverride
? params.browserContextOverride
: await BrowserUtils_1.BrowserUtils.create(browserConfig, videoDir, webConfig?.browser?.initialState && params.getBrowserStorageState
? await params.getBrowserStorageState(webConfig.browser.initialState)
: undefined);
// --- Inspector ---
const webTarget = { type: 'web', current: null };
const inspector = new WebTargetInspector_1.WebTargetInspector(webTarget, browserContext, interactionVisualizer);
return new WebTargetRuntime(browserContext, inspector, controlPanel, browserConfig, isControlPanelEnabled, targetWebsite, videoDir);
}
catch (error) {
controlPanel.close();
throw error;
}
}
getMetadataFields() {
return {
target: 'web',
web: {
browser: this.browserConfig,
targetWebsite: this.targetWebsite,
},
isControlPanelEnabled: this.isControlPanelEnabled,
};
}
getInitialToolCalls(flowParams) {
const targetWebsite = flowParams.web?.targetWebsite;
if (targetWebsite) {
return [
{
name: GoToWebpageTool_1.GoToWebpageTool.NAME,
parameters: {
rationale: 'Initializing web navigation.',
url: targetWebsite.toString(),
},
},
];
}
return [];
}
async destroy() {
const browser = this.browserContext?.browser();
try {
await this.browserContext?.close();
}
catch (error) {
Logger_1.appLogger.error('Error when attempting to close browser context:', error);
}
try {
await browser?.close();
}
catch (error) {
Logger_1.appLogger.error('Error when attempting to close browser:', error);
}
}
}
exports.WebTargetRuntime = WebTargetRuntime;
// ---------------------------------------------------------------------------
// WebTargetRuntimePlugin
// ---------------------------------------------------------------------------
/**
* Built-in {@link TargetRuntimePlugin} for web (Playwright browser) targets.
*
* Registered as `type: 'web'` at startup.
*/
exports.webTargetRuntimePlugin = {
type: 'web',
describe() {
return {
label: 'Web Browser',
supportedDevices: Array.from(BrowserUtils_1.BrowserUtils.getSupportedDevices().keys()),
};
},
async validate(flowParams) {
const validProtocols = ['https:', 'http:'];
const targetWebsite = flowParams.web?.targetWebsite;
if (targetWebsite) {
let parsedUrl;
try {
parsedUrl = new URL(targetWebsite);
}
catch {
throw new InvalidParamValueException_1.InvalidParamValueException('web.targetWebsite', targetWebsite, 'the URL is malformed');
}
if (!validProtocols.includes(parsedUrl.protocol)) {
throw new InvalidParamValueException_1.InvalidParamValueException('web.targetWebsite', targetWebsite, 'the URL must start with a supported protocol (example: "https://")');
}
}
},
async createRuntime(params) {
return WebTargetRuntime.create(params);
},
};
//# sourceMappingURL=WebTargetRuntime.js.map