n8n-nodes-github-copilot 4.2.1 → 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.
Files changed (37) hide show
  1. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +1 -0
  2. package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +47 -15
  3. package/dist/nodes/GitHubCopilotChatAPI/nodeProperties.js +37 -0
  4. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.d.ts +1 -0
  5. package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +174 -10
  6. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.d.ts +1 -0
  7. package/dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js +65 -4
  8. package/dist/nodes/GitHubCopilotOpenAI/nodeProperties.js +64 -27
  9. package/dist/nodes/GitHubCopilotPGVector/GitHubCopilotPGVector.node.d.ts +10 -0
  10. package/dist/nodes/GitHubCopilotPGVector/GitHubCopilotPGVector.node.js +421 -0
  11. package/dist/package.json +3 -4
  12. package/dist/shared/models/DynamicModelLoader.d.ts +1 -0
  13. package/dist/shared/models/DynamicModelLoader.js +12 -0
  14. package/dist/shared/models/GitHubCopilotModels.d.ts +14 -8
  15. package/dist/shared/models/GitHubCopilotModels.js +255 -74
  16. package/dist/shared/utils/DynamicModelsManager.d.ts +11 -0
  17. package/dist/shared/utils/DynamicModelsManager.js +50 -0
  18. package/dist/shared/utils/GitHubCopilotApiUtils.d.ts +1 -0
  19. package/dist/shared/utils/GitHubCopilotApiUtils.js +85 -6
  20. package/package.json +3 -4
  21. package/shared/icons/copilot.svg +0 -34
  22. package/shared/index.ts +0 -27
  23. package/shared/models/DynamicModelLoader.ts +0 -124
  24. package/shared/models/GitHubCopilotModels.ts +0 -420
  25. package/shared/models/ModelVersionRequirements.ts +0 -165
  26. package/shared/properties/ModelProperties.ts +0 -52
  27. package/shared/properties/ModelSelectionProperty.ts +0 -68
  28. package/shared/utils/DynamicModelsManager.ts +0 -355
  29. package/shared/utils/EmbeddingsApiUtils.ts +0 -135
  30. package/shared/utils/FileChunkingApiUtils.ts +0 -176
  31. package/shared/utils/FileOptimizationUtils.ts +0 -210
  32. package/shared/utils/GitHubCopilotApiUtils.ts +0 -407
  33. package/shared/utils/GitHubCopilotEndpoints.ts +0 -212
  34. package/shared/utils/GitHubDeviceFlowHandler.ts +0 -276
  35. package/shared/utils/OAuthTokenManager.ts +0 -196
  36. package/shared/utils/provider-injection.ts +0 -277
  37. 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 modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(model);
79
- if (includeMedia) {
80
- if (modelInfo &&
81
- !(modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities.vision) &&
82
- !(modelInfo === null || modelInfo === void 0 ? void 0 : modelInfo.capabilities.multimodal)) {
83
- throw new Error(`Model ${model} does not support vision/image processing. Please select a model with vision capabilities.`);
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 if (!modelInfo) {
86
- console.warn(`âš ī¸ Model ${model} not found in known models list. Vision capability unknown - proceeding anyway.`);
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
- const hasMedia = includeMedia;
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 || ((_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.includes('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: ((_c = (_b = response.choices[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content) || '',
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: ((_d = response.choices[0]) === null || _d === void 0 ? void 0 : _d.finish_reason) || 'unknown',
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 || this.options.enableVision === true;
208
+ const shouldUseVision = hasVisionContent;
149
209
  if (shouldUseVision) {
150
- console.log(`đŸ‘ī¸ Sending vision request with Copilot-Vision-Request header (auto=${hasVisionContent}, manual=${this.options.enableVision})`);
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=${((_a = choice.message.content) === null || _a === void 0 ? void 0 : _a.length) || 0}, finish_reason=${choice.finish_reason}`);
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 (Image Processing)',
338
- name: 'enableVision',
464
+ displayName: 'Enable Vision Fallback',
465
+ name: 'enableVisionFallback',
339
466
  type: 'boolean',
340
467
  default: false,
341
- description: 'Enable vision capabilities for processing images. Required when sending images via chat. Only works with vision-capable models (GPT-4o, GPT-5, Claude, etc.).',
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
- ...(options.enableVision &&
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
- const copilotModel = modelMapping[model] || model;
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, false);
277
- const retriesUsed = ((_f = response._retryMetadata) === null || _f === void 0 ? void 0 : _f.retries) || 0;
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
  }