n8n-nodes-github-copilot 3.22.0 → 3.24.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.
@@ -49,7 +49,7 @@ class GitHubCopilot {
49
49
  this.description = {
50
50
  displayName: 'GitHub Copilot',
51
51
  name: 'gitHubCopilot',
52
- icon: 'file:githubcopilot.svg',
52
+ icon: 'file:../../shared/icons/copilot.svg',
53
53
  group: ['transform'],
54
54
  version: 1,
55
55
  subtitle: '={{$parameter["operation"]}}',
@@ -5,12 +5,13 @@ 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 GitHubCopilotEndpoints_1 = require("../../shared/utils/GitHubCopilotEndpoints");
8
9
  class GitHubCopilotChatAPI {
9
10
  constructor() {
10
11
  this.description = {
11
12
  displayName: 'GitHub Copilot Chat API',
12
13
  name: 'gitHubCopilotChatAPI',
13
- icon: 'file:../../../shared/icons/copilot.svg',
14
+ icon: 'file:../../shared/icons/copilot.svg',
14
15
  group: ['AI'],
15
16
  version: 1,
16
17
  subtitle: '={{$parameter["operation"] + ": " + $parameter["model"]}}',
@@ -30,7 +31,7 @@ class GitHubCopilotChatAPI {
30
31
  };
31
32
  }
32
33
  async execute() {
33
- var _a, _b, _c;
34
+ var _a, _b, _c, _d;
34
35
  const items = this.getInputData();
35
36
  const returnData = [];
36
37
  for (let i = 0; i < items.length; i++) {
@@ -41,6 +42,8 @@ class GitHubCopilotChatAPI {
41
42
  const userMessage = this.getNodeParameter('message', i);
42
43
  const systemMessage = this.getNodeParameter('systemMessage', i, '');
43
44
  const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
45
+ const enableRetry = advancedOptions.enableRetry !== false;
46
+ const maxRetries = advancedOptions.maxRetries || 3;
44
47
  const includeMedia = this.getNodeParameter('includeMedia', i, false);
45
48
  const modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(model);
46
49
  if (includeMedia) {
@@ -104,13 +107,36 @@ class GitHubCopilotChatAPI {
104
107
  ...advancedOptions,
105
108
  };
106
109
  const hasMedia = includeMedia;
107
- const response = await (0, utils_1.makeApiRequest)(this, '/chat/completions', requestBody, hasMedia);
110
+ let response = null;
111
+ let attempt = 1;
112
+ while (attempt <= maxRetries + 1) {
113
+ try {
114
+ response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, hasMedia);
115
+ break;
116
+ }
117
+ catch (error) {
118
+ const isLastAttempt = attempt >= maxRetries + 1;
119
+ const errorObj = error;
120
+ const is403Error = errorObj.status === 403 || ((_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.includes('403'));
121
+ if (is403Error && enableRetry && !isLastAttempt) {
122
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
123
+ console.log(`GitHub Copilot API attempt ${attempt} failed with 403, retrying in ${delay}ms...`);
124
+ await new Promise(resolve => setTimeout(resolve, delay));
125
+ attempt++;
126
+ continue;
127
+ }
128
+ throw error;
129
+ }
130
+ }
131
+ if (!response) {
132
+ throw new Error('Failed to get response from GitHub Copilot API after all retry attempts');
133
+ }
108
134
  const result = {
109
- message: ((_b = (_a = response.choices[0]) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.content) || '',
135
+ message: ((_c = (_b = response.choices[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content) || '',
110
136
  model,
111
137
  operation,
112
138
  usage: response.usage || null,
113
- finish_reason: ((_c = response.choices[0]) === null || _c === void 0 ? void 0 : _c.finish_reason) || 'unknown',
139
+ finish_reason: ((_d = response.choices[0]) === null || _d === void 0 ? void 0 : _d.finish_reason) || 'unknown',
114
140
  };
115
141
  returnData.push({
116
142
  json: result,
@@ -167,6 +167,25 @@ exports.nodeProperties = [
167
167
  default: 1,
168
168
  description: 'Alternative to temperature, controls diversity via nucleus sampling',
169
169
  },
170
+ {
171
+ displayName: 'Auto Retry on 403 Error',
172
+ name: 'enableRetry',
173
+ type: 'boolean',
174
+ default: true,
175
+ description: 'Automatically retry requests when hitting TPM (Transactions Per Minute) quota limits (HTTP 403)',
176
+ },
177
+ {
178
+ displayName: 'Max Retry Attempts',
179
+ name: 'maxRetries',
180
+ type: 'number',
181
+ default: 3,
182
+ description: 'Maximum number of retry attempts for 403 errors',
183
+ displayOptions: {
184
+ show: {
185
+ enableRetry: [true],
186
+ },
187
+ },
188
+ },
170
189
  ],
171
190
  },
172
191
  ];
@@ -99,6 +99,25 @@ class GitHubCopilotChatModel {
99
99
  rows: 3,
100
100
  },
101
101
  },
102
+ {
103
+ displayName: 'Auto Retry on 403 Error',
104
+ name: 'enableRetry',
105
+ type: 'boolean',
106
+ default: true,
107
+ description: 'Automatically retry requests when hitting TPM (Transactions Per Minute) quota limits (HTTP 403)',
108
+ },
109
+ {
110
+ displayName: 'Max Retry Attempts',
111
+ name: 'maxRetries',
112
+ type: 'number',
113
+ default: 3,
114
+ description: 'Maximum number of retry attempts for 403 errors',
115
+ displayOptions: {
116
+ show: {
117
+ enableRetry: [true],
118
+ },
119
+ },
120
+ },
102
121
  ],
103
122
  },
104
123
  ],
@@ -131,6 +150,7 @@ class GitHubCopilotChatModel {
131
150
  temperature: options.temperature || 0.7,
132
151
  maxTokens: Math.min(options.maxTokens || 1000, (safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities.maxOutputTokens) || 4096),
133
152
  topP: options.topP || 1,
153
+ maxRetries: options.enableRetry !== false ? (options.maxRetries || 3) : 0,
134
154
  configuration: {
135
155
  baseURL: GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL,
136
156
  apiKey: token,
@@ -83,6 +83,230 @@ async function listAvailableModels(token, enableRetry = true, maxRetries = 3) {
83
83
  },
84
84
  };
85
85
  }
86
+ async function consolidatedModelTest(token, enableRetry = true, maxRetries = 3) {
87
+ const testStartTime = Date.now();
88
+ const testResults = {};
89
+ let totalTests = 0;
90
+ let successfulTests = 0;
91
+ let failedTests = 0;
92
+ try {
93
+ console.log('🧪 Starting Consolidated Model Test...');
94
+ const modelsResponse = await listAvailableModels(token, enableRetry, maxRetries);
95
+ if (!modelsResponse.success || !modelsResponse.data) {
96
+ return {
97
+ success: false,
98
+ timestamp: new Date().toISOString(),
99
+ error: 'Failed to fetch models list for consolidated test',
100
+ details: modelsResponse,
101
+ };
102
+ }
103
+ const availableModels = modelsResponse.data;
104
+ const testMessage = "Hello! Please respond with just 'OK' to confirm you're working.";
105
+ const testsPerModel = 5;
106
+ console.log(`📊 Testing ${availableModels.length} models, ${testsPerModel} times each...`);
107
+ for (const modelItem of availableModels) {
108
+ const model = modelItem;
109
+ const modelId = model.id || model.name;
110
+ const modelResults = {
111
+ modelInfo: {
112
+ id: modelId,
113
+ name: model.name || modelId,
114
+ vendor: model.vendor || 'unknown',
115
+ capabilities: model.capabilities || {},
116
+ },
117
+ tests: [],
118
+ summary: {
119
+ totalAttempts: 0,
120
+ successful: 0,
121
+ failed: 0,
122
+ successRate: 0,
123
+ avgResponseTime: 0,
124
+ avgTokensUsed: 0,
125
+ },
126
+ };
127
+ console.log(`🔍 Testing model: ${modelId}`);
128
+ for (let testNum = 1; testNum <= testsPerModel; testNum++) {
129
+ const testStart = Date.now();
130
+ totalTests++;
131
+ modelResults.summary.totalAttempts++;
132
+ try {
133
+ const response = await fetch(GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getChatCompletionsUrl(), {
134
+ method: 'POST',
135
+ headers: GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getAuthHeaders(token),
136
+ body: JSON.stringify({
137
+ model: modelId,
138
+ messages: [
139
+ {
140
+ role: 'user',
141
+ content: testMessage
142
+ }
143
+ ],
144
+ max_tokens: 10,
145
+ temperature: 0.1,
146
+ }),
147
+ });
148
+ const testEnd = Date.now();
149
+ const responseTime = testEnd - testStart;
150
+ if (response.ok) {
151
+ const data = await response.json();
152
+ successfulTests++;
153
+ modelResults.summary.successful++;
154
+ const choices = data.choices || [];
155
+ const firstChoice = choices[0] || {};
156
+ const message = firstChoice.message || {};
157
+ const usage = data.usage || {};
158
+ const testResult = {
159
+ testNumber: testNum,
160
+ success: true,
161
+ responseTime: responseTime,
162
+ response: message.content || 'No content',
163
+ usage: usage || null,
164
+ finishReason: firstChoice.finish_reason || 'unknown',
165
+ timestamp: new Date().toISOString(),
166
+ };
167
+ modelResults.tests.push(testResult);
168
+ const totalTokens = usage.total_tokens;
169
+ if (totalTokens) {
170
+ modelResults.summary.avgTokensUsed += totalTokens;
171
+ }
172
+ }
173
+ else {
174
+ const errorText = await response.text();
175
+ failedTests++;
176
+ modelResults.summary.failed++;
177
+ modelResults.tests.push({
178
+ testNumber: testNum,
179
+ success: false,
180
+ responseTime: responseTime,
181
+ error: `HTTP ${response.status}: ${errorText}`,
182
+ timestamp: new Date().toISOString(),
183
+ });
184
+ }
185
+ }
186
+ catch (error) {
187
+ const testEnd = Date.now();
188
+ const responseTime = testEnd - testStart;
189
+ failedTests++;
190
+ modelResults.summary.failed++;
191
+ modelResults.tests.push({
192
+ testNumber: testNum,
193
+ success: false,
194
+ responseTime: responseTime,
195
+ error: error instanceof Error ? error.message : 'Unknown error',
196
+ timestamp: new Date().toISOString(),
197
+ });
198
+ }
199
+ await new Promise(resolve => setTimeout(resolve, 100));
200
+ }
201
+ const successfulResponses = modelResults.tests.filter((t) => {
202
+ const test = t;
203
+ return test.success === true;
204
+ });
205
+ if (successfulResponses.length > 0) {
206
+ const totalResponseTime = successfulResponses.reduce((sum, t) => {
207
+ const test = t;
208
+ return sum + (test.responseTime || 0);
209
+ }, 0);
210
+ modelResults.summary.avgResponseTime = Math.round(totalResponseTime / successfulResponses.length);
211
+ modelResults.summary.avgTokensUsed = Math.round(modelResults.summary.avgTokensUsed / successfulResponses.length);
212
+ }
213
+ modelResults.summary.successRate = Math.round((modelResults.summary.successful / modelResults.summary.totalAttempts) * 100);
214
+ testResults[modelId] = modelResults;
215
+ }
216
+ const testEndTime = Date.now();
217
+ const totalTestTime = testEndTime - testStartTime;
218
+ const consolidatedSummary = {
219
+ success: true,
220
+ timestamp: new Date().toISOString(),
221
+ testConfiguration: {
222
+ testsPerModel: testsPerModel,
223
+ totalModels: availableModels.length,
224
+ totalTests: totalTests,
225
+ retryEnabled: enableRetry,
226
+ maxRetries: maxRetries,
227
+ },
228
+ overallResults: {
229
+ totalTests: totalTests,
230
+ successfulTests: successfulTests,
231
+ failedTests: failedTests,
232
+ overallSuccessRate: Math.round((successfulTests / totalTests) * 100),
233
+ totalTestTime: totalTestTime,
234
+ avgTimePerTest: Math.round(totalTestTime / totalTests),
235
+ },
236
+ modelResults: testResults,
237
+ recommendations: generateTestRecommendations(testResults),
238
+ };
239
+ console.log('✅ Consolidated test completed successfully!');
240
+ return consolidatedSummary;
241
+ }
242
+ catch (error) {
243
+ return {
244
+ success: false,
245
+ timestamp: new Date().toISOString(),
246
+ error: error instanceof Error ? error.message : 'Unknown error in consolidated test',
247
+ partialResults: testResults,
248
+ testDuration: Date.now() - testStartTime,
249
+ };
250
+ }
251
+ }
252
+ function generateTestRecommendations(testResults) {
253
+ const recommendations = [];
254
+ const modelStats = Object.entries(testResults).map(([modelId, results]) => {
255
+ const modelResult = results;
256
+ const summary = modelResult.summary;
257
+ const modelInfo = modelResult.modelInfo;
258
+ return {
259
+ modelId,
260
+ successRate: summary.successRate,
261
+ avgResponseTime: summary.avgResponseTime,
262
+ vendor: modelInfo.vendor,
263
+ };
264
+ });
265
+ const bestModels = modelStats
266
+ .filter(m => m.successRate === 100)
267
+ .sort((a, b) => a.avgResponseTime - b.avgResponseTime)
268
+ .slice(0, 3);
269
+ if (bestModels.length > 0) {
270
+ recommendations.push({
271
+ type: 'best_performance',
272
+ title: 'Top Performing Models (100% success rate)',
273
+ models: bestModels,
274
+ description: 'These models completed all tests successfully with fastest response times',
275
+ });
276
+ }
277
+ const problematicModels = modelStats.filter(m => m.successRate < 80);
278
+ if (problematicModels.length > 0) {
279
+ recommendations.push({
280
+ type: 'attention_needed',
281
+ title: 'Models Requiring Attention (< 80% success rate)',
282
+ models: problematicModels,
283
+ description: 'These models had reliability issues during testing',
284
+ });
285
+ }
286
+ const vendorStats = modelStats.reduce((acc, model) => {
287
+ const vendor = model.vendor;
288
+ if (!acc[vendor]) {
289
+ acc[vendor] = { count: 0, totalSuccessRate: 0, avgResponseTime: 0 };
290
+ }
291
+ const stats = acc[vendor];
292
+ stats.count++;
293
+ stats.totalSuccessRate += model.successRate;
294
+ stats.avgResponseTime += model.avgResponseTime;
295
+ return acc;
296
+ }, {});
297
+ Object.keys(vendorStats).forEach(vendor => {
298
+ const vendorData = vendorStats[vendor];
299
+ vendorData.avgSuccessRate = Math.round(vendorData.totalSuccessRate / vendorData.count);
300
+ vendorData.avgResponseTime = Math.round(vendorData.avgResponseTime / vendorData.count);
301
+ });
302
+ recommendations.push({
303
+ type: 'vendor_analysis',
304
+ title: 'Performance by Vendor',
305
+ vendors: vendorStats,
306
+ description: 'Comparative analysis of model performance by vendor',
307
+ });
308
+ return recommendations;
309
+ }
86
310
  class GitHubCopilotTest {
87
311
  constructor() {
88
312
  this.description = {
@@ -116,6 +340,11 @@ class GitHubCopilotTest {
116
340
  value: 'listModels',
117
341
  description: 'Get all models available for your GitHub Copilot subscription',
118
342
  },
343
+ {
344
+ name: 'Consolidated Model Test',
345
+ value: 'consolidatedTest',
346
+ description: 'Test all available models 5 times each and generate comprehensive report',
347
+ },
119
348
  ],
120
349
  default: 'listModels',
121
350
  description: 'Select the test function to execute',
@@ -163,6 +392,9 @@ class GitHubCopilotTest {
163
392
  case 'listModels':
164
393
  result = await listAvailableModels(token, enableRetry, maxRetries);
165
394
  break;
395
+ case 'consolidatedTest':
396
+ result = await consolidatedModelTest(token, enableRetry, maxRetries);
397
+ break;
166
398
  default:
167
399
  throw new Error(`Unknown test function: ${testFunction}`);
168
400
  }
@@ -0,0 +1,34 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
2
+ <defs>
3
+ <linearGradient id="copilotGradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#1f6feb;stop-opacity:1" />
5
+ <stop offset="100%" style="stop-color:#0969da;stop-opacity:1" />
6
+ </linearGradient>
7
+ </defs>
8
+
9
+ <!-- GitHub Copilot inspired icon -->
10
+ <circle cx="12" cy="12" r="11" fill="url(#copilotGradient)" stroke="#ffffff" stroke-width="1"/>
11
+
12
+ <!-- Copilot face -->
13
+ <ellipse cx="12" cy="10" rx="8" ry="6" fill="#ffffff" opacity="0.9"/>
14
+
15
+ <!-- Eyes -->
16
+ <circle cx="9" cy="9" r="1.5" fill="#1f6feb"/>
17
+ <circle cx="15" cy="9" r="1.5" fill="#1f6feb"/>
18
+
19
+ <!-- Light reflection in eyes -->
20
+ <circle cx="9.5" cy="8.5" r="0.5" fill="#ffffff"/>
21
+ <circle cx="15.5" cy="8.5" r="0.5" fill="#ffffff"/>
22
+
23
+ <!-- Mouth/Interface line -->
24
+ <path d="M8 12 L16 12" stroke="#1f6feb" stroke-width="1.5" stroke-linecap="round"/>
25
+
26
+ <!-- Code brackets -->
27
+ <path d="M6 15 L8 17 L6 19" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
28
+ <path d="M18 15 L16 17 L18 19" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
29
+
30
+ <!-- AI indicator dots -->
31
+ <circle cx="10" cy="17" r="0.5" fill="#ffffff" opacity="0.8"/>
32
+ <circle cx="12" cy="17" r="0.5" fill="#ffffff" opacity="0.6"/>
33
+ <circle cx="14" cy="17" r="0.5" fill="#ffffff" opacity="0.4"/>
34
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-github-copilot",
3
- "version": "3.22.0",
3
+ "version": "3.24.0",
4
4
  "description": "n8n community node for GitHub Copilot with CLI integration, Chat API access, and AI Chat Model for workflows - access GPT-5, Claude, Gemini and more using your Copilot subscription",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/sufficit/n8n-nodes-github-copilot",