n8n-nodes-github-copilot 4.4.17 โ 4.5.1
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/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -1
- package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +70 -545
- package/dist/nodes/GitHubCopilotOpenAI/execute/chatCompletion.d.ts +2 -0
- package/dist/nodes/GitHubCopilotOpenAI/execute/chatCompletion.js +243 -0
- package/dist/nodes/GitHubCopilotOpenAI/execute/listModels.d.ts +2 -0
- package/dist/nodes/GitHubCopilotOpenAI/execute/listModels.js +15 -0
- package/dist/nodes/GitHubCopilotOpenAI/execute/parseMessages.d.ts +17 -0
- package/dist/nodes/GitHubCopilotOpenAI/execute/parseMessages.js +149 -0
- package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +40 -426
- package/dist/nodes/GitHubCopilotOpenAI/properties/chatCompletionProperties.d.ts +2 -0
- package/dist/nodes/GitHubCopilotOpenAI/properties/chatCompletionProperties.js +352 -0
- package/dist/nodes/GitHubCopilotOpenAI/properties/modelsProperties.d.ts +2 -0
- package/dist/nodes/GitHubCopilotOpenAI/properties/modelsProperties.js +35 -0
- package/dist/nodes/GitHubCopilotOpenAI/utils/modelsApi.d.ts +3 -0
- package/dist/nodes/GitHubCopilotOpenAI/utils/modelsApi.js +62 -0
- package/dist/package.json +5 -3
- package/package.json +5 -3
|
@@ -3,29 +3,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GitHubCopilotOpenAI = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
5
|
const nodeProperties_1 = require("./nodeProperties");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const chatCompletion_1 = require("./execute/chatCompletion");
|
|
7
|
+
const listModels_1 = require("./execute/listModels");
|
|
8
8
|
const DynamicModelLoader_1 = require("../../shared/models/DynamicModelLoader");
|
|
9
|
-
const GitHubCopilotModels_1 = require("../../shared/models/GitHubCopilotModels");
|
|
10
|
-
const DynamicModelsManager_1 = require("../../shared/utils/DynamicModelsManager");
|
|
11
9
|
class GitHubCopilotOpenAI {
|
|
12
10
|
constructor() {
|
|
13
11
|
this.description = {
|
|
14
|
-
displayName:
|
|
15
|
-
name:
|
|
16
|
-
icon:
|
|
17
|
-
group: [
|
|
12
|
+
displayName: "GitHub Copilot OpenAI",
|
|
13
|
+
name: "gitHubCopilotOpenAI",
|
|
14
|
+
icon: "file:../../shared/icons/copilot.svg",
|
|
15
|
+
group: ["transform"],
|
|
18
16
|
version: 1,
|
|
19
|
-
subtitle: '={{$parameter["model"]}}',
|
|
20
|
-
description:
|
|
17
|
+
subtitle: '={{$parameter["operation"] === "listModels" ? "List Models" : $parameter["model"]}}',
|
|
18
|
+
description: "OpenAI-compatible GitHub Copilot API: Chat Completions and List Models (GET /v1/models)",
|
|
21
19
|
defaults: {
|
|
22
|
-
name:
|
|
20
|
+
name: "GitHub Copilot OpenAI",
|
|
23
21
|
},
|
|
24
|
-
inputs: [
|
|
25
|
-
outputs: [
|
|
22
|
+
inputs: ["main"],
|
|
23
|
+
outputs: ["main"],
|
|
26
24
|
credentials: [
|
|
27
25
|
{
|
|
28
|
-
name:
|
|
26
|
+
name: "githubCopilotApi",
|
|
29
27
|
required: true,
|
|
30
28
|
},
|
|
31
29
|
],
|
|
@@ -43,569 +41,96 @@ class GitHubCopilotOpenAI {
|
|
|
43
41
|
};
|
|
44
42
|
}
|
|
45
43
|
async execute() {
|
|
46
|
-
var _a
|
|
44
|
+
var _a;
|
|
47
45
|
const items = this.getInputData();
|
|
48
46
|
const returnData = [];
|
|
49
47
|
for (let i = 0; i < items.length; i++) {
|
|
50
48
|
try {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
const selectedModel = this.getNodeParameter('model', i);
|
|
62
|
-
if (selectedModel === '__manual__') {
|
|
63
|
-
model = this.getNodeParameter('customModel', i);
|
|
64
|
-
if (!model || model.trim() === '') {
|
|
65
|
-
throw new Error("Custom model name is required when selecting 'โ๏ธ Enter Custom Model Name'");
|
|
66
|
-
}
|
|
67
|
-
console.log(`โ๏ธ Using manually entered model: ${model}`);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
model = selectedModel;
|
|
71
|
-
console.log(`๐ Using model from list: ${model}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const messagesInputMode = this.getNodeParameter('messagesInputMode', i, 'manual');
|
|
75
|
-
let messages = [];
|
|
76
|
-
let requestBodyFromJson = undefined;
|
|
77
|
-
if (messagesInputMode === 'json') {
|
|
78
|
-
const messagesJson = this.getNodeParameter('messagesJson', i, '[]');
|
|
79
|
-
try {
|
|
80
|
-
let parsed;
|
|
81
|
-
if (typeof messagesJson === 'object') {
|
|
82
|
-
parsed = messagesJson;
|
|
83
|
-
console.log('๐ฅ Received messages as direct object/array (no parsing needed)');
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
parsed = JSON.parse(messagesJson);
|
|
87
|
-
console.log('๐ฅ Parsed messages from JSON string');
|
|
88
|
-
}
|
|
89
|
-
if (Array.isArray(parsed)) {
|
|
90
|
-
messages = parsed;
|
|
91
|
-
}
|
|
92
|
-
else if (parsed.messages && Array.isArray(parsed.messages)) {
|
|
93
|
-
messages = parsed.messages;
|
|
94
|
-
requestBodyFromJson = parsed;
|
|
95
|
-
console.log('๐ฅ Full OpenAI request body received:', JSON.stringify(parsed, null, 2));
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
messages = parsed;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
throw new Error(`Failed to parse messages JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
const messagesParam = this.getNodeParameter('messages', i, {
|
|
107
|
-
message: [],
|
|
108
|
-
});
|
|
109
|
-
console.log('๐ฅ Manual mode - messagesParam:', JSON.stringify(messagesParam, null, 2));
|
|
110
|
-
if (messagesParam.message && Array.isArray(messagesParam.message)) {
|
|
111
|
-
for (const msg of messagesParam.message) {
|
|
112
|
-
const message = {
|
|
113
|
-
role: msg.role,
|
|
114
|
-
content: msg.content,
|
|
115
|
-
};
|
|
116
|
-
const msgType = msg.type || 'text';
|
|
117
|
-
if (msgType === 'file_binary') {
|
|
118
|
-
const binaryKeyData = items[i].binary;
|
|
119
|
-
const keyToUse = msg.binaryPropertyName || 'data';
|
|
120
|
-
if (binaryKeyData && binaryKeyData[keyToUse]) {
|
|
121
|
-
try {
|
|
122
|
-
const binaryData = binaryKeyData[keyToUse];
|
|
123
|
-
let mimeType = binaryData.mimeType || 'application/octet-stream';
|
|
124
|
-
const buffer = await this.helpers.getBinaryDataBuffer(i, keyToUse);
|
|
125
|
-
if (!mimeType.startsWith('image/')) {
|
|
126
|
-
const detected = (0, utils_1.getImageMimeType)(buffer);
|
|
127
|
-
if (detected !== 'application/octet-stream') {
|
|
128
|
-
mimeType = detected;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
console.warn(`โ ๏ธ Could not detect image type for '${keyToUse}', defaulting to 'image/jpeg' or keeping original '${mimeType}'`);
|
|
132
|
-
if (mimeType === 'application/octet-stream') {
|
|
133
|
-
mimeType = 'image/jpeg';
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (!mimeType.startsWith('image/')) {
|
|
138
|
-
console.warn(`โ ๏ธ Forcing mime type '${mimeType}' to 'image/jpeg' to bypass schema validation (allowing backend to validate content)`);
|
|
139
|
-
mimeType = 'image/jpeg';
|
|
140
|
-
}
|
|
141
|
-
const base64 = buffer.toString('base64');
|
|
142
|
-
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
143
|
-
const contentArray = [];
|
|
144
|
-
const caption = msg.caption;
|
|
145
|
-
if (caption && caption.trim() !== '') {
|
|
146
|
-
contentArray.push({
|
|
147
|
-
type: 'text',
|
|
148
|
-
text: caption,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
else if (message.content && message.content.trim() !== '' && message.content !== '[object Object]') {
|
|
152
|
-
contentArray.push({
|
|
153
|
-
type: 'text',
|
|
154
|
-
text: message.content,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
contentArray.push({
|
|
158
|
-
type: 'image_url',
|
|
159
|
-
image_url: {
|
|
160
|
-
url: dataUrl,
|
|
161
|
-
detail: 'auto',
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
message.role = 'user';
|
|
165
|
-
message.content = contentArray;
|
|
166
|
-
delete message.type;
|
|
167
|
-
console.log(`๐ Attached binary file '${keyToUse}' (${mimeType}) as image_url`);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
if (err instanceof n8n_workflow_1.NodeOperationError) {
|
|
171
|
-
throw err;
|
|
172
|
-
}
|
|
173
|
-
console.error(`โ Failed to read binary data for '${keyToUse}':`, err);
|
|
174
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
175
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to read binary file '${keyToUse}': ${errorMessage}`, { itemIndex: i });
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
else {
|
|
179
|
-
const available = binaryKeyData ? Object.keys(binaryKeyData).join(', ') : 'none';
|
|
180
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Binary property '${keyToUse}' not found. Available binary properties: ${available}`, { itemIndex: i });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else if (msgType === 'file') {
|
|
184
|
-
message.type = 'file';
|
|
185
|
-
}
|
|
186
|
-
messages.push(message);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
console.log('๐ฅ Manual mode - parsed messages:', JSON.stringify(messages, null, 2));
|
|
190
|
-
}
|
|
191
|
-
if (messages.length === 0) {
|
|
192
|
-
messages.push({
|
|
193
|
-
role: 'user',
|
|
194
|
-
content: 'Hello! How can you help me?',
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
for (let msgIndex = 0; msgIndex < messages.length; msgIndex++) {
|
|
198
|
-
const msg = messages[msgIndex];
|
|
199
|
-
if (Array.isArray(msg.content)) {
|
|
200
|
-
for (const contentItem of msg.content) {
|
|
201
|
-
if (contentItem.type === 'file') {
|
|
202
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `โ GitHub Copilot API Error: File attachments cannot be used inside 'content' array.\n\n` +
|
|
203
|
-
`๐ INCORRECT FORMAT (OpenAI style - doesn't work):\n` +
|
|
204
|
-
`{\n` +
|
|
205
|
-
` "role": "user",\n` +
|
|
206
|
-
` "content": [\n` +
|
|
207
|
-
` {"type": "text", "text": "Analyze this"},\n` +
|
|
208
|
-
` {"type": "file", "file": "data:..."} โ WRONG\n` +
|
|
209
|
-
` ]\n` +
|
|
210
|
-
`}\n\n` +
|
|
211
|
-
`โ
CORRECT FORMAT (GitHub Copilot - message level):\n` +
|
|
212
|
-
`[\n` +
|
|
213
|
-
` {"role": "user", "content": "Analyze this file"},\n` +
|
|
214
|
-
` {"role": "user", "content": "data:image/png;base64,...", "type": "file"} โ
CORRECT\n` +
|
|
215
|
-
`]\n\n` +
|
|
216
|
-
`๐ก Solution: Use separate messages with 'type' property at message level, not inside content array.`, { itemIndex: i });
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
console.log('๐ค Final messages being sent to API:', JSON.stringify(messages, null, 2));
|
|
222
|
-
for (let msgIndex = 0; msgIndex < messages.length; msgIndex++) {
|
|
223
|
-
const msg = messages[msgIndex];
|
|
224
|
-
if (msg.content !== null &&
|
|
225
|
-
msg.content !== undefined &&
|
|
226
|
-
typeof msg.content === 'object' &&
|
|
227
|
-
!Array.isArray(msg.content)) {
|
|
228
|
-
const originalContent = msg.content;
|
|
229
|
-
msg.content = JSON.stringify(msg.content, null, 2);
|
|
230
|
-
console.log(`๐ Auto-converted message[${msgIndex}].content from object to JSON string`);
|
|
231
|
-
console.log(' Original type:', typeof originalContent);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
|
|
235
|
-
let parsedTools = [];
|
|
236
|
-
const tools = advancedOptions.tools;
|
|
237
|
-
if (tools) {
|
|
238
|
-
try {
|
|
239
|
-
if (typeof tools === 'object' && Array.isArray(tools) && tools.length > 0) {
|
|
240
|
-
parsedTools = tools;
|
|
241
|
-
console.log('๐ฅ Received tools as direct array (no parsing needed)');
|
|
242
|
-
}
|
|
243
|
-
else if (typeof tools === 'string' && tools.trim()) {
|
|
244
|
-
const parsed = JSON.parse(tools);
|
|
245
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
246
|
-
parsedTools = parsed;
|
|
247
|
-
console.log('๐ฅ Parsed tools from JSON string');
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
console.log('๐ฅ Tools string parsed but empty or not an array');
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
console.log('๐ฅ Tools field present but empty or invalid');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
console.log('โ ๏ธ Failed to parse tools, ignoring:', error instanceof Error ? error.message : 'Unknown error');
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
console.log('๐ฅ No tools specified');
|
|
263
|
-
}
|
|
264
|
-
let max_tokens = advancedOptions.max_tokens || 4096;
|
|
265
|
-
if (!max_tokens || max_tokens <= 0 || isNaN(max_tokens)) {
|
|
266
|
-
max_tokens = 4096;
|
|
267
|
-
console.log('โ ๏ธ Invalid max_tokens value, using default: 4096');
|
|
268
|
-
}
|
|
269
|
-
const temperature = (_a = advancedOptions.temperature) !== null && _a !== void 0 ? _a : 1;
|
|
270
|
-
const top_p = (_b = advancedOptions.top_p) !== null && _b !== void 0 ? _b : 1;
|
|
271
|
-
const frequency_penalty = (_c = advancedOptions.frequency_penalty) !== null && _c !== void 0 ? _c : 0;
|
|
272
|
-
const presence_penalty = (_d = advancedOptions.presence_penalty) !== null && _d !== void 0 ? _d : 0;
|
|
273
|
-
const seed = advancedOptions.seed || 0;
|
|
274
|
-
const stream = (_e = advancedOptions.stream) !== null && _e !== void 0 ? _e : false;
|
|
275
|
-
const user = advancedOptions.user || undefined;
|
|
276
|
-
const stop = advancedOptions.stop || undefined;
|
|
277
|
-
const response_format_ui = advancedOptions.response_format || 'text';
|
|
278
|
-
let response_format = undefined;
|
|
279
|
-
if (requestBodyFromJson === null || requestBodyFromJson === void 0 ? void 0 : requestBodyFromJson.response_format) {
|
|
280
|
-
response_format = requestBodyFromJson.response_format;
|
|
281
|
-
console.log('๐ response_format from JSON request body:', JSON.stringify(response_format));
|
|
282
|
-
}
|
|
283
|
-
else if (response_format_ui && response_format_ui !== 'text') {
|
|
284
|
-
response_format = { type: response_format_ui };
|
|
285
|
-
console.log('๐ response_format from UI field:', JSON.stringify(response_format));
|
|
286
|
-
}
|
|
287
|
-
else if (advancedOptions.response_format &&
|
|
288
|
-
typeof advancedOptions.response_format === 'string') {
|
|
289
|
-
try {
|
|
290
|
-
response_format = JSON.parse(advancedOptions.response_format);
|
|
291
|
-
console.log('๐ response_format from advancedOptions:', JSON.stringify(response_format));
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
console.log('โ ๏ธ Failed to parse response_format from advancedOptions');
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (response_format) {
|
|
298
|
-
console.log('โ
Final response_format:', JSON.stringify(response_format));
|
|
299
|
-
console.log('๐ response_format.type:', response_format.type);
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
console.log('โน๏ธ No response_format specified - using default text format');
|
|
303
|
-
}
|
|
304
|
-
const modelMapping = {
|
|
305
|
-
'gpt-4': 'gpt-4o',
|
|
306
|
-
'gpt-4o': 'gpt-4o',
|
|
307
|
-
'gpt-4o-mini': 'gpt-4o-mini',
|
|
308
|
-
'gpt-4-turbo': 'gpt-4o',
|
|
309
|
-
'claude-3-5-sonnet': 'claude-3.5-sonnet',
|
|
310
|
-
'claude-3.5-sonnet-20241022': 'claude-3.5-sonnet',
|
|
311
|
-
o1: 'o1',
|
|
312
|
-
'o1-preview': 'o1-preview',
|
|
313
|
-
'o1-mini': 'o1-mini',
|
|
314
|
-
};
|
|
315
|
-
let copilotModel = modelMapping[model] || model;
|
|
316
|
-
let hasVisionContent = false;
|
|
317
|
-
for (const msg of messages) {
|
|
318
|
-
const content = msg.content;
|
|
319
|
-
const type = msg.type;
|
|
320
|
-
if (type === 'file' || type === 'image') {
|
|
321
|
-
hasVisionContent = true;
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
if (typeof content === 'string') {
|
|
325
|
-
const trimmed = content.trim();
|
|
326
|
-
const isActualDataUrl = /^data:image\/[a-z]+;base64,[A-Za-z0-9+\/=]{100,}/i.test(trimmed);
|
|
327
|
-
const isCopilotFileUrl = trimmed.startsWith('copilot-file://');
|
|
328
|
-
if (isActualDataUrl || isCopilotFileUrl) {
|
|
329
|
-
hasVisionContent = true;
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
else if (Array.isArray(content)) {
|
|
334
|
-
for (const part of content) {
|
|
335
|
-
if ((part === null || part === void 0 ? void 0 : part.type) === 'image_url' || (part === null || part === void 0 ? void 0 : part.type) === 'image' || (part === null || part === void 0 ? void 0 : part.image_url) || (part === null || part === void 0 ? void 0 : part.type) === 'file') {
|
|
336
|
-
hasVisionContent = true;
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (hasVisionContent)
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
if (hasVisionContent) {
|
|
345
|
-
const credentials = await this.getCredentials('githubCopilotApi');
|
|
346
|
-
const oauthToken = credentials.oauthToken;
|
|
347
|
-
let supportsVision = DynamicModelsManager_1.DynamicModelsManager.modelSupportsVision(oauthToken, copilotModel);
|
|
348
|
-
if (supportsVision === null) {
|
|
349
|
-
const modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(copilotModel);
|
|
350
|
-
supportsVision = !!(((_f = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities) === null || _f === void 0 ? void 0 : _f.vision) || ((_g = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities) === null || _g === void 0 ? void 0 : _g.multimodal));
|
|
351
|
-
console.log(`๐๏ธ Vision check for model ${copilotModel}: using static list, supportsVision=${supportsVision}`);
|
|
352
|
-
}
|
|
353
|
-
else {
|
|
354
|
-
console.log(`๐๏ธ Vision check for model ${copilotModel}: using API cache, supportsVision=${supportsVision}`);
|
|
355
|
-
}
|
|
356
|
-
if (!supportsVision) {
|
|
357
|
-
const enableVisionFallback = advancedOptions.enableVisionFallback || false;
|
|
358
|
-
if (enableVisionFallback) {
|
|
359
|
-
const fallbackModelRaw = advancedOptions.visionFallbackModel;
|
|
360
|
-
const fallbackModel = fallbackModelRaw === '__manual__'
|
|
361
|
-
? advancedOptions.visionFallbackCustomModel
|
|
362
|
-
: fallbackModelRaw;
|
|
363
|
-
if (!fallbackModel || fallbackModel.trim() === '') {
|
|
364
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Vision fallback enabled but no fallback model was selected or provided. Please select a vision-capable model in Advanced Options.', { itemIndex: i });
|
|
365
|
-
}
|
|
366
|
-
console.log(`๐๏ธ Model ${copilotModel} does not support vision - using fallback model: ${fallbackModel}`);
|
|
367
|
-
copilotModel = fallbackModel;
|
|
368
|
-
}
|
|
369
|
-
else {
|
|
370
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Model ${copilotModel} does not support vision/image processing. Enable "Vision Fallback" in Advanced Options and select a vision-capable model, or choose a model with vision capabilities.`, { itemIndex: i });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
const requestBody = {
|
|
375
|
-
model: copilotModel,
|
|
376
|
-
messages,
|
|
377
|
-
stream,
|
|
378
|
-
temperature,
|
|
379
|
-
max_tokens,
|
|
380
|
-
};
|
|
381
|
-
if (top_p !== 1) {
|
|
382
|
-
requestBody.top_p = top_p;
|
|
383
|
-
}
|
|
384
|
-
if (frequency_penalty !== 0) {
|
|
385
|
-
requestBody.frequency_penalty = frequency_penalty;
|
|
386
|
-
}
|
|
387
|
-
if (presence_penalty !== 0) {
|
|
388
|
-
requestBody.presence_penalty = presence_penalty;
|
|
389
|
-
}
|
|
390
|
-
if (user) {
|
|
391
|
-
requestBody.user = user;
|
|
392
|
-
}
|
|
393
|
-
if (stop) {
|
|
394
|
-
try {
|
|
395
|
-
requestBody.stop = JSON.parse(stop);
|
|
396
|
-
}
|
|
397
|
-
catch {
|
|
398
|
-
requestBody.stop = stop;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
if (parsedTools.length > 0) {
|
|
402
|
-
requestBody.tools = parsedTools;
|
|
403
|
-
const tool_choice = advancedOptions.tool_choice || 'auto';
|
|
404
|
-
if (tool_choice !== 'auto') {
|
|
405
|
-
requestBody.tool_choice = tool_choice;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (response_format) {
|
|
409
|
-
requestBody.response_format = response_format;
|
|
410
|
-
}
|
|
411
|
-
if (seed > 0) {
|
|
412
|
-
requestBody.seed = seed;
|
|
413
|
-
}
|
|
414
|
-
console.log('๐ Sending request to GitHub Copilot API:');
|
|
415
|
-
console.log(' Model:', copilotModel);
|
|
416
|
-
console.log(' Messages count:', messages.length);
|
|
417
|
-
console.log(' Has Vision Content:', hasVisionContent);
|
|
418
|
-
console.log(' Request body:', JSON.stringify(requestBody, null, 2));
|
|
419
|
-
let response;
|
|
420
|
-
try {
|
|
421
|
-
response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, hasVisionContent);
|
|
422
|
-
}
|
|
423
|
-
catch (error) {
|
|
424
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
425
|
-
const enhancedError = new n8n_workflow_1.NodeOperationError(this.getNode(), `${errorMsg}\n\n๐ค Model used: ${copilotModel}`);
|
|
426
|
-
throw enhancedError;
|
|
427
|
-
}
|
|
428
|
-
const retriesUsed = ((_h = response._retryMetadata) === null || _h === void 0 ? void 0 : _h.retries) || 0;
|
|
429
|
-
if (retriesUsed > 0) {
|
|
430
|
-
console.log(`โน๏ธ Request completed with ${retriesUsed} retry(ies)`);
|
|
431
|
-
}
|
|
432
|
-
const cleanJsonFromMarkdown = (content) => {
|
|
433
|
-
if (!content || typeof content !== 'string') {
|
|
434
|
-
return content;
|
|
435
|
-
}
|
|
436
|
-
try {
|
|
437
|
-
const trimmed = content.trim();
|
|
438
|
-
console.log('๐งน cleanJsonFromMarkdown - Input length:', trimmed.length);
|
|
439
|
-
const jsonBlockRegex = /^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/;
|
|
440
|
-
const match = trimmed.match(jsonBlockRegex);
|
|
441
|
-
if (match && match[1]) {
|
|
442
|
-
const extracted = match[1].trim();
|
|
443
|
-
console.log('โ
cleanJsonFromMarkdown - Extracted from markdown block');
|
|
444
|
-
return extracted;
|
|
445
|
-
}
|
|
446
|
-
console.log('โน๏ธ cleanJsonFromMarkdown - No markdown block found, returning as is');
|
|
447
|
-
return trimmed;
|
|
448
|
-
}
|
|
449
|
-
catch (error) {
|
|
450
|
-
console.error('โ cleanJsonFromMarkdown - Error:', error);
|
|
451
|
-
return content;
|
|
452
|
-
}
|
|
453
|
-
};
|
|
454
|
-
console.log('๐จ Building OpenAI response...');
|
|
455
|
-
console.log('๐ response_format check:', (response_format === null || response_format === void 0 ? void 0 : response_format.type) === 'json_object' ? 'WILL CLEAN MARKDOWN' : 'WILL KEEP AS IS');
|
|
456
|
-
const openAIResponse = {
|
|
457
|
-
id: response.id || `chatcmpl-${Date.now()}`,
|
|
458
|
-
object: response.object || 'chat.completion',
|
|
459
|
-
created: response.created || Math.floor(Date.now() / 1000),
|
|
460
|
-
model: model,
|
|
461
|
-
choices: response.choices.map((choice, choiceIndex) => {
|
|
462
|
-
var _a;
|
|
463
|
-
console.log(`\n๐ Processing choice ${choiceIndex}:`);
|
|
464
|
-
console.log(' - role:', choice.message.role);
|
|
465
|
-
console.log(' - content type:', typeof choice.message.content);
|
|
466
|
-
console.log(' - content length:', ((_a = choice.message.content) === null || _a === void 0 ? void 0 : _a.length) || 0);
|
|
467
|
-
console.log(' - has tool_calls:', !!choice.message.tool_calls);
|
|
468
|
-
let processedContent = choice.message.content;
|
|
469
|
-
if (choice.message.content !== null && choice.message.content !== undefined) {
|
|
470
|
-
if ((response_format === null || response_format === void 0 ? void 0 : response_format.type) === 'json_object') {
|
|
471
|
-
console.log(' ๐งน Applying cleanJsonFromMarkdown (keeping as string)...');
|
|
472
|
-
processedContent = cleanJsonFromMarkdown(choice.message.content);
|
|
473
|
-
console.log(' โ
Processed content type:', typeof processedContent);
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
console.log(' โน๏ธ Keeping content as is');
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
const choiceObj = {
|
|
480
|
-
index: choice.index,
|
|
481
|
-
message: {
|
|
482
|
-
role: choice.message.role,
|
|
483
|
-
content: processedContent,
|
|
484
|
-
refusal: choice.message.refusal || null,
|
|
485
|
-
annotations: choice.message.annotations || [],
|
|
486
|
-
},
|
|
487
|
-
logprobs: choice.logprobs || null,
|
|
488
|
-
finish_reason: choice.finish_reason,
|
|
489
|
-
};
|
|
490
|
-
if (choice.message.tool_calls && choice.message.tool_calls.length > 0) {
|
|
491
|
-
choiceObj.message.tool_calls = choice.message.tool_calls;
|
|
492
|
-
}
|
|
493
|
-
return choiceObj;
|
|
494
|
-
}),
|
|
495
|
-
usage: response.usage || {
|
|
496
|
-
prompt_tokens: 0,
|
|
497
|
-
completion_tokens: 0,
|
|
498
|
-
total_tokens: 0,
|
|
499
|
-
},
|
|
500
|
-
};
|
|
501
|
-
if (response.system_fingerprint) {
|
|
502
|
-
openAIResponse.system_fingerprint = response.system_fingerprint;
|
|
503
|
-
}
|
|
504
|
-
returnData.push({
|
|
505
|
-
json: openAIResponse,
|
|
506
|
-
pairedItem: { item: i },
|
|
507
|
-
});
|
|
49
|
+
const operation = this.getNodeParameter("operation", i, "chatCompletions");
|
|
50
|
+
if (operation === "listModels") {
|
|
51
|
+
const result = await (0, listModels_1.executeListModels)(this, i);
|
|
52
|
+
returnData.push({ json: result, pairedItem: { item: i } });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const openAIResponse = await (0, chatCompletion_1.executeChatCompletion)(this, items, i);
|
|
56
|
+
returnData.push({ json: openAIResponse, pairedItem: { item: i } });
|
|
508
57
|
}
|
|
509
58
|
catch (error) {
|
|
510
59
|
if (this.continueOnFail()) {
|
|
511
|
-
const errorMessage = error instanceof Error ? error.message :
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
512
61
|
const errorString = JSON.stringify(error);
|
|
513
|
-
console.error(
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
console.log('๐งน Cleaned error message:', cleanMessage);
|
|
62
|
+
console.error("ERROR occurred:", errorMessage);
|
|
63
|
+
let cleanMessage = errorMessage
|
|
64
|
+
.replace(/\[Token used: [^\]]+\]/g, "")
|
|
65
|
+
.replace(/\[Attempt: \d+\/\d+\]/g, "")
|
|
66
|
+
.replace(/^GitHub Copilot API error:\s*/i, "")
|
|
67
|
+
.replace(/\s+/g, " ")
|
|
68
|
+
.trim();
|
|
521
69
|
let apiError = null;
|
|
522
70
|
try {
|
|
523
|
-
if (error && typeof error ===
|
|
71
|
+
if (error && typeof error === "object" && "cause" in error) {
|
|
524
72
|
const cause = error.cause;
|
|
525
|
-
if (cause
|
|
73
|
+
if (cause === null || cause === void 0 ? void 0 : cause.error)
|
|
526
74
|
apiError = cause.error;
|
|
527
|
-
}
|
|
528
75
|
}
|
|
529
|
-
if (!apiError && errorString.includes('
|
|
76
|
+
if (!apiError && errorString.includes('"error"')) {
|
|
530
77
|
const jsonMatch = errorString.match(/\{[^{}]*"error"[^{}]*\}/);
|
|
531
|
-
if (jsonMatch)
|
|
78
|
+
if (jsonMatch)
|
|
532
79
|
apiError = JSON.parse(jsonMatch[0]);
|
|
533
|
-
}
|
|
534
80
|
}
|
|
535
81
|
}
|
|
536
|
-
catch
|
|
537
|
-
console.error('Failed to parse API error:', parseError);
|
|
82
|
+
catch {
|
|
538
83
|
}
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
(apiError
|
|
543
|
-
if (
|
|
544
|
-
console.log('๐ซ 400 Bad Request detected - throwing non-retryable error');
|
|
84
|
+
const lower = cleanMessage.toLowerCase();
|
|
85
|
+
const is400 = lower.includes("400") ||
|
|
86
|
+
lower.includes("bad request") ||
|
|
87
|
+
((_a = apiError === null || apiError === void 0 ? void 0 : apiError.error) === null || _a === void 0 ? void 0 : _a.code) === "invalid_request_body";
|
|
88
|
+
if (is400) {
|
|
545
89
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Bad Request (400): ${cleanMessage}`, {
|
|
546
90
|
itemIndex: i,
|
|
547
|
-
description:
|
|
91
|
+
description: "The request was malformed. Retrying will not help.",
|
|
548
92
|
});
|
|
549
93
|
}
|
|
550
|
-
let errorType =
|
|
94
|
+
let errorType = "invalid_request_error";
|
|
551
95
|
let errorCode = null;
|
|
552
96
|
let errorParam = null;
|
|
553
97
|
let finalMessage = cleanMessage;
|
|
554
|
-
if (apiError
|
|
98
|
+
if (apiError === null || apiError === void 0 ? void 0 : apiError.error) {
|
|
555
99
|
finalMessage = apiError.error.message || cleanMessage;
|
|
556
100
|
errorType = apiError.error.type || errorType;
|
|
557
101
|
errorCode = apiError.error.code || null;
|
|
558
102
|
errorParam = apiError.error.param || null;
|
|
559
|
-
|
|
103
|
+
}
|
|
104
|
+
else if (lower.includes("403") || lower.includes("forbidden")) {
|
|
105
|
+
errorCode = "insufficient_quota";
|
|
106
|
+
finalMessage =
|
|
107
|
+
lower.includes("access") && lower.includes("forbidden")
|
|
108
|
+
? "You exceeded your current quota, please check your plan and billing details."
|
|
109
|
+
: cleanMessage;
|
|
110
|
+
}
|
|
111
|
+
else if (lower.includes("max") && lower.includes("token")) {
|
|
112
|
+
errorCode = "context_length_exceeded";
|
|
113
|
+
errorParam = "max_tokens";
|
|
114
|
+
finalMessage = "This model's maximum context length is exceeded.";
|
|
115
|
+
}
|
|
116
|
+
else if (lower.includes("401") || lower.includes("unauthorized")) {
|
|
117
|
+
errorCode = "invalid_api_key";
|
|
118
|
+
finalMessage = "Incorrect API key provided.";
|
|
119
|
+
}
|
|
120
|
+
else if (lower.includes("429") || lower.includes("rate limit")) {
|
|
121
|
+
errorType = "rate_limit_error";
|
|
122
|
+
errorCode = "rate_limit_exceeded";
|
|
123
|
+
finalMessage = "Rate limit reached. Please wait before making more requests.";
|
|
124
|
+
}
|
|
125
|
+
else if (lower.includes("timeout")) {
|
|
126
|
+
errorType = "api_error";
|
|
127
|
+
errorCode = "timeout";
|
|
128
|
+
finalMessage = "Request timeout. Please try again.";
|
|
560
129
|
}
|
|
561
130
|
else {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
errorCode = 'insufficient_quota';
|
|
565
|
-
if (lowerMessage.includes('access') && lowerMessage.includes('forbidden')) {
|
|
566
|
-
finalMessage =
|
|
567
|
-
'You exceeded your current quota, please check your plan and billing details.';
|
|
568
|
-
}
|
|
569
|
-
else {
|
|
570
|
-
finalMessage = cleanMessage;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
else if (lowerMessage.includes('max') && lowerMessage.includes('token')) {
|
|
574
|
-
errorType = 'invalid_request_error';
|
|
575
|
-
errorCode = 'context_length_exceeded';
|
|
576
|
-
errorParam = 'max_tokens';
|
|
577
|
-
finalMessage =
|
|
578
|
-
"This model's maximum context length is exceeded. Please reduce the length of the messages or completion.";
|
|
579
|
-
}
|
|
580
|
-
else if (lowerMessage.includes('401') || lowerMessage.includes('unauthorized')) {
|
|
581
|
-
errorType = 'invalid_request_error';
|
|
582
|
-
errorCode = 'invalid_api_key';
|
|
583
|
-
finalMessage =
|
|
584
|
-
'Incorrect API key provided. You can find your API key at https://platform.openai.com/account/api-keys.';
|
|
585
|
-
}
|
|
586
|
-
else if (lowerMessage.includes('429') || lowerMessage.includes('rate limit')) {
|
|
587
|
-
errorType = 'rate_limit_error';
|
|
588
|
-
errorCode = 'rate_limit_exceeded';
|
|
589
|
-
finalMessage = 'Rate limit reached. Please wait before making more requests.';
|
|
590
|
-
}
|
|
591
|
-
else if (lowerMessage.includes('timeout')) {
|
|
592
|
-
errorType = 'api_error';
|
|
593
|
-
errorCode = 'timeout';
|
|
594
|
-
finalMessage = 'Request timeout. Please try again.';
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
errorType = 'api_error';
|
|
598
|
-
errorCode = 'internal_error';
|
|
599
|
-
finalMessage = cleanMessage;
|
|
600
|
-
}
|
|
601
|
-
console.log('โ ๏ธ Using fallback error detection with cleaned message');
|
|
131
|
+
errorType = "api_error";
|
|
132
|
+
errorCode = "internal_error";
|
|
602
133
|
}
|
|
603
|
-
console.log('๐ Final error format:', {
|
|
604
|
-
message: finalMessage,
|
|
605
|
-
type: errorType,
|
|
606
|
-
param: errorParam,
|
|
607
|
-
code: errorCode,
|
|
608
|
-
});
|
|
609
134
|
returnData.push({
|
|
610
135
|
json: {
|
|
611
136
|
error: {
|