@@ -306,11 +306,33 @@ export const useStore = create<AppState & AppActions>((set, get) => ({
306306 { toolName : toolCall . toolName , args : toolCall . input } ,
307307 config ,
308308 ) ;
309- const output =
310- result === "" || result === null || result === undefined
311- ? "Tool executed successfully and produced no output."
312- : result ;
313- logger . info ( "Tool executed successfully." , { toolCall, output } ) ;
309+
310+ let finalOutput : object ;
311+
312+ if ( result === "" || result === null || result === undefined ) {
313+ finalOutput = {
314+ result : "Tool executed successfully and produced no output." ,
315+ } ;
316+ } else if ( typeof result === "string" ) {
317+ finalOutput = { result : result } ;
318+ } else if (
319+ typeof result === "object" &&
320+ Object . keys ( result ) . length === 0
321+ ) {
322+ // The AI SDK expects a non-empty object for the tool output.
323+ finalOutput = {
324+ result : "Tool executed successfully and produced an empty object." ,
325+ } ;
326+ } else {
327+ // Assuming result is a non-empty object that fits the expected type
328+ finalOutput = result ;
329+ }
330+
331+ logger . info ( "Tool executed successfully." , {
332+ toolCall,
333+ output : finalOutput ,
334+ } ) ;
335+
314336 toolResults . push ( {
315337 id : crypto . randomUUID ( ) ,
316338 role : "tool" ,
@@ -319,8 +341,11 @@ export const useStore = create<AppState & AppActions>((set, get) => ({
319341 type : "tool-result" ,
320342 toolCallId : toolCall . toolCallId ,
321343 toolName : toolCall . toolName ,
322- // eslint-disable-next-line @typescript-eslint/no-explicit-any
323- output : output as any ,
344+ // We use `as any` here because the exact type of the tool output
345+ // is not known at compile time and can vary between tools.
346+ // The logic above ensures that the output is always a non-empty
347+ // object, which satisfies the AI SDK's runtime validation.
348+ output : finalOutput as any ,
324349 } ,
325350 ] ,
326351 } ) ;
@@ -360,7 +385,18 @@ export const useStore = create<AppState & AppActions>((set, get) => ({
360385 logger . info ( "No tool calls, agent is now idle." ) ;
361386 }
362387 } catch ( error ) {
363- if ( error instanceof TransientError && retryCount < MAX_RETRIES ) {
388+ let typedError = error ;
389+ const errorMessage =
390+ typedError instanceof Error ? typedError . message : "An unknown error occurred." ;
391+
392+ // This is a workaround for a generic error thrown by the AI SDK when the LLM
393+ // returns an empty response (no text, no tool calls). We treat it as a
394+ // transient error and retry.
395+ if ( errorMessage . includes ( "No output generated" ) ) {
396+ typedError = new TransientError ( errorMessage ) ;
397+ }
398+
399+ if ( typedError instanceof TransientError && retryCount < MAX_RETRIES ) {
364400 logger . warn ( `Transient error caught, retrying... (${ retryCount + 1 } )` ) ;
365401 set ( { status : "idle" } ) ;
366402 const backoff = INITIAL_BACKOFF_MS * 2 ** retryCount ;
@@ -371,15 +407,15 @@ export const useStore = create<AppState & AppActions>((set, get) => ({
371407 return ;
372408 }
373409
374- const errorMessage =
375- error instanceof Error ? error . message : "An unknown error occurred." ;
410+ const finalErrorMessage =
411+ typedError instanceof Error ? typedError . message : "An unknown error occurred." ;
376412
377- if ( error instanceof FatalError ) {
378- logger . error ( `Fatal error: ${ errorMessage } ` ) ;
379- set ( { error : errorMessage , status : "error" } ) ;
413+ if ( typedError instanceof FatalError ) {
414+ logger . error ( `Fatal error: ${ finalErrorMessage } ` ) ;
415+ set ( { error : finalErrorMessage , status : "error" } ) ;
380416 } else {
381- logger . error ( `Unhandled error in agent logic: ${ errorMessage } ` ) ;
382- set ( { error : errorMessage , status : "error" } ) ;
417+ logger . error ( `Unhandled error in agent logic: ${ finalErrorMessage } ` ) ;
418+ set ( { error : finalErrorMessage , status : "error" } ) ;
383419 }
384420 }
385421 } ,
0 commit comments