InputLengthLimit guardrail does not consistently scan user messages
## Summary Follow-up to #3579088. The `InputLengthLimit` guardrail (added in 1.4.x) has the same message-selection defect that #3579088 fixed for `RegexpGuardrail` and `RestrictToTopic`: it does not consistently scan **user** messages across a conversation. The fix in [!1467](https://git.drupalcode.org/project/ai/-/merge_requests/1467) centralized this into a reusable `UserMessageSelectionTrait` (a role-filtered `selectUserMessages()` helper plus a shared "Scan all user messages" checkbox via `buildScanAllUserMessagesElement()`). `InputLengthLimit` was not part of that MR because it landed separately in 1.4.x, so it still uses its own bespoke logic. ## Problem/Motivation `InputLengthLimit::processInput()` selects the text to measure in two ways, both of which ignore message roles: - When **"Check total conversation length"** (`check_all_messages`) is enabled, it concatenates **every** `ChatMessage` — including `assistant`, `system`, and `tool` messages — not just `user` messages. The configured limit is therefore applied to text the user never sent. - When it is disabled, it calls `end($messages)` with no role check. If the most recent message is a `tool` result (tool-use follow-up turn) or replayed history, the actual user prompt is silently swapped out of what gets measured. This is the exact pattern `UserMessageSelectionTrait` was introduced to fix. `InputLengthLimit` should use it too so all guardrail plugins behave consistently. ## Steps to reproduce 1. Configure the `input_length_limit` guardrail with a character/token limit. 2. Run a multi-turn chat where the final message in `ChatInput` is a `role=tool` result (tool-use follow-up), or enable "Check total conversation length". 3. Observe that the length measured includes non-user messages (or the wrong message), rather than the user's input. ## Expected result `InputLengthLimit` measures only `role=user` messages, selecting either the most recent user message (default) or all user messages when "Scan all user messages" is enabled — consistent with `RegexpGuardrail` and `RestrictToTopic`. ## Actual result It measures `end($messages)` (any role) or concatenates all messages of any role, so non-user content is counted and tool-result/replayed turns can mask the real user input. ## Proposed resolution - Add `use UserMessageSelectionTrait;` to `InputLengthLimit`. - Replace the bespoke `check_all_messages` / `end($messages)` logic in `processInput()` with `selectUserMessages($input, $scan_all)`, looping over the returned user messages to build the text to measure. - Replace the `check_all_messages` form element with `buildScanAllUserMessagesElement()` and rename the config key to `scan_all_user_messages` for consistency with the sibling plugins. - Add a `post_update` hook to migrate any existing `check_all_messages` configuration to `scan_all_user_messages`, and update `config/schema/ai.schema.yml`. - Add unit test coverage mirroring `RegexpGuardrailTest` / `RestrictToTopicTest` (role filtering, scan-all vs. last-user-message, tool-result-last conversations). ## Remaining tasks - [ ] Apply `UserMessageSelectionTrait` to `InputLengthLimit`. - [ ] Add schema + `post_update` migration for the renamed config key. - [ ] Add unit tests for multi-turn / tool-result conversations. ## Environment - Module version: 1.4.x-dev <!-- This issue description was significantly AI-generated. -->
issue