n8n-nodes-github-copilot 3.36.0 → 3.37.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/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.d.ts +10 -0
- package/dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.js +403 -0
- package/dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.js +147 -0
- package/dist/package.json +2 -1
- package/dist/shared/models/DynamicModelLoader.d.ts +1 -0
- package/dist/shared/models/DynamicModelLoader.js +32 -44
- package/dist/shared/utils/DynamicModelsManager.d.ts +1 -0
- package/dist/shared/utils/DynamicModelsManager.js +7 -0
- package/dist/shared/utils/GitHubCopilotEndpoints.d.ts +5 -0
- package/dist/shared/utils/GitHubCopilotEndpoints.js +25 -0
- package/package.json +2 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, ILoadOptionsFunctions, INodePropertyOptions } from "n8n-workflow";
|
|
2
|
+
export declare class GitHubCopilotEmbeddings implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
methods: {
|
|
5
|
+
loadOptions: {
|
|
6
|
+
getAvailableEmbeddingModels(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GitHubCopilotEmbeddings = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const OAuthTokenManager_1 = require("../../shared/utils/OAuthTokenManager");
|
|
6
|
+
const GitHubCopilotEndpoints_1 = require("../../shared/utils/GitHubCopilotEndpoints");
|
|
7
|
+
const DynamicModelLoader_1 = require("../../shared/models/DynamicModelLoader");
|
|
8
|
+
async function executeEmbeddingsWithRetry(oauthToken, requestBody, enableRetry, maxRetries) {
|
|
9
|
+
let lastError = null;
|
|
10
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(`${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL}${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.EMBEDDINGS}`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getEmbeddingsHeaders(oauthToken),
|
|
15
|
+
body: JSON.stringify(requestBody),
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const errorText = await response.text();
|
|
19
|
+
if (GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.isTpmQuotaError(response.status) &&
|
|
20
|
+
enableRetry &&
|
|
21
|
+
attempt < maxRetries) {
|
|
22
|
+
const delay = GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getRetryDelay(attempt + 1);
|
|
23
|
+
console.log(`Attempt ${attempt + 1} failed with ${response.status}, retrying in ${delay}ms...`);
|
|
24
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`API Error ${response.status}: ${errorText}`);
|
|
28
|
+
}
|
|
29
|
+
const data = (await response.json());
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
34
|
+
if (attempt < maxRetries && enableRetry) {
|
|
35
|
+
const delay = GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getRetryDelay(attempt + 1);
|
|
36
|
+
console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
throw lastError;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw lastError || new Error("Maximum retry attempts exceeded");
|
|
44
|
+
}
|
|
45
|
+
class GitHubCopilotEmbeddings {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.description = {
|
|
48
|
+
displayName: "GitHub Copilot Embeddings",
|
|
49
|
+
name: "gitHubCopilotEmbeddings",
|
|
50
|
+
icon: "file:../../shared/icons/copilot.svg",
|
|
51
|
+
group: ["transform"],
|
|
52
|
+
version: 1,
|
|
53
|
+
subtitle: '={{$parameter["operation"]}}',
|
|
54
|
+
description: "Generate text embeddings using GitHub Copilot API",
|
|
55
|
+
defaults: {
|
|
56
|
+
name: "GitHub Copilot Embeddings",
|
|
57
|
+
},
|
|
58
|
+
inputs: ["main"],
|
|
59
|
+
outputs: ["main"],
|
|
60
|
+
credentials: [
|
|
61
|
+
{
|
|
62
|
+
name: "githubCopilotApi",
|
|
63
|
+
required: true,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
properties: [
|
|
67
|
+
{
|
|
68
|
+
displayName: "Operation",
|
|
69
|
+
name: "operation",
|
|
70
|
+
type: "options",
|
|
71
|
+
noDataExpression: true,
|
|
72
|
+
options: [
|
|
73
|
+
{
|
|
74
|
+
name: "Generate Embeddings",
|
|
75
|
+
value: "generate",
|
|
76
|
+
description: "Generate vector embeddings for text input",
|
|
77
|
+
action: "Generate embeddings for text",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
default: "generate",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
displayName: "Model",
|
|
84
|
+
name: "model",
|
|
85
|
+
type: "options",
|
|
86
|
+
typeOptions: {
|
|
87
|
+
loadOptionsMethod: "getAvailableEmbeddingModels",
|
|
88
|
+
},
|
|
89
|
+
options: [
|
|
90
|
+
{
|
|
91
|
+
name: "Text Embedding 3 Small",
|
|
92
|
+
value: "text-embedding-3-small",
|
|
93
|
+
description: "OpenAI's text-embedding-3-small model (recommended)",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "Text Embedding Ada 002",
|
|
97
|
+
value: "text-embedding-ada-002",
|
|
98
|
+
description: "Legacy embedding model",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "Text Embedding 3 Small (Inference)",
|
|
102
|
+
value: "text-embedding-3-small-inference",
|
|
103
|
+
description: "Optimized inference variant",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
default: "text-embedding-3-small",
|
|
107
|
+
description: "The embedding model to use. Models are auto-discovered from your GitHub Copilot subscription.",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
displayName: "Custom Model Name",
|
|
111
|
+
name: "customModel",
|
|
112
|
+
type: "string",
|
|
113
|
+
default: "",
|
|
114
|
+
required: true,
|
|
115
|
+
placeholder: "e.g., text-embedding-3-large",
|
|
116
|
+
description: "Enter the exact model name to use",
|
|
117
|
+
displayOptions: {
|
|
118
|
+
show: {
|
|
119
|
+
model: ["__manual__"],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
displayName: "Input Mode",
|
|
125
|
+
name: "inputMode",
|
|
126
|
+
type: "options",
|
|
127
|
+
options: [
|
|
128
|
+
{
|
|
129
|
+
name: "Single Text",
|
|
130
|
+
value: "single",
|
|
131
|
+
description: "Embed a single text string",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "Batch (Array)",
|
|
135
|
+
value: "batch",
|
|
136
|
+
description: "Embed multiple texts in a single request (more efficient)",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "From Field",
|
|
140
|
+
value: "field",
|
|
141
|
+
description: "Embed text from a specific field in each input item",
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
default: "single",
|
|
145
|
+
description: "How to provide the text input",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
displayName: "Text",
|
|
149
|
+
name: "text",
|
|
150
|
+
type: "string",
|
|
151
|
+
typeOptions: {
|
|
152
|
+
rows: 4,
|
|
153
|
+
},
|
|
154
|
+
default: "",
|
|
155
|
+
required: true,
|
|
156
|
+
placeholder: "Enter text to embed",
|
|
157
|
+
description: "The text to generate embeddings for",
|
|
158
|
+
displayOptions: {
|
|
159
|
+
show: {
|
|
160
|
+
inputMode: ["single"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
displayName: "Texts",
|
|
166
|
+
name: "texts",
|
|
167
|
+
type: "string",
|
|
168
|
+
typeOptions: {
|
|
169
|
+
rows: 8,
|
|
170
|
+
},
|
|
171
|
+
default: "",
|
|
172
|
+
required: true,
|
|
173
|
+
placeholder: '["Text 1", "Text 2", "Text 3"]',
|
|
174
|
+
description: "JSON array of texts to embed in a single request (more efficient than multiple calls)",
|
|
175
|
+
displayOptions: {
|
|
176
|
+
show: {
|
|
177
|
+
inputMode: ["batch"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
displayName: "Field Name",
|
|
183
|
+
name: "fieldName",
|
|
184
|
+
type: "string",
|
|
185
|
+
default: "text",
|
|
186
|
+
required: true,
|
|
187
|
+
placeholder: "text",
|
|
188
|
+
description: "Name of the field containing the text to embed",
|
|
189
|
+
displayOptions: {
|
|
190
|
+
show: {
|
|
191
|
+
inputMode: ["field"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
displayName: "Options",
|
|
197
|
+
name: "options",
|
|
198
|
+
type: "collection",
|
|
199
|
+
placeholder: "Add Option",
|
|
200
|
+
default: {},
|
|
201
|
+
options: [
|
|
202
|
+
{
|
|
203
|
+
displayName: "Dimensions",
|
|
204
|
+
name: "dimensions",
|
|
205
|
+
type: "number",
|
|
206
|
+
default: 1536,
|
|
207
|
+
description: "The number of dimensions for the embedding. Only supported by text-embedding-3-small.",
|
|
208
|
+
typeOptions: {
|
|
209
|
+
minValue: 1,
|
|
210
|
+
maxValue: 1536,
|
|
211
|
+
},
|
|
212
|
+
placeholder: "1536",
|
|
213
|
+
hint: "Common values: 512, 768, 1024, 1536",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
displayName: "Encoding Format",
|
|
217
|
+
name: "encoding_format",
|
|
218
|
+
type: "options",
|
|
219
|
+
options: [
|
|
220
|
+
{
|
|
221
|
+
name: "Float",
|
|
222
|
+
value: "float",
|
|
223
|
+
description: "Standard floating point format (default)",
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "Base64",
|
|
227
|
+
value: "base64",
|
|
228
|
+
description: "Base64-encoded format (more compact)",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
default: "float",
|
|
232
|
+
description: "The format to return the embeddings in",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
displayName: "Return Full Response",
|
|
236
|
+
name: "returnFullResponse",
|
|
237
|
+
type: "boolean",
|
|
238
|
+
default: false,
|
|
239
|
+
description: "Whether to return the full API response including usage statistics",
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
displayName: "Enable Retry",
|
|
243
|
+
name: "enableRetry",
|
|
244
|
+
type: "boolean",
|
|
245
|
+
default: true,
|
|
246
|
+
description: "Whether to retry on TPM quota errors (403)",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
displayName: "Max Retries",
|
|
250
|
+
name: "maxRetries",
|
|
251
|
+
type: "number",
|
|
252
|
+
default: 3,
|
|
253
|
+
description: "Maximum number of retry attempts",
|
|
254
|
+
displayOptions: {
|
|
255
|
+
show: {
|
|
256
|
+
enableRetry: [true],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
};
|
|
264
|
+
this.methods = {
|
|
265
|
+
loadOptions: {
|
|
266
|
+
async getAvailableEmbeddingModels() {
|
|
267
|
+
return await DynamicModelLoader_1.loadAvailableEmbeddingModels.call(this);
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async execute() {
|
|
273
|
+
const items = this.getInputData();
|
|
274
|
+
const returnData = [];
|
|
275
|
+
for (let i = 0; i < items.length; i++) {
|
|
276
|
+
try {
|
|
277
|
+
const operation = this.getNodeParameter("operation", i);
|
|
278
|
+
const selectedModel = this.getNodeParameter("model", i);
|
|
279
|
+
let model;
|
|
280
|
+
if (selectedModel === "__manual__") {
|
|
281
|
+
model = this.getNodeParameter("customModel", i);
|
|
282
|
+
if (!model || model.trim() === "") {
|
|
283
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Custom model name is required when selecting '✏️ Enter Custom Model Name'");
|
|
284
|
+
}
|
|
285
|
+
console.log(`✏️ Using manually entered embedding model: ${model}`);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
model = selectedModel;
|
|
289
|
+
console.log(`✅ Using embedding model from list: ${model}`);
|
|
290
|
+
}
|
|
291
|
+
const inputMode = this.getNodeParameter("inputMode", i);
|
|
292
|
+
const options = this.getNodeParameter("options", i, {});
|
|
293
|
+
const credentials = await this.getCredentials("githubCopilotApi", i);
|
|
294
|
+
const githubToken = credentials.token;
|
|
295
|
+
if (!githubToken) {
|
|
296
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "GitHub token is required");
|
|
297
|
+
}
|
|
298
|
+
if (!GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.validateToken(githubToken)) {
|
|
299
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Invalid GitHub token format. Token must start with 'gho_' or 'github_pat_'");
|
|
300
|
+
}
|
|
301
|
+
const oauthToken = await OAuthTokenManager_1.OAuthTokenManager.getValidOAuthToken(githubToken);
|
|
302
|
+
const enableRetry = options.enableRetry !== false;
|
|
303
|
+
const maxRetries = options.maxRetries || 3;
|
|
304
|
+
let input;
|
|
305
|
+
switch (inputMode) {
|
|
306
|
+
case "single":
|
|
307
|
+
input = this.getNodeParameter("text", i);
|
|
308
|
+
if (!input || input.trim() === "") {
|
|
309
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), "Text input cannot be empty");
|
|
310
|
+
}
|
|
311
|
+
input = [input];
|
|
312
|
+
break;
|
|
313
|
+
case "batch":
|
|
314
|
+
const textsStr = this.getNodeParameter("texts", i);
|
|
315
|
+
try {
|
|
316
|
+
const parsed = JSON.parse(textsStr);
|
|
317
|
+
if (!Array.isArray(parsed)) {
|
|
318
|
+
throw new Error("Input must be a JSON array");
|
|
319
|
+
}
|
|
320
|
+
input = parsed;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Invalid JSON array: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
case "field":
|
|
327
|
+
const fieldName = this.getNodeParameter("fieldName", i);
|
|
328
|
+
const fieldValue = items[i].json[fieldName];
|
|
329
|
+
if (!fieldValue) {
|
|
330
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Field '${fieldName}' not found in input data`);
|
|
331
|
+
}
|
|
332
|
+
input = [String(fieldValue)];
|
|
333
|
+
break;
|
|
334
|
+
default:
|
|
335
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown input mode: ${inputMode}`);
|
|
336
|
+
}
|
|
337
|
+
const requestBody = {
|
|
338
|
+
model,
|
|
339
|
+
input,
|
|
340
|
+
};
|
|
341
|
+
if (options.dimensions) {
|
|
342
|
+
requestBody.dimensions = options.dimensions;
|
|
343
|
+
}
|
|
344
|
+
if (options.encoding_format) {
|
|
345
|
+
requestBody.encoding_format = options.encoding_format;
|
|
346
|
+
}
|
|
347
|
+
const result = await executeEmbeddingsWithRetry(oauthToken, requestBody, enableRetry, maxRetries);
|
|
348
|
+
const returnFullResponse = options.returnFullResponse === true;
|
|
349
|
+
if (returnFullResponse) {
|
|
350
|
+
returnData.push({
|
|
351
|
+
json: result,
|
|
352
|
+
pairedItem: { item: i },
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
if (inputMode === "batch") {
|
|
357
|
+
const embeddings = result.data.map((item) => ({
|
|
358
|
+
index: item.index,
|
|
359
|
+
embedding: item.embedding,
|
|
360
|
+
dimensions: item.embedding.length,
|
|
361
|
+
}));
|
|
362
|
+
returnData.push({
|
|
363
|
+
json: {
|
|
364
|
+
model: result.model,
|
|
365
|
+
embeddings,
|
|
366
|
+
usage: result.usage,
|
|
367
|
+
},
|
|
368
|
+
pairedItem: { item: i },
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
const embeddingData = result.data[0];
|
|
373
|
+
returnData.push({
|
|
374
|
+
json: {
|
|
375
|
+
embedding: embeddingData.embedding,
|
|
376
|
+
dimensions: embeddingData.embedding.length,
|
|
377
|
+
model: result.model,
|
|
378
|
+
usage: result.usage,
|
|
379
|
+
},
|
|
380
|
+
pairedItem: { item: i },
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
if (this.continueOnFail()) {
|
|
387
|
+
returnData.push({
|
|
388
|
+
json: {
|
|
389
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
390
|
+
operation: this.getNodeParameter("operation", i),
|
|
391
|
+
},
|
|
392
|
+
pairedItem: { item: i },
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
throw error;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return [returnData];
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
exports.GitHubCopilotEmbeddings = GitHubCopilotEmbeddings;
|
|
@@ -408,6 +408,145 @@ function generateTestRecommendations(testResults) {
|
|
|
408
408
|
});
|
|
409
409
|
return recommendations;
|
|
410
410
|
}
|
|
411
|
+
async function testEmbeddingModels(token, enableRetry = true, maxRetries = 3) {
|
|
412
|
+
var _a, _b, _c, _d;
|
|
413
|
+
const testStartTime = Date.now();
|
|
414
|
+
try {
|
|
415
|
+
console.log("🧪 Testing embedding models...");
|
|
416
|
+
const modelsUrl = `${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.URLS.MODELS}`;
|
|
417
|
+
const modelsResponse = await fetch(modelsUrl, {
|
|
418
|
+
method: "GET",
|
|
419
|
+
headers: GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getAuthHeaders(token),
|
|
420
|
+
});
|
|
421
|
+
if (!modelsResponse.ok) {
|
|
422
|
+
throw new Error(`Failed to fetch models: ${modelsResponse.status}`);
|
|
423
|
+
}
|
|
424
|
+
const modelsData = (await modelsResponse.json());
|
|
425
|
+
const embeddingModels = modelsData.data.filter((model) => {
|
|
426
|
+
var _a;
|
|
427
|
+
const modelType = (_a = model.capabilities) === null || _a === void 0 ? void 0 : _a.type;
|
|
428
|
+
return modelType === "embeddings";
|
|
429
|
+
});
|
|
430
|
+
console.log(`📊 Found ${embeddingModels.length} embedding models to test`);
|
|
431
|
+
const testResults = {};
|
|
432
|
+
const testText = "This is a test sentence for embeddings generation.";
|
|
433
|
+
for (const model of embeddingModels) {
|
|
434
|
+
console.log(`\n🔬 Testing model: ${model.name} (${model.id})`);
|
|
435
|
+
const modelResults = {
|
|
436
|
+
modelId: model.id,
|
|
437
|
+
modelName: model.name,
|
|
438
|
+
vendor: model.vendor,
|
|
439
|
+
tests: [],
|
|
440
|
+
summary: {
|
|
441
|
+
successCount: 0,
|
|
442
|
+
failureCount: 0,
|
|
443
|
+
avgResponseTime: 0,
|
|
444
|
+
totalResponseTime: 0,
|
|
445
|
+
successRate: 0,
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
const testsPerModel = 3;
|
|
449
|
+
for (let testNum = 1; testNum <= testsPerModel; testNum++) {
|
|
450
|
+
const testStart = Date.now();
|
|
451
|
+
try {
|
|
452
|
+
const requestBody = {
|
|
453
|
+
model: model.id,
|
|
454
|
+
input: [testText],
|
|
455
|
+
};
|
|
456
|
+
const response = await fetch(GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getEmbeddingsUrl(), {
|
|
457
|
+
method: "POST",
|
|
458
|
+
headers: GitHubCopilotEndpoints_1.GitHubCopilotEndpoints.getEmbeddingsHeaders(token),
|
|
459
|
+
body: JSON.stringify(requestBody),
|
|
460
|
+
});
|
|
461
|
+
const testDuration = Date.now() - testStart;
|
|
462
|
+
if (response.ok) {
|
|
463
|
+
const data = (await response.json());
|
|
464
|
+
const embeddingLength = ((_c = (_b = (_a = data.data) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.embedding) === null || _c === void 0 ? void 0 : _c.length) || 0;
|
|
465
|
+
modelResults.tests.push({
|
|
466
|
+
testNumber: testNum,
|
|
467
|
+
success: true,
|
|
468
|
+
responseTime: testDuration,
|
|
469
|
+
embeddingDimensions: embeddingLength,
|
|
470
|
+
tokensUsed: ((_d = data.usage) === null || _d === void 0 ? void 0 : _d.total_tokens) || 0,
|
|
471
|
+
});
|
|
472
|
+
modelResults.summary.successCount++;
|
|
473
|
+
modelResults.summary.totalResponseTime += testDuration;
|
|
474
|
+
console.log(` ✅ Test ${testNum}/${testsPerModel}: Success (${testDuration}ms, ${embeddingLength}D)`);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const errorText = await response.text();
|
|
478
|
+
modelResults.tests.push({
|
|
479
|
+
testNumber: testNum,
|
|
480
|
+
success: false,
|
|
481
|
+
responseTime: testDuration,
|
|
482
|
+
error: `HTTP ${response.status}: ${errorText.substring(0, 100)}`,
|
|
483
|
+
});
|
|
484
|
+
modelResults.summary.failureCount++;
|
|
485
|
+
console.log(` ❌ Test ${testNum}/${testsPerModel}: Failed (${response.status})`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
const testDuration = Date.now() - testStart;
|
|
490
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
491
|
+
modelResults.tests.push({
|
|
492
|
+
testNumber: testNum,
|
|
493
|
+
success: false,
|
|
494
|
+
responseTime: testDuration,
|
|
495
|
+
error: errorMessage,
|
|
496
|
+
});
|
|
497
|
+
modelResults.summary.failureCount++;
|
|
498
|
+
console.log(` ❌ Test ${testNum}/${testsPerModel}: Error - ${errorMessage}`);
|
|
499
|
+
}
|
|
500
|
+
if (testNum < testsPerModel) {
|
|
501
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const successCount = modelResults.summary.successCount;
|
|
505
|
+
if (successCount > 0) {
|
|
506
|
+
modelResults.summary.avgResponseTime = Math.round(modelResults.summary.totalResponseTime / successCount);
|
|
507
|
+
}
|
|
508
|
+
modelResults.summary.successRate = Math.round((successCount / testsPerModel) * 100);
|
|
509
|
+
testResults[model.id] = modelResults;
|
|
510
|
+
}
|
|
511
|
+
const testDuration = Date.now() - testStartTime;
|
|
512
|
+
const allModels = Object.values(testResults);
|
|
513
|
+
const totalTests = allModels.length * 3;
|
|
514
|
+
const successfulTests = allModels.reduce((sum, m) => sum + m.summary.successCount, 0);
|
|
515
|
+
return {
|
|
516
|
+
success: true,
|
|
517
|
+
timestamp: new Date().toISOString(),
|
|
518
|
+
testDuration: testDuration,
|
|
519
|
+
testDurationFormatted: `${Math.floor(testDuration / 1000)}s`,
|
|
520
|
+
testConfig: {
|
|
521
|
+
testsPerModel: 3,
|
|
522
|
+
totalModels: embeddingModels.length,
|
|
523
|
+
totalTests: totalTests,
|
|
524
|
+
retryEnabled: enableRetry,
|
|
525
|
+
maxRetries: maxRetries,
|
|
526
|
+
},
|
|
527
|
+
overallResults: {
|
|
528
|
+
totalTests: totalTests,
|
|
529
|
+
successfulTests: successfulTests,
|
|
530
|
+
failedTests: totalTests - successfulTests,
|
|
531
|
+
overallSuccessRate: Math.round((successfulTests / totalTests) * 100),
|
|
532
|
+
},
|
|
533
|
+
modelResults: testResults,
|
|
534
|
+
availableModels: embeddingModels.map((m) => ({
|
|
535
|
+
id: m.id,
|
|
536
|
+
name: m.name,
|
|
537
|
+
vendor: m.vendor,
|
|
538
|
+
})),
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
return {
|
|
543
|
+
success: false,
|
|
544
|
+
timestamp: new Date().toISOString(),
|
|
545
|
+
error: error instanceof Error ? error.message : "Unknown error in embedding models test",
|
|
546
|
+
testDuration: Date.now() - testStartTime,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
}
|
|
411
550
|
class GitHubCopilotTest {
|
|
412
551
|
constructor() {
|
|
413
552
|
this.description = {
|
|
@@ -446,6 +585,11 @@ class GitHubCopilotTest {
|
|
|
446
585
|
value: "refreshCache",
|
|
447
586
|
description: "Force refresh the cached models list (clears cache and fetches fresh data from API)",
|
|
448
587
|
},
|
|
588
|
+
{
|
|
589
|
+
name: "Test Embedding Models",
|
|
590
|
+
value: "testEmbeddings",
|
|
591
|
+
description: "Test all embedding models (text-embedding-*) with sample text generation",
|
|
592
|
+
},
|
|
449
593
|
{
|
|
450
594
|
name: "Consolidated Model Test",
|
|
451
595
|
value: "consolidatedTest",
|
|
@@ -531,6 +675,9 @@ class GitHubCopilotTest {
|
|
|
531
675
|
case "refreshCache":
|
|
532
676
|
result = await refreshModelsCache(token, enableRetry, maxRetries);
|
|
533
677
|
break;
|
|
678
|
+
case "testEmbeddings":
|
|
679
|
+
result = await testEmbeddingModels(token, enableRetry, maxRetries);
|
|
680
|
+
break;
|
|
534
681
|
case "consolidatedTest":
|
|
535
682
|
result = await consolidatedModelTest(token, enableRetry, maxRetries, testsPerModel);
|
|
536
683
|
break;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-github-copilot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.37.1",
|
|
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",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.js",
|
|
34
34
|
"dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js",
|
|
35
35
|
"dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js",
|
|
36
|
+
"dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.js",
|
|
36
37
|
"dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js",
|
|
37
38
|
"dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.js"
|
|
38
39
|
]
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { ILoadOptionsFunctions, INodePropertyOptions } from "n8n-workflow";
|
|
2
2
|
export declare function loadAvailableModels(this: ILoadOptionsFunctions, forceRefresh?: boolean): Promise<INodePropertyOptions[]>;
|
|
3
|
+
export declare function loadAvailableEmbeddingModels(this: ILoadOptionsFunctions, forceRefresh?: boolean): Promise<INodePropertyOptions[]>;
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.loadAvailableModels = loadAvailableModels;
|
|
4
|
+
exports.loadAvailableEmbeddingModels = loadAvailableEmbeddingModels;
|
|
4
5
|
const DynamicModelsManager_1 = require("../utils/DynamicModelsManager");
|
|
5
6
|
const OAuthTokenManager_1 = require("../utils/OAuthTokenManager");
|
|
6
7
|
async function loadAvailableModels(forceRefresh = false) {
|
|
8
|
+
return loadModelsWithFilter.call(this, "chat", forceRefresh);
|
|
9
|
+
}
|
|
10
|
+
async function loadAvailableEmbeddingModels(forceRefresh = false) {
|
|
11
|
+
return loadModelsWithFilter.call(this, "embeddings", forceRefresh);
|
|
12
|
+
}
|
|
13
|
+
async function loadModelsWithFilter(modelType, forceRefresh = false) {
|
|
7
14
|
try {
|
|
8
15
|
const credentials = await this.getCredentials("githubCopilotApi");
|
|
9
16
|
if (!credentials || !credentials.token) {
|
|
10
17
|
console.warn("⚠️ No credentials found for dynamic model loading");
|
|
11
|
-
return
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
name: "✏️ Enter Custom Model Name",
|
|
21
|
+
value: "__manual__",
|
|
22
|
+
description: "Type your own model name (no credentials found)",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
12
25
|
}
|
|
13
26
|
const githubToken = credentials.token;
|
|
14
27
|
let oauthToken;
|
|
@@ -17,13 +30,21 @@ async function loadAvailableModels(forceRefresh = false) {
|
|
|
17
30
|
}
|
|
18
31
|
catch (error) {
|
|
19
32
|
console.error("❌ Failed to generate OAuth token for model loading:", error);
|
|
20
|
-
return
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
name: "✏️ Enter Custom Model Name",
|
|
36
|
+
value: "__manual__",
|
|
37
|
+
description: "Type your own model name (OAuth generation failed)",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
21
40
|
}
|
|
22
41
|
if (forceRefresh) {
|
|
23
42
|
DynamicModelsManager_1.DynamicModelsManager.clearCache(oauthToken);
|
|
24
43
|
console.log("🔄 Force refreshing models list...");
|
|
25
44
|
}
|
|
26
|
-
const
|
|
45
|
+
const allModels = await DynamicModelsManager_1.DynamicModelsManager.getAvailableModels(oauthToken);
|
|
46
|
+
const models = DynamicModelsManager_1.DynamicModelsManager.filterModelsByType(allModels, modelType);
|
|
47
|
+
console.log(`🔍 Filtered ${models.length} ${modelType} models from ${allModels.length} total models`);
|
|
27
48
|
const options = DynamicModelsManager_1.DynamicModelsManager.modelsToN8nOptions(models);
|
|
28
49
|
const optionsWithManualInput = [
|
|
29
50
|
{
|
|
@@ -33,50 +54,17 @@ async function loadAvailableModels(forceRefresh = false) {
|
|
|
33
54
|
},
|
|
34
55
|
...options,
|
|
35
56
|
];
|
|
36
|
-
console.log(`✅ Loaded ${options.length} models dynamically (+ manual input option)`);
|
|
57
|
+
console.log(`✅ Loaded ${options.length} ${modelType} models dynamically (+ manual input option)`);
|
|
37
58
|
return optionsWithManualInput;
|
|
38
59
|
}
|
|
39
60
|
catch (error) {
|
|
40
61
|
console.error("❌ Error loading dynamic models:", error);
|
|
41
|
-
return
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
name: "✏️ Enter Custom Model Name",
|
|
65
|
+
value: "__manual__",
|
|
66
|
+
description: "Type your own model name (discovery failed, using previous cache if available)",
|
|
67
|
+
},
|
|
68
|
+
];
|
|
42
69
|
}
|
|
43
70
|
}
|
|
44
|
-
function getDefaultModelsWithManualInput() {
|
|
45
|
-
return [
|
|
46
|
-
{
|
|
47
|
-
name: "✏️ Enter Custom Model Name",
|
|
48
|
-
value: "__manual__",
|
|
49
|
-
description: "Type your own model name (for new/beta models)",
|
|
50
|
-
},
|
|
51
|
-
...getDefaultModels(),
|
|
52
|
-
];
|
|
53
|
-
}
|
|
54
|
-
function getDefaultModels() {
|
|
55
|
-
return [
|
|
56
|
-
{
|
|
57
|
-
name: "GPT-4o (Latest)",
|
|
58
|
-
value: "gpt-4o",
|
|
59
|
-
description: "Most capable GPT-4o model - Best for complex tasks",
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "GPT-4o Mini",
|
|
63
|
-
value: "gpt-4o-mini",
|
|
64
|
-
description: "Faster and cheaper GPT-4o variant - Good for simple tasks",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: "Claude 3.5 Sonnet",
|
|
68
|
-
value: "claude-3.5-sonnet",
|
|
69
|
-
description: "Anthropic's most capable model - Excellent reasoning",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
name: "o1-preview",
|
|
73
|
-
value: "o1-preview",
|
|
74
|
-
description: "OpenAI's reasoning model (preview)",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: "o1-mini",
|
|
78
|
-
value: "o1-mini",
|
|
79
|
-
description: "Faster reasoning model variant",
|
|
80
|
-
},
|
|
81
|
-
];
|
|
82
|
-
}
|
|
@@ -15,6 +15,7 @@ export declare class DynamicModelsManager {
|
|
|
15
15
|
private static hashToken;
|
|
16
16
|
private static fetchModelsFromAPI;
|
|
17
17
|
static getAvailableModels(oauthToken: string): Promise<CopilotModel[]>;
|
|
18
|
+
static filterModelsByType(models: CopilotModel[], type: string): CopilotModel[];
|
|
18
19
|
static modelsToN8nOptions(models: CopilotModel[]): Array<{
|
|
19
20
|
name: string;
|
|
20
21
|
value: string;
|
|
@@ -69,6 +69,13 @@ class DynamicModelsManager {
|
|
|
69
69
|
throw error;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
static filterModelsByType(models, type) {
|
|
73
|
+
return models.filter((model) => {
|
|
74
|
+
var _a;
|
|
75
|
+
const modelType = (_a = model.capabilities) === null || _a === void 0 ? void 0 : _a.type;
|
|
76
|
+
return modelType === type;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
72
79
|
static modelsToN8nOptions(models) {
|
|
73
80
|
return models.map((model) => {
|
|
74
81
|
const badges = [];
|
|
@@ -4,6 +4,7 @@ export declare const GITHUB_COPILOT_API: {
|
|
|
4
4
|
readonly ENDPOINTS: {
|
|
5
5
|
readonly MODELS: "/models";
|
|
6
6
|
readonly CHAT_COMPLETIONS: "/chat/completions";
|
|
7
|
+
readonly EMBEDDINGS: "/embeddings";
|
|
7
8
|
readonly ORG_BILLING: (org: string) => string;
|
|
8
9
|
readonly ORG_SEATS: (org: string) => string;
|
|
9
10
|
readonly USER_COPILOT: "/user/copilot_access";
|
|
@@ -11,6 +12,7 @@ export declare const GITHUB_COPILOT_API: {
|
|
|
11
12
|
readonly URLS: {
|
|
12
13
|
readonly MODELS: "https://api.githubcopilot.com/models";
|
|
13
14
|
readonly CHAT_COMPLETIONS: "https://api.githubcopilot.com/chat/completions";
|
|
15
|
+
readonly EMBEDDINGS: "https://api.githubcopilot.com/embeddings";
|
|
14
16
|
readonly ORG_BILLING: (org: string) => string;
|
|
15
17
|
readonly ORG_SEATS: (org: string) => string;
|
|
16
18
|
readonly USER_COPILOT: "https://api.github.com/user/copilot_access";
|
|
@@ -57,10 +59,13 @@ export type GitHubCopilotStatusCode = typeof GITHUB_COPILOT_API.STATUS_CODES[key
|
|
|
57
59
|
export declare class GitHubCopilotEndpoints {
|
|
58
60
|
static getModelsUrl(): string;
|
|
59
61
|
static getChatCompletionsUrl(): string;
|
|
62
|
+
static getEmbeddingsUrl(): string;
|
|
60
63
|
static getOrgBillingUrl(org: string): string;
|
|
61
64
|
static getOrgSeatsUrl(org: string): string;
|
|
62
65
|
static getUserCopilotUrl(): string;
|
|
63
66
|
static getAuthHeaders(token: string, includeVSCodeHeaders?: boolean): Record<string, string>;
|
|
67
|
+
static getEmbeddingsHeaders(token: string): Record<string, string>;
|
|
68
|
+
private static generateUUID;
|
|
64
69
|
static getRetryDelay(attempt: number): number;
|
|
65
70
|
static isTpmQuotaError(statusCode: number): boolean;
|
|
66
71
|
static validateToken(token: string): boolean;
|
|
@@ -7,6 +7,7 @@ exports.GITHUB_COPILOT_API = {
|
|
|
7
7
|
ENDPOINTS: {
|
|
8
8
|
MODELS: "/models",
|
|
9
9
|
CHAT_COMPLETIONS: "/chat/completions",
|
|
10
|
+
EMBEDDINGS: "/embeddings",
|
|
10
11
|
ORG_BILLING: (org) => `/orgs/${org}/copilot/billing`,
|
|
11
12
|
ORG_SEATS: (org) => `/orgs/${org}/copilot/billing/seats`,
|
|
12
13
|
USER_COPILOT: "/user/copilot_access",
|
|
@@ -14,6 +15,7 @@ exports.GITHUB_COPILOT_API = {
|
|
|
14
15
|
URLS: {
|
|
15
16
|
MODELS: "https://api.githubcopilot.com/models",
|
|
16
17
|
CHAT_COMPLETIONS: "https://api.githubcopilot.com/chat/completions",
|
|
18
|
+
EMBEDDINGS: "https://api.githubcopilot.com/embeddings",
|
|
17
19
|
ORG_BILLING: (org) => `https://api.github.com/orgs/${org}/copilot/billing`,
|
|
18
20
|
ORG_SEATS: (org) => `https://api.github.com/orgs/${org}/copilot/billing/seats`,
|
|
19
21
|
USER_COPILOT: "https://api.github.com/user/copilot_access",
|
|
@@ -61,6 +63,9 @@ class GitHubCopilotEndpoints {
|
|
|
61
63
|
static getChatCompletionsUrl() {
|
|
62
64
|
return exports.GITHUB_COPILOT_API.URLS.CHAT_COMPLETIONS;
|
|
63
65
|
}
|
|
66
|
+
static getEmbeddingsUrl() {
|
|
67
|
+
return exports.GITHUB_COPILOT_API.URLS.EMBEDDINGS;
|
|
68
|
+
}
|
|
64
69
|
static getOrgBillingUrl(org) {
|
|
65
70
|
return exports.GITHUB_COPILOT_API.URLS.ORG_BILLING(org);
|
|
66
71
|
}
|
|
@@ -80,6 +85,26 @@ class GitHubCopilotEndpoints {
|
|
|
80
85
|
}
|
|
81
86
|
return headers;
|
|
82
87
|
}
|
|
88
|
+
static getEmbeddingsHeaders(token) {
|
|
89
|
+
const sessionId = `${this.generateUUID()}-${Date.now()}`;
|
|
90
|
+
return {
|
|
91
|
+
"Authorization": `Bearer ${token}`,
|
|
92
|
+
"Content-Type": "application/json",
|
|
93
|
+
"Accept": "application/json",
|
|
94
|
+
"Editor-Version": "vscode/1.95.0",
|
|
95
|
+
"Editor-Plugin-Version": "copilot/1.0.0",
|
|
96
|
+
"User-Agent": "GitHub-Copilot/1.0 (n8n-node)",
|
|
97
|
+
"Vscode-Sessionid": sessionId,
|
|
98
|
+
"X-GitHub-Api-Version": "2025-08-20",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
static generateUUID() {
|
|
102
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
103
|
+
const r = (Math.random() * 16) | 0;
|
|
104
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
105
|
+
return v.toString(16);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
83
108
|
static getRetryDelay(attempt) {
|
|
84
109
|
const delay = exports.GITHUB_COPILOT_API.RATE_LIMITS.TPM_RETRY_DELAY_BASE *
|
|
85
110
|
Math.pow(exports.GITHUB_COPILOT_API.RATE_LIMITS.EXPONENTIAL_BACKOFF_FACTOR, attempt - 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-github-copilot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.37.1",
|
|
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",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"dist/nodes/GitHubCopilotAuthHelper/GitHubCopilotAuthHelper.node.js",
|
|
34
34
|
"dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js",
|
|
35
35
|
"dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js",
|
|
36
|
+
"dist/nodes/GitHubCopilotEmbeddings/GitHubCopilotEmbeddings.node.js",
|
|
36
37
|
"dist/nodes/GitHubCopilotOpenAI/GitHubCopilotOpenAI.node.js",
|
|
37
38
|
"dist/nodes/GitHubCopilotTest/GitHubCopilotTest.node.js"
|
|
38
39
|
]
|