@@ -76,6 +76,31 @@ func (i *responsesInterceptionBase) disableParallelToolCalls() {
7676 }
7777}
7878
79+ // handleInnerAgenticLoop orchestrates the inner agentic loop whereby injected tools
80+ // are invoked and their results are sent back to the model.
81+ // This is in contrast to regular tool calls which will be handled by the client
82+ // in its own agentic loop.
83+ func (i * responsesInterceptionBase ) handleInnerAgenticLoop (ctx context.Context , pending []responses.ResponseFunctionToolCall , response * responses.Response ) (bool , error ) {
84+ // Invoke any injected function calls.
85+ // The Responses API refers to what we call "tools" as "functions", so we keep the terminology
86+ // consistent in this package.
87+ // See https://platform.openai.com/docs/guides/function-calling
88+ results , err := i .handleInjectedToolCalls (ctx , pending , response )
89+ if err != nil {
90+ return false , fmt .Errorf ("failed to handle injected tool calls: %w" , err )
91+ }
92+
93+ // No tool results means no tools were invocable, so the flow is complete.
94+ if len (results ) == 0 {
95+ return false , nil
96+ }
97+
98+ // We'll use the tool results to issue another request to provide the model with.
99+ i .prepareRequestForAgenticLoop (ctx , response , results )
100+
101+ return true , nil
102+ }
103+
79104// handleInjectedToolCalls checks for function calls that we need to handle in our inner agentic loop.
80105// These are functions injected by the MCP proxy.
81106// Returns a list of tool call results.
@@ -99,19 +124,55 @@ func (i *responsesInterceptionBase) handleInjectedToolCalls(ctx context.Context,
99124
100125// prepareRequestForAgenticLoop prepares the request by setting the output of the given
101126// response as input to the next request, in order for the tool call result(s) to make function correctly.
102- func (i * responsesInterceptionBase ) prepareRequestForAgenticLoop (response * responses.Response ) {
127+ func (i * responsesInterceptionBase ) prepareRequestForAgenticLoop (ctx context.Context , response * responses.Response , toolResults []responses.ResponseInputItemUnionParam ) error {
128+ var err error
129+
103130 // Unset the string input; we need a list now.
104- i .req .Input .OfString = param.Opt [string ]{}
131+ if i .req .Input .OfString .Valid () {
132+ // convert old string value to list item
133+ i .req .Input .OfInputItemList = responses.ResponseInputParam {
134+ responses .ResponseInputItemParamOfMessage (
135+ i .req .Input .OfString .Value ,
136+ responses .EasyInputMessageRoleUser ,
137+ ),
138+ }
139+ if i .reqPayload , err = sjson .SetBytes (i .reqPayload , "input" , i .req .Input .OfInputItemList ); err != nil {
140+ i .logger .Error (ctx , "failure to marshal str output to new input in inner agentic loop" , slog .Error (err ))
141+ return fmt .Errorf ("failed to marshal input: %v" , err )
142+ }
143+
144+ // clear old value
145+ i .req .Input .OfString = param.Opt [string ]{}
146+ }
147+ inputSize := len (i .req .Input .OfInputItemList )
105148
106149 // OutputText is also available, but by definition the trigger for a function call is not a simple
107150 // text response from the model.
108151 for _ , output := range response .Output {
109- i .appendOutputToInput (i .req , output )
152+ if inputItem := i .convertOutputToInput (output ); inputItem != nil {
153+ i .req .Input .OfInputItemList = append (i .req .Input .OfInputItemList , * inputItem )
154+ }
155+ }
156+
157+ for _ , result := range toolResults {
158+ i .req .Input .OfInputItemList = append (i .req .Input .OfInputItemList , result )
110159 }
160+
161+ // Append newly added items to reqPayload field
162+ // New items are appended to limit Input re-marshaling.
163+ // See responsesInterceptionBase.requestOptions for more details about marshaling issues.
164+ for j := inputSize ; j < len (i .req .Input .OfInputItemList ); j ++ {
165+ if i .reqPayload , err = sjson .SetBytes (i .reqPayload , "input.-1" , i .req .Input .OfInputItemList [j ]); err != nil {
166+ i .logger .Error (ctx , "failure to marshal output to new input in inner agentic loop" , slog .Error (err ))
167+ return fmt .Errorf ("failed to marshal input: %v" , err )
168+ }
169+ }
170+
171+ return nil
111172}
112173
113174// getPendingInjectedToolCalls extracts function calls from the response that are managed by MCP proxy
114- func (i * responsesInterceptionBase ) getPendingInjectedToolCalls (ctx context. Context , response * responses.Response ) []responses.ResponseFunctionToolCall {
175+ func (i * responsesInterceptionBase ) getPendingInjectedToolCalls (response * responses.Response ) []responses.ResponseFunctionToolCall {
115176 var calls []responses.ResponseFunctionToolCall
116177
117178 for _ , item := range response .Output {
@@ -171,14 +232,14 @@ func (i *responsesInterceptionBase) invokeInjectedTool(ctx context.Context, resp
171232 return responses .ResponseInputItemParamOfFunctionCallOutput (fc .CallID , output )
172233}
173234
174- // appendOutputToInput converts a response output item to an input item and appends it to the
235+ // convertOutputToInput converts a response output item to an input item and appends it to the
175236// request's input list. This is used in agentic loops where we need to feed the model's output
176237// back as input for the next iteration (e.g., when processing tool call results).
177238//
178239// The conversion uses the openai-go library's ToParam() methods where available, which leverage
179240// param.Override() with raw JSON to preserve all fields. For types without ToParam(), we use
180241// the ResponseInputItemParamOf* helper functions.
181- func (i * responsesInterceptionBase ) appendOutputToInput ( req * ResponsesNewParamsWrapper , item responses.ResponseOutputItemUnion ) {
242+ func (i * responsesInterceptionBase ) convertOutputToInput ( item responses.ResponseOutputItemUnion ) * responses. ResponseInputItemUnionParam {
182243 var inputItem responses.ResponseInputItemUnionParam
183244
184245 switch item .Type {
@@ -228,8 +289,8 @@ func (i *responsesInterceptionBase) appendOutputToInput(req *ResponsesNewParamsW
228289 // - mcp_call, mcp_list_tools, mcp_approval_request: MCP-specific outputs
229290 default :
230291 i .logger .Debug (context .Background (), "skipping output item type for input" , slog .F ("type" , item .Type ))
231- return
292+ return nil
232293 }
233294
234- req . Input . OfInputItemList = append ( req . Input . OfInputItemList , inputItem )
295+ return & inputItem
235296}
0 commit comments