diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 3f65f7e35..380e5e4ca 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -1525,6 +1525,14 @@ func (al *AgentLoop) runLLMIteration( ) } + // Recover XML tool calls emitted as plain text by some providers. + if len(response.ToolCalls) == 0 { + if xmlCalls := providers.ExtractXMLToolCalls(response.Content); len(xmlCalls) > 0 { + response.ToolCalls = xmlCalls + } + } + response.Content = providers.StripXMLToolCalls(response.Content) + // Check if no tool calls - we're done if len(response.ToolCalls) == 0 { // Plan continuation: if unchecked steps remain, nudge the LLM to diff --git a/pkg/providers/tool_call_extract.go b/pkg/providers/tool_call_extract.go index a41d92882..9d48f9945 100644 --- a/pkg/providers/tool_call_extract.go +++ b/pkg/providers/tool_call_extract.go @@ -171,6 +171,11 @@ func findToolCallBlock(text string) (blockStart, blockEnd int, content string, f // value // // +// ExtractXMLToolCalls is the exported version for use by the agent loop. +func ExtractXMLToolCalls(text string) []ToolCall { + return extractXMLToolCalls(text) +} + func extractXMLToolCalls(text string) []ToolCall { var result []ToolCall remaining := text @@ -260,6 +265,11 @@ func extractXMLToolCalls(text string) []ToolCall { return result } +// StripXMLToolCalls is the exported version for use by the agent loop. +func StripXMLToolCalls(text string) string { + return stripXMLToolCalls(text) +} + // stripXMLToolCalls removes XML tool call blocks from response text. // Prevents raw XML tool calls from leaking to users. func stripXMLToolCalls(text string) string {