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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.makeGitHubCopilotRequest = makeGitHubCopilotRequest;
|
|
4
|
+
exports.uploadFileToCopilot = uploadFileToCopilot;
|
|
4
5
|
exports.downloadFileFromUrl = downloadFileFromUrl;
|
|
5
6
|
exports.getFileFromBinary = getFileFromBinary;
|
|
6
7
|
exports.getImageMimeType = getImageMimeType;
|
|
@@ -87,6 +88,41 @@ async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = fals
|
|
|
87
88
|
headers,
|
|
88
89
|
body: JSON.stringify(body),
|
|
89
90
|
};
|
|
91
|
+
async function uploadFile(buffer, filename, mimeType = 'application/octet-stream') {
|
|
92
|
+
const url = `${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL}/copilot/chat/attachments/files`;
|
|
93
|
+
let form;
|
|
94
|
+
try {
|
|
95
|
+
form = new FormData();
|
|
96
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
97
|
+
form.append('file', blob, filename);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const FormData = require('form-data');
|
|
101
|
+
form = new FormData();
|
|
102
|
+
form.append('file', buffer, { filename, contentType: mimeType });
|
|
103
|
+
}
|
|
104
|
+
const uploadHeaders = {
|
|
105
|
+
...GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.HEADERS.WITH_AUTH(token),
|
|
106
|
+
'X-GitHub-Api-Version': '2025-05-01',
|
|
107
|
+
'X-Interaction-Type': 'copilot-chat',
|
|
108
|
+
'OpenAI-Intent': 'conversation-panel',
|
|
109
|
+
'Copilot-Integration-Id': 'vscode-chat',
|
|
110
|
+
};
|
|
111
|
+
if (typeof form.getHeaders === 'function') {
|
|
112
|
+
Object.assign(uploadHeaders, form.getHeaders());
|
|
113
|
+
}
|
|
114
|
+
const res = await fetch(url, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: uploadHeaders,
|
|
117
|
+
body: form,
|
|
118
|
+
});
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
const text = await res.text();
|
|
121
|
+
throw new Error(`File upload failed: ${res.status} ${res.statusText} - ${text}`);
|
|
122
|
+
}
|
|
123
|
+
const json = await res.json();
|
|
124
|
+
return json;
|
|
125
|
+
}
|
|
90
126
|
const fullUrl = `${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL}${endpoint}`;
|
|
91
127
|
let lastError = null;
|
|
92
128
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
@@ -104,19 +140,15 @@ async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = fals
|
|
|
104
140
|
const errorText = await response.text();
|
|
105
141
|
if (response.status === 400) {
|
|
106
142
|
console.log(`🚫 400 Bad Request detected - not retrying`);
|
|
107
|
-
|
|
108
|
-
throw new Error(enhancedError);
|
|
143
|
+
throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}. ${errorText}`);
|
|
109
144
|
}
|
|
110
145
|
const tokenPrefix = token.substring(0, 4);
|
|
111
146
|
const tokenSuffix = token.substring(token.length - 5);
|
|
112
147
|
const tokenInfo = `${tokenPrefix}...${tokenSuffix}`;
|
|
113
148
|
console.error(`❌ GitHub Copilot API Error: ${response.status} ${response.statusText}`);
|
|
114
149
|
console.error(`❌ Error details: ${errorText}`);
|
|
115
|
-
console.error(`❌ Used credential type: ${credentialType}`);
|
|
116
|
-
console.error(`❌ Token format used: ${tokenInfo}`);
|
|
117
150
|
console.error(`❌ Attempt: ${attempt}/${MAX_RETRIES}`);
|
|
118
|
-
|
|
119
|
-
throw new Error(enhancedError);
|
|
151
|
+
throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}. ${errorText} [Token: ${tokenInfo}] [Attempt: ${attempt}/${MAX_RETRIES}]`);
|
|
120
152
|
}
|
|
121
153
|
if (attempt > 1) {
|
|
122
154
|
console.log(`✅ GitHub Copilot API succeeded on attempt ${attempt}/${MAX_RETRIES}`);
|
|
@@ -144,6 +176,53 @@ async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = fals
|
|
|
144
176
|
}
|
|
145
177
|
throw lastError || new Error("GitHub Copilot API request failed after all retries");
|
|
146
178
|
}
|
|
179
|
+
async function uploadFileToCopilot(context, buffer, filename, mimeType = 'application/octet-stream') {
|
|
180
|
+
let credentialType = 'githubCopilotApi';
|
|
181
|
+
try {
|
|
182
|
+
credentialType = context.getNodeParameter('credentialType', 0, 'githubCopilotApi');
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
185
|
+
const credentials = await context.getCredentials(credentialType);
|
|
186
|
+
if (!credentials || !credentials.token) {
|
|
187
|
+
throw new Error('GitHub Copilot: No token found in credentials for file upload');
|
|
188
|
+
}
|
|
189
|
+
const githubToken = credentials.token;
|
|
190
|
+
const token = await OAuthTokenManager_1.OAuthTokenManager.getValidOAuthToken(githubToken);
|
|
191
|
+
const url = `${GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL}/copilot/chat/attachments/files`;
|
|
192
|
+
let form;
|
|
193
|
+
try {
|
|
194
|
+
form = new FormData();
|
|
195
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
196
|
+
form.append('file', blob, filename);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const FormData = require('form-data');
|
|
200
|
+
form = new FormData();
|
|
201
|
+
form.append('file', buffer, { filename, contentType: mimeType });
|
|
202
|
+
}
|
|
203
|
+
const headers = {
|
|
204
|
+
...GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.HEADERS.WITH_AUTH(token),
|
|
205
|
+
'X-GitHub-Api-Version': '2025-05-01',
|
|
206
|
+
'X-Interaction-Type': 'copilot-chat',
|
|
207
|
+
'OpenAI-Intent': 'conversation-panel',
|
|
208
|
+
'Copilot-Integration-Id': 'vscode-chat',
|
|
209
|
+
'Copilot-Vision-Request': 'true',
|
|
210
|
+
'Copilot-Media-Request': 'true',
|
|
211
|
+
};
|
|
212
|
+
if (typeof form.getHeaders === 'function') {
|
|
213
|
+
Object.assign(headers, form.getHeaders());
|
|
214
|
+
}
|
|
215
|
+
const res = await fetch(url, {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
headers: headers,
|
|
218
|
+
body: form,
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
const text = await res.text();
|
|
222
|
+
throw new Error(`File upload failed: ${res.status} ${res.statusText} - ${text}`);
|
|
223
|
+
}
|
|
224
|
+
return await res.json();
|
|
225
|
+
}
|
|
147
226
|
async function downloadFileFromUrl(url) {
|
|
148
227
|
const response = await fetch(url);
|
|
149
228
|
if (!response.ok) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-github-copilot",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "n8n community node for GitHub Copilot with CLI, Chat API, AI Chat Model
|
|
3
|
+
"version": "4.4.0",
|
|
4
|
+
"description": "n8n community node for GitHub Copilot with CLI integration, Chat API access, and AI Chat Model for workflows with full tools and function calling support - 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",
|
|
7
7
|
"author": {
|
|
@@ -21,8 +21,7 @@
|
|
|
21
21
|
"prepublishOnly": "npm run build"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
|
-
"dist"
|
|
25
|
-
"shared"
|
|
24
|
+
"dist"
|
|
26
25
|
],
|
|
27
26
|
"n8n": {
|
|
28
27
|
"n8nNodesApiVersion": 1,
|
package/shared/icons/copilot.svg
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
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/shared/index.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared utilities and models for n8n-nodes-copilot
|
|
3
|
-
*
|
|
4
|
-
* @module shared
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Version detection utilities
|
|
8
|
-
export {
|
|
9
|
-
detectN8nVersion,
|
|
10
|
-
isN8nV2OrHigher,
|
|
11
|
-
isChatHubAvailable,
|
|
12
|
-
getN8nVersionString,
|
|
13
|
-
type N8nVersionInfo,
|
|
14
|
-
} from './utils/version-detection';
|
|
15
|
-
|
|
16
|
-
// Provider injection utilities
|
|
17
|
-
export {
|
|
18
|
-
injectGitHubCopilotProvider,
|
|
19
|
-
getInjectionStatus,
|
|
20
|
-
isProviderInjected,
|
|
21
|
-
autoInject,
|
|
22
|
-
type ProviderInjectionStatus,
|
|
23
|
-
} from './utils/provider-injection';
|
|
24
|
-
|
|
25
|
-
// Auto-inject on module load if enabled
|
|
26
|
-
import { autoInject } from './utils/provider-injection';
|
|
27
|
-
autoInject();
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dynamic Model Loader for n8n Nodes
|
|
3
|
-
*
|
|
4
|
-
* Provides dynamic model loading functionality for n8n nodes.
|
|
5
|
-
* Models are loaded based on the authenticated user's available models.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { ILoadOptionsFunctions, INodePropertyOptions } from "n8n-workflow";
|
|
9
|
-
import { DynamicModelsManager } from "../utils/DynamicModelsManager";
|
|
10
|
-
import { OAuthTokenManager } from "../utils/OAuthTokenManager";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Load available models dynamically based on user authentication
|
|
14
|
-
* Use this in node properties with `loadOptions` method
|
|
15
|
-
*
|
|
16
|
-
* @param forceRefresh - If true, bypasses cache and fetches fresh data from API
|
|
17
|
-
*/
|
|
18
|
-
export async function loadAvailableModels(
|
|
19
|
-
this: ILoadOptionsFunctions,
|
|
20
|
-
forceRefresh = false
|
|
21
|
-
): Promise<INodePropertyOptions[]> {
|
|
22
|
-
return loadModelsWithFilter.call(this, "chat", forceRefresh);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Load available embedding models dynamically
|
|
27
|
-
* Use this in embedding nodes with `loadOptions` method
|
|
28
|
-
*
|
|
29
|
-
* @param forceRefresh - If true, bypasses cache and fetches fresh data from API
|
|
30
|
-
*/
|
|
31
|
-
export async function loadAvailableEmbeddingModels(
|
|
32
|
-
this: ILoadOptionsFunctions,
|
|
33
|
-
forceRefresh = false
|
|
34
|
-
): Promise<INodePropertyOptions[]> {
|
|
35
|
-
return loadModelsWithFilter.call(this, "embeddings", forceRefresh);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Internal function to load models with type filter
|
|
40
|
-
*/
|
|
41
|
-
async function loadModelsWithFilter(
|
|
42
|
-
this: ILoadOptionsFunctions,
|
|
43
|
-
modelType: "chat" | "embeddings",
|
|
44
|
-
forceRefresh = false
|
|
45
|
-
): Promise<INodePropertyOptions[]> {
|
|
46
|
-
try {
|
|
47
|
-
// Get credentials
|
|
48
|
-
const credentials = await this.getCredentials("githubCopilotApi");
|
|
49
|
-
|
|
50
|
-
if (!credentials || !credentials.token) {
|
|
51
|
-
console.warn("⚠️ No credentials found for dynamic model loading");
|
|
52
|
-
// Return only manual input option (no fallback)
|
|
53
|
-
return [
|
|
54
|
-
{
|
|
55
|
-
name: "✏️ Enter Custom Model Name",
|
|
56
|
-
value: "__manual__",
|
|
57
|
-
description: "Type your own model name (no credentials found)",
|
|
58
|
-
},
|
|
59
|
-
];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Get GitHub token
|
|
63
|
-
const githubToken = credentials.token as string;
|
|
64
|
-
|
|
65
|
-
// Generate OAuth token
|
|
66
|
-
let oauthToken: string;
|
|
67
|
-
try {
|
|
68
|
-
oauthToken = await OAuthTokenManager.getValidOAuthToken(githubToken);
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error("❌ Failed to generate OAuth token for model loading:", error);
|
|
71
|
-
// Return only manual input option (no fallback)
|
|
72
|
-
return [
|
|
73
|
-
{
|
|
74
|
-
name: "✏️ Enter Custom Model Name",
|
|
75
|
-
value: "__manual__",
|
|
76
|
-
description: "Type your own model name (OAuth generation failed)",
|
|
77
|
-
},
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Clear cache if force refresh requested
|
|
82
|
-
if (forceRefresh) {
|
|
83
|
-
DynamicModelsManager.clearCache(oauthToken);
|
|
84
|
-
console.log("🔄 Force refreshing models list...");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Fetch available models
|
|
88
|
-
const allModels = await DynamicModelsManager.getAvailableModels(oauthToken);
|
|
89
|
-
|
|
90
|
-
// Filter by type
|
|
91
|
-
const models = DynamicModelsManager.filterModelsByType(allModels, modelType);
|
|
92
|
-
|
|
93
|
-
console.log(`🔍 Filtered ${models.length} ${modelType} models from ${allModels.length} total models`);
|
|
94
|
-
|
|
95
|
-
// Convert to n8n options format
|
|
96
|
-
const options = DynamicModelsManager.modelsToN8nOptions(models);
|
|
97
|
-
|
|
98
|
-
// Add manual input option at the top (for both chat and embeddings)
|
|
99
|
-
const optionsWithManualInput: INodePropertyOptions[] = [
|
|
100
|
-
{
|
|
101
|
-
name: "✏️ Enter Custom Model Name",
|
|
102
|
-
value: "__manual__",
|
|
103
|
-
description: "Type your own model name (for new/beta models)",
|
|
104
|
-
},
|
|
105
|
-
...options,
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
console.log(`✅ Loaded ${options.length} ${modelType} models dynamically (+ manual input option)`);
|
|
109
|
-
return optionsWithManualInput;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
console.error("❌ Error loading dynamic models:", error);
|
|
112
|
-
// Return empty list with manual input option (no fallback)
|
|
113
|
-
// User must enter model name manually or wait for next successful discovery
|
|
114
|
-
return [
|
|
115
|
-
{
|
|
116
|
-
name: "✏️ Enter Custom Model Name",
|
|
117
|
-
value: "__manual__",
|
|
118
|
-
description: "Type your own model name (discovery failed, using previous cache if available)",
|
|
119
|
-
},
|
|
120
|
-
];
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|