n8n-nodes-openai-compatible-chat-trigger 1.0.5 → 1.0.6

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.
package/README.md CHANGED
@@ -54,6 +54,9 @@ A custom n8n community node package that lets you expose your n8n workflows as d
54
54
  - **Response Mode** (`responseMode`): Set to `When Last Node Finishes` (returns output of the final node) or `Using Respond to Webhook Node`.
55
55
  - **Authentication** (`authentication`): `None` or `Header (Bearer Token)`. If set to header authentication, provide an `API Key`.
56
56
  - **Mock Models** (`mockModels`): Comma-separated list of models returned from the `/v1/models` endpoint (e.g. `gpt-3.5-turbo, gpt-4o, my-n8n-agent`).
57
+ - **Support File Uploads / Vision** (`supportFiles`): Enable/disable parsing of base64 image/file payloads from the client message. Parses them into native n8n binary files.
58
+ - **Allowed File Types** (`allowedFileTypes`): Comma-separated list of allowed MIME types (e.g., `image/png, image/jpeg, image/webp`). Non-matching files are skipped.
59
+ - **Support Tools / Function Calling** (`supportTools`): Enable/disable tools. If disabled, incoming tool calling definitions are filtered out to prevent errors in downstream nodes.
57
60
 
58
61
  ### 2. OpenAI Completions Response
59
62
 
@@ -106,6 +106,32 @@ class OpenAiCompatibleTrigger {
106
106
  default: 'gpt-3.5-turbo, gpt-4o, n8n-bot',
107
107
  description: 'Comma-separated list of models to return from the /v1/models endpoint',
108
108
  },
109
+ {
110
+ displayName: 'Support File Uploads / Vision',
111
+ name: 'supportFiles',
112
+ type: 'boolean',
113
+ default: true,
114
+ description: 'Whether to allow and parse base64 image/file uploads from the client',
115
+ },
116
+ {
117
+ displayName: 'Allowed File Types',
118
+ name: 'allowedFileTypes',
119
+ type: 'string',
120
+ default: 'image/png, image/jpeg, image/webp',
121
+ displayOptions: {
122
+ show: {
123
+ supportFiles: [true],
124
+ },
125
+ },
126
+ description: 'Comma-separated list of allowed MIME types (e.g. image/png, image/jpeg). Use * or *.* to allow all.',
127
+ },
128
+ {
129
+ displayName: 'Support Tools / Function Calling',
130
+ name: 'supportTools',
131
+ type: 'boolean',
132
+ default: true,
133
+ description: 'Whether to support tools. If disabled, incoming tool/function definitions will be automatically filtered out to prevent downstream errors.',
134
+ },
109
135
  ],
110
136
  };
111
137
  async webhook() {
@@ -155,10 +181,21 @@ class OpenAiCompatibleTrigger {
155
181
  const body = this.getBodyData();
156
182
  const query = this.getQueryData();
157
183
  const headers = this.getHeaderData();
184
+ const supportFiles = this.getNodeParameter('supportFiles', true);
185
+ const allowedFileTypesStr = this.getNodeParameter('allowedFileTypes', 'image/png, image/jpeg, image/webp');
186
+ const supportTools = this.getNodeParameter('supportTools', true);
187
+ // Clean up tool calling if tools are disabled
188
+ if (!supportTools) {
189
+ if (body.tools)
190
+ delete body.tools;
191
+ if (body.tool_choice)
192
+ delete body.tool_choice;
193
+ }
158
194
  let messageContent = '';
159
195
  let messagesArray = [];
160
196
  let modelName = '';
161
197
  const binaryData = {};
198
+ const allowedFileTypes = allowedFileTypesStr.split(',').map(t => t.trim().toLowerCase());
162
199
  if (webhookName === 'completions') {
163
200
  messageContent = (body.prompt || '');
164
201
  modelName = (body.model || '');
@@ -177,20 +214,32 @@ class OpenAiCompatibleTrigger {
177
214
  if (part.type === 'text') {
178
215
  messageContent += part.text || '';
179
216
  }
180
- else if (part.type === 'image_url') {
217
+ else if (part.type === 'image_url' && supportFiles) {
181
218
  const url = part.image_url?.url || '';
182
219
  if (url.startsWith('data:')) {
183
220
  try {
184
221
  const matches = url.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
185
222
  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);
223
+ const mimeType = matches[1].toLowerCase();
224
+ // Check if file type is allowed
225
+ const isAllowed = allowedFileTypes.some(type => {
226
+ if (type === '*' || type === '*/*' || type === 'all')
227
+ return true;
228
+ if (type.endsWith('/*')) {
229
+ const prefix = type.split('/')[0];
230
+ return mimeType.startsWith(prefix + '/');
231
+ }
232
+ return mimeType === type;
233
+ });
234
+ if (isAllowed) {
235
+ const base64Data = matches[2];
236
+ const buffer = Buffer.from(base64Data, 'base64');
237
+ const extension = mimeType.split('/')[1] || 'png';
238
+ const fileCount = Object.keys(binaryData).length;
239
+ const propertyName = `data_${fileCount}`;
240
+ const fileName = `chat_image_${Date.now()}_${fileCount}.${extension}`;
241
+ binaryData[propertyName] = await this.helpers.prepareBinaryData(buffer, fileName, mimeType);
242
+ }
194
243
  }
195
244
  }
196
245
  catch (e) {
@@ -111,6 +111,32 @@ export class OpenAiCompatibleTrigger implements INodeType {
111
111
  default: 'gpt-3.5-turbo, gpt-4o, n8n-bot',
112
112
  description: 'Comma-separated list of models to return from the /v1/models endpoint',
113
113
  },
114
+ {
115
+ displayName: 'Support File Uploads / Vision',
116
+ name: 'supportFiles',
117
+ type: 'boolean',
118
+ default: true,
119
+ description: 'Whether to allow and parse base64 image/file uploads from the client',
120
+ },
121
+ {
122
+ displayName: 'Allowed File Types',
123
+ name: 'allowedFileTypes',
124
+ type: 'string',
125
+ default: 'image/png, image/jpeg, image/webp',
126
+ displayOptions: {
127
+ show: {
128
+ supportFiles: [true],
129
+ },
130
+ },
131
+ description: 'Comma-separated list of allowed MIME types (e.g. image/png, image/jpeg). Use * or *.* to allow all.',
132
+ },
133
+ {
134
+ displayName: 'Support Tools / Function Calling',
135
+ name: 'supportTools',
136
+ type: 'boolean',
137
+ default: true,
138
+ description: 'Whether to support tools. If disabled, incoming tool/function definitions will be automatically filtered out to prevent downstream errors.',
139
+ },
114
140
  ],
115
141
  };
116
142
 
@@ -165,11 +191,23 @@ export class OpenAiCompatibleTrigger implements INodeType {
165
191
  const query = this.getQueryData() as IDataObject;
166
192
  const headers = this.getHeaderData() as IDataObject;
167
193
 
194
+ const supportFiles = this.getNodeParameter('supportFiles', true) as boolean;
195
+ const allowedFileTypesStr = this.getNodeParameter('allowedFileTypes', 'image/png, image/jpeg, image/webp') as string;
196
+ const supportTools = this.getNodeParameter('supportTools', true) as boolean;
197
+
198
+ // Clean up tool calling if tools are disabled
199
+ if (!supportTools) {
200
+ if (body.tools) delete body.tools;
201
+ if (body.tool_choice) delete body.tool_choice;
202
+ }
203
+
168
204
  let messageContent = '';
169
205
  let messagesArray: any[] = [];
170
206
  let modelName = '';
171
207
  const binaryData: any = {};
172
208
 
209
+ const allowedFileTypes = allowedFileTypesStr.split(',').map(t => t.trim().toLowerCase());
210
+
173
211
  if (webhookName === 'completions') {
174
212
  messageContent = (body.prompt || '') as string;
175
213
  modelName = (body.model || '') as string;
@@ -186,22 +224,35 @@ export class OpenAiCompatibleTrigger implements INodeType {
186
224
  for (const part of lastMsg.content) {
187
225
  if (part.type === 'text') {
188
226
  messageContent += part.text || '';
189
- } else if (part.type === 'image_url') {
227
+ } else if (part.type === 'image_url' && supportFiles) {
190
228
  const url = part.image_url?.url || '';
191
229
  if (url.startsWith('data:')) {
192
230
  try {
193
231
  const matches = url.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
194
232
  if (matches && matches.length === 3) {
195
- const mimeType = matches[1];
196
- const base64Data = matches[2];
197
- const buffer = Buffer.from(base64Data, 'base64');
233
+ const mimeType = matches[1].toLowerCase();
198
234
 
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);
235
+ // Check if file type is allowed
236
+ const isAllowed = allowedFileTypes.some(type => {
237
+ if (type === '*' || type === '*/*' || type === 'all') return true;
238
+ if (type.endsWith('/*')) {
239
+ const prefix = type.split('/')[0];
240
+ return mimeType.startsWith(prefix + '/');
241
+ }
242
+ return mimeType === type;
243
+ });
244
+
245
+ if (isAllowed) {
246
+ const base64Data = matches[2];
247
+ const buffer = Buffer.from(base64Data, 'base64');
248
+
249
+ const extension = mimeType.split('/')[1] || 'png';
250
+ const fileCount = Object.keys(binaryData).length;
251
+ const propertyName = `data_${fileCount}`;
252
+ const fileName = `chat_image_${Date.now()}_${fileCount}.${extension}`;
253
+
254
+ binaryData[propertyName] = await this.helpers.prepareBinaryData(buffer, fileName, mimeType);
255
+ }
205
256
  }
206
257
  } catch (e) {
207
258
  // ignore invalid file formats
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-openai-compatible-chat-trigger",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "OpenAI-compatible chat trigger and helper nodes for n8n.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {