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