UNPKG

@net3/queuer

Version:

433 lines (432 loc) 21.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createUpdateQueue = void 0; const pieces_framework_1 = require("@activepieces/pieces-framework"); const queue_manager_1 = require("../common/queue-manager"); const mcp_manager_1 = require("../common/mcp-manager"); /** * Action: Create/Update Queue * Builds or updates a queue configuration and returns current status. * Now uses MCP tools instead of regular pieces. */ exports.createUpdateQueue = (0, pieces_framework_1.createAction)({ name: 'create_update_queue', displayName: 'Create/Update Queue', description: 'Create a new queue or update existing queue configuration for MCP tools', props: { /* -------------------- MCP Configuration -------------------- */ targetMCPTool: pieces_framework_1.Property.Dropdown({ displayName: 'MCP Tool', description: 'The MCP tool that all items in this queue will use', required: true, refreshers: [], options: async ({ auth }) => { const authConfig = auth; if (!authConfig?.mcpServerUrl) { return { options: [], placeholder: 'MCP Server URL required in auth configuration' }; } try { const config = { mcpServerUrl: authConfig.mcpServerUrl }; const tools = await mcp_manager_1.MCPManager.listMCPTools(config); if (!tools || tools.length === 0) { return { options: [], placeholder: 'No MCP tools available from server' }; } return { options: tools.map((tool) => ({ label: `${tool.name} - ${tool.description || 'No description'}`, value: tool.name, })) }; } catch (error) { console.error('Failed to fetch MCP tools:', error); return { options: [], disabled: true, placeholder: `Error: ${error.message || 'Failed to connect to MCP server'}` }; } }, }), queueSettings: pieces_framework_1.Property.DynamicProperties({ displayName: 'Queue Settings', description: 'Configure queue settings (auto-loads existing settings if queue exists)', required: false, refreshers: ['targetMCPTool'], props: async ({ targetMCPTool, auth }, context) => { // Default values for new queue const defaults = { delayType: 'fixed', delayUnit: 'seconds', delayValue: 30, delayMin: 20, delayMax: 60, dailyLimit: 100, hourlyLimit: 20, activeHours: { timezone: 'America/New_York', schedule: { monday: { enabled: true, start: '09:00', end: '17:00' }, tuesday: { enabled: true, start: '09:00', end: '17:00' }, wednesday: { enabled: true, start: '09:00', end: '17:00' }, thursday: { enabled: true, start: '09:00', end: '17:00' }, friday: { enabled: true, start: '09:00', end: '17:00' }, saturday: { enabled: false }, sunday: { enabled: false }, }, } }; if (!targetMCPTool) { return { instructions: pieces_framework_1.Property.MarkDown({ value: `**Select an MCP tool above to configure queue settings.**` }), delayType: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Type', description: 'Select an MCP tool first', required: true, defaultValue: 'fixed', options: { options: [{ label: 'Select Tool First', value: 'fixed' }] }, }), delayUnit: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Unit', description: 'Select an MCP tool first', required: true, defaultValue: 'seconds', options: { options: [{ label: 'Select Tool First', value: 'seconds' }] }, }), delayValue: pieces_framework_1.Property.Number({ displayName: 'Delay Value', description: 'Select an MCP tool first', required: false, defaultValue: 0, }), delayMin: pieces_framework_1.Property.Number({ displayName: 'Minimum Delay', description: 'Select an MCP tool first', required: false, defaultValue: 0, }), delayMax: pieces_framework_1.Property.Number({ displayName: 'Maximum Delay', description: 'Select an MCP tool first', required: false, defaultValue: 0, }), dailyLimit: pieces_framework_1.Property.Number({ displayName: 'Daily Limit', description: 'Select an MCP tool first', required: false, defaultValue: 0, }), hourlyLimit: pieces_framework_1.Property.Number({ displayName: 'Hourly Limit', description: 'Select an MCP tool first', required: false, defaultValue: 0, }), activeHours: pieces_framework_1.Property.Object({ displayName: 'Active Hours', description: 'Select an MCP tool first', required: false, defaultValue: {}, }), }; } const authConfig = auth; if (!authConfig?.mcpServerUrl) { return { instructions: pieces_framework_1.Property.MarkDown({ value: `**Error:** MCP Server URL required in auth configuration.` }), delayType: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Type', description: 'Configure auth first', required: true, defaultValue: 'fixed', options: { options: [{ label: 'Configure Auth First', value: 'fixed' }] }, }), delayUnit: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Unit', description: 'Configure auth first', required: true, defaultValue: 'seconds', options: { options: [{ label: 'Configure Auth First', value: 'seconds' }] }, }), delayValue: pieces_framework_1.Property.Number({ displayName: 'Delay Value', description: 'Configure auth first', required: false, defaultValue: 0, }), delayMin: pieces_framework_1.Property.Number({ displayName: 'Minimum Delay', description: 'Configure auth first', required: false, defaultValue: 0, }), delayMax: pieces_framework_1.Property.Number({ displayName: 'Maximum Delay', description: 'Configure auth first', required: false, defaultValue: 0, }), dailyLimit: pieces_framework_1.Property.Number({ displayName: 'Daily Limit', description: 'Configure auth first', required: false, defaultValue: 0, }), hourlyLimit: pieces_framework_1.Property.Number({ displayName: 'Hourly Limit', description: 'Configure auth first', required: false, defaultValue: 0, }), activeHours: pieces_framework_1.Property.Object({ displayName: 'Active Hours', description: 'Configure auth first', required: false, defaultValue: {}, }), }; } // PropertyContext doesn't have store access - context.store is undefined const queueId = `mcp_${targetMCPTool}_queue`; return { instructions: pieces_framework_1.Property.MarkDown({ value: `**⚙️ Configure queue:** ${queueId}\n\n*Note: Form shows defaults due to ActivePieces PropertyContext limitations. If this queue exists, your current settings will be preserved when you submit.*` }), delayType: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Type', description: 'Type of delay between queue items', required: true, defaultValue: defaults.delayType, options: { options: [ { label: 'Fixed Delay', value: 'fixed' }, { label: 'Random Delay', value: 'random' }, ], }, }), delayUnit: pieces_framework_1.Property.StaticDropdown({ displayName: 'Delay Unit', description: 'Time unit for delays', required: true, defaultValue: defaults.delayUnit, options: { options: [ { label: 'Seconds', value: 'seconds' }, { label: 'Minutes', value: 'minutes' }, { label: 'Hours', value: 'hours' }, { label: 'Days', value: 'days' }, ], }, }), delayValue: pieces_framework_1.Property.Number({ displayName: 'Delay Value', description: 'Fixed delay amount between items', required: false, defaultValue: defaults.delayValue, }), delayMin: pieces_framework_1.Property.Number({ displayName: 'Minimum Delay', description: 'Minimum delay for random delays', required: false, defaultValue: defaults.delayMin, }), delayMax: pieces_framework_1.Property.Number({ displayName: 'Maximum Delay', description: 'Maximum delay for random delays', required: false, defaultValue: defaults.delayMax, }), dailyLimit: pieces_framework_1.Property.Number({ displayName: 'Daily Limit', description: 'Maximum items to process per day (0 = unlimited)', required: false, defaultValue: defaults.dailyLimit, }), hourlyLimit: pieces_framework_1.Property.Number({ displayName: 'Hourly Limit', description: 'Maximum items to process per hour (0 = unlimited)', required: false, defaultValue: defaults.hourlyLimit, }), activeHours: pieces_framework_1.Property.Object({ displayName: 'Active Hours', description: 'Only process queue during these hours (leave empty for 24×7)', required: false, defaultValue: defaults.activeHours, }), }; }, }), }, async run(context) { try { const { targetMCPTool, delayType, delayUnit, delayValue, delayMin, delayMax, dailyLimit, hourlyLimit, activeHours, } = context.propsValue; const authConfig = context.auth; const mcpServerUrl = authConfig.mcpServerUrl; /* -------- Validation -------- */ // Basic required fields if (!mcpServerUrl) { throw new Error('MCP Server URL is required in auth configuration'); } if (!targetMCPTool) { throw new Error('MCP Tool selection is required'); } // Delay validation if (delayType === 'fixed') { if (!delayValue || delayValue <= 0) { throw new Error(`Delay value must be > 0 for fixed delays. Got: ${delayValue}`); } } else if (delayType === 'random') { if (!delayMin || !delayMax || delayMin <= 0 || delayMax <= 0) { throw new Error(`Min/Max delays must be > 0 for random delays. Got min: ${delayMin}, max: ${delayMax}`); } if (delayMin >= delayMax) { throw new Error(`Minimum delay (${delayMin}) must be less than maximum delay (${delayMax})`); } } // Limits validation if (dailyLimit && dailyLimit < 0) { throw new Error(`Daily limit must be >= 0. Got: ${dailyLimit}`); } if (hourlyLimit && hourlyLimit < 0) { throw new Error(`Hourly limit must be >= 0. Got: ${hourlyLimit}`); } // Active hours validation if (activeHours) { const value = activeHours; if (!value || typeof value !== 'object') { throw new Error('Active hours must be an object'); } if (!value.timezone || typeof value.timezone !== 'string' || !value.timezone.includes('/')) { throw new Error(`timezone must be a valid IANA name, e.g., America/Toronto. Got: ${value.timezone}`); } // Handle schedule as either JSON string or object let schedule = value.schedule; if (typeof schedule === 'string') { try { schedule = JSON.parse(schedule); } catch (parseError) { throw new Error(`Schedule JSON string is invalid: ${parseError.message}`); } } if (!schedule || typeof schedule !== 'object') { throw new Error(`Active hours schedule must be an object or valid JSON string. Got type: ${typeof value.schedule}`); } const days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; const timeRegex = /^([01]\d|2[0-3]):[0-5]\d$/; // More precise regex: 00:00 to 23:59 for (const d of days) { const entry = schedule[d]; if (!entry) { throw new Error(`Missing schedule for ${d}`); } if (typeof entry.enabled !== 'boolean') { throw new Error(`${d}.enabled must be boolean. Got: ${typeof entry.enabled} (${entry.enabled})`); } if (entry.enabled) { if (!entry.start || !timeRegex.test(entry.start)) { throw new Error(`${d} start time must be HH:MM format (00:00-23:59). Got: "${entry.start}"`); } if (!entry.end || !timeRegex.test(entry.end)) { throw new Error(`${d} end time must be HH:MM format (00:00-23:59). Got: "${entry.end}"`); } // Convert to minutes for proper comparison const startMinutes = parseInt(entry.start.split(':')[0]) * 60 + parseInt(entry.start.split(':')[1]); const endMinutes = parseInt(entry.end.split(':')[0]) * 60 + parseInt(entry.end.split(':')[1]); if (startMinutes >= endMinutes) { throw new Error(`${d} start time (${entry.start}) must be before end time (${entry.end})`); } } } // Update the activeHours with parsed schedule for storage if (typeof value.schedule === 'string') { value.schedule = schedule; } } /* -------- Build / Update configuration -------- */ const queueId = `mcp_${targetMCPTool}_queue`; // Try to get existing configuration let existing = null; try { existing = await queue_manager_1.QueueManager.getQueueConfiguration(context, queueId); } catch (error) { // Queue doesn't exist (normal for new queues) existing = null; } const now = Date.now(); const queueConfig = { id: queueId, mcpToolName: targetMCPTool, delayType: delayType, delayUnit: delayUnit, delayValue, delayMin, delayMax, dailyLimit: dailyLimit || 0, hourlyLimit: hourlyLimit || 0, activeHours: activeHours || undefined, createdAt: existing?.createdAt || now, updatedAt: now, lastUsed: existing?.lastUsed, totalProcessed: existing?.totalProcessed || 0, }; // Save queue configuration try { await context.store.put(`queue_config_${queueId}`, queueConfig, pieces_framework_1.StoreScope.PROJECT); } catch (storeError) { throw new Error(`Failed to save queue configuration: ${storeError.message}`); } // Create initial queue state if this is a new queue if (!existing) { const initState = { queueId, lastReleaseTime: 0, itemCount: 0, currentExecutingItem: null, lastExecutedTime: 0, version: 0, }; try { await context.store.put(`queue_state_${queueId}`, initState, pieces_framework_1.StoreScope.PROJECT); } catch (storeError) { throw new Error(`Failed to create initial queue state: ${storeError.message}`); } } // Get queue status let status; try { status = await queue_manager_1.QueueManager.getQueueStatus(context, queueId); } catch (statusError) { throw new Error(`Failed to get queue status: ${statusError.message}`); } return { success: true, queueId, queueLabel: `MCP: ${targetMCPTool}`, toolName: targetMCPTool, created: !existing, updated: !!existing, status, }; } catch (error) { throw new Error(`Queue creation failed: ${error.message}`); } }, });