Issue #3607044: Append trailing user turn (1.3.x native-SDK path)
Summary
Anthropic rejects a chat request whose last message is not from the user ("the conversation must end with a user message; no assistant prefill"). The AI Agents multi-agent handoff and Drupal Canvas AI can produce a conversation that ends with a trailing assistant turn, producing a generic "Something went wrong" downstream.
Adds a guard in AnthropicProvider::buildMessageCreateParams() after the typed message array is built: if the final message role is not user, append a minimal user turn carrying the text "Continue.". Role=system messages have already been lifted to the top-level system param by extractSystemPrompt() upstream, so only a trailing assistant is possible at this point.
What changed
src/Plugin/AiProvider/AnthropicProvider.php— 5-line guard added inbuildMessageCreateParams()right afterbuildMessageContent()returns.tests/src/Unit/Plugin/AiProvider/AnthropicProviderNativeChatTest.php— two new unit tests covering the trailing-assistant (appended) and trailing-user (unchanged) cases againstbuildMessageCreateParams(). Reuses the existingbuildProviderWithCapabilities()+invoke()reflection helpers; no new mock framework.
Relation to MR !30
MR !30 by @RajabNatshah correctly diagnosed this bug and fixes it on the 1.2.x compat-layer stack (override chat() and normalise the ChatInput). That approach doesn't port cleanly to 1.3.x because chat() was fully rewritten in the native-SDK path — parent::chat() no longer resolves to a usable Anthropic implementation.
This MR ports the fix onto the 1.3.x native-SDK path: the guard runs after the typed MessageParam[] array is assembled inside buildMessageCreateParams(), using the same "Continue." nudge RajabNatshah picked. MR !30 stays open — it's still the correct fix for anyone on the 1.2.x compat-layer stack.
How it was verified
Local drupalci-parity gates (runnable locally):
phpcs(Drupal + DrupalPractice): clean.phpunittargeted (--filter testBuildParamsAppendsUserWhenTrailingAssistant|testBuildParamsUnchangedWhenTrailingUser): 2 tests, 8 assertions, 12ms — green.phpunitfull module unit suite: 86 tests (2 new), 161 assertions, 55ms — green. Zero regressions.php -lon both edited files: no syntax errors.
Deferred to drupalci pipeline: phpstan (workspace env has an unrelated contrib with a broken autoloader), cspell (not installed locally), PHPUnit concurrent variant, opt-in variants.
AI-Generated: Yes
Used Claude Opus 4.8 to draft the 5-line dispatch guard on the 1.3.x path and the two unit tests, reusing the "Continue." nudge that @RajabNatshah picked for MR !30. The bug diagnosis and the prompt-text choice are RajabNatshah's original contributions — this MR ports his work onto the 1.3.x native-SDK stack. Dependencies, logic, security, and GPL compatibility verified. Full contributor responsibility assumed.
Closes #3607044 (once merged alongside MR !30 for 1.2.x, or on its own if the 1.2.x fix ships separately).