n8n-nodes-github-copilot 3.31.31 → 3.32.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.
@@ -1,9 +1,9 @@
1
- import { ICredentialType, INodeProperties, ICredentialTestRequest, IAuthenticateGeneric } from "n8n-workflow";
1
+ import { ICredentialType, INodeProperties, ICredentialTestRequest, IHttpRequestOptions, ICredentialDataDecryptedObject } from "n8n-workflow";
2
2
  export declare class GitHubCopilotApi implements ICredentialType {
3
3
  name: string;
4
4
  displayName: string;
5
5
  documentationUrl: string;
6
6
  properties: INodeProperties[];
7
- authenticate: IAuthenticateGeneric;
7
+ authenticate(credentials: ICredentialDataDecryptedObject, requestOptions: IHttpRequestOptions): Promise<IHttpRequestOptions>;
8
8
  test: ICredentialTestRequest;
9
9
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GitHubCopilotApi = void 0;
4
4
  const GitHubCopilotEndpoints_1 = require("../shared/utils/GitHubCopilotEndpoints");
5
+ const OAuthTokenManager_1 = require("../shared/utils/OAuthTokenManager");
5
6
  class GitHubCopilotApi {
6
7
  constructor() {
7
8
  this.name = "githubCopilotApi";
@@ -9,34 +10,20 @@ class GitHubCopilotApi {
9
10
  this.documentationUrl = "https://docs.github.com/en/copilot/github-copilot-chat/copilot-chat-in-ides/using-github-copilot-chat-in-your-ide";
10
11
  this.properties = [
11
12
  {
12
- displayName: "⚠️ IMPORTANT: GitHub CLI Token Required",
13
- name: "warningNotice",
13
+ displayName: "Setup Instructions",
14
+ name: "setupNotice",
14
15
  type: "notice",
15
- default: "",
16
- },
17
- {
18
- displayName: "🚀 Don't have GitHub CLI installed?",
19
- name: "helperNotice1",
20
- type: "notice",
21
- default: "Use the visual 'GitHub Copilot Auth Helper' node to generate your token without touching the terminal!",
22
- },
23
- {
24
- displayName: "📝 Step 1: Create Helper Workflow",
25
- name: "helperNotice2",
26
- type: "notice",
27
- default: "Add a new workflow and insert the 'GitHub Copilot Auth Helper' trigger node.",
28
- },
29
- {
30
- displayName: "▶️ Step 2: Activate & Access",
31
- name: "helperNotice3",
32
- type: "notice",
33
- default: "Activate the workflow, then click 'Test workflow' to open the visual token generator in your browser.",
34
- },
35
- {
36
- displayName: "📋 Step 3: Copy Token",
37
- name: "helperNotice4",
38
- type: "notice",
39
- default: "Follow the on-screen instructions to authenticate with GitHub and copy the generated 'gho_' token back here.",
16
+ default: "ℹ️ Auto OAuth Token Generation\n" +
17
+ "Your GitHub CLI token will be used to automatically generate OAuth tokens. Tokens are cached and refreshed automatically before expiration (every ~20 minutes). No manual intervention needed!\n\n" +
18
+ "⚠️ IMPORTANT: GitHub CLI Token Required\n\n" +
19
+ "🚀 Don't have GitHub CLI installed?\n" +
20
+ "Use the visual 'GitHub Copilot Auth Helper' node to generate your token without touching the terminal!\n\n" +
21
+ "📝 Step 1: Create Helper Workflow\n" +
22
+ "Add a new workflow and insert the 'GitHub Copilot Auth Helper' trigger node.\n\n" +
23
+ "▶️ Step 2: Activate & Access\n" +
24
+ "Activate the workflow, then click 'Test workflow' to open the visual token generator in your browser.\n\n" +
25
+ "📋 Step 3: Copy Token\n" +
26
+ "Follow the on-screen instructions to authenticate with GitHub and copy the generated 'gho_' token back here.",
40
27
  },
41
28
  {
42
29
  displayName: "GitHub CLI Token",
@@ -47,20 +34,10 @@ class GitHubCopilotApi {
47
34
  },
48
35
  default: "",
49
36
  required: true,
50
- description: "Token generated by GitHub CLI (starts with 'gho_'). Use the Auth Helper node above for easy generation, or run in terminal: gh auth login && gh auth token",
37
+ description: "Token generated by GitHub CLI (starts with 'gho_'). OAuth tokens will be auto-generated and refreshed from this token.",
51
38
  placeholder: "gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
52
39
  },
53
40
  ];
54
- this.authenticate = {
55
- type: "generic",
56
- properties: {
57
- headers: {
58
- Authorization: "=Bearer {{$credentials.token}}",
59
- Accept: "application/json",
60
- "Content-Type": "application/json",
61
- },
62
- },
63
- };
64
41
  this.test = {
65
42
  request: {
66
43
  baseURL: GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.BASE_URL,
@@ -69,5 +46,28 @@ class GitHubCopilotApi {
69
46
  },
70
47
  };
71
48
  }
49
+ async authenticate(credentials, requestOptions) {
50
+ const githubToken = credentials.token;
51
+ if (!githubToken) {
52
+ throw new Error("GitHub CLI token is required");
53
+ }
54
+ if (!githubToken.startsWith("gho_")) {
55
+ throw new Error("Invalid token format. GitHub CLI token must start with 'gho_'");
56
+ }
57
+ let authToken;
58
+ try {
59
+ authToken = await OAuthTokenManager_1.OAuthTokenManager.getValidOAuthToken(githubToken);
60
+ }
61
+ catch (error) {
62
+ throw new Error(`Failed to generate OAuth token: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ if (!requestOptions.headers) {
65
+ requestOptions.headers = {};
66
+ }
67
+ requestOptions.headers.Authorization = `Bearer ${authToken}`;
68
+ requestOptions.headers.Accept = "application/json";
69
+ requestOptions.headers["Content-Type"] = "application/json";
70
+ return requestOptions;
71
+ }
72
72
  }
73
73
  exports.GitHubCopilotApi = GitHubCopilotApi;
@@ -31,7 +31,7 @@ class GitHubCopilotChatAPI {
31
31
  };
32
32
  }
33
33
  async execute() {
34
- var _a, _b, _c, _d;
34
+ var _a, _b, _c, _d, _e, _f;
35
35
  const items = this.getInputData();
36
36
  const returnData = [];
37
37
  for (let i = 0; i < items.length; i++) {
@@ -109,34 +109,41 @@ class GitHubCopilotChatAPI {
109
109
  const hasMedia = includeMedia;
110
110
  let response = null;
111
111
  let attempt = 1;
112
- while (attempt <= maxRetries + 1) {
112
+ const totalAttempts = maxRetries + 1;
113
+ let retriesUsed = 0;
114
+ while (attempt <= totalAttempts) {
113
115
  try {
114
116
  response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, hasMedia);
117
+ retriesUsed = ((_a = response._retryMetadata) === null || _a === void 0 ? void 0 : _a.retries) || 0;
118
+ attempt = ((_b = response._retryMetadata) === null || _b === void 0 ? void 0 : _b.attempts) || 1;
115
119
  break;
116
120
  }
117
121
  catch (error) {
118
- const isLastAttempt = attempt >= maxRetries + 1;
122
+ const isLastAttempt = attempt >= totalAttempts;
119
123
  const errorObj = error;
120
- const is403Error = errorObj.status === 403 || ((_a = errorObj.message) === null || _a === void 0 ? void 0 : _a.includes("403"));
124
+ const is403Error = errorObj.status === 403 || ((_c = errorObj.message) === null || _c === void 0 ? void 0 : _c.includes("403"));
121
125
  if (is403Error && enableRetry && !isLastAttempt) {
122
126
  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...`);
127
+ console.log(`GitHub Copilot API attempt ${attempt}/${totalAttempts} failed with 403, retrying in ${delay}ms...`);
124
128
  await new Promise((resolve) => setTimeout(resolve, delay));
125
129
  attempt++;
126
130
  continue;
127
131
  }
132
+ retriesUsed = attempt - 1;
128
133
  throw error;
129
134
  }
130
135
  }
131
136
  if (!response) {
132
- throw new Error("Failed to get response from GitHub Copilot API after all retry attempts");
137
+ throw new Error(`Failed to get response from GitHub Copilot API after ${totalAttempts} attempts (${retriesUsed} retries)`);
133
138
  }
134
139
  const result = {
135
- message: ((_c = (_b = response.choices[0]) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.content) || "",
140
+ message: ((_e = (_d = response.choices[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content) || "",
136
141
  model,
137
142
  operation,
138
143
  usage: response.usage || null,
139
- finish_reason: ((_d = response.choices[0]) === null || _d === void 0 ? void 0 : _d.finish_reason) || "unknown",
144
+ finish_reason: ((_f = response.choices[0]) === null || _f === void 0 ? void 0 : _f.finish_reason) || "unknown",
145
+ retries: retriesUsed,
146
+ attempts: attempt,
140
147
  };
141
148
  returnData.push({
142
149
  json: result,
@@ -30,6 +30,7 @@ class GitHubCopilotOpenAI {
30
30
  };
31
31
  }
32
32
  async execute() {
33
+ var _a, _b;
33
34
  const items = this.getInputData();
34
35
  const returnData = [];
35
36
  for (let i = 0; i < items.length; i++) {
@@ -163,6 +164,11 @@ class GitHubCopilotOpenAI {
163
164
  requestBody.seed = seed;
164
165
  }
165
166
  const response = await (0, utils_1.makeApiRequest)(this, GitHubCopilotEndpoints_1.GITHUB_COPILOT_API.ENDPOINTS.CHAT_COMPLETIONS, requestBody, false);
167
+ const retriesUsed = ((_a = response._retryMetadata) === null || _a === void 0 ? void 0 : _a.retries) || 0;
168
+ const attemptsUsed = ((_b = response._retryMetadata) === null || _b === void 0 ? void 0 : _b.attempts) || 1;
169
+ if (retriesUsed > 0) {
170
+ console.log(`ℹ️ Request completed after ${attemptsUsed} attempts (${retriesUsed} retries)`);
171
+ }
166
172
  const cleanJsonFromMarkdown = (content) => {
167
173
  if (!content || typeof content !== 'string') {
168
174
  return content;
@@ -230,6 +236,12 @@ class GitHubCopilotOpenAI {
230
236
  completion_tokens: 0,
231
237
  total_tokens: 0,
232
238
  },
239
+ ...(retriesUsed > 0 && {
240
+ _retry_info: {
241
+ attempts: attemptsUsed,
242
+ retries: retriesUsed,
243
+ }
244
+ }),
233
245
  };
234
246
  returnData.push({
235
247
  json: openAIResponse,
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-github-copilot",
3
- "version": "3.31.31",
3
+ "version": "3.32.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",
@@ -26,6 +26,11 @@ export interface CopilotResponse {
26
26
  completion_tokens: number;
27
27
  total_tokens: number;
28
28
  };
29
+ _retryMetadata?: {
30
+ attempts: number;
31
+ retries: number;
32
+ succeeded: boolean;
33
+ };
29
34
  }
30
35
  export interface RetryConfig {
31
36
  maxRetries?: number;
@@ -10,8 +10,9 @@ exports.estimateTokens = estimateTokens;
10
10
  exports.validateTokenLimit = validateTokenLimit;
11
11
  exports.truncateToTokenLimit = truncateToTokenLimit;
12
12
  const GitHubCopilotEndpoints_1 = require("./GitHubCopilotEndpoints");
13
+ const OAuthTokenManager_1 = require("./OAuthTokenManager");
13
14
  async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = false, retryConfig) {
14
- var _a, _b, _c, _d;
15
+ var _a, _b, _c;
15
16
  const MAX_RETRIES = (_a = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.maxRetries) !== null && _a !== void 0 ? _a : 3;
16
17
  const BASE_DELAY = (_b = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.baseDelay) !== null && _b !== void 0 ? _b : 500;
17
18
  const RETRY_ON_403 = (_c = retryConfig === null || retryConfig === void 0 ? void 0 : retryConfig.retryOn403) !== null && _c !== void 0 ? _c : true;
@@ -24,10 +25,22 @@ async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = fals
24
25
  }
25
26
  const credentials = await context.getCredentials(credentialType);
26
27
  console.log(`🔍 ${credentialType} Credentials Debug:`, Object.keys(credentials));
27
- const token = (credentials.accessToken ||
28
- credentials.access_token ||
29
- ((_d = credentials.oauthTokenData) === null || _d === void 0 ? void 0 : _d.access_token) ||
30
- credentials.token);
28
+ const githubToken = credentials.token;
29
+ if (!githubToken) {
30
+ throw new Error("GitHub CLI token (gho_*) not found in credentials");
31
+ }
32
+ if (!githubToken.startsWith("gho_")) {
33
+ throw new Error("Invalid GitHub token format. Must start with gho_");
34
+ }
35
+ console.log(`🔄 Using GitHub token to generate OAuth token...`);
36
+ let token;
37
+ try {
38
+ token = await OAuthTokenManager_1.OAuthTokenManager.getValidOAuthToken(githubToken);
39
+ console.log(`✅ OAuth token ready (auto-generated from GitHub token)`);
40
+ }
41
+ catch (error) {
42
+ throw new Error(`Failed to generate OAuth token: ${error instanceof Error ? error.message : String(error)}`);
43
+ }
31
44
  if (!token) {
32
45
  console.error(`❌ Available ${credentialType} credential properties:`, Object.keys(credentials));
33
46
  console.error(`❌ Full ${credentialType} credential object:`, JSON.stringify(credentials, null, 2));
@@ -83,7 +96,13 @@ async function makeGitHubCopilotRequest(context, endpoint, body, hasMedia = fals
83
96
  if (attempt > 1) {
84
97
  console.log(`✅ GitHub Copilot API succeeded on attempt ${attempt}/${MAX_RETRIES}`);
85
98
  }
86
- return await response.json();
99
+ const responseData = await response.json();
100
+ responseData._retryMetadata = {
101
+ attempts: attempt,
102
+ retries: attempt - 1,
103
+ succeeded: true
104
+ };
105
+ return responseData;
87
106
  }
88
107
  catch (error) {
89
108
  lastError = error instanceof Error ? error : new Error(String(error));
@@ -0,0 +1,19 @@
1
+ interface OAuthTokenCache {
2
+ token: string;
3
+ expiresAt: number;
4
+ generatedAt: number;
5
+ refreshIn: number;
6
+ }
7
+ export declare class OAuthTokenManager {
8
+ private static tokenCache;
9
+ private static machineIdCache;
10
+ static getValidOAuthToken(githubToken: string): Promise<string>;
11
+ private static generateOAuthToken;
12
+ private static getMachineId;
13
+ private static generateSessionId;
14
+ private static getCacheKey;
15
+ static clearCache(githubToken: string): void;
16
+ static getCacheInfo(githubToken: string): OAuthTokenCache | null;
17
+ static needsRefresh(githubToken: string, bufferMinutes?: number): boolean;
18
+ }
19
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OAuthTokenManager = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ const https_1 = __importDefault(require("https"));
9
+ class OAuthTokenManager {
10
+ static async getValidOAuthToken(githubToken) {
11
+ if (!githubToken || !githubToken.startsWith('gho_')) {
12
+ throw new Error('Invalid GitHub token. Must start with gho_');
13
+ }
14
+ const cacheKey = this.getCacheKey(githubToken);
15
+ const cached = this.tokenCache.get(cacheKey);
16
+ if (cached && cached.expiresAt > Date.now() + 120000) {
17
+ const remainingMinutes = Math.round((cached.expiresAt - Date.now()) / 1000 / 60);
18
+ console.log(`✅ Using cached OAuth token (${remainingMinutes} minutes remaining)`);
19
+ return cached.token;
20
+ }
21
+ console.log('🔄 Generating new OAuth token...');
22
+ const newToken = await this.generateOAuthToken(githubToken);
23
+ return newToken;
24
+ }
25
+ static async generateOAuthToken(githubToken) {
26
+ const machineId = this.getMachineId(githubToken);
27
+ const sessionId = this.generateSessionId();
28
+ return new Promise((resolve, reject) => {
29
+ const options = {
30
+ hostname: 'api.github.com',
31
+ path: '/copilot_internal/v2/token',
32
+ method: 'GET',
33
+ headers: {
34
+ 'Authorization': `token ${githubToken}`,
35
+ 'Vscode-Machineid': machineId,
36
+ 'Vscode-Sessionid': sessionId,
37
+ 'Editor-Version': 'vscode/1.105.1',
38
+ 'Editor-Plugin-Version': 'copilot-chat/0.32.3',
39
+ 'X-GitHub-Api-Version': '2025-08-20',
40
+ 'Accept': 'application/json',
41
+ 'User-Agent': 'n8n-nodes-copilot/1.0.0',
42
+ },
43
+ };
44
+ const req = https_1.default.request(options, (res) => {
45
+ let data = '';
46
+ res.on('data', (chunk) => (data += chunk));
47
+ res.on('end', () => {
48
+ if (res.statusCode === 200) {
49
+ try {
50
+ const response = JSON.parse(data);
51
+ const oauthToken = response.token;
52
+ const expiresAt = response.expires_at * 1000;
53
+ const refreshIn = response.refresh_in;
54
+ const cacheKey = this.getCacheKey(githubToken);
55
+ this.tokenCache.set(cacheKey, {
56
+ token: oauthToken,
57
+ expiresAt: expiresAt,
58
+ generatedAt: Date.now(),
59
+ refreshIn: refreshIn,
60
+ });
61
+ const expiresInMinutes = Math.round((expiresAt - Date.now()) / 1000 / 60);
62
+ console.log(`✅ OAuth token generated successfully (expires in ${expiresInMinutes} minutes)`);
63
+ resolve(oauthToken);
64
+ }
65
+ catch (error) {
66
+ reject(new Error(`Failed to parse OAuth token response: ${error}`));
67
+ }
68
+ }
69
+ else {
70
+ reject(new Error(`Failed to generate OAuth token: ${res.statusCode} ${res.statusMessage}`));
71
+ }
72
+ });
73
+ });
74
+ req.on('error', (error) => {
75
+ reject(new Error(`Network error generating OAuth token: ${error.message}`));
76
+ });
77
+ req.setTimeout(10000, () => {
78
+ req.destroy();
79
+ reject(new Error('OAuth token generation timeout'));
80
+ });
81
+ req.end();
82
+ });
83
+ }
84
+ static getMachineId(githubToken) {
85
+ const cacheKey = this.getCacheKey(githubToken);
86
+ if (!this.machineIdCache.has(cacheKey)) {
87
+ const uuid = crypto_1.default.randomUUID();
88
+ const machineId = crypto_1.default.createHash('sha256').update(uuid).digest('hex');
89
+ this.machineIdCache.set(cacheKey, machineId);
90
+ console.log('🆔 Generated new machine ID');
91
+ }
92
+ return this.machineIdCache.get(cacheKey);
93
+ }
94
+ static generateSessionId() {
95
+ return `${crypto_1.default.randomUUID()}${Date.now()}`;
96
+ }
97
+ static getCacheKey(githubToken) {
98
+ return githubToken.substring(0, 20);
99
+ }
100
+ static clearCache(githubToken) {
101
+ const cacheKey = this.getCacheKey(githubToken);
102
+ this.tokenCache.delete(cacheKey);
103
+ this.machineIdCache.delete(cacheKey);
104
+ console.log('🗑️ OAuth token cache cleared');
105
+ }
106
+ static getCacheInfo(githubToken) {
107
+ const cacheKey = this.getCacheKey(githubToken);
108
+ return this.tokenCache.get(cacheKey) || null;
109
+ }
110
+ static needsRefresh(githubToken, bufferMinutes = 2) {
111
+ const cacheKey = this.getCacheKey(githubToken);
112
+ const cached = this.tokenCache.get(cacheKey);
113
+ if (!cached)
114
+ return true;
115
+ const bufferMs = bufferMinutes * 60 * 1000;
116
+ return cached.expiresAt <= Date.now() + bufferMs;
117
+ }
118
+ }
119
+ exports.OAuthTokenManager = OAuthTokenManager;
120
+ OAuthTokenManager.tokenCache = new Map();
121
+ OAuthTokenManager.machineIdCache = new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-github-copilot",
3
- "version": "3.31.31",
3
+ "version": "3.32.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",