Skip to content

Sync AI updates and fix the Doctum deployment - #65

Merged
binaryfire merged 16 commits into
0.4from
upstream-sync-batch-2
Oct 20, 2026
Merged

binaryfire merged 16 commits into
0.4from
upstream-sync-batch-2

Conversation

@binaryfire

@binaryfire binaryfire commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Laravel Updates

  • AI failure events — Dispatch failure events for audio, image, transcription and reranking requests, file uploads and store changes, as embeddings and classification already did. Each failed attempt dispatches one, including attempts that failover recovers from, while errors thrown by success listeners don't. As with Hypervel's other optional events, failure events are skipped when nothing listens for them. Document the new events.
  • Interrupted tool results — Port the regression test for the interrupted tool results replayed after a failed turn, and use upstream's named arguments. Hypervel already kept the result ID that OpenAI, Azure OpenAI and xAI use to pair tool output with its call.
  • Conversation model keys — Port the test that conversations and messages created through Eloquent keep their string keys. Hypervel's conversation models already use UUID keys, so upstream's Laravel 12 source change isn't needed.
  • Classifying images — Classify image attachments with TypeSafe and OpenRouter. OpenAI and OpenAI-compatible classification label images given string keys, so questions can refer to them by name. Port the Classifying Images documentation, adapted for these providers and named images, and add the missing table of contents entries.
  • AI workbench dependencies — Update source-map-js to 1.2.2 in the AI workbench.
  • Provider resource IDs — Encode file, store and document IDs as a single URL path segment in the OpenAI, Azure OpenAI, Anthropic, OpenRouter and Gemini gateways, and reject empty and dot-segment IDs. An ID containing /, ?, # or .. could otherwise reach a different endpoint, such as a file ID that deletes a vector store.
  • Raw file responses — Keep the provider's raw HTTP response on stored and retrieved files, so metadata such as a file's size is available. The response is dropped when the file is serialized. Document it.
  • Image response escaping — Escape the MIME type and image data in ImageResponse::toHtml(). Only the alt text was escaped, so quotes in either could break out of the src attribute.
  • Schema normalizer limit — Adopt upstream's 10,000-node limit and message for shared $ref expansion in the schema normalizer. Hypervel already capped each call at 20,000 nodes, and the lower limit bounds the same work. MCP tools still drop the parameters of a schema over the limit.
  • Anthropic server tool text — Separate the text Anthropic writes before and after a server tool, such as a web search, with a blank line, while keeping text split by citations joined. Streamed and final text match.
  • Structured prompt responses — Give prompt() and agent() conditional return types, so static analysis accepts array access on a structured agent's response. Structured agents always return a StructuredAgentResponse, with an empty payload when a run produces none, such as a stopped run or a Bedrock approval pause. Document the empty structured value.
  • Gemini metadata filters — Validate Gemini file search metadata filter keys and wrap every condition in parentheses, since AIP-160 gives OR higher precedence than AND. Hypervel already escaped values and supported whereNotIn(). Document Gemini's key rules.

Additional Hypervel Fixes

  • Deploy the Doctum API docs with pnpm dlx wrangler@4. The Cloudflare action installed Wrangler with pnpm add at the workspace root, which pnpm 12 rejects, so every deployment since the pnpm 12 upgrade failed.
  • Correct the classification state types, which only allowed string keys, so static analysis rejected the list states classification supports. Add a type fixture.
  • Reject a TypeSafe structured state with an attachment named images alongside unnamed attachments. Upstream replaced that attachment's reference with the unnamed images but still uploaded it.
  • Keep integer keys, such as record IDs, in a TypeSafe structured state when images are attached. Upstream merged the images with an array spread, which renumbered those keys.
  • Ignore empty Anthropic text blocks after a server tool. Upstream added a blank line for them that the model never wrote and the stream never sent.
  • Return a structured response for agents with an empty schema too. Upstream returned a plain AgentResponse for them, contradicting the declared return type. Their text remains available.
  • Handle empty Gemini whereIn() and whereNotIn() lists, which produced an invalid empty group. An empty exclusion list is omitted, and an empty inclusion list throws before the request, since Gemini has no portable condition that matches nothing.
  • Ask contributors reconciling upstream changes to explain the non-obvious Hypervel behavior they keep, so later updates can see why it exists.

The affected suites, formatting and static analysis pass. CI will run the full suite and supported service matrix.


Summary by cubic

Syncs upstream Laravel AI updates to Hypervel's AI SDK and fixes the broken Doctum deployment.

The deployment failure: the Cloudflare action installed Wrangler with pnpm add at the workspace root, which pnpm 12 rejects, so every deployment has failed since the pnpm 12 upgrade. It now runs pnpm dlx wrangler@4 instead.

AI changes

  • Dispatches failure events for audio, image, transcription, reranking, classification, embedding, file upload, and store changes, once per failed attempt including attempts failover recovers from.
  • Classifies image attachments with TypeSafe and OpenRouter; OpenAI and OpenAI-compatible classification label images given string keys so questions can refer to them by name.
  • Encodes file, store, and document IDs as single URL path segments in the OpenAI, Azure OpenAI, Anthropic, OpenRouter, and Gemini gateways, and rejects empty and dot-segment IDs.
  • Keeps the provider's raw HTTP response on stored and retrieved files so metadata like file size is available; dropped when serialized.
  • Escapes MIME type and image data in ImageResponse::toHtml().
  • Adopts upstream's 10,000-node schema normalizer limit for shared $ref expansion.
  • Separates Anthropic text written before and after a server tool with a blank line, while keeping citation-split text joined.
  • Gives prompt() and agent() conditional return types so structured agents always return a StructuredAgentResponse.
  • Validates Gemini file search metadata filter keys, wraps every condition in parentheses, and handles empty whereIn() and whereNotIn() lists.
  • Corrects classification state types, rejects a TypeSafe structured state with an attachment named images alongside unnamed attachments, and asks contributors reconciling upstream changes to explain non-obvious Hypervel behavior.
  • Updates source-map-js to 1.2.2 in the AI workbench and documents the new events and behaviors.

Written for commit 403427f. Summary will update on new commits.

View guided diff Turn on auto-fix

Note

Add image classification attachments, failure events, and resource ID encoding to AI gateways

  • Classification gateways (OpenAI, OpenRouter, TypeSafe) now accept image attachments. Named attachments are sent with a text label before the image; OpenRouter uses base64 data URLs and TypeSafe uses referenced assets via the new ResolvesInlineImages concern.
  • All AI operations (classification, embeddings, image, audio, transcription, reranking, file, and store operations) now dispatch a matching failure event before rethrowing, through the shared Provider::dispatchingFailure helper.
  • File and store gateways now encode resource IDs through the new EncodesResourceIds concern, so IDs with path or query characters stay within one URL segment. FileResponse and StoredFileResponse retain the raw provider HTTP response.
  • Structured-output agents now always return StructuredAgentResponse, with empty structured data when no structured output was produced. Anthropic text after tool blocks gets a two-newline separator.
  • Risk: SchemaNormalizer node budget drops from 20,000 to 10,000, so schemas that previously normalized now throw; GeminiProvider::formatMetadataCondition is removed and Gemini metadata filters now reject empty inclusion lists and invalid keys; ImageResponse::toHtml now escapes MIME type and image data.

Macroscope summarized b44851e.

Every Doctum deployment since the pnpm 12 upgrade has failed. The deploy
step used wrangler-action, which installs Wrangler with `pnpm add` at the
workspace root. pnpm 12 rejects that twice: the global
ignore-workspace-root-check setting is no longer supported, and once it
is bypassed the install fails on unapproved esbuild and workerd build
scripts.

Run Wrangler through `pnpm dlx wrangler@4 pages deploy`, passing the
Cloudflare credentials and project name through the step environment.
This installs only Wrangler, honors the workspace's minimum release age
and leaves the checkout untouched. A local run of the command reaches
Wrangler's credentials check.
Ask contributors reconciling upstream changes to add missing
explanations for non-obvious Hypervel behavior they keep, so later syncs
can see why it exists.
Port laravel/ai#1090 (laravel/ai 1.x at d44ae14d17fb). Embeddings, image,
audio, transcription, reranking and classification requests, file
uploads and store changes dispatched a starting and a success event but
nothing on failure, so listeners could not close a failed call.

Add AudioFailed, ImageFailed, TranscriptionFailed, RerankingFailed,
StoringFileFailed, CreatingStoreFailed, AddingFileToStoreFailed and
RemovingFileFromStoreFailed, dispatched through
Provider::dispatchingFailure() for each failed attempt, including
attempts failover recovers from. The existing EmbeddingsFailed and
ClassificationFailed events move onto the same helper. Errors thrown by
success listeners still do not produce failure events.

The helper keeps Hypervel's hasListeners() guard, so passive observers
alone do not trigger optional failure events. The new events type the
provider as the contract, like their starting events. Classifies also
carries laravel/ai#1134's state and attachment docblock types.

Port upstream's operation, file and store event tests and remove the
Hypervel embeddings tests they duplicate, keeping the check that a
failed attempt dispatches no success event. Formatting, static analysis
and the AI and Sentry suites pass.
Port laravel/ai#1133 (laravel/ai 1.x at d44ae14d17fb). A failed turn
replays its unanswered tool calls with an interrupted placeholder
result. Hypervel already kept the stored result ID, so OpenAI, Azure
OpenAI and xAI receive the call_id their output is paired by. Match
upstream's named arguments and port its regression test, which fails
without the result ID.
Port laravel/ai#1137's test (laravel/ai 1.x at d44ae14d17fb). Its source
change restores $incrementing for Laravel 12, which lacks the
WithoutIncrementing attribute. Hypervel's conversation models use
HasUuids and are already non-incrementing, so only the test applies: a
conversation and message created through Eloquent keep their string
keys.
Port laravel/ai#1134 (laravel/ai 1.x at d44ae14d17fb). TypeSafe and
OpenRouter classification rejected image attachments. TypeSafe now sends
images as referenced assets with its images header, OpenRouter appends
data URL image parts to the state, and OpenAI and OpenAI-compatible
classification label images given string keys, so questions can refer
to them by name. ResolvesInlineImages shares the inline image checks.

Fix two defects. Every classification state docblock said
array<string, mixed>, although list states are supported, so list states
failed static analysis; widen them to array<array-key, mixed>, pinned by
a types fixture. TypeSafe also dropped an attachment named "images" from
a structured state when unnamed attachments were given, while still
uploading it; that combination is now rejected before any request.

Port upstream's TypeSafe, OpenRouter, OpenAI, fake and integration tests
with the updated fixture images and the text-only classifier helper.
ClassificationFakeTest also drops the classification failure assertions
that duplicate laravel/ai#1090's tests. Formatting, static analysis and
the AI suite pass.
Port laravel/ai#1142 (laravel/ai 1.x at d44ae14d17fb), updating the
workbench lockfile to source-map-js 1.2.2 with npm.
Port laravel/ai#1140 and laravel/ai#1146 (laravel/ai 1.x at
d44ae14d17fb), which change the same file gateways.

File, store and document IDs went into request paths unchanged, so an ID
containing "/", "?", "#" or ".." could reach a different endpoint, such
as a file ID that deletes a vector store. EncodesResourceIds now encodes
each ID as one path segment and rejects empty and dot-segment IDs, in
the OpenAI, Azure OpenAI, Anthropic, OpenRouter and Gemini gateways.
Gemini still strips its own prefixes, but a document path from another
store no longer names that document.

Stored and retrieved file responses now keep the provider's raw HTTP
response, which is dropped when they are serialized, so provider
metadata such as a file's size is available.

Port upstream's resource ID and gateway tests. Formatting, static
analysis and the AI suite pass.
Port laravel/ai#1149 (laravel/ai 1.x at d44ae14d17fb).
ImageResponse::toHtml() escaped only the alt text, so a MIME type or
image payload containing quotes could break out of the src attribute.
Escape both, with upstream's test.
Port laravel/ai#1145 (laravel/ai 1.x at d44ae14d17fb). Inlining a $ref
shared by sibling properties at every use can expand a small MCP tool
schema exponentially. Hypervel's normalizer already bounded each call;
adopt upstream's 10,000-node limit and message, which bound the same
work. McpTool::schema() still drops the parameters of a schema that
exceeds the limit. Port upstream's normalizer and MCP tool tests,
replacing the Hypervel test they duplicate.
Port laravel/ai#1143 (laravel/ai 1.x at d44ae14d17fb). Text an Anthropic
response wrote before and after a server tool, such as a web search, ran
together. Put a blank line between those remarks unless the line already
breaks there, and keep citation-split text joined. Streaming adds the
separator to the first delta after the tool, while replayed blocks keep
their original text, so streamed and final text match. Port upstream's
response and streaming tests.
Port laravel/ai#1138 (laravel/ai 1.x at d44ae14d17fb). prompt() was
declared as returning AgentResponse, so static analysis rejected array
access on a structured agent's response. Promptable::prompt() and agent()
now carry conditional return types, pinned by a types fixture.

Structured agents now always return a StructuredAgentResponse, with an
empty payload when a run produces none, such as a stopped run, a string
fake or a Bedrock approval pause. Unlike upstream, the response type is
chosen by HasStructuredOutput rather than by a filled schema, so agents
with an empty schema also match the declared type; their text remains
available and the response casts to an empty payload.

Port upstream's tests and fixture agent, adapting the empty-schema test
to the corrected behavior. Formatting, static analysis and the AI suite
pass.
Port laravel/ai#1144 (laravel/ai 1.x at d44ae14d17fb). Gemini filter keys
were interpolated unchecked, and conditions were not grouped although
AIP-160 gives OR higher precedence than AND. Hypervel already escaped
values and supported whereNotIn(); adopt upstream's formatter, which
also validates keys and parenthesizes every condition.

Also fix empty whereIn() and whereNotIn() lists, which produced an
invalid empty group. An empty exclusion list is now omitted, and an
empty inclusion list throws before the request, since Gemini has no
portable condition that matches nothing.

Port upstream's tests, folding Hypervel's duplicate filter tests into
them.
Document laravel/ai#1090, #1134, #1138, #1144 and #1146 in the AI SDK
docs, porting laravel/docs 13.x at 12080c72699b where it covers them:

- list the new failure events and when they are dispatched
- port the Classifying Images section, adapted for the providers that
  now accept images and for named images, with its table of contents
  entry and upstream's missing Reranking Collections and Using Stored
  Files in Conversations entries
- note that a structured run without a payload returns an empty
  structured value
- note Gemini's metadata filter key and empty-list rules
- show a stored file's raw HTTP response
@coderabbitai

coderabbitai Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: hypervel/components-backup/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 57996092-08db-46af-b243-e1dbe60f3017

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Sync AI SDK improvements and restore Doctum deployment

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add AI failure events, image classification, structured-response guarantees, and provider response
 metadata.
• Harden resource IDs, Gemini filters, image HTML, schema expansion, and provider text handling.
• Expand regression coverage and documentation; restore Doctum deployment under pnpm 12.
Diagram

graph TD
  A["AI SDK callers"] --> B["Provider operations"] --> C["Provider gateways"] --> D["External AI APIs"]
  B --> E["Failure events"]
  C --> F["AI responses"]
  A --> G["Agent prompting"] --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Dispatch failures in a generic gateway wrapper
  • ➕ Could centralize failure handling across provider operations.
  • ➖ Would need additional context to construct each operation-specific event.
  • ➖ Could blur the boundary between gateway failures and exceptions from success listeners.
2. Validate provider IDs with a restricted character allowlist
  • ➕ Would reject suspicious IDs before URL construction.
  • ➖ Could reject legitimate provider-generated IDs and still require provider-specific prefix handling.

Recommendation: Keep the shared provider failure helper and single-segment ID encoder. They centralize the cross-provider invariants while leaving operation-specific event context and provider-specific resource prefixes explicit. Review encoded-ID behavior against each provider API as part of the service matrix.

Files changed (88) +2446 / -316

Enhancement (26) +580 / -84
AddingFileToStoreFailed.phpAdd failed store-file addition event +23/-0

Add failed store-file addition event

• Carries the invocation, provider, store and file IDs, and original exception.

src/ai/src/Events/AddingFileToStoreFailed.php

AudioFailed.phpAdd failed audio generation event +27/-0

Add failed audio generation event

• Exposes the failed attempt's invocation, provider, model, prompt, and exception.

src/ai/src/Events/AudioFailed.php

CreatingStoreFailed.phpAdd failed store creation event +27/-0

Add failed store creation event

• Preserves the requested store settings and exception alongside invocation and provider information.

src/ai/src/Events/CreatingStoreFailed.php

ImageFailed.phpAdd failed image generation event +27/-0

Add failed image generation event

• Captures the invocation, provider, model, image prompt, and exception.

src/ai/src/Events/ImageFailed.php

RemovingFileFromStoreFailed.phpAdd failed store-file removal event +23/-0

Add failed store-file removal event

• Captures the store and document IDs, provider, invocation, and exception.

src/ai/src/Events/RemovingFileFromStoreFailed.php

RerankingFailed.phpAdd failed reranking event +27/-0

Add failed reranking event

• Exposes the failed reranking attempt's provider, model, prompt, invocation, and exception.

src/ai/src/Events/RerankingFailed.php

StoringFileFailed.phpAdd failed file storage event +23/-0

Add failed file storage event

• Exposes the uploaded file, provider, invocation, and original exception.

src/ai/src/Events/StoringFileFailed.php

TranscriptionFailed.phpAdd failed transcription event +27/-0

Add failed transcription event

• Captures the transcription prompt and failed attempt's provider, model, invocation, and exception.

src/ai/src/Events/TranscriptionFailed.php

ResolvesInlineImages.phpShare inline image validation across classifiers +43/-0

Share inline image validation across classifiers

• Resolves uploaded or SDK images to content and MIME type, rejecting unsupported formats and non-inline attachments.

src/ai/src/Gateway/Concerns/ResolvesInlineImages.php

OpenAiClassificationGateway.phpLabel named images in OpenAI classification +22/-31

Label named images in OpenAI classification

• Adds each string-keyed image's name before its image part. Reuses shared inline-image validation and broadens accepted input annotations.

src/ai/src/Gateway/OpenAi/OpenAiClassificationGateway.php

OpenRouterClassificationGateway.phpClassify inline images through OpenRouter +77/-1

Classify inline images through OpenRouter

• Adds named image parts to classification state, supports provider image options, and validates inline JPEG, PNG, and WebP content.

src/ai/src/Gateway/OpenRouter/OpenRouterClassificationGateway.php

TypeSafeGateway.phpClassify TypeSafe image assets with named references +96/-1

Classify TypeSafe image assets with named references

• Uploads supported inline images as referenced assets and places references into string, list, or structured states. Rejects conflicting structured-state keys, including a named 'images' attachment alongside unnamed images.

src/ai/src/Gateway/TypeSafeGateway.php

Promptable.phpDeclare conditional prompt response types +4/-0

Declare conditional prompt response types

• Annotates 'prompt()' to return a structured response for agents implementing structured output.

src/ai/src/Promptable.php

Classifies.phpShare classification failure dispatch +7/-14

Share classification failure dispatch

• Routes gateway exceptions through the common failure helper while keeping success-event dispatch outside its catch boundary. Updates supported input annotations.

src/ai/src/Providers/Concerns/Classifies.php

GeneratesAudio.phpEmit audio failure events +13/-8

Emit audio failure events

• Dispatches 'AudioFailed' for failed gateway attempts without treating success-listener exceptions as generation failures.

src/ai/src/Providers/Concerns/GeneratesAudio.php

GeneratesImages.phpEmit image generation failure events +14/-9

Emit image generation failure events

• Wraps image gateway calls to dispatch 'ImageFailed' on failed attempts.

src/ai/src/Providers/Concerns/GeneratesImages.php

GeneratesTranscriptions.phpEmit transcription failure events +13/-8

Emit transcription failure events

• Dispatches 'TranscriptionFailed' when the transcription gateway throws.

src/ai/src/Providers/Concerns/GeneratesTranscriptions.php

ManagesFiles.phpEmit file upload failure events +6/-1

Emit file upload failure events

• Dispatches 'StoringFileFailed' for failed provider uploads while preserving the original exception.

src/ai/src/Providers/Concerns/ManagesFiles.php

ManagesStores.phpEmit store operation failure events +16/-3

Emit store operation failure events

• Covers failed store creation and file addition or removal with events carrying the corresponding operation context.

src/ai/src/Providers/Concerns/ManagesStores.php

Reranks.phpEmit reranking failure events +13/-8

Emit reranking failure events

• Dispatches 'RerankingFailed' for gateway failures before rethrowing.

src/ai/src/Providers/Concerns/Reranks.php

OpenRouterProvider.phpAdvertise OpenRouter image classification +8/-0

Advertise OpenRouter image classification

• Marks classification attachments as supported so images reach the OpenRouter gateway.

src/ai/src/Providers/OpenRouterProvider.php

Provider.phpCentralize optional per-attempt failure events +27/-0

Centralize optional per-attempt failure events

• Adds a gateway-call helper that dispatches a failure event only when it has listeners, then rethrows the original exception.

src/ai/src/Providers/Provider.php

TypeSafeProvider.phpAdvertise TypeSafe image classification +8/-0

Advertise TypeSafe image classification

• Enables classification attachments for the TypeSafe provider.

src/ai/src/Providers/TypeSafeProvider.php

FileResponse.phpExpose retrieved file's raw HTTP response +4/-0

Expose retrieved file's raw HTTP response

• Adds the existing raw-response concern to retrieved file responses.

src/ai/src/Responses/FileResponse.php

StoredFileResponse.phpExpose stored file's raw HTTP response +3/-0

Expose stored file's raw HTTP response

• Adds access to the upload response through the existing raw-response concern.

src/ai/src/Responses/StoredFileResponse.php

functions.phpType ad-hoc agent factory by schema presence +2/-0

Type ad-hoc agent factory by schema presence

• Declares the structured anonymous agent return type when 'agent()' receives a schema.

src/ai/src/functions.php

Bug fix (21) +181 / -98
Classification.phpAccept list states and named classification attachments in types +2/-2

Accept list states and named classification attachments in types

• Broadens PHPDoc to describe list-shaped states and string-keyed attachments.

src/ai/src/Classification.php

ClassificationGateway.phpBroaden classification gateway argument types +2/-2

Broaden classification gateway argument types

• Updates gateway PHPDoc for list states and named image attachments.

src/ai/src/Contracts/Gateway/ClassificationGateway.php

ClassificationProvider.phpBroaden classification provider argument types +2/-2

Broaden classification provider argument types

• Aligns the provider contract's state and attachment annotations with supported inputs.

src/ai/src/Contracts/Providers/ClassificationProvider.php

AnthropicFileGateway.phpProtect Anthropic file paths and retain raw responses +7/-5

Protect Anthropic file paths and retain raw responses

• Encodes file IDs as individual path segments and attaches raw HTTP responses to stored and retrieved files.

src/ai/src/Gateway/Anthropic/AnthropicFileGateway.php

HandlesTextStreaming.phpSeparate streamed text around Anthropic server tools +11/-9

Separate streamed text around Anthropic server tools

• Adds a separator to streamed text after server-tool blocks while avoiding an extra separator when text already ends or begins with a newline.

src/ai/src/Gateway/Anthropic/Concerns/HandlesTextStreaming.php

ParsesTextResponses.phpSeparate final Anthropic text around server tools +40/-3

Separate final Anthropic text around server tools

• Joins citation-split text directly but inserts a blank line across server-tool boundaries. Shares tool-block recognition and separator rules with streaming.

src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php

BedrockTextGateway.phpKeep structured shape during Bedrock approval pauses +1/-1

Keep structured shape during Bedrock approval pauses

• Decodes structured output from the available output when a structured run has no separate structured payload.

src/ai/src/Gateway/Bedrock/BedrockTextGateway.php

AnswersQuestions.phpType list states and named classification images +2/-2

Type list states and named classification images

• Broadens shared classification gateway annotations to match supported state and attachment keys.

src/ai/src/Gateway/Concerns/AnswersQuestions.php

EncodesResourceIds.phpEncode provider resource IDs as safe path segments +24/-0

Encode provider resource IDs as safe path segments

• Introduces shared raw URL encoding and rejects empty, current-directory, and parent-directory IDs.

src/ai/src/Gateway/Concerns/EncodesResourceIds.php

FakeClassificationGateway.phpAlign fake classification input annotations +2/-2

Align fake classification input annotations

• Types list states and string-keyed attachments accepted by classification fakes.

src/ai/src/Gateway/FakeClassificationGateway.php

GeminiFileGateway.phpNormalize Gemini file IDs safely +16/-5

Normalize Gemini file IDs safely

• Keeps Gemini's file prefix while encoding the remaining ID as one segment. Retains raw responses on file retrieval and upload.

src/ai/src/Gateway/Gemini/GeminiFileGateway.php

GeminiStoreGateway.phpRebuild Gemini store and document paths safely +6/-18

Rebuild Gemini store and document paths safely

• Normalizes supported resource prefixes, then encodes store, file, and document IDs as their intended path segments.

src/ai/src/Gateway/Gemini/GeminiStoreGateway.php

OpenAiFileGateway.phpProtect OpenAI file paths and retain raw responses +7/-5

Protect OpenAI file paths and retain raw responses

• Encodes retrieved and deleted file IDs and preserves upload and retrieval HTTP responses on returned file objects.

src/ai/src/Gateway/OpenAi/OpenAiFileGateway.php

OpenAiStoreGateway.phpProtect OpenAI vector-store resource paths +6/-4

Protect OpenAI vector-store resource paths

• Encodes store and document IDs wherever they appear in vector-store request URLs.

src/ai/src/Gateway/OpenAi/OpenAiStoreGateway.php

OpenRouterFileGateway.phpProtect OpenRouter file paths and retain raw responses +7/-5

Protect OpenRouter file paths and retain raw responses

• Encodes file IDs in retrieval and deletion URLs and retains the provider response for stored and retrieved files.

src/ai/src/Gateway/OpenRouter/OpenRouterFileGateway.php

PendingClassification.phpType supported pending-classification inputs +2/-2

Type supported pending-classification inputs

• Updates state and attachment annotations for list inputs and named images.

src/ai/src/PendingResponses/PendingClassification.php

ClassificationPrompt.phpAlign classification prompt input annotations +2/-2

Align classification prompt input annotations

• Documents list states and integer- or string-keyed attachments.

src/ai/src/Prompts/ClassificationPrompt.php

GeneratesText.phpAlways return structured responses for structured agents +9/-2

Always return structured responses for structured agents

• Bases response type on the agent's structured-output contract, including empty schemas and runs without a structured payload.

src/ai/src/Providers/Concerns/GeneratesText.php

GeminiProvider.phpValidate and group Gemini metadata filters +25/-19

Validate and group Gemini metadata filters

• Restricts metadata keys, parenthesizes every condition to preserve precedence, and handles empty inclusion or exclusion lists explicitly.

src/ai/src/Providers/GeminiProvider.php

ImageResponse.phpEscape image HTML source attributes +2/-2

Escape image HTML source attributes

• Escapes the MIME type and image data as well as alt text before rendering an HTML image tag.

src/ai/src/Responses/ImageResponse.php

SchemaNormalizer.phpLimit expanded schemas to 10,000 nodes +6/-6

Limit expanded schemas to 10,000 nodes

• Lowers the shared-reference expansion budget and adopts the corresponding error message.

src/ai/src/Schema/SchemaNormalizer.php

Refactor (2) +10 / -17
GeneratesEmbeddings.phpUse shared embeddings failure dispatch +5/-12

Use shared embeddings failure dispatch

• Replaces local exception dispatch with the common per-attempt failure helper.

src/ai/src/Providers/Concerns/GeneratesEmbeddings.php

DatabaseConversationStore.phpName interrupted tool-result arguments +5/-5

Name interrupted tool-result arguments

• Uses named arguments when reconstructing interrupted tool results, retaining the result ID used to pair output with its call.

src/ai/src/Storage/DatabaseConversationStore.php

Tests (34) +1612 / -108
AgentFakeTest.phpTest structured fake responses without payloads +16/-3

Test structured fake responses without payloads

• Verifies structured agents retain their response type for plain-text fakes and empty schemas.

tests/Ai/Feature/AgentFakeTest.php

AgentMiddlewareTest.phpTest stopped structured agent responses +4/-3

Test stopped structured agent responses

• Asserts that stopping before the first step returns an empty structured response.

tests/Ai/Feature/AgentMiddlewareTest.php

ClassificationFakeTest.phpAlign classification fake tests with image support +9/-11

Align classification fake tests with image support

• Updates attachment-capability coverage and retains checks for failure sequencing and success-listener behavior.

tests/Ai/Feature/ClassificationFakeTest.php

ConversationRelationshipTest.phpTest Eloquent conversation string keys +29/-0

Test Eloquent conversation string keys

• Checks that newly created conversations and messages keep their string identifiers.

tests/Ai/Feature/ConversationRelationshipTest.php

EmbeddingsFakeTest.phpConsolidate embeddings event coverage +1/-27

Consolidate embeddings event coverage

• Removes a redundant success-listener failure test now covered by shared operation-event tests.

tests/Ai/Feature/EmbeddingsFakeTest.php

FailedTurnRecordingTest.phpTest replayed interrupted tool result IDs +48/-0

Test replayed interrupted tool result IDs

• Verifies that replay after a failed turn retains the identifier used to match tool output to its call.

tests/Ai/Feature/FailedTurnRecordingTest.php

FileAndStoreEventsTest.phpCover failure events for files and stores +93/-0

Cover failure events for files and stores

• Checks that four failed operations dispatch events mirroring their starting-event context and rethrow the gateway exception.

tests/Ai/Feature/FileAndStoreEventsTest.php

OperationEventsTest.phpCover AI operation failure-event lifecycle +174/-0

Cover AI operation failure-event lifecycle

• Exercises generation, reranking, and classification failures and successes, recovered failover, and exceptions thrown by success listeners.

tests/Ai/Feature/OperationEventsTest.php

ProviderResourceIdTest.phpTest provider resource ID isolation +172/-0

Test provider resource ID isolation

• Checks single-segment encoding across file, store, and document endpoints and rejects empty or dot-segment IDs before requests.

tests/Ai/Feature/ProviderResourceIdTest.php

FileGatewayTest.phpTest Anthropic file response and ID handling +14/-0

Test Anthropic file response and ID handling

• Checks retained raw file responses and encoded deletion IDs.

tests/Ai/Feature/Providers/Anthropic/FileGatewayTest.php

ProviderToolCallsTest.phpTest final text around Anthropic server tools +72/-0

Test final text around Anthropic server tools

• Verifies separation across server-tool blocks without splitting citation text or adding unwanted newlines.

tests/Ai/Feature/Providers/Anthropic/ProviderToolCallsTest.php

StreamingTest.phpTest streamed Anthropic tool-text boundaries +68/-0

Test streamed Anthropic tool-text boundaries

• Checks stream text separation and avoids duplicate spacing when model text already contains a newline.

tests/Ai/Feature/Providers/Anthropic/StreamingTest.php

FileGatewayTest.phpTest Azure OpenAI encoded file IDs +13/-0

Test Azure OpenAI encoded file IDs

• Asserts that file deletion treats unsafe IDs as one path segment.

tests/Ai/Feature/Providers/AzureOpenAi/FileGatewayTest.php

StoreGatewayTest.phpTest Azure store and document ID encoding +13/-0

Test Azure store and document ID encoding

• Verifies that file removal safely encodes both resource identifiers.

tests/Ai/Feature/Providers/AzureOpenAi/StoreGatewayTest.php

ToolCallLoopTest.phpTest structured Bedrock approval pauses +27/-0

Test structured Bedrock approval pauses

• Checks that a run paused for tool approval keeps a structured response with an empty payload.

tests/Ai/Feature/Providers/Bedrock/ToolCallLoopTest.php

FileGatewayTest.phpTest Gemini file prefix and raw responses +29/-0

Test Gemini file prefix and raw responses

• Covers retained raw responses, encoded deletion IDs, and rejection of prefixed dot segments.

tests/Ai/Feature/Providers/Gemini/FileGatewayTest.php

StoreGatewayTest.phpTest Gemini store and document paths +38/-0

Test Gemini store and document paths

• Checks encoded store and document IDs and safe reconstruction of full document paths.

tests/Ai/Feature/Providers/Gemini/StoreGatewayTest.php

ToolMappingTest.phpTest Gemini metadata filter safety +137/-33

Test Gemini metadata filter safety

• Covers escaped values, invalid keys, condition grouping, exclusion lists, and both empty-list behaviors through provider requests.

tests/Ai/Feature/Providers/Gemini/ToolMappingTest.php

ClassificationTest.phpTest named OpenAI classification images +10/-4

Test named OpenAI classification images

• Checks that remote and stored images are sent inline after their name labels.

tests/Ai/Feature/Providers/OpenAi/ClassificationTest.php

FileGatewayTest.phpTest OpenAI file encoding and raw responses +70/-0

Test OpenAI file encoding and raw responses

• Covers unsafe and dot-segment IDs, raw responses on retrieval and upload, and omission of raw responses during serialization.

tests/Ai/Feature/Providers/OpenAi/FileGatewayTest.php

StoreGatewayTest.phpTest OpenAI vector-store ID encoding +25/-0

Test OpenAI vector-store ID encoding

• Verifies safe store deletion and store-file removal URLs.

tests/Ai/Feature/Providers/OpenAi/StoreGatewayTest.php

ClassificationTest.phpTest OpenRouter image classification +85/-6

Test OpenRouter image classification

• Covers named data-URL image parts, list and structured states, WebP support, and rejection of unsupported attachments.

tests/Ai/Feature/Providers/OpenRouter/ClassificationTest.php

FileGatewayTest.phpTest OpenRouter raw file responses +2/-0

Test OpenRouter raw file responses

• Asserts that retrieval and upload expose their provider HTTP responses.

tests/Ai/Feature/Providers/OpenRouter/FileGatewayTest.php

ClassificationTest.phpTest TypeSafe image asset mapping +208/-0

Test TypeSafe image asset mapping

• Exercises image assets, named and unnamed references, state shapes, supported formats, and conflicting-key rejection.

tests/Ai/Feature/Providers/TypeSafe/ClassificationTest.php

ToolApprovalResumeTest.phpTest structured responses after rejected approval +35/-0

Test structured responses after rejected approval

• Verifies that resuming and rejecting a structured agent's tool approval returns an empty structured response.

tests/Ai/Feature/ToolApprovalResumeTest.php

RememberingStructuredApprovableAgent.phpAdd structured approval-resume fixture +46/-0

Add structured approval-resume fixture

• Provides a structured, conversation-backed agent with an approvable tool for resume tests.

tests/Ai/Fixtures/Agents/RememberingStructuredApprovableAgent.php

helpers.phpAdd text-only classification test provider +19/-0

Add text-only classification test provider

• Registers a provider that rejects attachments so capability checks can be tested independently of TypeSafe's new image support.

tests/Ai/Fixtures/helpers.php

ImageResponseTest.phpTest HTML escaping of image attributes +3/-3

Test HTML escaping of image attributes

• Checks escaping of MIME type and image content in addition to alt text.

tests/Ai/Unit/Responses/ImageResponseTest.php

SchemaNormalizerTest.phpTest shared-reference schema expansion limit +54/-18

Test shared-reference schema expansion limit

• Verifies references expand at each use and schemas exceeding the 10,000-node budget fail.

tests/Ai/Unit/Schema/SchemaNormalizerTest.php

McpToolTest.phpTest oversized MCP parameter fallback +19/-0

Test oversized MCP parameter fallback

• Checks that an over-budget parameter schema is dropped without discarding the whole tool.

tests/Ai/Unit/Tools/McpToolTest.php

ClassificationTest.phpExercise named images against classification providers +18/-0

Exercise named images against classification providers

• Adds service-backed coverage for referring to classification images by name.

tests/Integration/Ai/ClassificationTest.php

Providers.phpInclude image-capable classification providers +2/-0

Include image-capable classification providers

• Extends provider fixtures used by the named-image integration test.

tests/Integration/Ai/Fixtures/Providers.php

Classification.phpAdd classification static-type fixtures +16/-0

Add classification static-type fixtures

• Asserts accepted string, structured, and list states, including named and unnamed image attachments.

types/Ai/Classification.php

Promptable.phpAdd conditional agent-response type fixtures +43/-0

Add conditional agent-response type fixtures

• Asserts ordinary and structured return types for concrete agents and the ad-hoc 'agent()' factory.

types/Ai/Promptable.php

Documentation (4) +58 / -3
AGENTS.mdExplain retained behavior during upstream reconciliations +1/-1

Explain retained behavior during upstream reconciliations

• Asks contributors to document non-obvious Hypervel behavior preserved while syncing upstream changes.

AGENTS.md

ClassificationFailed.phpClarify classification failure event timing +3/-0

Clarify classification failure event timing

• Documents that the event is emitted per failed attempt, including attempts recovered by failover.

src/ai/src/Events/ClassificationFailed.php

EmbeddingsFailed.phpClarify embeddings failure event timing +3/-0

Clarify embeddings failure event timing

• Documents per-attempt dispatch, including recovered failovers.

src/ai/src/Events/EmbeddingsFailed.php

ai-sdk.mdDocument new AI capabilities and constraints +51/-2

Document new AI capabilities and constraints

• Documents image classification, raw file responses, failure events, empty structured results, and Gemini filter rules. Adds missing table-of-contents entries.

src/docs/ai-sdk.md

Other (1) +5 / -6
doctum.ymlDeploy Doctum through pnpm dlx +5/-6

Deploy Doctum through pnpm dlx

• Replaces the Cloudflare action's workspace-root Wrangler installation with a direct Wrangler 4 invocation. Supplies Cloudflare credentials and project name through step environment variables.

.github/workflows/doctum.yml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Gemini imports target the wrong file 🐞 Bug ≡ Correctness
Description
normalizeFileId() now URL-encodes the ID even when addFile() places the resulting name in the
JSON fileName field rather than a URL path. When a file name returned by Gemini contains a
reserved character, importing that stored file sends a different resource name from the one the
provider returned.
Code

src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[223]

+        return 'files/' . $this->resourceId(Str::chopStart($fileId, 'files/'));
Evidence
addFile() assigns the normalized value to the JSON fileName field, while file uploads expose the
provider's file.name verbatim as the stored ID. The existing import test asserts that fileName
is a provider resource name, not a URL.

src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[87-96]
src/ai/src/Gateway/Gemini/GeminiFileGateway.php[69-73]
tests/Ai/Feature/Providers/Gemini/StoreGatewayTest.php[126-146]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Gemini store imports use a provider resource name in a JSON field, but the shared normalization now URL-encodes its file ID.
## Fix Focus Areas
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[87-96]
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[218-224]
## Recommended Fix
Separate normalization of provider resource names from URL-path encoding. Send the original `files/` resource name in the import request body, reserving segment encoding for IDs interpolated into URLs; add a test with a reserved character.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Gemini cannot remove full-path documents 🐞 Bug ≡ Correctness
Description
normalizeDocumentId() compares a supplied full document path against a store prefix whose ID has
already been URL-encoded. If the store ID contains a reserved character, the raw full path does not
match that prefix and the entire path is encoded and appended as though it were the document ID.
Code

src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[231]

+        return "{$storeId}/documents/" . $this->resourceId(Str::chopStart($documentId, ["{$storeId}/documents/", 'documents/', 'files/']));
Evidence
removeFile() normalizes $storeId before passing it to normalizeDocumentId(). The gateway
already supports full document paths, but for a raw store ID such as a/b, its encoded prefix
fileSearchStores/a%2Fb/documents/ cannot match the supplied path
fileSearchStores/a/b/documents/doc1.

src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[162-172]
src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[209-232]
tests/Ai/Feature/Providers/Gemini/StoreGatewayTest.php[315-325]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Full Gemini document paths fail to normalize when the store ID contains characters that are encoded for the request URL.
## Fix Focus Areas
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[162-172]
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[229-232]
## Recommended Fix
Compare and strip the document's full-path prefix using the unencoded store resource name before encoding the store and document IDs independently for the URL. Test a full document path with a reserved character in its store ID.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Streamed text can lack a final blank line ✓ Resolved
Description
extractText prepends the separator to any text block after a server tool, including an empty one,
and clears $afterTool; the streaming path skips empty text_deltas and keeps $afterTool set
until a non-empty delta. When the only text block after a server tool is empty and nothing follows,
the final response text ends with an extra "\n\n" that the stream never emitted, breaking the stated
streamed/final parity.
Code

src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php[R117-120]

+            if ($type === 'text') {
+                $blockText = (string) ($block['text'] ?? '');
+                $text .= ($afterTool && $text !== '' ? $this->textSeparator($text, $blockText) : '') . $blockText;
+                $afterTool = false;
Evidence
In extractText, a block with text '' still evaluates textSeparator($text, ''), which returns
"\n\n" since '' doesn't start with a newline, and appends it. In the stream, the separator is only
computed inside if ($textDelta !== ''), so an empty block emits nothing.

src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php[109-127]
src/ai/src/Gateway/Anthropic/Concerns/HandlesTextStreaming.php[193-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Anthropic final text adds a "\n\n" separator for an empty text block after a server tool, while the stream emits nothing, so the two texts differ.
## Fix Focus Areas
- src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php[117-120]
## Recommended Fix
Inside the `$type === 'text'` branch, `continue` when `$blockText === ''` before computing the separator or resetting `$afterTool`, mirroring the streaming path's `$textDelta !== ''` guard.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can open the rule or relationship behind a finding in one click

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/ai/src/Gateway/Gemini/GeminiStoreGateway.php
};

return "{$storeId}/documents/{$documentId}";
return "{$storeId}/documents/" . $this->resourceId(Str::chopStart($documentId, ["{$storeId}/documents/", 'documents/', 'files/']));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Gemini cannot remove full-path documents 🐞 Bug ≡ Correctness

normalizeDocumentId() compares a supplied full document path against a store prefix whose ID has
already been URL-encoded. If the store ID contains a reserved character, the raw full path does not
match that prefix and the entire path is encoded and appended as though it were the document ID.
Agent Prompt
## Issue description
Full Gemini document paths fail to normalize when the store ID contains characters that are encoded for the request URL.
## Fix Focus Areas
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[162-172]
- src/ai/src/Gateway/Gemini/GeminiStoreGateway.php[229-232]
## Recommended Fix
Compare and strip the document's full-path prefix using the unencoded store resource name before encoding the store and document IDs independently for the URL. Test a full document path with a reserved character in its store ID.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping this. Gemini file search store IDs only contain lowercase letters, digits and dashes, per the FileSearchStore resource docs (https://ai.google.dev/api/file-search/file-search-stores#FileSearchStore), so encoding never changes a real store ID and the full-path prefix always matches.

Comment thread src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php
@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5 Tier: plus

[High impact] The PR appears safe to merge; both earlier findings are fixed.

Summary

Updates the AI SDK and switches Doctum deployment to pnpm dlx wrangler@4.

The latest changes address both earlier findings:

  • Image attachments can now be included in classification requests.
  • AI operations now emit failure events when provider calls fail.
  • Provider resource IDs now stay within a single URL path segment.
  • Structured agents keep a structured response shape when a run has no data.
  • Gemini file searches validate and group metadata filters consistently.
  • Anthropic separates text around server tools with a blank line.
  • Stored and retrieved files expose the provider's raw HTTP response.
  • Image HTML escapes both parts of its source attribute.
  • Schema normalization stops after 10,000 expanded nodes.
  • Replayed interrupted tool calls keep the ID their provider pairs with output.
  • Conversation and message creation tests preserve string keys.
  • Doctum deploys its API docs with a workspace-safe Wrangler command.

Both fixes have regression tests. No new actionable issues were found. Tests were inspected, not run.

Reviews (2) · Last reviewed commit: "Ignore empty Anthropic text blocks after..." · Reviewed by Greptile

Comment thread src/ai/src/Gateway/TypeSafeGateway.php Outdated
Comment thread src/ai/src/Gateway/Anthropic/Concerns/ParsesTextResponses.php
laravel/ai#1134 (laravel/ai 1.x at d44ae14d17fb) merged image references
into a structured TypeSafe state with an array spread, which renumbers
integer keys. PHP stores numeric string keys such as record IDs as
integers, so a state keyed by IDs lost them once images were attached.
Merge with a union instead; the existing duplicate-key check already
rejects overlaps, so no state value is replaced. Add an integer-key case
to the image reference states. Formatting, static analysis and the AI
suite pass.
laravel/ai#1143 (laravel/ai 1.x at d44ae14d17fb) added a blank line
before an empty text block that followed a server tool, so the final
text gained a separator the model never wrote. The block also used up
the pending separator, so a following line break was doubled. Streaming
skips empty deltas, so streamed and final text differed. Skip empty
blocks before applying the separator, as streaming does. Cover an empty
block at the end, before text and before text that starts a new line.
Formatting, static analysis and the AI suite pass.
@binaryfire
binaryfire merged commit fd8e131 into 0.4 Oct 20, 2026
53 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.

1 participant