refactor(persistence)!: trim the public API surface to what apps call - #1034
Conversation
Generation persistence is server-driven, so the options and types that only existed to support a client-managed copy of a run are gone, along with a few exports no consumer ever named. - remove `initialResumeSnapshot` from every generation hook (7 hooks x 5 frameworks) and from `GenerationClient` / `VideoGenerationClient`; `useChat` keeps its own - unexport the generation hydration internals from `@tanstack/ai-client`: `GenerationResumeSnapshot` / `GenerationResumeState` / `GenerationResumeStatus` / `GenerationResultSnapshot` / `GenerationErrorSnapshot` / `GenerationEventSnapshot` / `parseGenerationResumeSnapshot` / `updateGenerationResumeSnapshot` / `ChatResumeSnapshot` - delete `GenerationPendingArtifact` and `GenerationPersistenceOption` (an alias for `boolean`) - collapse `ChatResumeSnapshotV1` / `ChatResumeSnapshotV2` into one shape with no `schemaVersion`: they were structurally identical, no reader branched on the version, and only V2 was written - drop the `TValue` generic from `localStoragePersistence` / `sessionStoragePersistence` / `indexedDBPersistence`; it existed so generation could share the chat adapters and had zero non-default instantiations - stop re-exporting `PersistedArtifactRef` from the framework packages, where no hook type refers to it - unexport `artifactBlobKey` in favour of `resolveArtifactBlobKey`, which its own docs already recommended for reads - delete `createInterruptController` / `InterruptController`, a five-method forwarder to the `interrupts` store with no caller outside its own test Tests: the base-hook "no auto-fire from a seeded running snapshot" guards are redundant with each framework's existing hydrated-running test and are removed; the video ones are converted to the surviving `persistence: true` + `hydrateGeneration` path so the coverage stays. Docs: - document the ctx-capability plumbing (`PersistenceCapability`, `InterruptsCapability`, `getPersistence`, `getInterrupts`, `providePersistence`, `provideInterrupts`) in persistence/internals - fix the `ai-core/client-persistence` skill frontmatter, which still described the removed client-driven generation mode its own body denies - split the 1513-line build-your-own-adapter page into build-your-own-adapter (shape, store choice, existing schemas, conformance), build-your-own-chat-adapter, build-your-own-generation-adapter, and store-reference; inbound links and anchors repointed, nav updated - drop every em dash and separator glyph from docs/persistence
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThe PR narrows generation persistence to server hydration, removes public generation resume APIs, unifies chat snapshots, limits storage adapters to chat state, and adds separate persistence adapter and store-reference guides. ChangesPersistence API and hydration
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ai-client/src/generation-client.ts (1)
145-198: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winPrevent server hydration during Svelte setup. Both clients rely on
mountDevtools()being called after client mount, but the supplied Svelte factories call it during setup. This can invokehydrateGenerationduring SSR after client-managed snapshot seeds are removed.
packages/ai-client/src/generation-client.ts#L145-L198: update the Svelte generation wiring so it callsmountDevtools()fromonMount, not setup.packages/ai-client/src/video-generation-client.ts#L149-L191: update the Svelte video-generation wiring so it callsmountDevtools()fromonMount, not setup.🤖 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 145 - 198, Update the Svelte generation wiring in packages/ai-client/src/generation-client.ts around the GenerationClient setup to invoke mountDevtools from Svelte’s onMount callback rather than during setup. Apply the same change to the Svelte video-generation wiring in packages/ai-client/src/video-generation-client.ts around the video client setup; both files must defer mountDevtools until onMount.
🧹 Nitpick comments (2)
packages/ai-solid/tests/use-generation.test.ts (1)
83-86: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftPlace the unit test beside the covered source.
This
*.test.tsfile is underpackages/ai-solid/tests/, but it coverspackages/ai-solid/src/use-generation.ts. Move the test beside the source module.As per coding guidelines, files matching
**/*.test.tsmust place unit tests alongside the source they cover.🤖 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/tests/use-generation.test.ts` around lines 83 - 86, Move the use-generation unit test file from the package-level tests directory to the directory containing the covered use-generation source module, keeping the test contents and behavior unchanged.Source: Coding guidelines
packages/ai-react/tests/use-generation.test.ts (1)
1024-1049: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffMove the changed hydration tests beside their source modules.
Both changed tests remain in package-level
tests/directories. Place each test in a colocated*.test.tsfile.
packages/ai-react/tests/use-generation.test.ts#L1024-L1049: move theuseGenerateVideohydration test besidepackages/ai-react/src/use-generate-video.ts.packages/ai-svelte/tests/create-generation.test.ts#L844-L864: move thecreateGenerateVideohydration test besidepackages/ai-svelte/src/create-generate-video.svelte.ts.As per coding guidelines, “Place unit tests in
*.test.tsfiles alongside the source they cover.”🤖 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/tests/use-generation.test.ts` around lines 1024 - 1049, Move the useGenerateVideo hydration test from packages/ai-react/tests/use-generation.test.ts#L1024-L1049 into a colocated *.test.ts file beside packages/ai-react/src/use-generate-video.ts, and move the createGenerateVideo hydration test from packages/ai-svelte/tests/create-generation.test.ts#L844-L864 into a colocated *.test.ts file beside packages/ai-svelte/src/create-generate-video.svelte.ts; preserve both tests unchanged apart from required imports and setup adjustments.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/persistence/build-your-own-adapter.md`:
- Around line 286-294: Update the “Let your coding agent write it” section to
scope the workflow to the selected backend: describe extending the existing
persistence stack for raw pg, Kysely, Mongo, and Supabase, and limit the ORM
configuration/schema and four-table instructions to relational chat skills.
In `@docs/persistence/build-your-own-chat-adapter.md`:
- Around line 155-165: The createOrResume implementation in
docs/persistence/build-your-own-chat-adapter.md lines 155-165 must re-read the
run by input.runId after insert.run and return it through mapRun, so the result
reflects the record that won a concurrent insert. Apply the same change to
docs/persistence/build-your-own-generation-adapter.md lines 142-163: re-read the
stored generation run after insert.run and map that record instead of returning
caller-supplied fields.
- Around line 81-87: Validate every rehydrated persisted value before returning
it: in docs/persistence/build-your-own-chat-adapter.md lines 81-87, validate the
complete ModelMessage[] shape in loadThread; lines 128-140, validate parsed
usage and required run fields before returning RunRecord; lines 239-251,
validate interrupt payloads and parsed responses; and lines 346-349, apply safe
parsing and validation to metadata values. In
docs/persistence/build-your-own-generation-adapter.md lines 104-125, validate
generation error, result, artifact, usage, and required scalar fields; and lines
345-355, validate customMetadata before returning BlobRecord. Preserve
unknown/missing-value behavior while preventing malformed JSON or
valid-but-wrong-shaped data from crossing adapter boundaries.
In `@docs/persistence/client-persistence.md`:
- Around line 193-194: Update the custom persistence adapter’s rehydration guard
for ChatPersistedState to validate every message element and the complete
resumeState structure, plus optional pendingInterrupts, before getItem returns
the record. Replace the shallow messages-array and unknown resume checks with
the existing complete type guard or Standard Schema parser, while preserving
valid persisted records.
In `@docs/persistence/generation-persistence.md`:
- Line 53: Rewrite the lifecycle description at line 53 as complete sentences
instead of comma-separated fragments, and update the “Status running” text at
line 428 to be a complete sentence. Preserve the original meaning while
improving readability in both locations.
In `@docs/persistence/internals.md`:
- Around line 186-187: Update the adapter guide description in the referenced
documentation text to replace “end to end” with clearer wording such as “the
complete SQLite flow,” while preserving the existing link and meaning.
In `@packages/ai-client/src/types.ts`:
- Around line 40-50: Update readResumeState() and persistResumeSnapshot() to
explicitly handle legacy ChatPersistedState.resume records with schemaVersion: 1
before applying them, migrating them into the current ChatResumeSnapshot shape
or intentionally invalidating them. Ensure invalid legacy data cannot be
silently treated as a valid resume state. Clarify the ChatResumeSnapshot
documentation and visibility to reflect whether this snapshot shape is a stable
API contract.
In
`@packages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md`:
- Around line 348-349: Update the helper description in the
build-cloudflare-artifact-store skill to refer only to retrieveBlob, or
explicitly state that blob reads resolve the key; do not imply retrieveArtifact
performs blob-key resolution.
In `@packages/ai-solid/tests/use-generation.test.ts`:
- Around line 83-86: Update the hydrateGeneration mock and related assertions in
the test to verify it receives the expected thread ID, “video-no-auto-fire,”
rather than ignoring the argument. Return a hydrated snapshot whose
resumeState.threadId matches that requested ID, while preserving the existing
resume behavior checks.
---
Outside diff comments:
In `@packages/ai-client/src/generation-client.ts`:
- Around line 145-198: Update the Svelte generation wiring in
packages/ai-client/src/generation-client.ts around the GenerationClient setup to
invoke mountDevtools from Svelte’s onMount callback rather than during setup.
Apply the same change to the Svelte video-generation wiring in
packages/ai-client/src/video-generation-client.ts around the video client setup;
both files must defer mountDevtools until onMount.
---
Nitpick comments:
In `@packages/ai-react/tests/use-generation.test.ts`:
- Around line 1024-1049: Move the useGenerateVideo hydration test from
packages/ai-react/tests/use-generation.test.ts#L1024-L1049 into a colocated
*.test.ts file beside packages/ai-react/src/use-generate-video.ts, and move the
createGenerateVideo hydration test from
packages/ai-svelte/tests/create-generation.test.ts#L844-L864 into a colocated
*.test.ts file beside packages/ai-svelte/src/create-generate-video.svelte.ts;
preserve both tests unchanged apart from required imports and setup adjustments.
In `@packages/ai-solid/tests/use-generation.test.ts`:
- Around line 83-86: Move the use-generation unit test file from the
package-level tests directory to the directory containing the covered
use-generation source module, keeping the test contents and behavior unchanged.
🪄 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 Plus
Run ID: c72cbabe-3e53-416f-9ace-d0c7e224c369
📒 Files selected for processing (85)
.changeset/client-typed-storage-adapter-defaults.md.changeset/generation-mount-hydration-and-speech-restore.md.changeset/generation-persistence-server-only.md.changeset/generation-persistence.md.changeset/generation-run-threadid-required.md.changeset/hooks-expose-run-id.md.changeset/trim-persistence-public-surface.mddocs/config.jsondocs/persistence/build-your-own-adapter.mddocs/persistence/build-your-own-chat-adapter.mddocs/persistence/build-your-own-generation-adapter.mddocs/persistence/chat-persistence.mddocs/persistence/client-persistence.mddocs/persistence/controls.mddocs/persistence/generation-persistence.mddocs/persistence/id-map.mddocs/persistence/internals.mddocs/persistence/keep-generated-files.mddocs/persistence/overview.mddocs/persistence/store-reference.mdpackages/ai-angular/src/index.tspackages/ai-angular/src/inject-generate-audio.tspackages/ai-angular/src/inject-generate-video.tspackages/ai-angular/src/inject-generation.tspackages/ai-angular/tests/inject-generation.test.tspackages/ai-angular/tests/test-utils.tspackages/ai-client/src/chat-client.tspackages/ai-client/src/connection-adapters.tspackages/ai-client/src/generation-client.tspackages/ai-client/src/generation-types.tspackages/ai-client/src/index.tspackages/ai-client/src/storage-adapters.tspackages/ai-client/src/types.tspackages/ai-client/src/video-generation-client.tspackages/ai-client/tests/chat-client-interrupt-correlation.test.tspackages/ai-client/tests/chat-client-interrupts.test.tspackages/ai-client/tests/resume-snapshot.test.tspackages/ai-persistence/skills/ai-persistence/SKILL.mdpackages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.mdpackages/ai-persistence/src/index.tspackages/ai-persistence/src/interrupts.tspackages/ai-persistence/src/retrieve.tspackages/ai-persistence/tests/capabilities.test.tspackages/ai-preact/tests/test-utils.tspackages/ai-react/src/index.tspackages/ai-react/src/use-generate-audio.tspackages/ai-react/src/use-generate-image.tspackages/ai-react/src/use-generate-speech.tspackages/ai-react/src/use-generate-video.tspackages/ai-react/src/use-generation.tspackages/ai-react/src/use-summarize.tspackages/ai-react/src/use-transcription.tspackages/ai-react/tests/test-utils.tspackages/ai-react/tests/use-generation.test.tspackages/ai-solid/src/index.tspackages/ai-solid/src/use-generate-audio.tspackages/ai-solid/src/use-generate-image.tspackages/ai-solid/src/use-generate-speech.tspackages/ai-solid/src/use-generate-video.tspackages/ai-solid/src/use-generation.tspackages/ai-solid/src/use-summarize.tspackages/ai-solid/src/use-transcription.tspackages/ai-solid/tests/test-utils.tspackages/ai-solid/tests/use-generation.test.tspackages/ai-svelte/src/create-generate-audio.svelte.tspackages/ai-svelte/src/create-generate-image.svelte.tspackages/ai-svelte/src/create-generate-speech.svelte.tspackages/ai-svelte/src/create-generate-video.svelte.tspackages/ai-svelte/src/create-generation.svelte.tspackages/ai-svelte/src/create-summarize.svelte.tspackages/ai-svelte/src/create-transcription.svelte.tspackages/ai-svelte/src/index.tspackages/ai-svelte/tests/create-generation.test.tspackages/ai-svelte/tests/test-utils.tspackages/ai-vue/src/index.tspackages/ai-vue/src/use-generate-audio.tspackages/ai-vue/src/use-generate-image.tspackages/ai-vue/src/use-generate-speech.tspackages/ai-vue/src/use-generate-video.tspackages/ai-vue/src/use-generation.tspackages/ai-vue/src/use-summarize.tspackages/ai-vue/src/use-transcription.tspackages/ai-vue/tests/test-utils.tspackages/ai-vue/tests/use-generation.test.tspackages/ai/skills/ai-core/client-persistence/SKILL.md
💤 Files with no reviewable changes (17)
- packages/ai-persistence/src/interrupts.ts
- packages/ai-vue/src/index.ts
- packages/ai-react/src/use-generate-speech.ts
- packages/ai-svelte/src/index.ts
- packages/ai-client/tests/chat-client-interrupt-correlation.test.ts
- packages/ai-client/tests/chat-client-interrupts.test.ts
- packages/ai-react/src/use-generate-audio.ts
- packages/ai-persistence/tests/capabilities.test.ts
- packages/ai-angular/src/index.ts
- packages/ai-react/src/use-generate-image.ts
- packages/ai-react/src/use-summarize.ts
- packages/ai-solid/src/index.ts
- packages/ai-react/src/index.ts
- packages/ai-client/src/chat-client.ts
- packages/ai-react/src/use-transcription.ts
- packages/ai-client/tests/resume-snapshot.test.ts
- packages/ai-persistence/src/index.ts
|
|
||
| ## Let your coding agent write it | ||
|
|
||
| You do not have to type this page out. `@tanstack/ai-persistence` ships | ||
| [Agent Skills](../getting-started/agent-skills) that turn it into a recipe your | ||
| assistant follows against **your** stack: it reads your existing ORM config, | ||
| schema file, and database handle, appends the four tables to the schema you | ||
| already have, and writes a single `src/lib/chat-persistence.ts` exporting the | ||
| `ChatPersistence` — no new package, no second database client, and no migration | ||
| `ChatPersistence`. No new package, no second database client, and no migration |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scope the agent instructions to the selected backend.
The text says every skill reads ORM configuration and appends four tables. The same table includes build-custom-adapter for raw pg, Kysely, Mongo, and Supabase. Those backends do not share one table-based workflow. Describe extending the existing persistence stack, or limit the four-table statement to relational chat skills.
🤖 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/persistence/build-your-own-adapter.md` around lines 286 - 294, Update
the “Let your coding agent write it” section to scope the workflow to the
selected backend: describe extending the existing persistence stack for raw pg,
Kysely, Mongo, and Supabase, and limit the ORM configuration/schema and
four-table instructions to relational chat skills.
| async loadThread(threadId) { | ||
| const json = select.get(threadId)?.messages_json | ||
| // Unknown thread → [] (never null). `node:sqlite` types columns as a | ||
| // SQL-value union, so narrow to string before parsing (no cast). | ||
| if (typeof json !== 'string') return [] | ||
| const parsed: Array<ModelMessage> = JSON.parse(json) | ||
| return parsed |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Validate persisted JSON at every adapter boundary.
JSON.parse is used as if it were validation. It only validates syntax. Corrupt rows can throw during hydration, and valid JSON can still have the wrong shape.
docs/persistence/build-your-own-chat-adapter.md#L81-L87: validate the fullModelMessage[]shape before returningloadThread.docs/persistence/build-your-own-chat-adapter.md#L128-L140: validate parsed usage and required run fields before returningRunRecord.docs/persistence/build-your-own-chat-adapter.md#L239-L251: validate interrupt payloads and parsed responses.docs/persistence/build-your-own-chat-adapter.md#L346-L349: apply the same safe parsing policy to metadata values.docs/persistence/build-your-own-generation-adapter.md#L104-L125: validate generation error, result, artifact, usage, and required scalar fields.docs/persistence/build-your-own-generation-adapter.md#L345-L355: validatecustomMetadatabefore returningBlobRecord.
Based on learnings, hand-rolled persistence adapters must validate rehydrated values before returning them.
📍 Affects 2 files
docs/persistence/build-your-own-chat-adapter.md#L81-L87(this comment)docs/persistence/build-your-own-chat-adapter.md#L128-L140docs/persistence/build-your-own-chat-adapter.md#L239-L251docs/persistence/build-your-own-chat-adapter.md#L346-L349docs/persistence/build-your-own-generation-adapter.md#L104-L125docs/persistence/build-your-own-generation-adapter.md#L345-L355
🤖 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/persistence/build-your-own-chat-adapter.md` around lines 81 - 87,
Validate every rehydrated persisted value before returning it: in
docs/persistence/build-your-own-chat-adapter.md lines 81-87, validate the
complete ModelMessage[] shape in loadThread; lines 128-140, validate parsed
usage and required run fields before returning RunRecord; lines 239-251,
validate interrupt payloads and parsed responses; and lines 346-349, apply safe
parsing and validation to metadata values. In
docs/persistence/build-your-own-generation-adapter.md lines 104-125, validate
generation error, result, artifact, usage, and required scalar fields; and lines
345-355, validate customMetadata before returning BlobRecord. Preserve
unknown/missing-value behavior while preventing malformed JSON or
valid-but-wrong-shaped data from crossing adapter boundaries.
Source: Learnings
| async createOrResume(input) { | ||
| const existing = select.get(input.runId) | ||
| if (existing) return mapRun(existing) | ||
| const status: RunStatus = input.status ?? 'running' | ||
| insert.run(input.runId, input.threadId, status, input.startedAt) | ||
| return { | ||
| runId: input.runId, | ||
| threadId: input.threadId, | ||
| status, | ||
| startedAt: input.startedAt, | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Make createOrResume return the record that won the insert race.
Both implementations read before inserting and then return the caller's input. If another process inserts the same run ID between those operations, ON CONFLICT DO NOTHING ignores the second insert but the function returns unsaved fields.
docs/persistence/build-your-own-chat-adapter.md#L155-L165: re-read and map the stored chat run afterinsert.run.docs/persistence/build-your-own-generation-adapter.md#L142-L163: re-read and map the stored generation run afterinsert.run.
📍 Affects 2 files
docs/persistence/build-your-own-chat-adapter.md#L155-L165(this comment)docs/persistence/build-your-own-generation-adapter.md#L142-L163
🤖 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/persistence/build-your-own-chat-adapter.md` around lines 155 - 165, The
createOrResume implementation in docs/persistence/build-your-own-chat-adapter.md
lines 155-165 must re-read the run by input.runId after insert.run and return it
through mapRun, so the result reflects the record that won a concurrent insert.
Apply the same change to docs/persistence/build-your-own-generation-adapter.md
lines 142-163: re-read the stored generation run after insert.run and map that
record instead of returning caller-supplied fields.
| `{ messages, resume? }` blob per chat id (the transcript plus the pointer that | ||
| lets a reload rejoin an in-flight run), so `setItem` receives that whole record, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 '\bChatPersistedState\b|ChatClientPersistence|resume' \
packages/ai-client/src docs/persistence/client-persistence.mdRepository: TanStack/ai
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== docs custom adapter lines =="
sed -n '190,235p' docs/persistence/client-persistence.md
echo "== client persistor normalize/read lines =="
sed -n '1,140p' packages/ai-client/src/client-persistor.ts
echo "== relevant types definitions =="
rg -n -C 3 'export type (ChatPersistedState|ChatResumeSnapshot|ChatClientPersistence)|interface ChatClientPersistence|ChatResumeSnapshot|ResumeSnapshot' packages/ai-client/src packages/ai-client -g '*.ts' -g '*.d.ts'Repository: TanStack/ai
Length of output: 50370
Validate the full ChatPersistedState shape before rehydration.
The custom adapter’s guard accepts records where messages is an array, even if the array contains arbitrary values. It also accepts any resume: unknown when present. Since the persistor returns this object directly from getItem, use a complete type guard or Standard Schema parser for messages, resumeState, and optional pendingInterrupts before returning the persisted record.
🤖 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/persistence/client-persistence.md` around lines 193 - 194, Update the
custom persistence adapter’s rehydration guard for ChatPersistedState to
validate every message element and the complete resumeState structure, plus
optional pendingInterrupts, before getItem returns the record. Replace the
shallow messages-array and unknown resume checks with the existing complete type
guard or Standard Schema parser, while preserving valid persisted records.
Source: Learnings
| add server byte storage: see [Keep generated files](./keep-generated-files). | ||
|
|
||
| The record's lifecycle is small — one status field the middleware advances: | ||
| The record's lifecycle is small, one status field the middleware advances: |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Replace the comma-based fragments with complete sentences.
Line 53 leaves the lifecycle description compressed and difficult to parse. Line 428 leaves Status running as a sentence fragment.
Proposed wording
- The record's lifecycle is small, one status field the middleware advances:
+ The record has a small lifecycle. The middleware advances one status field:
- // Opens the run. Status `running`, and there is no video yet.
+ // Opens the run with status `running`; no video exists yet.Also applies to: 428-428
🤖 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/persistence/generation-persistence.md` at line 53, Rewrite the lifecycle
description at line 53 as complete sentences instead of comma-separated
fragments, and update the “Status running” text at line 428 to be a complete
sentence. Preserve the original meaning while improving readability in both
locations.
| stores it exposes. [Build a chat adapter](./build-your-own-chat-adapter) shows | ||
| this end to end for SQLite. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use clearer wording for the adapter guide description.
The phrase end to end reads awkwardly here and is flagged by the static analysis hint. Replace it with a clear phrase such as the complete SQLite flow.
🧰 Tools
🪛 LanguageTool
[grammar] ~187-~187: Use a hyphen to join words.
Context: ...ld-your-own-chat-adapter) shows this end to end for SQLite. composePersistence do...
(QB_NEW_EN_HYPHEN)
🤖 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/persistence/internals.md` around lines 186 - 187, Update the adapter
guide description in the referenced documentation text to replace “end to end”
with clearer wording such as “the complete SQLite flow,” while preserving the
existing link and meaning.
Source: Linters/SAST tools
| /** | ||
| * The durable pointer a chat keeps for the run it may need to rejoin, plus any | ||
| * interrupt that run is waiting on. | ||
| * | ||
| * @internal | ||
| */ | ||
| export interface ChatResumeSnapshot { | ||
| resumeState: ChatResumeState | ||
| pendingInterrupts?: Array<ChatPendingInterrupt> | ||
| } | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect migration or intentional invalidation for old snapshots, plus
# declaration configuration that keeps internal hydration types private.
rg -n -C 5 \
'ChatResumeSnapshot(V1|V2)?|schemaVersion|getResumeSnapshot|resumeInterruptsUnsafe|stripInternal|api-extractor' \
.Repository: TanStack/ai
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate ai-client types and api extractor config =="
git ls-files | rg '(^|/)api-extractor\.json$|packages/ai-client/src/types\.ts|packages/ai-client/package\.json|packages/ai-client/tsconfig.*\.json$|packages/ai-client/dist' || true
echo
echo "== ai-client types relevant lines =="
wc -l packages/ai-client/src/types.ts
sed -n '1,90p' packages/ai-client/src/types.ts
echo "... around lines 550-590 ..."
sed -n '550,600p' packages/ai-client/src/types.ts
echo
echo "== local references to ChatResumeSnapshot in source =="
rg -n "ChatResumeSnapshot" packages/ai-client packages/ai-persistence packages/ai-react packages/ai-vue packages/ai-svelte packages/ai-angular docs examples testing/e2e 2>/dev/null || true
echo
echo "== api extractor / typedoc / tsup relevant files =="
git ls-files | rg 'api-extractor|typedoc|tsup|tsconfig' | sed -n '1,200p'
echo
echo "== package config snippets for ai-client =="
for f in $(git ls-files | rg '(^|/)packages/ai-client/(package|tsup|typedoc|api-extractor).*\.(json|ts|js)$'); do
echo "--- $f"
sed -n '1,220p' "$f"
done
echo
echo "== declaration export/stripping scripts/config =="
rg -n "stripInternal|internal|api-extractor|exports|declaration|dts|package.json|api\.json" packages/ai-client packages/ai-persistence config 2>/dev/null || trueRepository: TanStack/ai
Length of output: 19237
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read-only behavioral probe: derive the effective ChatResumeSnapshot shape from source and
# check whether schemaVersion remains present in the direct type or constructor-accepted snapshot.
python3 - <<'PY'
from pathlib import Path
import re
paths = [
Path("packages/ai-client/src/types.ts"),
Path("packages/ai-persistence/src/reconstruct-generation.ts"),
]
for p in paths:
print(f"== {p} ==")
text = p.read_text()
for needle in ["ChatResumeSnapshot", "schemaVersion", "resumeSnapshot", "ReconstructedGeneration"]:
occurs = [i for i, line in enumerate(text.splitlines(), 1) if needle in line]
print(f"{needle}: occurrences at lines {occurs[:20]}{'...' if len(occurs)>20 else ''} total={len(occurs)}")
PYRepository: TanStack/ai
Length of output: 748
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== chat-client resume/snapshot methods =="
sed -n '1,80p' packages/ai-client/src/chat-client.ts
sed -n '740,830p' packages/ai-client/src/chat-client.ts
echo
echo "== resume snapshot tests =="
sed -n '1,90p' packages/ai-client/tests/resume-snapshot.test.ts
rg -n -C 4 'schemaVersion|applyResumeSnapshot|applyPersistedResume|persistResumeSnapshot|initialResumeSnapshot|ChatResumeSnapshot' packages/ai-client/tests packages/ai-client/src/chat-client.ts packages/ai-client/src/client-persistor.ts packages/ai-client/src/types.ts
echo
echo "== build config and generated/internal strip sources =="
cat packages/ai-client/tsconfig.json
cat packages/ai-client/vite.config.ts
rg -n --glob '*.{ts,js,json,html}' "stripInternal|internal|declaration|emitDeclarationOnly|vite-plugin-dts|rollup|api-extractor" packages/ai-client packages/ai-client/src packages tests 2>/dev/null | sed -n '1,220p' || trueRepository: TanStack/ai
Length of output: 50367
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== readResumeState implementation surrounding line =="
sed -n '210,255p' packages/ai-client/src/chat-client.ts
echo
echo "== persistor normalize and writeState implementation =="
sed -n '1,180p' packages/ai-client/src/client-persistor.ts
sed -n '180,225p' packages/ai-client/src/client-persistor.ts
echo
echo "== package build scripts and root workspace scripts =="
cat packages/ai-client/package.json
sed -n '1,220p' package.json
echo
echo "== typedoc/doc generation files mentioning internals =="
rg -n "`@internal`|stripInternal" docs packages/tsconfig.docs.json packages/ ai -g '*.{ts,md,json,jsonc,js,html}' 2>/dev/null | sed -n '1,220p' || trueRepository: TanStack/ai
Length of output: 19837
Handle old persisted chat resume snapshots before applying them.
readResumeState() and persistResumeSnapshot() in @tanstack/ai-client now accept a snapshot without schemaVersion, but the persisted generation resume contracts still use schemaVersion: 1. Old ChatPersistedState.resume records may migrate to a different internal shape than @tanstack/ai-client expects, or be silently applied as invalid. Add migration/intentional invalidation for older resume records and document the stable ChatResumeSnapshot contract if @internal does not mean “not API guaranteed”.
🤖 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/types.ts` around lines 40 - 50, Update
readResumeState() and persistResumeSnapshot() to explicitly handle legacy
ChatPersistedState.resume records with schemaVersion: 1 before applying them,
migrating them into the current ChatResumeSnapshot shape or intentionally
invalidating them. Ensure invalid legacy data cannot be silently treated as a
valid resume state. Clarify the ChatResumeSnapshot documentation and visibility
to reflect whether this snapshot shape is a stable API contract.
| const videoResumeSnapshot = { | ||
| schemaVersion: 1 as const, | ||
| resumeState: { threadId: 'thread-resume', runId: 'run-resume' }, | ||
| status: 'running' as const, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Assert the hydration request identity.
hydrateGeneration ignores its threadId argument. The fixture contains resumeState.threadId: 'thread-resume', while the hook requests 'video-no-auto-fire'. This test can pass if the client sends the wrong thread ID. Make the mock assert the argument and return a snapshot with the same thread ID.
Suggested test adjustment
- const hydrateGeneration = vi.fn(async () => ({
- resumeSnapshot: videoResumeSnapshot,
+ const hydrateGeneration = vi.fn(async (threadId: string) => ({
+ resumeSnapshot: {
+ ...videoResumeSnapshot,
+ resumeState: {
+ ...videoResumeSnapshot.resumeState,
+ threadId,
+ },
+ },
activeRun: null,
}))
...
+ expect(hydrateGeneration).toHaveBeenCalledWith('video-no-auto-fire')Also applies to: 1390-1400
🤖 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/tests/use-generation.test.ts` around lines 83 - 86, Update
the hydrateGeneration mock and related assertions in the test to verify it
receives the expected thread ID, “video-no-auto-fire,” rather than ignoring the
argument. Return a hydrated snapshot whose resumeState.threadId matches that
requested ID, while preserving the existing resume behavior checks.
Conflict: docs/persistence/build-your-own-adapter.md. This branch split that page into build-your-own-adapter / build-your-own-chat-adapter / build-your-own-generation-adapter / store-reference, while #1033 edited the sections that moved. Resolved by keeping the split and porting #1033's changes to their new homes: - build-your-own-generation-adapter: the SQLite `BlobStore` walkthrough gains ranged reads (`resolveBlobRange`, metadata-only `selectMeta`, `substr`-based `selectSlice`, `blobObject`'s `range` argument, metadata-only `head`), plus a bullet pointing at the `get` contract. - store-reference: `BlobObject.range`, `BlobPutOptions.expectedLength`, `BlobRange`, `BlobGetOptions`, the widened `BlobStore.get` signature, and the two new contract sections for `put` (drain a length-less stream) and `get` (honour `options.range`). - keep-generated-files: repoint `#blobstore` at store-reference, and clear the em dashes the merged content brought in.
🚀 Changeset Version Preview19 package(s) bumped directly, 32 bumped as dependents. 🟥 Major bumps
🟨 Minor bumps
🟩 Patch bumps
|
|
View your CI Pipeline Execution ↗ for commit e5537f6
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-codex
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-sandbox
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-vercel
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
Post-merge pass with the docs skill over the file that conflicted: - collapse the double blank lines the split left before three headings - `build-your-own-adapter` no longer holds a walkthrough, so stop saying "type this page out" and "all four stores" on a page that presents seven - rewrap two paragraphs the ported edits ran long
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md (1)
581-583: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftAdd the ownership check before serving bytes.
The route accepts a caller-controlled
artifactIdand performs no session-derivedthreadIdorrunIdcheck. A caller who learns or guesses an artifact ID can read another user's media. Add the authorization check and return404for unauthorized records before callingretrieveBlob.🤖 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-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md` around lines 581 - 583, Update the artifact-serving flow around retrieveArtifact to derive the authorized threadId or runId from the current session and verify the retrieved record belongs to it before calling retrieveBlob. Return 404 for missing or unauthorized records, and never serve bytes for an artifact that fails this ownership check.
♻️ Duplicate comments (1)
packages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md (1)
559-560: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDescribe only the helper that resolves the blob key.
retrieveArtifactreturns theArtifactRecord;retrieveBlobresolves the stored blob key. Change “Both” to “retrieveBlob”. This repeats the previous review finding.#!/bin/bash set -euo pipefail rg -n -C 8 'retrieveArtifact|retrieveBlob|resolveArtifactBlobKey' packages/ai-persistence/src/retrieve.ts🤖 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-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md` around lines 559 - 560, Update the referenced wording to describe only retrieveBlob as resolving the stored blob key; do not include retrieveArtifact or “Both,” since retrieveArtifact returns the ArtifactRecord.
🤖 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/persistence/store-reference.md`:
- Around line 309-315: Update the documented blob persistence and get flow to
persist the actual drained byte count for byte-backed records, rather than using
expectedLength as the object size. Require the stored size before calling
resolveBlobRange for ranged reads, and ensure the example does not evaluate
range handling until that persisted size is available.
---
Outside diff comments:
In
`@packages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md`:
- Around line 581-583: Update the artifact-serving flow around retrieveArtifact
to derive the authorized threadId or runId from the current session and verify
the retrieved record belongs to it before calling retrieveBlob. Return 404 for
missing or unauthorized records, and never serve bytes for an artifact that
fails this ownership check.
---
Duplicate comments:
In
`@packages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.md`:
- Around line 559-560: Update the referenced wording to describe only
retrieveBlob as resolving the stored blob key; do not include retrieveArtifact
or “Both,” since retrieveArtifact returns the ArtifactRecord.
🪄 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 Plus
Run ID: 9fbadcb3-ce80-40c2-b6c6-f3eb992f45a4
📒 Files selected for processing (7)
docs/persistence/build-your-own-adapter.mddocs/persistence/build-your-own-generation-adapter.mddocs/persistence/keep-generated-files.mddocs/persistence/store-reference.mdpackages/ai-persistence/skills/ai-persistence/build-cloudflare-artifact-store/SKILL.mdpackages/ai-persistence/src/index.tspackages/ai-persistence/src/retrieve.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/ai-persistence/src/index.ts
- docs/persistence/build-your-own-generation-adapter.md
- packages/ai-persistence/src/retrieve.ts
- docs/persistence/build-your-own-adapter.md
| And one for `get`: honour `options.range` by returning **only that slice**. | ||
| `size` keeps reporting the whole object, and the returned `range` reports what | ||
| you actually served. Together they are the `206` response a media player's | ||
| seeking depends on. `resolveBlobRange(size, range)` does the clamping (a | ||
| `length` past the end is legal and clamps; an `offset` past the end throws, | ||
| because a serve route should have answered `416` from `record.size` first): | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 8 'interface BlobRecord|size\?: number|resolveBlobRange|expectedLength|BlobStore' packages/ai-persistence docs/persistenceRepository: TanStack/ai
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== docs/persistence/store-reference.md relevant lines =="
sed -n '280,340p' docs/persistence/store-reference.md
echo
echo "== docs/persistence/store-reference.md lines 318-330 =="
sed -n '318,330p' docs/persistence/store-reference.md
echo
echo "== packages/ai-persistence/src/blob-range.ts =="
cat -n packages/ai-persistence/src/blob-range.ts
echo
echo "== packages/ai-persistence/src/memory.ts put/get relevant lines =="
sed -n '330,425p' packages/ai-persistence/src/memory.ts
echo
echo "== packages/ai-persistence/src/testkit/conformance.ts put/expectedLength tests =="
sed -n '760,798p' packages/ai-persistence/src/testkit/conformance.ts
echo
echo "== local references to size undefined in blob helpers/tests =="
rg -n "blobObject\\(|mapBlobRecord\\(|resolveBlobRange\\(|size\\?:" packages/ai-persistence/docs packages/ai-persistence/src -g '.*' | head -n 120Repository: TanStack/ai
Length of output: 13968
Persist and require the stored byte count for ranged blobs.
resolveBlobRange(row.size, options.range) passes the stored row size into BlobRecord.size, and the example can evaluate that path before the stored bytes are known. Persist the real drained byte count for byte-backed records, require it for range-capable reads, and do not store expectedLength as the object size.
🤖 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/persistence/store-reference.md` around lines 309 - 315, Update the
documented blob persistence and get flow to persist the actual drained byte
count for byte-backed records, rather than using expectedLength as the object
size. Require the stored size before calling resolveBlobRange for ranged reads,
and ensure the example does not evaluate range handling until that persisted
size is available.
Reconciles #1034's persistence docs split and API trim with this branch's durable-run additions: - build-your-own-adapter.md: took main's hub; re-homed this branch's skipMethods conformance content into its conformance section. - build-your-own-chat-adapter.md: re-homed the durable-run walkthrough changes (runs-table columns, 'aborted' status, RunError two-column mapping, presence-keyed update branches, listByThread/listReclaimable). - store-reference.md: re-homed the RunStore contract rewrite; widened GenerationRunStatus to match GenerationRunStatus = RunStatus. - chat-persistence.md: kept this branch's abort/detach semantics, restyled to main's no-em-dash pass. - Kept the isTerminalRunStatus / TerminalRunStatus re-exports in @tanstack/ai-persistence: the store reference teaches them to backend authors, which meets #1034's "what apps call" bar. Main's removals (artifactBlobKey, createInterruptController) stand. - Retargeted sandbox-doc and adapter-skill references from the dismantled hub page to store-reference / build-your-own-chat-adapter. Verified: pnpm test:pr green (70 projects), doc links clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🎯 Changes
An audit of everything the persistence work (#984, #1011, #1004, #955) added to the public API turned up a set of exports with no consumer anywhere in the repo: docs, examples, e2e, or the packages themselves. Most of it is the options-and-types half of the client-managed generation mode that #1011 refactored away mid-PR, leaving the supporting surface behind.
This is deletion only. No behavior changes.
Removed from
@tanstack/ai-clientand the framework packagesinitialResumeSnapshotis gone from every generation hook (7 hooks × 5 frameworks) and fromGenerationClient/VideoGenerationClient. It seeded the storage mode that no longer exists; a run is restored bypersistence: trueplus ahydrateGenerationhandler.useChatkeeps its owninitialResumeSnapshot.GenerationResumeSnapshot,GenerationResumeState,GenerationResumeStatus,GenerationResultSnapshot,GenerationErrorSnapshot,GenerationEventSnapshot,parseGenerationResumeSnapshot,updateGenerationResumeSnapshot,ChatResumeSnapshot.GenerationPendingArtifact(a bare alias with one internal use) andGenerationPersistenceOption(a public alias forboolean;persistence?: booleanis inline now).GenerationPersistenceOptions, the union that requires athreadIdalongsidepersistence, is unchanged.ChatResumeSnapshotV1/ChatResumeSnapshotV2collapsed into oneChatResumeSnapshotwith noschemaVersion. The two were structurally identical, no reader branched on the version, and only V2 was ever written.TValuedropped fromlocalStoragePersistence/sessionStoragePersistence/indexedDBPersistence(andWebStoragePersistenceOptions). The parameter existed so generation could share the chat adapters; there are zero non-default instantiations in the repo.PersistedArtifactRefre-export dropped from the 5 framework packages, where no local type refers to it. It stays public on@tanstack/ai, where docs and the sqlite example import it.Removed from
@tanstack/ai-persistenceartifactBlobKeyis internal.resolveArtifactBlobKeyis the read path its own JSDoc already recommended, since a record written with a customstorageKeycarries its real key inblobKey.createInterruptController/InterruptControllerdeleted. A five-method forwarder tostores.interruptswith no caller outside its own test.Tests
The five
initialResumeSnapshot-seeded "no auto-fire on mount" guards were split by whether the coverage still existed elsewhere. The base-hook one is redundant with each framework's existing "hydrated running snapshot with nojoinRunsurfaces as interrupted" test, so it's deleted. The video one had no hydration-path equivalent, so it's converted topersistence: true+hydrateGenerationand keeps the coverage. The interrupt-controller tests were assertions aboutstores.interruptsbehavior already covered directly bytests/interrupts.test.ts.Docs
Documented the ctx-capability plumbing (
PersistenceCapability,InterruptsCapability,getPersistence,providePersistence,getInterrupts,provideInterrupts) inpersistence/internals. It is legitimate surface for third-party middleware that needs the storeswithPersistenceholds, and had zero mentions in docs.Fixed the
ai-core/client-persistenceskill frontmatter, which still advertised the removed client-driven generation mode andgeneration:<threadId>storage keys while the skill's own body is titled "Generation hooks: server-driven only". The frontmatter is what routes an agent to the skill, so it was the worst place for it to be wrong.Split
build-your-own-adapter.md(1513 lines, three reader journeys in one page) into four, keeping the original path so every inbound link survives:build-your-own-adapter(337): what an adapter is, which stores you need, mapping onto an existing schema, the conformance suite, the agent skillsbuild-your-own-chat-adapter(436): the SQLite walkthrough for the four chat storesbuild-your-own-generation-adapter(517): generation runs, artifacts, blobsstore-reference(286): the seven store contractsAnchors into the moved sections (
#generation--media-stores,#store-interface-reference) are repointed fromcontrols,generation-persistence,keep-generated-files,internals, andoverview; nav has the three new entries.Removed all 96 em dashes and the
·separators fromdocs/persistence/**, rewritten sentence by sentence rather than swapped for another glyph.Changesets
New
trim-persistence-public-surface.md, plus six pending changesets reconciled where they described a removed API as surviving (generation-persistence-server-onlyclaimedinitialResumeSnapshotwas "unchanged";client-typed-storage-adapter-defaultswas entirely about theTValuedefault that no longer exists).Known leak left in place
GenerationClient.getResumeSnapshot()/VideoGenerationClient.getResumeSnapshot()are still public methods returning the now-internal snapshot type, and only tests call them. Removing them means rewriting ~14 assertions ingeneration-client.test.ts; happy to fold that in if wanted.✅ Checklist
pnpm run test:pr.test:types(68/68),test:lib,build,test:build,test:docs,test:kiira(875 snippets),test:oxlint,test:knip,test:sherif,test:dts, the react-native smoke, and the E2E suite (387 passed, 11 flaky TTS click timeouts that pass on retry) are all green locally. One unrelated pre-existing failure on Windows:packages/ai-sandbox/tests/harness-cwd.test.tsasserts\tmp\...against/tmp/..., a path-separator bug in the test itself.🚀 Release Impact
🤖 Generated with Claude Code
Summary by CodeRabbit
Breaking Changes
persistence: truewith a thread ID.runId.Documentation