@@ -72,109 +72,148 @@ export async function* runAgent(
7272 runner : InMemoryRunner ,
7373 sessionId ?: string
7474) : AsyncGenerator < AgentStreamChunk , void , unknown > {
75- const activeRunner = runner ;
7675 const sid = sessionId ?? crypto . randomUUID ( ) ;
7776
78- // Try to get existing session, or create a new one
79- let session = await activeRunner . sessionService . getSession ( {
77+ let session = await runner . sessionService . getSession ( {
8078 appName : APP_NAME ,
8179 userId : 'default_user' ,
8280 sessionId : sid ,
8381 } ) ;
8482
8583 if ( ! session ) {
86- session = await activeRunner . sessionService . createSession ( {
84+ session = await runner . sessionService . createSession ( {
8785 appName : APP_NAME ,
8886 userId : 'default_user' ,
8987 sessionId : sid ,
9088 } ) ;
9189 }
9290
93- // Create user message
9491 const userMessage = createUserContent ( input ) ;
9592
96- // Run the agent and stream responses
93+ const MAX_RETRIES = 5 ;
94+ const BASE_DELAY_MS = 1000 ;
95+ let attempt = 0 ;
96+ let gotFinalContent = false ;
97+ let lastErrorCode : string | undefined ;
98+ let lastErrorMessage : string | undefined ;
99+
100+ function retryReason ( ) : string {
101+ return lastErrorMessage ?? lastErrorCode ?? 'empty response' ;
102+ }
103+
97104 try {
98- let eventIndex = 0 ;
99- for await ( const event of activeRunner . runAsync ( {
100- userId : 'default_user' ,
101- sessionId : sid ,
102- newMessage : userMessage ,
103- } ) ) {
104- eventIndex ++ ;
105- agentLogger . debug ( `[Runner] ===== EVENT #${ eventIndex } =====` ) ;
106- agentLogger . debug (
107- `[Runner] Event ID: ${ event . id } , from ${ event . author } , parts: ${ event . content ?. parts ?. length ?? 0 } , role: ${ event . content ?. role } , isFinal: ${ isFinalResponse ( event ) } , transferToAgent: ${ event . actions ?. transferToAgent ?? 'none' } `
108- ) ;
109-
110- // Highlight if this event has a transfer action
111- if ( event . actions ?. transferToAgent ) {
112- agentLogger . debug (
113- `[Runner] *** TRANSFER DETECTED: ${ event . author } -> ${ event . actions . transferToAgent } ***`
105+ while ( attempt <= MAX_RETRIES && ! gotFinalContent ) {
106+ if ( attempt > 0 ) {
107+ const delayMs = BASE_DELAY_MS * Math . pow ( 2 , attempt - 1 ) ; // 1s, 2s, 4s, 8s, 16s
108+ const reason = retryReason ( ) ;
109+ agentLogger . info (
110+ `[Runner] Retrying in ${ delayMs } ms (attempt ${ attempt + 1 } /${ MAX_RETRIES + 1 } ): ${ reason } `
114111 ) ;
112+ yield {
113+ type : 'status' ,
114+ content : `Retrying (${ attempt } /${ MAX_RETRIES } ): ${ reason } ` ,
115+ } ;
116+ await new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) ) ;
115117 }
116118
117- // Log what type of parts this event has
118- const partTypes =
119- event . content ?. parts
120- ?. map ( ( p ) => {
121- if ( 'text' in p && p . text ) return 'text' ;
122- if ( 'functionCall' in p && p . functionCall ) return `functionCall:${ p . functionCall . name } ` ;
123- if ( 'functionResponse' in p && p . functionResponse )
124- return `functionResponse:${ ( p . functionResponse as { name ?: string } ) . name } ` ;
125- return `unknown(${ Object . keys ( p ) . join ( ',' ) } )` ;
126- } )
127- . join ( ', ' ) ?? 'no parts' ;
128- agentLogger . debug ( `[Runner] Event parts: ${ partTypes } ` ) ;
129-
130- // If this is a function response, log details
131- if ( event . content ?. parts ?. some ( ( p ) => 'functionResponse' in p ) ) {
132- agentLogger . debug ( '[Runner] Function response event detected!' ) ;
133- }
119+ const message =
120+ attempt === 0 ? userMessage : createUserContent ( 'Please continue with your response.' ) ;
121+
122+ let eventIndex = 0 ;
123+ for await ( const event of runner . runAsync ( {
124+ userId : 'default_user' ,
125+ sessionId : sid ,
126+ newMessage : message ,
127+ } ) ) {
128+ eventIndex ++ ;
129+ agentLogger . debug ( `[Runner] ===== EVENT #${ eventIndex } (attempt ${ attempt + 1 } ) =====` ) ;
130+ agentLogger . debug (
131+ `[Runner] Event ID: ${ event . id } , from ${ event . author } , parts: ${ event . content ?. parts ?. length ?? 0 } , role: ${ event . content ?. role } , isFinal: ${ isFinalResponse ( event ) } , transferToAgent: ${ event . actions ?. transferToAgent ?? 'none' } , errorCode: ${ ( event as any ) . errorCode ?? 'none' } , errorMessage: ${ ( event as any ) . errorMessage ?? 'none' } `
132+ ) ;
134133
135- // Extract text content from event
136- if ( event . content ?. parts ) {
137- for ( const part of event . content . parts ) {
138- if ( part . text ) {
139- yield { type : 'text' , content : part . text } ;
140- }
141- // Yield tool calls
142- if ( 'functionCall' in part && part . functionCall ?. name ) {
143- agentLogger . debug (
144- `[Runner] Tool call: ${ part . functionCall . name } , args: ${ JSON . stringify ( part . functionCall . args ) } `
145- ) ;
146- yield {
147- type : 'tool_call' ,
148- toolCall : {
149- function : {
150- name : part . functionCall . name ,
151- arguments : part . functionCall . args as Record < string , unknown > ,
134+ if ( event . actions ?. transferToAgent ) {
135+ agentLogger . debug (
136+ `[Runner] *** TRANSFER DETECTED: ${ event . author } -> ${ event . actions . transferToAgent } ***`
137+ ) ;
138+ }
139+
140+ const partTypes =
141+ event . content ?. parts
142+ ?. map ( ( p ) => {
143+ if ( 'text' in p && p . text ) return 'text' ;
144+ if ( 'functionCall' in p && p . functionCall )
145+ return `functionCall:${ p . functionCall . name } ` ;
146+ if ( 'functionResponse' in p && p . functionResponse )
147+ return `functionResponse:${ ( p . functionResponse as { name ?: string } ) . name } ` ;
148+ return `unknown(${ Object . keys ( p ) . join ( ',' ) } )` ;
149+ } )
150+ . join ( ', ' ) ?? 'no parts' ;
151+ agentLogger . debug ( `[Runner] Event parts: ${ partTypes } ` ) ;
152+
153+ if ( event . content ?. parts ?. some ( ( p ) => 'functionResponse' in p ) ) {
154+ agentLogger . debug ( '[Runner] Function response event detected!' ) ;
155+ }
156+
157+ if ( event . content ?. parts ) {
158+ for ( const part of event . content . parts ) {
159+ if ( part . text ) {
160+ yield { type : 'text' , content : part . text } ;
161+ }
162+ if ( 'functionCall' in part && part . functionCall ?. name ) {
163+ agentLogger . debug (
164+ `[Runner] Tool call: ${ part . functionCall . name } , args: ${ JSON . stringify ( part . functionCall . args ) } `
165+ ) ;
166+ yield {
167+ type : 'tool_call' ,
168+ toolCall : {
169+ function : {
170+ name : part . functionCall . name ,
171+ arguments : part . functionCall . args as Record < string , unknown > ,
172+ } ,
152173 } ,
153- } ,
154- } ;
174+ } ;
175+ }
155176 }
156177 }
157- }
158178
159- // Check for final response
160- // Note: ADK may yield empty "auth" events that appear final but aren't meaningful
161- // Skip these and continue to the next event
162- if ( isFinalResponse ( event ) ) {
163- const hasContent =
164- ( event . content ?. parts ?. length ?? 0 ) > 0 || event . actions ?. transferToAgent ;
165- if ( hasContent ) {
166- agentLogger . debug ( `[Runner] Final response received from ${ event . author } ` ) ;
167- yield { type : 'done' } ;
168- return ;
179+ // ADK may yield empty "auth" events that appear final but aren't meaningful.
180+ // Skip these and retry.
181+ if ( isFinalResponse ( event ) ) {
182+ const hasContent =
183+ ( event . content ?. parts ?. length ?? 0 ) > 0 || event . actions ?. transferToAgent ;
184+
185+ if ( hasContent ) {
186+ agentLogger . debug ( `[Runner] Final response received from ${ event . author } ` ) ;
187+ gotFinalContent = true ;
188+ yield { type : 'done' } ;
189+ return ;
190+ }
191+
192+ lastErrorCode = ( event as any ) . errorCode ?. toString ( ) ;
193+ lastErrorMessage = ( event as any ) . errorMessage ;
194+ agentLogger . warn (
195+ `[Runner] Empty final event from ${ event . author } ` +
196+ `(attempt ${ attempt + 1 } /${ MAX_RETRIES + 1 } ). ` +
197+ `errorCode: ${ lastErrorCode ?? 'none' } , errorMessage: ${ lastErrorMessage ?? 'none' } `
198+ ) ;
199+ break ;
169200 }
170- agentLogger . warn (
171- `[Runner] Empty final event from ${ event . author } — model may have failed silently (auth error? invalid model name?). Event: ${ JSON . stringify ( { id : event . id , role : event . content ?. role , parts : event . content ?. parts ?. length ?? 0 , actions : event . actions } ) } `
172- ) ;
201+ }
202+
203+ if ( ! gotFinalContent ) {
204+ attempt ++ ;
173205 }
174206 }
175207
176- agentLogger . debug ( '[Runner] Loop completed without final response' ) ;
177- yield { type : 'done' } ;
208+ if ( ! gotFinalContent ) {
209+ const reason = retryReason ( ) ;
210+ agentLogger . error ( `[Runner] All ${ MAX_RETRIES + 1 } attempts exhausted — ${ reason } ` ) ;
211+ yield {
212+ type : 'text' ,
213+ content : `The model failed after ${ MAX_RETRIES + 1 } attempts: ${ reason } ` ,
214+ } ;
215+ yield { type : 'done' } ;
216+ }
178217 } catch ( error ) {
179218 agentLogger . error ( { error } , '[Runner] Error during agent execution' ) ;
180219 yield {
0 commit comments