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
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|