@@ -56,7 +56,15 @@ import {
5656 rewriteUrlForCodex ,
5757 shouldRefreshToken ,
5858 transformRequestForCodex ,
59+ handleRateLimitWithRotation ,
60+ getActiveAccountCredentials ,
61+ isRateLimitError ,
5962} from "./lib/request/fetch-helpers.js" ;
63+ import {
64+ isMultiAccountEnabled ,
65+ addAccount ,
66+ getCurrentAccount ,
67+ } from "./lib/auth/account-manager.js" ;
6068import type { UserConfig } from "./lib/types.js" ;
6169
6270/**
@@ -164,64 +172,108 @@ export const OpenAIAuthPlugin: Plugin = async ({ client }: PluginInput) => {
164172 input : Request | string | URL ,
165173 init ?: RequestInit ,
166174 ) : Promise < Response > {
167- // Step 1: Check and refresh token if needed
168- let currentAuth = await getAuth ( ) ;
169- if ( shouldRefreshToken ( currentAuth ) ) {
170- currentAuth = await refreshAndUpdateToken ( currentAuth , client ) ;
171- }
175+ const MAX_RETRY_ATTEMPTS = 5 ;
176+ let retryCount = 0 ;
177+ let currentAccountId = accountId ;
178+ let currentAccessToken = "" ;
172179
173- // Step 2: Extract and rewrite URL for Codex backend
174- const originalUrl = extractRequestUrl ( input ) ;
175- const url = rewriteUrlForCodex ( originalUrl ) ;
180+ while ( retryCount < MAX_RETRY_ATTEMPTS ) {
181+ let authToUse = await getAuth ( ) ;
176182
177- // Step 3: Transform request body with model-specific Codex instructions
178- // Instructions are fetched per model family (codex-max, codex, gpt-5.1)
179- // Capture original stream value before transformation
180- // generateText() sends no stream field, streamText() sends stream=true
181- const originalBody = init ?. body ? JSON . parse ( init . body as string ) : { } ;
182- const isStreaming = originalBody . stream === true ;
183+ if ( isMultiAccountEnabled ( ) ) {
184+ const multiAccountCreds = await getActiveAccountCredentials ( ) ;
185+ if ( multiAccountCreds ) {
186+ currentAccessToken = multiAccountCreds . accessToken ;
187+ currentAccountId = multiAccountCreds . accountId ;
188+ } else {
189+ if ( shouldRefreshToken ( authToUse ) ) {
190+ authToUse = await refreshAndUpdateToken ( authToUse , client ) ;
191+ }
192+ currentAccessToken = authToUse . type === "oauth" ? authToUse . access : "" ;
193+ currentAccountId = accountId ;
194+ }
195+ } else {
196+ if ( shouldRefreshToken ( authToUse ) ) {
197+ authToUse = await refreshAndUpdateToken ( authToUse , client ) ;
198+ }
199+ currentAccessToken = authToUse . type === "oauth" ? authToUse . access : "" ;
200+ currentAccountId = accountId ;
201+ }
183202
184- const transformation = await transformRequestForCodex (
185- init ,
186- url ,
187- userConfig ,
188- codexMode ,
189- ) ;
190- const requestInit = transformation ?. updatedInit ?? init ;
203+ const originalUrl = extractRequestUrl ( input ) ;
204+ const url = rewriteUrlForCodex ( originalUrl ) ;
191205
192- // Step 4: Create headers with OAuth and ChatGPT account info
193- const accessToken =
194- currentAuth . type === "oauth" ? currentAuth . access : "" ;
195- const headers = createCodexHeaders (
196- requestInit ,
197- accountId ,
198- accessToken ,
199- {
200- model : transformation ?. body . model ,
201- promptCacheKey : ( transformation ?. body as any ) ?. prompt_cache_key ,
202- } ,
203- ) ;
206+ const originalBody = init ?. body ? JSON . parse ( init . body as string ) : { } ;
207+ const isStreaming = originalBody . stream === true ;
208+
209+ const transformation = await transformRequestForCodex (
210+ init ,
211+ url ,
212+ userConfig ,
213+ codexMode ,
214+ ) ;
215+ const requestInit = transformation ?. updatedInit ?? init ;
204216
205- // Step 5: Make request to Codex API
206- const response = await fetch ( url , {
207- ...requestInit ,
208- headers,
209- } ) ;
217+ const headers = createCodexHeaders (
218+ requestInit ,
219+ currentAccountId ,
220+ currentAccessToken ,
221+ {
222+ model : transformation ?. body . model ,
223+ promptCacheKey : ( transformation ?. body as any ) ?. prompt_cache_key ,
224+ } ,
225+ ) ;
210226
211- // Step 6: Log response
212- logRequest ( LOG_STAGES . RESPONSE , {
213- status : response . status ,
214- ok : response . ok ,
215- statusText : response . statusText ,
216- headers : Object . fromEntries ( response . headers . entries ( ) ) ,
217- } ) ;
227+ const response = await fetch ( url , {
228+ ...requestInit ,
229+ headers,
230+ } ) ;
218231
219- // Step 7: Handle error or success response
220- if ( ! response . ok ) {
221- return await handleErrorResponse ( response ) ;
232+ logRequest ( LOG_STAGES . RESPONSE , {
233+ status : response . status ,
234+ ok : response . ok ,
235+ statusText : response . statusText ,
236+ headers : Object . fromEntries ( response . headers . entries ( ) ) ,
237+ retryCount,
238+ accountId : currentAccountId ,
239+ } ) ;
240+
241+ if ( ! response . ok ) {
242+ const errorResponse = await handleErrorResponse ( response ) ;
243+
244+ if ( isRateLimitError ( errorResponse ) && isMultiAccountEnabled ( ) ) {
245+ const rotationResult = await handleRateLimitWithRotation (
246+ errorResponse ,
247+ currentAccountId ,
248+ ) ;
249+
250+ if ( rotationResult . shouldRetry && rotationResult . newAccount ) {
251+ retryCount ++ ;
252+ console . log (
253+ `[${ PLUGIN_NAME } ] Retrying with account ${ rotationResult . newAccount . id } (attempt ${ retryCount } /${ MAX_RETRY_ATTEMPTS } )` ,
254+ ) ;
255+ continue ;
256+ }
257+ }
258+
259+ return errorResponse ;
260+ }
261+
262+ return await handleSuccessResponse ( response , isStreaming ) ;
222263 }
223264
224- return await handleSuccessResponse ( response , isStreaming ) ;
265+ return new Response (
266+ JSON . stringify ( {
267+ error : {
268+ message : ERROR_MESSAGES . ALL_ACCOUNTS_RATE_LIMITED ,
269+ type : "rate_limit_exceeded" ,
270+ } ,
271+ } ) ,
272+ {
273+ status : 429 ,
274+ headers : { "content-type" : "application/json" } ,
275+ } ,
276+ ) ;
225277 } ,
226278 } ;
227279 } ,
0 commit comments