UNPKG

@kaia-team/n8n-nodes-kaia

Version:

n8n node to interact with Kaia API for signal capture and security gating

461 lines (460 loc) 20.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SaveSignal = void 0; const crypto_1 = require("crypto"); class SaveSignal { constructor() { this.description = { displayName: 'KAIA Save Signal', name: 'saveSignal', icon: 'file:kaia.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"]}}', description: 'Capture a signal for knowledge graph processing', defaults: { name: 'KAIA Save Signal', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'kaiaApi', required: false, }, ], properties: [ { displayName: 'Content', name: 'content', type: 'string', typeOptions: { rows: 4, }, default: '={{JSON.stringify($json)}}', required: true, description: 'Signal content', }, { displayName: 'Additional Fields', name: 'additionalFields', type: 'collection', placeholder: 'Add Field', default: {}, options: [ { displayName: 'Signal Type', name: 'signalType', type: 'options', options: [ { name: 'API Call', value: 'api_call', }, { name: 'Custom', value: 'custom', }, { name: 'Data Sync', value: 'data_sync', }, { name: 'Decision Made', value: 'decision_made', }, { name: 'Department Data', value: 'department_data', }, { name: 'Document Processed', value: 'document_processed', }, { name: 'Email Sent', value: 'email_sent', }, { name: 'LLM Input', value: 'llm_input', }, { name: 'LLM Output', value: 'llm_output', }, { name: 'Product Data', value: 'product_data', }, { name: 'User Data', value: 'user_data', }, { name: 'Workflow Execution', value: 'workflow_execution', }, ], default: 'custom', description: 'Type of signal being captured', }, { displayName: 'Title', name: 'title', type: 'string', default: '', description: 'Signal title', }, { displayName: 'User ID', name: 'userId', type: 'string', default: '', description: 'User identifier', }, { displayName: 'User Email', name: 'userEmail', type: 'string', default: '', description: 'User email address', }, { displayName: 'User Roles', name: 'userRoles', type: 'fixedCollection', typeOptions: { multipleValues: true, }, default: {}, description: 'Array of user roles', options: [ { displayName: 'Role', name: 'role', values: [ { displayName: 'Role', name: 'value', type: 'string', default: '', description: 'User role', }, ], }, ], }, { displayName: 'Department', name: 'department', type: 'options', options: [ { name: 'Engineering', value: 'Engineering', }, { name: 'General', value: 'General', }, { name: 'HR', value: 'HR', }, { name: 'Legal', value: 'Legal', }, { name: 'Sales', value: 'Sales', }, ], default: 'General', description: 'User department', }, { displayName: 'Workflow ID', name: 'workflowId', type: 'string', default: '={{$workflow.id}}', description: 'Workflow identifier', }, { displayName: 'Workflow Name', name: 'workflowName', type: 'string', default: '={{$workflow.name}}', description: 'Human-readable workflow name', }, { displayName: 'Execution ID', name: 'executionId', type: 'string', default: '={{$execution.id}}', description: 'Execution identifier', }, { displayName: 'Node ID', name: 'nodeId', type: 'string', default: '={{$prevNode.name}}-{{$prevNode.outputIndex}}-{{$prevNode.runIndex}}', description: 'Node identifier within the workflow', }, { displayName: 'Node Name', name: 'nodeName', type: 'string', default: '={{$prevNode.name}}', description: 'Human-readable node name', }, { displayName: 'Priority', name: 'priority', type: 'options', options: [ { name: 'Low', value: 'low', }, { name: 'Medium', value: 'medium', }, { name: 'High', value: 'high', }, { name: 'Critical', value: 'critical', }, ], default: 'medium', description: 'Signal priority level', }, { displayName: 'Context Level', name: 'context_level', type: 'options', options: [ { name: 'Raw', value: 'raw', }, { name: 'Baked', value: 'baked', }, { name: 'Cluster', value: 'cluster', }, ], default: 'raw', description: 'Context processing level', }, { displayName: 'Category', name: 'category', type: 'string', default: '', description: 'Signal category', }, { displayName: 'Tags', name: 'tags', type: 'string', default: '', description: 'Comma-separated tags, or use expressions for array', hint: 'Use expressions like =["tag1", "tag2"] for array format', }, { displayName: 'LLM Context', name: 'llmContext', type: 'json', default: '', description: 'LLM context information as JSON object', }, { displayName: 'Knowledge Graph', name: 'knowledge_graph', type: 'json', default: '', description: 'Knowledge graph structure as JSON object', }, ], }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; for (let i = 0; i < items.length; i++) { try { // Get credentials let credentials; let baseUrl = ''; let token = ''; try { credentials = (await this.getCredentials('kaiaApi')); baseUrl = credentials?.baseUrl || ''; token = credentials?.token || ''; } catch { // Credentials not configured - will throw error below } // Get parameters const content = this.getNodeParameter('content', i); const additionalFields = this.getNodeParameter('additionalFields', i); // Get optional fields from additionalFields const userId = additionalFields.userId; const userEmail = additionalFields.userEmail; const userRolesRaw = additionalFields.userRoles; const signalType = additionalFields.signalType; const title = additionalFields.title; const department = additionalFields.department; // Get workflow context from additionalFields or use defaults // Check if fields exist in additionalFields, otherwise use defaults const workflow = this.getWorkflow(); const currentNode = this.getNode(); // Get workflow context - use additionalFields if provided, otherwise use defaults const workflowId = additionalFields.workflowId || workflow.id?.toString() || ''; const workflowName = additionalFields.workflowName || workflow.name || ''; const executionId = additionalFields.executionId || this.getExecutionId() || ''; // For nodeId and nodeName, try to get from previous node if available const currentItem = items[i]; const prevNodeName = currentItem?.json?.name; const nodeId = additionalFields.nodeId || (prevNodeName ? `${prevNodeName}-${currentItem?.json?.outputIndex || ''}-${currentItem?.json?.runIndex || ''}` : currentNode.id || ''); const nodeName = additionalFields.nodeName || prevNodeName || currentNode.name || ''; // Auto-generate content hash const contentHash = (0, crypto_1.createHash)('sha256').update(content).digest('hex'); // Parse userRoles from fixedCollection let userRoles = []; if (userRolesRaw && typeof userRolesRaw === 'object') { // Handle fixedCollection format: { role: [{ value: 'role1' }, { value: 'role2' }] } if (userRolesRaw.role && Array.isArray(userRolesRaw.role)) { userRoles = userRolesRaw.role .map((item) => item.value) .filter((r) => r); } } else if (userRolesRaw && Array.isArray(userRolesRaw)) { // Handle case where userRoles is already an array userRoles = userRolesRaw.map((r) => String(r)).filter((r) => r); } // Build request body const body = { workflowId, workflowName, executionId, nodeId, nodeName, content, }; // Only include userId, userEmail, userRoles if they have values if (userId && userId.trim()) { body.userId = userId; } if (userEmail && userEmail.trim()) { body.userEmail = userEmail; } if (userRoles && userRoles.length > 0) { body.userRoles = userRoles; } // Only include signalType, title, and department if they are defined by the user // Server will set defaults if not provided if (signalType && signalType.trim()) { body.signalType = signalType; } if (title && title.trim()) { body.title = title; } if (department && department.trim()) { body.department = department; } // Always include contentHash (auto-generated) body.contentHash = contentHash; // Add additional fields if (additionalFields.priority) { body.priority = additionalFields.priority; } if (additionalFields.context_level) { body.context_level = additionalFields.context_level; } if (additionalFields.category) { body.category = additionalFields.category; } if (additionalFields.tags) { let tags = []; if (typeof additionalFields.tags === 'string') { try { tags = JSON.parse(additionalFields.tags); } catch { tags = additionalFields.tags.split(',').map((t) => t.trim()).filter((t) => t); } } else if (Array.isArray(additionalFields.tags)) { tags = additionalFields.tags.map((tag) => String(tag)); } if (tags.length > 0) { body.tags = tags; } } if (additionalFields.llmContext) { try { const llmContext = typeof additionalFields.llmContext === 'string' ? JSON.parse(additionalFields.llmContext) : additionalFields.llmContext; body.llmContext = llmContext; } catch (error) { throw new Error(`Invalid LLM Context JSON: ${error}`); } } if (additionalFields.knowledge_graph) { try { const knowledgeGraph = typeof additionalFields.knowledge_graph === 'string' ? JSON.parse(additionalFields.knowledge_graph) : additionalFields.knowledge_graph; body.knowledge_graph = knowledgeGraph; } catch (error) { throw new Error(`Invalid Knowledge Graph JSON: ${error}`); } } // Make API request if (!baseUrl || !token) { throw new Error('Kaia API credentials are required. Please configure the credentials.'); } const requestUrl = `${baseUrl}/api/signals/remember`; const options = { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, method: 'POST', body, url: requestUrl, json: true, }; const response = await this.helpers.httpRequest(options); returnData.push(response); } catch (error) { if (this.continueOnFail()) { const errorMessage = error instanceof Error ? error.message : String(error); returnData.push({ error: errorMessage }); continue; } throw error; } } return [this.helpers.returnJsonArray(returnData)]; } } exports.SaveSignal = SaveSignal;