n8n-nodes-openai-compatible-chat-trigger 1.0.2 → 1.0.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.
@@ -19,16 +19,22 @@ class OpenAiCompatibleTrigger {
19
19
  outputs: ['main'],
20
20
  webhooks: [
21
21
  {
22
- name: 'default',
22
+ name: 'chatCompletions',
23
23
  httpMethod: 'POST',
24
24
  responseMode: '={{$parameter["responseMode"]}}',
25
- path: '={{$parameter["path"]}}/*',
25
+ path: '={{$parameter["path"]}}/v1/chat/completions',
26
26
  },
27
27
  {
28
- name: 'setup',
28
+ name: 'completions',
29
+ httpMethod: 'POST',
30
+ responseMode: '={{$parameter["responseMode"]}}',
31
+ path: '={{$parameter["path"]}}/v1/completions',
32
+ },
33
+ {
34
+ name: 'models',
29
35
  httpMethod: 'GET',
30
36
  responseMode: 'onReceived',
31
- path: '={{$parameter["path"]}}/*',
37
+ path: '={{$parameter["path"]}}/v1/models',
32
38
  },
33
39
  ],
34
40
  properties: [
@@ -104,8 +110,7 @@ class OpenAiCompatibleTrigger {
104
110
  };
105
111
  async webhook() {
106
112
  const req = this.getRequestObject();
107
- const url = req.originalUrl || req.url || '';
108
- const method = req.method || 'POST';
113
+ const webhookName = this.getWebhookName();
109
114
  // 1. Verify Authorization Header if required
110
115
  const authentication = this.getNodeParameter('authentication', 'none');
111
116
  if (authentication === 'headerAuth') {
@@ -127,7 +132,7 @@ class OpenAiCompatibleTrigger {
127
132
  }
128
133
  }
129
134
  // 2. Handle /v1/models endpoint (GET)
130
- if (method === 'GET' || url.endsWith('/v1/models') || url.endsWith('/models') || url.includes('/models')) {
135
+ if (webhookName === 'models') {
131
136
  const mockModelsStr = this.getNodeParameter('mockModels', 'gpt-3.5-turbo, gpt-4o, n8n-bot');
132
137
  const modelsList = mockModelsStr.split(',').map((m) => m.trim()).filter((m) => m !== '');
133
138
  const responseData = {
@@ -141,6 +146,7 @@ class OpenAiCompatibleTrigger {
141
146
  };
142
147
  return {
143
148
  webhookResponse: {
149
+ statusCode: 200,
144
150
  body: responseData,
145
151
  },
146
152
  };
@@ -152,33 +158,71 @@ class OpenAiCompatibleTrigger {
152
158
  let messageContent = '';
153
159
  let messagesArray = [];
154
160
  let modelName = '';
155
- let webhookType = 'chatCompletions';
156
- const isCompletions = url.endsWith('/v1/completions') || url.endsWith('/completions');
157
- if (isCompletions) {
161
+ const binaryData = {};
162
+ if (webhookName === 'completions') {
158
163
  messageContent = (body.prompt || '');
159
164
  modelName = (body.model || '');
160
- webhookType = 'completions';
161
165
  }
162
166
  else {
163
167
  // default to chatCompletions
164
168
  messagesArray = (body.messages || []);
165
169
  if (messagesArray.length > 0) {
166
170
  const lastMsg = messagesArray[messagesArray.length - 1];
167
- messageContent = lastMsg.content || '';
171
+ if (typeof lastMsg.content === 'string') {
172
+ messageContent = lastMsg.content;
173
+ }
174
+ else if (Array.isArray(lastMsg.content)) {
175
+ // Handle multimodal prompt content (e.g. containing text parts and image data URLs)
176
+ for (const part of lastMsg.content) {
177
+ if (part.type === 'text') {
178
+ messageContent += part.text || '';
179
+ }
180
+ else if (part.type === 'image_url') {
181
+ const url = part.image_url?.url || '';
182
+ if (url.startsWith('data:')) {
183
+ try {
184
+ const matches = url.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
185
+ if (matches && matches.length === 3) {
186
+ const mimeType = matches[1];
187
+ const base64Data = matches[2];
188
+ const buffer = Buffer.from(base64Data, 'base64');
189
+ const extension = mimeType.split('/')[1] || 'png';
190
+ const fileCount = Object.keys(binaryData).length;
191
+ const propertyName = `data_${fileCount}`;
192
+ const fileName = `chat_image_${Date.now()}_${fileCount}.${extension}`;
193
+ binaryData[propertyName] = await this.helpers.prepareBinaryData(buffer, fileName, mimeType);
194
+ }
195
+ }
196
+ catch (e) {
197
+ // ignore invalid file formats
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
168
203
  }
169
204
  modelName = (body.model || '');
170
205
  }
206
+ const responseItem = {
207
+ chatInput: messageContent,
208
+ sessionId: (body.user || body.sessionId || body.conversationId || 'default-session'),
209
+ message: messageContent,
210
+ messages: messagesArray.length > 0 ? messagesArray : undefined,
211
+ model: modelName || undefined,
212
+ body: body,
213
+ headers: headers,
214
+ query: query,
215
+ webhookType: webhookName,
216
+ };
217
+ const executionData = {
218
+ json: responseItem,
219
+ };
220
+ if (Object.keys(binaryData).length > 0) {
221
+ executionData.binary = binaryData;
222
+ }
171
223
  return {
172
224
  workflowData: [
173
- this.helpers.returnJsonArray({
174
- message: messageContent,
175
- messages: messagesArray.length > 0 ? messagesArray : undefined,
176
- model: modelName || undefined,
177
- body: body,
178
- headers: headers,
179
- query: query,
180
- webhookType: webhookType,
181
- }),
225
+ [executionData]
182
226
  ],
183
227
  };
184
228
  }
@@ -24,16 +24,22 @@ export class OpenAiCompatibleTrigger implements INodeType {
24
24
  outputs: ['main'],
25
25
  webhooks: [
26
26
  {
27
- name: 'default',
27
+ name: 'chatCompletions' as any,
28
28
  httpMethod: 'POST',
29
29
  responseMode: '={{$parameter["responseMode"]}}',
30
- path: '={{$parameter["path"]}}/*',
30
+ path: '={{$parameter["path"]}}/v1/chat/completions',
31
31
  },
32
32
  {
33
- name: 'setup',
33
+ name: 'completions' as any,
34
+ httpMethod: 'POST',
35
+ responseMode: '={{$parameter["responseMode"]}}',
36
+ path: '={{$parameter["path"]}}/v1/completions',
37
+ },
38
+ {
39
+ name: 'models' as any,
34
40
  httpMethod: 'GET',
35
41
  responseMode: 'onReceived',
36
- path: '={{$parameter["path"]}}/*',
42
+ path: '={{$parameter["path"]}}/v1/models',
37
43
  },
38
44
  ],
39
45
  properties: [
@@ -110,8 +116,7 @@ export class OpenAiCompatibleTrigger implements INodeType {
110
116
 
111
117
  async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
112
118
  const req = this.getRequestObject();
113
- const url = req.originalUrl || req.url || '';
114
- const method = req.method || 'POST';
119
+ const webhookName = this.getWebhookName();
115
120
 
116
121
  // 1. Verify Authorization Header if required
117
122
  const authentication = this.getNodeParameter('authentication', 'none') as string;
@@ -135,7 +140,7 @@ export class OpenAiCompatibleTrigger implements INodeType {
135
140
  }
136
141
 
137
142
  // 2. Handle /v1/models endpoint (GET)
138
- if (method === 'GET' || url.endsWith('/v1/models') || url.endsWith('/models') || url.includes('/models')) {
143
+ if (webhookName === 'models') {
139
144
  const mockModelsStr = this.getNodeParameter('mockModels', 'gpt-3.5-turbo, gpt-4o, n8n-bot') as string;
140
145
  const modelsList = mockModelsStr.split(',').map((m) => m.trim()).filter((m) => m !== '');
141
146
  const responseData = {
@@ -149,6 +154,7 @@ export class OpenAiCompatibleTrigger implements INodeType {
149
154
  };
150
155
  return {
151
156
  webhookResponse: {
157
+ statusCode: 200,
152
158
  body: responseData,
153
159
  },
154
160
  };
@@ -162,35 +168,75 @@ export class OpenAiCompatibleTrigger implements INodeType {
162
168
  let messageContent = '';
163
169
  let messagesArray: any[] = [];
164
170
  let modelName = '';
165
- let webhookType = 'chatCompletions';
166
-
167
- const isCompletions = url.endsWith('/v1/completions') || url.endsWith('/completions');
171
+ const binaryData: any = {};
168
172
 
169
- if (isCompletions) {
173
+ if (webhookName === 'completions') {
170
174
  messageContent = (body.prompt || '') as string;
171
175
  modelName = (body.model || '') as string;
172
- webhookType = 'completions';
173
176
  } else {
174
177
  // default to chatCompletions
175
178
  messagesArray = (body.messages || []) as any[];
176
179
  if (messagesArray.length > 0) {
177
180
  const lastMsg = messagesArray[messagesArray.length - 1];
178
- messageContent = lastMsg.content || '';
181
+
182
+ if (typeof lastMsg.content === 'string') {
183
+ messageContent = lastMsg.content;
184
+ } else if (Array.isArray(lastMsg.content)) {
185
+ // Handle multimodal prompt content (e.g. containing text parts and image data URLs)
186
+ for (const part of lastMsg.content) {
187
+ if (part.type === 'text') {
188
+ messageContent += part.text || '';
189
+ } else if (part.type === 'image_url') {
190
+ const url = part.image_url?.url || '';
191
+ if (url.startsWith('data:')) {
192
+ try {
193
+ const matches = url.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
194
+ if (matches && matches.length === 3) {
195
+ const mimeType = matches[1];
196
+ const base64Data = matches[2];
197
+ const buffer = Buffer.from(base64Data, 'base64');
198
+
199
+ const extension = mimeType.split('/')[1] || 'png';
200
+ const fileCount = Object.keys(binaryData).length;
201
+ const propertyName = `data_${fileCount}`;
202
+ const fileName = `chat_image_${Date.now()}_${fileCount}.${extension}`;
203
+
204
+ binaryData[propertyName] = await this.helpers.prepareBinaryData(buffer, fileName, mimeType);
205
+ }
206
+ } catch (e) {
207
+ // ignore invalid file formats
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
179
213
  }
180
214
  modelName = (body.model || '') as string;
181
215
  }
182
216
 
217
+ const responseItem: any = {
218
+ chatInput: messageContent,
219
+ sessionId: (body.user || body.sessionId || body.conversationId || 'default-session') as string,
220
+ message: messageContent,
221
+ messages: messagesArray.length > 0 ? messagesArray : undefined,
222
+ model: modelName || undefined,
223
+ body: body,
224
+ headers: headers,
225
+ query: query,
226
+ webhookType: webhookName,
227
+ };
228
+
229
+ const executionData: any = {
230
+ json: responseItem,
231
+ };
232
+
233
+ if (Object.keys(binaryData).length > 0) {
234
+ executionData.binary = binaryData;
235
+ }
236
+
183
237
  return {
184
238
  workflowData: [
185
- this.helpers.returnJsonArray({
186
- message: messageContent,
187
- messages: messagesArray.length > 0 ? messagesArray : undefined,
188
- model: modelName || undefined,
189
- body: body,
190
- headers: headers,
191
- query: query,
192
- webhookType: webhookType,
193
- }),
239
+ [executionData]
194
240
  ],
195
241
  };
196
242
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-openai-compatible-chat-trigger",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
4
4
  "description": "OpenAI-compatible chat trigger and helper nodes for n8n.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {