Normalize AI Copilot tool schemas for the provider - #366
Merged
AllTerrainDeveloper merged 2 commits intoJul 18, 2026
Merged
Conversation
One tool with a valid-but-unsupported input_schema makes the AI Copilot return a
400 for every query: the provider's tool-schema validator rejects the entire tool
list, not just the offending tool, so the assistant is 100% unusable, not
degraded. Three shapes seen in the wild trigger it: a "type" array (an ability's
GET/null run-path), a top-level oneOf/anyOf/allOf, and an empty "properties" array
that encodes to JSON as [] where an object needs {}.
Add desktop_mode_ai_normalize_tool_schema() and apply it to every tool in the
final list, after the desktop_mode_ai_tools filter, so it covers built-in
abilities, command tools, and anything a plugin injected. This reshapes only the
model-facing copy: WP_Ability::execute() still validates arguments against the
real schema and permission_callback still gates execution, so no enforcement is
lost. Only the top level is constrained; nested combinators are preserved. The
projection is idempotent.
Adds unit coverage for all three shapes plus nested-combinator preservation and
idempotence.
Fixes WordPress#362.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A schema whose only content was a top-level combinator (or a bare type
union) normalized to {"type":"object"} with no properties key at all,
which leaves the provider-facing schema incomplete for the same strict
validator this fix targets. Default a missing properties key to an empty
object, same as the empty-array coercion, so the projection always emits
a complete object schema.
Collaborator
|
Reviewed this locally, the projection had one remaining gap: a schema whose only content is a top-level combinator (e.g. |
AllTerrainDeveloper
enabled auto-merge (squash)
July 18, 2026 17:13
Collaborator
|
Just a note, that you are already appearing here: If you have any inconvenience, please tell it to me :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #362.
Thanks @AllTerrainDeveloper for confirming the approach on the issue. This is the normalization fix; the fail-soft-per-tool and name-the-culprit ideas are left as the follow-ups you flagged.
The problem
desktop_mode_ai_run_search()advertises each built-in tool with its ability'sinput_schemapassed straight through as the toolparameters(includes/ai-copilot/search.php). The Abilities API accepts the full breadth of JSON Schema, but the provider's tool-schema validator does not — and it rejects the whole request, not just the offending tool. So a single tool with a legal-but-unsupported schema makes Ask AI return a 400 for every query: unusable, not degraded, and the error names atools.Nindex rather than the plugin, so it's painful to trace.Three shapes, all valid JSON Schema, trigger it in the wild:
typeas an array — e.g.['object','null'], an ability's GET/null run-path. The provider wants the literal"object"at the top level.oneOf/anyOf/allOf— e.g. "supplypost_idORslug". Rejected with "does not support oneOf, allOf, or anyOf at the top level".properties— a no-args tool's[], where an object schema needs{}.The fix
A small pure helper,
desktop_mode_ai_normalize_tool_schema(), projects a schema onto the provider-supported subset:typeto"object"oneOf/anyOf/allOf(nested combinators are left intact — those are real constraints the provider accepts)propertiesarray to an object{"type":"object","properties":{}}It's applied to every tool in the final list, after the
desktop_mode_ai_toolsfilter — so it covers the complete set the provider receives: built-in abilities, command tools, and anything a plugin injected. No single tool, from any source, can 400 the request.Nothing loses enforcement. This reshapes only the copy advertised to the model.
WP_Ability::execute()still validates arguments against the real schema andpermission_callbackstill gates execution — the model is simply told the constraint in prose (the tool description) instead of a schema construct the provider can't parse. The projection is idempotent, so a plugin that already normalizes on the filter is unaffected.Tests
tests/phpunit/tests/aiToolSchemaNormalization.phpcovers each of the three shapes, nested-combinator preservation, empty/non-array input, and idempotence. The helper is pure, so these run without the network or the ability registry.Notes
@since 0.9.6as a guess against the current0.9.5release — please adjust to your intended version.phpcsreports no new violations on the change. I couldn't run the full PHPUnit suite locally (needs the wp-env test DB), so I'd lean on CI for thetest:phpleg.Prepared by @juanlentino with Claude Code assistance.