UNPKG

donobu

Version:

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

236 lines 12.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdminApiController = void 0; const express_1 = __importDefault(require("express")); const Logger_1 = require("../utils/Logger"); const RequestContextHolder_1 = require("./RequestContextHolder"); const RequestContext_1 = require("../models/RequestContext"); const DonobuException_1 = require("../exceptions/DonobuException"); const ToolManager_1 = require("./ToolManager"); const ToolsApi_1 = require("../apis/ToolsApi"); const FlowsApi_1 = require("../apis/FlowsApi"); const FlowsFilesApi_1 = require("../apis/FlowsFilesApi"); const FlowsToolCallsApi_1 = require("../apis/FlowsToolCallsApi"); const VersionApi_1 = require("../apis/VersionApi"); const PingApi_1 = require("../apis/PingApi"); const DefaultApiAuthApi_1 = require("../apis/DefaultApiAuthApi"); const SpecialFlowsApi_1 = require("../apis/SpecialFlowsApi"); const DonobuDeploymentEnvironment_1 = require("../models/DonobuDeploymentEnvironment"); const GptConfigsApi_1 = require("../apis/GptConfigsApi"); const AgentsApi_1 = require("../apis/AgentsApi"); const AskAiApi_1 = require("../apis/AskAiApi"); const DonobuStack_1 = require("./DonobuStack"); /** * This class sets up the API for managing DonobuFlow flows. */ class AdminApiController { /** * Creates a new instance. * @param donobuDeploymentEnvironment The environment in which this application is running in. * This has material consequences! If this is set to a value of LOCAL then... * - additional API endpoints are registered, of which allow operations that would * otherwise be considered unsafe. * - operations around active flows are considered fair game. * - no checking for flow ownership is performed, as all flows are considered owned by the * local environment. */ static async create(donobuDeploymentEnvironment) { const expressApp = await this.setupExpressFramework(donobuDeploymentEnvironment); return new AdminApiController(expressApp); } constructor(app) { this.app = app; } /** * Serves the API and web assets; this blocks until stop() is called. * If the given port is 0, a random port is assigned. */ start(port) { Logger_1.appLogger.debug(`Starting AdminController on port ${port}`); this.server = this.app.listen(port); } /** * Stops the server. */ stop() { if (this.server) { const address = this.server.address(); const port = typeof address === 'string' ? address : address?.port; Logger_1.appLogger.debug(`Stopping AdminController on port ${port}`); this.server.close(); } } static async setupExpressFramework(donobuDeploymentEnvironment) { const app = (0, express_1.default)(); const requestContextHolder = new RequestContextHolder_1.RequestContextHolder(); // JSON body parser with 1MB limit. app.use(express_1.default.json({ limit: '1mb', type: '*/*', strict: true, verify: (_req, res, buf, _encoding) => { try { JSON.parse(buf.toString()); } catch (_e) { res.statusCode = 400; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'Bad Request', message: 'Invalid JSON format in request body', })); throw new Error('Invalid JSON'); } }, })); // Request context middleware. app.use((req, _, next) => { const defaultApiAuth = requestContextHolder.get(); const authHeader = req.headers.authorization; let accessToken = null; if (authHeader?.startsWith('Bearer ')) { accessToken = authHeader.substring(7); } else { accessToken = defaultApiAuth.accessToken; } requestContextHolder.setForCurrentContext(new RequestContext_1.RequestContext(accessToken)); next(); }); // Clear request context after request. app.use((_, res, next) => { res.on('finish', () => { requestContextHolder.clearForCurrentContext(); }); next(); }); // Disable caching for all API routes. app.use((_req, res, next) => { res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, private'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); next(); }); // Access logging middleware. app.use((req, res, next) => { const start = performance.now(); res.on('finish', () => { const duration = Math.round(performance.now() - start); const logLevel = req.path === '/api/ping' ? 'debug' : 'info'; Logger_1.accessLogger[logLevel](`${req.method} | ${req.path} | ${res.statusCode} | ${duration}ms`); }); next(); }); // Set up the API endpoints. await this.prepareEndpoints(donobuDeploymentEnvironment, app, requestContextHolder); // Error handling middleware. This must happen after the endpoints have // been set up, as the order in which Express's initialization methods // are called matters. app.use((err, req, res, _next) => { // Check if response has already been sent. This can happen if some other // middleware errored out and closed the request on its own. if (res.headersSent) { return; } try { if (err instanceof DonobuException_1.DonobuException) { if (err.httpCode >= 500) { Logger_1.appLogger.error(`Unexpected exception when handling ${req.method} request at ${req.url}`, err); } res.status(err.httpCode).json({ code: err.donobuErrorCode, message: err.userFacingMessage, }); } else { Logger_1.appLogger.error(`Unexpected exception when handling ${req.method} request at ${req.url}`, err); // Only send error details in development const message = process.env.NODE_ENV === 'development' ? err.message : 'Internal Server Error'; res.status(500).json({ error: 'Internal Server Error', message, }); } } catch (error) { Logger_1.appLogger.error('Error in error handling middleware: ', error); res.status(500).send('Internal Server Error'); } }); return app; } static async prepareEndpoints(donobuDeploymentEnvironment, app, requestContextHolder) { const donobuStack = await (0, DonobuStack_1.setupDonobuStack)(donobuDeploymentEnvironment, requestContextHolder); const gptConfigsApi = new GptConfigsApi_1.GptConfigsApi(donobuStack.gptConfigsManager, donobuStack.agentsManager); const agentsApi = new AgentsApi_1.AgentsApi(donobuStack.agentsManager); const askAiApi = new AskAiApi_1.AskAiApi(donobuStack.gptConfigsManager, donobuStack.agentsManager); const toolsApi = new ToolsApi_1.ToolsApi(ToolManager_1.ToolManager.DEFAULT_TOOLS); const flowsApi = new FlowsApi_1.FlowsApi(donobuStack.flowsManager); const flowsFilesApi = new FlowsFilesApi_1.FlowsFilesApi(donobuStack.flowsPersistenceFactory); const flowsToolCallsApi = new FlowsToolCallsApi_1.FlowsToolCallsApi(donobuStack.flowsManager); const versionApi = new VersionApi_1.VersionApi(); const pingApi = new PingApi_1.PingApi(); const specialFlowsApi = new SpecialFlowsApi_1.SpecialFlowsApi(donobuStack.flowsManager); // Set up routes based on deployment environment switch (donobuDeploymentEnvironment) { case DonobuDeploymentEnvironment_1.DonobuDeploymentEnvironment.LOCAL: { const defaultApiAuthApi = new DefaultApiAuthApi_1.DefaultApiAuthApi(requestContextHolder); app.post('/api/default-auth', this.asyncHandler(defaultApiAuthApi.setDefaultApiAuth.bind(defaultApiAuthApi))); app.post('/api/flows/:flowId/cancel', this.asyncHandler(flowsApi.cancelFlow.bind(flowsApi))); app.post('/api/flows/:flowId/pause', this.asyncHandler(flowsApi.pauseFlow.bind(flowsApi))); app.post('/api/flows/:flowId/resume', this.asyncHandler(flowsApi.resumeFlow.bind(flowsApi))); app.post('/api/flows/:flowId/tool-calls', this.asyncHandler(flowsToolCallsApi.postToolCalls.bind(flowsToolCallsApi))); break; } case DonobuDeploymentEnvironment_1.DonobuDeploymentEnvironment.DONOBU_HOSTED_SINGLE_TENANT: case DonobuDeploymentEnvironment_1.DonobuDeploymentEnvironment.DONOBU_HOSTED_MULTI_TENANT: // No additional endpoints for these environments break; } // Common endpoints for all environments app.get('/api/gpt-configs', this.asyncHandler(gptConfigsApi.getAll.bind(gptConfigsApi))); app.get('/api/gpt-configs/:name', this.asyncHandler(gptConfigsApi.get.bind(gptConfigsApi))); app.post('/api/gpt-configs/:name', this.asyncHandler(gptConfigsApi.set.bind(gptConfigsApi))); app.delete('/api/gpt-configs/:name', this.asyncHandler(gptConfigsApi.delete.bind(gptConfigsApi))); app.get('/api/agents', this.asyncHandler(agentsApi.getAll.bind(agentsApi))); app.get('/api/agents/:name', this.asyncHandler(agentsApi.get.bind(agentsApi))); app.post('/api/agents/:name', this.asyncHandler(agentsApi.set.bind(agentsApi))); app.post('/api/ask-ai', this.asyncHandler(askAiApi.ask.bind(askAiApi))); app.get('/api/tools', this.asyncHandler(toolsApi.getSupportedTools.bind(toolsApi))); app.get('/api/flows', this.asyncHandler(flowsApi.getFlows.bind(flowsApi))); app.post('/api/flows', this.asyncHandler(flowsApi.createFlow.bind(flowsApi))); app.get('/api/flows/:flowId', this.asyncHandler(flowsApi.getFlowMetadata.bind(flowsApi))); app.get('/api/flows/:flowId/rerun', this.asyncHandler(flowsApi.getFlowAsRerun.bind(flowsApi))); app.get('/api/flows/:flowId/code', this.asyncHandler(flowsApi.getFlowAsCode.bind(flowsApi))); app.delete('/api/flows/:flowId', this.asyncHandler(flowsApi.deleteFlow.bind(flowsApi))); app.get('/api/flows/:flowId/images/:imageId', this.asyncHandler(flowsFilesApi.getFlowImage.bind(flowsFilesApi))); app.get('/api/flows/:flowId/video', this.asyncHandler(flowsFilesApi.getFlowVideo.bind(flowsFilesApi))); app.get('/api/flows/:flowId/tool-calls', this.asyncHandler(flowsToolCallsApi.getToolCalls.bind(flowsToolCallsApi))); app.get('/api/flows/:flowId/tool-calls/:toolCallId', this.asyncHandler(flowsToolCallsApi.getToolCall.bind(flowsToolCallsApi))); app.get('/api/version', this.asyncHandler(versionApi.version.bind(versionApi))); app.get('/api/ping', this.asyncHandler(pingApi.ping.bind(pingApi))); ///////////////////////////// // BEGIN special endpoints // ///////////////////////////// app.post('/api/analyze-cookie-consent', this.asyncHandler(specialFlowsApi.analyzeCookieConsent.bind(specialFlowsApi))); app.post('/api/detect-broken-links', this.asyncHandler(specialFlowsApi.detectBrokenLinks.bind(specialFlowsApi))); app.post('/api/extract-public-facebook-entity-data', this.asyncHandler(specialFlowsApi.extractPublicFacebookEntityData.bind(specialFlowsApi))); app.post('/api/extract-google-streetview-entity-data', this.asyncHandler(specialFlowsApi.extractGoogleStreetviewEntityData.bind(specialFlowsApi))); app.post('/api/extract-payment-provider-data', this.asyncHandler(specialFlowsApi.extractPaymentProviderData.bind(specialFlowsApi))); /////////////////////////// // END special endpoints // /////////////////////////// } static asyncHandler(fn) { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; } } exports.AdminApiController = AdminApiController; //# sourceMappingURL=AdminApiController.js.map