Skip to content

AI Workspace: stream provider replies once the PHP AI Client can - #1083

Draft
whyisjake wants to merge 2 commits into
feat/ai-workspacefrom
feat/ai-workspace-streaming
Draft

whyisjake wants to merge 2 commits into
feat/ai-workspacefrom
feat/ai-workspace-streaming

Conversation

@whyisjake

@whyisjake whyisjake commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

What?

Follow up to #1004. Depends on WordPress/php-ai-client#255.

Brings back token-by-token provider streaming for the AI Workspace. #1004 took it out so the workspace can merge on released code; this PR is a single commit that reverts that removal.

Why?

Streaming depends on WordPress/php-ai-client#255, which is not merged or released. Until now #1004 carried a vendored copy of that PR's streaming classes, which made an unreleased upstream change a blocker for the whole workspace. Splitting it out lets #1004 land with every reply arriving whole, and lets streaming follow once the PHP AI Client ships it. Streaming support in the PHP AI Client is one of the goals on the WordPress 7.2 roadmap.

How?

This reverts 68425d6 ("answer every turn buffered until the PHP AI Client streams") and restores:

  • the Anthropic streaming driver, model, stream mapper and fopen() transport under includes/Experiments/AI_Workspace/Streaming/, with their tests;
  • the vendored streaming classes and HTTP DTOs, and the SDK overlay's streaming feature;
  • the default streaming driver in Prompt_Model_Client, the "cannot stream" transcript notice, and the streaming docs, hooks and changelog lines.

The resulting tree is identical to feat/ai-workspace before the split (e2e9360).

Before merging: once #255 is merged and released, re-vendor the streaming classes from that release rather than from the PR head, and check the getStream / isStream guards in SDK_Overlay. Streaming remains Anthropic-only; other providers keep the buffered path.

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Splitting provider streaming out of #1004 into this revert, and writing this description.

Testing Instructions

This PR's tree is identical to feat/ai-workspace at e2e9360. On that tree:

  • npm run build, npm run typecheck, npm run lint:js, npm run lint:php and npm run lint:php:stan passed.
  • npm run test:php passed: 2,049 tests, 0 failures.
  • The AI Workspace e2e specs passed: ai-workspace.spec.js, ai-workspace-tools.spec.ts and ai-workspace-handoff.spec.ts.

To try streaming manually, activate the Anthropic provider with a key, open Tools > AI Workspace and send a turn; the reply should appear as it is generated.

Changelog Entry

Added - AI Workspace replies from Anthropic models stream in as they are generated.

🤖 Generated with Claude Code

Open WordPress Playground Preview

Restore the provider streaming that #1004 took out to land on released
code. This reverts 68425d6: the Anthropic streaming driver, model,
stream mapper and fopen() transport, the vendored streaming classes and
the SDK overlay's streaming feature, the default streaming driver in
Prompt_Model_Client, the "cannot stream" transcript notice, and the
streaming docs, hooks and tests.

This depends on WordPress/php-ai-client#255. Once it is merged and
released, re-vendor the streaming classes from that release before
merging.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.31770% with 97 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.73%. Comparing base (225802b) to head (03ce7e1).

Files with missing lines Patch % Lines
...s/AI_Workspace/Streaming/Streaming_Turn_Driver.php 14.49% 59 Missing ⚠️
...nts/AI_Workspace/Streaming/Fopen_Stream_Opener.php 86.58% 11 Missing ⚠️
...ming/Anthropic_Streaming_Text_Generation_Model.php 83.87% 10 Missing ⚠️
...AI_Workspace/Streaming/Anthropic_Stream_Mapper.php 95.62% 7 Missing ⚠️
...Workspace/Streaming/Streaming_Http_Transporter.php 95.29% 4 Missing ⚠️
includes/SDK_Overlay.php 0.00% 3 Missing ⚠️
...s/Experiments/AI_Workspace/Prompt_Model_Client.php 83.33% 1 Missing ⚠️
...AI_Workspace/Streaming/Stream_Opener_Interface.php 0.00% 1 Missing ⚠️
...nts/AI_Workspace/Streaming/Streaming_Exception.php 0.00% 1 Missing ⚠️
Additional details and impacted files
@@                   Coverage Diff                   @@
##             feat/ai-workspace    #1083      +/-   ##
=======================================================
- Coverage                83.87%   83.73%   -0.15%     
- Complexity                3737     3917     +180     
=======================================================
  Files                      148      155       +7     
  Lines                    14352    14817     +465     
=======================================================
+ Hits                     12038    12407     +369     
- Misses                    2314     2410      +96     
Flag Coverage Δ
unit 83.73% <79.31%> (-0.15%) ⬇️

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.

@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

Bring in the fix that leaves unsigned thinking out of replayed
conversations (225802b). Prompt_Model_Client conflicted: keep both
imports, and filter unsigned thought parts first, then hand the round
to the default streaming driver. The streaming model signs the thinking
it captures, so its thought parts pass through the filter unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@jakaria-istauk

Copy link
Copy Markdown

The E2E failure on shard 1/3 (abilities-explorer-surface.spec.js:239 › "does not let a refresh answered after a remove undo it") looks like a race in the test, not a regression from the streaming code.

What happens: the Abilities Explorer search box is debounced. The test fills "Reader Fixture", clicks Refresh, then hovers the "Assistant Reader Fixture" row right away. On a slow runner the hover lands while the unfiltered list is still showing (y=585 in the trace). The filter then applies, the table re-renders with 2 rows, the target row moves up, and the pointer ends up over "Withheld Reader Fixture". The row actions hide again, so the click fails with element is outside of the viewport. The CI trace frames for this run show the shift.

It fails the same way at e2e9360, and feat/ai-workspace passes without the streaming commit. My guess is that the extra code only changes timing enough to lose the race on CI every time. The test itself came from #1074, so it can flake on any branch.

Reproduced locally: it passes at normal speed. With Chrome CPU throttled 6x (Emulation.setCPUThrottlingRate), the original test failed 3 of 3 runs. With the change below, all 18 runs of the spec pass, and all 23 Abilities Explorer specs pass unthrottled.

Fix: wait until the search has applied before hovering a row:

--- a/tests/e2e/utils/abilities-explorer.js
+++ b/tests/e2e/utils/abilities-explorer.js
+/**
+ * Searches the list by ability label and waits until only matching rows show.
+ *
+ * The search input is debounced, so the unfiltered rows stay on screen for a
+ * moment. Hovering a row then is unsafe: the table re-renders under the pointer
+ * and the row's actions hide again before they can be clicked.
+ *
+ * @param {Object} page The page.
+ * @param {string} text Text in the label of every matching row.
+ */
+const searchLabel = async ( page, text ) => {
+	await search( page, text );
+	await expect( dataRows( page ).filter( { hasNotText: text } ) ).toHaveCount(
+		0
+	);
+};

abilities-explorer-surface.spec.js then uses searchLabel( page, 'Reader Fixture' ) in place of search() in all six tests, since each one hovers a row right after searching. search() itself is unchanged. Putting the wait inside it would break abilities-explorer-list.spec.js, which hides the Slug column and then searches ai-e2e/.

Happy to open this as a PR against develop, where the spec lives, if that helps.

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.

2 participants