@@ -110,9 +110,11 @@ interface AttemptFetchOptions {
110110 thoughtSignature ?: string
111111}
112112
113+ type AttemptFetchResult = Response | null | "pass-through" | "needs-refresh"
114+
113115async function attemptFetch (
114116 options : AttemptFetchOptions
115- ) : Promise < Response | null | "pass-through" > {
117+ ) : Promise < AttemptFetchResult > {
116118 const { endpoint, url, init, accessToken, projectId, sessionId, modelName, thoughtSignature } =
117119 options
118120 debugLog ( `Trying endpoint: ${ endpoint } ` )
@@ -183,6 +185,11 @@ async function attemptFetch(
183185 `[RESP] status=${ response . status } content-type=${ response . headers . get ( "content-type" ) ?? "" } url=${ response . url } `
184186 )
185187
188+ if ( response . status === 401 ) {
189+ debugLog ( `[401] Unauthorized response detected, signaling token refresh needed` )
190+ return "needs-refresh"
191+ }
192+
186193 if ( response . status === 403 ) {
187194 try {
188195 const text = await response . clone ( ) . text ( )
@@ -448,59 +455,135 @@ export function createAntigravityFetch(
448455 const thoughtSignature = getThoughtSignature ( fetchInstanceId )
449456 debugLog ( `[TSIG][GET] sessionId=${ sessionId } , signature=${ thoughtSignature ? thoughtSignature . substring ( 0 , 20 ) + "..." : "none" } ` )
450457
451- for ( let i = 0 ; i < maxEndpoints ; i ++ ) {
452- const endpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS [ i ]
453-
454- const response = await attemptFetch ( {
455- endpoint,
456- url,
457- init,
458- accessToken : cachedTokens . access_token ,
459- projectId,
460- sessionId,
461- modelName,
462- thoughtSignature,
463- } )
458+ let hasRefreshedFor401 = false
459+
460+ const executeWithEndpoints = async ( ) : Promise < Response > => {
461+ for ( let i = 0 ; i < maxEndpoints ; i ++ ) {
462+ const endpoint = ANTIGRAVITY_ENDPOINT_FALLBACKS [ i ]
463+
464+ const response = await attemptFetch ( {
465+ endpoint,
466+ url,
467+ init,
468+ accessToken : cachedTokens ! . access_token ,
469+ projectId,
470+ sessionId,
471+ modelName,
472+ thoughtSignature,
473+ } )
464474
465- if ( response === "pass-through" ) {
466- debugLog ( "Non-string body detected, passing through with auth headers" )
467- const headersWithAuth = {
468- ...init . headers ,
469- Authorization : `Bearer ${ cachedTokens . access_token } ` ,
475+ if ( response === "pass-through" ) {
476+ debugLog ( "Non-string body detected, passing through with auth headers" )
477+ const headersWithAuth = {
478+ ...init . headers ,
479+ Authorization : `Bearer ${ cachedTokens ! . access_token } ` ,
480+ }
481+ return fetch ( url , { ...init , headers : headersWithAuth } )
470482 }
471- return fetch ( url , { ...init , headers : headersWithAuth } )
472- }
473483
474- if ( response ) {
475- debugLog ( `Success with endpoint: ${ endpoint } ` )
476- const transformedResponse = await transformResponseWithThinking (
477- response ,
478- modelName || "" ,
479- fetchInstanceId
480- )
481- return transformedResponse
484+ if ( response === "needs-refresh" ) {
485+ if ( hasRefreshedFor401 ) {
486+ debugLog ( "[401] Already refreshed once, returning unauthorized error" )
487+ return new Response (
488+ JSON . stringify ( {
489+ error : {
490+ message : "Authentication failed after token refresh" ,
491+ type : "unauthorized" ,
492+ code : "token_refresh_failed" ,
493+ } ,
494+ } ) ,
495+ {
496+ status : 401 ,
497+ statusText : "Unauthorized" ,
498+ headers : { "Content-Type" : "application/json" } ,
499+ }
500+ )
501+ }
502+
503+ debugLog ( "[401] Refreshing token and retrying..." )
504+ hasRefreshedFor401 = true
505+
506+ try {
507+ const newTokens = await refreshAccessToken (
508+ refreshParts . refreshToken ,
509+ clientId ,
510+ clientSecret
511+ )
512+
513+ cachedTokens = {
514+ type : "antigravity" ,
515+ access_token : newTokens . access_token ,
516+ refresh_token : newTokens . refresh_token ,
517+ expires_in : newTokens . expires_in ,
518+ timestamp : Date . now ( ) ,
519+ }
520+
521+ clearProjectContextCache ( )
522+
523+ const formattedRefresh = formatTokenForStorage (
524+ newTokens . refresh_token ,
525+ refreshParts . projectId || "" ,
526+ refreshParts . managedProjectId
527+ )
528+
529+ await client . set ( providerId , {
530+ access : newTokens . access_token ,
531+ refresh : formattedRefresh ,
532+ expires : Date . now ( ) + newTokens . expires_in * 1000 ,
533+ } )
534+
535+ debugLog ( "[401] Token refreshed, retrying request..." )
536+ return executeWithEndpoints ( )
537+ } catch ( refreshError ) {
538+ debugLog ( `[401] Token refresh failed: ${ refreshError instanceof Error ? refreshError . message : "Unknown error" } ` )
539+ return new Response (
540+ JSON . stringify ( {
541+ error : {
542+ message : `Token refresh failed: ${ refreshError instanceof Error ? refreshError . message : "Unknown error" } ` ,
543+ type : "unauthorized" ,
544+ code : "token_refresh_failed" ,
545+ } ,
546+ } ) ,
547+ {
548+ status : 401 ,
549+ statusText : "Unauthorized" ,
550+ headers : { "Content-Type" : "application/json" } ,
551+ }
552+ )
553+ }
554+ }
555+
556+ if ( response ) {
557+ debugLog ( `Success with endpoint: ${ endpoint } ` )
558+ const transformedResponse = await transformResponseWithThinking (
559+ response ,
560+ modelName || "" ,
561+ fetchInstanceId
562+ )
563+ return transformedResponse
564+ }
482565 }
566+
567+ const errorMessage = `All Antigravity endpoints failed after ${ maxEndpoints } attempts`
568+ debugLog ( errorMessage )
569+
570+ return new Response (
571+ JSON . stringify ( {
572+ error : {
573+ message : errorMessage ,
574+ type : "endpoint_failure" ,
575+ code : "all_endpoints_failed" ,
576+ } ,
577+ } ) ,
578+ {
579+ status : 503 ,
580+ statusText : "Service Unavailable" ,
581+ headers : { "Content-Type" : "application/json" } ,
582+ }
583+ )
483584 }
484585
485- // All endpoints failed
486- const errorMessage = `All Antigravity endpoints failed after ${ maxEndpoints } attempts`
487- debugLog ( errorMessage )
488-
489- // Return error response
490- return new Response (
491- JSON . stringify ( {
492- error : {
493- message : errorMessage ,
494- type : "endpoint_failure" ,
495- code : "all_endpoints_failed" ,
496- } ,
497- } ) ,
498- {
499- status : 503 ,
500- statusText : "Service Unavailable" ,
501- headers : { "Content-Type" : "application/json" } ,
502- }
503- )
586+ return executeWithEndpoints ( )
504587 }
505588}
506589
0 commit comments