Skip to content

Comments

feat: add PHPStan static analysis and fix all level-max errors#25

Merged
abnegate merged 11 commits intomainfrom
static-analysis
Feb 24, 2026
Merged

feat: add PHPStan static analysis and fix all level-max errors#25
abnegate merged 11 commits intomainfrom
static-analysis

Conversation

@ChiragAgg5k
Copy link
Member

@ChiragAgg5k ChiragAgg5k commented Feb 24, 2026

Summary

  • Add phpstan.neon config running level max analysis on src/ and tests/
  • Upgrade phpstan/phpstan from 1.9.x-dev^2.0 for PHP 8.4 compatibility
  • Upgrade laravel/pint from 1.2.*^1.18 to eliminate PHP 8.4 deprecation warnings during composer format/lint
  • Broaden Agent::setInstructions() type from array<string, string>array<string, string|list<string>> to match how callers (e.g. Appwrite cloud) actually use it
  • Fix all adapters (Anthropic, Deepseek, Gemini, Ollama, OpenAI, Perplexity, XAI) to handle array instruction values and add proper types around json_decode results
  • Fix Schema::toJson() to safely access mixed property values
  • Remove redundant PHPUnit assertions that PHPStan identified as always-true given the declared return types

Test plan

  • composer check passes with 0 PHPStan errors
  • composer lint passes with no deprecation warnings
  • composer format runs cleanly with no deprecation warnings

Summary by CodeRabbit

  • New Features

    • Added PHPStan static analysis configuration for enhanced code quality checks.
  • Bug Fixes

    • Improved robustness of streaming response parsing across all AI adapters with defensive type checks.
    • Enhanced error handling and message extraction from API responses.
    • Added support for array-based instruction content in adapter implementations.
  • Chores

    • Updated development dependencies: Laravel Pint, PHPStan, and PHPUnit.
    • Updated test framework configuration and code style rules.
    • Refined Dockerfile dependency installation strategy.

- Add phpstan.neon config targeting src/ and tests/ at level max
- Upgrade phpstan/phpstan from 1.9.x-dev to ^2.0 for PHP 8.4 support
- Upgrade laravel/pint from 1.2.* to ^1.18 to fix PHP 8.4 deprecation warnings in composer format/lint
- Broaden Agent::setInstructions() type from array<string, string> to array<string, string|list<string>> so callers can pass array values per instruction key
- Fix all adapters (Anthropic, Deepseek, Gemini, Ollama, OpenAI, Perplexity, XAI) to handle array instruction values and properly type json_decode results
- Fix Schema::toJson() to safely access mixed property values
- Remove redundant PHPUnit assertions that were always-true per static analysis
@coderabbitai
Copy link

coderabbitai bot commented Feb 24, 2026

Warning

Rate limit exceeded

@ChiragAgg5k has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 64ca0fb and 6b6aa38.

📒 Files selected for processing (1)
  • Dockerfile

Walkthrough

This pull request updates dependencies (PHPStan, PHPUnit, Laravel Pint), introduces configuration files (phpstan.neon, pint.json), modernizes existing configurations (Dockerfile, phpunit.xml), and applies systematic code cleanup across the agents codebase. The primary changes include: removing documentation annotations from core classes and adapter implementations; introducing defensive parsing logic (type guards, null checks) in streaming response handlers; improving error handling across adapters; and updating test files with syntax changes and assertion removals. The functional interface change adds an optional nullable listener parameter to the Adapter::send() method.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding PHPStan static analysis and fixing all level-max errors, which aligns with the primary objectives of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch static-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/Agents/AgentTest.php (1)

99-141: ⚠️ Potential issue | 🟡 Minor

assertGreaterThan(0, $result['modelLoadingDuration']) is fragile for warm-model runs.

The Ollama adapter now returns load_duration as-is (an int) when present, including 0 when the model is already loaded. An assertGreaterThan(0, 0) will fail in that case. When load_duration is absent, the adapter returns null, and the assertion will also fail.

Consider using assertGreaterThanOrEqual(0, ...) or only asserting that the field exists (assertArrayHasKey) rather than checking its magnitude.

🔧 Suggested fix
-        $this->assertGreaterThan(0, $result['modelLoadingDuration']);
+        $this->assertGreaterThanOrEqual(0, $result['modelLoadingDuration']);

(applies to both test_embed_returns_array_with_embedding_adapter line 109 and test_embedding_dimensions line 132)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/Agents/AgentTest.php` around lines 99 - 141, The failing assertion is
that test_embed_returns_array_with_embedding_adapter and
test_embedding_dimensions use assertGreaterThan(0,
$result['modelLoadingDuration']) which is fragile because Agent::embed (Ollama
adapter) can return 0 or null for load_duration; replace those strict >0 checks
with a non-negative check (e.g. use assertGreaterThanOrEqual(0, ...) when value
is numeric) or simply assert the key exists
(assertArrayHasKey('modelLoadingDuration', $result)) and only assert magnitude
when not null, referencing the test methods
test_embed_returns_array_with_embedding_adapter and test_embedding_dimensions
and the $result['modelLoadingDuration'] reference.
src/Agents/Adapters/Anthropic.php (1)

271-310: ⚠️ Potential issue | 🟠 Major

Accumulate multiple deltas per chunk to avoid truncation.
Right now Line 301 overwrites $block; if a chunk contains multiple content_block_delta lines, earlier deltas are lost. Consider appending and emitting the delta text.

🔧 Suggested fix
-                    if ($deltaType === 'text_delta' && isset($json['delta']['text']) && is_string($json['delta']['text'])) {
-                        $block = $json['delta']['text'];
-                    }
-
-                    if (! empty($block)) {
-                        if ($listener !== null) {
-                            $listener($block);
-                        }
-                    }
+                    if ($deltaType === 'text_delta' && isset($json['delta']['text']) && is_string($json['delta']['text'])) {
+                        $deltaText = $json['delta']['text'];
+                        if ($deltaText !== '') {
+                            $block .= $deltaText;
+                            if ($listener !== null) {
+                                $listener($deltaText);
+                            }
+                        }
+                    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/Anthropic.php` around lines 271 - 310, The handler for
'content_block_delta' currently assigns $block = $json['delta']['text'], which
overwrites previous deltas and causes truncation; instead accumulate deltas by
appending $json['delta']['text'] to $block (e.g. $block .= ...) so multiple
'content_block_delta' events per chunk are preserved, ensure $block is
initialized/cleared when entering 'content_block_start' and only emit to
$listener when $block is non-empty; update references in the
'content_block_delta' and 'content_block_start' cases and keep the
$listener($block) call as the emission point.
🧹 Nitpick comments (3)
src/Agents/Adapters/XAI.php (2)

154-156: is_scalar accepts booleans, which cast to "1" or "".

is_scalar returns true for bool as well as string/int/float. If $json['code'] is false, (string) false yields "", producing "() ..." as the error type. Narrowing to is_string is safer.

♻️ Suggested fix
-        $errorType = isset($json['code']) && is_scalar($json['code']) ? (string) $json['code'] : 'unknown_error';
+        $errorType = isset($json['code']) && is_string($json['code']) ? $json['code'] : 'unknown_error';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/XAI.php` around lines 154 - 156, The code that computes
$errorType uses is_scalar which accepts booleans and causes false to cast to an
empty string; change the check in the $errorType assignment to use
is_string($json['code']) instead of is_scalar(...) so only actual strings are
accepted, keep the cast to (string) and the fallback 'unknown_error' unchanged;
leave the $errorMessage logic as-is (is_string check) and update the assignment
of $errorType where it's defined to use is_string to prevent boolean -> "" error
types.

128-137: $block is overwritten per loop iteration rather than accumulated.

If a single HTTP chunk carries more than one data: SSE line, the listener receives all content pieces correctly, but process() returns only the final piece — so the parent's $content .= $this->process(...) accumulator will silently drop all but the last. This is pre-existing behaviour, but it's worth addressing while this code is being touched.

♻️ Suggested fix
-            if (isset($delta['content']) && is_string($delta['content'])) {
-                $block = $delta['content'];
+            if (isset($delta['content']) && is_string($delta['content'])) {
+                $block .= $delta['content'];
 
                 if (! empty($block) && $listener !== null) {
                     $listener($block);

The same pattern appears in Perplexity::process() and Gemini::process() and should be updated there as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/XAI.php` around lines 128 - 137, The code in XAI::process
is overwriting $block for each SSE/data piece instead of accumulating them,
causing callers' accumulators to only get the last piece; change the logic in
XAI::process to append new content (e.g., use concatenation on $block or a local
$accumulator) when multiple delta['content'] pieces are present so the listener
and return value contain all pieces from the chunk, and apply the same pattern
to Perplexity::process() and Gemini::process() where the identical overwrite
occurs (look for $choices, $firstChoice, $delta, and $block variables to locate
the spots to replace overwrite with accumulation).
src/Agents/Adapters/Gemini.php (1)

302-307: json_encode failure is silently swallowed via cast.

json_encode returns false when encoding fails (e.g., non-UTF-8 sequences in details). Casting false to string yields "" in PHP, so $errorDetails silently disappears rather than indicating an encoding failure.

♻️ Suggested fix
-        $errorDetails = isset($error['details']) ? (string) json_encode($error['details'], JSON_PRETTY_PRINT) : '';
+        $encoded = isset($error['details']) ? json_encode($error['details'], JSON_PRETTY_PRINT) : '';
+        $errorDetails = $encoded !== false ? $encoded : '(details not encodable)';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/Gemini.php` around lines 302 - 307, The current
assignment to $errorDetails uses (string) json_encode($error['details'],
JSON_PRETTY_PRINT) which silently hides encoding failures because json_encode
returns false and casting yields an empty string; update the logic in Gemini.php
where $errorDetails is set so you call json_encode and check its return (e.g.,
$encoded = json_encode($error['details'], JSON_PRETTY_PRINT)); if $encoded ===
false, build a fallback string using print_r($error['details'], true) or
var_export and append json_last_error_msg() to indicate the encoding error,
otherwise use the $encoded value; ensure you still produce a human-readable
$errorDetails used in the final return.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Agents/Adapters/OpenAI.php`:
- Around line 268-276: The code in the OpenAI adapter currently overwrites
$block with a single delta per parsed chunk, losing earlier deltas when a chunk
contains multiple "data:" lines; change the extraction logic in the stream
parsing (around the variables $choices, $firstChoice, $delta, $block, and
$listener) to accumulate/appending each delta's 'content' into $block (e.g.,
concatenate each found $delta['content'] instead of assigning), then only call
$listener($block) when the accumulated block is non-empty; ensure you still
validate is_string($delta['content']) and handle empty/null $listener as before.

---

Outside diff comments:
In `@src/Agents/Adapters/Anthropic.php`:
- Around line 271-310: The handler for 'content_block_delta' currently assigns
$block = $json['delta']['text'], which overwrites previous deltas and causes
truncation; instead accumulate deltas by appending $json['delta']['text'] to
$block (e.g. $block .= ...) so multiple 'content_block_delta' events per chunk
are preserved, ensure $block is initialized/cleared when entering
'content_block_start' and only emit to $listener when $block is non-empty;
update references in the 'content_block_delta' and 'content_block_start' cases
and keep the $listener($block) call as the emission point.

In `@tests/Agents/AgentTest.php`:
- Around line 99-141: The failing assertion is that
test_embed_returns_array_with_embedding_adapter and test_embedding_dimensions
use assertGreaterThan(0, $result['modelLoadingDuration']) which is fragile
because Agent::embed (Ollama adapter) can return 0 or null for load_duration;
replace those strict >0 checks with a non-negative check (e.g. use
assertGreaterThanOrEqual(0, ...) when value is numeric) or simply assert the key
exists (assertArrayHasKey('modelLoadingDuration', $result)) and only assert
magnitude when not null, referencing the test methods
test_embed_returns_array_with_embedding_adapter and test_embedding_dimensions
and the $result['modelLoadingDuration'] reference.

---

Nitpick comments:
In `@src/Agents/Adapters/Gemini.php`:
- Around line 302-307: The current assignment to $errorDetails uses (string)
json_encode($error['details'], JSON_PRETTY_PRINT) which silently hides encoding
failures because json_encode returns false and casting yields an empty string;
update the logic in Gemini.php where $errorDetails is set so you call
json_encode and check its return (e.g., $encoded =
json_encode($error['details'], JSON_PRETTY_PRINT)); if $encoded === false, build
a fallback string using print_r($error['details'], true) or var_export and
append json_last_error_msg() to indicate the encoding error, otherwise use the
$encoded value; ensure you still produce a human-readable $errorDetails used in
the final return.

In `@src/Agents/Adapters/XAI.php`:
- Around line 154-156: The code that computes $errorType uses is_scalar which
accepts booleans and causes false to cast to an empty string; change the check
in the $errorType assignment to use is_string($json['code']) instead of
is_scalar(...) so only actual strings are accepted, keep the cast to (string)
and the fallback 'unknown_error' unchanged; leave the $errorMessage logic as-is
(is_string check) and update the assignment of $errorType where it's defined to
use is_string to prevent boolean -> "" error types.
- Around line 128-137: The code in XAI::process is overwriting $block for each
SSE/data piece instead of accumulating them, causing callers' accumulators to
only get the last piece; change the logic in XAI::process to append new content
(e.g., use concatenation on $block or a local $accumulator) when multiple
delta['content'] pieces are present so the listener and return value contain all
pieces from the chunk, and apply the same pattern to Perplexity::process() and
Gemini::process() where the identical overwrite occurs (look for $choices,
$firstChoice, $delta, and $block variables to locate the spots to replace
overwrite with accumulation).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9291c46 and 1351364.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • .github/workflows/code-ql-analysis.yml
  • composer.json
  • phpstan.neon
  • src/Agents/Adapter.php
  • src/Agents/Adapters/Anthropic.php
  • src/Agents/Adapters/Deepseek.php
  • src/Agents/Adapters/Gemini.php
  • src/Agents/Adapters/Ollama.php
  • src/Agents/Adapters/OpenAI.php
  • src/Agents/Adapters/Perplexity.php
  • src/Agents/Adapters/XAI.php
  • src/Agents/Agent.php
  • src/Agents/Conversation.php
  • src/Agents/DiffCheck/DiffCheck.php
  • src/Agents/Message.php
  • src/Agents/Messages/Image.php
  • src/Agents/Messages/Text.php
  • src/Agents/Role.php
  • src/Agents/Roles/Assistant.php
  • src/Agents/Roles/User.php
  • src/Agents/Schema.php
  • src/Agents/Schema/SchemaObject.php
  • tests/Agents/AgentTest.php
  • tests/Agents/Conversation/ConversationBase.php
  • tests/Agents/DiffCheck/DiffCheckOpenAITest.php
  • tests/Agents/DiffCheck/DiffCheckTest.php
  • tests/Agents/Messages/ImageTest.php
  • tests/Agents/Messages/TextTest.php
  • tests/Agents/Roles/AssistantTest.php
  • tests/Agents/Roles/UserTest.php
  • tests/Agents/Schema/SchemaObjectTest.php
  • tests/Agents/SchemaTest.php
💤 Files with no reviewable changes (5)
  • src/Agents/Message.php
  • src/Agents/Role.php
  • src/Agents/Messages/Text.php
  • src/Agents/Roles/User.php
  • src/Agents/Roles/Assistant.php

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Agents/Adapters/Anthropic.php (1)

301-309: ⚠️ Potential issue | 🟠 Major

$block overwrite drops earlier deltas; misplaced listener call emits stale content.

Two issues in content_block_delta handling:

  1. Truncation (line 302): $block = $json['delta']['text'] overwrites instead of appends. Anthropic's streaming protocol specifies "one or more content_block_delta events" per content block, and the official example shows two separate text_delta events ("Hello" then "!") in the same content block. If utopia-php/fetch delivers these in a single network chunk, only the last delta survives in $block, and $content .= $this->process(...) accumulates a truncated response.

  2. Stale listener (lines 305-309): The if (! empty($block)) / $listener($block) call sits outside the text_delta guard. Anthropic streaming also delivers thinking_delta and signature_delta as content_block_delta events in extended-thinking models. When those arrive, $block retains its previous value, and the listener re-emits already-delivered text.

The correct pattern (already used in Deepseek::process()) is to accumulate and call the listener inside the conditional:

🐛 Proposed fix
-                    if ($deltaType === 'text_delta' && isset($json['delta']['text']) && is_string($json['delta']['text'])) {
-                        $block = $json['delta']['text'];
-                    }
-
-                    if (! empty($block)) {
-                        if ($listener !== null) {
-                            $listener($block);
-                        }
-                    }
+                    if ($deltaType === 'text_delta' && isset($json['delta']['text']) && is_string($json['delta']['text'])) {
+                        $deltaText = $json['delta']['text'];
+                        if ($deltaText !== '') {
+                            $block .= $deltaText;
+                            if ($listener !== null) {
+                                $listener($deltaText);
+                            }
+                        }
+                    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/Anthropic.php` around lines 301 - 309, In
content_block_delta handling update the logic so text deltas are appended not
overwritten and the listener is invoked only when a new text delta is processed:
inside the branch that checks $deltaType === 'text_delta' and
isset($json['delta']['text']) replace the assignment $block =
$json['delta']['text'] with an append (e.g. $block .= ...) and immediately call
$listener($block) if $listener !== null; remove the separate outer if (!
empty($block))/$listener call so non-text deltas (thinking_delta,
signature_delta) do not re-emit stale content; refer to the content_block_delta
handling, $deltaType, $json['delta']['text'], $block and $listener to locate the
code.
♻️ Duplicate comments (1)
src/Agents/Adapters/OpenAI.php (1)

269-278: $block = $delta['content'] still overwrites instead of accumulating.

The same truncation issue from the previous review is unaddressed: if a single network chunk contains multiple data: lines (e.g., "Hello" + " world"), $block is overwritten each iteration and only the last delta is returned from process(), causing $content to be missing earlier tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/OpenAI.php` around lines 269 - 278, In process() of
OpenAI.php the line `$block = $delta['content']` overwrites prior tokens when a
network chunk contains multiple SSE `data:` lines; change this to accumulate the
delta content (e.g., append `$delta['content']` to an existing buffer) instead
of replacing it so earlier tokens are preserved, and ensure the buffer variable
(used later as `$content`/return value) is initialized outside the loop and
updated (e.g., `$block .= $delta['content']` or append into `$content`) before
invoking `$listener($block)` or returning.
🧹 Nitpick comments (3)
Dockerfile (1)

1-1: Consider updating the composer:2.0 image to a more recent tag.

composer:2.0 is a very old pinned tag (Composer is now at 2.7+). It may lack security fixes in composer itself and its bundled PHP runtime. Prefer composer:2 (floating latest 2.x) or a higher explicit pin (e.g., composer:2.7).

🔧 Suggested update
-FROM composer:2.0 AS composer
+FROM composer:2 AS composer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` at line 1, The Dockerfile pins the base stage to an outdated
image "FROM composer:2.0"; update this to a newer Composer image tag (e.g.,
"composer:2" to follow latest 2.x or a specific newer pin like "composer:2.7")
to get security and runtime fixes. Edit the Dockerfile's FROM line (referencing
the existing FROM composer:2.0) and replace the tag with the chosen updated tag,
rebuild and verify the build and runtime Composer behavior.
phpunit.xml (1)

2-5: LGTM — schema URL and attribute cleanup are correct for PHPUnit 12.

The schema definition for PHPUnit 12.0 can always be found at https://schema.phpunit.de/12.0/phpunit.xsd, so the xsi:noNamespaceSchemaLocation value is correct. Removing verbose and stopOnFailure is also appropriate as both were deprecated in earlier PHPUnit versions.

One optional alternative: pointing to vendor/phpunit/phpunit/phpunit.xsd (the local vendored copy) means IDEs and validators always use the exact installed version, which avoids any minor drift when ^12 resolves to 12.1 or 12.2.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@phpunit.xml` around lines 2 - 5, No change required: the
xsi:noNamespaceSchemaLocation value is correct for PHPUnit 12 and removing
deprecated attributes (verbose, stopOnFailure) is appropriate; optionally, if
you want IDE/validator stability against minor patch-version drift, change the
xsi:noNamespaceSchemaLocation to point at the vendored schema
(vendor/phpunit/phpunit/phpunit.xsd) so tools use the exact installed version
instead of the remote https://schema.phpunit.de/12.0/phpunit.xsd.
src/Agents/Adapters/Anthropic.php (1)

222-223: $content reused with a different type in the non-stream path.

$content is declared as string on line 181, then reassigned to mixed ($json['content'] ?? null) on line 223. The final non-stream return path uses $text, so there is no runtime impact, but the reuse creates a confusing type change. Consider a dedicated variable name (e.g. $responseContentBlocks) for the response body content array.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Agents/Adapters/Anthropic.php` around lines 222 - 223, The variable
$content is declared as a string earlier and then reassigned to an array/mixed
($json['content'] ?? null), causing a confusing type change; change the
reassigned variable name (for example to $responseContentBlocks) in the
non-stream handling branch where $json is inspected, update any subsequent
references that expect the array to use the new name, and leave the original
$content (string) and $text usages untouched so types remain consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/Agents/Adapters/Anthropic.php`:
- Around line 301-309: In content_block_delta handling update the logic so text
deltas are appended not overwritten and the listener is invoked only when a new
text delta is processed: inside the branch that checks $deltaType ===
'text_delta' and isset($json['delta']['text']) replace the assignment $block =
$json['delta']['text'] with an append (e.g. $block .= ...) and immediately call
$listener($block) if $listener !== null; remove the separate outer if (!
empty($block))/$listener call so non-text deltas (thinking_delta,
signature_delta) do not re-emit stale content; refer to the content_block_delta
handling, $deltaType, $json['delta']['text'], $block and $listener to locate the
code.

---

Duplicate comments:
In `@src/Agents/Adapters/OpenAI.php`:
- Around line 269-278: In process() of OpenAI.php the line `$block =
$delta['content']` overwrites prior tokens when a network chunk contains
multiple SSE `data:` lines; change this to accumulate the delta content (e.g.,
append `$delta['content']` to an existing buffer) instead of replacing it so
earlier tokens are preserved, and ensure the buffer variable (used later as
`$content`/return value) is initialized outside the loop and updated (e.g.,
`$block .= $delta['content']` or append into `$content`) before invoking
`$listener($block)` or returning.

---

Nitpick comments:
In `@Dockerfile`:
- Line 1: The Dockerfile pins the base stage to an outdated image "FROM
composer:2.0"; update this to a newer Composer image tag (e.g., "composer:2" to
follow latest 2.x or a specific newer pin like "composer:2.7") to get security
and runtime fixes. Edit the Dockerfile's FROM line (referencing the existing
FROM composer:2.0) and replace the tag with the chosen updated tag, rebuild and
verify the build and runtime Composer behavior.

In `@phpunit.xml`:
- Around line 2-5: No change required: the xsi:noNamespaceSchemaLocation value
is correct for PHPUnit 12 and removing deprecated attributes (verbose,
stopOnFailure) is appropriate; optionally, if you want IDE/validator stability
against minor patch-version drift, change the xsi:noNamespaceSchemaLocation to
point at the vendored schema (vendor/phpunit/phpunit/phpunit.xsd) so tools use
the exact installed version instead of the remote
https://schema.phpunit.de/12.0/phpunit.xsd.

In `@src/Agents/Adapters/Anthropic.php`:
- Around line 222-223: The variable $content is declared as a string earlier and
then reassigned to an array/mixed ($json['content'] ?? null), causing a
confusing type change; change the reassigned variable name (for example to
$responseContentBlocks) in the non-stream handling branch where $json is
inspected, update any subsequent references that expect the array to use the new
name, and leave the original $content (string) and $text usages untouched so
types remain consistent.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e561ee9 and 64ca0fb.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • Dockerfile
  • composer.json
  • phpunit.xml
  • pint.json
  • src/Agents/Adapters/Anthropic.php
  • src/Agents/Adapters/Deepseek.php
  • src/Agents/Adapters/Gemini.php
  • src/Agents/Adapters/Ollama.php
  • src/Agents/Adapters/OpenAI.php
  • src/Agents/DiffCheck/DiffCheck.php
  • tests/Agents/AgentTest.php
  • tests/Agents/Conversation/ConversationBase.php
  • tests/Agents/DiffCheck/DiffCheckOpenAITest.php
💤 Files with no reviewable changes (2)
  • tests/Agents/Conversation/ConversationBase.php
  • tests/Agents/AgentTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
  • composer.json
  • pint.json
  • tests/Agents/DiffCheck/DiffCheckOpenAITest.php
  • src/Agents/Adapters/Gemini.php

@abnegate abnegate merged commit 0522279 into main Feb 24, 2026
4 checks passed
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