HEX
Server: LiteSpeed
System: Linux houston.panomity.com 6.8.0-100-generic #100-Ubuntu SMP PREEMPT_DYNAMIC Tue Jan 13 16:40:06 UTC 2026 x86_64
User: nudepix (1011)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //opt/agentcloud/webapp/src/controllers/tool.ts
'use strict';

import { dynamicResponse } from '@dr';
import { removeAgentsTool } from 'db/agent';
import { getAssetById } from 'db/asset';
import { getCredentialsByTeam } from 'db/credential';
import { getDatasourceById, getDatasourcesByTeam } from 'db/datasource';
import { addTool, deleteToolById, editTool, getToolById, getToolsByTeam } from 'db/tool';
import toObjectId from 'misc/toobjectid';
import toSnakeCase from 'misc/tosnakecase';
import { Retriever,ToolType, ToolTypes } from 'struct/tool';
import { chainValidations } from 'utils/validationUtils';

export async function toolsData(req, res, _next) {
	const [tools, credentials, datasources] = await Promise.all([
		getToolsByTeam(req.params.resourceSlug),
		getCredentialsByTeam(req.params.resourceSlug),
		getDatasourcesByTeam(req.params.resourceSlug),
	]);
	return {
		csrf: req.csrfToken(),
		tools,
		credentials,
		datasources,
	};
}

/**
 * GET /[resourceSlug]/tools
 * tool page html
 */
export async function toolsPage(app, req, res, next) {
	const data = await toolsData(req, res, next);
	res.locals.data = { ...data, account: res.locals.account };
	return app.render(req, res, `/${req.params.resourceSlug}/tools`);
}

/**
 * GET /[resourceSlug]/tools.json
 * team tools json data
 */
export async function toolsJson(req, res, next) {
	const data = await toolsData(req, res, next);
	return res.json({ ...data, account: res.locals.account });
}

export async function toolData(req, res, _next) {
	const [tool, credentials, datasources] = await Promise.all([
		getToolById(req.params.resourceSlug, req.params.toolId),
		getCredentialsByTeam(req.params.resourceSlug),
		getDatasourcesByTeam(req.params.resourceSlug),
	]);
	return {
		csrf: req.csrfToken(),
		tool,
		credentials,
		datasources,
	};
}

/**
 * GET /[resourceSlug]/tool/:toolId.json
 * tool json data
 */
export async function toolJson(req, res, next) {
	const data = await toolsData(req, res, next);
	return res.json({ ...data, account: res.locals.account });
}

/**
 * GET /[resourceSlug]/tool/:toolId/edit
 * tool json data
 */
export async function toolEditPage(app, req, res, next) {
	const data = await toolData(req, res, next);
	res.locals.data = { ...data, account: res.locals.account };
	return app.render(req, res, `/${req.params.resourceSlug}/tool/${data.tool._id}/edit`);
}
 
/**
 * GET /[resourceSlug]/tool/add
 * tool json data
 */
export async function toolAddPage(app, req, res, next) {
	const data = await toolData(req, res, next);
	res.locals.data = { ...data, account: res.locals.account };
	return app.render(req, res, `/${req.params.resourceSlug}/tool/add`);
}

function validateTool(tool) {
	return chainValidations(tool, [
		{ field: 'name', validation: { notEmpty: true }},
		{ field: 'type', validation: { notEmpty: true, inSet: new Set(Object.values(ToolTypes))}},
		{ field: 'retriever', validation: { notEmpty: true, inSet: new Set(Object.values(Retriever))}},
		{ field: 'description', validation: { notEmpty: true, lengthMin: 2 }, validateIf: { field: 'type', condition: (value) => value === ToolType.RAG_TOOL }},
		{ field: 'datasourceId', validation: { notEmpty: true, hasLength: 24, customError: 'Invalid data sources' }, validateIf: { field: 'type', condition: (value) => value == ToolType.RAG_TOOL }},
		{ field: 'data.description', validation: { notEmpty: true }, validateIf: { field: 'type', condition: (value) => value !== ToolType.RAG_TOOL }},
		{ field: 'data.parameters', validation: { notEmpty: true }, validateIf: { field: 'type', condition: (value) => value !== ToolType.RAG_TOOL }},
		{ field: 'schema', validation: { notEmpty: true }, validateIf: { field: 'type', condition: (value) => value == ToolType.API_TOOL }},
		{ field: 'naame', validation: { regexMatch: new RegExp('^[\\w_][A-Za-z0-9_]*$','gm'),
			customError: 'Name must not contain spaces or start with a number. Only alphanumeric and underscore characters allowed' },
		validateIf: { field: 'type', condition: (value) => value == ToolType.API_TOOL }},
		{ field: 'data.parameters.properties', validation: { objectHasKeys: true }, validateIf: { field: 'type', condition: (value) => value == ToolType.API_TOOL }},
		{ field: 'data.parameters.code', validation: { objectHasKeys: true }, validateIf: { field: 'type', condition: (value) => value == ToolType.FUNCTION_TOOL }},
	], { 
		name: 'Name',
		retriever_type: 'Retrieval Strategy',
		type: 'Type',
		credentialId: 'Credential',
		'data.builtin': 'Is built-in',
		'data.description': 'Description',
		'data.parameters': 'Parameters',
		'data.parameters.properties': '',
		'data.parameters.code': ''
	});
}

export async function addToolApi(req, res, next) {

	const { name, type, data, schema, datasourceId, description, iconId, retriever, retriever_config }  = req.body;

	const validationError = validateTool(req.body);
	if (validationError) {	
		return dynamicResponse(req, res, 400, { error: validationError });
	}

	if (datasourceId && (typeof datasourceId !== 'string' || datasourceId.length !== 24)) {
		const foundDatasource = await getDatasourceById(req.params.resourceSlug, datasourceId);
		if (!foundDatasource) {
			return dynamicResponse(req, res, 400, { error: 'Invalid datasource IDs' });
		}
	}

	const foundIcon = await getAssetById(iconId);

	const addedTool = await addTool({
		orgId: res.locals.matchingOrg.id,
		teamId: toObjectId(req.params.resourceSlug),
	    name,
	    description,
	 	type: type as ToolType,
		datasourceId: toObjectId(datasourceId),
	 	retriever_type: retriever || null,
	 	retriever_config: retriever_config || {}, //TODO: validation
	 	schema: schema,
		data: {
			...data,
			builtin: false,
			name: (type as ToolType) === ToolType.API_TOOL
				? 'openapi_request'
				: ((type as ToolType) === ToolType.FUNCTION_TOOL 
					? toSnakeCase(name)
					: name),
		},
		icon: foundIcon ? {
			id: foundIcon._id,
			filename: foundIcon.filename,
		} : null,
	});

	return dynamicResponse(req, res, 302, { _id: addedTool.insertedId, redirect: `/${req.params.resourceSlug}/tools` });

}

export async function editToolApi(req, res, next) {

	const { name, type, data, toolId, schema, description, datasourceId, retriever, retriever_config }  = req.body;

	const validationError = validateTool(req.body);
	if (validationError) {	
		return dynamicResponse(req, res, 400, { error: validationError });
	}

	if (datasourceId && (typeof datasourceId !== 'string' || datasourceId.length !== 24)) {
		const foundDatasource = await getDatasourceById(req.params.resourceSlug, datasourceId);
		if (!foundDatasource) {
			return dynamicResponse(req, res, 400, { error: 'Invalid datasource IDs' });
		}
	}

	await editTool(req.params.resourceSlug, toolId, {
	    name,
	 	type: type as ToolType,
	    description,
	 	schema: schema,
	 	datasourceId: toObjectId(datasourceId),
	 	retriever_type: retriever || null,
	 	retriever_config: retriever_config || {}, //TODO: validation
		data: {
			...data,
			builtin: false,
			name: (type as ToolType) === ToolType.API_TOOL
				? 'openapi_request'
				: ((type as ToolType) === ToolType.FUNCTION_TOOL 
					? toSnakeCase(name)
					: name),
		},
	});

	return dynamicResponse(req, res, 302, { /*redirect: `/${req.params.resourceSlug}/tools`*/ });

}

/**
 * @api {delete} /forms/tool/[toolId] Delete a tool
 * @apiName delete
 * @apiGroup Tool
 *
 * @apiParam {String} toolID tool id
 */
export async function deleteToolApi(req, res, next) {

	const { toolId } = req.body;

	if (!toolId || typeof toolId !== 'string' || toolId.length !== 24) {
		return dynamicResponse(req, res, 400, { error: 'Invalid inputs' });
	}

	await Promise.all([
		deleteToolById(req.params.resourceSlug, toolId),
		removeAgentsTool(req.params.resourceSlug, toolId),
	]);

	return dynamicResponse(req, res, 302, { /*redirect: `/${req.params.resourceSlug}/agents`*/ });

}