@@ -187,17 +187,22 @@ local function get_client(adapter)
187187 end
188188end
189189
190- --- Find a message in the table that has a specific tool call ID
190+ --- Find the index of the parent tool call message that contains a specific tool call ID
191191--- @param id string
192192--- @param messages CodeCompanion.Chat.Messages
193- --- @return table | nil
194- local function find_tool_call (id , messages )
195- for _ , msg in ipairs (messages ) do
196- if msg .tools and msg .tools .call_id and msg .tools .call_id == id then
197- return msg
193+ --- @return number | nil parent_index The index of the parent tool call message
194+ --- @return number | nil call_position The position of this tool call within tools.calls (1-based )
195+ local function find_tool_call_index (id , messages )
196+ for i , msg in ipairs (messages ) do
197+ if msg .tools and msg .tools .calls then
198+ for j , call in ipairs (msg .tools .calls ) do
199+ if call .id == id then
200+ return i , j
201+ end
202+ end
198203 end
199204 end
200- return nil
205+ return nil , nil
201206end
202207
203208--- Increment the cycle count in the chat buffer
@@ -1561,16 +1566,36 @@ function Chat:add_tool_output(tool, for_llm, for_user)
15611566 visible = true ,
15621567 })
15631568
1564- -- Ensure that tool output is merged if it has the same tool call ID
1565- local existing = find_tool_call (tool_call .id , self .messages )
1566- if existing then
1567- if existing .content ~= " " then
1568- existing .content = existing .content .. " \n\n " .. output .content
1569+ -- Find the parent tool call message and insert result right after it
1570+ -- call_position ensures correct ordering when multiple tool calls exist in same message
1571+ local parent_index , call_position = find_tool_call_index (tool_call .id , self .messages )
1572+ if not parent_index then
1573+ -- Fallback: append to end if parent not found (e.g., direct execute calls in tests)
1574+ log :warn (" Could not find parent tool call for ID: %s, appending to end" , tool_call .id )
1575+ table.insert (self .messages , output )
1576+ else
1577+ -- Calculate insert position, clamping to valid range to avoid sparse arrays
1578+ local insert_index = math.min (parent_index + call_position , # self .messages + 1 )
1579+
1580+ -- Search from after parent to insert position for existing tool result to merge with
1581+ local existing_msg = nil
1582+ for i = parent_index + 1 , insert_index do
1583+ local msg = self .messages [i ]
1584+ if msg and msg .tools and msg .tools .call_id == tool_call .id then
1585+ existing_msg = msg
1586+ break
1587+ end
1588+ end
1589+
1590+ if existing_msg then
1591+ if existing_msg .content ~= " " then
1592+ existing_msg .content = existing_msg .content .. " \n\n " .. output .content
1593+ else
1594+ existing_msg .content = output .content
1595+ end
15691596 else
1570- existing . content = output . content
1597+ table.insert ( self . messages , insert_index , output )
15711598 end
1572- else
1573- table.insert (self .messages , output )
15741599 end
15751600
15761601 -- Allow tools to pass in an empty string to not write any output to the buffer
0 commit comments