n8n-nodes-cribops 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,568 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Cribops = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const CribopsHttp_1 = require("../../utils/CribopsHttp");
6
+ class Cribops {
7
+ description = {
8
+ displayName: 'Cribops',
9
+ name: 'cribops',
10
+ icon: 'file:cribops.svg',
11
+ group: ['communication'],
12
+ version: 1,
13
+ subtitle: '={{$parameter["operation"]}}',
14
+ description: 'Interact with Cribops AI agents',
15
+ defaults: {
16
+ name: 'Cribops',
17
+ },
18
+ inputs: ["main" /* NodeConnectionType.Main */],
19
+ outputs: ["main" /* NodeConnectionType.Main */],
20
+ credentials: [
21
+ {
22
+ name: 'cribopsApi',
23
+ required: true,
24
+ },
25
+ ],
26
+ requestDefaults: {
27
+ headers: {
28
+ Accept: 'application/json',
29
+ 'Content-Type': 'application/json',
30
+ },
31
+ },
32
+ properties: [
33
+ {
34
+ displayName: 'Operation',
35
+ name: 'operation',
36
+ type: 'options',
37
+ noDataExpression: true,
38
+ options: [
39
+ {
40
+ name: 'Get Agent',
41
+ value: 'getAgent',
42
+ description: 'Get information about a specific agent',
43
+ action: 'Get information about a specific agent',
44
+ },
45
+ {
46
+ name: 'List Agents',
47
+ value: 'listAgents',
48
+ description: 'List all available agents',
49
+ action: 'List all available agents',
50
+ },
51
+ {
52
+ name: 'Reply to Conversation',
53
+ value: 'replyToConversation',
54
+ description: 'Reply to an existing conversation',
55
+ action: 'Reply to an existing conversation',
56
+ },
57
+ {
58
+ name: 'Send Message',
59
+ value: 'sendMessage',
60
+ description: 'Send a message to a Cribops agent',
61
+ action: 'Send a message to a cribops agent',
62
+ },
63
+ {
64
+ name: 'Send Typing Indicator',
65
+ value: 'sendTypingIndicator',
66
+ description: 'Send typing indicator to a conversation',
67
+ action: 'Send typing indicator to a conversation',
68
+ },
69
+ ],
70
+ default: 'sendMessage',
71
+ },
72
+ {
73
+ displayName: 'Agent',
74
+ name: 'agentId',
75
+ type: 'resourceLocator',
76
+ default: { mode: 'list', value: '' },
77
+ required: true,
78
+ displayOptions: {
79
+ show: {
80
+ operation: ['sendMessage', 'replyToConversation', 'getAgent', 'sendTypingIndicator'],
81
+ },
82
+ },
83
+ modes: [
84
+ {
85
+ displayName: 'From List',
86
+ name: 'list',
87
+ type: 'list',
88
+ placeholder: 'Select an agent...',
89
+ typeOptions: {
90
+ searchListMethod: 'searchAgents',
91
+ searchable: true,
92
+ },
93
+ },
94
+ {
95
+ displayName: 'By ID',
96
+ name: 'id',
97
+ type: 'string',
98
+ placeholder: 'agent_123',
99
+ validation: [
100
+ {
101
+ type: 'regex',
102
+ properties: {
103
+ regex: '^[a-zA-Z0-9_-]+$',
104
+ errorMessage: 'Agent ID must contain only letters, numbers, hyphens, and underscores',
105
+ },
106
+ },
107
+ ],
108
+ },
109
+ ],
110
+ },
111
+ {
112
+ displayName: 'Conversation ID',
113
+ name: 'conversationId',
114
+ type: 'string',
115
+ required: true,
116
+ default: '',
117
+ placeholder: 'conversation_123',
118
+ displayOptions: {
119
+ show: {
120
+ operation: ['replyToConversation'],
121
+ },
122
+ },
123
+ description: 'ID of the conversation to reply to',
124
+ },
125
+ {
126
+ displayName: 'Conversation ID',
127
+ name: 'conversationId',
128
+ type: 'string',
129
+ required: true,
130
+ default: '',
131
+ placeholder: 'conversation_123',
132
+ displayOptions: {
133
+ show: {
134
+ operation: ['sendTypingIndicator'],
135
+ },
136
+ },
137
+ description: 'ID of the conversation to send typing indicator to',
138
+ },
139
+ {
140
+ displayName: 'Typing',
141
+ name: 'typing',
142
+ type: 'boolean',
143
+ required: true,
144
+ default: true,
145
+ displayOptions: {
146
+ show: {
147
+ operation: ['sendTypingIndicator'],
148
+ },
149
+ },
150
+ description: 'Whether to show typing indicator (true) or stop typing (false)',
151
+ },
152
+ {
153
+ displayName: 'Message',
154
+ name: 'message',
155
+ type: 'string',
156
+ required: true,
157
+ default: '',
158
+ placeholder: 'Hello, how can you help me?',
159
+ displayOptions: {
160
+ show: {
161
+ operation: ['sendMessage', 'replyToConversation'],
162
+ },
163
+ },
164
+ description: 'The message to send',
165
+ },
166
+ {
167
+ displayName: 'Conversation ID',
168
+ name: 'conversationId',
169
+ type: 'string',
170
+ default: '',
171
+ placeholder: 'conversation_123',
172
+ displayOptions: {
173
+ show: {
174
+ operation: ['sendMessage'],
175
+ },
176
+ },
177
+ description: 'ID of the conversation (leave empty to start a new conversation)',
178
+ },
179
+ {
180
+ displayName: 'User ID',
181
+ name: 'userId',
182
+ type: 'string',
183
+ default: '',
184
+ placeholder: 'user_123',
185
+ displayOptions: {
186
+ show: {
187
+ operation: ['sendMessage'],
188
+ },
189
+ },
190
+ description: 'ID of the user sending the message',
191
+ },
192
+ {
193
+ displayName: 'File Attachment',
194
+ name: 'fileAttachment',
195
+ type: 'fixedCollection',
196
+ default: {},
197
+ placeholder: 'Add file attachment',
198
+ displayOptions: {
199
+ show: {
200
+ operation: ['sendMessage'],
201
+ },
202
+ },
203
+ typeOptions: {
204
+ multipleValues: false,
205
+ },
206
+ options: [
207
+ {
208
+ name: 'file',
209
+ displayName: 'File',
210
+ values: [
211
+ {
212
+ displayName: 'File URL',
213
+ name: 'url',
214
+ type: 'string',
215
+ default: '',
216
+ placeholder: 'https://example.com/file.pdf',
217
+ description: 'URL of the file to attach',
218
+ },
219
+ {
220
+ displayName: 'File Name',
221
+ name: 'name',
222
+ type: 'string',
223
+ default: '',
224
+ placeholder: 'document.pdf',
225
+ description: 'Name of the file (optional)',
226
+ },
227
+ {
228
+ displayName: 'File Type',
229
+ name: 'type',
230
+ type: 'string',
231
+ default: '',
232
+ placeholder: 'application/pdf',
233
+ description: 'MIME type of the file (optional)',
234
+ },
235
+ ],
236
+ },
237
+ ],
238
+ },
239
+ {
240
+ displayName: 'Additional Fields',
241
+ name: 'additionalFields',
242
+ type: 'collection',
243
+ placeholder: 'Add Field',
244
+ default: {},
245
+ options: [
246
+ {
247
+ displayName: 'Metadata',
248
+ name: 'metadata',
249
+ type: 'fixedCollection',
250
+ placeholder: 'Add Metadata',
251
+ default: {},
252
+ typeOptions: {
253
+ multipleValues: true,
254
+ },
255
+ options: [
256
+ {
257
+ name: 'metadataValues',
258
+ displayName: 'Metadata',
259
+ values: [
260
+ {
261
+ displayName: 'Key',
262
+ name: 'key',
263
+ type: 'string',
264
+ default: '',
265
+ description: 'Metadata key',
266
+ },
267
+ {
268
+ displayName: 'Value',
269
+ name: 'value',
270
+ type: 'string',
271
+ default: '',
272
+ description: 'Metadata value',
273
+ },
274
+ ],
275
+ },
276
+ ],
277
+ },
278
+ {
279
+ displayName: 'Timeout',
280
+ name: 'timeout',
281
+ type: 'number',
282
+ default: 30000,
283
+ description: 'Request timeout in milliseconds',
284
+ },
285
+ ],
286
+ },
287
+ ],
288
+ };
289
+ methods = {
290
+ listSearch: {
291
+ searchAgents: async function (filter) {
292
+ const credentials = await this.getCredentials('cribopsApi');
293
+ const cribopsHttp = new CribopsHttp_1.CribopsHttp({
294
+ baseUrl: credentials.baseUrl,
295
+ apiToken: credentials.apiToken,
296
+ });
297
+ try {
298
+ const agents = await cribopsHttp.getAgents();
299
+ const results = agents
300
+ .filter((agent) => !filter || agent.name.toLowerCase().includes(filter.toLowerCase()))
301
+ .map((agent) => ({
302
+ name: `${agent.name} (${agent.id})`,
303
+ value: agent.id,
304
+ }));
305
+ return {
306
+ results,
307
+ };
308
+ }
309
+ catch (error) {
310
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to load agents: ${error}`, {
311
+ description: 'Make sure your API credentials are correct and the Cribops API is accessible',
312
+ });
313
+ }
314
+ },
315
+ },
316
+ };
317
+ async execute() {
318
+ const items = this.getInputData();
319
+ const results = [];
320
+ const credentials = await this.getCredentials('cribopsApi');
321
+ const cribopsHttp = new CribopsHttp_1.CribopsHttp({
322
+ baseUrl: credentials.baseUrl,
323
+ apiToken: credentials.apiToken,
324
+ });
325
+ for (let i = 0; i < items.length; i++) {
326
+ const operation = this.getNodeParameter('operation', i);
327
+ try {
328
+ let responseData;
329
+ switch (operation) {
330
+ case 'replyToConversation':
331
+ responseData = await replyToConversation(this, cribopsHttp, i);
332
+ break;
333
+ case 'sendMessage':
334
+ responseData = await sendMessage(this, cribopsHttp, i);
335
+ break;
336
+ case 'sendTypingIndicator':
337
+ responseData = await sendTypingIndicator(this, cribopsHttp, i);
338
+ break;
339
+ case 'getAgent':
340
+ responseData = await getAgent(this, cribopsHttp, i);
341
+ break;
342
+ case 'listAgents':
343
+ responseData = await listAgents(this, cribopsHttp, i);
344
+ break;
345
+ default:
346
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`, { itemIndex: i });
347
+ }
348
+ results.push({
349
+ json: responseData,
350
+ pairedItem: { item: i },
351
+ });
352
+ }
353
+ catch (error) {
354
+ if (this.continueOnFail()) {
355
+ results.push({
356
+ json: {
357
+ error: error.message,
358
+ },
359
+ pairedItem: { item: i },
360
+ });
361
+ }
362
+ else {
363
+ throw error;
364
+ }
365
+ }
366
+ }
367
+ return [results];
368
+ }
369
+ }
370
+ exports.Cribops = Cribops;
371
+ // Simple UUID v4 generator
372
+ function generateUUID() {
373
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
374
+ const r = Math.random() * 16 | 0;
375
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
376
+ return v.toString(16);
377
+ });
378
+ }
379
+ async function replyToConversation(executeFunctions, cribopsHttp, itemIndex) {
380
+ const agentId = executeFunctions.getNodeParameter('agentId', itemIndex, '', { extractValue: true });
381
+ const conversationId = executeFunctions.getNodeParameter('conversationId', itemIndex);
382
+ const message = executeFunctions.getNodeParameter('message', itemIndex);
383
+ // Get the response webhook URL from the input data
384
+ const inputData = executeFunctions.getInputData()[itemIndex];
385
+ let responseWebhook = inputData.json.response_webhook;
386
+ // If not found directly, check if it's in the original trigger data (passed through by typing indicator)
387
+ if (!responseWebhook && inputData.json._originalTriggerData) {
388
+ const originalTriggerData = inputData.json._originalTriggerData;
389
+ responseWebhook = originalTriggerData.response_webhook;
390
+ }
391
+ // If still not found, try to find it in the workflow execution data from the CribopsTrigger node
392
+ if (!responseWebhook) {
393
+ try {
394
+ const workflowData = executeFunctions.getWorkflowDataProxy(itemIndex);
395
+ const cribopsTriggerData = workflowData.$('Cribops Trigger');
396
+ if (cribopsTriggerData && cribopsTriggerData.item && cribopsTriggerData.item.json) {
397
+ responseWebhook = cribopsTriggerData.item.json.response_webhook;
398
+ }
399
+ }
400
+ catch (error) {
401
+ // Ignore errors if Cribops Trigger node is not found
402
+ }
403
+ }
404
+ // Log the raw values to debug expression evaluation
405
+ console.log('Debug - agentId:', agentId);
406
+ console.log('Debug - conversationId:', conversationId);
407
+ console.log('Debug - message:', message);
408
+ console.log('Debug - responseWebhook:', responseWebhook);
409
+ // Check if expressions were not evaluated (contain literal expression syntax)
410
+ if (conversationId.includes('{{') || conversationId.includes('}}')) {
411
+ throw new n8n_workflow_1.NodeOperationError(executeFunctions.getNode(), `Conversation ID contains unevaluated expression: ${conversationId}. Please ensure the expression is properly formatted.`, { itemIndex });
412
+ }
413
+ if (!conversationId || conversationId.trim() === '') {
414
+ throw new n8n_workflow_1.NodeOperationError(executeFunctions.getNode(), 'Conversation ID is required but was empty', { itemIndex });
415
+ }
416
+ if (!responseWebhook) {
417
+ console.log('Warning: response_webhook not found in input data:', JSON.stringify(inputData.json, null, 2));
418
+ // Fallback: try to use the agent webhook endpoint
419
+ console.log('Falling back to agent webhook endpoint');
420
+ const fallbackMessageData = {
421
+ id: generateUUID(),
422
+ content: message,
423
+ conversationId: conversationId,
424
+ agentId: agentId,
425
+ type: 'agent_response',
426
+ timestamp: new Date().toISOString(),
427
+ };
428
+ return await cribopsHttp.sendMessage(agentId, fallbackMessageData);
429
+ }
430
+ // Try to get user_id and organization_id from trigger data
431
+ let userId = inputData.json.user_id || '';
432
+ let organizationId = inputData.json.organization_id || '';
433
+ // If not found, try to get from workflow data
434
+ if ((!userId || !organizationId) && responseWebhook) {
435
+ try {
436
+ const workflowData = executeFunctions.getWorkflowDataProxy(itemIndex);
437
+ const cribopsTriggerData = workflowData.$('Cribops Trigger');
438
+ if (cribopsTriggerData && cribopsTriggerData.item && cribopsTriggerData.item.json) {
439
+ userId = userId || cribopsTriggerData.item.json.user_id || '';
440
+ organizationId = organizationId || cribopsTriggerData.item.json.organization_id || '';
441
+ }
442
+ }
443
+ catch (error) {
444
+ // Ignore errors
445
+ }
446
+ }
447
+ const messageData = {
448
+ conversation_id: conversationId,
449
+ content: message,
450
+ message_id: generateUUID(),
451
+ timestamp: new Date().toISOString(),
452
+ // Include additional fields that might be needed
453
+ user_id: userId,
454
+ organization_id: organizationId,
455
+ };
456
+ try {
457
+ console.log('Debug - Response webhook URL:', responseWebhook);
458
+ console.log('Debug - Request body:', JSON.stringify(messageData, null, 2));
459
+ // Send to the response webhook URL
460
+ // Note: The standard workflow uses form parameters, not JSON
461
+ const formData = new URLSearchParams();
462
+ Object.keys(messageData).forEach(key => {
463
+ formData.append(key, String(messageData[key]));
464
+ });
465
+ const requestOptions = {
466
+ method: 'POST',
467
+ url: responseWebhook,
468
+ headers: {
469
+ 'Authorization': `Bearer ${cribopsHttp['config'].apiToken}`,
470
+ 'Content-Type': 'application/x-www-form-urlencoded',
471
+ },
472
+ body: formData.toString(),
473
+ json: false,
474
+ };
475
+ const response = await executeFunctions.helpers.httpRequest(requestOptions);
476
+ return response;
477
+ }
478
+ catch (error) {
479
+ // Enhanced error logging for 422 errors
480
+ if (error.response?.status === 422) {
481
+ console.error('422 Error Details:');
482
+ console.error('Response body:', JSON.stringify(error.response.body, null, 2));
483
+ console.error('Request that failed:', {
484
+ agentId: agentId,
485
+ body: messageData,
486
+ conversationId: conversationId
487
+ });
488
+ const errorDetails = error.response.body?.errors || error.response.body?.message || 'Unknown validation error';
489
+ throw new n8n_workflow_1.NodeOperationError(executeFunctions.getNode(), `Validation error (422): ${JSON.stringify(errorDetails)}. ConversationId: "${conversationId}"`, { itemIndex, description: `Full error: ${JSON.stringify(error.response.body)}` });
490
+ }
491
+ throw new n8n_workflow_1.NodeOperationError(executeFunctions.getNode(), `Failed to reply to conversation: ${error instanceof Error ? error.message : String(error)}`, { itemIndex });
492
+ }
493
+ }
494
+ async function sendMessage(executeFunctions, cribopsHttp, itemIndex) {
495
+ const agentId = executeFunctions.getNodeParameter('agentId', itemIndex, '', { extractValue: true });
496
+ const message = executeFunctions.getNodeParameter('message', itemIndex);
497
+ const conversationId = executeFunctions.getNodeParameter('conversationId', itemIndex, '');
498
+ const userId = executeFunctions.getNodeParameter('userId', itemIndex, '');
499
+ const fileAttachment = executeFunctions.getNodeParameter('fileAttachment', itemIndex, {});
500
+ const additionalFields = executeFunctions.getNodeParameter('additionalFields', itemIndex, {});
501
+ const messageData = {
502
+ id: generateUUID(),
503
+ content: message,
504
+ conversationId: conversationId || `conversation_${Date.now()}`,
505
+ userId: userId || undefined,
506
+ type: 'user_message',
507
+ timestamp: new Date().toISOString(),
508
+ };
509
+ // Add file attachment if provided
510
+ if (fileAttachment.file) {
511
+ const file = fileAttachment.file;
512
+ if (file.url) {
513
+ messageData.fileUrl = file.url;
514
+ messageData.fileName = file.name || 'file';
515
+ messageData.fileType = file.type || 'application/octet-stream';
516
+ }
517
+ }
518
+ // Add metadata if provided
519
+ if (additionalFields.metadata) {
520
+ const metadata = additionalFields.metadata;
521
+ const metadataValues = metadata.metadataValues;
522
+ if (metadataValues && metadataValues.length > 0) {
523
+ messageData.metadata = {};
524
+ metadataValues.forEach((item) => {
525
+ messageData.metadata[item.key] = item.value;
526
+ });
527
+ }
528
+ }
529
+ return await cribopsHttp.sendMessage(agentId, messageData);
530
+ }
531
+ async function getAgent(executeFunctions, cribopsHttp, itemIndex) {
532
+ const agentId = executeFunctions.getNodeParameter('agentId', itemIndex, '', { extractValue: true });
533
+ return await cribopsHttp.getAgent(agentId);
534
+ }
535
+ async function listAgents(executeFunctions, cribopsHttp, itemIndex) {
536
+ const agents = await cribopsHttp.getAgents();
537
+ return { agents, count: agents.length };
538
+ }
539
+ async function sendTypingIndicator(executeFunctions, cribopsHttp, itemIndex) {
540
+ const agentId = executeFunctions.getNodeParameter('agentId', itemIndex, '', { extractValue: true });
541
+ const conversationId = executeFunctions.getNodeParameter('conversationId', itemIndex);
542
+ const typing = executeFunctions.getNodeParameter('typing', itemIndex);
543
+ // Check if we have a response_webhook in the input data to extract the correct base URL
544
+ const inputData = executeFunctions.getInputData()[itemIndex];
545
+ const responseWebhook = inputData.json.response_webhook;
546
+ let typingResult;
547
+ if (responseWebhook) {
548
+ // Extract base URL from response webhook
549
+ const webhookUrl = new URL(responseWebhook);
550
+ const baseUrl = `${webhookUrl.protocol}//${webhookUrl.host}`;
551
+ // Create a temporary HTTP client with the correct base URL
552
+ const dynamicCribopsHttp = new CribopsHttp_1.CribopsHttp({
553
+ baseUrl: baseUrl,
554
+ apiToken: cribopsHttp['config'].apiToken,
555
+ });
556
+ typingResult = await dynamicCribopsHttp.sendTypingIndicator(agentId, conversationId, typing);
557
+ }
558
+ else {
559
+ // Fallback to the configured base URL
560
+ typingResult = await cribopsHttp.sendTypingIndicator(agentId, conversationId, typing);
561
+ }
562
+ // Preserve the original trigger data for subsequent nodes
563
+ return {
564
+ ...typingResult,
565
+ // Pass through the original trigger data so replyToConversation can access it
566
+ _originalTriggerData: inputData.json
567
+ };
568
+ }
@@ -0,0 +1,7 @@
1
+ <svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="60" height="60" rx="12" fill="#4F46E5"/>
3
+ <path d="M15 20C15 17.7909 16.7909 16 19 16H41C43.2091 16 45 17.7909 45 20V32C45 34.2091 43.2091 36 41 36H26L18 42V36H19C16.7909 36 15 34.2091 15 32V20Z" fill="white"/>
4
+ <circle cx="24" cy="26" r="2" fill="#4F46E5"/>
5
+ <circle cx="30" cy="26" r="2" fill="#4F46E5"/>
6
+ <circle cx="36" cy="26" r="2" fill="#4F46E5"/>
7
+ </svg>
@@ -0,0 +1,18 @@
1
+ import { INodeType, INodeTypeDescription, ITriggerFunctions, ITriggerResponse, IWebhookFunctions, IWebhookResponseData, IHookFunctions, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
2
+ export declare class CribopsTrigger implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getAgents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ };
8
+ };
9
+ webhookMethods: {
10
+ default: {
11
+ checkExists(this: IHookFunctions): Promise<boolean>;
12
+ create(this: IHookFunctions): Promise<boolean>;
13
+ delete(this: IHookFunctions): Promise<boolean>;
14
+ };
15
+ };
16
+ trigger(this: ITriggerFunctions): Promise<ITriggerResponse>;
17
+ webhook(this: IWebhookFunctions): Promise<IWebhookResponseData>;
18
+ }