n8n-nodes-github-copilot 2.0.0 → 3.1.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/README.md +54 -11
- package/dist/nodes/GitHubCopilot/GitHubCopilot.node.js +3 -3
- package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.d.ts +5 -0
- package/dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js +603 -0
- package/dist/nodes/GitHubCopilotChatAPI/copilot.svg +34 -0
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -4,15 +4,27 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
Este é um **community node** para [n8n](https://n8n.io/) que integra o **GitHub Copilot CLI**
|
|
7
|
+
Este é um **community node** para [n8n](https://n8n.io/) que integra o **GitHub Copilot** de duas formas: através do CLI tradicional e da nova **API oficial do GitHub Copilot**, permitindo acesso direto aos modelos avançados de IA como GPT-5, Claude Opus 4.1, Gemini 2.5 Pro e muito mais usando seus créditos existentes do Copilot.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 🚀 Nodes Disponíveis
|
|
10
10
|
|
|
11
|
+
### 1. GitHub Copilot (CLI)
|
|
11
12
|
- **Sugestões de Código**: Gere código em múltiplas linguagens de programação
|
|
12
13
|
- **Explicação de Código**: Obtenha explicações detalhadas sobre funcionalidades de código
|
|
13
14
|
- **Comandos Shell**: Receba sugestões de comandos para Git, Docker, NPM e muito mais
|
|
14
15
|
- **Múltiplas Linguagens**: Suporte para JavaScript, TypeScript, Python, Ruby, Java, C#, Go, PHP, C++, Rust, SQL, HTML, CSS
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
### 2. GitHub Copilot Chat API (Novo! ⭐)
|
|
18
|
+
- **Chat Completion**: Conversas diretas com modelos avançados de IA
|
|
19
|
+
- **Análise de Imagens**: Processamento de imagens com modelos de visão
|
|
20
|
+
- **Transcrição de Áudio**: Conversão de áudio para texto (planejado)
|
|
21
|
+
- **Modelos Disponíveis**: GPT-5, GPT-5 Mini, Claude Opus 4.1, Gemini 2.5 Pro, Grok Code Fast 1, GPT-4.1 Copilot
|
|
22
|
+
- **Sem Custos Extras**: Usa seus créditos existentes do GitHub Copilot
|
|
23
|
+
|
|
24
|
+
## 🎯 Funcionalidades
|
|
25
|
+
|
|
26
|
+
- **Integração Dual**: CLI tradicional + API oficial do GitHub Copilot
|
|
27
|
+
- **Modelos Premium**: Acesso a GPT-5, Claude, Gemini através de sua assinatura Copilot
|
|
16
28
|
|
|
17
29
|
## 📋 Pré-requisitos
|
|
18
30
|
|
|
@@ -93,18 +105,49 @@ npm install n8n-nodes-github-copilot
|
|
|
93
105
|
|
|
94
106
|
## ⚙️ Configuração
|
|
95
107
|
|
|
96
|
-
###
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
### 🔐 IMPORTANTE: Autenticação
|
|
109
|
+
|
|
110
|
+
**GitHub Copilot CLI tem requisitos específicos de autenticação:**
|
|
111
|
+
|
|
112
|
+
#### ✅ **O que FUNCIONA:**
|
|
113
|
+
- **Autenticação Local**: `gh auth login` (recomendado para servidores)
|
|
114
|
+
- **Tokens do GitHub CLI**: Gerados por `gh auth token` após fazer login
|
|
115
|
+
|
|
116
|
+
#### ❌ **O que NÃO FUNCIONA:**
|
|
117
|
+
- **Personal Access Tokens** criados no site do GitHub
|
|
118
|
+
- **OAuth App Tokens** de aplicações externas
|
|
119
|
+
- **GitHub App Tokens** de aplicações personalizadas
|
|
120
|
+
|
|
121
|
+
### 🛠️ **Configuração Recomendada:**
|
|
122
|
+
|
|
123
|
+
#### **Opção 1: Autenticação Local (Servidores)**
|
|
124
|
+
```bash
|
|
125
|
+
# No servidor onde roda o n8n
|
|
126
|
+
gh auth login
|
|
127
|
+
gh extension install github/gh-copilot
|
|
128
|
+
|
|
129
|
+
# Testar se funciona
|
|
130
|
+
gh copilot explain "ls -la"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### **Opção 2: Token Manual (Para workflows específicos)**
|
|
134
|
+
```bash
|
|
135
|
+
# Primeiro faça login local
|
|
136
|
+
gh auth login
|
|
137
|
+
|
|
138
|
+
# Depois obtenha o token
|
|
139
|
+
gh auth token
|
|
140
|
+
# Resultado: gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
141
|
+
|
|
142
|
+
# Use este token no campo "GitHub Token (Optional)" do node
|
|
143
|
+
```
|
|
102
144
|
|
|
103
|
-
###
|
|
145
|
+
### 1. Adicionar o Node
|
|
104
146
|
1. Crie um novo workflow
|
|
105
147
|
2. Procure por **GitHub Copilot** na lista de nodes
|
|
106
148
|
3. Arraste para o canvas
|
|
107
|
-
4.
|
|
149
|
+
4. **Deixe o campo token vazio** (se o servidor tem `gh auth login`)
|
|
150
|
+
5. **OU** insira um token gerado por `gh auth token`
|
|
108
151
|
|
|
109
152
|
## 🎮 Como Usar
|
|
110
153
|
|
|
@@ -106,7 +106,7 @@ class GitHubCopilot {
|
|
|
106
106
|
typeOptions: { password: true },
|
|
107
107
|
default: '',
|
|
108
108
|
placeholder: 'gho_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
109
|
-
description: '
|
|
109
|
+
description: '⚠️ IMPORTANT: Only tokens generated by GitHub CLI itself work with Copilot. Personal Access Tokens from GitHub website DO NOT work. If not provided, will use local "gh auth login" authentication. To get a working token, use: gh auth token',
|
|
110
110
|
},
|
|
111
111
|
{
|
|
112
112
|
displayName: 'Filter Output',
|
|
@@ -268,8 +268,8 @@ class GitHubCopilot {
|
|
|
268
268
|
}
|
|
269
269
|
else if (stderr.includes('401') || stderr.includes('Unauthorized') || stderr.includes('Bad credentials')) {
|
|
270
270
|
const tokenHelp = useToken
|
|
271
|
-
? '
|
|
272
|
-
: ' Please run "gh auth login" on the server or provide a
|
|
271
|
+
? ' IMPORTANT: Only tokens generated by "gh auth token" command work with Copilot. Personal Access Tokens from GitHub website DO NOT work. Try: run "gh auth login" first, then use "gh auth token" to get a working token.'
|
|
272
|
+
: ' Please run "gh auth login" on the server first, or provide a token generated by "gh auth token" command.';
|
|
273
273
|
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `GitHub authentication failed (HTTP 401).${tokenHelp}${debugInfo} Full error response: ${stderr}`);
|
|
274
274
|
}
|
|
275
275
|
else if (stderr.includes('403') || stderr.includes('Forbidden')) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class GitHubCopilotChatAPI implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.GitHubCopilotChatAPI = void 0;
|
|
27
|
+
async function makeApiRequest(context, endpoint, body) {
|
|
28
|
+
const credentials = await context.getCredentials('githubApi');
|
|
29
|
+
const options = {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Authorization': `Bearer ${credentials.accessToken}`,
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'User-Agent': 'n8n-github-copilot-chat/3.0.0',
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify(body),
|
|
37
|
+
};
|
|
38
|
+
const response = await fetch(`https://api.githubcopilot.com${endpoint}`, options);
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
const errorText = await response.text();
|
|
41
|
+
throw new Error(`GitHub Copilot API error: ${response.status} ${response.statusText}. ${errorText}`);
|
|
42
|
+
}
|
|
43
|
+
return await response.json();
|
|
44
|
+
}
|
|
45
|
+
async function downloadFileFromUrl(url) {
|
|
46
|
+
const response = await fetch(url);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`Failed to download file from URL: ${response.status} ${response.statusText}`);
|
|
49
|
+
}
|
|
50
|
+
return Buffer.from(await response.arrayBuffer());
|
|
51
|
+
}
|
|
52
|
+
async function getFileFromBinary(context, itemIndex, propertyName) {
|
|
53
|
+
const items = context.getInputData();
|
|
54
|
+
const item = items[itemIndex];
|
|
55
|
+
if (!item.binary || !item.binary[propertyName]) {
|
|
56
|
+
throw new Error(`No binary data found in property "${propertyName}"`);
|
|
57
|
+
}
|
|
58
|
+
const binaryData = item.binary[propertyName];
|
|
59
|
+
if (binaryData.data) {
|
|
60
|
+
return Buffer.from(binaryData.data, 'base64');
|
|
61
|
+
}
|
|
62
|
+
else if (binaryData.id) {
|
|
63
|
+
return await context.helpers.getBinaryDataBuffer(itemIndex, propertyName);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw new Error(`Invalid binary data format in property "${propertyName}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function processAudioFile(context, itemIndex) {
|
|
70
|
+
const audioSource = context.getNodeParameter('audioSource', itemIndex);
|
|
71
|
+
let audioBuffer;
|
|
72
|
+
let filename = 'audio.mp3';
|
|
73
|
+
switch (audioSource) {
|
|
74
|
+
case 'manual':
|
|
75
|
+
const audioFile = context.getNodeParameter('audioFile', itemIndex);
|
|
76
|
+
if (audioFile.startsWith('data:audio/')) {
|
|
77
|
+
const base64Data = audioFile.split(',')[1];
|
|
78
|
+
audioBuffer = Buffer.from(base64Data, 'base64');
|
|
79
|
+
}
|
|
80
|
+
else if (audioFile.match(/^[A-Za-z0-9+/=]+$/)) {
|
|
81
|
+
audioBuffer = Buffer.from(audioFile, 'base64');
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
85
|
+
audioBuffer = fs.readFileSync(audioFile);
|
|
86
|
+
filename = audioFile.split(/[\\\/]/).pop() || 'audio.mp3';
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
case 'url':
|
|
90
|
+
const audioUrl = context.getNodeParameter('audioUrl', itemIndex);
|
|
91
|
+
audioBuffer = await downloadFileFromUrl(audioUrl);
|
|
92
|
+
filename = audioUrl.split('/').pop() || 'audio.mp3';
|
|
93
|
+
break;
|
|
94
|
+
case 'binary':
|
|
95
|
+
const audioBinaryProperty = context.getNodeParameter('audioBinaryProperty', itemIndex);
|
|
96
|
+
audioBuffer = await getFileFromBinary(context, itemIndex, audioBinaryProperty);
|
|
97
|
+
const items = context.getInputData();
|
|
98
|
+
const item = items[itemIndex];
|
|
99
|
+
if (item.binary && item.binary[audioBinaryProperty] && item.binary[audioBinaryProperty].fileName) {
|
|
100
|
+
filename = item.binary[audioBinaryProperty].fileName || 'audio.mp3';
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`Unknown audio source: ${audioSource}`);
|
|
105
|
+
}
|
|
106
|
+
const mimeType = getAudioMimeType(filename);
|
|
107
|
+
return `data:${mimeType};base64,${audioBuffer.toString('base64')}`;
|
|
108
|
+
}
|
|
109
|
+
async function processImageFile(context, itemIndex) {
|
|
110
|
+
const imageSource = context.getNodeParameter('imageSource', itemIndex);
|
|
111
|
+
let imageBuffer;
|
|
112
|
+
let filename = 'image.jpg';
|
|
113
|
+
switch (imageSource) {
|
|
114
|
+
case 'manual':
|
|
115
|
+
const imageFile = context.getNodeParameter('imageFile', itemIndex);
|
|
116
|
+
if (imageFile.startsWith('data:image/')) {
|
|
117
|
+
return imageFile;
|
|
118
|
+
}
|
|
119
|
+
else if (imageFile.match(/^[A-Za-z0-9+/=]+$/)) {
|
|
120
|
+
imageBuffer = Buffer.from(imageFile, 'base64');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
124
|
+
imageBuffer = fs.readFileSync(imageFile);
|
|
125
|
+
filename = imageFile.split(/[\\\/]/).pop() || 'image.jpg';
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
case 'url':
|
|
129
|
+
const imageUrl = context.getNodeParameter('imageUrl', itemIndex);
|
|
130
|
+
imageBuffer = await downloadFileFromUrl(imageUrl);
|
|
131
|
+
filename = imageUrl.split('/').pop() || 'image.jpg';
|
|
132
|
+
break;
|
|
133
|
+
case 'binary':
|
|
134
|
+
const imageBinaryProperty = context.getNodeParameter('imageBinaryProperty', itemIndex);
|
|
135
|
+
imageBuffer = await getFileFromBinary(context, itemIndex, imageBinaryProperty);
|
|
136
|
+
const items = context.getInputData();
|
|
137
|
+
const item = items[itemIndex];
|
|
138
|
+
if (item.binary && item.binary[imageBinaryProperty] && item.binary[imageBinaryProperty].fileName) {
|
|
139
|
+
filename = item.binary[imageBinaryProperty].fileName || 'image.jpg';
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
throw new Error(`Unknown image source: ${imageSource}`);
|
|
144
|
+
}
|
|
145
|
+
const mimeType = getMimeType(filename);
|
|
146
|
+
return `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
|
|
147
|
+
}
|
|
148
|
+
function getMimeType(filename) {
|
|
149
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
150
|
+
switch (ext) {
|
|
151
|
+
case 'jpg':
|
|
152
|
+
case 'jpeg':
|
|
153
|
+
return 'image/jpeg';
|
|
154
|
+
case 'png':
|
|
155
|
+
return 'image/png';
|
|
156
|
+
case 'webp':
|
|
157
|
+
return 'image/webp';
|
|
158
|
+
case 'gif':
|
|
159
|
+
return 'image/gif';
|
|
160
|
+
default:
|
|
161
|
+
return 'image/jpeg';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function getAudioMimeType(filename) {
|
|
165
|
+
const ext = filename.toLowerCase().split('.').pop();
|
|
166
|
+
switch (ext) {
|
|
167
|
+
case 'mp3':
|
|
168
|
+
return 'audio/mpeg';
|
|
169
|
+
case 'wav':
|
|
170
|
+
return 'audio/wav';
|
|
171
|
+
case 'm4a':
|
|
172
|
+
return 'audio/mp4';
|
|
173
|
+
case 'flac':
|
|
174
|
+
return 'audio/flac';
|
|
175
|
+
case 'ogg':
|
|
176
|
+
return 'audio/ogg';
|
|
177
|
+
case 'aac':
|
|
178
|
+
return 'audio/aac';
|
|
179
|
+
default:
|
|
180
|
+
return 'audio/mpeg';
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
class GitHubCopilotChatAPI {
|
|
184
|
+
constructor() {
|
|
185
|
+
this.description = {
|
|
186
|
+
displayName: 'GitHub Copilot Chat API',
|
|
187
|
+
name: 'gitHubCopilotChatAPI',
|
|
188
|
+
icon: 'file:copilot.svg',
|
|
189
|
+
group: ['AI'],
|
|
190
|
+
version: 1,
|
|
191
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["model"]}}',
|
|
192
|
+
description: 'Use official GitHub Copilot Chat API with your subscription - access GPT-5, Claude, Gemini and more',
|
|
193
|
+
defaults: {
|
|
194
|
+
name: 'GitHub Copilot Chat API',
|
|
195
|
+
},
|
|
196
|
+
inputs: ["main"],
|
|
197
|
+
outputs: ["main"],
|
|
198
|
+
credentials: [
|
|
199
|
+
{
|
|
200
|
+
name: 'githubApi',
|
|
201
|
+
required: true,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
properties: [
|
|
205
|
+
{
|
|
206
|
+
displayName: 'Operation',
|
|
207
|
+
name: 'operation',
|
|
208
|
+
type: 'options',
|
|
209
|
+
options: [
|
|
210
|
+
{
|
|
211
|
+
name: 'Chat Completion',
|
|
212
|
+
value: 'chatCompletion',
|
|
213
|
+
description: 'Send a message and get AI response using Copilot models',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'Audio Transcription',
|
|
217
|
+
value: 'audioTranscription',
|
|
218
|
+
description: 'Transcribe audio file to text using Copilot',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'Image Analysis',
|
|
222
|
+
value: 'imageAnalysis',
|
|
223
|
+
description: 'Analyze image with AI (vision models)',
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
default: 'chatCompletion',
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
displayName: 'Model',
|
|
230
|
+
name: 'model',
|
|
231
|
+
type: 'options',
|
|
232
|
+
options: [
|
|
233
|
+
{
|
|
234
|
+
name: 'GPT-5 (Latest)',
|
|
235
|
+
value: 'gpt-5',
|
|
236
|
+
description: 'OpenAI GPT-5 - Latest and most advanced model',
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'GPT-5 Mini',
|
|
240
|
+
value: 'gpt-5-mini',
|
|
241
|
+
description: 'OpenAI GPT-5 Mini - Faster and more efficient',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'Claude Opus 4.1',
|
|
245
|
+
value: 'claude-opus-4.1',
|
|
246
|
+
description: 'Anthropic Claude Opus 4.1 - Advanced reasoning',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'Gemini 2.5 Pro',
|
|
250
|
+
value: 'gemini-2.5-pro',
|
|
251
|
+
description: 'Google Gemini 2.5 Pro - Multimodal capabilities',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'Grok Code Fast 1',
|
|
255
|
+
value: 'grok-code-fast-1',
|
|
256
|
+
description: 'xAI Grok - Optimized for coding tasks',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'GPT-4.1 Copilot',
|
|
260
|
+
value: 'gpt-4.1-copilot',
|
|
261
|
+
description: 'GitHub Copilot optimized GPT-4.1 model',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
default: 'gpt-5',
|
|
265
|
+
description: 'Choose the AI model from your Copilot subscription',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
displayName: 'Message',
|
|
269
|
+
name: 'message',
|
|
270
|
+
type: 'string',
|
|
271
|
+
typeOptions: {
|
|
272
|
+
rows: 4,
|
|
273
|
+
},
|
|
274
|
+
default: '',
|
|
275
|
+
placeholder: 'Enter your message here...',
|
|
276
|
+
description: 'The message to send to the AI',
|
|
277
|
+
displayOptions: {
|
|
278
|
+
show: {
|
|
279
|
+
operation: ['chatCompletion', 'imageAnalysis'],
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
displayName: 'Audio Source',
|
|
285
|
+
name: 'audioSource',
|
|
286
|
+
type: 'options',
|
|
287
|
+
options: [
|
|
288
|
+
{ name: 'File Path or Base64', value: 'manual' },
|
|
289
|
+
{ name: 'Download from URL', value: 'url' },
|
|
290
|
+
{ name: 'From Previous Node Binary', value: 'binary' },
|
|
291
|
+
],
|
|
292
|
+
default: 'manual',
|
|
293
|
+
description: 'Choose how to provide the audio file',
|
|
294
|
+
displayOptions: {
|
|
295
|
+
show: {
|
|
296
|
+
operation: ['audioTranscription'],
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
displayName: 'Audio File',
|
|
302
|
+
name: 'audioFile',
|
|
303
|
+
type: 'string',
|
|
304
|
+
default: '',
|
|
305
|
+
placeholder: 'Path to audio file or base64 data',
|
|
306
|
+
description: 'Audio file to transcribe (supports MP3, WAV, M4A, FLAC, OGG)',
|
|
307
|
+
displayOptions: {
|
|
308
|
+
show: {
|
|
309
|
+
operation: ['audioTranscription'],
|
|
310
|
+
audioSource: ['manual'],
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
displayName: 'Audio URL',
|
|
316
|
+
name: 'audioUrl',
|
|
317
|
+
type: 'string',
|
|
318
|
+
default: '',
|
|
319
|
+
placeholder: 'https://example.com/audio.mp3',
|
|
320
|
+
description: 'URL to download audio file from',
|
|
321
|
+
displayOptions: {
|
|
322
|
+
show: {
|
|
323
|
+
operation: ['audioTranscription'],
|
|
324
|
+
audioSource: ['url'],
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
displayName: 'Binary Property Name',
|
|
330
|
+
name: 'audioBinaryProperty',
|
|
331
|
+
type: 'string',
|
|
332
|
+
default: 'data',
|
|
333
|
+
placeholder: 'data',
|
|
334
|
+
description: 'Name of the binary property containing the audio file',
|
|
335
|
+
displayOptions: {
|
|
336
|
+
show: {
|
|
337
|
+
operation: ['audioTranscription'],
|
|
338
|
+
audioSource: ['binary'],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
displayName: 'Audio Language',
|
|
344
|
+
name: 'audioLanguage',
|
|
345
|
+
type: 'options',
|
|
346
|
+
options: [
|
|
347
|
+
{ name: 'Auto-detect', value: 'auto' },
|
|
348
|
+
{ name: 'Portuguese (Brazil)', value: 'pt' },
|
|
349
|
+
{ name: 'English', value: 'en' },
|
|
350
|
+
{ name: 'Spanish', value: 'es' },
|
|
351
|
+
{ name: 'French', value: 'fr' },
|
|
352
|
+
{ name: 'German', value: 'de' },
|
|
353
|
+
{ name: 'Italian', value: 'it' },
|
|
354
|
+
{ name: 'Japanese', value: 'ja' },
|
|
355
|
+
{ name: 'Chinese', value: 'zh' },
|
|
356
|
+
],
|
|
357
|
+
default: 'auto',
|
|
358
|
+
description: 'Language of the audio (helps with accuracy)',
|
|
359
|
+
displayOptions: {
|
|
360
|
+
show: {
|
|
361
|
+
operation: ['audioTranscription'],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
displayName: 'Image Source',
|
|
367
|
+
name: 'imageSource',
|
|
368
|
+
type: 'options',
|
|
369
|
+
options: [
|
|
370
|
+
{ name: 'File Path or Base64', value: 'manual' },
|
|
371
|
+
{ name: 'Download from URL', value: 'url' },
|
|
372
|
+
{ name: 'From Previous Node Binary', value: 'binary' },
|
|
373
|
+
],
|
|
374
|
+
default: 'manual',
|
|
375
|
+
description: 'Choose how to provide the image file',
|
|
376
|
+
displayOptions: {
|
|
377
|
+
show: {
|
|
378
|
+
operation: ['imageAnalysis'],
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
displayName: 'Image File',
|
|
384
|
+
name: 'imageFile',
|
|
385
|
+
type: 'string',
|
|
386
|
+
default: '',
|
|
387
|
+
placeholder: 'Path to image file or base64 data',
|
|
388
|
+
description: 'Image file to analyze (supports JPG, PNG, WebP, GIF)',
|
|
389
|
+
displayOptions: {
|
|
390
|
+
show: {
|
|
391
|
+
operation: ['imageAnalysis'],
|
|
392
|
+
imageSource: ['manual'],
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
displayName: 'Image URL',
|
|
398
|
+
name: 'imageUrl',
|
|
399
|
+
type: 'string',
|
|
400
|
+
default: '',
|
|
401
|
+
placeholder: 'https://example.com/image.jpg',
|
|
402
|
+
description: 'URL to download image file from',
|
|
403
|
+
displayOptions: {
|
|
404
|
+
show: {
|
|
405
|
+
operation: ['imageAnalysis'],
|
|
406
|
+
imageSource: ['url'],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
displayName: 'Binary Property Name',
|
|
412
|
+
name: 'imageBinaryProperty',
|
|
413
|
+
type: 'string',
|
|
414
|
+
default: 'data',
|
|
415
|
+
placeholder: 'data',
|
|
416
|
+
description: 'Name of the binary property containing the image file',
|
|
417
|
+
displayOptions: {
|
|
418
|
+
show: {
|
|
419
|
+
operation: ['imageAnalysis'],
|
|
420
|
+
imageSource: ['binary'],
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
displayName: 'System Prompt',
|
|
426
|
+
name: 'systemPrompt',
|
|
427
|
+
type: 'string',
|
|
428
|
+
typeOptions: {
|
|
429
|
+
rows: 2,
|
|
430
|
+
},
|
|
431
|
+
default: '',
|
|
432
|
+
placeholder: 'You are a helpful assistant...',
|
|
433
|
+
description: 'Optional system prompt to set AI behavior',
|
|
434
|
+
displayOptions: {
|
|
435
|
+
show: {
|
|
436
|
+
operation: ['chatCompletion', 'imageAnalysis'],
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
displayName: 'Temperature',
|
|
442
|
+
name: 'temperature',
|
|
443
|
+
type: 'number',
|
|
444
|
+
typeOptions: {
|
|
445
|
+
minValue: 0,
|
|
446
|
+
maxValue: 2,
|
|
447
|
+
numberStepSize: 0.1,
|
|
448
|
+
},
|
|
449
|
+
default: 1,
|
|
450
|
+
description: 'Controls randomness: 0 = focused, 2 = creative',
|
|
451
|
+
displayOptions: {
|
|
452
|
+
show: {
|
|
453
|
+
operation: ['chatCompletion', 'imageAnalysis'],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
displayName: 'Max Tokens',
|
|
459
|
+
name: 'maxTokens',
|
|
460
|
+
type: 'number',
|
|
461
|
+
typeOptions: {
|
|
462
|
+
minValue: 1,
|
|
463
|
+
maxValue: 8192,
|
|
464
|
+
},
|
|
465
|
+
default: 2000,
|
|
466
|
+
description: 'Maximum tokens in response',
|
|
467
|
+
displayOptions: {
|
|
468
|
+
show: {
|
|
469
|
+
operation: ['chatCompletion', 'imageAnalysis'],
|
|
470
|
+
},
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async execute() {
|
|
477
|
+
const items = this.getInputData();
|
|
478
|
+
const returnData = [];
|
|
479
|
+
for (let i = 0; i < items.length; i++) {
|
|
480
|
+
try {
|
|
481
|
+
const operation = this.getNodeParameter('operation', i);
|
|
482
|
+
const model = this.getNodeParameter('model', i);
|
|
483
|
+
let result;
|
|
484
|
+
if (operation === 'chatCompletion') {
|
|
485
|
+
const message = this.getNodeParameter('message', i);
|
|
486
|
+
const systemPrompt = this.getNodeParameter('systemPrompt', i);
|
|
487
|
+
const temperature = this.getNodeParameter('temperature', i);
|
|
488
|
+
const maxTokens = this.getNodeParameter('maxTokens', i);
|
|
489
|
+
const messages = [];
|
|
490
|
+
if (systemPrompt) {
|
|
491
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
492
|
+
}
|
|
493
|
+
messages.push({ role: 'user', content: message });
|
|
494
|
+
const requestBody = {
|
|
495
|
+
model: model,
|
|
496
|
+
messages: messages,
|
|
497
|
+
temperature: temperature,
|
|
498
|
+
max_tokens: maxTokens,
|
|
499
|
+
stream: false,
|
|
500
|
+
};
|
|
501
|
+
const response = await makeApiRequest(this, '/chat/completions', requestBody);
|
|
502
|
+
result = {
|
|
503
|
+
response: response.choices[0].message.content,
|
|
504
|
+
model: model,
|
|
505
|
+
usage: response.usage,
|
|
506
|
+
finish_reason: response.choices[0].finish_reason,
|
|
507
|
+
timestamp: new Date().toISOString(),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
else if (operation === 'audioTranscription') {
|
|
511
|
+
const language = this.getNodeParameter('audioLanguage', i);
|
|
512
|
+
const audioDataUrl = await processAudioFile(this, i);
|
|
513
|
+
const transcriptionPrompt = language === 'auto'
|
|
514
|
+
? `Please transcribe this audio file to text. Detect the language automatically and provide the transcription.`
|
|
515
|
+
: `Please transcribe this audio file to text. The audio is in ${language} language.`;
|
|
516
|
+
const messages = [
|
|
517
|
+
{
|
|
518
|
+
role: 'system',
|
|
519
|
+
content: 'You are an expert at audio transcription. When given an audio file, provide an accurate transcription of the spoken content.'
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
role: 'user',
|
|
523
|
+
content: `${transcriptionPrompt}\n\nAudio file: ${audioDataUrl}`
|
|
524
|
+
}
|
|
525
|
+
];
|
|
526
|
+
const requestBody = {
|
|
527
|
+
model: model,
|
|
528
|
+
messages: messages,
|
|
529
|
+
temperature: 0.1,
|
|
530
|
+
max_tokens: 4000,
|
|
531
|
+
stream: false,
|
|
532
|
+
};
|
|
533
|
+
const response = await makeApiRequest(this, '/chat/completions', requestBody);
|
|
534
|
+
result = {
|
|
535
|
+
transcription: response.choices[0].message.content,
|
|
536
|
+
language: language,
|
|
537
|
+
model: model,
|
|
538
|
+
usage: response.usage,
|
|
539
|
+
timestamp: new Date().toISOString(),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
else if (operation === 'imageAnalysis') {
|
|
543
|
+
const message = this.getNodeParameter('message', i);
|
|
544
|
+
const systemPrompt = this.getNodeParameter('systemPrompt', i);
|
|
545
|
+
const temperature = this.getNodeParameter('temperature', i);
|
|
546
|
+
const maxTokens = this.getNodeParameter('maxTokens', i);
|
|
547
|
+
const imageBase64 = await processImageFile(this, i);
|
|
548
|
+
const messages = [];
|
|
549
|
+
if (systemPrompt) {
|
|
550
|
+
messages.push({ role: 'system', content: systemPrompt });
|
|
551
|
+
}
|
|
552
|
+
messages.push({
|
|
553
|
+
role: 'user',
|
|
554
|
+
content: [
|
|
555
|
+
{ type: 'text', text: message },
|
|
556
|
+
{ type: 'image_url', image_url: { url: imageBase64 } }
|
|
557
|
+
]
|
|
558
|
+
});
|
|
559
|
+
const requestBody = {
|
|
560
|
+
model: model,
|
|
561
|
+
messages: messages,
|
|
562
|
+
temperature: temperature,
|
|
563
|
+
max_tokens: maxTokens,
|
|
564
|
+
stream: false,
|
|
565
|
+
};
|
|
566
|
+
const response = await makeApiRequest(this, '/chat/completions', requestBody);
|
|
567
|
+
result = {
|
|
568
|
+
response: response.choices[0].message.content,
|
|
569
|
+
model: model,
|
|
570
|
+
usage: response.usage,
|
|
571
|
+
finish_reason: response.choices[0].finish_reason,
|
|
572
|
+
timestamp: new Date().toISOString(),
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
throw new Error(`Unknown operation: ${operation}`);
|
|
577
|
+
}
|
|
578
|
+
returnData.push({
|
|
579
|
+
json: result,
|
|
580
|
+
pairedItem: { item: i },
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
catch (error) {
|
|
584
|
+
if (this.continueOnFail()) {
|
|
585
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
586
|
+
returnData.push({
|
|
587
|
+
json: {
|
|
588
|
+
error: errorMessage,
|
|
589
|
+
operation: this.getNodeParameter('operation', i),
|
|
590
|
+
model: this.getNodeParameter('model', i),
|
|
591
|
+
},
|
|
592
|
+
pairedItem: { item: i },
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return [returnData];
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
exports.GitHubCopilotChatAPI = GitHubCopilotChatAPI;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="copilotGradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
+
<stop offset="0%" style="stop-color:#1f6feb;stop-opacity:1" />
|
|
5
|
+
<stop offset="100%" style="stop-color:#0969da;stop-opacity:1" />
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
|
|
9
|
+
<!-- GitHub Copilot inspired icon -->
|
|
10
|
+
<circle cx="12" cy="12" r="11" fill="url(#copilotGradient)" stroke="#ffffff" stroke-width="1"/>
|
|
11
|
+
|
|
12
|
+
<!-- Copilot face -->
|
|
13
|
+
<ellipse cx="12" cy="10" rx="8" ry="6" fill="#ffffff" opacity="0.9"/>
|
|
14
|
+
|
|
15
|
+
<!-- Eyes -->
|
|
16
|
+
<circle cx="9" cy="9" r="1.5" fill="#1f6feb"/>
|
|
17
|
+
<circle cx="15" cy="9" r="1.5" fill="#1f6feb"/>
|
|
18
|
+
|
|
19
|
+
<!-- Light reflection in eyes -->
|
|
20
|
+
<circle cx="9.5" cy="8.5" r="0.5" fill="#ffffff"/>
|
|
21
|
+
<circle cx="15.5" cy="8.5" r="0.5" fill="#ffffff"/>
|
|
22
|
+
|
|
23
|
+
<!-- Mouth/Interface line -->
|
|
24
|
+
<path d="M8 12 L16 12" stroke="#1f6feb" stroke-width="1.5" stroke-linecap="round"/>
|
|
25
|
+
|
|
26
|
+
<!-- Code brackets -->
|
|
27
|
+
<path d="M6 15 L8 17 L6 19" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
28
|
+
<path d="M18 15 L16 17 L18 19" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
29
|
+
|
|
30
|
+
<!-- AI indicator dots -->
|
|
31
|
+
<circle cx="10" cy="17" r="0.5" fill="#ffffff" opacity="0.8"/>
|
|
32
|
+
<circle cx="12" cy="17" r="0.5" fill="#ffffff" opacity="0.6"/>
|
|
33
|
+
<circle cx="14" cy="17" r="0.5" fill="#ffffff" opacity="0.4"/>
|
|
34
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-github-copilot",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "n8n community node for GitHub Copilot CLI integration",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "n8n community node for GitHub Copilot with CLI integration and official Chat API access to GPT-5, Claude, Gemini and more using your existing Copilot credits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/sufficit/n8n-nodes-github-copilot",
|
|
7
7
|
"author": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"dist/credentials/GitHubApiManual.credentials.js"
|
|
32
32
|
],
|
|
33
33
|
"nodes": [
|
|
34
|
-
"dist/nodes/GitHubCopilot/GitHubCopilot.node.js"
|
|
34
|
+
"dist/nodes/GitHubCopilot/GitHubCopilot.node.js",
|
|
35
|
+
"dist/nodes/GitHubCopilotChatAPI/GitHubCopilotChatAPI.node.js"
|
|
35
36
|
]
|
|
36
37
|
},
|
|
37
38
|
"keywords": [
|
|
@@ -39,6 +40,12 @@
|
|
|
39
40
|
"github",
|
|
40
41
|
"copilot",
|
|
41
42
|
"ai",
|
|
43
|
+
"gpt-5",
|
|
44
|
+
"claude",
|
|
45
|
+
"gemini",
|
|
46
|
+
"chat-completion",
|
|
47
|
+
"image-analysis",
|
|
48
|
+
"audio-transcription",
|
|
42
49
|
"code-generation",
|
|
43
50
|
"automation"
|
|
44
51
|
],
|