@@ -54,7 +54,7 @@ import { hasExecuteFunction } from './tool-types.js';
5454import { isStopConditionMet , stepCountIs } from './stop-conditions.js' ;
5555import {
5656 isOutputMessage ,
57- isFunctionCallOutputItem ,
57+ isFunctionCallItem ,
5858 isReasoningOutputItem ,
5959 isWebSearchCallOutputItem ,
6060 isFileSearchCallOutputItem ,
@@ -478,6 +478,35 @@ export class ModelResult<TTools extends readonly Tool[]> {
478478 const tool = this . options . tools ?. find ( ( t ) => t . function . name === toolCall . name ) ;
479479 if ( ! tool || ! hasExecuteFunction ( tool ) ) continue ;
480480
481+ // Check if arguments failed to parse (remained as string instead of object)
482+ // This happens when the model returns invalid JSON for tool call arguments
483+ // We use 'unknown' cast because the type system doesn't know arguments can be a string
484+ // when JSON parsing fails in stream-transformers.ts
485+ const args : unknown = toolCall . arguments ;
486+ if ( typeof args === 'string' ) {
487+ const rawArgs = args ;
488+ const errorMessage = `Failed to parse tool call arguments for "${ toolCall . name } ": The model provided invalid JSON. ` +
489+ `Raw arguments received: "${ rawArgs } ". ` +
490+ `Please provide valid JSON arguments for this tool call.` ;
491+
492+ // Emit error event if broadcaster exists
493+ if ( this . toolEventBroadcaster ) {
494+ this . toolEventBroadcaster . push ( {
495+ type : 'tool_result' as const ,
496+ toolCallId : toolCall . id ,
497+ result : { error : errorMessage } as InferToolOutputsUnion < TTools > ,
498+ } ) ;
499+ }
500+
501+ toolResults . push ( {
502+ type : 'function_call_output' as const ,
503+ id : `output_${ toolCall . id } ` ,
504+ callId : toolCall . id ,
505+ output : JSON . stringify ( { error : errorMessage } ) ,
506+ } ) ;
507+ continue ;
508+ }
509+
481510 // Track preliminary results for this specific tool call
482511 const preliminaryResultsForCall : InferToolEventsUnion < TTools > [ ] = [ ] ;
483512
@@ -531,7 +560,13 @@ export class ModelResult<TTools extends readonly Tool[]> {
531560 private async resolveAsyncFunctionsForTurn ( turnContext : TurnContext ) : Promise < void > {
532561 if ( hasAsyncFunctions ( this . options . request ) ) {
533562 const resolved = await resolveAsyncFunctions ( this . options . request , turnContext ) ;
534- this . resolvedRequest = { ...resolved , stream : false } ;
563+ // Preserve accumulated input from previous turns
564+ const preservedInput = this . resolvedRequest ?. input ;
565+ this . resolvedRequest = {
566+ ...resolved ,
567+ stream : false ,
568+ ...( preservedInput !== undefined && { input : preservedInput } ) ,
569+ } ;
535570 }
536571 }
537572
@@ -572,8 +607,17 @@ export class ModelResult<TTools extends readonly Tool[]> {
572607 currentResponse : models . OpenResponsesNonStreamingResponse ,
573608 toolResults : models . OpenResponsesFunctionCallOutput [ ]
574609 ) : Promise < models . OpenResponsesNonStreamingResponse > {
575- // Build new input with tool results
610+ // Build new input preserving original conversation + tool results
611+ const originalInput = this . resolvedRequest ?. input ;
612+ const normalizedOriginalInput : models . OpenResponsesInput1 [ ] =
613+ Array . isArray ( originalInput )
614+ ? originalInput
615+ : originalInput
616+ ? [ { role : 'user' , content : originalInput } ]
617+ : [ ] ;
618+
576619 const newInput : models . OpenResponsesInput = [
620+ ...normalizedOriginalInput ,
577621 ...( Array . isArray ( currentResponse . output )
578622 ? currentResponse . output
579623 : [ currentResponse . output ] ) ,
@@ -584,9 +628,14 @@ export class ModelResult<TTools extends readonly Tool[]> {
584628 throw new Error ( 'Request not initialized' ) ;
585629 }
586630
587- const newRequest : models . OpenResponsesRequest = {
631+ // Update resolvedRequest.input with accumulated conversation for next turn
632+ this . resolvedRequest = {
588633 ...this . resolvedRequest ,
589634 input : newInput ,
635+ } ;
636+
637+ const newRequest : models . OpenResponsesRequest = {
638+ ...this . resolvedRequest ,
590639 stream : false ,
591640 } ;
592641
@@ -1184,8 +1233,16 @@ export class ModelResult<TTools extends readonly Tool[]> {
11841233 // Execute tools if needed
11851234 await this . executeToolsIfNeeded ( ) ;
11861235
1187- // Yield function call outputs for each executed tool
1236+ // Yield function calls and outputs for each tool round
11881237 for ( const round of this . allToolExecutionRounds ) {
1238+ // Round 0's function_calls already yielded via buildItemsStream
1239+ if ( round . round > 0 ) {
1240+ for ( const item of round . response . output ) {
1241+ if ( isFunctionCallItem ( item ) ) {
1242+ yield item ;
1243+ }
1244+ }
1245+ }
11891246 for ( const toolResult of round . toolResults ) {
11901247 yield toolResult ;
11911248 }
@@ -1196,7 +1253,7 @@ export class ModelResult<TTools extends readonly Tool[]> {
11961253 for ( const item of this . finalResponse . output ) {
11971254 if (
11981255 isOutputMessage ( item ) ||
1199- isFunctionCallOutputItem ( item ) ||
1256+ isFunctionCallItem ( item ) ||
12001257 isReasoningOutputItem ( item ) ||
12011258 isWebSearchCallOutputItem ( item ) ||
12021259 isFileSearchCallOutputItem ( item ) ||
@@ -1216,11 +1273,12 @@ export class ModelResult<TTools extends readonly Tool[]> {
12161273 *
12171274 * Stream incremental message updates as content is added in responses format.
12181275 * Each iteration yields an updated version of the message with new content.
1219- * Also yields OpenResponsesFunctionCallOutput after tool execution completes.
1220- * Returns ResponsesOutputMessage or OpenResponsesFunctionCallOutput compatible with OpenAI Responses API format.
1276+ * Also yields function_call items and OpenResponsesFunctionCallOutput after tool execution completes.
1277+ * Returns ResponsesOutputMessage, ResponsesOutputItemFunctionCall, or OpenResponsesFunctionCallOutput
1278+ * compatible with OpenAI Responses API format.
12211279 */
12221280 getNewMessagesStream ( ) : AsyncIterableIterator <
1223- models . ResponsesOutputMessage | models . OpenResponsesFunctionCallOutput
1281+ models . ResponsesOutputMessage | models . OpenResponsesFunctionCallOutput | models . ResponsesOutputItemFunctionCall
12241282 > {
12251283 return async function * ( this : ModelResult < TTools > ) {
12261284 await this . initStream ( ) ;
@@ -1234,8 +1292,15 @@ export class ModelResult<TTools extends readonly Tool[]> {
12341292 // Execute tools if needed
12351293 await this . executeToolsIfNeeded ( ) ;
12361294
1237- // Yield function call outputs for each executed tool
1295+ // Yield function calls and their outputs for each executed tool
12381296 for ( const round of this . allToolExecutionRounds ) {
1297+ // First yield the function_call items from the response that triggered tool execution
1298+ for ( const item of round . response . output ) {
1299+ if ( isFunctionCallItem ( item ) ) {
1300+ yield item ;
1301+ }
1302+ }
1303+ // Then yield the function_call_output results
12391304 for ( const toolResult of round . toolResults ) {
12401305 yield toolResult ;
12411306 }
0 commit comments