Repository navigation
Sync upstream updates: AI tool failure recovery, raw responses and Inertia QUERY redirects - #100
Conversation
…reranking responses These responses kept no reference to the provider's HTTP response, so provider-specific data such as OpenRouter's usage cost was out of reach. Each response now uses HasRawResponse, and every HTTP gateway for these operations attaches the response it parsed. Individually cached embeddings carry the fresh request's response. Bedrock (AWS SDK) and the fakes leave it null, as upstream does, and serialization still drops it. Ports laravel/ai#1181, laravel/ai#1180, laravel/ai#1179 and laravel/ai#1182 at c8495608a1. They share the gateways and ResponseSerializationTest. Their ai-sdk.md change lands with the later agent-run commit in this batch, which shares that page. Ported the OpenAI, OpenRouter, Cohere, Jina and Voyage AI raw-response tests, EmbeddingsCacheTest's raw-response test in both cache modes and the four serialization tests. tests/Ai passes.
Boolean questions accept array instructions, but an array criterion was interpolated into the OpenAI decision prompt as "Array" with a conversion warning. predicateInstructions() now passes each criterion through toText(), so arrays are sent as JSON text, and the Boolean docblock allows array criteria. Port of laravel/ai#1202 at c8495608a1. Ported the OpenAI ClassificationTest array-criteria test and the integration testBooleanCriteriaMayBeGivenAsArrays cases. The docs already allow array criteria. The OpenAI ClassificationTest file passes.
…urn text These upstream changes share the text generation loop, the Anthropic stream, the conversation store, src/docs/ai-sdk.md and their tests, so they land together. Ported from laravel/ai at c8495608a1. laravel/ai#1162: a streamed turn that paused for approval lost its pre-pause text on resume. Ported TextDelta::join(). Unlike upstream, resumePausedRow() joins the resumed text to the paused content instead of rejoining every stored step. Rejoining split a continuation without tools, such as a pause_turn, that the stream had shown as one paragraph. Prompted resumes are unchanged. Ported the prompted/streamed resume cases and the double-pause test, and added a regression test for a continued step. laravel/ai#1190: agent fakes can resume tool approvals, so approved tools run and rejected tools get a denied result before the next faked response. Ported pausedForApproval(), resumesApproval() and the upstream resumableApprovalFor()/approvalResultRecorderFor() signatures. Hypervel records approvals through the claiming store, so prepareRunContext() attaches it with RunContext::withApprovalStore() only for resumed approvals. runContextFor() keeps its upstream signature, and the store and conversation ID leave the RunContext constructor. Ported the six AgentFakeTest resume tests and their fixture. laravel/ai#1188: Anthropic messages that end with end_turn but contain tool calls now run those calls. Bedrock Converse can do the same, so its buffered and streamed paths get the same fix. Ported the Anthropic streaming and tool-loop tests and added the Bedrock equivalents. laravel/ai#1191: a tool that throws no longer fails the run. The exception is reported and the model receives a failed tool result; listener exceptions still fail the run. Unlike upstream, cancellation is not wrapped in ToolExecutionException, so it is never recovered as the tool's own failure. Ported ToolFailureRecoveryTest and the event, failed-turn, InvokesTools and integration test updates. laravel/ai#1186: streamed text joins across Anthropic pause_turn continuations, starting a new paragraph only after a step that ran tools. StreamedAgentResponse's single pass follows the same rule. Ported the pause_turn streaming tests and the TextDelta test updates. laravel/ai#1203: conversation title generation runs as its own agent run with the turn's parentInvocationId, so agent events report it. A failed title run still falls back to the prompt. Ported the five title-event tests and the step-attribution update. ai-sdk.md also gains the raw-response types from laravel/ai#1181, laravel/ai#1180, laravel/ai#1179 and laravel/ai#1182, and the concurrent-tools failure sentence is corrected. tests/Ai passes, and composer analyse is clean.
A multipart upload of an empty file was recorded with a null size, because the size check treated 0 as missing. It now checks for null, as upstream does, so an empty attachment reports 0KB. Port of laravel/telescope#1782 at a5e100fd5c. Hypervel already reads the size of attached resources through their streams. Added ClientRequestWatcherTest::testClientRequestWatcherReportsTheSizeOfAnEmptyResourceFileUpload. Upstream's ViewWatcherTest realpath() change does not apply: the Hypervel test uses a temp directory path with no relative segments. The Telescope suite passes.
…navigation These upstream changes share the rebuilt dashboard bundle, so they land together. Ported from laravel/telescope at a5e100fd5c. laravel/telescope#1781: the HTML mail preview ran the email's scripts in the dashboard's origin. MailHtmlController now returns the HTML with a sandbox Content-Security-Policy, an explicit text/html content type and nosniff, and the preview iframe gets the matching sandbox attribute. MailEmlController's return type is corrected from mixed to Response. Ported Http/RouteTest::testMailPreviewIsSandboxed. laravel/telescope#1783: Hypervel's dashboard already runs on Vue 3, Vue Router 4 and Bootstrap 5, but upstream's back-navigation fix was missing. RelatedEntries::activateTab() replaced the URL hash outside the router, so the router's stored location lost the hash and going back to an entry reopened its first tab. It now uses $router.replace({ hash }). Templates use {{ title }} as upstream. Ported the RelatedEntries "restores the selected tab when going back to the entry" test, the four routes.test.js tests and the queries preview stub's optional default slot. The overflow-tab test now mounts a router and checks the router-managed history state, and the Hypervel route test keeps only its per-section named-route coverage. Dist is rebuilt. The Telescope PHPUnit suite and the Telescope Vitest project pass, and vp check is clean.
Upstream now flushes memoized feature values when a queued job fails, as it already did after processed jobs. Hypervel memoizes feature values per coroutine in PennantState, and the queue worker runs each job in a new coroutine without copied context, so no job can read another job's values and no listeners are needed. Covers laravel/pennant#190 at cec5f8d3c3. The PennantServiceProvider comment now names all three queue events. Upstream's Feature/QueueEventsTest is not ported; a REMOVED note on CoroutineIsolationTest::testValuesAreIsolatedBetweenConcurrentCoroutines points to the isolation it relies on. The Pennant suite passes.
A redirect answering a QUERY request kept its 302 status, so browsers could repeat the QUERY against the redirect target. The Inertia middleware now converts these redirects to 303, as it already does for PUT, PATCH and DELETE. Port of inertiajs/inertia-laravel#927 at e68dadfc16. Unlike upstream, the global EnsureGetOnRedirect middleware also includes QUERY, with a comment. It converts redirects issued outside the Inertia middleware, such as a login redirect, and otherwise kept them as 302s. Ported MiddlewareTest::testRedirectsFromQueryRequestsAreConvertedTo303 and added InertiaServiceProviderTest::testRedirectsFromQueryRequestsOutsideTheInertiaMiddlewareAreConvertedTo303. The Inertia suite passes.
Ported from inertiajs/docs at 8cfece18fd. inertiajs/docs#98: documents the QUERY method with its server counterpart, inertia-laravel#927. inertia.md gains the Manual Visits QUERY Method section, the link and prefetching query examples, query in the router, form helper and useHttp method lists and in the state preservation and validation sentences, the 303 redirect wording now including QUERY, and the --no-stop-before-start paragraph, which StartSsr already supports. Client badges become v3.10.0+, and the Laravel adapter badge is dropped. The prefetching Svelte example imports Link, correcting upstream's inertia import. The protocol page is excluded, and the CSRF page's sentence for other frameworks does not apply to Hypervel's own CSRF text. inertiajs/docs#94: the only applicable change is upstream's protocol sentence in How It Works. Its adapter-building, community adapter, DevTools protocol and protocol pages cover other backends or protocol implementation, which Hypervel's docs exclude.
The example's try_files $uri would serve any .php file under public as plain text, exposing its source. A location block now returns 404 for them. The WebSocket example serves no files. Port of laravel/docs#11425 at 625d1dfb61.
Applications sharing a Redis server can overwrite each other's cache, session and queue keys unless each uses its own prefix or database. redis.md gains a Key Prefixes section, and queues.md points to it from the Redis driver. Port of laravel/docs#11428 at 625d1dfb61. Adapted: the default prefix comes from the application ID, the example uses Hypervel's colon prefix style, and the database alternative names REDIS_DB and the per-connection REDIS_CACHE_DB, REDIS_SESSION_DB, REDIS_QUEUE_DB and REDIS_REVERB_DB overrides.
Entries stored forever were recorded with a null cache.ttl. The cache.put span now adds cache.ttl only when the entry has an expiry, for single and many writes. Port of getsentry/sentry-laravel#1205 at fc6c60a9b5, with upstream's comment. Hypervel's integration records no session spans. Ported Features/CacheIntegrationTest::testCachePutSpanOmitsTtlWhenEntryNeverExpires without upstream's tracing-availability skip. The CacheIntegration tests pass.
Upstream's GuzzleSender missed Guzzle 8's NetworkException family, so those failures escaped as raw Guzzle exceptions and skipped retries. Hypervel needs no source change: requests go through Hypervel's HTTP client, which turns every Guzzle TransferException into a ConnectionException, and SaloonManager::send() raises and retries it as a FatalRequestException. Port of saloonphp/saloon#569 at 6e6932a635. Ported Feature/Guzzle8SenderExceptionTest: the four failure types through Http::fake() and the connector retry case through RetryConnector(3), without upstream's Guzzle 7 skip. The sendAsync() case is REMOVED, since coroutine pools replace promises. The test file passes.
…se age js-beautify, used by @vue/test-utils in the frontend tests, still requires glob 13 and minimatch 10, which are affected by GHSA-3g3m-2fjr-6pw4 and GHSA-g3hv-75wg-m249. Workspace overrides now require the patched glob ^14.0.2 and minimatch ^11.0.1. The lock resolves glob 14.0.3, minimatch 11.0.3, path-scurry 3.0.0 and @isaacs/nonrecursive-glob-match 1.0.3; nothing else changes, and neither package is in the dashboard bundles. Port of laravel/telescope#1788 at a5e100fd5c. Its Guzzle constraint is already covered by Telescope's hypervel/guzzle ^8.3 requirement. The pnpm minimumReleaseAge drops from 7 days to 3 (4320 minutes). The Horizon and Telescope Vitest projects pass.
…rors When an approved tool threw while a paused turn was resumed, the model received a failed tool result, but the exception was never reported, unlike the same failure on the normal tool path. The resume path also caught every Throwable, so an exception thrown by an InvokingTool or ToolInvoked listener was returned to the model as a tool failure, even when the tool had already run. The resume path now catches only ToolExecutionException, as executedToolResult() does. The tool's own exception is reported and the model receives a failed result; listener exceptions and coroutine cancellation end the run. Upstream laravel/ai at c8495608a1 catches Throwable here without reporting, and a comment records the difference. AgentEventsTest now asserts that a tool failing during a resume is reported once, and covers a throwing ToolInvoked listener failing the resume without the tool running again or another request being sent. Both tests fail without the change and pass with it.
Classification instructions, Boolean criteria, Choice option descriptions and Score levels may be arrays when one sentence is not enough, and the gateways already send lists correctly: OpenAI encodes any array as JSON text, and the other gateways pass it through. The annotations only allowed string-keyed arrays, so static analysis rejected lists such as ['Money lost.', 'Customer blocked.']. The free-form values now use array<array-key, mixed> in Boolean, Choice, Score, CollectionChoice (options, decide() text and resolved descriptions), OpenAiClassificationGateway::mapLevel(), FakeClassificationGateway::fakeScoreAnswer() and ScoreAnswer's legend and label(). The outer shapes and record-shaped arrays such as provider options are unchanged. Upstream laravel/ai at c8495608a1 uses array<string, mixed> for these values. The Classification type fixture now passes list inputs to each question type and to CollectionChoice::decide(). It reported the narrowed types before the change. Static analysis and the OpenAI and fake classification tests pass.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
|
@coderabbitai review |
Action not completed
|
PR Summary by QodoSync AI run recovery, raw responses, and Inertia QUERY redirects
AI Description
Diagram
High-Level Assessment
Files changed (106)
|
Code Review by Qodo
1.
|
…ailures
Tool failures were returned to the model as "The tool call failed: "
followed by the exception message. That message can carry query
bindings, server paths and other private details. It was sent to the
provider, stored in the conversation, and streamed to the client as the
tool's error text. Sub-agent failures ("Agent failed: ...") and the
CopyFile, MoveFile and GetFileUrl tools did the same.
With app.debug disabled, these results now omit the exception message:
the model receives "The tool call failed.", "Agent failed." or the
operation and paths, such as "Unable to copy [a.txt] to [b.txt]." Debug
mode keeps the detailed text. This applies to ordinary tool failures,
approval resumes, AgentTool and the three filesystem tools. Validation
feedback and other non-exception results are unchanged. Upstream
laravel/ai at c8495608a1 always includes the message; comments at each
site, the AI README and the porting guide record the difference.
Failures that were turned into results without reaching the exception
handler are now reported:
- AgentTool reports a failed sub-agent on both the prompt and streaming
paths. AgentFailed only reaches listeners, and the parent run never
sees the exception.
- CopyFile, MoveFile and GetFileUrl report the exceptions they catch.
- ReadFile and GetFileMetadata treated any size() failure as a missing
file, so an S3 outage or permission error told the model that an
existing file was missing. After a failed lookup they now check
existence: a missing file keeps its result, and any other failure is
reported and returned as "Unable to read [path]." or "Unable to get
metadata for [path]." The success path makes no extra request.
- ReadFile returns "Unable to read [path]." when a non-throwing disk
returns null, instead of describing the file as binary.
Live sub-agent snapshots also keep the sub-agent's ToolResult events, so
TextDelta::combine() starts a new paragraph after a step that ran tools,
as the final result does. Since the pause_turn change, snapshots had
joined every step together.
The AI docs cover tool, sub-agent and file storage failures.
ToolFailureRecoveryTest, AgentToolTest, AgentEventsTest and
FilesystemToolsTest cover both debug modes, the new reporting and the
lookup and null-read corrections. TextGenerationLoopTest covers a tool
step boundary in live snapshots. The new cases fail without the change,
and the affected tests, formatting and static analysis pass.
Upstream Updates
RunContext::withApprovalStore()only when a run resumes approvals, sorunContextFor()keeps upstream's signature. Document faked resumes in the testing section.end_turn.pause_turncontinuations, starting a new paragraph only after a step that ran tools. The streamed response's single pass follows the same rule.parentInvocationId, so agent events report it. A failed title run still falls back to the start of the prompt. Document it.Content-Security-Policy,text/htmlandnosniff, and the preview iframe is sandboxed.hypervel/guzzle^8.3 requirement already covers upstream's raised Guzzle floor.QUERYrequests to 303, as the Inertia middleware already does forPUT,PATCHandDELETE.QUERYvisits and the SSR--no-stop-before-startoption, and adopt the protocol sentence in How It Works. Correct the Svelte prefetching example'sLinkimport..phpfiles in the Nginx deployment example, which otherwise served their source as plain text.cache.ttlfrom Sentrycache.putspans for entries that never expire.ConnectionException, which Saloon raises and retries asFatalRequestException.Additional Hypervel changes
end_turn, in both buffered and streamed responses, matching the Anthropic fix.pause_turn, that the stream had shown as one paragraph.app.debugis enabled. The messages can carry query bindings and server paths to the provider, the conversation and the client. The exceptions are still reported.QUERYrequests to 303 in the globalEnsureGetOnRedirectmiddleware too. It handles redirects issued outside the Inertia middleware, such as login redirects, which stayed 302.MailEmlControllerresponse asResponseinstead ofmixed.minimumReleaseAgefrom 7 days to 3.Affected tests, formatting, and source analysis pass, as do the Horizon and Telescope frontend tests. CI runs the full suite and supported service matrix.
Note
Sync upstream: AI tool failure recovery, raw provider responses, and QUERY redirect support
ToolExecutionExceptionand returned to the model as failed tool results. Exception details reach the model only whenapp.debugis enabled; the exception is always reported.withRawResponse()on EmbeddingsResponse.php and the other response models.TextDelta::combineno longer inserts a blank line between consecutive streamed steps that ran no tools; the boundary remains after aToolResult.RunContextmoves approval-store and conversation-ID setup out of the constructor into awithApprovalStore()fluent setter.Stopfinish reason toToolCalls, so the tool-call loop runs.RunContextconstructor signature changed.RunContextno longer accepts the approval store and conversation ID as constructor parameters — out-of-tree callers constructing it that way break; usewithApprovalStore(). Tool exceptions no longer escape a run, so code relying on a throwing tool to fail the run must rethrow via aToolFailedlistener.Macroscope summarized 3d724a7.