import { ITools } from '../../../types/tools/tool-interfaces.js'; import { cleanObject } from '../../../utils/serialization/safe-json.js'; import { ResponseFactory } from '../../../utils/responses/response-factory.js'; import type { HandlerArgs } from '../../../types/handlers/handler-types.js'; import { executeAutomationRequest, getTimeoutMs, normalizePathFields } from '../foundation/dispatch/common-handlers.js'; import { sanitizePath } from '../../../utils/paths/path-security.js'; /** Valid parameters for each input action */ const VALID_PARAMS_BY_ACTION: Record> = { create_input_action: new Set(['action', 'name', 'path', 'priority', 'timeoutMs']), create_input_mapping_context: new Set(['action', 'name', 'path', 'priority', 'timeoutMs']), add_mapping: new Set(['action', 'contextPath', 'actionPath', 'key', 'triggerType', 'modifierType', 'timeoutMs']), remove_mapping: new Set(['action', 'contextPath', 'actionPath', 'key', 'timeoutMs']), add_legacy_action_mapping: new Set(['action', 'name', 'actionName', 'key', 'shift', 'ctrl', 'alt', 'cmd', 'timeoutMs']), remove_legacy_action_mapping: new Set(['action', 'name', 'actionName', 'key', 'shift', 'ctrl', 'alt', 'cmd', 'timeoutMs']), add_legacy_axis_mapping: new Set(['action', 'name', 'axisName', 'key', 'scale', 'timeoutMs']), remove_legacy_axis_mapping: new Set(['action', 'name', 'axisName', 'key', 'scale', 'timeoutMs']), map_input_action: new Set(['action', 'contextPath', 'actionPath', 'key', 'timeoutMs']), set_input_trigger: new Set(['action', 'actionPath', 'triggerType', 'timeoutMs']), set_input_modifier: new Set(['action', 'contextPath', 'actionPath', 'key', 'modifierType', 'timeoutMs']), enable_input_mapping: new Set(['action', 'contextPath', 'priority', 'timeoutMs']), disable_input_action: new Set(['action', 'actionPath', 'timeoutMs']), get_input_info: new Set(['action', 'assetPath', 'timeoutMs']), }; /** Validate that no extraneous parameters are present */ function validateNoExtraParams(action: string, args: Record): { valid: boolean; error?: string } { const validParams = VALID_PARAMS_BY_ACTION[action]; if (!validParams) { return { valid: false, error: `manage_input/${action}: Unknown action` }; } const providedParams = Object.keys(args); const extraParams = providedParams.filter(p => !validParams.has(p)); if (extraParams.length > 0) { return { valid: false, error: `manage_input/${action}: Invalid parameters: ${extraParams.join(', ')}. Valid params: ${[...validParams].join(', ')}` }; } return { valid: true }; } /** Validate and sanitize path parameters */ function validateAndSanitizePaths(paths: Record, requiredKeys: string[] = []): { valid: boolean; sanitized: Record; error?: string } { const sanitized: Record = {}; const normalizedPaths = normalizePathFields(paths, Object.keys(paths)); for (const [key, value] of Object.entries(normalizedPaths)) { if (value === undefined || value === null) { // If this is a required key, missing value is an error if (requiredKeys.includes(key)) { return { valid: false, sanitized: {}, error: `${key} is required and must be provided` }; } continue; } if (typeof value !== 'string') { return { valid: false, sanitized: {}, error: `${key} must be a string` }; } if (value.length === 0) { // If this is a required key, empty string is an error if (requiredKeys.includes(key)) { return { valid: false, sanitized: {}, error: `${key} must be a non-empty string` }; } continue; } try { sanitized[key] = sanitizePath(value); } catch (e) { const message = e instanceof Error ? e.message : String(e); return { valid: false, sanitized: {}, error: `manage_input: Invalid ${key}: ${message}` }; } } return { valid: true, sanitized }; } /** Required path parameters for each action */ const REQUIRED_PATHS_BY_ACTION: Record = { create_input_action: ['path'], create_input_mapping_context: ['path'], add_mapping: ['contextPath', 'actionPath'], remove_mapping: ['contextPath', 'actionPath'], map_input_action: ['contextPath', 'actionPath'], set_input_trigger: ['actionPath'], set_input_modifier: ['actionPath'], enable_input_mapping: ['contextPath'], disable_input_action: ['actionPath'], get_input_info: ['assetPath'], }; export async function handleInputTools( action: string, args: HandlerArgs, tools: ITools ): Promise> { const argsRecord = args as Record; const timeoutMs = typeof argsRecord.timeoutMs === 'number' ? argsRecord.timeoutMs : getTimeoutMs(); // Validate no extraneous parameters const paramValidation = validateNoExtraParams(action, argsRecord); if (!paramValidation.valid) { return ResponseFactory.error(paramValidation.error || 'manage_input: Invalid parameters'); } // All actions are dispatched to C++ via automation bridge const sendRequest = async (subAction: string): Promise> => { // Validate and sanitize path parameters const requiredPaths = REQUIRED_PATHS_BY_ACTION[subAction] || []; const pathParams: Record = {}; for (const pathKey of requiredPaths) { if (argsRecord[pathKey] !== undefined) { pathParams[pathKey] = argsRecord[pathKey]; } } // Also check for optional path params (path, assetPath, contextPath, actionPath for non-required) if (argsRecord.path !== undefined) pathParams.path = argsRecord.path; if (argsRecord.assetPath !== undefined) pathParams.assetPath = argsRecord.assetPath; if (argsRecord.contextPath !== undefined) pathParams.contextPath = argsRecord.contextPath; if (argsRecord.actionPath !== undefined) pathParams.actionPath = argsRecord.actionPath; const pathValidation = validateAndSanitizePaths(pathParams, requiredPaths); if (!pathValidation.valid) { return ResponseFactory.error(`manage_input/${subAction}: ${pathValidation.error || 'Invalid path'}`); } // Build sanitized payload const sanitizedPayload: Record = { ...argsRecord, subAction }; // Apply sanitized paths back to payload for (const [key, value] of Object.entries(pathValidation.sanitized)) { sanitizedPayload[key] = value; } const result = await executeAutomationRequest( tools, 'manage_input', sanitizedPayload as HandlerArgs, `Automation bridge not available for input action: ${subAction}`, { timeoutMs } ); return cleanObject(result) as Record; }; switch (action) { case 'create_input_action': return sendRequest('create_input_action'); case 'create_input_mapping_context': return sendRequest('create_input_mapping_context'); case 'add_mapping': return sendRequest('add_mapping'); case 'remove_mapping': return sendRequest('remove_mapping'); case 'add_legacy_action_mapping': return sendRequest('add_legacy_action_mapping'); case 'remove_legacy_action_mapping': return sendRequest('remove_legacy_action_mapping'); case 'add_legacy_axis_mapping': return sendRequest('add_legacy_axis_mapping'); case 'remove_legacy_axis_mapping': return sendRequest('remove_legacy_axis_mapping'); // New actions - dispatched to C++ via automation bridge case 'map_input_action': return sendRequest('map_input_action'); case 'set_input_trigger': return sendRequest('set_input_trigger'); case 'set_input_modifier': return sendRequest('set_input_modifier'); case 'enable_input_mapping': return sendRequest('enable_input_mapping'); case 'disable_input_action': return sendRequest('disable_input_action'); case 'get_input_info': return sendRequest('get_input_info'); default: return ResponseFactory.error(`Unknown input action: ${action}`); } }