Skip to content

fix(assistant): execute text tool calls#32

Merged
omarluq merged 2 commits into
mainfrom
fix/provider-tool-calls
May 21, 2026
Merged

fix(assistant): execute text tool calls#32
omarluq merged 2 commits into
mainfrom
fix/provider-tool-calls

Conversation

@omarluq
Copy link
Copy Markdown
Owner

@omarluq omarluq commented May 21, 2026

Summary

  • parse XML-like text tool calls emitted by providers without native tool calling
  • execute parsed text tool calls through the normal tool loop
  • feed tool results back into OpenAI-compatible and Anthropic flows

Validation

  • mise exec -- go test ./...
  • mise exec -- task build
  • mise exec -- task ci

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Iterative tool execution: the assistant can perform multiple tool calls within a single conversation turn.
    • Unified tool handling with provider-aware fallbacks (text-to-tool and tool-to-text) and improved tool-result reporting.
  • Tests

    • Expanded test coverage for tool parsing, tool-result prompts, validation, and provider-specific execution and fallback scenarios.

Walkthrough

This PR enables iterative tool calling for both OpenAI Chat and Anthropic providers. Each provider now loops through tool execution, handles native tool calls, and falls back to parsing XML-like <tool_use> blocks from text responses when native tools unavailable. Support includes schema generation, text fallback parsing with field normalization, tool execution management, and comprehensive test coverage across both provider implementations.

Changes

Tool-calling loop and fallback

Layer / File(s) Summary
Tool model, constants, and error helpers
internal/assistant/client.go, internal/assistant/errors_extra.go
toolCall struct gains TextFallback boolean. New JSON keys for input_schema and arguments. Role/type constants expanded with tool and Anthropic tool_use/tool_result types. New emptyProviderResponseError helper for empty provider responses.
Tool schema generation and iteration limits
internal/assistant/tool_schema.go
maxToolIterations constant and toolIterationLimitError helper. Replaces tool-definition builder with OpenAI Chat Completions-compatible openAIChatTools(). Adds toolArgumentsFromJSON for JSON argument parsing. Bash and edit tool schemas updated to use constant JSON keys.
Text tool fallback parser and formatters
internal/assistant/text_tool_calls.go
New file: XML-like <tool_use> block extraction from text with tool name/field normalization, argument JSON encoding, and deterministic fallback call IDs. Includes helpers to detect text fallback usage and render tool-result prompt summaries for conversation context.
Tool execution and loop infrastructure
internal/assistant/tool_loop.go
executeToolCalls refactored to construct ToolCallEvent with explicit field assignments. New finishTextResult helper validates and trims text, returning empty-response error when blank.
OpenAI Chat tool-calling loop and messaging
internal/assistant/openai_chat.go, internal/assistant/openai_responses.go
completeOpenAIChat implements iterative loop: builds payload with tools, POSTs to provider, parses native tool calls or falls back to text parsing, executes tools, appends tool/result messages, enforces iteration limit. Refactors message building to output OpenAI tool-call and tool-result formats with tool_calls and tool_call_id fields. Updates argument parsing and role mapping to use JSON constants.
Anthropic tool-calling loop and HTTP refactoring
internal/assistant/anthropic.go
completeAnthropic implements iterative loop with similar flow: manages loop state, repeatedly advances, POSTs to provider, parses mixed content blocks into text and tool calls, validates/executes tools or falls back to text parsing, appends tool-result messages, enforces iteration limit. Splits HTTP and parsing into requestAnthropic and parseAnthropicResult. Updates anthropicPayload to accept message history and message type to []map[string]any.
Tool-calling tests and existing test updates
internal/assistant/anthropic_tools_test.go, internal/assistant/text_tool_calls_test.go, internal/assistant/provider_tool_calls_test.go, internal/assistant/anthropic_internal_test.go, internal/assistant/tool_loop_test.go
New test suites verify text tool parsing (XML extraction, field mapping, unknown tools), Anthropic tool parsing/result messages, and end-to-end OpenAI/Anthropic tool execution with HTTP mocks and request/response verification. Updates existing tests to reflect new function signatures and TextFallback field.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • omarluq/librecode#30: Tool-calling loop and tool-result message building directly depend on the runtime/client tool-callback infrastructure (CompletionRequest.OnToolCall, ToolCallEvent) introduced in that PR.

Poem

🐰 I parsed the tags and hopped with glee,

tools called, results returned to me,
OpenAI and Anthropic in a loop so neat,
fallback text made every call complete,
carrot-powered tests keep the flow upbeat.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(assistant): execute text tool calls' directly summarizes the main change: enabling execution of text tool calls from providers without native tool-calling support.
Description check ✅ Passed The description provides relevant context about parsing XML-like text tool calls, executing them through the tool loop, and feeding results back into provider flows, all of which align with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/provider-tool-calls

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 21, 2026

Codecov Report

❌ Patch coverage is 80.41475% with 85 lines in your changes missing coverage. Please review.
✅ Project coverage is 59.97%. Comparing base (d9eacf6) to head (3b4691b).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
internal/assistant/anthropic.go 73.46% 29 Missing and 10 partials ⚠️
internal/assistant/openai_chat.go 84.96% 12 Missing and 8 partials ⚠️
internal/assistant/text_tool_calls.go 89.52% 8 Missing and 3 partials ⚠️
internal/assistant/tool_schema.go 78.57% 4 Missing and 2 partials ⚠️
internal/assistant/openai_responses.go 28.57% 5 Missing ⚠️
internal/assistant/errors_extra.go 0.00% 2 Missing ⚠️
internal/assistant/tool_loop.go 83.33% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #32      +/-   ##
==========================================
+ Coverage   58.13%   59.97%   +1.83%     
==========================================
  Files         165      168       +3     
  Lines       15973    16532     +559     
==========================================
+ Hits         9286     9915     +629     
+ Misses       5719     5611     -108     
- Partials      968     1006      +38     
Flag Coverage Δ
unittests 59.97% <80.41%> (+1.83%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/assistant/openai_chat.go`:
- Around line 210-222: openAIChatToolMessages currently leaves callID empty when
len(events) > len(calls), producing invalid tool role messages; update the
function to fail fast by checking if index >= len(calls) and panic/return a
descriptive error (e.g., "mismatched counts: events vs calls") instead of using
an empty fallback, so that the invariant violation surfaces immediately;
reference openAIChatToolMessages and adjust callers
(appendOpenAIChatToolConversation / advanceOpenAIChatLoop) as needed to handle
the new behavior or propagate the error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 65b2d4d8-58c8-4a76-afce-346f6e7b1de6

📥 Commits

Reviewing files that changed from the base of the PR and between fc90076 and 597b6a9.

📒 Files selected for processing (13)
  • internal/assistant/anthropic.go
  • internal/assistant/anthropic_internal_test.go
  • internal/assistant/anthropic_tools_test.go
  • internal/assistant/client.go
  • internal/assistant/errors_extra.go
  • internal/assistant/openai_chat.go
  • internal/assistant/openai_responses.go
  • internal/assistant/provider_tool_calls_test.go
  • internal/assistant/text_tool_calls.go
  • internal/assistant/text_tool_calls_test.go
  • internal/assistant/tool_loop.go
  • internal/assistant/tool_loop_test.go
  • internal/assistant/tool_schema.go

Comment thread internal/assistant/openai_chat.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
internal/assistant/provider_tool_calls_test.go (1)

186-188: ⚡ Quick win

Assert successful tool execution, not just tool detection.

Line 186-188 currently verifies only event count/name. This can pass even when the read tool fails and returns an error payload. Please assert successful execution output in this fallback path too.

Proposed test assertion improvement
 	require.Len(t, result.ToolEvents, 1)
 	assert.Equal(t, jsonReadToolName, result.ToolEvents[0].Name)
+	assert.Empty(t, result.ToolEvents[0].Error)
+	assert.Contains(t, result.ToolEvents[0].Result, "librecode")
 	require.Len(t, requests, 2)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/assistant/provider_tool_calls_test.go` around lines 186 - 188, The
test currently only checks result.ToolEvents length/name (result.ToolEvents,
jsonReadToolName, requests) which can pass when the read tool failed; update the
assertion to verify successful execution by asserting the tool event's
output/response is the expected successful payload (e.g., inspect
result.ToolEvents[0].Output or Result field contains the expected JSON/body and
no error indicator) and/or assert the corresponding requests entry (requests[1])
contains the successful response content and status we expect; ensure you still
keep the existing name/count asserts but add assertions that the tool returned
the correct success value rather than an error payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@internal/assistant/provider_tool_calls_test.go`:
- Around line 186-188: The test currently only checks result.ToolEvents
length/name (result.ToolEvents, jsonReadToolName, requests) which can pass when
the read tool failed; update the assertion to verify successful execution by
asserting the tool event's output/response is the expected successful payload
(e.g., inspect result.ToolEvents[0].Output or Result field contains the expected
JSON/body and no error indicator) and/or assert the corresponding requests entry
(requests[1]) contains the successful response content and status we expect;
ensure you still keep the existing name/count asserts but add assertions that
the tool returned the correct success value rather than an error payload.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: abcf3acd-d8e2-493b-8e43-2b23d7e6813d

📥 Commits

Reviewing files that changed from the base of the PR and between 597b6a9 and 3b4691b.

📒 Files selected for processing (8)
  • internal/assistant/anthropic.go
  • internal/assistant/anthropic_tools_test.go
  • internal/assistant/client.go
  • internal/assistant/openai_chat.go
  • internal/assistant/provider_tool_calls_test.go
  • internal/assistant/text_tool_calls.go
  • internal/assistant/text_tool_calls_test.go
  • internal/assistant/tool_loop_test.go

@omarluq omarluq merged commit 4dbbd5c into main May 21, 2026
12 checks passed
@omarluq omarluq deleted the fix/provider-tool-calls branch May 21, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants