@@ -737,11 +737,9 @@ export function prepareAntigravityRequest(
737737 // Use stable session ID for signature caching across multi-turn conversations
738738 ( req as any ) . sessionId = signatureSessionKey ;
739739 stripInjectedDebugFromRequestPayload ( req as Record < string , unknown > ) ;
740+ sanitizeCrossModelPayloadInPlace ( req , { targetModel : effectiveModel } ) ;
740741
741742 if ( isClaude ) {
742- // Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
743- sanitizeCrossModelPayloadInPlace ( req , { targetModel : effectiveModel } ) ;
744-
745743 // Step 1: Strip corrupted/unsigned thinking blocks FIRST
746744 deepFilterThinkingBlocks ( req , signatureSessionKey , getCachedSignature , true ) ;
747745
@@ -1180,10 +1178,8 @@ export function prepareAntigravityRequest(
11801178 // For Claude models, filter out unsigned thinking blocks (required by Claude API)
11811179 // Attempts to restore signatures from cache for multi-turn conversations
11821180 // Handle both Gemini-style contents[] and Anthropic-style messages[] payloads.
1181+ sanitizeCrossModelPayloadInPlace ( requestPayload , { targetModel : effectiveModel } ) ;
11831182 if ( isClaude ) {
1184- // Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
1185- sanitizeCrossModelPayloadInPlace ( requestPayload , { targetModel : effectiveModel } ) ;
1186-
11871183 // Step 1: Strip corrupted/unsigned thinking blocks FIRST
11881184 deepFilterThinkingBlocks ( requestPayload , signatureSessionKey , getCachedSignature , true ) ;
11891185
@@ -1497,6 +1493,49 @@ export async function transformAntigravityResponse(
14971493 toolDebugPayload ?: string ,
14981494 debugLines ?: string [ ] ,
14991495) : Promise < Response > {
1496+ const extractBestErrorMessage = ( errorValue : unknown ) : string => {
1497+ if ( ! errorValue || typeof errorValue !== "object" ) {
1498+ return "" ;
1499+ }
1500+
1501+ const errorObj = errorValue as Record < string , unknown > ;
1502+ if ( typeof errorObj . message === "string" && errorObj . message . trim ( ) ) {
1503+ return errorObj . message ;
1504+ }
1505+ if ( typeof errorObj . status === "string" && errorObj . status . trim ( ) ) {
1506+ return errorObj . status ;
1507+ }
1508+
1509+ if ( Array . isArray ( errorObj . details ) && errorObj . details . length > 0 ) {
1510+ const firstDetail = errorObj . details [ 0 ] ;
1511+ if ( firstDetail && typeof firstDetail === "object" ) {
1512+ const detailRecord = firstDetail as Record < string , unknown > ;
1513+ if ( typeof detailRecord . reason === "string" && detailRecord . reason . trim ( ) ) {
1514+ return detailRecord . reason ;
1515+ }
1516+ }
1517+ }
1518+
1519+ if ( Array . isArray ( errorObj . errors ) && errorObj . errors . length > 0 ) {
1520+ const firstBatchError = errorObj . errors [ 0 ] ;
1521+ if ( firstBatchError && typeof firstBatchError === "object" ) {
1522+ const batchRecord = firstBatchError as Record < string , unknown > ;
1523+ if ( typeof batchRecord . message === "string" && batchRecord . message . trim ( ) ) {
1524+ return batchRecord . message ;
1525+ }
1526+ if ( typeof batchRecord . status === "string" && batchRecord . status . trim ( ) ) {
1527+ return batchRecord . status ;
1528+ }
1529+ }
1530+ }
1531+
1532+ try {
1533+ return JSON . stringify ( errorObj ) ;
1534+ } catch {
1535+ return "" ;
1536+ }
1537+ } ;
1538+
15001539 const contentType = response . headers . get ( "content-type" ) ?? "" ;
15011540 const isJsonResponse = contentType . includes ( "application/json" ) ;
15021541 const isEventStreamResponse = contentType . includes ( "text/event-stream" ) ;
@@ -1558,27 +1597,67 @@ export async function transformAntigravityResponse(
15581597 const text = await response . text ( ) ;
15591598
15601599 if ( ! response . ok ) {
1561- let errorBody ;
1600+ let errorBody : any ;
15621601 try {
15631602 errorBody = JSON . parse ( text ) ;
15641603 } catch {
15651604 errorBody = { error : { message : text } } ;
15661605 }
15671606
1607+ if ( ! errorBody || typeof errorBody !== "object" ) {
1608+ errorBody = { error : { message : String ( errorBody ?? text ) } } ;
1609+ }
1610+
1611+ if ( ! errorBody . error || typeof errorBody . error !== "object" ) {
1612+ errorBody . error = {
1613+ message : typeof errorBody . message === "string" ? errorBody . message : undefined ,
1614+ status : typeof errorBody . status === "string" ? errorBody . status : undefined ,
1615+ details : Array . isArray ( errorBody . details ) ? errorBody . details : undefined ,
1616+ errors : Array . isArray ( errorBody . errors ) ? errorBody . errors : undefined ,
1617+ } ;
1618+ }
1619+
1620+ // Extract retry headers from google.rpc.RetryInfo when present.
1621+ if ( Array . isArray ( errorBody ?. error ?. details ) ) {
1622+ const retryInfo = errorBody . error . details . find (
1623+ ( detail : any ) => detail ?. [ "@type" ] === "type.googleapis.com/google.rpc.RetryInfo" ,
1624+ ) ;
1625+
1626+ if ( retryInfo ?. retryDelay ) {
1627+ const match = retryInfo . retryDelay . match ( / ^ ( [ \d . ] + ) s $ / ) ;
1628+ if ( match && match [ 1 ] ) {
1629+ const retrySeconds = parseFloat ( match [ 1 ] ) ;
1630+ if ( ! isNaN ( retrySeconds ) && retrySeconds > 0 ) {
1631+ const retryAfterSec = Math . ceil ( retrySeconds ) . toString ( ) ;
1632+ const retryAfterMs = Math . ceil ( retrySeconds * 1000 ) . toString ( ) ;
1633+ headers . set ( "Retry-After" , retryAfterSec ) ;
1634+ headers . set ( "retry-after-ms" , retryAfterMs ) ;
1635+ }
1636+ }
1637+ }
1638+ }
1639+
15681640 // Inject Debug Info
15691641 if ( errorBody ?. error ) {
1642+ const extractedMessage = extractBestErrorMessage ( errorBody . error ) || "Unknown error" ;
15701643 const debugInfo = `\n\n[Debug Info]\nRequested Model: ${ requestedModel || "Unknown" } \nEffective Model: ${ effectiveModel || "Unknown" } \nProject: ${ projectId || "Unknown" } \nEndpoint: ${ endpoint || "Unknown" } \nStatus: ${ response . status } \nRequest ID: ${ headers . get ( "x-request-id" ) || "N/A" } ${ toolDebugMissing !== undefined ? `\nTool Debug Missing: ${ toolDebugMissing } ` : "" } ${ toolDebugSummary ? `\nTool Debug Summary: ${ toolDebugSummary } ` : "" } ${ toolDebugPayload ? `\nTool Debug Payload: ${ toolDebugPayload } ` : "" } ` ;
15711644 const injectedDebug = debugText ? `\n\n${ debugText } ` : "" ;
1572- errorBody . error . message = ( errorBody . error . message || "Unknown error" ) + debugInfo + injectedDebug ;
1573-
1574- // Check if this is a recoverable thinking error - throw to trigger retry
1575- const errorType = detectErrorType ( errorBody . error . message || "" ) ;
1645+ errorBody . error . message = extractedMessage + debugInfo + injectedDebug ;
1646+
1647+ // Signal recoverable thinking errors via headers so caller can retry without throwing.
1648+ const firstDetailReason =
1649+ Array . isArray ( errorBody ?. error ?. details ) &&
1650+ errorBody . error . details [ 0 ] &&
1651+ typeof errorBody . error . details [ 0 ] === "object"
1652+ ? ( ( errorBody . error . details [ 0 ] as Record < string , unknown > ) . reason as string | undefined )
1653+ : undefined ;
1654+ const errorType = detectErrorType (
1655+ [ extractedMessage , typeof firstDetailReason === "string" ? firstDetailReason : "" ]
1656+ . filter ( Boolean )
1657+ . join ( " " ) ,
1658+ ) ;
15761659 if ( errorType === "thinking_block_order" ) {
1577- const recoveryError = new Error ( "THINKING_RECOVERY_NEEDED" ) ;
1578- ( recoveryError as any ) . recoveryType = errorType ;
1579- ( recoveryError as any ) . originalError = errorBody ;
1580- ( recoveryError as any ) . debugInfo = debugInfo ;
1581- throw recoveryError ;
1660+ headers . set ( "x-antigravity-recovery-needed" , errorType ) ;
15821661 }
15831662
15841663 // Detect context length / prompt too long errors - signal to caller for toast
@@ -1607,25 +1686,6 @@ export async function transformAntigravityResponse(
16071686 headers
16081687 } ) ;
16091688 }
1610-
1611- if ( errorBody ?. error ?. details && Array . isArray ( errorBody . error . details ) ) {
1612- const retryInfo = errorBody . error . details . find (
1613- ( detail : any ) => detail [ '@type' ] === 'type.googleapis.com/google.rpc.RetryInfo'
1614- ) ;
1615-
1616- if ( retryInfo ?. retryDelay ) {
1617- const match = retryInfo . retryDelay . match ( / ^ ( [ \d . ] + ) s $ / ) ;
1618- if ( match && match [ 1 ] ) {
1619- const retrySeconds = parseFloat ( match [ 1 ] ) ;
1620- if ( ! isNaN ( retrySeconds ) && retrySeconds > 0 ) {
1621- const retryAfterSec = Math . ceil ( retrySeconds ) . toString ( ) ;
1622- const retryAfterMs = Math . ceil ( retrySeconds * 1000 ) . toString ( ) ;
1623- headers . set ( 'Retry-After' , retryAfterSec ) ;
1624- headers . set ( 'retry-after-ms' , retryAfterMs ) ;
1625- }
1626- }
1627- }
1628- }
16291689 }
16301690
16311691 const init = {
0 commit comments