11import type { Hooks , PluginInput } from "@opencode-ai/plugin"
2- import { Installation } from "@/installation"
3- import { setTimeout as sleep } from "node:timers/promises"
4- import path from "path"
5- import os from "os"
2+ import { authenticate , getToken } from "kiro-ai-provider"
63
7- const oidc = ( region : string ) => `https://oidc.${ region } .amazonaws.com`
84const BUILDER_ID_URL = "https://view.awsapps.com/start"
9- const SCOPES = [
10- "codewhisperer:completions" ,
11- "codewhisperer:analysis" ,
12- "codewhisperer:conversations" ,
13- "codewhisperer:transformations" ,
14- "codewhisperer:taskassist" ,
15- ]
16- const GRANT_TYPES = [ "urn:ietf:params:oauth:grant-type:device_code" , "refresh_token" ]
17- const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code"
18- const POLLING_MARGIN_MS = 3000
19- const TOKEN_PATH = path . join ( os . homedir ( ) , ".aws" , "sso" , "cache" , "kiro-auth-token.json" )
20- const CLIENT_PATH = path . join ( os . homedir ( ) , ".aws" , "sso" , "cache" , "kiro-client-registration.json" )
215const USER_AGENT = "aws-sdk-js/1.0.27 ua/2.1 os/darwin lang/js api/codewhispererstreaming#1.0.27 m/E Kiro-opencode"
226const USER_AGENT_SHORT = "aws-sdk-js/1.0.27 Kiro-opencode"
237
24- function read < T > ( filepath : string ) : Promise < T | undefined > {
25- const file = Bun . file ( filepath )
26- return file
27- . exists ( )
28- . then ( ( found ) => ( found ? file . text ( ) . then ( ( text ) => JSON . parse ( text ) as T ) : undefined ) )
29- . catch ( ( ) => undefined )
30- }
31-
32- function write ( filepath : string , data : unknown ) : Promise < void > {
33- return Bun . write ( filepath , JSON . stringify ( data , null , 2 ) )
34- . then ( ( ) => { } )
35- . catch ( ( ) => { } )
36- }
37-
38- function mkdir ( dir : string ) : Promise < void > {
39- return import ( "fs/promises" )
40- . then ( ( fs ) => fs . mkdir ( dir , { recursive : true } ) . then ( ( ) => { } ) )
41- . catch ( ( ) => { } )
42- }
43-
448export async function KiroAuthPlugin ( _input : PluginInput ) : Promise < Hooks > {
459 return {
4610 auth : {
@@ -51,20 +15,18 @@ export async function KiroAuthPlugin(_input: PluginInput): Promise<Hooks> {
5115
5216 return {
5317 async fetch ( request : RequestInfo | URL , init ?: RequestInit ) {
54- const token = await read < { accessToken : string } > ( TOKEN_PATH )
18+ const token = await getToken ( )
5519 if ( ! token ) return fetch ( request , init )
5620
57- const headers : Record < string , string > = {
58- ...( init ?. headers as Record < string , string > ) ,
59- Authorization : `Bearer ${ token . accessToken } ` ,
60- "User-Agent" : USER_AGENT ,
61- "x-amz-user-agent" : USER_AGENT_SHORT ,
62- "x-amzn-codewhisperer-optout" : "true" ,
63- }
64-
6521 return fetch ( request , {
6622 ...init ,
67- headers,
23+ headers : {
24+ ...( init ?. headers as Record < string , string > ) ,
25+ Authorization : `Bearer ${ token } ` ,
26+ "User-Agent" : USER_AGENT ,
27+ "x-amz-user-agent" : USER_AGENT_SHORT ,
28+ "x-amzn-codewhisperer-optout" : "true" ,
29+ } ,
6830 } )
6931 } ,
7032 }
@@ -106,138 +68,29 @@ export async function KiroAuthPlugin(_input: PluginInput): Promise<Hooks> {
10668 ? ( inputs . region || process . env . AWS_SSO_REGION || "us-east-1" )
10769 : "us-east-1"
10870
109- const registration = await fetch ( `${ oidc ( region ) } /client/register` , {
110- method : "POST" ,
111- headers : {
112- "Content-Type" : "application/json" ,
113- "User-Agent" : `opencode/${ Installation . VERSION } ` ,
114- } ,
115- body : JSON . stringify ( {
116- clientName : "opencode-kiro" ,
117- clientType : "public" ,
118- scopes : SCOPES ,
119- grantTypes : GRANT_TYPES ,
120- issuerUrl : url ,
121- } ) ,
122- } )
71+ const { promise : pending , resolve } = Promise . withResolvers < { url : string ; code : string } > ( )
12372
124- if ( ! registration . ok ) {
125- throw new Error ( "Failed to register OIDC client" )
126- }
127-
128- const client = ( await registration . json ( ) ) as {
129- clientId : string
130- clientSecret : string
131- clientIdIssuedAt : number
132- clientSecretExpiresAt : number
133- }
134-
135- const device = await fetch ( `${ oidc ( region ) } /device_authorization` , {
136- method : "POST" ,
137- headers : {
138- "Content-Type" : "application/json" ,
139- "User-Agent" : `opencode/${ Installation . VERSION } ` ,
140- } ,
141- body : JSON . stringify ( {
142- clientId : client . clientId ,
143- clientSecret : client . clientSecret ,
144- startUrl : url ,
145- } ) ,
73+ const auth = authenticate ( {
74+ startUrl : url ,
75+ region,
76+ onVerification : ( verify , code ) => resolve ( { url : verify , code } ) ,
14677 } )
14778
148- if ( ! device . ok ) {
149- throw new Error ( "Failed to start device authorization" )
150- }
151-
152- const auth = ( await device . json ( ) ) as {
153- verificationUri : string
154- verificationUriComplete : string
155- userCode : string
156- deviceCode : string
157- interval : number
158- expiresIn : number
159- }
79+ const verification = await pending
16080
16181 return {
162- url : auth . verificationUriComplete ,
163- instructions : `Enter code: ${ auth . userCode } ` ,
82+ url : verification . url ,
83+ instructions : verification . code ,
16484 method : "auto" as const ,
165- async callback ( ) {
166- const delay = { ms : auth . interval }
167-
168- while ( true ) {
169- const response = await fetch ( `${ oidc ( region ) } /token` , {
170- method : "POST" ,
171- headers : {
172- "Content-Type" : "application/json" ,
173- "User-Agent" : `opencode/${ Installation . VERSION } ` ,
174- } ,
175- body : JSON . stringify ( {
176- clientId : client . clientId ,
177- clientSecret : client . clientSecret ,
178- grantType : DEVICE_GRANT ,
179- deviceCode : auth . deviceCode ,
180- } ) ,
181- } )
182-
183- if ( response . ok ) {
184- const tokens = ( await response . json ( ) ) as {
185- accessToken : string
186- refreshToken : string
187- expiresIn : number
188- tokenType : string
189- }
190-
191- const expires = new Date ( Date . now ( ) + tokens . expiresIn * 1000 )
192-
193- await mkdir ( path . dirname ( TOKEN_PATH ) )
194-
195- await write ( TOKEN_PATH , {
196- accessToken : tokens . accessToken ,
197- refreshToken : tokens . refreshToken ,
198- expiresAt : expires . toISOString ( ) ,
199- region,
200- clientId : client . clientId ,
201- clientSecret : client . clientSecret ,
202- } )
203-
204- await write ( CLIENT_PATH , {
205- clientId : client . clientId ,
206- clientSecret : client . clientSecret ,
207- clientIdIssuedAt : client . clientIdIssuedAt ,
208- clientSecretExpiresAt : client . clientSecretExpiresAt ,
209- } )
210-
211- return {
212- type : "success" as const ,
213- refresh : tokens . refreshToken ,
214- access : tokens . accessToken ,
215- expires : expires . getTime ( ) ,
216- }
217- }
218-
219- const error = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
220- error ?: string
221- error_description ?: string
222- }
223-
224- if ( error . error === "authorization_pending" ) {
225- await sleep ( delay . ms * 1000 + POLLING_MARGIN_MS )
226- continue
227- }
228-
229- if ( error . error === "slow_down" ) {
230- delay . ms = delay . ms + 5
231- await sleep ( delay . ms * 1000 + POLLING_MARGIN_MS )
232- continue
233- }
234-
235- if ( error . error ) return { type : "failed" as const }
236-
237- await sleep ( delay . ms * 1000 + POLLING_MARGIN_MS )
238- continue
239- }
240- } ,
85+ callback : ( ) =>
86+ auth
87+ . then ( ( result ) => ( {
88+ type : "success" as const ,
89+ refresh : result . refreshToken ,
90+ access : result . accessToken ,
91+ expires : Date . now ( ) + 3600000 ,
92+ } ) )
93+ . catch ( ( ) => ( { type : "failed" as const } ) ) ,
24194 }
24295 } ,
24396 } ,
0 commit comments