1- function createCopilotPlugin ( client , { providerId, isEnterprise } ) {
1+ /**
2+ * @type {import('@opencode-ai/plugin').Plugin }
3+ */
4+ export async function CopilotAuthPlugin ( { client } ) {
25 const CLIENT_ID = "Iv1.b507a08c87ecfe98" ;
36 const HEADERS = {
47 "User-Agent" : "GitHubCopilotChat/0.35.0" ,
@@ -23,7 +26,7 @@ function createCopilotPlugin(client, { providerId, isEnterprise }) {
2326
2427 return {
2528 auth : {
26- provider : providerId ,
29+ provider : "github-copilot" ,
2730 loader : async ( getAuth , provider ) => {
2831 let info = await getAuth ( ) ;
2932 if ( ! info || info . type !== "oauth" ) return { } ;
@@ -117,197 +120,135 @@ function createCopilotPlugin(client, { providerId, isEnterprise }) {
117120 } ,
118121 } ;
119122 } ,
120- methods : isEnterprise
121- ? [
123+ methods : [
124+ {
125+ type : "custom" ,
126+ label : "Login with GitHub Copilot" ,
127+ prompts : [
122128 {
123- type : "custom" ,
124- label : "Login with GitHub Enterprise" ,
125- prompts : [
129+ type : "select" ,
130+ key : "deploymentType" ,
131+ message : "Select GitHub deployment type" ,
132+ options : [
133+ {
134+ label : "GitHub.com" ,
135+ value : "github.com" ,
136+ hint : "Public" ,
137+ } ,
126138 {
127- key : "enterpriseUrl" ,
128- message : "Enter your GitHub Enterprise URL or domain" ,
129- placeholder : "github.company.com or https://github.company.com" ,
130- validate : ( value ) => {
131- if ( ! value ) return "URL or domain is required" ;
132- try {
133- const url = value . includes ( "://" )
134- ? new URL ( value )
135- : new URL ( `https://${ value } ` ) ;
136- if ( ! url . hostname )
137- return "Please enter a valid URL or domain" ;
138- return undefined ;
139- } catch {
140- return "Please enter a valid URL (e.g., github.company.com)" ;
141- }
142- } ,
139+ label : "GitHub Enterprise" ,
140+ value : "enterprise" ,
141+ hint : "Data residency or self-hosted" ,
143142 } ,
144143 ] ,
145- async authorize ( inputs ) {
146- const enterpriseUrl = inputs . enterpriseUrl ;
147- const domain = normalizeDomain ( enterpriseUrl ) ;
148- const urls = getUrls ( domain ) ;
149-
150- const deviceResponse = await fetch ( urls . DEVICE_CODE_URL , {
151- method : "POST" ,
152- headers : {
153- Accept : "application/json" ,
154- "Content-Type" : "application/json" ,
155- "User-Agent" : "GitHubCopilotChat/0.35.0" ,
156- } ,
157- body : JSON . stringify ( {
158- client_id : CLIENT_ID ,
159- scope : "read:user" ,
160- } ) ,
161- } ) ;
162-
163- if ( ! deviceResponse . ok ) {
164- return { type : "failed" } ;
144+ } ,
145+ {
146+ type : "text" ,
147+ key : "enterpriseUrl" ,
148+ message : "Enter your GitHub Enterprise URL or domain" ,
149+ placeholder : "company.ghe.com or https://company.ghe.com" ,
150+ condition : ( inputs ) => inputs . deploymentType === "enterprise" ,
151+ validate : ( value ) => {
152+ if ( ! value ) return "URL or domain is required" ;
153+ try {
154+ const url = value . includes ( "://" )
155+ ? new URL ( value )
156+ : new URL ( `https://${ value } ` ) ;
157+ if ( ! url . hostname )
158+ return "Please enter a valid URL or domain" ;
159+ return undefined ;
160+ } catch {
161+ return "Please enter a valid URL (e.g., company.ghe.com or https://company.ghe.com)" ;
165162 }
163+ } ,
164+ } ,
165+ ] ,
166+ async authorize ( inputs ) {
167+ const deploymentType = inputs . deploymentType ;
166168
167- const deviceData = await deviceResponse . json ( ) ;
169+ let domain = "github.com" ;
170+ let actualProvider = "github-copilot" ;
168171
169- // Display URL and code for user
170- console . log ( `Go to: ${ deviceData . verification_uri } ` ) ;
171- console . log ( `Enter code: ${ deviceData . user_code } ` ) ;
172+ if ( deploymentType === "enterprise" ) {
173+ const enterpriseUrl = inputs . enterpriseUrl ;
174+ domain = normalizeDomain ( enterpriseUrl ) ;
175+ actualProvider = "github-copilot-enterprise" ;
176+ }
172177
173- // Poll for authorization
174- while ( true ) {
175- await new Promise ( ( resolve ) =>
176- setTimeout ( resolve , ( deviceData . interval || 5 ) * 1000 ) ,
177- ) ;
178+ const urls = getUrls ( domain ) ;
178179
179- const response = await fetch ( urls . ACCESS_TOKEN_URL , {
180- method : "POST" ,
181- headers : {
182- Accept : "application/json" ,
183- "Content-Type" : "application/json" ,
184- "User-Agent" : "GitHubCopilotChat/0.35.0" ,
185- } ,
186- body : JSON . stringify ( {
187- client_id : CLIENT_ID ,
188- device_code : deviceData . device_code ,
189- grant_type :
190- "urn:ietf:params:oauth:grant-type:device_code" ,
191- } ) ,
192- } ) ;
180+ const deviceResponse = await fetch ( urls . DEVICE_CODE_URL , {
181+ method : "POST" ,
182+ headers : {
183+ Accept : "application/json" ,
184+ "Content-Type" : "application/json" ,
185+ "User-Agent" : "GitHubCopilotChat/0.35.0" ,
186+ } ,
187+ body : JSON . stringify ( {
188+ client_id : CLIENT_ID ,
189+ scope : "read:user" ,
190+ } ) ,
191+ } ) ;
193192
194- if ( ! response . ok ) return { type : "failed" } ;
193+ if ( ! deviceResponse . ok ) {
194+ return { type : "failed" } ;
195+ }
195196
196- const data = await response . json ( ) ;
197+ const deviceData = await deviceResponse . json ( ) ;
197198
198- if ( data . access_token ) {
199- return {
200- type : "success" ,
201- auth_type : "oauth" ,
202- refresh : data . access_token ,
203- access : "" ,
204- expires : 0 ,
205- enterpriseUrl : domain ,
206- } ;
207- }
199+ console . log ( `Go to: ${ deviceData . verification_uri } ` ) ;
200+ console . log ( `Enter code: ${ deviceData . user_code } ` ) ;
208201
209- if ( data . error === "authorization_pending" ) {
210- continue ;
211- }
202+ while ( true ) {
203+ await new Promise ( ( resolve ) =>
204+ setTimeout ( resolve , ( deviceData . interval || 5 ) * 1000 ) ,
205+ ) ;
212206
213- if ( data . error ) return { type : "failed" } ;
214- }
215- } ,
216- } ,
217- ]
218- : [
219- {
220- type : "oauth" ,
221- label : "Login with GitHub" ,
222- authorize : async ( ) => {
223- const urls = getUrls ( "github.com" ) ;
207+ const response = await fetch ( urls . ACCESS_TOKEN_URL , {
208+ method : "POST" ,
209+ headers : {
210+ Accept : "application/json" ,
211+ "Content-Type" : "application/json" ,
212+ "User-Agent" : "GitHubCopilotChat/0.35.0" ,
213+ } ,
214+ body : JSON . stringify ( {
215+ client_id : CLIENT_ID ,
216+ device_code : deviceData . device_code ,
217+ grant_type :
218+ "urn:ietf:params:oauth:grant-type:device_code" ,
219+ } ) ,
220+ } ) ;
224221
225- const deviceResponse = await fetch ( urls . DEVICE_CODE_URL , {
226- method : "POST" ,
227- headers : {
228- Accept : "application/json" ,
229- "Content-Type" : "application/json" ,
230- "User-Agent" : "GitHubCopilotChat/0.35.0" ,
231- } ,
232- body : JSON . stringify ( {
233- client_id : CLIENT_ID ,
234- scope : "read:user" ,
235- } ) ,
236- } ) ;
237- const deviceData = await deviceResponse . json ( ) ;
238- return {
239- url : deviceData . verification_uri ,
240- instructions : `Enter code: ${ deviceData . user_code } ` ,
241- method : "auto" ,
242- callback : async ( ) => {
243- while ( true ) {
244- const response = await fetch ( urls . ACCESS_TOKEN_URL , {
245- method : "POST" ,
246- headers : {
247- Accept : "application/json" ,
248- "Content-Type" : "application/json" ,
249- "User-Agent" : "GitHubCopilotChat/0.35.0" ,
250- } ,
251- body : JSON . stringify ( {
252- client_id : CLIENT_ID ,
253- device_code : deviceData . device_code ,
254- grant_type :
255- "urn:ietf:params:oauth:grant-type:device_code" ,
256- } ) ,
257- } ) ;
222+ if ( ! response . ok ) return { type : "failed" } ;
258223
259- if ( ! response . ok ) return { type : "failed" } ;
224+ const data = await response . json ( ) ;
260225
261- const data = await response . json ( ) ;
226+ if ( data . access_token ) {
227+ const result = {
228+ type : "success" ,
229+ auth_type : "oauth" ,
230+ refresh : data . access_token ,
231+ access : "" ,
232+ expires : 0 ,
233+ } ;
262234
263- if ( data . access_token ) {
264- return {
265- type : "success" ,
266- refresh : data . access_token ,
267- access : "" ,
268- expires : 0 ,
269- } ;
270- }
235+ if ( actualProvider === "github-copilot-enterprise" ) {
236+ result . provider = "github-copilot-enterprise" ;
237+ result . enterpriseUrl = domain ;
238+ }
271239
272- if ( data . error === "authorization_pending" ) {
273- await new Promise ( ( resolve ) =>
274- setTimeout ( resolve , deviceData . interval * 1000 ) ,
275- ) ;
276- continue ;
277- }
240+ return result ;
241+ }
278242
279- if ( data . error ) return { type : "failed" } ;
243+ if ( data . error === "authorization_pending" ) {
244+ continue ;
245+ }
280246
281- await new Promise ( ( resolve ) =>
282- setTimeout ( resolve , deviceData . interval * 1000 ) ,
283- ) ;
284- continue ;
285- }
286- } ,
287- } ;
288- } ,
289- } ,
290- ] ,
247+ if ( data . error ) return { type : "failed" } ;
248+ }
249+ } ,
250+ } ,
251+ ] ,
291252 } ,
292253 } ;
293254}
294-
295- /**
296- * @type {import('@opencode-ai/plugin').Plugin }
297- */
298- export async function CopilotAuthPlugin ( { client } ) {
299- return createCopilotPlugin ( client , {
300- providerId : "github-copilot" ,
301- isEnterprise : false ,
302- } ) ;
303- }
304-
305- /**
306- * @type {import('@opencode-ai/plugin').Plugin }
307- */
308- export async function CopilotEnterpriseAuthPlugin ( { client } ) {
309- return createCopilotPlugin ( client , {
310- providerId : "github-copilot-enterprise" ,
311- isEnterprise : true ,
312- } ) ;
313- }
0 commit comments