Skip to content

feat(interrupts): AG-UI interrupt lifecycle overhaul (ephemeral)#970

Open
AlemTuzlak wants to merge 80 commits into
mainfrom
feat/interrupts-overhaul
Open

feat(interrupts): AG-UI interrupt lifecycle overhaul (ephemeral)#970
AlemTuzlak wants to merge 80 commits into
mainfrom
feat/interrupts-overhaul

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Extracts the AG-UI interrupts overhaul (from #935, which was stacked on the persistence PR #785) into its own PR targeting main, excluding the persistence story. Interrupts run fully ephemeral here: resume reconstructs pending tool calls from the client's message history in a fresh child run. A follow-up stacked PR will add durable persistence back on top.

What's in scope

  • AG-UI interrupt lifecycle: RUN_FINISHED with outcome.type === 'interrupt', interrupt descriptors/bindings, ephemeral resume via parentRunId + resume batch.
  • Tool-approval and client-tool interrupt kinds.
  • Client-side interruptManager (hydrate / resolve / batch submit), useChat integration across frameworks.
  • ts-react-chat interrupt-lab example for manual testing.

Explicitly out of scope (deferred to the stacked persistence PR)

  • @tanstack/ai-persistence, -drizzle, -prisma, -cloudflare.
  • Durable interrupts (recovery via joinRun, server-side interrupt state fetchers).

Notable fixes

  • Forward parentRunId so ephemeral resume correlates the interrupted run to its continuation child run.
  • Resolve the approval tool-call's owning message from history when a MESSAGES_SNAPSHOT reset the live maps, so the approval UI state still updates.
  • Removed durable (interrupts-v2) e2e routes/fixtures/specs and persistence-era recovery tests; these return with persistence.

Testing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added AG-UI interrupt lifecycle with support for tool approvals, generic responses, and client-tool execution, including typed payloads/edited args, cancellation, retry, and atomic multi-interrupt resolution.
    • Introduced resumable interrupt state via resume/continuation handling, plus persistence with durable interrupt recovery (ephemeral enabled by default) and new storage-adapter options.
    • Expanded interrupt/resume state and controls across React, Preact, Vue, Solid, Svelte, and Angular integrations.
    • Added an “Interrupts Lab” example and new /interrupts UI flow.
  • Documentation

    • Reworked and expanded guides covering the interrupt protocol, migration, tool approvals, generic interrupts, and durable vs ephemeral behavior.

AlemTuzlak and others added 30 commits July 1, 2026 14:33
…M-event catalog

- Add optional in-band `cursor` to StreamChunk + `cursor` input on chat().
- Add a *replayAndResume() seam: when a cursor is supplied and a ResumeSource
  capability is provided, replay the persisted event tail; if the run is still
  running and the adapter supports re-attach, continue live. No-op without a
  resume source (a non-persisted run is unchanged).
- Move the generic LockStore + LocksCapability ('locks') into core so it is a
  single shared token across the sandbox and persistence layers.
- Add ResumeSource capability/contract and a typed catalog of well-known CUSTOM
  event names (file.changed, process.*, approval.*, artifact.*, sandbox.*).
…andbox bridge

- @tanstack/ai-persistence: store contracts, withPersistence middleware,
  memoryPersistence, cursor utilities, approval controller, resume-source
  adapter, history projection. Fully optional; works with and without sandbox.
- @tanstack/ai-persistence-sql: one SQL store impl behind a minimal SqlDriver
  (sqlite|postgres dialect) + versioned migrations + DDL.
- Backends: -sqlite (node:sqlite/better-sqlite3), -postgres (pg), -cloudflare
  (D1, compile-verified), -drizzle and -prisma (BYO).
- @tanstack/ai-sandbox-persistence: durable SQL-backed SandboxStore +
  withPersistenceBridge wiring durable store/locks into withSandbox (agent mode).
- Track the latest in-band cursor per active run; expose getResumeState(),
  resume(), and maybeAutoResume() with an `autoResume` opt-out (default on).
- Pass `cursor` through the connection adapter's RunAgentInput payload so the
  server can replay a run. streamResponse reuses the original runId on resume.
…ip config

- New docs/persistence/overview.md (+ nav entry, addedAt) with server + client +
  agent-mode usage.
- New ai-core/persistence agent skill.
- Changeset covering the feature; knip ignore for the optional pg peer dep.
# Conflicts:
#	docs/media/transcription.md
# Conflicts:
#	examples/ts-react-chat/src/routes/generations.image.tsx
#	examples/ts-react-chat/src/routes/generations.video.tsx
#	testing/e2e/src/routes/$provider/index.tsx
@pkg-pr-new

pkg-pr-new Bot commented Jul 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@970

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@970

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@970

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@970

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@970

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@970

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@970

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@970

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@970

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@970

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@970

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@970

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@970

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@970

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@970

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@970

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@970

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@970

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@970

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@970

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@970

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@970

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@970

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@970

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@970

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@970

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@970

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@970

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@970

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@970

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@970

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@970

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@970

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@970

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@970

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@970

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@970

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@970

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@970

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@970

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@970

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@970

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@970

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@970

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@970

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@970

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@970

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@970

commit: 20c4379

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (3)
examples/ts-react-chat/src/routes/generations.structured-output.tsx (1)

146-153: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the PartialResult cast

payload.value.object is still unknown, so setResult(payload.value.object) won’t typecheck against PartialResult | null. Keep the cast or add a real type guard before assigning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ts-react-chat/src/routes/generations.structured-output.tsx` around
lines 146 - 153, Restore the PartialResult cast when assigning
payload.value.object to the result state, or add a type guard that narrows the
unknown value to PartialResult before the setResult call. Update the assignment
path using StreamChunkPayload while preserving the existing null handling.
packages/ai-svelte/src/create-generation.svelte.ts (2)

1-1: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Gate the raw onResult passthrough behind disposed, like every other callback.

Both files add a disposed flag this PR and gate onError/onProgress/onChunk/onResultChange/onLoadingChange/onErrorChange/onStatusChange (and, in the video file, onJobCreated/onStatusUpdate/onJobIdChange/onVideoStatusChange) behind it — but the raw onResult pass-through to the user-supplied callback is left unguarded in both. If a generation resolves after dispose() is called (e.g. component unmounted while a fetch is in flight), the user's onResult will still fire despite every other callback being suppressed post-disposal.

  • packages/ai-svelte/src/create-generation.svelte.ts#L184-186: wrap options.onResult?.(r) in if (!disposed).
  • packages/ai-svelte/src/create-generate-video.svelte.ts#L188-190: wrap options.onResult?.(r) in if (!disposed).
🛡️ Proposed fix
-    onResult: ((r: TResult) => options.onResult?.(r)) as (
-      result: TResult,
-    ) => TOutput | null | void,
+    onResult: ((r: TResult) => {
+      if (disposed) return
+      return options.onResult?.(r)
+    }) as (result: TResult) => TOutput | null | void,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-svelte/src/create-generation.svelte.ts` at line 1, Guard the raw
onResult callbacks in the generation result handlers with the existing disposed
flag. Update the onResult handling in create-generation.svelte.ts and
create-generate-video.svelte.ts so options.onResult is invoked only when
disposed is false, matching the guards on the other callbacks.

254-292: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear resumeSnapshot on reset
client.reset() clears the client state, but the wrapper still exposes the previous resumeSnapshot, so resumeState, pendingArtifacts, and resultArtifacts can stay stale after a reset. Reset the local snapshot too, or have the client emit a cleared snapshot.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-svelte/src/create-generation.svelte.ts` around lines 254 - 292,
Update the reset flow around the returned reset API and local resumeSnapshot
state so reset clears resumeSnapshot after invoking client.reset(). Ensure
resumeState, pendingArtifacts, and resultArtifacts immediately return their
empty/null defaults after reset, while preserving existing reset behavior.
🧹 Nitpick comments (11)
docs/tools/tool-approval.md (3)

282-288: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid non-null assertions (!) in documentation code samples.

As per coding guidelines, documentation code samples must not use type-assertion casts and should type-check by narrowing or validating values. Please remove the non-null assertion or extract it to a local constant outside the closure.

♻️ Proposed refactor
               <button
                 key={i}
                 onClick={() =>
                   addToolApprovalResponse({
-                    id: part.approval!.id,
+                    id: part.approval.id,
                     approved: true,
                   })
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tools/tool-approval.md` around lines 282 - 288, Update the onClick
handler around addToolApprovalResponse to remove the non-null assertion on
part.approval. Narrow or validate part.approval before the closure, using a
local constant if needed, while preserving the existing approval ID and
approved: true behavior.

Source: Coding guidelines


191-197: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid non-null assertions (!) in documentation code samples.

As per coding guidelines, documentation code samples must not use type-assertion casts and should type-check by narrowing or validating values. The non-null assertion part.approval! acts as a type assertion.

Since part.approval is checked in the outer if statement and part is not reassigned, TypeScript should be able to narrow it without the !. If TypeScript loses the narrowing inside the onClick closure, extract the ID to a constant before the return statement:
const approval = part.approval;

♻️ Proposed refactor
                   <button
                     onClick={() =>
                       addToolApprovalResponse({
-                        id: part.approval!.id,
+                        id: part.approval.id,
                         approved: true,
                       })
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tools/tool-approval.md` around lines 191 - 197, Remove the non-null
assertion from the approval ID access in the addToolApprovalResponse onClick
handler, relying on the surrounding part.approval narrowing. If narrowing is not
preserved inside the closure, capture part.approval in a local constant before
returning the element and use that validated value.

Source: Coding guidelines


201-207: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Avoid non-null assertions (!) in documentation code samples.

As per coding guidelines, documentation code samples must not use type-assertion casts and should type-check by narrowing or validating values. Please remove the non-null assertion or extract it to a local constant outside the closure.

♻️ Proposed refactor
                   <button
                     onClick={() =>
                       addToolApprovalResponse({
-                        id: part.approval!.id,
+                        id: part.approval.id,
                         approved: false,
                       })
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/tools/tool-approval.md` around lines 201 - 207, Remove the non-null
assertion from the onClick handler’s use of part.approval in the
addToolApprovalResponse call. Narrow or validate part.approval before accessing
its id, extracting a local value outside the closure if needed, while preserving
the existing approved: false behavior.

Source: Coding guidelines

packages/ai-client/src/generation-client.ts (1)

527-578: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Extract the shared resume-snapshot persistence lifecycle and coalesce per-chunk writes. observeResumeSnapshot, completePlainFetcherResumeSnapshot, persistResumeSnapshot, writeResumeSnapshot (and the getResumeSnapshot/getResumePersistenceError/getStatus getters) are identical in both clients, so any fix to the queue/persistence logic must be applied in two places and can silently drift. Separately, observeResumeSnapshot enqueues a serverPersistence.setItem write on every stream chunk; with last-write-wins semantics the intermediate writes are wasted I/O (notably for high-frequency VIDEO_STATUS polling chunks).

  • packages/ai-client/src/generation-client.ts#L527-L578: move these helpers into a shared module/mixin (e.g. a ResumeSnapshotPersister used by both clients) and coalesce writes (debounce/trailing-write or persist only on meaningful transitions: runningcomplete/error, artifact/result changes).
  • packages/ai-client/src/video-generation-client.ts#L613-L664: replace the duplicated helpers with the shared implementation so both clients stay in sync.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-client/src/generation-client.ts` around lines 527 - 578, Extract
the duplicated resume-snapshot lifecycle from generation-client.ts#L527-L578 and
video-generation-client.ts#L613-L664, including observeResumeSnapshot,
completePlainFetcherResumeSnapshot, persistResumeSnapshot, writeResumeSnapshot,
and the related getters, into one shared ResumeSnapshotPersister implementation
used by both clients. Add trailing-write coalescing or equivalent
meaningful-transition persistence so per-chunk updates do not enqueue redundant
serverPersistence.setItem calls, while preserving final complete/error snapshots
and existing persistence-error behavior; update both sites to delegate to the
shared implementation.
packages/ai-client/tests/interrupts-types.test-d.ts (1)

162-164: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

toMatchTypeOf is deprecated; prefer toExtend.

Per expect-type's docs, .toMatchTypeOf is deprecated in favor of .toExtend (identical semantics) or .toMatchObjectType (stricter, plain-object-only). Not urgent since removal isn't planned, but new tests shouldn't introduce the deprecated form.

♻️ Suggested fix
-expectTypeOf<Extract<Interrupt, { kind: 'tool-approval' }>>().toMatchTypeOf<
+expectTypeOf<Extract<Interrupt, { kind: 'tool-approval' }>>().toExtend<
   ToolApprovalInterrupt<Tools[number]>
 >()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-client/tests/interrupts-types.test-d.ts` around lines 162 - 164,
Replace the deprecated toMatchTypeOf assertion in the Interrupt type test with
toExtend, preserving the existing Extract<Interrupt, { kind: 'tool-approval' }>
and ToolApprovalInterrupt<Tools[number]> type relationship.
packages/ai-client/tests/generation-resume-state.test.ts (1)

245-258: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test doesn't exercise "explicit stop" scenario despite its name.

Only a RUN_STARTED chunk is reduced; there's no finishReason: 'stop' (or equivalent) chunk in the input, so the assertions about cancelled/cancelEndpoint pass trivially regardless of whether the reducer actually models explicit stop correctly. This leaves the described guard unverified.

♻️ Suggested fix: actually reduce a stop-related chunk
   it('does not model explicit stop as durable cancel state', () => {
     const snapshot = reduceChunks([
       {
         type: EventType.RUN_STARTED,
         threadId: 'thread-1',
         runId: 'run-1',
         timestamp: 1,
       },
+      {
+        type: EventType.RUN_FINISHED,
+        threadId: 'thread-1',
+        runId: 'run-1',
+        finishReason: 'stop',
+        timestamp: 2,
+      },
     ])

-    expect(snapshot.status).toBe('running')
+    expect(snapshot.status).toBe('complete')
     expect(snapshot).not.toHaveProperty('cancelled')
     expect(snapshot).not.toHaveProperty('cancelEndpoint')
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-client/tests/generation-resume-state.test.ts` around lines 245 -
258, Update the test around reduceChunks to include a run-completion chunk
representing an explicit stop, using the repository’s established finishReason
or equivalent stop event shape. Keep the assertions that the resulting snapshot
is not marked cancelled and has no cancelEndpoint, so the test verifies reducer
behavior rather than only the initial RUN_STARTED state.
packages/ai/src/activities/chat/tools/json-schema-validator.ts (1)

105-119: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Reuse a shared Ajv2020 instance

compileJsonSchema202012 creates a fresh Ajv2020 and re-registers formats on every validation. This helper is on the interrupt-resume and interrupt-manager validation paths, so the setup cost repeats on each schema check. Move the Ajv instance (and addFormats) to module scope, or cache compiled validators by schema identity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/activities/chat/tools/json-schema-validator.ts` around lines
105 - 119, Update compileJsonSchema202012 to reuse a module-scoped Ajv2020
instance configured with the existing options and registered formats, rather
than constructing Ajv2020 and calling addFormats on every invocation. Preserve
the current validation behavior and schema compilation flow.
packages/ai-angular/src/inject-chat.ts (1)

279-287: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant if/else — both branches are identical.

resolveInterrupts branches on typeof resolution === 'boolean' but both arms call client.resolveInterrupts(resolution) with no difference. Since client.resolveInterrupts is already overloaded to accept both boolean and resolver-function forms, this wrapper can just forward the argument directly.

♻️ Proposed simplification
   const resolveInterrupts = (
     resolution: boolean | ((interrupt: ChatInterrupt<TTools>) => undefined),
   ) => {
-    if (typeof resolution === 'boolean') {
-      client.resolveInterrupts(resolution)
-    } else {
-      client.resolveInterrupts(resolution)
-    }
+    client.resolveInterrupts(resolution)
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-angular/src/inject-chat.ts` around lines 279 - 287, Simplify
resolveInterrupts by removing the redundant typeof-based if/else and directly
forwarding resolution to client.resolveInterrupts; preserve support for both
boolean and resolver-function inputs through the existing overloads.
packages/ai-react/src/types.ts (1)

204-225: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add JSDoc for the new interrupt/resume return fields.

Every other member of BaseUseChatReturn has a doc comment; this new block (resumeState, interrupts, interruptErrors, resuming, resolveInterrupts, cancelInterrupts, retryInterrupts, resumeInterruptsUnsafe) has none except the two @deprecated aliases. This is a sizable new public API surface for consumers to discover without editor tooltips.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-react/src/types.ts` around lines 204 - 225, Add concise JSDoc
comments to each new public member in BaseUseChatReturn: resumeState,
interrupts, interruptErrors, resuming, resolveInterrupts, cancelInterrupts,
retryInterrupts, and resumeInterruptsUnsafe. Describe each member’s purpose and
usage, while preserving the existing deprecation comments for pendingInterrupts
and resumeInterrupts.
packages/ai-vue/src/use-generate-video.ts (1)

45-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Inconsistent with sibling composables: doesn't derive persistence/initialResumeSnapshot/resume fields via Pick/Omit.

use-generate-image.ts, use-generate-speech.ts, and use-transcription.ts derive persistence/initialResumeSnapshot via Pick<UseGenerationOptions<...>, ...> and derive resume/artifact return fields via Omit<UseGenerationReturn<TOutput>, 'generate'>. This file duplicates the same fields manually, risking type drift if GenerationClientOptions/UseGenerationReturn change later.

♻️ Suggested alignment with sibling composables
+import type {
+  UseGenerationOptions,
+  UseGenerationReturn,
+} from './use-generation'
+
-export interface UseGenerateVideoOptions<TOutput = VideoGenerateResult> {
+export interface UseGenerateVideoOptions<
+  TOutput = VideoGenerateResult,
+> extends Pick<
+  UseGenerationOptions<VideoGenerateInput, VideoGenerateResult, TOutput>,
+  'persistence' | 'initialResumeSnapshot'
+> {
   /** Connect-based adapter for streaming transport (server handles polling) */
   connection?: ConnectConnectionAdapter
   ...
-  /** Server-side lightweight generation state persistence. */
-  persistence?: GenerationPersistenceOptions
-  /** Initial lightweight resume snapshot restored by the app (read-only state). */
-  initialResumeSnapshot?: GenerationResumeSnapshot
   ...
 }

-export interface UseGenerateVideoReturn<TOutput = VideoGenerateResult> {
+export interface UseGenerateVideoReturn<
+  TOutput = VideoGenerateResult,
+> extends Omit<UseGenerationReturn<TOutput>, 'generate'> {
   ...
-  resumeSnapshot: DeepReadonly<ShallowRef<GenerationResumeSnapshot | undefined>>
-  resumeState: DeepReadonly<ShallowRef<GenerationResumeState | null>>
-  pendingArtifacts: DeepReadonly<ShallowRef<Array<GenerationPendingArtifact>>>
-  resultArtifacts: DeepReadonly<ShallowRef<Array<PersistedArtifactRef>>>
 }

Also applies to: 93-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-vue/src/use-generate-video.ts` around lines 45 - 48, Align
useGenerateVideo’s option and return types with the sibling composables by
deriving persistence and initialResumeSnapshot through
Pick<UseGenerationOptions<...>, ...>, and deriving resume/artifact return fields
through Omit<UseGenerationReturn<TOutput>, 'generate'>. Remove the duplicated
manual field declarations while preserving the existing generate API and
video-specific types.
packages/ai-react/src/use-generation.ts (1)

240-243: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the resume-field derivation to avoid duplication with use-generate-video.ts.

The resumeState/pendingArtifacts/resultArtifacts derivation from resumeSnapshot here is byte-for-byte duplicated in use-generate-video.ts (which has its own independent client-construction path rather than delegating to useGeneration). A shared helper (e.g. deriveResumeFields(snapshot) in a small shared module) would keep both in sync if GenerationResumeSnapshot's shape evolves.

♻️ Proposed shared helper
+function deriveResumeFields<TArtifact>(
+  snapshot: GenerationResumeSnapshot | undefined,
+): {
+  resumeState: GenerationResumeState | null
+  pendingArtifacts: Array<GenerationPendingArtifact>
+  resultArtifacts: Array<TArtifact>
+} {
+  return {
+    resumeState: snapshot?.resumeState ?? null,
+    pendingArtifacts: snapshot?.pendingArtifacts ?? [],
+    resultArtifacts: (snapshot?.result?.artifacts ?? []) as Array<TArtifact>,
+  }
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-react/src/use-generation.ts` around lines 240 - 243, Extract the
duplicated resumeSnapshot field derivation from the current hook and
use-generate-video.ts into a shared helper such as deriveResumeFields(snapshot).
Update both client-construction paths to call the helper while preserving the
existing null and empty-array defaults for resumeState, pendingArtifacts, and
resultArtifacts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/architecture/approval-flow-processing.md`:
- Around line 137-145: Update both documentation
snippets—docs/architecture/approval-flow-processing.md lines 137-145 and
docs/interrupts/tool-approval.md lines 72-80—to use the gpt-chat-latest alias in
their openaiText calls instead of gpt-5.5; no other example behavior should
change.

In `@docs/config.json`:
- Around line 104-112: Update the timestamp metadata for all pages modified or
added in this change, including the entries represented by the visible Tool
Approval Flow object and the referenced locations, to use "2026-07-20" for
updatedAt and, for newly added pages, addedAt as well.

In `@packages/ai-client/tests/chat-client-interrupts.test.ts`:
- Line 1: Re-save packages/ai-client/tests/chat-client-interrupts.test.ts as
UTF-8 without a BOM and replace the mojibake em-dashes in comments at lines 148,
328, and 395; likewise remove the leading BOM from
packages/ai-client/tests/chat-client-resume.test.ts before its import statement.

In `@packages/ai-solid/src/use-chat.ts`:
- Around line 331-334: Update resumeInterruptsUnsafe to await the underlying
client().resumeInterruptsUnsafe call, then invoke syncResumeState() after it
settles, matching the other resume/interrupt-affecting methods and keeping its
existing arguments and return behavior.

In `@packages/ai/src/activities/chat/index.ts`:
- Around line 3307-3328: Update applyResumeToolState to merge
state.genericInterrupts into the corresponding resume-state collection,
preserving each interrupt’s identifier and resolution like the existing
approvals, clientToolResults, and deniedToolResults handling. Ensure
reconstructed generic-interrupt resume resolutions are retained.

In `@testing/e2e/src/components/ChatUI.tsx`:
- Around line 36-38: Update the Send button’s disabled condition in ChatUI to
include hasPendingInterrupt, so it is disabled when an interrupt is pending or
the input is empty. Keep handleSubmit’s existing no-op guard and apply the same
pending-interrupt state consistently to the other affected input controls.

In `@testing/e2e/src/routes/`$provider/$feature.tsx:
- Around line 122-145: Update parsePendingInterrupts to validate every array
entry and throw a TypeError when an entry is not a record or lacks string
id/reason, matching deserializeMessages and deserializeResumeSnapshot behavior.
Preserve the existing normalization of valid toolCallId and metadata fields, but
remove the silent continue paths so malformed persisted data cannot produce a
partial list.

In `@testing/e2e/tests/tool-approval.spec.ts`:
- Line 13: Update the suite title in the tool-approval test.describe declaration
to use the intended em dash character (—) instead of the mojibake sequence
(—); preserve the existing provider interpolation and title text.

---

Outside diff comments:
In `@examples/ts-react-chat/src/routes/generations.structured-output.tsx`:
- Around line 146-153: Restore the PartialResult cast when assigning
payload.value.object to the result state, or add a type guard that narrows the
unknown value to PartialResult before the setResult call. Update the assignment
path using StreamChunkPayload while preserving the existing null handling.

In `@packages/ai-svelte/src/create-generation.svelte.ts`:
- Line 1: Guard the raw onResult callbacks in the generation result handlers
with the existing disposed flag. Update the onResult handling in
create-generation.svelte.ts and create-generate-video.svelte.ts so
options.onResult is invoked only when disposed is false, matching the guards on
the other callbacks.
- Around line 254-292: Update the reset flow around the returned reset API and
local resumeSnapshot state so reset clears resumeSnapshot after invoking
client.reset(). Ensure resumeState, pendingArtifacts, and resultArtifacts
immediately return their empty/null defaults after reset, while preserving
existing reset behavior.

---

Nitpick comments:
In `@docs/tools/tool-approval.md`:
- Around line 282-288: Update the onClick handler around addToolApprovalResponse
to remove the non-null assertion on part.approval. Narrow or validate
part.approval before the closure, using a local constant if needed, while
preserving the existing approval ID and approved: true behavior.
- Around line 191-197: Remove the non-null assertion from the approval ID access
in the addToolApprovalResponse onClick handler, relying on the surrounding
part.approval narrowing. If narrowing is not preserved inside the closure,
capture part.approval in a local constant before returning the element and use
that validated value.
- Around line 201-207: Remove the non-null assertion from the onClick handler’s
use of part.approval in the addToolApprovalResponse call. Narrow or validate
part.approval before accessing its id, extracting a local value outside the
closure if needed, while preserving the existing approved: false behavior.

In `@packages/ai-angular/src/inject-chat.ts`:
- Around line 279-287: Simplify resolveInterrupts by removing the redundant
typeof-based if/else and directly forwarding resolution to
client.resolveInterrupts; preserve support for both boolean and
resolver-function inputs through the existing overloads.

In `@packages/ai-client/src/generation-client.ts`:
- Around line 527-578: Extract the duplicated resume-snapshot lifecycle from
generation-client.ts#L527-L578 and video-generation-client.ts#L613-L664,
including observeResumeSnapshot, completePlainFetcherResumeSnapshot,
persistResumeSnapshot, writeResumeSnapshot, and the related getters, into one
shared ResumeSnapshotPersister implementation used by both clients. Add
trailing-write coalescing or equivalent meaningful-transition persistence so
per-chunk updates do not enqueue redundant serverPersistence.setItem calls,
while preserving final complete/error snapshots and existing persistence-error
behavior; update both sites to delegate to the shared implementation.

In `@packages/ai-client/tests/generation-resume-state.test.ts`:
- Around line 245-258: Update the test around reduceChunks to include a
run-completion chunk representing an explicit stop, using the repository’s
established finishReason or equivalent stop event shape. Keep the assertions
that the resulting snapshot is not marked cancelled and has no cancelEndpoint,
so the test verifies reducer behavior rather than only the initial RUN_STARTED
state.

In `@packages/ai-client/tests/interrupts-types.test-d.ts`:
- Around line 162-164: Replace the deprecated toMatchTypeOf assertion in the
Interrupt type test with toExtend, preserving the existing Extract<Interrupt, {
kind: 'tool-approval' }> and ToolApprovalInterrupt<Tools[number]> type
relationship.

In `@packages/ai-react/src/types.ts`:
- Around line 204-225: Add concise JSDoc comments to each new public member in
BaseUseChatReturn: resumeState, interrupts, interruptErrors, resuming,
resolveInterrupts, cancelInterrupts, retryInterrupts, and
resumeInterruptsUnsafe. Describe each member’s purpose and usage, while
preserving the existing deprecation comments for pendingInterrupts and
resumeInterrupts.

In `@packages/ai-react/src/use-generation.ts`:
- Around line 240-243: Extract the duplicated resumeSnapshot field derivation
from the current hook and use-generate-video.ts into a shared helper such as
deriveResumeFields(snapshot). Update both client-construction paths to call the
helper while preserving the existing null and empty-array defaults for
resumeState, pendingArtifacts, and resultArtifacts.

In `@packages/ai-vue/src/use-generate-video.ts`:
- Around line 45-48: Align useGenerateVideo’s option and return types with the
sibling composables by deriving persistence and initialResumeSnapshot through
Pick<UseGenerationOptions<...>, ...>, and deriving resume/artifact return fields
through Omit<UseGenerationReturn<TOutput>, 'generate'>. Remove the duplicated
manual field declarations while preserving the existing generate API and
video-specific types.

In `@packages/ai/src/activities/chat/tools/json-schema-validator.ts`:
- Around line 105-119: Update compileJsonSchema202012 to reuse a module-scoped
Ajv2020 instance configured with the existing options and registered formats,
rather than constructing Ajv2020 and calling addFormats on every invocation.
Preserve the current validation behavior and schema compilation flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 300877ad-3d76-45c8-be03-b57ded65d2e7

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7aa09 and 0883ecb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (268)
  • .changeset/ag-ui-interrupts.md
  • docs/architecture/approval-flow-processing.md
  • docs/config.json
  • docs/interrupts/generic.md
  • docs/interrupts/migration.md
  • docs/interrupts/multiple.md
  • docs/interrupts/overview.md
  • docs/interrupts/tool-approval.md
  • docs/reference/functions/toolDefinition.md
  • docs/tools/client-tools.md
  • docs/tools/tool-approval.md
  • examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts
  • examples/ts-react-chat/.env.example
  • examples/ts-react-chat/README.md
  • examples/ts-react-chat/package.json
  • examples/ts-react-chat/src/components/Header.tsx
  • examples/ts-react-chat/src/lib/guitar-tools.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/InterruptLabPage.tsx
  • examples/ts-react-chat/src/lib/interrupt-lab/client-ui.test.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/client-ui.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/scenarios.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/server.test.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/server.ts
  • examples/ts-react-chat/src/lib/realtime-tools.ts
  • examples/ts-react-chat/src/lib/server-audio-adapters.ts
  • examples/ts-react-chat/src/lib/server-fns.ts
  • examples/ts-react-chat/src/routeTree.gen.ts
  • examples/ts-react-chat/src/routes/api.generate.image.ts
  • examples/ts-react-chat/src/routes/api.generate.video.ts
  • examples/ts-react-chat/src/routes/api.interrupts.ts
  • examples/ts-react-chat/src/routes/api.mcp-apps-call.ts
  • examples/ts-react-chat/src/routes/api.mcp-manual.ts
  • examples/ts-react-chat/src/routes/api.mcp-pool.ts
  • examples/ts-react-chat/src/routes/api.structured-output.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-react-chat/src/routes/example.runtime-context.tsx
  • examples/ts-react-chat/src/routes/generation-hooks.tsx
  • examples/ts-react-chat/src/routes/generations.audio.tsx
  • examples/ts-react-chat/src/routes/generations.image.tsx
  • examples/ts-react-chat/src/routes/generations.speech.tsx
  • examples/ts-react-chat/src/routes/generations.structured-chat.tsx
  • examples/ts-react-chat/src/routes/generations.structured-output.tsx
  • examples/ts-react-chat/src/routes/generations.summarize.tsx
  • examples/ts-react-chat/src/routes/generations.video.tsx
  • examples/ts-react-chat/src/routes/image-tool-repro.tsx
  • examples/ts-react-chat/src/routes/interrupts.tsx
  • examples/ts-react-chat/src/routes/issue-176-tool-result.tsx
  • examples/ts-react-chat/src/routes/mcp-apps.tsx
  • examples/ts-react-chat/src/routes/mcp-demo.tsx
  • examples/ts-react-chat/src/routes/realtime.tsx
  • examples/ts-react-chat/src/routes/threads.tsx
  • packages/ai-acp/tests/compatible.test.ts
  • packages/ai-acp/tests/sandbox-provisioning.test.ts
  • packages/ai-angular/package.json
  • packages/ai-angular/src/inject-chat.ts
  • packages/ai-angular/src/inject-generate-audio.ts
  • packages/ai-angular/src/inject-generate-image.ts
  • packages/ai-angular/src/inject-generate-speech.ts
  • packages/ai-angular/src/inject-generate-video.ts
  • packages/ai-angular/src/inject-generation.ts
  • packages/ai-angular/src/inject-summarize.ts
  • packages/ai-angular/src/inject-transcription.ts
  • packages/ai-angular/src/types.ts
  • packages/ai-angular/tests/inject-audio-recorder.test.ts
  • packages/ai-angular/tests/inject-chat-types.test.ts
  • packages/ai-angular/tests/inject-chat.test.ts
  • packages/ai-angular/tests/inject-generation.test.ts
  • packages/ai-angular/tests/test-utils.ts
  • packages/ai-anthropic/tests/anthropic-adapter.test.ts
  • packages/ai-client/package.json
  • packages/ai-client/src/chat-client.ts
  • packages/ai-client/src/chat-persistence-controller.ts
  • packages/ai-client/src/cleared-stream-tracker.ts
  • packages/ai-client/src/client-persistor.ts
  • packages/ai-client/src/connection-adapters.ts
  • packages/ai-client/src/generation-client.ts
  • packages/ai-client/src/generation-types.ts
  • packages/ai-client/src/index.ts
  • packages/ai-client/src/interrupt-manager.ts
  • packages/ai-client/src/interrupt-recovery-parse.ts
  • packages/ai-client/src/storage-adapters.ts
  • packages/ai-client/src/types.ts
  • packages/ai-client/src/video-generation-client.ts
  • packages/ai-client/tests/chat-client-interrupt-correlation.test.ts
  • packages/ai-client/tests/chat-client-interrupts.test.ts
  • packages/ai-client/tests/chat-client-resume.test.ts
  • packages/ai-client/tests/chat-client.test.ts
  • packages/ai-client/tests/chat-persistence-controller.test.ts
  • packages/ai-client/tests/cleared-stream-tracker.test.ts
  • packages/ai-client/tests/client-persistor.test.ts
  • packages/ai-client/tests/connection-adapters-xhr.test.ts
  • packages/ai-client/tests/generation-client.test.ts
  • packages/ai-client/tests/generation-resume-state.test.ts
  • packages/ai-client/tests/interrupts-types.test-d.ts
  • packages/ai-client/tests/storage-adapters-types.test-d.ts
  • packages/ai-client/tests/storage-adapters.test.ts
  • packages/ai-client/tests/test-utils.ts
  • packages/ai-event-client/src/index.ts
  • packages/ai-event-client/tests/emit.test.ts
  • packages/ai-event-client/tests/generation-events-types.test-d.ts
  • packages/ai-gemini/tests/text-interactions-adapter.test.ts
  • packages/ai-openrouter/tests/openrouter-responses-adapter.test.ts
  • packages/ai-preact/src/types.ts
  • packages/ai-preact/src/use-chat.ts
  • packages/ai-preact/tests/mcp-apps.test.tsx
  • packages/ai-preact/tests/test-utils.ts
  • packages/ai-preact/tests/use-chat-types.test.ts
  • packages/ai-preact/tests/use-chat.test.ts
  • packages/ai-react/src/types.ts
  • packages/ai-react/src/use-chat.ts
  • packages/ai-react/src/use-generate-audio.ts
  • packages/ai-react/src/use-generate-image.ts
  • packages/ai-react/src/use-generate-speech.ts
  • packages/ai-react/src/use-generate-video.ts
  • packages/ai-react/src/use-generation.ts
  • packages/ai-react/src/use-summarize.ts
  • packages/ai-react/src/use-transcription.ts
  • packages/ai-react/tests/mcp-apps.test.tsx
  • packages/ai-react/tests/test-utils.ts
  • packages/ai-react/tests/use-chat-fetcher.test.ts
  • packages/ai-react/tests/use-chat-options-probe.test.ts
  • packages/ai-react/tests/use-chat-structured-output.test.ts
  • packages/ai-react/tests/use-chat-types.test.ts
  • packages/ai-react/tests/use-chat.test.ts
  • packages/ai-react/tests/use-generation.test.ts
  • packages/ai-solid/src/types.ts
  • packages/ai-solid/src/use-chat.ts
  • packages/ai-solid/src/use-generate-audio.ts
  • packages/ai-solid/src/use-generate-image.ts
  • packages/ai-solid/src/use-generate-speech.ts
  • packages/ai-solid/src/use-generate-video.ts
  • packages/ai-solid/src/use-generation.ts
  • packages/ai-solid/src/use-summarize.ts
  • packages/ai-solid/src/use-transcription.ts
  • packages/ai-solid/tests/test-utils.ts
  • packages/ai-solid/tests/use-chat-types.test.ts
  • packages/ai-solid/tests/use-chat.test.ts
  • packages/ai-solid/tests/use-generation.test.ts
  • packages/ai-svelte/src/create-chat.svelte.ts
  • packages/ai-svelte/src/create-generate-audio.svelte.ts
  • packages/ai-svelte/src/create-generate-image.svelte.ts
  • packages/ai-svelte/src/create-generate-speech.svelte.ts
  • packages/ai-svelte/src/create-generate-video.svelte.ts
  • packages/ai-svelte/src/create-generation.svelte.ts
  • packages/ai-svelte/src/create-summarize.svelte.ts
  • packages/ai-svelte/src/create-transcription.svelte.ts
  • packages/ai-svelte/src/types.ts
  • packages/ai-svelte/tests/create-chat-types.test.ts
  • packages/ai-svelte/tests/create-generation.test.ts
  • packages/ai-svelte/tests/test-utils.ts
  • packages/ai-svelte/tests/use-chat.test.ts
  • packages/ai-utils/src/base64.ts
  • packages/ai-utils/src/index.ts
  • packages/ai-utils/tests/base64.test.ts
  • packages/ai-vue/src/types.ts
  • packages/ai-vue/src/use-chat.ts
  • packages/ai-vue/src/use-generate-audio.ts
  • packages/ai-vue/src/use-generate-image.ts
  • packages/ai-vue/src/use-generate-speech.ts
  • packages/ai-vue/src/use-generate-video.ts
  • packages/ai-vue/src/use-generation.ts
  • packages/ai-vue/src/use-summarize.ts
  • packages/ai-vue/src/use-transcription.ts
  • packages/ai-vue/tests/test-utils.ts
  • packages/ai-vue/tests/use-chat-types.test.ts
  • packages/ai-vue/tests/use-chat.test.ts
  • packages/ai-vue/tests/use-generation.test.ts
  • packages/ai/package.json
  • packages/ai/skills/ai-core/tool-calling/SKILL.md
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/activities/chat/mcp/manager.ts
  • packages/ai/src/activities/chat/mcp/types.ts
  • packages/ai/src/activities/chat/messages.ts
  • packages/ai/src/activities/chat/middleware/index.ts
  • packages/ai/src/activities/chat/middleware/types.ts
  • packages/ai/src/activities/chat/stream/processor.ts
  • packages/ai/src/activities/chat/tools/approval-schema.ts
  • packages/ai/src/activities/chat/tools/json-schema-validator.ts
  • packages/ai/src/activities/chat/tools/tool-calls.ts
  • packages/ai/src/activities/chat/tools/tool-definition.ts
  • packages/ai/src/activities/generateAudio/index.ts
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateSpeech/index.ts
  • packages/ai/src/activities/generateTranscription/index.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/activities/generation-run.ts
  • packages/ai/src/activities/middleware/index.ts
  • packages/ai/src/activities/middleware/run.ts
  • packages/ai/src/activities/middleware/types.ts
  • packages/ai/src/activities/stream-generation-result.ts
  • packages/ai/src/client.ts
  • packages/ai/src/custom-events.ts
  • packages/ai/src/index.ts
  • packages/ai/src/interrupt-resume.ts
  • packages/ai/src/interrupt-serialization.ts
  • packages/ai/src/interrupts.ts
  • packages/ai/src/stream-to-response.ts
  • packages/ai/src/types.ts
  • packages/ai/src/utilities/chat-params.ts
  • packages/ai/tests/ag-ui-wire.test.ts
  • packages/ai/tests/agent-loop-strategies.test.ts
  • packages/ai/tests/chat-mcp-manager.test.ts
  • packages/ai/tests/chat-params.test.ts
  • packages/ai/tests/chat-structured-output-null-normalization.test.ts
  • packages/ai/tests/chat-structured-output-stream.test.ts
  • packages/ai/tests/chat.test.ts
  • packages/ai/tests/content-guard-middleware.test.ts
  • packages/ai/tests/custom-events.test.ts
  • packages/ai/tests/debug-logging-chat.test.ts
  • packages/ai/tests/devtools-system-prompt-mirror.test.ts
  • packages/ai/tests/extend-adapter.test.ts
  • packages/ai/tests/generation-params.test.ts
  • packages/ai/tests/generation-result-transforms.test.ts
  • packages/ai/tests/generation-run-identity-replay.test.ts
  • packages/ai/tests/interrupts-types.test-d.ts
  • packages/ai/tests/interrupts.test.ts
  • packages/ai/tests/lazy-tool-manager.test.ts
  • packages/ai/tests/message-updaters.test.ts
  • packages/ai/tests/messages.test.ts
  • packages/ai/tests/middleware.test.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • packages/ai/tests/multimodal-tool-result.test.ts
  • packages/ai/tests/strategies.test.ts
  • packages/ai/tests/stream-generation.test.ts
  • packages/ai/tests/stream-processor.test.ts
  • packages/ai/tests/stream-to-response.test.ts
  • packages/ai/tests/summarize-max-length.test.ts
  • packages/ai/tests/system-prompts.test.ts
  • packages/ai/tests/test-utils.ts
  • packages/ai/tests/tool-cache-middleware.test.ts
  • packages/ai/tests/tool-call-manager.test.ts
  • packages/ai/tests/tool-calls-null-input.test.ts
  • packages/ai/tests/tool-definition.test.ts
  • packages/ai/tests/tool-result.test.ts
  • packages/ai/tests/type-check.test.ts
  • packages/ai/tests/usage-cost-types.test.ts
  • pnpm-workspace.yaml
  • testing/e2e/src/components/AudioGenUI.tsx
  • testing/e2e/src/components/ChatUI.tsx
  • testing/e2e/src/components/ImageGenUI.tsx
  • testing/e2e/src/components/TTSUI.tsx
  • testing/e2e/src/components/TranscriptionUI.tsx
  • testing/e2e/src/components/VideoGenUI.tsx
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/media-providers.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routes/$provider/$feature.tsx
  • testing/e2e/src/routes/$provider/index.tsx
  • testing/e2e/src/routes/api.audio.stream.ts
  • testing/e2e/src/routes/api.audio.ts
  • testing/e2e/src/routes/api.chat.ts
  • testing/e2e/src/routes/api.image.stream.ts
  • testing/e2e/src/routes/api.image.ts
  • testing/e2e/src/routes/api.middleware-test.ts
  • testing/e2e/src/routes/api.multimodal-tool-result-wire.ts
  • testing/e2e/src/routes/api.openai-shell-skills-wire.ts
  • testing/e2e/src/routes/api.openrouter-web-tools-wire.ts
  • testing/e2e/src/routes/api.otel-usage.ts
  • testing/e2e/src/routes/api.tools-test.ts
  • testing/e2e/src/routes/api.tts.stream.ts
  • testing/e2e/src/routes/api.tts.ts
  • testing/e2e/src/routes/api.video.stream.ts
  • testing/e2e/src/routes/api.video.ts
  • testing/e2e/src/routes/devtools-generation-hooks.tsx
  • testing/e2e/src/routes/middleware-test.tsx
  • testing/e2e/src/routes/tools-test.tsx
  • testing/e2e/tests/tool-approval.spec.ts
💤 Files with no reviewable changes (4)
  • packages/ai-client/tests/client-persistor.test.ts
  • packages/ai-client/src/client-persistor.ts
  • packages/ai/src/stream-to-response.ts
  • examples/ts-react-chat/src/routes/realtime.tsx

Comment on lines +137 to +145
const stream = chat({
adapter: openaiText('gpt-5.5'),
messages: params.messages,
threadId: params.threadId,
runId: params.runId,
parentRunId: params.parentRunId,
...(params.resume ? { resume: params.resume } : {}),
tools: [deleteProject],
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the OpenAI adapter model-meta file and list its models
fd 'model-meta.ts' packages/ai-openai --exec cat {}

Repository: TanStack/ai

Length of output: 50367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n-- docs/architecture/approval-flow-processing.md --\n'
sed -n '125,155p' docs/architecture/approval-flow-processing.md

printf '\n-- docs/interrupts/tool-approval.md --\n'
sed -n '60,90p' docs/interrupts/tool-approval.md

printf '\n-- OpenAI model entries around gpt-5.5 --\n'
rg -n "GPT_5_5|GPT_CHAT_LATEST|GPT_5_5_PRO|openaiText\\('gpt-5.5'\\)" packages -n

Repository: TanStack/ai

Length of output: 8027


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('packages/ai-openai/src/model-meta.ts')
text = p.read_text()
for key in ["const GPT_5_5 =", "const GPT_CHAT_LATEST =", "export const OPENAI_CHAT_MODELS = ["]:
    idx = text.find(key)
    print(f"\n### {key} @ {idx}")
    if idx != -1:
        start = max(0, idx-250)
        end = min(len(text), idx+900)
        print(text[start:end])
PY

Repository: TanStack/ai

Length of output: 3717


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for path in [Path('docs/architecture/approval-flow-processing.md'), Path('docs/interrupts/tool-approval.md')]:
    print(f"\n## {path}")
    text = path.read_text().splitlines()
    for i, line in enumerate(text, 1):
        if "gpt-5.5" in line or "openaiText(" in line:
            for j in range(max(1, i-3), min(len(text), i+4)+1):
                print(f"{j:4}: {text[j-1]}")
            print()
PY

Repository: TanStack/ai

Length of output: 904


Use gpt-chat-latest in both snippets

Both docs examples still use openaiText('gpt-5.5'); switch them to the latest OpenAI chat model alias.

📍 Affects 2 files
  • docs/architecture/approval-flow-processing.md#L137-L145 (this comment)
  • docs/interrupts/tool-approval.md#L72-L80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/approval-flow-processing.md` around lines 137 - 145, Update
both documentation snippets—docs/architecture/approval-flow-processing.md lines
137-145 and docs/interrupts/tool-approval.md lines 72-80—to use the
gpt-chat-latest alias in their openaiText calls instead of gpt-5.5; no other
example behavior should change.

Source: Coding guidelines

Comment thread docs/config.json
Comment on lines 104 to 112
"addedAt": "2026-04-15",
"updatedAt": "2026-07-08"
"updatedAt": "2026-07-14"
},
{
"label": "Tool Approval Flow",
"to": "tools/tool-approval",
"addedAt": "2026-04-15",
"updatedAt": "2026-07-08"
"updatedAt": "2026-07-14"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update timestamps to today's date.

As per coding guidelines, when making substantive content changes or adding new pages, you must set or refresh the timestamps to today's date. The current date is July 20, 2026, but the updatedAt and addedAt fields modified in this PR are set to past dates (e.g., 2026-07-14, 2026-07-15, 2026-07-16, etc.).

Please update the modified updatedAt fields (and addedAt for newly added pages) to "2026-07-20".

Also applies to: 147-149, 181-183, 212-214, 361-363, 394-396, 437-439, 506-508, 874-876

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/config.json` around lines 104 - 112, Update the timestamp metadata for
all pages modified or added in this change, including the entries represented by
the visible Tool Approval Flow object and the referenced locations, to use
"2026-07-20" for updatedAt and, for newly added pages, addedAt as well.

Source: Coding guidelines

Comment thread packages/ai-client/tests/chat-client-interrupts.test.ts
Comment on lines +331 to +334
const resumeInterruptsUnsafe = (
resumeItems: Array<RunAgentResumeItem>,
state?: ChatResumeState,
) => client().resumeInterruptsUnsafe(resumeItems, state)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

resumeInterruptsUnsafe doesn't resync resumeState/interrupt signals after resolving.

Every other resume/interrupt-affecting async method in this file (sendMessage, append, reload, addToolApprovalResponse, and even the deprecated resumeInterrupts alias at Line 304-311) calls syncResumeState() after the underlying client call settles. resumeInterruptsUnsafe — the recommended non-deprecated API per the JSDoc on its deprecated sibling — skips this, so resumeState(), interrupts(), interruptErrors(), and resuming() can remain stale after a resume until an unrelated state change triggers a resync.

🐛 Proposed fix
   const resumeInterruptsUnsafe = (
     resumeItems: Array<RunAgentResumeItem>,
     state?: ChatResumeState,
-  ) => client().resumeInterruptsUnsafe(resumeItems, state)
+  ) => client().resumeInterruptsUnsafe(resumeItems, state).finally(syncResumeState)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resumeInterruptsUnsafe = (
resumeItems: Array<RunAgentResumeItem>,
state?: ChatResumeState,
) => client().resumeInterruptsUnsafe(resumeItems, state)
const resumeInterruptsUnsafe = (
resumeItems: Array<RunAgentResumeItem>,
state?: ChatResumeState,
) => client().resumeInterruptsUnsafe(resumeItems, state).finally(syncResumeState)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-solid/src/use-chat.ts` around lines 331 - 334, Update
resumeInterruptsUnsafe to await the underlying client().resumeInterruptsUnsafe
call, then invoke syncResumeState() after it settles, matching the other
resume/interrupt-affecting methods and keeping its existing arguments and return
behavior.

Comment thread packages/ai/src/activities/chat/index.ts
Comment on lines +36 to 38
/** Block new input while pending interrupts await resolution. */
hasPendingInterrupt?: boolean
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Send button stays enabled while hasPendingInterrupt is true.

handleSubmit correctly no-ops when hasPendingInterrupt, but the Send button's disabled={!input.trim()} (line 287) doesn't factor in the new flag, so it remains clickable. Since this component drives e2e assertions, a spec checking for a disabled Send button during a pending interrupt would fail even though submission is safely blocked.

🛠️ Suggested fix
-          disabled={!input.trim()}
+          disabled={!input.trim() || hasPendingInterrupt}

Also applies to: 52-52, 64-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/components/ChatUI.tsx` around lines 36 - 38, Update the Send
button’s disabled condition in ChatUI to include hasPendingInterrupt, so it is
disabled when an interrupt is pending or the input is empty. Keep handleSubmit’s
existing no-op guard and apply the same pending-interrupt state consistently to
the other affected input controls.

Comment thread testing/e2e/src/routes/$provider/$feature.tsx Outdated
Comment thread testing/e2e/tests/tool-approval.spec.ts Outdated
@@ -10,7 +10,7 @@ import {
import { providersFor } from './test-matrix'

for (const provider of providersFor('tool-approval')) {
test.describe(`${provider} tool-approval`, () => {
test.describe(`${provider} — tool-approval`, () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore the suite-title encoding.

The title contains — instead of the intended em dash , which degrades Playwright report readability.

Proposed fix
-  test.describe(`${provider} — tool-approval`, () => {
+  test.describe(`${provider} — tool-approval`, () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test.describe(`${provider} — tool-approval`, () => {
test.describe(`${provider} tool-approval`, () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/tests/tool-approval.spec.ts` at line 13, Update the suite title
in the tool-approval test.describe declaration to use the intended em dash
character (—) instead of the mojibake sequence (—); preserve the existing
provider interpolation and title text.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

♻️ Duplicate comments (1)
testing/e2e/tests/tool-approval.spec.ts (1)

13-13: 📐 Maintainability & Code Quality | 🟡 Minor | 💤 Low value

Restore the suite-title encoding.

The title contains — instead of the intended em dash , which degrades Playwright report readability.

🛠️ Proposed fix
-  test.describe(`${provider} — tool-approval`, () => {
+  test.describe(`${provider} — tool-approval`, () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/tests/tool-approval.spec.ts` at line 13, Restore the intended em
dash character in the suite title template within the tool-approval
test.describe declaration, replacing the mojibake `—` while preserving the
provider interpolation and remaining title text.
🧹 Nitpick comments (3)
packages/ai-react/src/use-chat.ts (1)

421-432: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Dead branching in resolveInterrupts — both arms are identical.

The typeof resolution === 'boolean' check has no effect; both branches call client.resolveInterrupts(resolution) with no distinction. Simplify by removing the conditional.

♻️ Suggested simplification
   const resolveInterrupts = useCallback(
     (
       resolution: boolean | ((interrupt: ChatInterrupt<TTools>) => undefined),
     ) => {
-      if (typeof resolution === 'boolean') {
-        client.resolveInterrupts(resolution)
-      } else {
-        client.resolveInterrupts(resolution)
-      }
+      client.resolveInterrupts(resolution)
     },
     [client],
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-react/src/use-chat.ts` around lines 421 - 432, Remove the
redundant typeof resolution conditional in resolveInterrupts and directly call
client.resolveInterrupts(resolution), preserving the existing callback signature
and client dependency.
packages/ai-react/src/types.ts (1)

210-213: 📐 Maintainability & Code Quality | 🔵 Trivial

Resolver return type => undefined is stricter than typical callback typing.

Typing the function-resolver branch as (interrupt: ChatInterrupt<TTools>) => undefined forces callers' arrow functions to literally evaluate to undefined; any other return value (even though it's discarded at runtime) will fail type-checking. Consider => void instead, which is the idiomatic TS pattern for callbacks whose return value is intentionally ignored, unless there's a good reason to hard-enforce undefined here.

♻️ Suggested typing change
   resolveInterrupts: {
     (approved: boolean): void
-    (resolver: (interrupt: ChatInterrupt<TTools>) => undefined): void
+    (resolver: (interrupt: ChatInterrupt<TTools>) => void): void
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-react/src/types.ts` around lines 210 - 213, Update the
function-resolver overload in resolveInterrupts to accept callbacks returning
void instead of requiring a literal undefined return. Keep the approved boolean
overload unchanged and preserve the existing ChatInterrupt<TTools> parameter
type.
docs/interrupts/generic.md (1)

14-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Page shows only the client-side flow; no server-side snippet for emitting a generic interrupt.

This page documents both the concept and a full client rendering example for generic interrupts, but never shows how a server route actually emits one (the responseSchema/interrupt outcome). Per docs guidelines, pages that "cover both server and client behavior" should include snippets for both halves — docs/architecture/approval-flow-processing.md does this for tool approvals (Server setup + client UI sections), but this page has no equivalent server example.

As per coding guidelines, "When a documentation page covers both server and client behavior, include snippets for both halves: the server endpoint and the client consumption."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/interrupts/generic.md` around lines 14 - 104, Add a concise server-side
example before or alongside the client section showing how a server route emits
a generic interrupt outcome, including a JSON-compatible responseSchema and
interrupt message/reason, then resumes or processes the submitted value
server-side. Keep the existing GenericInterruptForm and GenericInterrupts client
flow unchanged, and use the established generic interrupt/server API symbols
from the codebase rather than inventing a separate protocol.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/reference/functions/toolDefinition.md`:
- Around line 146-159: Mark the new resolveInterrupt example block in the
documentation as non-type-checked by changing its TypeScript fence to the
project’s `ts ignore` convention. Keep the existing branch-specific
resolveInterrupt examples unchanged.

In `@examples/ts-react-chat/src/routes/generations.structured-chat.tsx`:
- Line 243: Annotate the recipe variable in the structured chat generation flow
as Partial<Recipe> while retaining the existing part.data, part.partial, and
empty-object fallback behavior, so later property access such as recipe.cuisine
remains type-safe.

In `@examples/ts-react-chat/src/routes/generations.structured-output.tsx`:
- Line 236: Restore the type assertion on payload.value.object in the setResult
call within the streaming result update flow, casting it to the state’s expected
PartialResult-compatible type so the unknown StreamChunkPayload value satisfies
SetStateAction<PartialResult | null>.

In `@packages/ai-angular/src/inject-chat.ts`:
- Around line 275-278: Update the pendingInterrupts computed selector to return
interruptState().pendingInterrupts instead of interruptState().interrupts. Keep
the interrupts, interruptErrors, and resuming selectors unchanged so
hasPendingInterrupt and approval-gating flows receive only pending interrupts.

In `@packages/ai-preact/src/use-chat.ts`:
- Around line 58-65: Initialize interruptState from the initial resume snapshot
synchronously, matching resumeState and the Angular/Svelte hooks. Update the
useState initializer around interruptState to derive its value from the newly
constructed client’s getInterruptState() rather than hardcoded empty arrays,
while preserving the existing resuming and interrupt error state.

In `@packages/ai-svelte/tests/create-chat-types.test.ts`:
- Around line 232-233: Remove the duplicate Generic type alias declaration in
the test scope, retaining a single `type Generic = Extract<Interrupt, { kind:
'generic' }>` definition so the file typechecks without changing its usage.

In `@packages/ai/src/activities/chat/tools/approval-schema.ts`:
- Around line 198-205: Update hashSchemaInput so non-JSON-convertible schemas do
not all use the constant standardValidator identity. Preserve the
undefined-schema hash behavior, but for schemas where
schemaToWire(schema).jsonSchema is absent, either reject hashing explicitly or
derive the identity from schema-specific stable metadata such as vendor and a
caller-supplied version/id, ensuring schema changes produce different hashes for
interrupt resume drift checks.

In `@packages/ai/src/activities/chat/tools/json-schema-validator.ts`:
- Around line 105-138: Refactor compileJsonSchema202012 to reuse a module-scoped
Ajv2020 instance with the existing options and full-format registration, rather
than constructing Ajv2020 and calling addFormats on every invocation. Keep
schema compilation and JsonSchemaCompilationError handling within
compileJsonSchema202012, using the shared instance to compile each independent
schema.

In `@packages/ai/src/activities/chat/tools/tool-calls.ts`:
- Around line 455-461: Preserve explicit null tool-argument edits by updating
editedApprovalArgs to return a wrapper such as { value: unknown } when the
approved resolution has its own editedArgs property, including null, and
undefined otherwise. At packages/ai/src/activities/chat/tools/tool-calls.ts
lines 455-461, check for the editedArgs property explicitly; at lines 845-852
and 917-924, replace nullish-coalescing fallback with the wrapper-unwrapping
logic so input is updated whenever an edit exists, including an intentional
null.

---

Duplicate comments:
In `@testing/e2e/tests/tool-approval.spec.ts`:
- Line 13: Restore the intended em dash character in the suite title template
within the tool-approval test.describe declaration, replacing the mojibake `—`
while preserving the provider interpolation and remaining title text.

---

Nitpick comments:
In `@docs/interrupts/generic.md`:
- Around line 14-104: Add a concise server-side example before or alongside the
client section showing how a server route emits a generic interrupt outcome,
including a JSON-compatible responseSchema and interrupt message/reason, then
resumes or processes the submitted value server-side. Keep the existing
GenericInterruptForm and GenericInterrupts client flow unchanged, and use the
established generic interrupt/server API symbols from the codebase rather than
inventing a separate protocol.

In `@packages/ai-react/src/types.ts`:
- Around line 210-213: Update the function-resolver overload in
resolveInterrupts to accept callbacks returning void instead of requiring a
literal undefined return. Keep the approved boolean overload unchanged and
preserve the existing ChatInterrupt<TTools> parameter type.

In `@packages/ai-react/src/use-chat.ts`:
- Around line 421-432: Remove the redundant typeof resolution conditional in
resolveInterrupts and directly call client.resolveInterrupts(resolution),
preserving the existing callback signature and client dependency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 300877ad-3d76-45c8-be03-b57ded65d2e7

📥 Commits

Reviewing files that changed from the base of the PR and between 7c7aa09 and 0883ecb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (268)
  • .changeset/ag-ui-interrupts.md
  • docs/architecture/approval-flow-processing.md
  • docs/config.json
  • docs/interrupts/generic.md
  • docs/interrupts/migration.md
  • docs/interrupts/multiple.md
  • docs/interrupts/overview.md
  • docs/interrupts/tool-approval.md
  • docs/reference/functions/toolDefinition.md
  • docs/tools/client-tools.md
  • docs/tools/tool-approval.md
  • examples/ts-code-mode-web/src/routes/_database-demo/api.database-demo.ts
  • examples/ts-react-chat/.env.example
  • examples/ts-react-chat/README.md
  • examples/ts-react-chat/package.json
  • examples/ts-react-chat/src/components/Header.tsx
  • examples/ts-react-chat/src/lib/guitar-tools.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/InterruptLabPage.tsx
  • examples/ts-react-chat/src/lib/interrupt-lab/client-ui.test.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/client-ui.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/scenarios.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/server.test.ts
  • examples/ts-react-chat/src/lib/interrupt-lab/server.ts
  • examples/ts-react-chat/src/lib/realtime-tools.ts
  • examples/ts-react-chat/src/lib/server-audio-adapters.ts
  • examples/ts-react-chat/src/lib/server-fns.ts
  • examples/ts-react-chat/src/routeTree.gen.ts
  • examples/ts-react-chat/src/routes/api.generate.image.ts
  • examples/ts-react-chat/src/routes/api.generate.video.ts
  • examples/ts-react-chat/src/routes/api.interrupts.ts
  • examples/ts-react-chat/src/routes/api.mcp-apps-call.ts
  • examples/ts-react-chat/src/routes/api.mcp-manual.ts
  • examples/ts-react-chat/src/routes/api.mcp-pool.ts
  • examples/ts-react-chat/src/routes/api.structured-output.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-react-chat/src/routes/example.runtime-context.tsx
  • examples/ts-react-chat/src/routes/generation-hooks.tsx
  • examples/ts-react-chat/src/routes/generations.audio.tsx
  • examples/ts-react-chat/src/routes/generations.image.tsx
  • examples/ts-react-chat/src/routes/generations.speech.tsx
  • examples/ts-react-chat/src/routes/generations.structured-chat.tsx
  • examples/ts-react-chat/src/routes/generations.structured-output.tsx
  • examples/ts-react-chat/src/routes/generations.summarize.tsx
  • examples/ts-react-chat/src/routes/generations.video.tsx
  • examples/ts-react-chat/src/routes/image-tool-repro.tsx
  • examples/ts-react-chat/src/routes/interrupts.tsx
  • examples/ts-react-chat/src/routes/issue-176-tool-result.tsx
  • examples/ts-react-chat/src/routes/mcp-apps.tsx
  • examples/ts-react-chat/src/routes/mcp-demo.tsx
  • examples/ts-react-chat/src/routes/realtime.tsx
  • examples/ts-react-chat/src/routes/threads.tsx
  • packages/ai-acp/tests/compatible.test.ts
  • packages/ai-acp/tests/sandbox-provisioning.test.ts
  • packages/ai-angular/package.json
  • packages/ai-angular/src/inject-chat.ts
  • packages/ai-angular/src/inject-generate-audio.ts
  • packages/ai-angular/src/inject-generate-image.ts
  • packages/ai-angular/src/inject-generate-speech.ts
  • packages/ai-angular/src/inject-generate-video.ts
  • packages/ai-angular/src/inject-generation.ts
  • packages/ai-angular/src/inject-summarize.ts
  • packages/ai-angular/src/inject-transcription.ts
  • packages/ai-angular/src/types.ts
  • packages/ai-angular/tests/inject-audio-recorder.test.ts
  • packages/ai-angular/tests/inject-chat-types.test.ts
  • packages/ai-angular/tests/inject-chat.test.ts
  • packages/ai-angular/tests/inject-generation.test.ts
  • packages/ai-angular/tests/test-utils.ts
  • packages/ai-anthropic/tests/anthropic-adapter.test.ts
  • packages/ai-client/package.json
  • packages/ai-client/src/chat-client.ts
  • packages/ai-client/src/chat-persistence-controller.ts
  • packages/ai-client/src/cleared-stream-tracker.ts
  • packages/ai-client/src/client-persistor.ts
  • packages/ai-client/src/connection-adapters.ts
  • packages/ai-client/src/generation-client.ts
  • packages/ai-client/src/generation-types.ts
  • packages/ai-client/src/index.ts
  • packages/ai-client/src/interrupt-manager.ts
  • packages/ai-client/src/interrupt-recovery-parse.ts
  • packages/ai-client/src/storage-adapters.ts
  • packages/ai-client/src/types.ts
  • packages/ai-client/src/video-generation-client.ts
  • packages/ai-client/tests/chat-client-interrupt-correlation.test.ts
  • packages/ai-client/tests/chat-client-interrupts.test.ts
  • packages/ai-client/tests/chat-client-resume.test.ts
  • packages/ai-client/tests/chat-client.test.ts
  • packages/ai-client/tests/chat-persistence-controller.test.ts
  • packages/ai-client/tests/cleared-stream-tracker.test.ts
  • packages/ai-client/tests/client-persistor.test.ts
  • packages/ai-client/tests/connection-adapters-xhr.test.ts
  • packages/ai-client/tests/generation-client.test.ts
  • packages/ai-client/tests/generation-resume-state.test.ts
  • packages/ai-client/tests/interrupts-types.test-d.ts
  • packages/ai-client/tests/storage-adapters-types.test-d.ts
  • packages/ai-client/tests/storage-adapters.test.ts
  • packages/ai-client/tests/test-utils.ts
  • packages/ai-event-client/src/index.ts
  • packages/ai-event-client/tests/emit.test.ts
  • packages/ai-event-client/tests/generation-events-types.test-d.ts
  • packages/ai-gemini/tests/text-interactions-adapter.test.ts
  • packages/ai-openrouter/tests/openrouter-responses-adapter.test.ts
  • packages/ai-preact/src/types.ts
  • packages/ai-preact/src/use-chat.ts
  • packages/ai-preact/tests/mcp-apps.test.tsx
  • packages/ai-preact/tests/test-utils.ts
  • packages/ai-preact/tests/use-chat-types.test.ts
  • packages/ai-preact/tests/use-chat.test.ts
  • packages/ai-react/src/types.ts
  • packages/ai-react/src/use-chat.ts
  • packages/ai-react/src/use-generate-audio.ts
  • packages/ai-react/src/use-generate-image.ts
  • packages/ai-react/src/use-generate-speech.ts
  • packages/ai-react/src/use-generate-video.ts
  • packages/ai-react/src/use-generation.ts
  • packages/ai-react/src/use-summarize.ts
  • packages/ai-react/src/use-transcription.ts
  • packages/ai-react/tests/mcp-apps.test.tsx
  • packages/ai-react/tests/test-utils.ts
  • packages/ai-react/tests/use-chat-fetcher.test.ts
  • packages/ai-react/tests/use-chat-options-probe.test.ts
  • packages/ai-react/tests/use-chat-structured-output.test.ts
  • packages/ai-react/tests/use-chat-types.test.ts
  • packages/ai-react/tests/use-chat.test.ts
  • packages/ai-react/tests/use-generation.test.ts
  • packages/ai-solid/src/types.ts
  • packages/ai-solid/src/use-chat.ts
  • packages/ai-solid/src/use-generate-audio.ts
  • packages/ai-solid/src/use-generate-image.ts
  • packages/ai-solid/src/use-generate-speech.ts
  • packages/ai-solid/src/use-generate-video.ts
  • packages/ai-solid/src/use-generation.ts
  • packages/ai-solid/src/use-summarize.ts
  • packages/ai-solid/src/use-transcription.ts
  • packages/ai-solid/tests/test-utils.ts
  • packages/ai-solid/tests/use-chat-types.test.ts
  • packages/ai-solid/tests/use-chat.test.ts
  • packages/ai-solid/tests/use-generation.test.ts
  • packages/ai-svelte/src/create-chat.svelte.ts
  • packages/ai-svelte/src/create-generate-audio.svelte.ts
  • packages/ai-svelte/src/create-generate-image.svelte.ts
  • packages/ai-svelte/src/create-generate-speech.svelte.ts
  • packages/ai-svelte/src/create-generate-video.svelte.ts
  • packages/ai-svelte/src/create-generation.svelte.ts
  • packages/ai-svelte/src/create-summarize.svelte.ts
  • packages/ai-svelte/src/create-transcription.svelte.ts
  • packages/ai-svelte/src/types.ts
  • packages/ai-svelte/tests/create-chat-types.test.ts
  • packages/ai-svelte/tests/create-generation.test.ts
  • packages/ai-svelte/tests/test-utils.ts
  • packages/ai-svelte/tests/use-chat.test.ts
  • packages/ai-utils/src/base64.ts
  • packages/ai-utils/src/index.ts
  • packages/ai-utils/tests/base64.test.ts
  • packages/ai-vue/src/types.ts
  • packages/ai-vue/src/use-chat.ts
  • packages/ai-vue/src/use-generate-audio.ts
  • packages/ai-vue/src/use-generate-image.ts
  • packages/ai-vue/src/use-generate-speech.ts
  • packages/ai-vue/src/use-generate-video.ts
  • packages/ai-vue/src/use-generation.ts
  • packages/ai-vue/src/use-summarize.ts
  • packages/ai-vue/src/use-transcription.ts
  • packages/ai-vue/tests/test-utils.ts
  • packages/ai-vue/tests/use-chat-types.test.ts
  • packages/ai-vue/tests/use-chat.test.ts
  • packages/ai-vue/tests/use-generation.test.ts
  • packages/ai/package.json
  • packages/ai/skills/ai-core/tool-calling/SKILL.md
  • packages/ai/src/activities/chat/index.ts
  • packages/ai/src/activities/chat/mcp/manager.ts
  • packages/ai/src/activities/chat/mcp/types.ts
  • packages/ai/src/activities/chat/messages.ts
  • packages/ai/src/activities/chat/middleware/index.ts
  • packages/ai/src/activities/chat/middleware/types.ts
  • packages/ai/src/activities/chat/stream/processor.ts
  • packages/ai/src/activities/chat/tools/approval-schema.ts
  • packages/ai/src/activities/chat/tools/json-schema-validator.ts
  • packages/ai/src/activities/chat/tools/tool-calls.ts
  • packages/ai/src/activities/chat/tools/tool-definition.ts
  • packages/ai/src/activities/generateAudio/index.ts
  • packages/ai/src/activities/generateImage/index.ts
  • packages/ai/src/activities/generateSpeech/index.ts
  • packages/ai/src/activities/generateTranscription/index.ts
  • packages/ai/src/activities/generateVideo/index.ts
  • packages/ai/src/activities/generation-run.ts
  • packages/ai/src/activities/middleware/index.ts
  • packages/ai/src/activities/middleware/run.ts
  • packages/ai/src/activities/middleware/types.ts
  • packages/ai/src/activities/stream-generation-result.ts
  • packages/ai/src/client.ts
  • packages/ai/src/custom-events.ts
  • packages/ai/src/index.ts
  • packages/ai/src/interrupt-resume.ts
  • packages/ai/src/interrupt-serialization.ts
  • packages/ai/src/interrupts.ts
  • packages/ai/src/stream-to-response.ts
  • packages/ai/src/types.ts
  • packages/ai/src/utilities/chat-params.ts
  • packages/ai/tests/ag-ui-wire.test.ts
  • packages/ai/tests/agent-loop-strategies.test.ts
  • packages/ai/tests/chat-mcp-manager.test.ts
  • packages/ai/tests/chat-params.test.ts
  • packages/ai/tests/chat-structured-output-null-normalization.test.ts
  • packages/ai/tests/chat-structured-output-stream.test.ts
  • packages/ai/tests/chat.test.ts
  • packages/ai/tests/content-guard-middleware.test.ts
  • packages/ai/tests/custom-events.test.ts
  • packages/ai/tests/debug-logging-chat.test.ts
  • packages/ai/tests/devtools-system-prompt-mirror.test.ts
  • packages/ai/tests/extend-adapter.test.ts
  • packages/ai/tests/generation-params.test.ts
  • packages/ai/tests/generation-result-transforms.test.ts
  • packages/ai/tests/generation-run-identity-replay.test.ts
  • packages/ai/tests/interrupts-types.test-d.ts
  • packages/ai/tests/interrupts.test.ts
  • packages/ai/tests/lazy-tool-manager.test.ts
  • packages/ai/tests/message-updaters.test.ts
  • packages/ai/tests/messages.test.ts
  • packages/ai/tests/middleware.test.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • packages/ai/tests/multimodal-tool-result.test.ts
  • packages/ai/tests/strategies.test.ts
  • packages/ai/tests/stream-generation.test.ts
  • packages/ai/tests/stream-processor.test.ts
  • packages/ai/tests/stream-to-response.test.ts
  • packages/ai/tests/summarize-max-length.test.ts
  • packages/ai/tests/system-prompts.test.ts
  • packages/ai/tests/test-utils.ts
  • packages/ai/tests/tool-cache-middleware.test.ts
  • packages/ai/tests/tool-call-manager.test.ts
  • packages/ai/tests/tool-calls-null-input.test.ts
  • packages/ai/tests/tool-definition.test.ts
  • packages/ai/tests/tool-result.test.ts
  • packages/ai/tests/type-check.test.ts
  • packages/ai/tests/usage-cost-types.test.ts
  • pnpm-workspace.yaml
  • testing/e2e/src/components/AudioGenUI.tsx
  • testing/e2e/src/components/ChatUI.tsx
  • testing/e2e/src/components/ImageGenUI.tsx
  • testing/e2e/src/components/TTSUI.tsx
  • testing/e2e/src/components/TranscriptionUI.tsx
  • testing/e2e/src/components/VideoGenUI.tsx
  • testing/e2e/src/lib/feature-support.ts
  • testing/e2e/src/lib/features.ts
  • testing/e2e/src/lib/media-providers.ts
  • testing/e2e/src/lib/types.ts
  • testing/e2e/src/routes/$provider/$feature.tsx
  • testing/e2e/src/routes/$provider/index.tsx
  • testing/e2e/src/routes/api.audio.stream.ts
  • testing/e2e/src/routes/api.audio.ts
  • testing/e2e/src/routes/api.chat.ts
  • testing/e2e/src/routes/api.image.stream.ts
  • testing/e2e/src/routes/api.image.ts
  • testing/e2e/src/routes/api.middleware-test.ts
  • testing/e2e/src/routes/api.multimodal-tool-result-wire.ts
  • testing/e2e/src/routes/api.openai-shell-skills-wire.ts
  • testing/e2e/src/routes/api.openrouter-web-tools-wire.ts
  • testing/e2e/src/routes/api.otel-usage.ts
  • testing/e2e/src/routes/api.tools-test.ts
  • testing/e2e/src/routes/api.tts.stream.ts
  • testing/e2e/src/routes/api.tts.ts
  • testing/e2e/src/routes/api.video.stream.ts
  • testing/e2e/src/routes/api.video.ts
  • testing/e2e/src/routes/devtools-generation-hooks.tsx
  • testing/e2e/src/routes/middleware-test.tsx
  • testing/e2e/src/routes/tools-test.tsx
  • testing/e2e/tests/tool-approval.spec.ts
💤 Files with no reviewable changes (4)
  • packages/ai-client/src/client-persistor.ts
  • packages/ai-client/tests/client-persistor.test.ts
  • examples/ts-react-chat/src/routes/realtime.tsx
  • packages/ai/src/stream-to-response.ts

Comment thread docs/reference/functions/toolDefinition.md
// Both fields are typed via the schema, so `recipe.title` etc. accesses
// below are checked at compile time.
const recipe = part.data ?? part.partial ?? ({} as Partial<Recipe>)
const recipe = part.data ?? part.partial ?? {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix TypeScript inference for the fallback object.

Removing the as Partial<Recipe> cast is good, but without an explicit type annotation on the variable, TypeScript infers the fallback {} as an empty object type. Accessing properties like recipe.cuisine later will cause a type error under strict mode because {} has no properties.

Explicitly annotating the variable as Partial<Recipe> resolves the type error safely.

💻 Proposed fix
-  const recipe = part.data ?? part.partial ?? {}
+  const recipe: Partial<Recipe> = part.data ?? part.partial ?? {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const recipe = part.data ?? part.partial ?? {}
const recipe: Partial<Recipe> = part.data ?? part.partial ?? {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ts-react-chat/src/routes/generations.structured-chat.tsx` at line
243, Annotate the recipe variable in the structured chat generation flow as
Partial<Recipe> while retaining the existing part.data, part.partial, and
empty-object fallback behavior, so later property access such as recipe.cuisine
remains type-safe.

Comment thread examples/ts-react-chat/src/routes/generations.structured-output.tsx Outdated
Comment on lines +275 to +278
const interrupts = computed(() => interruptState().interrupts)
const pendingInterrupts = computed(() => interruptState().interrupts)
const interruptErrors = computed(() => interruptState().interruptErrors)
const resuming = computed(() => interruptState().resuming)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

pendingInterrupts selector returns the wrong field.

Line 276 reads interruptState().interrupts instead of interruptState().pendingInterrupts. Per the ChatInterruptState contract (interrupts, pendingInterrupts, interruptErrors, resuming are distinct fields), this makes the exposed pendingInterrupts signal identical to interrupts — consumers gating UI on "still awaiting resolution" interrupts will see already-resolved ones too, breaking approval-gating flows that this PR's ChatUI relies on (hasPendingInterrupt).

🐛 Proposed fix
-  const pendingInterrupts = computed(() => interruptState().interrupts)
+  const pendingInterrupts = computed(() => interruptState().pendingInterrupts)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const interrupts = computed(() => interruptState().interrupts)
const pendingInterrupts = computed(() => interruptState().interrupts)
const interruptErrors = computed(() => interruptState().interruptErrors)
const resuming = computed(() => interruptState().resuming)
const interrupts = computed(() => interruptState().interrupts)
const pendingInterrupts = computed(() => interruptState().pendingInterrupts)
const interruptErrors = computed(() => interruptState().interruptErrors)
const resuming = computed(() => interruptState().resuming)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-angular/src/inject-chat.ts` around lines 275 - 278, Update the
pendingInterrupts computed selector to return interruptState().pendingInterrupts
instead of interruptState().interrupts. Keep the interrupts, interruptErrors,
and resuming selectors unchanged so hasPendingInterrupt and approval-gating
flows receive only pending interrupts.

Comment on lines +58 to +65
const [interruptState, setInterruptState] = useState<
ChatInterruptState<TTools>
>(() => ({
interrupts: EMPTY_INTERRUPTS,
pendingInterrupts: EMPTY_INTERRUPTS,
interruptErrors: EMPTY_INTERRUPT_ERRORS,
resuming: false,
}))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

interruptState isn't seeded from initialResumeSnapshot, unlike resumeState.

resumeState correctly bootstraps from options.initialResumeSnapshot?.resumeState (line 56), but interruptState is always initialized to empty arrays regardless of a supplied resume snapshot. It only gets corrected once the mount useEffect calls syncResumeState(client) (line 295), so the first render shows no pending interrupts even when one was supplied.

Contrast with packages/ai-angular/tests/inject-chat.test.ts and packages/ai-svelte/tests/use-chat.test.ts (both in this PR): both assert interrupts reflects the staged/error interrupts from initialResumeSnapshot immediately, with no flush()/tick() beforehand — i.e., siblings project this state synchronously, this hook does not.

Based on the Angular and Svelte test expectations for synchronous interrupt-state projection from initialResumeSnapshot (packages/ai-angular/tests/inject-chat.test.ts lines 23-51, packages/ai-svelte/tests/use-chat.test.ts lines 21-48), this hook should seed interruptState the same way, likely by deriving the initial value from the newly constructed client.getInterruptState() rather than a hardcoded empty default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-preact/src/use-chat.ts` around lines 58 - 65, Initialize
interruptState from the initial resume snapshot synchronously, matching
resumeState and the Angular/Svelte hooks. Update the useState initializer around
interruptState to derive its value from the newly constructed client’s
getInterruptState() rather than hardcoded empty arrays, while preserving the
existing resuming and interrupt error state.

Comment on lines +232 to +233
type Generic = Extract<Interrupt, { kind: 'generic' }>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Duplicate type Generic declaration — compile error.

type Generic = Extract<Interrupt, { kind: 'generic' }> is declared twice in the same scope, which TypeScript rejects as a duplicate identifier and will fail typecheck.

🐛 Proposed fix
     type Generic = Extract<Interrupt, { kind: 'generic' }>
-    type Generic = Extract<Interrupt, { kind: 'generic' }>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type Generic = Extract<Interrupt, { kind: 'generic' }>
type Generic = Extract<Interrupt, { kind: 'generic' }>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai-svelte/tests/create-chat-types.test.ts` around lines 232 - 233,
Remove the duplicate Generic type alias declaration in the test scope, retaining
a single `type Generic = Extract<Interrupt, { kind: 'generic' }>` definition so
the file typechecks without changing its usage.

Comment on lines +198 to +205
export function hashSchemaInput(schema: SchemaInput | undefined): string {
if (schema === undefined) return digestInterruptJson('undefined')
const normalized = schemaToWire(schema)
const identity = normalized.jsonSchema ?? {
standardValidator: 'unserialized',
}
return digestInterruptJson(canonicalInterruptJson(identity))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

hashSchemaInput collapses all non-JSON-convertible schemas to the same hash, defeating drift detection.

When schemaToWire(schema).jsonSchema is undefined (a StandardSchemaV1 that doesn't implement the JSON Schema extension), the identity used for hashing falls back to the constant { standardValidator: 'unserialized' }. Every such schema — regardless of actual shape — hashes to the exact same sha256:... value.

This is consumed in interrupt-resume.ts to detect schema drift: hashSchemaInput(tool.inputSchema) !== binding.inputSchemaHash (line 409) and hashSchemaInput(tool.outputSchema) !== binding.outputSchemaHash (line 377). For any tool whose input/output schema is a non-JSON-convertible standard schema, these checks can never fire — a tool's input/output schema could change entirely between the interrupt being issued and being resumed, and the "stale" detection would silently pass, letting resume proceed against a mismatched schema.

🛡️ Suggested direction
 export function hashSchemaInput(schema: SchemaInput | undefined): string {
   if (schema === undefined) return digestInterruptJson('undefined')
   const normalized = schemaToWire(schema)
-  const identity = normalized.jsonSchema ?? {
-    standardValidator: 'unserialized',
-  }
+  if (normalized.jsonSchema === undefined) {
+    throw new TypeError(
+      'Schema drift detection requires a JSON-Schema-convertible SchemaInput.',
+    )
+  }
+  const identity = normalized.jsonSchema
   return digestInterruptJson(canonicalInterruptJson(identity))
 }

Alternatively, incorporate something schema-specific (e.g. vendor + a caller-supplied version/id) into the identity instead of throwing, if rejecting non-convertible schemas outright is too disruptive.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function hashSchemaInput(schema: SchemaInput | undefined): string {
if (schema === undefined) return digestInterruptJson('undefined')
const normalized = schemaToWire(schema)
const identity = normalized.jsonSchema ?? {
standardValidator: 'unserialized',
}
return digestInterruptJson(canonicalInterruptJson(identity))
}
export function hashSchemaInput(schema: SchemaInput | undefined): string {
if (schema === undefined) return digestInterruptJson('undefined')
const normalized = schemaToWire(schema)
if (normalized.jsonSchema === undefined) {
throw new TypeError(
'Schema drift detection requires a JSON-Schema-convertible SchemaInput.',
)
}
const identity = normalized.jsonSchema
return digestInterruptJson(canonicalInterruptJson(identity))
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/activities/chat/tools/approval-schema.ts` around lines 198 -
205, Update hashSchemaInput so non-JSON-convertible schemas do not all use the
constant standardValidator identity. Preserve the undefined-schema hash
behavior, but for schemas where schemaToWire(schema).jsonSchema is absent,
either reject hashing explicitly or derive the identity from schema-specific
stable metadata such as vendor and a caller-supplied version/id, ensuring schema
changes produce different hashes for interrupt resume drift checks.

Comment on lines +105 to +138
export function compileJsonSchema202012(
schema: unknown,
): (value: unknown) => ReadonlyArray<JsonSchemaValidationIssue> {
assertSupportedSchemaTree(schema)
const ajv = new Ajv2020({
allErrors: true,
strict: true,
strictRequired: false,
allowUnionTypes: true,
validateFormats: true,
coerceTypes: false,
useDefaults: false,
removeAdditional: false,
})
addFormats(ajv, { mode: 'full' })

let validate
try {
validate = ajv.compile(schema)
} catch (cause) {
throw new JsonSchemaCompilationError('Invalid Draft 2020-12 schema.', {
cause,
})
}

return (value) => {
if (validate(value)) return []
return (validate.errors ?? []).map((error) => ({
keyword: error.keyword,
message: error.message ?? 'Schema validation failed.',
path: issuePath(error),
}))
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Reuse a single Ajv instance instead of constructing one per call.

compileJsonSchema202012 builds a brand-new Ajv2020 and re-registers all formats (addFormats(..., { mode: 'full' })) on every invocation. This function sits in a hot path — validateInterruptResumeBatch calls it (via validateSchemaValue) up to several times per interrupt item (payload, edited args, tool output, approval envelope), and ai-client's canValidateGeneric calls it on every hydration attempt. Constructing a fresh Ajv instance each time is unnecessary overhead since Ajv instances safely compile multiple independent (anonymous) schemas.

⚡ Suggested fix
+const ajv = new Ajv2020({
+  allErrors: true,
+  strict: true,
+  strictRequired: false,
+  allowUnionTypes: true,
+  validateFormats: true,
+  coerceTypes: false,
+  useDefaults: false,
+  removeAdditional: false,
+})
+addFormats(ajv, { mode: 'full' })
+
 export function compileJsonSchema202012(
   schema: unknown,
 ): (value: unknown) => ReadonlyArray<JsonSchemaValidationIssue> {
   assertSupportedSchemaTree(schema)
-  const ajv = new Ajv2020({
-    allErrors: true,
-    strict: true,
-    strictRequired: false,
-    allowUnionTypes: true,
-    validateFormats: true,
-    coerceTypes: false,
-    useDefaults: false,
-    removeAdditional: false,
-  })
-  addFormats(ajv, { mode: 'full' })
-
   let validate
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function compileJsonSchema202012(
schema: unknown,
): (value: unknown) => ReadonlyArray<JsonSchemaValidationIssue> {
assertSupportedSchemaTree(schema)
const ajv = new Ajv2020({
allErrors: true,
strict: true,
strictRequired: false,
allowUnionTypes: true,
validateFormats: true,
coerceTypes: false,
useDefaults: false,
removeAdditional: false,
})
addFormats(ajv, { mode: 'full' })
let validate
try {
validate = ajv.compile(schema)
} catch (cause) {
throw new JsonSchemaCompilationError('Invalid Draft 2020-12 schema.', {
cause,
})
}
return (value) => {
if (validate(value)) return []
return (validate.errors ?? []).map((error) => ({
keyword: error.keyword,
message: error.message ?? 'Schema validation failed.',
path: issuePath(error),
}))
}
}
const ajv = new Ajv2020({
allErrors: true,
strict: true,
strictRequired: false,
allowUnionTypes: true,
validateFormats: true,
coerceTypes: false,
useDefaults: false,
removeAdditional: false,
})
addFormats(ajv, { mode: 'full' })
export function compileJsonSchema202012(
schema: unknown,
): (value: unknown) => ReadonlyArray<JsonSchemaValidationIssue> {
assertSupportedSchemaTree(schema)
let validate
try {
validate = ajv.compile(schema)
} catch (cause) {
throw new JsonSchemaCompilationError('Invalid Draft 2020-12 schema.', {
cause,
})
}
return (value) => {
if (validate(value)) return []
return (validate.errors ?? []).map((error) => ({
keyword: error.keyword,
message: error.message ?? 'Schema validation failed.',
path: issuePath(error),
}))
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ai/src/activities/chat/tools/json-schema-validator.ts` around lines
105 - 138, Refactor compileJsonSchema202012 to reuse a module-scoped Ajv2020
instance with the existing options and full-format registration, rather than
constructing Ajv2020 and calling addFormats on every invocation. Keep schema
compilation and JsonSchemaCompilationError handling within
compileJsonSchema202012, using the shared instance to compile each independent
schema.

Comment thread packages/ai/src/activities/chat/tools/tool-calls.ts
jherr and others added 2 commits July 20, 2026 21:14
…rupts

The AG-UI interrupt lifecycle was extracted from a larger persistence stack
and shipped ephemeral-only, but carried persistence-shaped code that is never
exercised without a durable layer (which is not part of this package). Remove it.

Server (@tanstack/ai):
- Drop the never-provided InterruptPersistenceGateway capability
  (getInterruptPersistence/provideInterruptPersistence) and its input types
  (OpenInterruptBatchInput, CommitInterruptResolutionsInput). The two call
  sites now always take the ephemeral branch.
- Remove InterruptCommitResult, the InterruptResumeValidationError.recovery
  field, the recovery type guard, and the tanstack:interruptRecovery emission.

Client (@tanstack/ai-client):
- Remove the recovery state machine: getRecoveryState, getPersistedDrafts,
  reportRecoveryError, restorePersistedDrafts, recoverInterrupts,
  {start,recover}PersistedInterrupts, joinContinuationRun, the persisted-state
  branch of applyResumeSnapshot, and the conflict/replay branches of
  submitInterruptBatch (now a plain ephemeral submit).
- Delete interrupt-recovery-parse.ts; drop PersistedInterruptDraft and the
  ChatContinuationLoader option/type.

Kept as a dormant extension seam for a future persistence layer: the optional
ConnectionAdapter.loadInterruptState hook and its InterruptRecoveryStateV1 /
InterruptRecoveryQuery types.

Docs: trim the dangling withChatPersistence step and the persistence-only
migration checklist / recovery sections to match the ephemeral-only surface.

Runtime behavior is unchanged (persistence was never provided). Verified:
typecheck + lint clean across ai/ai-client/6 framework packages, full unit
suites green, docs links pass, and the interrupt lab round-trip re-verified
against the rebuilt dist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread docs/interrupts/overview.md Outdated
Comment on lines +44 to +50
**Client tools are not on this list.** Running a tool in the browser and
returning its result is not something you resolve by hand — a tool with a
`.client()` implementation runs automatically and reports its own result. See
[Client Tools](../tools/client-tools). Approval is a separate axis: add
`needsApproval: true` to any tool (server or client) and it pauses on a
`tool-approval` interrupt first; a client tool then runs automatically once
approved.

@tombeckenham tombeckenham Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why aren't client tools included? That isn't explained here

Option A slim for #970: restore generation-resume, example/e2e media noise,
import-only churn, and unrelated provider test touch-ups to main while keeping
the AG-UI interrupt lifecycle (engine, InterruptManager, client chat
persistence needed by ChatClient, framework useChat, interrupt lab, docs).

Residue snapshot: branch chore/non-interrupt-residue-from-970 (pre-slim tip).
Restore pure import/style test churn to main. Drop dedicated storage-adapter,
chat-persistence-controller, and cleared-stream-tracker unit tests from the
ephemeral interrupts PR. Thin chat-client-resume to interrupt resume behavior
only (drop persistence.server hydrate/persist matrix). Remove unused
fake-indexeddb devDependency.
Defer interrupt resume until the parent stream settles so auto-executed
client tools can continue. Surface client-tool validation failures, match
native CTE reasons, tighten binding types, and lock resume validation with
unit tests. Update docs/skill and e2e/example UIs to the bound interrupt API,
and link the Interrupts Lab from the chat example nav and home page.
… from interrupts PR

Restore main's ChatPersistor for optional message storage and keep
ClearedStreamTracker for clear-during-stream. Durable resume adapters
(storage-adapters, ChatPersistenceController, client/server persistence
options) already live on feat/persistence and are out of the ephemeral
interrupt surface.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
docs/architecture/approval-flow-processing.md (1)

139-139: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use gpt-chat-latest in documentation examples instead of gpt-5.5.

These documentation snippets use the superseded openaiText('gpt-5.5'). As per coding guidelines and previous reviews, switch them to the latest OpenAI chat model alias based on the adapter's model-meta.ts (e.g., gpt-chat-latest).

  • docs/architecture/approval-flow-processing.md#L139-L139: replace gpt-5.5 with gpt-chat-latest.
  • packages/ai/skills/ai-core/tool-calling/SKILL.md#L78-L78: replace gpt-5.5 with gpt-chat-latest.
  • packages/ai/skills/ai-core/tool-calling/SKILL.md#L681-L681: replace gpt-5.5 with gpt-chat-latest.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/approval-flow-processing.md` at line 139, Replace the
superseded gpt-5.5 model name with gpt-chat-latest in the documentation examples
at docs/architecture/approval-flow-processing.md:139-139,
packages/ai/skills/ai-core/tool-calling/SKILL.md:78-78, and
packages/ai/skills/ai-core/tool-calling/SKILL.md:681-681, preserving the
existing adapter usage.

Source: Coding guidelines

🧹 Nitpick comments (1)
testing/e2e/src/routes/tools-test.tsx (1)

464-487: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate lookup/cast logic in Approve and Deny handlers.

Both handlers repeat the exact same as ReadonlyArray<{...}> cast + .find() lookup for the matching tool-approval interrupt. The cast also erases whatever real (likely discriminated-union) type interrupts already has. Extract a shared helper using a type predicate instead of as so both call sites stay in sync and TypeScript actually narrows the result.

♻️ Proposed refactor
+  function findApprovalInterrupt(toolCallId: string) {
+    return interrupts.find(
+      (item): item is Extract<typeof item, { kind: 'tool-approval' }> =>
+        item.kind === 'tool-approval' && item.toolCallId === toolCallId,
+    )
+  }
+
   onClick={() => {
     const approvalId = tc.approval?.id
     if (!approvalId || respondedApprovals.current.has(approvalId)) {
       return
     }
-    const interrupt = (
-      interrupts as ReadonlyArray<{
-        kind: string
-        toolCallId?: string
-        resolveInterrupt: (approved: boolean) => void
-      }>
-    ).find(
-      (item) => item.kind === 'tool-approval' && item.toolCallId === tc.id,
-    )
+    const interrupt = findApprovalInterrupt(tc.id)
     if (!interrupt) return
     ...

(mirror the same change in the Deny handler at Line 511-522)

Also applies to: 506-529

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/tools-test.tsx` around lines 464 - 487, Extract the
repeated interrupt lookup from the Approve and Deny handlers into a shared
helper near the surrounding tool-approval logic. Have the helper search
interrupts for the matching toolCallId and tool-approval kind using a type
predicate, preserving the existing narrowed interrupt type without an `as
ReadonlyArray` cast; update both handlers to call this helper and retain their
existing approval/denial behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@docs/architecture/approval-flow-processing.md`:
- Line 139: Replace the superseded gpt-5.5 model name with gpt-chat-latest in
the documentation examples at
docs/architecture/approval-flow-processing.md:139-139,
packages/ai/skills/ai-core/tool-calling/SKILL.md:78-78, and
packages/ai/skills/ai-core/tool-calling/SKILL.md:681-681, preserving the
existing adapter usage.

---

Nitpick comments:
In `@testing/e2e/src/routes/tools-test.tsx`:
- Around line 464-487: Extract the repeated interrupt lookup from the Approve
and Deny handlers into a shared helper near the surrounding tool-approval logic.
Have the helper search interrupts for the matching toolCallId and tool-approval
kind using a type predicate, preserving the existing narrowed interrupt type
without an `as ReadonlyArray` cast; update both handlers to call this helper and
retain their existing approval/denial behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce8b6d71-6ade-4bec-a413-dc706126281f

📥 Commits

Reviewing files that changed from the base of the PR and between 8c4ef83 and 3f12314.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • docs/architecture/approval-flow-processing.md
  • docs/config.json
  • docs/interrupts/generic.md
  • docs/interrupts/migration.md
  • docs/interrupts/multiple.md
  • docs/tools/client-tools.md
  • examples/ts-react-chat/src/components/Header.tsx
  • examples/ts-react-chat/src/lib/interrupt-lab/InterruptLabPage.tsx
  • examples/ts-react-chat/src/lib/interrupt-lab/scenarios.ts
  • examples/ts-react-chat/src/routes/index.tsx
  • packages/ai-client/src/chat-client.ts
  • packages/ai-client/src/interrupt-manager.ts
  • packages/ai-client/src/types.ts
  • packages/ai-client/tests/chat-client-interrupts.test.ts
  • packages/ai-client/tests/chat-client-resume.test.ts
  • packages/ai/skills/ai-core/tool-calling/SKILL.md
  • packages/ai/src/activities/chat/tools/tool-calls.ts
  • packages/ai/src/interrupt-resume.ts
  • packages/ai/src/interrupts.ts
  • packages/ai/tests/interrupt-resume.test.ts
  • testing/e2e/src/components/ChatUI.tsx
  • testing/e2e/src/routes/$provider/$feature.tsx
  • testing/e2e/src/routes/tools-test.tsx
🚧 Files skipped from review as they are similar to previous changes (14)
  • docs/interrupts/multiple.md
  • docs/config.json
  • docs/tools/client-tools.md
  • docs/interrupts/migration.md
  • packages/ai/src/interrupts.ts
  • testing/e2e/src/routes/$provider/$feature.tsx
  • examples/ts-react-chat/src/lib/interrupt-lab/scenarios.ts
  • packages/ai-client/tests/chat-client-interrupts.test.ts
  • packages/ai-client/tests/chat-client-resume.test.ts
  • packages/ai-client/src/types.ts
  • packages/ai/src/interrupt-resume.ts
  • packages/ai/src/activities/chat/tools/tool-calls.ts
  • packages/ai-client/src/chat-client.ts
  • packages/ai-client/src/interrupt-manager.ts

…c lab runId

Ephemeral resume now reconstructs client-tool pending items even when the
client already wrote tool results into message history for UI. Align generic
lab interrupt ids with the terminal provider runId so parentRunId correlates,
surface interrupt validation messages in the lab sanitizer, and match the
home-page Interrupts tile to other demo tiles.
Resolve conflicts with type-safe tool call events (#452):
- Keep both InterruptSubmissionError and ProviderTool imports
- Keep interrupt resume deniedToolResults + surface input on declined results
- Keep AnyTool-based mergeAgentTools typing with RunAgentResumeItem for resume
SafeToolInput/Output treat schema generics defaulting to undefined as
unknown, and MCP manager tests use AnyServerTool so fixtures can carry
JSON Schema input without fighting the no-schema ServerTool default.

@tombeckenham tombeckenham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is looking good now. I've added a few nit pic comments, and had to fix a few things, but happy to approve

Comment thread docs/interrupts/tool-approval.md Outdated
import { transferTool } from '../tools/transfer'

export function TransferApprovals() {
const { interrupts, resuming } = useChat({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You'd probably want to render the chat in this example the approval ui would appear alongside or in the same chat

Comment thread docs/interrupts/generic.md Outdated
setErrors(['Enter valid JSON.'])
return
}
const result = z.fromJSONSchema(interrupt.responseSchema).safeParse(candidate)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Couldn't you pass value directly to safeParse?

schema: unknown,
): (value: unknown) => ReadonlyArray<JsonSchemaValidationIssue> {
assertSupportedSchemaTree(schema)
const ajv = new Ajv2020({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might be an expensive operation. The alternative is to initialize at module level, or create a factory but I never love that either. Happy to leave it

@@ -0,0 +1,138 @@
import Ajv2020 from 'ajv/dist/2020.js'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wondered whether we could just use zod. Zod is a peer dependency, but to keep it that way you've added arv as a direct dependency. I'm happy to keep it as arv is smaller, but maybe consider if we could just use zod. Alem and I discussed and happy to keep arv

tombeckenham and others added 14 commits July 21, 2026 19:39
Drop residual Prisma lock entries left from the persistence-era history.
Re-resolve only this branch's package.json deltas on top of main's lock.
- Drop unused PublicInterruptBinding and knip ignore for @standard-schema/spec
- Clean interrupt-manager shadowing / unnecessary conditionals / async
- Include serverPersistence in e2e provider links
- Use AnyServerTool for heterogeneous tool arrays in code-mode example
…arness

- Clear interrupt/resume state on successful continuation streams even when
  provider run ids diverge; always clear after a successful interrupt batch
- Immediately reflect approval decisions on tool-call message parts (#532)
- Hide submitting interrupts from the public list; block sends only on
  actionable pending/staged interrupts
- Register client delete_file needsApproval stub so tools-test hydrates
  tool-approval interrupts; match resolve by toolCallId/approval id
- Only render pending approval prompts in e2e ChatUI
Missing closing brace on the deny-button handler broke prettier parse.
…tovers

- rewrite the interrupt guides to lead with the problem then the API:
  overview explains what pauses a run and how client tools fit; tool-approval
  covers server and client tools and renders the approval inline in the chat;
  multiple contrasts per-item vs root-helper resolution; generic frames the
  application-pause use case
- remove stale references to a separately-shipped durable persistence layer
  from the interrupt, tools, and architecture docs (ephemeral-only in this PR)
- address review feedback: explain client tools in the overview, render the
  chat alongside the approval UI, and validate the generic value directly
- refresh docs/config.json updatedAt for the touched pages
Client-tool interrupt paths emit AG-UI MESSAGES_SNAPSHOT from ModelMessages,
which rebuild tool-call parts as input-complete without output and wipe the
complete/output state the client already applied from TOOL_CALL_END/RESULT.
Reconcile snapshots by folding tool-result content into matching tool-call
parts (and prefer pre-snapshot complete state when richer).
MESSAGES_SNAPSHOT reconciliation now folds tool-result into the
sibling tool-call; update the anchor test to match.
Avoid a mutated outer `changed` flag that control-flow analysis treats
as always falsy; detect part identity changes instead.
Replace the prose-only "when an answer is wrong" section in multiple.md with a
runnable component that renders item errors and root interruptErrors, gates
buttons on status and resuming (not just canResolve), and demonstrates both
recovery paths (clearResolution and retryInterrupts).
Add a "Consume the decision on the server" section to tool-approval.md: a
server execute snippet reading the (possibly edited) input, and an honest
account of the payload branches (reject payload becomes the tool result the
model reads; approve payload is decision metadata, not passed to execute, use
editedArgs for values the tool needs).
…noble/hashes

The library no longer transforms a generic interrupt's wire JSON Schema into a
validator or validates the resolved value against it, on the client or the
server. Values pass through as-is; the application validates them (e.g. with
z.fromJSONSchema on the client and its own check on the server). Validation of a
tool's code-authored Standard Schema (approvalSchema / inputSchema) is unchanged.

- remove the ajv-based json-schema-validator and the ajv / ajv-formats deps
- generic items are always resolvable; canResolve no longer depends on the
  library being able to compile the wire schema
- replace @noble/hashes with a small bundled SHA-256 (same sha256:<hex> wire
  shape), verified against known vectors; drop the dependency
- update the ts-react-chat interrupt lab to validate the generic payload itself
  with zod, and fix the docs that claimed the library validates
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.

3 participants