n8n-nodes-github-copilot 3.10.0 → 3.11.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/utils/helpers.d.ts +9 -17
- package/dist/nodes/GitHubCopilotChatAPI/utils/helpers.js +12 -143
- package/dist/nodes/GitHubCopilotChatAPI/utils/types.d.ts +2 -13
- package/dist/nodes/GitHubCopilotChatModel/GitHubCopilotChatModel.node.js +62 -16
- package/dist/shared/utils/GitHubCopilotApiUtils.d.ts +37 -0
- package/dist/shared/utils/GitHubCopilotApiUtils.js +167 -0
- package/package.json +1 -1
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
import { IExecuteFunctions } from 'n8n-workflow';
|
|
2
|
-
import { CopilotResponse } from '
|
|
2
|
+
import { CopilotResponse, downloadFileFromUrl as sharedDownloadFileFromUrl, getFileFromBinary as sharedGetFileFromBinary, getImageMimeType as sharedGetImageMimeType, getAudioMimeType as sharedGetAudioMimeType, validateFileSize as sharedValidateFileSize, estimateTokens as sharedEstimateTokens, validateTokenLimit as sharedValidateTokenLimit, truncateToTokenLimit as sharedTruncateToTokenLimit } from '../../../shared/utils/GitHubCopilotApiUtils';
|
|
3
3
|
export declare function makeApiRequest(context: IExecuteFunctions, endpoint: string, body: Record<string, unknown>, hasMedia?: boolean): Promise<CopilotResponse>;
|
|
4
|
-
export declare
|
|
5
|
-
export declare
|
|
6
|
-
export declare
|
|
7
|
-
export declare
|
|
8
|
-
export declare
|
|
9
|
-
export declare
|
|
10
|
-
export declare
|
|
11
|
-
|
|
12
|
-
message?: string;
|
|
13
|
-
};
|
|
14
|
-
export declare function truncateToTokenLimit(content: string, maxTokens?: number): {
|
|
15
|
-
content: string;
|
|
16
|
-
truncated: boolean;
|
|
17
|
-
originalTokens: number;
|
|
18
|
-
finalTokens: number;
|
|
19
|
-
};
|
|
4
|
+
export declare const downloadFileFromUrl: typeof sharedDownloadFileFromUrl;
|
|
5
|
+
export declare const getFileFromBinary: typeof sharedGetFileFromBinary;
|
|
6
|
+
export declare const getImageMimeType: typeof sharedGetImageMimeType;
|
|
7
|
+
export declare const getAudioMimeType: typeof sharedGetAudioMimeType;
|
|
8
|
+
export declare const validateFileSize: typeof sharedValidateFileSize;
|
|
9
|
+
export declare const estimateTokens: typeof sharedEstimateTokens;
|
|
10
|
+
export declare const validateTokenLimit: typeof sharedValidateTokenLimit;
|
|
11
|
+
export declare const truncateToTokenLimit: typeof sharedTruncateToTokenLimit;
|
|
@@ -1,147 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.truncateToTokenLimit = exports.validateTokenLimit = exports.estimateTokens = exports.validateFileSize = exports.getAudioMimeType = exports.getImageMimeType = exports.getFileFromBinary = exports.downloadFileFromUrl = void 0;
|
|
3
4
|
exports.makeApiRequest = makeApiRequest;
|
|
4
|
-
|
|
5
|
-
exports.getFileFromBinary = getFileFromBinary;
|
|
6
|
-
exports.getImageMimeType = getImageMimeType;
|
|
7
|
-
exports.getAudioMimeType = getAudioMimeType;
|
|
8
|
-
exports.validateFileSize = validateFileSize;
|
|
9
|
-
exports.estimateTokens = estimateTokens;
|
|
10
|
-
exports.validateTokenLimit = validateTokenLimit;
|
|
11
|
-
exports.truncateToTokenLimit = truncateToTokenLimit;
|
|
5
|
+
const GitHubCopilotApiUtils_1 = require("../../../shared/utils/GitHubCopilotApiUtils");
|
|
12
6
|
async function makeApiRequest(context, endpoint, body, hasMedia = false) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
throw new Error('GitHub Copilot: No access token found in OAuth2 credentials. Available properties: ' + Object.keys(credentials).join(', '));
|
|
24
|
-
}
|
|
25
|
-
const tokenPrefix = token.substring(0, Math.min(4, token.indexOf('_') + 1)) || token.substring(0, 4);
|
|
26
|
-
const tokenSuffix = token.substring(Math.max(0, token.length - 5));
|
|
27
|
-
console.log(`🔍 GitHub Copilot OAuth2 Debug: Using token ${tokenPrefix}...${tokenSuffix}`);
|
|
28
|
-
if (!token.startsWith('gho_') && !token.startsWith('ghu_') && !token.startsWith('github_pat_')) {
|
|
29
|
-
console.warn(`⚠️ Unexpected token format: ${tokenPrefix}...${tokenSuffix}. Trying API call anyway.`);
|
|
30
|
-
}
|
|
31
|
-
const headers = {
|
|
32
|
-
'Authorization': `Bearer ${token}`,
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
'User-Agent': 'n8n-github-copilot-chat-api-node',
|
|
35
|
-
};
|
|
36
|
-
if (hasMedia) {
|
|
37
|
-
headers['Copilot-Vision-Request'] = 'true';
|
|
38
|
-
headers['Copilot-Media-Request'] = 'true';
|
|
39
|
-
}
|
|
40
|
-
const options = {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers,
|
|
43
|
-
body: JSON.stringify(body),
|
|
44
|
-
};
|
|
45
|
-
const response = await fetch(`https://api.githubcopilot.com${endpoint}`, options);
|
|
46
|
-
if (!response.ok) {
|
|
47
|
-
const errorText = await response.text();
|
|
48
|
-
throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}. ${errorText}`);
|
|
49
|
-
}
|
|
50
|
-
return await response.json();
|
|
51
|
-
}
|
|
52
|
-
async function downloadFileFromUrl(url) {
|
|
53
|
-
const response = await fetch(url);
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
throw new Error(`Failed to download file from URL: ${response.status} ${response.statusText}`);
|
|
56
|
-
}
|
|
57
|
-
return Buffer.from(await response.arrayBuffer());
|
|
58
|
-
}
|
|
59
|
-
async function getFileFromBinary(context, itemIndex, propertyName) {
|
|
60
|
-
const items = context.getInputData();
|
|
61
|
-
const item = items[itemIndex];
|
|
62
|
-
if (!item.binary || !item.binary[propertyName]) {
|
|
63
|
-
throw new Error(`No binary data found in property "${propertyName}"`);
|
|
64
|
-
}
|
|
65
|
-
const binaryData = item.binary[propertyName];
|
|
66
|
-
if (binaryData.data) {
|
|
67
|
-
return Buffer.from(binaryData.data, 'base64');
|
|
68
|
-
}
|
|
69
|
-
else if (binaryData.id) {
|
|
70
|
-
return await context.helpers.getBinaryDataBuffer(itemIndex, propertyName);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
throw new Error(`Invalid binary data format in property "${propertyName}"`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
function getImageMimeType(filename) {
|
|
77
|
-
const ext = filename.toLowerCase().split('.').pop();
|
|
78
|
-
switch (ext) {
|
|
79
|
-
case 'jpg':
|
|
80
|
-
case 'jpeg':
|
|
81
|
-
return 'image/jpeg';
|
|
82
|
-
case 'png':
|
|
83
|
-
return 'image/png';
|
|
84
|
-
case 'webp':
|
|
85
|
-
return 'image/webp';
|
|
86
|
-
case 'gif':
|
|
87
|
-
return 'image/gif';
|
|
88
|
-
default:
|
|
89
|
-
return 'image/jpeg';
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
function getAudioMimeType(filename) {
|
|
93
|
-
const ext = filename.toLowerCase().split('.').pop();
|
|
94
|
-
switch (ext) {
|
|
95
|
-
case 'mp3':
|
|
96
|
-
return 'audio/mpeg';
|
|
97
|
-
case 'wav':
|
|
98
|
-
return 'audio/wav';
|
|
99
|
-
case 'm4a':
|
|
100
|
-
return 'audio/mp4';
|
|
101
|
-
case 'flac':
|
|
102
|
-
return 'audio/flac';
|
|
103
|
-
case 'ogg':
|
|
104
|
-
return 'audio/ogg';
|
|
105
|
-
case 'aac':
|
|
106
|
-
return 'audio/aac';
|
|
107
|
-
default:
|
|
108
|
-
return 'audio/mpeg';
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function validateFileSize(buffer, maxSizeKB = 1024) {
|
|
112
|
-
const sizeKB = buffer.length / 1024;
|
|
113
|
-
if (sizeKB > maxSizeKB) {
|
|
114
|
-
throw new Error(`File size ${sizeKB.toFixed(2)}KB exceeds limit of ${maxSizeKB}KB`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function estimateTokens(base64String) {
|
|
118
|
-
return Math.ceil((base64String.length / 4 * 3) / 4);
|
|
119
|
-
}
|
|
120
|
-
function validateTokenLimit(estimatedTokens, maxTokens = 128000) {
|
|
121
|
-
if (estimatedTokens <= maxTokens) {
|
|
122
|
-
return { valid: true };
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
valid: false,
|
|
126
|
-
message: `Content too large: ${estimatedTokens} tokens exceeds limit of ${maxTokens}. Consider using smaller files or text.`
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function truncateToTokenLimit(content, maxTokens = 100000) {
|
|
130
|
-
const originalTokens = Math.ceil(content.length / 4);
|
|
131
|
-
if (originalTokens <= maxTokens) {
|
|
132
|
-
return {
|
|
133
|
-
content,
|
|
134
|
-
truncated: false,
|
|
135
|
-
originalTokens,
|
|
136
|
-
finalTokens: originalTokens
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
const targetLength = Math.floor(content.length * (maxTokens / originalTokens));
|
|
140
|
-
const truncatedContent = content.slice(0, targetLength) + '...[truncated]';
|
|
141
|
-
return {
|
|
142
|
-
content: truncatedContent,
|
|
143
|
-
truncated: true,
|
|
144
|
-
originalTokens,
|
|
145
|
-
finalTokens: Math.ceil(truncatedContent.length / 4)
|
|
146
|
-
};
|
|
147
|
-
}
|
|
7
|
+
return (0, GitHubCopilotApiUtils_1.makeGitHubCopilotRequest)(context, endpoint, body, 'githubOAuth2Api', hasMedia);
|
|
8
|
+
}
|
|
9
|
+
exports.downloadFileFromUrl = GitHubCopilotApiUtils_1.downloadFileFromUrl;
|
|
10
|
+
exports.getFileFromBinary = GitHubCopilotApiUtils_1.getFileFromBinary;
|
|
11
|
+
exports.getImageMimeType = GitHubCopilotApiUtils_1.getImageMimeType;
|
|
12
|
+
exports.getAudioMimeType = GitHubCopilotApiUtils_1.getAudioMimeType;
|
|
13
|
+
exports.validateFileSize = GitHubCopilotApiUtils_1.validateFileSize;
|
|
14
|
+
exports.estimateTokens = GitHubCopilotApiUtils_1.estimateTokens;
|
|
15
|
+
exports.validateTokenLimit = GitHubCopilotApiUtils_1.validateTokenLimit;
|
|
16
|
+
exports.truncateToTokenLimit = GitHubCopilotApiUtils_1.truncateToTokenLimit;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { IExecuteFunctions } from 'n8n-workflow';
|
|
2
|
+
import { CopilotResponse } from '../../../shared/utils/GitHubCopilotApiUtils';
|
|
3
|
+
export { CopilotResponse };
|
|
2
4
|
export interface ChatMessage {
|
|
3
5
|
role: 'system' | 'user' | 'assistant';
|
|
4
6
|
content: string | Array<ChatMessageContent>;
|
|
@@ -10,19 +12,6 @@ export interface ChatMessageContent {
|
|
|
10
12
|
url: string;
|
|
11
13
|
};
|
|
12
14
|
}
|
|
13
|
-
export interface CopilotResponse {
|
|
14
|
-
choices: Array<{
|
|
15
|
-
message: {
|
|
16
|
-
content: string;
|
|
17
|
-
};
|
|
18
|
-
finish_reason: string;
|
|
19
|
-
}>;
|
|
20
|
-
usage?: {
|
|
21
|
-
prompt_tokens: number;
|
|
22
|
-
completion_tokens: number;
|
|
23
|
-
total_tokens: number;
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
15
|
export interface FileProcessOptions {
|
|
27
16
|
context: IExecuteFunctions;
|
|
28
17
|
itemIndex: number;
|
|
@@ -33,12 +33,45 @@ class GitHubCopilotChatModel {
|
|
|
33
33
|
outputs: ["ai_languageModel"],
|
|
34
34
|
outputNames: ['Model'],
|
|
35
35
|
credentials: [
|
|
36
|
+
{
|
|
37
|
+
name: 'githubApiManual',
|
|
38
|
+
required: true,
|
|
39
|
+
displayOptions: {
|
|
40
|
+
show: {
|
|
41
|
+
credentialType: ['manual'],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
36
45
|
{
|
|
37
46
|
name: 'githubOAuth2Api',
|
|
38
47
|
required: true,
|
|
48
|
+
displayOptions: {
|
|
49
|
+
show: {
|
|
50
|
+
credentialType: ['oauth2'],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
39
53
|
},
|
|
40
54
|
],
|
|
41
55
|
properties: [
|
|
56
|
+
{
|
|
57
|
+
displayName: 'Credential Type',
|
|
58
|
+
name: 'credentialType',
|
|
59
|
+
type: 'options',
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
name: 'OAuth2 (Recommended)',
|
|
63
|
+
value: 'oauth2',
|
|
64
|
+
description: 'Use OAuth2 authentication for enterprise/organization accounts',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Manual Token',
|
|
68
|
+
value: 'manual',
|
|
69
|
+
description: 'Use manual token for development/personal accounts',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
default: 'oauth2',
|
|
73
|
+
description: 'Choose the authentication method',
|
|
74
|
+
},
|
|
42
75
|
{
|
|
43
76
|
displayName: 'Model',
|
|
44
77
|
name: 'model',
|
|
@@ -104,26 +137,36 @@ class GitHubCopilotChatModel {
|
|
|
104
137
|
};
|
|
105
138
|
}
|
|
106
139
|
async supplyData(itemIndex) {
|
|
140
|
+
var _a;
|
|
107
141
|
const model = this.getNodeParameter('model', itemIndex);
|
|
142
|
+
const credentialType = this.getNodeParameter('credentialType', itemIndex, 'oauth2');
|
|
108
143
|
const options = this.getNodeParameter('options', itemIndex, {});
|
|
109
144
|
const modelInfo = GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(model);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
145
|
+
let token;
|
|
146
|
+
if (credentialType === 'oauth2') {
|
|
147
|
+
const credentials = await this.getCredentials('githubOAuth2Api');
|
|
148
|
+
token = (credentials.accessToken ||
|
|
149
|
+
credentials.access_token ||
|
|
150
|
+
((_a = credentials.oauthTokenData) === null || _a === void 0 ? void 0 : _a.access_token) ||
|
|
151
|
+
credentials.token);
|
|
152
|
+
if (!token) {
|
|
153
|
+
console.error('❌ Available OAuth2 credential properties:', Object.keys(credentials));
|
|
154
|
+
throw new Error('GitHub Copilot: No access token found in OAuth2 credentials. Available properties: ' + Object.keys(credentials).join(', '));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const credentials = await this.getCredentials('githubApiManual');
|
|
159
|
+
token = credentials.accessToken;
|
|
160
|
+
if (!token) {
|
|
161
|
+
throw new Error('GitHub Copilot: No access token found in manual credentials');
|
|
162
|
+
}
|
|
114
163
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
`Received token with prefix: ${tokenPrefix}...${tokenSuffix}. ` +
|
|
121
|
-
`Personal Access Tokens (ghp_*) and other formats are not supported. ` +
|
|
122
|
-
`Please obtain a GitHub Copilot access token from your organization.`);
|
|
164
|
+
const tokenPrefix = token.substring(0, Math.min(4, token.indexOf('_') + 1)) || token.substring(0, 4);
|
|
165
|
+
const tokenSuffix = token.substring(Math.max(0, token.length - 5));
|
|
166
|
+
console.log(`🔍 GitHub Copilot ChatModel ${credentialType} Debug: Using token ${tokenPrefix}...${tokenSuffix}`);
|
|
167
|
+
if (!token.startsWith('gho_') && !token.startsWith('ghu_') && !token.startsWith('github_pat_')) {
|
|
168
|
+
console.warn(`⚠️ Unexpected token format: ${tokenPrefix}...${tokenSuffix}. Trying API call anyway.`);
|
|
123
169
|
}
|
|
124
|
-
const tokenPrefix = token.substring(0, token.indexOf('_') + 1);
|
|
125
|
-
const tokenSuffix = token.substring(token.length - 5);
|
|
126
|
-
console.log(`🔍 GitHub Copilot Auth Debug: Using token ${tokenPrefix}...${tokenSuffix}`);
|
|
127
170
|
const safeModel = modelInfo ? model : GitHubCopilotModels_1.DEFAULT_MODELS.GENERAL;
|
|
128
171
|
const safeModelInfo = modelInfo || GitHubCopilotModels_1.GitHubCopilotModelsManager.getModelByValue(GitHubCopilotModels_1.DEFAULT_MODELS.GENERAL);
|
|
129
172
|
const modelConfig = {
|
|
@@ -135,8 +178,11 @@ class GitHubCopilotChatModel {
|
|
|
135
178
|
baseURL: 'https://api.githubcopilot.com',
|
|
136
179
|
apiKey: token,
|
|
137
180
|
defaultHeaders: {
|
|
138
|
-
'User-Agent': 'n8n
|
|
181
|
+
'User-Agent': 'GitHubCopilotChat/1.0.0 n8n/3.10.1',
|
|
139
182
|
'Accept': 'application/json',
|
|
183
|
+
'Editor-Version': 'vscode/1.85.0',
|
|
184
|
+
'Editor-Plugin-Version': 'copilot-chat/0.12.0',
|
|
185
|
+
'X-Request-Id': `n8n-chatmodel-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
140
186
|
...(options.enableVision && (safeModelInfo === null || safeModelInfo === void 0 ? void 0 : safeModelInfo.capabilities.vision) && {
|
|
141
187
|
'Copilot-Vision-Request': 'true',
|
|
142
188
|
'Copilot-Media-Request': 'true'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { IExecuteFunctions } from 'n8n-workflow';
|
|
2
|
+
export interface CopilotResponse {
|
|
3
|
+
id: string;
|
|
4
|
+
object: string;
|
|
5
|
+
created: number;
|
|
6
|
+
model: string;
|
|
7
|
+
choices: Array<{
|
|
8
|
+
index: number;
|
|
9
|
+
message: {
|
|
10
|
+
role: string;
|
|
11
|
+
content: string;
|
|
12
|
+
};
|
|
13
|
+
finish_reason: string;
|
|
14
|
+
}>;
|
|
15
|
+
usage: {
|
|
16
|
+
prompt_tokens: number;
|
|
17
|
+
completion_tokens: number;
|
|
18
|
+
total_tokens: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export declare function makeGitHubCopilotRequest(context: IExecuteFunctions, endpoint: string, body: Record<string, unknown>, credentialType?: 'githubOAuth2Api' | 'githubApiManual', hasMedia?: boolean): Promise<CopilotResponse>;
|
|
22
|
+
export declare function downloadFileFromUrl(url: string): Promise<Buffer>;
|
|
23
|
+
export declare function getFileFromBinary(context: IExecuteFunctions, itemIndex: number, propertyName: string): Promise<Buffer>;
|
|
24
|
+
export declare function getImageMimeType(filename: string): string;
|
|
25
|
+
export declare function getAudioMimeType(filename: string): string;
|
|
26
|
+
export declare function validateFileSize(buffer: Buffer, maxSizeKB?: number): void;
|
|
27
|
+
export declare function estimateTokens(base64String: string): number;
|
|
28
|
+
export declare function validateTokenLimit(estimatedTokens: number, maxTokens?: number): {
|
|
29
|
+
valid: boolean;
|
|
30
|
+
message?: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function truncateToTokenLimit(content: string, maxTokens?: number): {
|
|
33
|
+
content: string;
|
|
34
|
+
truncated: boolean;
|
|
35
|
+
originalTokens: number;
|
|
36
|
+
finalTokens: number;
|
|
37
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeGitHubCopilotRequest = makeGitHubCopilotRequest;
|
|
4
|
+
exports.downloadFileFromUrl = downloadFileFromUrl;
|
|
5
|
+
exports.getFileFromBinary = getFileFromBinary;
|
|
6
|
+
exports.getImageMimeType = getImageMimeType;
|
|
7
|
+
exports.getAudioMimeType = getAudioMimeType;
|
|
8
|
+
exports.validateFileSize = validateFileSize;
|
|
9
|
+
exports.estimateTokens = estimateTokens;
|
|
10
|
+
exports.validateTokenLimit = validateTokenLimit;
|
|
11
|
+
exports.truncateToTokenLimit = truncateToTokenLimit;
|
|
12
|
+
async function makeGitHubCopilotRequest(context, endpoint, body, credentialType = 'githubOAuth2Api', hasMedia = false) {
|
|
13
|
+
var _a;
|
|
14
|
+
let token;
|
|
15
|
+
if (credentialType === 'githubOAuth2Api') {
|
|
16
|
+
const credentials = await context.getCredentials('githubOAuth2Api');
|
|
17
|
+
console.log('🔍 OAuth2 Credentials Debug:', Object.keys(credentials));
|
|
18
|
+
token = (credentials.accessToken ||
|
|
19
|
+
credentials.access_token ||
|
|
20
|
+
((_a = credentials.oauthTokenData) === null || _a === void 0 ? void 0 : _a.access_token) ||
|
|
21
|
+
credentials.token);
|
|
22
|
+
if (!token) {
|
|
23
|
+
console.error('❌ Available OAuth2 credential properties:', Object.keys(credentials));
|
|
24
|
+
console.error('❌ Full OAuth2 credential object:', JSON.stringify(credentials, null, 2));
|
|
25
|
+
throw new Error('GitHub Copilot: No access token found in OAuth2 credentials. Available properties: ' + Object.keys(credentials).join(', '));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const credentials = await context.getCredentials('githubApiManual');
|
|
30
|
+
console.log('🔍 Manual Credentials Debug:', Object.keys(credentials));
|
|
31
|
+
token = credentials.token || credentials.accessToken;
|
|
32
|
+
if (!token) {
|
|
33
|
+
console.error('❌ Available manual credential properties:', Object.keys(credentials));
|
|
34
|
+
throw new Error('GitHub Copilot: No access token found in manual credentials. Available properties: ' + Object.keys(credentials).join(', '));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const tokenPrefix = token.substring(0, Math.min(4, token.indexOf('_') + 1)) || token.substring(0, 4);
|
|
38
|
+
const tokenSuffix = token.substring(Math.max(0, token.length - 5));
|
|
39
|
+
console.log(`🔍 GitHub Copilot ${credentialType} Debug: Using token ${tokenPrefix}...${tokenSuffix}`);
|
|
40
|
+
if (!token.startsWith('gho_') && !token.startsWith('ghu_') && !token.startsWith('github_pat_')) {
|
|
41
|
+
console.warn(`⚠️ Unexpected token format: ${tokenPrefix}...${tokenSuffix}. Trying API call anyway.`);
|
|
42
|
+
}
|
|
43
|
+
const headers = {
|
|
44
|
+
'Authorization': `Bearer ${token}`,
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
'User-Agent': 'GitHubCopilotChat/1.0.0 n8n/3.10.1',
|
|
47
|
+
'Accept': 'application/json',
|
|
48
|
+
'Editor-Version': 'vscode/1.85.0',
|
|
49
|
+
'Editor-Plugin-Version': 'copilot-chat/0.12.0',
|
|
50
|
+
'X-Request-Id': `n8n-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
51
|
+
};
|
|
52
|
+
if (hasMedia) {
|
|
53
|
+
headers['Copilot-Vision-Request'] = 'true';
|
|
54
|
+
headers['Copilot-Media-Request'] = 'true';
|
|
55
|
+
}
|
|
56
|
+
const options = {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers,
|
|
59
|
+
body: JSON.stringify(body),
|
|
60
|
+
};
|
|
61
|
+
const response = await fetch(`https://api.githubcopilot.com${endpoint}`, options);
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorText = await response.text();
|
|
64
|
+
console.error(`❌ GitHub Copilot API Error: ${response.status} ${response.statusText}`);
|
|
65
|
+
console.error(`❌ Error details: ${errorText}`);
|
|
66
|
+
console.error(`❌ Used credential type: ${credentialType}`);
|
|
67
|
+
console.error(`❌ Token format: ${tokenPrefix}...${tokenSuffix}`);
|
|
68
|
+
throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}. ${errorText}`);
|
|
69
|
+
}
|
|
70
|
+
return await response.json();
|
|
71
|
+
}
|
|
72
|
+
async function downloadFileFromUrl(url) {
|
|
73
|
+
const response = await fetch(url);
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new Error(`Failed to download file from URL: ${response.status} ${response.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
return Buffer.from(await response.arrayBuffer());
|
|
78
|
+
}
|
|
79
|
+
async function getFileFromBinary(context, itemIndex, propertyName) {
|
|
80
|
+
const items = context.getInputData();
|
|
81
|
+
const item = items[itemIndex];
|
|
82
|
+
if (!item.binary || !item.binary[propertyName]) {
|
|
83
|
+
throw new Error(`No binary data found in property "${propertyName}"`);
|
|
84
|
+
}
|
|
85
|
+
const binaryData = item.binary[propertyName];
|
|
86
|
+
if (binaryData.data) {
|
|
87
|
+
return Buffer.from(binaryData.data, 'base64');
|
|
88
|
+
}
|
|
89
|
+
else if (binaryData.id) {
|
|
90
|
+
return await context.helpers.getBinaryDataBuffer(itemIndex, propertyName);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new Error(`Invalid binary data format in property "${propertyName}"`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function getImageMimeType(filename) {
|
|
97
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
98
|
+
switch (ext) {
|
|
99
|
+
case 'jpg':
|
|
100
|
+
case 'jpeg':
|
|
101
|
+
return 'image/jpeg';
|
|
102
|
+
case 'png':
|
|
103
|
+
return 'image/png';
|
|
104
|
+
case 'webp':
|
|
105
|
+
return 'image/webp';
|
|
106
|
+
case 'gif':
|
|
107
|
+
return 'image/gif';
|
|
108
|
+
default:
|
|
109
|
+
return 'image/jpeg';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function getAudioMimeType(filename) {
|
|
113
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
114
|
+
switch (ext) {
|
|
115
|
+
case 'mp3':
|
|
116
|
+
return 'audio/mpeg';
|
|
117
|
+
case 'wav':
|
|
118
|
+
return 'audio/wav';
|
|
119
|
+
case 'm4a':
|
|
120
|
+
return 'audio/mp4';
|
|
121
|
+
case 'flac':
|
|
122
|
+
return 'audio/flac';
|
|
123
|
+
case 'ogg':
|
|
124
|
+
return 'audio/ogg';
|
|
125
|
+
case 'aac':
|
|
126
|
+
return 'audio/aac';
|
|
127
|
+
default:
|
|
128
|
+
return 'audio/mpeg';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function validateFileSize(buffer, maxSizeKB = 1024) {
|
|
132
|
+
const sizeKB = buffer.length / 1024;
|
|
133
|
+
if (sizeKB > maxSizeKB) {
|
|
134
|
+
throw new Error(`File size ${sizeKB.toFixed(2)}KB exceeds limit of ${maxSizeKB}KB`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function estimateTokens(base64String) {
|
|
138
|
+
return Math.ceil((base64String.length / 4 * 3) / 4);
|
|
139
|
+
}
|
|
140
|
+
function validateTokenLimit(estimatedTokens, maxTokens = 128000) {
|
|
141
|
+
if (estimatedTokens <= maxTokens) {
|
|
142
|
+
return { valid: true };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
valid: false,
|
|
146
|
+
message: `Content too large: ${estimatedTokens} tokens exceeds limit of ${maxTokens}. Consider using smaller files or text.`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function truncateToTokenLimit(content, maxTokens = 100000) {
|
|
150
|
+
const originalTokens = Math.ceil(content.length / 4);
|
|
151
|
+
if (originalTokens <= maxTokens) {
|
|
152
|
+
return {
|
|
153
|
+
content,
|
|
154
|
+
truncated: false,
|
|
155
|
+
originalTokens,
|
|
156
|
+
finalTokens: originalTokens
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const targetLength = Math.floor(content.length * (maxTokens / originalTokens));
|
|
160
|
+
const truncatedContent = content.slice(0, targetLength) + '...[truncated]';
|
|
161
|
+
return {
|
|
162
|
+
content: truncatedContent,
|
|
163
|
+
truncated: true,
|
|
164
|
+
originalTokens,
|
|
165
|
+
finalTokens: Math.ceil(truncatedContent.length / 4)
|
|
166
|
+
};
|
|
167
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-github-copilot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.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",
|