Skip to content

refactor(anthropic): merge consecutive same-role messages in provider conversion layer#505

Merged
ahmedhesham6 merged 1 commit intomainfrom
refactor/merge-consecutive-messages-in-anthropic-provider
Feb 8, 2026
Merged

refactor(anthropic): merge consecutive same-role messages in provider conversion layer#505
ahmedhesham6 merged 1 commit intomainfrom
refactor/merge-consecutive-messages-in-anthropic-provider

Conversation

@ahmedhesham6
Copy link
Collaborator

Description

Move the fix for Anthropic's alternating-role requirement from a pre-API sanitization step in the CLI layer (sanitize_tool_results) into the Anthropic provider's conversion pipeline (build_messages_with_caching).

When multiple tool results are converted individually, each Role::Tool message becomes a separate role="user" Anthropic message, violating Anthropic's strict alternating user/assistant requirement. The new merge_consecutive_messages() function combines these into a single user message with all tool_result content blocks — fixing the problem at the exact layer where it originates.

Related Issues

N/A — proactive refactor to simplify the defense-in-depth strategy.

Changes Made

  • libs/ai/src/providers/anthropic/convert.rs: Add merge_consecutive_messages() as Phase 2 of build_messages_with_caching(). Helper functions content_to_blocks() and merge_content() handle content type normalization and merging. 10 new tests covering: consecutive user/assistant messages, tool result merging, mixed string/blocks content, cache control preservation, empty/single messages, and full conversation flows with multiple tool results.
  • libs/ai/src/providers/anthropic/types.rs: Add Default impl for AnthropicMessageContent to enable idiomatic std::mem::take.
  • cli/src/commands/agent/run/mode_interactive.rs: Remove sanitize_tool_results() (dedup + orphan removal) and its pre-API call site. This was a ChatMessage-level workaround now replaced by the provider-level merge. Retain has_pending_tool_calls() and get_unresolved_tool_call_ids() as defense-in-depth guards. Refactor remaining tests to use shared helpers (test_tool_call, assistant_with_tool_calls, tool_message).

Why this is safe

  • Role alternation: Handled by the new merge_consecutive_messages() in the Anthropic provider — the only provider that requires it.
  • Dedup tool results: Handled by TaskBoardContextManager::dedup_tool_results() (other context managers flatten to text, making duplication irrelevant).
  • Pending tool calls: Still guarded by has_pending_tool_calls() before every API call.
  • Orphaned tool_use blocks: Still guarded by get_unresolved_tool_call_ids() in the user message handler.

Testing

  • All tests pass (cargo test --workspace — 172 bin + all lib tests)
  • cargo clippy --all-targets — zero warnings
  • cargo fmt --check — clean
  • Tested on macOS

Breaking Changes

None — internal refactor only, no public API changes.

… conversion layer

Move the fix for Anthropic's alternating-role requirement from a pre-API
sanitization step in the CLI layer into the Anthropic provider's
conversion pipeline (build_messages_with_caching).

When multiple tool results are converted individually, each Role::Tool
message becomes a separate role="user" Anthropic message, violating
Anthropic's strict alternating user/assistant requirement. The new
merge_consecutive_messages() function combines these into a single user
message with all tool_result content blocks.

Changes:
- Add merge_consecutive_messages() with content_to_blocks() and
  merge_content() helpers in anthropic/convert.rs
- Add Default impl for AnthropicMessageContent to enable std::mem::take
- Remove sanitize_tool_results() from mode_interactive.rs (dedup/orphan
  removal now handled by TaskBoardContextManager and provider layer)
- Retain has_pending_tool_calls() and get_unresolved_tool_call_ids()
  guards as defense-in-depth
- Refactor remaining tests to use shared helpers (test_tool_call,
  assistant_with_tool_calls, tool_message)
- Add 10 tests covering merge scenarios: consecutive user/assistant
  messages, tool results, mixed string/blocks, cache control
  preservation, and full conversation flows
@ahmedhesham6 ahmedhesham6 merged commit cd4e141 into main Feb 8, 2026
1 check passed
@ahmedhesham6 ahmedhesham6 deleted the refactor/merge-consecutive-messages-in-anthropic-provider branch February 8, 2026 18:05
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.

1 participant