fix: #3622011 Reject unvalidated tool input in AiFunctionCall plugins instead of crashing

Summary

A sweep for the same class of defect already fixed elsewhere in this module

  • unvalidated AI-agent tool input reaching Drupal Views internals and crashing, instead of being rejected with a readable tool error - found and fixed four separate, independently reproduced cases:

  • create_view_handler: a nonexistent table/field pair was accepted and saved as a broken handler, fataling later when the view renders.

  • create_view: an entity type with no Views base table (any config entity, e.g. user_role) crashed with an uncaught InvalidArgumentException deep in ViewExecutable::mergeDefaults().

  • create_view_display: display_id: 'default' silently overwrote and wiped the existing default display's entire configuration - 'default' is a real plugin ID, so it passed plugin-ID validation, but addDisplay('default') always maps back to the ID 'default'. Worse than a crash: silent, irreversible data loss.

  • update_view_plugins: a structurally malformed value for an array-shaped option (e.g. a bare number for pager) crashed with an uncaught TypeError inside a legacy ViewsConfigUpdater hook during $view->save(); unlike its sibling update_view_handler_option, this tool had no save-path guard at all.

Fixing the first one also exposed a pre-existing bug in ViewsAgentHelper::getViewHandlerData(): its base-table lookup ran right after getHandlers(), which switches the view's active display internally and then switches it back to 'default' before returning - so a field brought in through a relationship added on a non-default display was wrongly rejected as invalid. Fixed by explicitly re-selecting the target display before computing base tables.

All four were reproduced live against the unfixed code through the real, running views_agent agent (fixes temporarily reverted, ddev drush agent views_agent) before being fixed, confirming these are genuine, live-reachable defects and not artifacts of the test harness:

  • create_view/user_role: the raw internal exception (A valid cache entry key is required...) reached the user as the agent's entire reply.
  • create_view_display/display_id: default: reported success with no error at all, while silently wiping a real field and a custom title from the existing default display (confirmed via drush config:get immediately after).
  • update_view_plugins/pager: 42: not a caught tool error - the drush process itself crashed with a full PHP stack trace and terminated abnormally.
  • create_view_handler/views_bulk_operations: silently accepted with no error; rendering the view afterward then crashed separately (TypeError: Unsupported operand types: array + null), far from the tool call that actually caused it.

Changes

  • src/Trait/ViewHandlerTrait.php: handleHandlerDataAndFieldSelection() now rejects a table/field pair not present in the view's real available fields.
  • src/Service/ViewsAgent/ViewsAgentHelper.php: new getEntityTypeBaseTable(), validated against Views::viewsData(); getViewHandlerData() now re-selects the target display before reading base tables.
  • src/Plugin/AiFunctionCall/CreateView.php: validates the entity type's base table before creating the view.
  • src/Plugin/AiFunctionCall/CreateViewDisplay.php: rejects display_id: 'default'.
  • src/Plugin/AiFunctionCall/UpdateViewPlugins.php: wraps the overrideOption()/save() path in a catch (\Throwable $e) guard, matching UpdateViewHandlerOptions's existing one.
  • New Kernel Integration tests for all four, each proving the working view (real field, real filtered/paginated query results) survives the rejected attempt untouched: tests/src/Kernel/Integration/CreateViewHandlerTableFieldValidationTest.php, CreateViewEntityTypeValidationTest.php, CreateViewDisplayDefaultRejectionTest.php, UpdateViewPluginsMalformedValueTest.php.
  • New Kernel plugin-level regression tests in CreateViewHandlerTest.php, CreateViewTest.php, CreateViewDisplayTest.php, UpdateViewPluginsTest.php.

Test plan

  • Each new regression test confirmed failing before its fix and passing after.
  • ConfigureRelationshipFieldsTest::testFieldsViaRelationships, which regressed when the table/field validation first went in, passes again after the display-scoping fix.
  • Full ai_agents_views Kernel suite: 87/87 tests, 965 assertions, 1 pre-existing unrelated skip, no failures.
  • phpcs --standard=Drupal,DrupalPractice, phpstan analyse (module config) and cspell (module dictionary) all clean on every changed file.
  • All four fixes verified live against the real views_agent AI agent both ways (ddev drush agent views_agent, an actual OpenAI-backed tool call through the agent's own loop): reproduced crashing/ corrupting on the unfixed code first (see Summary above), then confirmed clean - readable rejection, no crash, no data loss - on the fixed code with the exact same prompts. The relationship-fields case was verified the same way, live.

Closes #3622011

AI-Generated: Yes (Used Claude Code to sweep the remaining tools for this class of defect, diagnose each root cause, implement the fixes, write the regression and integration tests, and verify live against the shipped agent.)

Merge request reports

Loading