n8n-nodes-github-copilot 4.3.0 â 4.4.0
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/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +1 -0
- package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +47 -15
- package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.js +37 -0
- package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.d.ts +1 -0
- package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +174 -10
- package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -0
- package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +65 -4
- package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +64 -27
- package/dist/package.json +3 -4
- package/dist/shared/models/DynamicModelLoader.d.ts +1 -0
- package/dist/shared/models/DynamicModelLoader.js +12 -0
- package/dist/shared/models/GitHubCopilotModels.d.ts +14 -8
- package/dist/shared/models/GitHubCopilotModels.js +255 -74
- package/dist/shared/utils/DynamicModelsManager.d.ts +11 -0
- package/dist/shared/utils/DynamicModelsManager.js +50 -0
- package/dist/shared/utils/GitHubCopilotApiUtils.d.ts +1 -0
- package/dist/shared/utils/GitHubCopilotApiUtils.js +85 -6
- package/package.json +3 -4
- package/shared/icons/copilot.svg +0 -34
- package/shared/index.ts +0 -27
- package/shared/models/DynamicModelLoader.ts +0 -124
- package/shared/models/GitHubCopilotModels.ts +0 -420
- package/shared/models/ModelVersionRequirements.ts +0 -165
- package/shared/properties/ModelProperties.ts +0 -52
- package/shared/properties/ModelSelectionProperty.ts +0 -68
- package/shared/utils/DynamicModelsManager.ts +0 -355
- package/shared/utils/EmbeddingsApiUtils.ts +0 -135
- package/shared/utils/FileChunkingApiUtils.ts +0 -176
- package/shared/utils/FileOptimizationUtils.ts +0 -210
- package/shared/utils/GitHubCopilotApiUtils.ts +0 -407
- package/shared/utils/GitHubCopilotEndpoints.ts +0 -212
- package/shared/utils/GitHubDeviceFlowHandler.ts +0 -276
- package/shared/utils/OAuthTokenManager.ts +0 -196
- package/shared/utils/provider-injection.ts +0 -277
- package/shared/utils/version-detection.ts +0 -145
|
@@ -4,6 +4,7 @@ export declare class GitHubCopilotChatAPI implements INodeType {
|
|
|
4
4
|
methods: {
|
|
5
5
|
loadOptions: {
|
|
6
6
|
getAvailableModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
getVisionFallbackModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
8
|
};
|
|
8
9
|
};
|
|
9
10
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("./utils");
|
|
|
5
5
|
const nodeProperties_1 = require("./nodeProperties");
|
|
6
6
|
const mediaDetection_1 = require("./utils/mediaDetection");
|
|
7
7
|
const GitHubCopilotModels_1 = require("../../shared/models/GitHubCopilotModels");
|
|
8
|
+
const DynamicModelsManager_1 = require("../../shared/utils/DynamicModelsManager");
|
|
8
9
|
const GitHubCopilotEndpoints_1 = require("../../shared/utils/GitHubCopilotEndpoints");
|
|
9
10
|
const DynamicModelLoader_1 = require("../../shared/models/DynamicModelLoader");
|
|
10
11
|
class GitHubCopilotChatAPI {
|
|
@@ -35,11 +36,14 @@ class GitHubCopilotChatAPI {
|
|
|
35
36
|
async getAvailableModels() {
|
|
36
37
|
return await DynamicModelLoader_1.loadAvailableModels.call(this);
|
|
37
38
|
},
|
|
39
|
+
async getVisionFallbackModels() {
|
|
40
|
+
return await DynamicModelLoader_1.loadAvailableVisionModels.call(this);
|
|
41
|
+
},
|
|
38
42
|
},
|
|
39
43
|
};
|
|
40
44
|
}
|
|
41
45
|
async execute() {
|
|
42
|
-
var _a, _b, _c, _d;
|
|
46
|
+
var _a, _b, _c, _d, _e, _f;
|
|
43
47
|
const items = this.getInputData();
|
|
44
48
|
const returnData = [];
|
|
45
49
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -75,15 +79,38 @@ class GitHubCopilotChatAPI {
|
|
|
75
79
|
const enableRetry = advancedOptions.enableRetry !== false;
|
|
76
80
|
const maxRetries = advancedOptions.maxRetries || 3;
|
|
77
81
|
const includeMedia = this.getNodeParameter('includeMedia', i, false);
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
const credentials = await this.getCredentials('githubCopilotApi');
|
|
83
|
+
const oauthToken = credentials.oauthToken;
|
|
84
|
+
const hasVisionInMessage = userMessage.includes('data:image/') || !!userMessage.match(/\[.*image.*\]/i) || userMessage.startsWith('copilot-file://');
|
|
85
|
+
const hasVisionContent = includeMedia || hasVisionInMessage;
|
|
86
|
+
if (hasVisionInMessage) {
|
|
87
|
+
console.log(`đī¸ Vision content detected in message text (data URL or image reference)`);
|
|
88
|
+
}
|
|
89
|
+
let supportsVision = DynamicModelsManager_1.DynamicModelsManager.modelSupportsVision(oauthToken, model);
|
|
90
|
+
if (supportsVision === null) {
|
|
91
|
+
const modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(model);
|
|
92
|
+
supportsVision = !!(((_a = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities) === null || _a === void 0 ? void 0 : _a.vision) || ((_b = modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities) === null || _b === void 0 ? void 0 : _b.multimodal));
|
|
93
|
+
console.log(`đī¸ Vision check for model ${model}: using static list, supportsVision=${supportsVision}`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(`đī¸ Vision check for model ${model}: using API cache, supportsVision=${supportsVision}`);
|
|
97
|
+
}
|
|
98
|
+
let effectiveModel = model;
|
|
99
|
+
if (hasVisionContent && !supportsVision) {
|
|
100
|
+
const enableVisionFallback = advancedOptions.enableVisionFallback || false;
|
|
101
|
+
if (enableVisionFallback) {
|
|
102
|
+
const fallbackModelRaw = advancedOptions.visionFallbackModel;
|
|
103
|
+
const fallbackModel = fallbackModelRaw === '__manual__'
|
|
104
|
+
? advancedOptions.visionFallbackCustomModel
|
|
105
|
+
: fallbackModelRaw;
|
|
106
|
+
if (!fallbackModel || fallbackModel.trim() === '') {
|
|
107
|
+
throw new Error('Vision fallback enabled but no fallback model was selected or provided. Please select a vision-capable model in Advanced Options.');
|
|
108
|
+
}
|
|
109
|
+
effectiveModel = fallbackModel;
|
|
110
|
+
console.log(`đī¸ Model ${model} does not support vision - using fallback model: ${effectiveModel}`);
|
|
84
111
|
}
|
|
85
|
-
else
|
|
86
|
-
|
|
112
|
+
else {
|
|
113
|
+
throw new Error(`Model ${model} 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.`);
|
|
87
114
|
}
|
|
88
115
|
}
|
|
89
116
|
const messages = [];
|
|
@@ -134,12 +161,15 @@ class GitHubCopilotChatAPI {
|
|
|
134
161
|
});
|
|
135
162
|
}
|
|
136
163
|
const requestBody = {
|
|
137
|
-
model,
|
|
164
|
+
model: effectiveModel,
|
|
138
165
|
messages,
|
|
139
166
|
stream: false,
|
|
140
167
|
...advancedOptions,
|
|
141
168
|
};
|
|
142
|
-
|
|
169
|
+
delete requestBody.enableVisionFallback;
|
|
170
|
+
delete requestBody.visionFallbackModel;
|
|
171
|
+
delete requestBody.visionFallbackCustomModel;
|
|
172
|
+
const hasMedia = hasVisionContent;
|
|
143
173
|
let response = null;
|
|
144
174
|
let attempt = 1;
|
|
145
175
|
const totalAttempts = maxRetries + 1;
|
|
@@ -153,7 +183,7 @@ class GitHubCopilotChatAPI {
|
|
|
153
183
|
catch (error) {
|
|
154
184
|
const isLastAttempt = attempt >= totalAttempts;
|
|
155
185
|
const errorObj = error;
|
|
156
|
-
const is403Error = errorObj.status === 403 || ((
|
|
186
|
+
const is403Error = errorObj.status === 403 || ((_c = errorObj.message) === null || _c === void 0 ? void 0 : _c.includes('403'));
|
|
157
187
|
if (is403Error && enableRetry && !isLastAttempt) {
|
|
158
188
|
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
|
|
159
189
|
console.log(`GitHub Copilot API attempt ${attempt}/${totalAttempts} failed with 403, retrying in ${delay}ms...`);
|
|
@@ -169,11 +199,13 @@ class GitHubCopilotChatAPI {
|
|
|
169
199
|
throw new Error(`Failed to get response from GitHub Copilot API after ${totalAttempts} attempts (${retriesUsed} retries)`);
|
|
170
200
|
}
|
|
171
201
|
const result = {
|
|
172
|
-
message: ((
|
|
173
|
-
model,
|
|
202
|
+
message: ((_e = (_d = response.choices[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content) || '',
|
|
203
|
+
model: effectiveModel,
|
|
204
|
+
originalModel: effectiveModel !== model ? model : undefined,
|
|
205
|
+
usedVisionFallback: effectiveModel !== model,
|
|
174
206
|
operation,
|
|
175
207
|
usage: response.usage || null,
|
|
176
|
-
finish_reason: ((
|
|
208
|
+
finish_reason: ((_f = response.choices[0]) === null || _f === void 0 ? void 0 : _f.finish_reason) || 'unknown',
|
|
177
209
|
retries: retriesUsed,
|
|
178
210
|
};
|
|
179
211
|
returnData.push({
|
|
@@ -179,6 +179,43 @@ exports.nodeProperties = [
|
|
|
179
179
|
},
|
|
180
180
|
},
|
|
181
181
|
},
|
|
182
|
+
{
|
|
183
|
+
displayName: 'Enable Vision Fallback',
|
|
184
|
+
name: 'enableVisionFallback',
|
|
185
|
+
type: 'boolean',
|
|
186
|
+
default: false,
|
|
187
|
+
description: 'When the primary model does not support vision, automatically use a vision-capable fallback model to process images. Enable this if you want to send images but your primary model does not support vision.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
displayName: 'Vision Fallback Model',
|
|
191
|
+
name: 'visionFallbackModel',
|
|
192
|
+
type: 'options',
|
|
193
|
+
typeOptions: {
|
|
194
|
+
loadOptionsMethod: 'getVisionFallbackModels',
|
|
195
|
+
},
|
|
196
|
+
default: '',
|
|
197
|
+
description: 'Select a vision-capable model to use when processing images with a non-vision primary model',
|
|
198
|
+
displayOptions: {
|
|
199
|
+
show: {
|
|
200
|
+
enableVisionFallback: [true],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
displayName: 'Custom Vision Model',
|
|
206
|
+
name: 'visionFallbackCustomModel',
|
|
207
|
+
type: 'string',
|
|
208
|
+
default: '',
|
|
209
|
+
placeholder: 'gpt-4o, claude-sonnet-4, gemini-2.0-flash, etc.',
|
|
210
|
+
description: 'Enter the model name manually for vision fallback',
|
|
211
|
+
hint: 'Enter the exact model ID for vision processing (e.g., gpt-4o, claude-sonnet-4)',
|
|
212
|
+
displayOptions: {
|
|
213
|
+
show: {
|
|
214
|
+
enableVisionFallback: [true],
|
|
215
|
+
visionFallbackModel: ['__manual__'],
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
182
219
|
],
|
|
183
220
|
},
|
|
184
221
|
];
|
|
@@ -4,6 +4,7 @@ export declare class GitHubCopilotChatModel implements INodeType {
|
|
|
4
4
|
methods: {
|
|
5
5
|
loadOptions: {
|
|
6
6
|
getAvailableModels(this: import("n8n-workflow").ILoadOptionsFunctions): Promise<import("n8n-workflow").INodePropertyOptions[]>;
|
|
7
|
+
getVisionFallbackModels(this: import("n8n-workflow").ILoadOptionsFunctions): Promise<import("n8n-workflow").INodePropertyOptions[]>;
|
|
7
8
|
};
|
|
8
9
|
};
|
|
9
10
|
supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData>;
|
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.GitHubCopilotChatModel = void 0;
|
|
4
37
|
const openai_1 = require("@langchain/openai");
|
|
5
38
|
const messages_1 = require("@langchain/core/messages");
|
|
6
39
|
const GitHubCopilotModels_1 = require("../../shared/models/GitHubCopilotModels");
|
|
7
40
|
const GitHubCopilotEndpoints_1 = require("../../shared/utils/GitHubCopilotEndpoints");
|
|
41
|
+
const DynamicModelsManager_1 = require("../../shared/utils/DynamicModelsManager");
|
|
8
42
|
const DynamicModelLoader_1 = require("../../shared/models/DynamicModelLoader");
|
|
9
43
|
const ModelProperties_1 = require("../../shared/properties/ModelProperties");
|
|
10
44
|
const ModelVersionRequirements_1 = require("../../shared/models/ModelVersionRequirements");
|
|
@@ -14,6 +48,7 @@ class GitHubCopilotChatOpenAI extends openai_1.ChatOpenAI {
|
|
|
14
48
|
super(config);
|
|
15
49
|
this.context = context;
|
|
16
50
|
this.options = options;
|
|
51
|
+
this.oauthToken = config.configuration.apiKey;
|
|
17
52
|
}
|
|
18
53
|
invocationParams(options) {
|
|
19
54
|
const params = super.invocationParams(options);
|
|
@@ -21,7 +56,7 @@ class GitHubCopilotChatOpenAI extends openai_1.ChatOpenAI {
|
|
|
21
56
|
return params;
|
|
22
57
|
}
|
|
23
58
|
async _generate(messages, options) {
|
|
24
|
-
var _a;
|
|
59
|
+
var _a, _b, _c;
|
|
25
60
|
if (!messages || messages.length === 0) {
|
|
26
61
|
throw new Error('No messages provided for generation');
|
|
27
62
|
}
|
|
@@ -45,7 +80,7 @@ class GitHubCopilotChatOpenAI extends openai_1.ChatOpenAI {
|
|
|
45
80
|
let content = '';
|
|
46
81
|
const rawContent = msg.content;
|
|
47
82
|
if (typeof rawContent === 'string') {
|
|
48
|
-
if (rawContent.includes('data:image/') || rawContent.match(/\[.*image.*\]/i)) {
|
|
83
|
+
if (rawContent.includes('data:image/') || rawContent.match(/\[.*image.*\]/i) || rawContent.startsWith('copilot-file://')) {
|
|
49
84
|
hasVisionContent = true;
|
|
50
85
|
console.log(`đī¸ Vision content detected in string message (data URL or image reference)`);
|
|
51
86
|
}
|
|
@@ -139,17 +174,109 @@ class GitHubCopilotChatOpenAI extends openai_1.ChatOpenAI {
|
|
|
139
174
|
top_p: this.topP,
|
|
140
175
|
stream: this.options.enableStreaming || false,
|
|
141
176
|
};
|
|
177
|
+
if (hasVisionContent) {
|
|
178
|
+
let baseSupportsVision = DynamicModelsManager_1.DynamicModelsManager.modelSupportsVision(this.oauthToken, this.model);
|
|
179
|
+
if (baseSupportsVision === null) {
|
|
180
|
+
const baseModelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(this.model);
|
|
181
|
+
baseSupportsVision = !!(((_a = baseModelInfo === null || baseModelInfo === void 0 ? void 0 : baseModelInfo.capabilities) === null || _a === void 0 ? void 0 : _a.vision) || ((_b = baseModelInfo === null || baseModelInfo === void 0 ? void 0 : baseModelInfo.capabilities) === null || _b === void 0 ? void 0 : _b.multimodal));
|
|
182
|
+
console.log(`đī¸ Vision check for model ${this.model}: using static list, supportsVision=${baseSupportsVision}`);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log(`đī¸ Vision check for model ${this.model}: using API cache, supportsVision=${baseSupportsVision}`);
|
|
186
|
+
}
|
|
187
|
+
if (!baseSupportsVision) {
|
|
188
|
+
if (this.options.enableVisionFallback) {
|
|
189
|
+
const fallbackRaw = this.options.visionFallbackModel;
|
|
190
|
+
const fallbackModel = fallbackRaw === '__manual__' ? this.options.visionFallbackCustomModel : fallbackRaw;
|
|
191
|
+
if (!fallbackModel || fallbackModel.trim() === '') {
|
|
192
|
+
throw new Error('Vision fallback enabled but no fallback model was selected or provided');
|
|
193
|
+
}
|
|
194
|
+
requestBody.model = fallbackModel;
|
|
195
|
+
console.log(`đī¸ Using vision fallback model ${fallbackModel} for image processing`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
throw new Error('Selected model does not support vision; enable Vision Fallback and pick a fallback model to process images.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
142
202
|
if (this.options.tools && JSON.parse(this.options.tools).length > 0) {
|
|
143
203
|
requestBody.tools = JSON.parse(this.options.tools);
|
|
144
204
|
requestBody.tool_choice = this.options.tool_choice || 'auto';
|
|
145
205
|
console.log(`đ§ Request includes ${requestBody.tools.length} tools`);
|
|
146
206
|
}
|
|
147
207
|
const startTime = Date.now();
|
|
148
|
-
const shouldUseVision = hasVisionContent
|
|
208
|
+
const shouldUseVision = hasVisionContent;
|
|
149
209
|
if (shouldUseVision) {
|
|
150
|
-
console.log(`đī¸ Sending vision request with Copilot-Vision-Request header (
|
|
210
|
+
console.log(`đī¸ Sending vision request with Copilot-Vision-Request header (images detected)`);
|
|
151
211
|
}
|
|
152
212
|
try {
|
|
213
|
+
if (hasVisionContent) {
|
|
214
|
+
console.log('đī¸ Preparing image uploads for vision content...');
|
|
215
|
+
const SUPPORTED_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/webp'];
|
|
216
|
+
for (const msg of requestBody.messages) {
|
|
217
|
+
if (!(msg === null || msg === void 0 ? void 0 : msg.content) || !Array.isArray(msg.content))
|
|
218
|
+
continue;
|
|
219
|
+
for (const part of msg.content) {
|
|
220
|
+
if ((part === null || part === void 0 ? void 0 : part.type) === 'image_url' && part.image_url && part.image_url.url) {
|
|
221
|
+
const url = String(part.image_url.url || '');
|
|
222
|
+
try {
|
|
223
|
+
let buffer = null;
|
|
224
|
+
let mime = 'image/png';
|
|
225
|
+
let filename = `upload-${Date.now()}.png`;
|
|
226
|
+
if (url.startsWith('data:image/')) {
|
|
227
|
+
const match = url.match(/^data:(image\/[^;]+);base64,(.*)$/);
|
|
228
|
+
if (match) {
|
|
229
|
+
mime = match[1].toLowerCase();
|
|
230
|
+
const base64 = match[2];
|
|
231
|
+
buffer = Buffer.from(base64, 'base64');
|
|
232
|
+
const ext = mime.split('/').pop() || 'png';
|
|
233
|
+
filename = `image-${Date.now()}.${ext}`;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
237
|
+
const res = await fetch(url);
|
|
238
|
+
if (!res.ok)
|
|
239
|
+
throw new Error(`Failed to download image: ${res.status}`);
|
|
240
|
+
mime = (res.headers.get('content-type') || 'image/png').toLowerCase();
|
|
241
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
242
|
+
buffer = Buffer.from(arrayBuffer);
|
|
243
|
+
const ext = (mime.split('/')[1] || 'png').split('+')[0].split(';')[0];
|
|
244
|
+
filename = `image-${Date.now()}.${ext}`;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log(`â ī¸ Skipping unsupported URL format: ${url.substring(0, 50)}...`);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (!SUPPORTED_IMAGE_TYPES.includes(mime)) {
|
|
251
|
+
console.warn(`â ī¸ Image type ${mime} may not be supported. Supported: ${SUPPORTED_IMAGE_TYPES.join(', ')}`);
|
|
252
|
+
}
|
|
253
|
+
console.log(`đī¸ Processing image: ${filename}, type: ${mime}, size: ${(buffer === null || buffer === void 0 ? void 0 : buffer.length) || 0} bytes`);
|
|
254
|
+
if (buffer) {
|
|
255
|
+
try {
|
|
256
|
+
const uploadResult = await Promise.resolve().then(() => __importStar(require('../../shared/utils/GitHubCopilotApiUtils'))).then(m => m.uploadFileToCopilot(this.context, buffer, filename, mime));
|
|
257
|
+
const newUrl = (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.url) || (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.file_url) || (uploadResult === null || uploadResult === void 0 ? void 0 : uploadResult.id) ? (uploadResult.url || `copilot-file://${uploadResult.id}`) : null;
|
|
258
|
+
if (newUrl) {
|
|
259
|
+
part.image_url.url = newUrl;
|
|
260
|
+
console.log(`đī¸ Uploaded image and replaced URL with ${newUrl}`);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
console.warn('â ī¸ File upload succeeded but no URL/id returned by API', uploadResult);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
console.error('â Image upload failed:', err instanceof Error ? err.message : String(err));
|
|
268
|
+
throw err;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
console.error('â Preparing/uploading image failed:', err instanceof Error ? err.message : String(err));
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
153
280
|
const response = await (0, GitHubCopilotApiUtils_1.makeGitHubCopilotRequest)(this.context, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, shouldUseVision);
|
|
154
281
|
const endTime = Date.now();
|
|
155
282
|
const latency = endTime - startTime;
|
|
@@ -164,7 +291,7 @@ class GitHubCopilotChatOpenAI extends openai_1.ChatOpenAI {
|
|
|
164
291
|
const langchainMessage = new messages_1.AIMessage({
|
|
165
292
|
content: choice.message.content || '',
|
|
166
293
|
});
|
|
167
|
-
console.log(`đ Response: role=${choice.message.role}, content_length=${((
|
|
294
|
+
console.log(`đ Response: role=${choice.message.role}, content_length=${((_c = choice.message.content) === null || _c === void 0 ? void 0 : _c.length) || 0}, finish_reason=${choice.finish_reason}`);
|
|
168
295
|
const generation = {
|
|
169
296
|
text: choice.message.content || '',
|
|
170
297
|
generationInfo: {
|
|
@@ -334,11 +461,41 @@ class GitHubCopilotChatModel {
|
|
|
334
461
|
},
|
|
335
462
|
},
|
|
336
463
|
{
|
|
337
|
-
displayName: 'Enable Vision
|
|
338
|
-
name: '
|
|
464
|
+
displayName: 'Enable Vision Fallback',
|
|
465
|
+
name: 'enableVisionFallback',
|
|
339
466
|
type: 'boolean',
|
|
340
467
|
default: false,
|
|
341
|
-
description: '
|
|
468
|
+
description: 'When the primary model does not support vision, automatically use a vision-capable fallback model to process images. Enable this if you want to send images but your primary model does not support vision.',
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
displayName: 'Vision Fallback Model',
|
|
472
|
+
name: 'visionFallbackModel',
|
|
473
|
+
type: 'options',
|
|
474
|
+
typeOptions: {
|
|
475
|
+
loadOptionsMethod: 'getVisionFallbackModels',
|
|
476
|
+
},
|
|
477
|
+
default: '',
|
|
478
|
+
description: 'Select a vision-capable model to use when processing images with a non-vision primary model',
|
|
479
|
+
displayOptions: {
|
|
480
|
+
show: {
|
|
481
|
+
enableVisionFallback: [true],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
displayName: 'Custom Vision Model',
|
|
487
|
+
name: 'visionFallbackCustomModel',
|
|
488
|
+
type: 'string',
|
|
489
|
+
default: '',
|
|
490
|
+
placeholder: 'gpt-4o, claude-sonnet-4, gemini-2.0-flash, etc.',
|
|
491
|
+
description: 'Enter the model name manually for vision fallback',
|
|
492
|
+
hint: 'Enter the exact model ID for vision processing (e.g., gpt-4o, claude-sonnet-4)',
|
|
493
|
+
displayOptions: {
|
|
494
|
+
show: {
|
|
495
|
+
enableVisionFallback: [true],
|
|
496
|
+
visionFallbackModel: ['__manual__'],
|
|
497
|
+
},
|
|
498
|
+
},
|
|
342
499
|
},
|
|
343
500
|
],
|
|
344
501
|
},
|
|
@@ -349,10 +506,14 @@ class GitHubCopilotChatModel {
|
|
|
349
506
|
async getAvailableModels() {
|
|
350
507
|
return await DynamicModelLoader_1.loadAvailableModels.call(this);
|
|
351
508
|
},
|
|
509
|
+
async getVisionFallbackModels() {
|
|
510
|
+
return await DynamicModelLoader_1.loadAvailableVisionModels.call(this);
|
|
511
|
+
},
|
|
352
512
|
},
|
|
353
513
|
};
|
|
354
514
|
}
|
|
355
515
|
async supplyData(itemIndex) {
|
|
516
|
+
var _a, _b;
|
|
356
517
|
let model = this.getNodeParameter('model', itemIndex);
|
|
357
518
|
if (model === '__manual__') {
|
|
358
519
|
const customModel = this.getNodeParameter('customModel', itemIndex);
|
|
@@ -387,6 +548,10 @@ class GitHubCopilotChatModel {
|
|
|
387
548
|
const minVSCodeVersion = (0, ModelVersionRequirements_1.getMinVSCodeVersion)(safeModel);
|
|
388
549
|
const additionalHeaders = (0, ModelVersionRequirements_1.getAdditionalHeaders)(safeModel);
|
|
389
550
|
console.log(`đ§ Model: ${safeModel} requires VS Code version: ${minVSCodeVersion}`);
|
|
551
|
+
if (((_a = safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities) === null || _a === void 0 ? void 0 : _a.vision) || ((_b = safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities) === null || _b === void 0 ? void 0 : _b.multimodal)) {
|
|
552
|
+
options.enableVision = true;
|
|
553
|
+
console.log(`đī¸ Model ${safeModel} supports vision - enabling vision automatically`);
|
|
554
|
+
}
|
|
390
555
|
let parsedTools = [];
|
|
391
556
|
if (options.tools && options.tools.trim()) {
|
|
392
557
|
try {
|
|
@@ -427,8 +592,7 @@ class GitHubCopilotChatModel {
|
|
|
427
592
|
'OpenAI-Intent': 'conversation-panel',
|
|
428
593
|
'Copilot-Integration-Id': 'vscode-chat',
|
|
429
594
|
...additionalHeaders,
|
|
430
|
-
...(
|
|
431
|
-
(safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities.vision) && {
|
|
595
|
+
...((safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities.vision) && {
|
|
432
596
|
'Copilot-Vision-Request': 'true',
|
|
433
597
|
'Copilot-Media-Request': 'true',
|
|
434
598
|
}),
|
|
@@ -4,6 +4,7 @@ export declare class GitHubCopilotOpenAI implements INodeType {
|
|
|
4
4
|
methods: {
|
|
5
5
|
loadOptions: {
|
|
6
6
|
getAvailableModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
getVisionFallbackModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
8
|
};
|
|
8
9
|
};
|
|
9
10
|
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
@@ -6,6 +6,8 @@ const nodeProperties_1 = require("./nodeProperties");
|
|
|
6
6
|
const utils_1 = require("../GitHubCopilotChatAPI/utils");
|
|
7
7
|
const GitHubCopilotEndpoints_1 = require("../../shared/utils/GitHubCopilotEndpoints");
|
|
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");
|
|
9
11
|
class GitHubCopilotOpenAI {
|
|
10
12
|
constructor() {
|
|
11
13
|
this.description = {
|
|
@@ -34,11 +36,14 @@ class GitHubCopilotOpenAI {
|
|
|
34
36
|
async getAvailableModels() {
|
|
35
37
|
return await DynamicModelLoader_1.loadAvailableModels.call(this);
|
|
36
38
|
},
|
|
39
|
+
async getVisionFallbackModels() {
|
|
40
|
+
return await DynamicModelLoader_1.loadAvailableVisionModels.call(this);
|
|
41
|
+
},
|
|
37
42
|
},
|
|
38
43
|
};
|
|
39
44
|
}
|
|
40
45
|
async execute() {
|
|
41
|
-
var _a, _b, _c, _d, _e, _f;
|
|
46
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
42
47
|
const items = this.getInputData();
|
|
43
48
|
const returnData = [];
|
|
44
49
|
for (let i = 0; i < items.length; i++) {
|
|
@@ -228,7 +233,62 @@ class GitHubCopilotOpenAI {
|
|
|
228
233
|
'o1-preview': 'o1-preview',
|
|
229
234
|
'o1-mini': 'o1-mini',
|
|
230
235
|
};
|
|
231
|
-
|
|
236
|
+
let copilotModel = modelMapping[model] || model;
|
|
237
|
+
let hasVisionContent = false;
|
|
238
|
+
for (const msg of messages) {
|
|
239
|
+
const content = msg.content;
|
|
240
|
+
const type = msg.type;
|
|
241
|
+
if (type === 'file' || type === 'image') {
|
|
242
|
+
hasVisionContent = true;
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
if (typeof content === 'string') {
|
|
246
|
+
if (content.includes('data:image/') || content.match(/\[.*image.*\]/i) || content.startsWith('copilot-file://')) {
|
|
247
|
+
hasVisionContent = true;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
else if (Array.isArray(content)) {
|
|
252
|
+
for (const part of content) {
|
|
253
|
+
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') {
|
|
254
|
+
hasVisionContent = true;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (hasVisionContent)
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (hasVisionContent) {
|
|
263
|
+
const credentials = await this.getCredentials('githubCopilotApi');
|
|
264
|
+
const oauthToken = credentials.oauthToken;
|
|
265
|
+
let supportsVision = DynamicModelsManager_1.DynamicModelsManager.modelSupportsVision(oauthToken, copilotModel);
|
|
266
|
+
if (supportsVision === null) {
|
|
267
|
+
const modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(copilotModel);
|
|
268
|
+
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));
|
|
269
|
+
console.log(`đī¸ Vision check for model ${copilotModel}: using static list, supportsVision=${supportsVision}`);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
console.log(`đī¸ Vision check for model ${copilotModel}: using API cache, supportsVision=${supportsVision}`);
|
|
273
|
+
}
|
|
274
|
+
if (!supportsVision) {
|
|
275
|
+
const enableVisionFallback = advancedOptions.enableVisionFallback || false;
|
|
276
|
+
if (enableVisionFallback) {
|
|
277
|
+
const fallbackModelRaw = advancedOptions.visionFallbackModel;
|
|
278
|
+
const fallbackModel = fallbackModelRaw === '__manual__'
|
|
279
|
+
? advancedOptions.visionFallbackCustomModel
|
|
280
|
+
: fallbackModelRaw;
|
|
281
|
+
if (!fallbackModel || fallbackModel.trim() === '') {
|
|
282
|
+
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 });
|
|
283
|
+
}
|
|
284
|
+
console.log(`đī¸ Model ${copilotModel} does not support vision - using fallback model: ${fallbackModel}`);
|
|
285
|
+
copilotModel = fallbackModel;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
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 });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
232
292
|
const requestBody = {
|
|
233
293
|
model: copilotModel,
|
|
234
294
|
messages,
|
|
@@ -272,9 +332,10 @@ class GitHubCopilotOpenAI {
|
|
|
272
332
|
console.log('đ Sending request to GitHub Copilot API:');
|
|
273
333
|
console.log(' Model:', copilotModel);
|
|
274
334
|
console.log(' Messages count:', messages.length);
|
|
335
|
+
console.log(' Has Vision Content:', hasVisionContent);
|
|
275
336
|
console.log(' Request body:', JSON.stringify(requestBody, null, 2));
|
|
276
|
-
const response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody,
|
|
277
|
-
const retriesUsed = ((
|
|
337
|
+
const response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, hasVisionContent);
|
|
338
|
+
const retriesUsed = ((_h = response._retryMetadata) === null || _h === void 0 ? void 0 : _h.retries) || 0;
|
|
278
339
|
if (retriesUsed > 0) {
|
|
279
340
|
console.log(`âšī¸ Request completed with ${retriesUsed} retry(ies)`);
|
|
280
341
|
}
|