Skip to content

Fix: make connector validity capability-aware instead of text-only - #935

Open
whyisjake wants to merge 8 commits into
developfrom
fix/capability-aware-connector-validity
Open

Fix: make connector validity capability-aware instead of text-only#935
whyisjake wants to merge 8 commits into
developfrom
fix/capability-aware-connector-validity

Conversation

@whyisjake

@whyisjake whyisjake commented Aug 13, 2026

Copy link
Copy Markdown
Member

What?

Closes #933

Makes the connector validity check capability-aware, so a site whose only connector provides speech or image generation is no longer told its connectors may be invalid.

Why?

has_valid_ai_credentials() decided whether any configured connector was valid by asking a single question — can it generate text:

return wp_ai_client_prompt( 'Test' )->is_supported_for_text_generation();

That verdict reached the settings screen as PAGE_DATA.hasValidCredentials and rendered a red error: "Please review the AI Connectors you have configured to ensure they are valid."

A connector registering only TEXT_TO_SPEECH_CONVERSION / SPEECH_GENERATION is authenticated, registered, and working. It simply does not do text. The connector was detected correctly — the verdict and the wording were what mismatched.

This repository had already solved the narrower version of the problem: #679 gated the image generation UI on real provider support, and #748 refined that into has_image_generation_support(), a request-free scan over connector model metadata. The global credentials check never picked up the same capability awareness.

How?

Direction 1 of the three the issue proposed — generalize the check and reword the notice — chosen because it is the smallest correct fix and mirrors the existing #679/#748 precedent. has_valid_ai_credentials() keeps its name, signature, return type, and semantics as the "can this plugin do its text-based work" gate.

  • has_capability_support( string $capability, bool $reset_cache = false ) generalizes the scan out of has_image_generation_support(), memoized per capability, with a new wpai_has_capability_support filter. has_image_generation_support() becomes a delegate and applies wpai_has_image_generation_support after the general filter, so existing third-party integrations stay authoritative for image generation.
  • has_valid_ai_credentials() is re-backed with that scan for TEXT_GENERATION. The has_ai_credentials() early return and the wpai_pre_has_valid_credentials_check short-circuit keep their exact positions. Its docblock now states that it is a capability-specific gate, not a judgement about connector validity.
  • get_supported_capabilities() reports what the connectors can do, and is exposed to the settings page as capabilities alongside hasCredentials and hasValidCredentials. Together the three signals distinguish "no connectors" from "connectors that don't generate text".
  • The settings notice names the requirement and what the connectors provide, at warning rather than error — nothing is broken. The "no connectors configured" branch is unchanged and stays an error. Capability values map to human-readable labels in the UI; unrecognized values are dropped so a raw identifier can never reach user-visible copy, and an empty or absent list falls back to a capability-free sentence.
  • wp ai alt-text generate splits its message the same way.

CapabilityEnum is a constants class extending AbstractEnum, not a native PHP enum, so the helper takes the constant's string value rather than an enum instance.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5
Used for: tracing the behaviour to has_valid_ai_credentials(), drafting the implementation plan, implementing the change, and writing the tests.

Testing Instructions

Reproducing the original report needs the AI plugin plus exactly one non-text connector. The e2e suite simulates that without shipping a speech provider, via a capability override endpoint added to the e2e-testing support plugin.

Automated:

npm run wp-env:test start
npm run test:php
npm run test:e2e -- tests/e2e/specs/admin/connector-capabilities.spec.js

Manual:

  1. Configure any connector with a working API key.
  2. Force it to advertise a non-text capability, e.g.
    add_filter( 'wpai_has_capability_support', fn( $s, $c ) => 'speech_generation' === $c, 10, 2 );
  3. Visit Settings → AI. The notice should name text generation as the requirement and speech generation as what is available, and should not say the connectors may be invalid.
  4. Remove all connector credentials and reload — the original "Verify you have one or more AI Connectors configured" error should be unchanged.

Tests executed for this PR:

  • Full PHPUnit suite — 1395 tests, 0 failures, 39 skipped
  • HelpersTest — new has_capability_support, get_supported_capabilities, and has_valid_ai_credentials coverage; the 7 pre-existing has_image_generation_support tests pass with unmodified bodies
  • Settings_PageTest::test_script_module_data_includes_capability_status
  • Alt_Text_CommandTest — 28 tests
  • Playwright connector-capabilities.spec.js (3) and settings.spec.js (17), 20/20 on a clean environment
  • phpcs, phpstan, eslint, tsc — all clean

Screenshots or screencast

Both captured on a speech-only connector.

Before After
notice-before notice-after

Changelog Entry

Fixed - Connectors that provide only non-text capabilities, such as speech or image generation, are no longer reported as possibly invalid. The settings screen and the wp ai alt-text command now explain that a text-generating connector is required and name what the configured connectors do provide.

Open WordPress Playground Preview

has_valid_ai_credentials() decided whether any configured connector was
valid by asking a single question -- can it generate text. A site whose
only connector provides speech or image generation was therefore told its
connectors may be invalid, when they were correctly configured and working.

Generalize the request-free scan already used by has_image_generation_support()
into has_capability_support( $capability ), memoized per capability, and back
has_valid_ai_credentials() with it for TEXT_GENERATION. The verdict for
text-based features is unchanged; what changes is that the check no longer
routes through a live prompt builder and the reason for a false result is now
expressible per capability.

has_image_generation_support() becomes a delegate. Its wpai_has_image_generation_support
filter is applied after the new wpai_has_capability_support filter, so existing
third-party integrations remain authoritative for image generation.

The docblock on has_valid_ai_credentials() now states that it is a
capability-specific gate rather than a judgement about connector validity.
Add get_supported_capabilities(), which reports the capabilities the configured
connectors can actually provide, and pass it to the settings page script module
as `capabilities` alongside the existing `hasCredentials` and `hasValidCredentials`.

Together the three signals let the settings screen distinguish "no connectors
configured" from "connectors configured, but none of them generate text" -- and
name what the connectors do provide instead.

Extract the script module data assembly out of the filter closure into a private
method so the payload contract is testable. The route render function that guards
filter registration is never defined under WPAI_IS_TEST, so testing through the
filter itself would only ever skip.
…connector

A site whose only connector provides speech or image generation was shown a red
error notice reading "Please review the AI Connectors you have configured to
ensure they are valid." The connector was valid; it simply did not do text.

Replace that branch with copy that names the requirement and what the configured
connectors actually provide -- "These AI features need an AI Connector that can
generate text. The AI Connectors you have configured provide speech generation."
-- and lower its intent from error to warning, because nothing is broken. The
"no connectors configured" branch is unchanged and stays an error.

Capability values are mapped to human-readable labels in the UI layer, and
unrecognized values are dropped so a raw identifier can never reach user-visible
copy. When the capability list is empty or absent -- including stale cached script
data after an upgrade -- the notice falls back to a capability-free sentence.

Adds e2e coverage for all three notice states, with a capability override endpoint
in the e2e-testing plugin so specs can simulate a non-text connector without
shipping a real speech provider.
`wp ai alt-text generate` reported "No valid AI credentials found" whenever
has_valid_ai_credentials() was false, which reads as broken configuration even
when the connector is authenticated and working but only does speech or images.

Split the message: when no credentials exist at all, point at Settings > Connectors
as before; when credentials exist but no connector generates text, say that instead.

test_generate_errors_without_credentials() was already filtering has_ai_credentials
to true, so despite its name it exercised the capability-mismatch path rather than
the missing-credential path. Point it at the state it names and add separate
coverage for the capability-mismatch message.
@github-actions

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 74.76%. Comparing base (fc6bb2d) to head (9b1d728).

Files with missing lines Patch % Lines
includes/helpers.php 95.23% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #935      +/-   ##
=============================================
+ Coverage      74.57%   74.76%   +0.19%     
- Complexity      3132     3134       +2     
=============================================
  Files            132      132              
  Lines          12213    12231      +18     
=============================================
+ Hits            9108     9145      +37     
+ Misses          3105     3086      -19     
Flag Coverage Δ
unit 74.76% <97.05%> (+0.19%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@whyisjake
whyisjake marked this pull request as ready for review August 13, 2026 20:38
@whyisjake
whyisjake requested a review from a team August 13, 2026 20:38
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: whyisjake <whyisjake@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

The settings page data filter is only registered when ai_ai_wp_admin_render_page()
exists, and Requirements::check() short-circuits its asset check under WPAI_IS_TEST,
so the generated route file that defines it is never loaded in PHPUnit. Nothing
therefore asserted that the filter is attached under the hook the route script
module actually reads -- the existing coverage invokes the payload builder directly.

Add a global-namespace stand-in for that function, following the wp-cli-stubs.php
pattern, and assert the wiring: the filter is registered under the page slug,
existing script module data is preserved, and the payload carries the credential
and capability keys the settings notice depends on.
@jeffpaul jeffpaul added this to the 1.4.0 milestone Aug 13, 2026
@dkotter

dkotter commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

We were already considering just removing the has_valid_ai_credentials function as we really only use that right now to show an admin notice (as the attached Issue mentions) and that doesn't really seem to be that helpful.

So in looking at this PR, I guess where I'm struggling is if it's worth 1000+ lines of code to make that error message more useful or if the right answer is to just get rid of that entirely. If we were actually gating functionality based on that check we'd obviously want that to be more robust but when it's just used for an admin notice, trying to decide if this is worth keeping or not. Any thoughts?

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.

Connector validity check assumes text generation, so speech-only and image-only connectors report as invalid

3 participants