@tanstack/ai-persistence 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/memory.js +8 -1
- package/dist/esm/memory.js.map +1 -1
- package/dist/esm/merge-stored.d.ts +2 -0
- package/dist/esm/merge-stored.js +47 -0
- package/dist/esm/merge-stored.js.map +1 -0
- package/dist/esm/middleware.d.ts +2 -1
- package/dist/esm/middleware.js +18 -44
- package/dist/esm/middleware.js.map +1 -1
- package/dist/esm/reconstruct.js +90 -1
- package/dist/esm/reconstruct.js.map +1 -1
- package/dist/esm/subagent-runs.d.ts +40 -0
- package/dist/esm/subagent-runs.js +366 -0
- package/dist/esm/subagent-runs.js.map +1 -0
- package/dist/esm/testkit/conformance.d.ts +4 -1
- package/dist/esm/testkit/conformance.js +94 -1
- package/dist/esm/testkit/conformance.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-persistence/build-cloudflare-adapter/SKILL.md +41 -16
- package/skills/ai-persistence/build-custom-adapter/SKILL.md +49 -20
- package/skills/ai-persistence/build-drizzle-adapter/SKILL.md +54 -9
- package/skills/ai-persistence/build-prisma-adapter/SKILL.md +32 -6
- package/skills/ai-persistence/stores/SKILL.md +46 -29
- package/src/memory.ts +16 -0
- package/src/merge-stored.ts +67 -0
- package/src/middleware.ts +19 -67
- package/src/reconstruct.ts +155 -2
- package/src/subagent-runs.ts +581 -0
- package/src/testkit/conformance.ts +125 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","names":[],"sources":["../../src/middleware.ts"],"sourcesContent":["import {\n defineChatMiddleware,\n fromSpecTokenUsage,\n getDetachableRun,\n InterruptResumeValidationError,\n MetadataCapability,\n provideMetadata,\n readInterruptBinding,\n validateInterruptResumeBatch,\n wasCancelRequested,\n} from '@tanstack/ai'\nimport {\n createInterruptBinding,\n getGenericInterruptDefinitionRegistry,\n providePendingTurn,\n rehydrateInterruptRequest,\n toRunErrorPayload,\n} from '@tanstack/ai/adapter-internals'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '@tanstack/ai/adapter-internals'\nimport { base64ToUint8Array } from '@tanstack/ai-utils'\nimport {\n InterruptsCapability,\n PersistenceCapability,\n PersistenceCompletionCapability,\n provideInterrupts,\n providePersistence,\n providePersistenceCompletion,\n} from './capabilities'\nimport {\n validateChatPersistenceStores,\n validateGenerationPersistenceStores,\n} from './types'\nimport type {\n AbortInfo,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ChatResumeToolState,\n ErrorInfo,\n FinishInfo,\n GenerationAbortInfo,\n GenerationErrorInfo,\n GenerationFinishInfo,\n GenerationMiddleware,\n GenerationMiddlewareContext,\n Interrupt,\n ModelMessage,\n PendingInterruptResumeRecord,\n PersistedArtifactActivity,\n PersistedArtifactRef,\n PersistedArtifactRole,\n RunAgentResumeItem,\n StreamChunk,\n Tool,\n ToolApprovalResolution,\n BilledUsage,\n TokenUsage,\n} from '@tanstack/ai'\nimport type {\n AIPersistence,\n AIPersistenceStores,\n ArtifactRecord,\n BlobBody,\n ChatTranscriptStores,\n InterruptCommitEntry,\n InterruptRecord,\n MessagePage,\n RunStore,\n} from './types'\nimport { artifactBlobKey } from './retrieve'\n\n/**\n * How generated media is turned into durable artifacts: which pieces of a\n * result become artifacts, what they are named, where their bytes land, and how\n * the bytes are fetched when the provider returns a URL rather than inline data.\n *\n * Consumed by {@link withGenerationPersistence} through\n * {@link WithGenerationPersistenceOptions}. Chat persistence has no artifacts —\n * its options are {@link WithPersistenceOptions}.\n */\nexport interface ArtifactPersistenceOptions {\n extractArtifacts?: (\n input: GenerationArtifactExtractionInput,\n ) =>\n | Array<GenerationArtifactDescriptor | PersistedArtifactRef>\n | Promise<Array<GenerationArtifactDescriptor | PersistedArtifactRef>>\n nameArtifact?: (input: GenerationArtifactNameInput) => string\n /**\n * Map a freshly-persisted artifact ref to the durable app-origin URL that\n * serves its bytes (your `GET` route around `retrieveArtifact` /\n * `retrieveBlob`). The returned URL is stamped onto `ref.url` and written into\n * the result's media field, so both the live and the restored result render\n * durable media from your own origin instead of the provider's expiring link.\n * Return `undefined` to leave a ref without a durable URL.\n */\n artifactUrl?: (ref: PersistedArtifactRef) => string | undefined\n /**\n * Choose the blob-store key each artifact's bytes are written under, so\n * generated media can land in your own folder structure rather than the\n * default `artifacts/<runId>/<artifactId>`.\n *\n * ```ts\n * storageKey: ({ runId, artifactId, mimeType }) =>\n * `video/${videoId}/frames/${runId}-${artifactId}.png`\n * ```\n *\n * Server-side only, and deliberately so: a key supplied by the browser would\n * be a path-traversal and cross-tenant-write vector.\n *\n * The resolved key is recorded on `ArtifactRecord.blobKey`, because once the\n * path is arbitrary a reader can no longer recompute it. Returning a\n * non-unique key overwrites — include `artifactId` (or something equally\n * unique) unless you intend that.\n */\n storageKey?: (input: {\n artifactId: string\n runId: string\n threadId: string\n role: PersistedArtifactRole\n activity: PersistedArtifactActivity\n path: string\n mimeType: string\n name: string\n }) => string\n /**\n * Opt in to fetching prompt media referenced by URL (`role: 'input'`).\n *\n * Off by default, and deliberately expressed as a predicate rather than a\n * boolean: input URLs come from the caller, so fetching them server-side\n * turns your server into a proxy for whatever the caller names — cloud\n * metadata endpoints, `localhost` admin services, anything your network can\n * reach. The bytes are also redundant in the common case, since the client\n * already had the media it referenced.\n *\n * Enable this only when you genuinely need a durable copy of caller-supplied\n * media (a \"paste an image URL\" input box, say), and validate the target:\n *\n * ```ts\n * allowInputUrl: ({ url }) => url.hostname.endsWith('.cdn.example.com')\n * ```\n *\n * Requests are additionally forced through the same baseline checks every\n * artifact fetch gets (http/https only, timeout, size cap), plus — because\n * the target is untrusted — a loopback/private/link-local host block and\n * `redirect: 'manual'` so a 302 cannot hop to an internal address. Those are\n * a backstop, not a substitute for a narrow predicate: a hostname that\n * resolves to a private address still passes a literal-IP check.\n */\n allowInputUrl?: (input: {\n url: URL\n descriptor: GenerationArtifactDescriptor\n }) => boolean | Promise<boolean>\n /** Abort an artifact fetch after this many ms. Default 30_000. */\n artifactFetchTimeoutMs?: number\n /**\n * Refuse an artifact body larger than this many bytes. Default 1 GiB.\n *\n * This is a bound on TRANSFER, not on memory: the URL path streams into the\n * blob store and never buffers, so a 1 GiB artifact costs a streaming store\n * (R2, S3, filesystem) flat memory. What the cap buys is a ceiling on what a\n * broken or hostile origin can make you pull and store — `content-length` is\n * advisory, so without it an artifact fetch is an unbounded transfer billed\n * to you.\n *\n * Pass `false` to remove the ceiling entirely. That also removes the\n * cap-enforcing `TransformStream` wrapper, so the fetched body reaches your\n * store exactly as `fetch` produced it — on workerd that means it keeps its\n * native declared length and `R2Bucket.put` can single-shot it with no hint,\n * no multipart, and nothing buffered. Do that when you trust the origins you\n * fetch from (your provider's CDN); keep the cap when `allowInputUrl` lets\n * callers name the URL.\n */\n maxArtifactBytes?: number | false\n /**\n * `fetch` used to download artifact bytes. Defaults to the global. Inject to\n * route downloads through a proxy or an egress-restricted agent — the most\n * robust SSRF control available here, since it can resolve and check the\n * address actually connected to.\n */\n artifactFetch?: typeof globalThis.fetch\n}\n\n/**\n * Options for {@link withGenerationPersistence}: everything in\n * {@link ArtifactPersistenceOptions}, plus an optional scope override.\n */\nexport interface WithGenerationPersistenceOptions extends ArtifactPersistenceOptions {\n /**\n * Override the scope runs are filed under. Defaults to the `threadId` you\n * passed the activity, which is normally what you want, so leave this unset\n * unless the record belongs somewhere other than the activity's own scope.\n */\n threadId?: string\n}\n\n/**\n * The slot this generation's runs are filed under: `ctx.threadId` (the\n * `threadId` the caller passed the activity), or the option when it overrides.\n *\n * Throws when neither supplies one. A run filed under no scope can never be\n * hydrated by one, so `persistence: true` would restore nothing, forever. That\n * is worth failing loudly for, since the alternative is a silent hole a reader\n * cannot diagnose from behavior.\n */\nfunction generationScope(\n ctx: GenerationMiddlewareContext,\n opts: WithGenerationPersistenceOptions,\n): string {\n const threadId = opts.threadId ?? ctx.threadId\n if (threadId === undefined || threadId.length === 0) {\n throw new Error(\n 'Generation persistence requires a `threadId`, the stable scope successive ' +\n 'runs are filed under. Pass it to the activity, e.g. ' +\n '`generateImage({ threadId, middleware: [withGenerationPersistence(p)] })`, ' +\n 'or override it with `withGenerationPersistence(p, { threadId })`.',\n )\n }\n return threadId\n}\n\nconst DEFAULT_ARTIFACT_FETCH_TIMEOUT_MS = 30_000\n// 1 GiB, because generated video clips routinely run to a few hundred MB and\n// the old 100 MiB default silently failed them. The cap is a drain-time\n// counter, not a buffer: the URL path streams into the store, so raising it\n// costs a streaming store nothing in memory. It still earns its keep as the\n// only ceiling on what a runaway or hostile origin can make you transfer and\n// store (`content-length` is advisory, and on a compressed reply it measures\n// the compressed body). `maxArtifactBytes: false` removes it — and the wrapper\n// with it, which is the zero-copy path onto workerd + R2.\nconst DEFAULT_MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024\n\nexport interface GenerationArtifactDescriptor {\n role: PersistedArtifactRole\n path: string\n mediaType?: PersistedArtifactRef['source']['mediaType']\n mimeType?: string\n bytes?: BlobBody\n url?: string\n json?: unknown\n name?: string\n jobId?: string\n expiresAt?: string | Date\n}\n\nexport interface GenerationArtifactExtractionInput {\n activity: PersistedArtifactActivity\n provider: string\n model: string\n threadId: string\n runId: string\n inputs: unknown\n result: unknown\n}\n\nexport interface GenerationArtifactNameInput {\n descriptor: GenerationArtifactDescriptor\n activity: PersistedArtifactActivity\n provider: string\n model: string\n threadId: string\n runId: string\n index: number\n}\n\ninterface RunStateEntry {\n merged: boolean\n interrupted: boolean\n /**\n * Resumes accepted in `onConfig` but not yet committed to the interrupt\n * store. They are applied (resolve/cancel) only once the run reaches a\n * successful boundary — see {@link commitPendingResumes}. Left uncommitted\n * (still pending in the store) if the run fails or aborts first.\n */\n pendingResumes?: {\n pending: Array<InterruptRecord>\n resumeByInterruptId: Map<string, RunAgentResumeItem>\n }\n /** Usage accumulated across every model call in this chat invocation. */\n usage?: TokenUsage\n /** Accumulated terminal-turn text, for throttled streaming snapshots (B). */\n streamingText?: string\n /** Epoch ms of the last streaming snapshot, to throttle writes (B). */\n lastSnapshotAt?: number\n /**\n * The current assistant turn's stream messageId, captured from\n * `TEXT_MESSAGE_START`. Persisted onto the assistant message so its identity\n * survives the persist → hydrate round-trip and a reload can resume the same\n * bubble in place.\n */\n streamingMessageId?: string\n streamingMessageCreatedAt?: Date\n completion?: {\n promise: Promise<void>\n resolve: () => void\n reject: (error: unknown) => void\n }\n}\n\nconst runState = new WeakMap<object, RunStateEntry>()\n\nconst validResumeStatuses = new Set(['resolved', 'cancelled'])\n\nfunction mergeMaps<K, V>(\n left?: ReadonlyMap<K, V>,\n right?: ReadonlyMap<K, V>,\n): Map<K, V> | undefined {\n if (!left && !right) return undefined\n return new Map([...(left ?? []), ...(right ?? [])])\n}\n\nfunction mergeSets<T>(\n left?: ReadonlySet<T>,\n right?: ReadonlySet<T>,\n): Set<T> | undefined {\n if (!left && !right) return undefined\n return new Set([...(left ?? []), ...(right ?? [])])\n}\n\nfunction mergeResumeToolState(\n left: ChatResumeToolState | undefined,\n right: ChatResumeToolState | undefined,\n): ChatResumeToolState | undefined {\n if (!left) return right\n if (!right) return left\n return {\n approvals: mergeMaps(left.approvals, right.approvals),\n clientToolResults: mergeMaps(\n left.clientToolResults,\n right.clientToolResults,\n ),\n genericInterrupts: mergeMaps(\n left.genericInterrupts,\n right.genericInterrupts,\n ),\n genericInterruptRequests: mergeMaps(\n left.genericInterruptRequests,\n right.genericInterruptRequests,\n ),\n deniedToolResults: mergeMaps(\n left.deniedToolResults,\n right.deniedToolResults,\n ),\n cancelledToolCallIds: mergeSets(\n left.cancelledToolCallIds,\n right.cancelledToolCallIds,\n ),\n }\n}\n\nfunction rejectMixedRunPending(\n pending: Array<InterruptRecord>,\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n): void {\n const runIds = new Set(pending.map((interrupt) => interrupt.runId))\n if (runIds.size <= 1) return\n throw new InterruptResumeValidationError([\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId: ctx.runId,\n generation: 0,\n interruptIds: pending.map((interrupt) => interrupt.interruptId),\n code: 'stale',\n message: 'Thread has pending interrupts from more than one run.',\n source: 'server',\n retryable: false,\n },\n ])\n}\n\nfunction validatePendingResumes(\n pending: Array<InterruptRecord>,\n resume: Array<RunAgentResumeItem> | undefined,\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n): Map<string, RunAgentResumeItem> {\n const interruptedRunId = pending[0]?.runId ?? ctx.runId\n const failure = (\n interruptId: string,\n code: 'conflict' | 'unknown-interrupt',\n message: string,\n ): never => {\n throw new InterruptResumeValidationError([\n {\n scope: 'item',\n threadId: ctx.threadId,\n interruptedRunId,\n generation: 0,\n interruptId,\n code,\n message,\n source: 'client',\n retryable: false,\n },\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId,\n generation: 0,\n interruptIds: pending.map((interrupt) => interrupt.interruptId),\n code: code === 'conflict' ? 'conflict' : 'incomplete-batch',\n message:\n 'Resume entries must resolve or cancel the complete interrupt batch.',\n source: 'client',\n retryable: false,\n },\n ])\n }\n const pendingInterruptIds = new Set(\n pending.map((interrupt) => interrupt.interruptId),\n )\n const resumeByInterruptId = new Map<string, RunAgentResumeItem>()\n for (const entry of resume ?? []) {\n if (resumeByInterruptId.has(entry.interruptId)) {\n return failure(\n entry.interruptId,\n 'conflict',\n `Interrupt ${entry.interruptId} has duplicate resume entries.`,\n )\n }\n resumeByInterruptId.set(entry.interruptId, entry)\n }\n if (pending.length === 0) {\n const staleEntry = resume?.[0]\n if (staleEntry) {\n return failure(\n staleEntry.interruptId,\n 'unknown-interrupt',\n `Resume entry references non-pending interrupt ${staleEntry.interruptId}.`,\n )\n }\n return resumeByInterruptId\n }\n const firstPending = pending[0]\n if (firstPending === undefined) return resumeByInterruptId\n if (!resume || resume.length === 0) {\n return failure(\n firstPending.interruptId,\n 'unknown-interrupt',\n `Thread has pending interrupts; resume is required before accepting new input.`,\n )\n }\n\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) {\n return failure(\n interrupt.interruptId,\n 'unknown-interrupt',\n `Missing resume entry for pending interrupt ${interrupt.interruptId}.`,\n )\n }\n if (!validResumeStatuses.has(entry.status)) {\n return failure(\n interrupt.interruptId,\n 'unknown-interrupt',\n `Invalid resume status for pending interrupt ${interrupt.interruptId}: ${entry.status}.`,\n )\n }\n }\n for (const entry of resume) {\n if (!pendingInterruptIds.has(entry.interruptId)) {\n return failure(\n entry.interruptId,\n 'unknown-interrupt',\n `Resume entry references non-pending interrupt ${entry.interruptId}.`,\n )\n }\n }\n return resumeByInterruptId\n}\n\nasync function applyPendingResumes(\n pending: Array<InterruptRecord>,\n resumeByInterruptId: Map<string, RunAgentResumeItem>,\n interrupts: NonNullable<AIPersistence['stores']['interrupts']>,\n): Promise<void> {\n const entries: Array<InterruptCommitEntry> = []\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) continue\n if (entry.status === 'resolved') {\n entries.push({\n interruptId: interrupt.interruptId,\n status: 'resolved',\n response: entry.payload,\n })\n } else {\n entries.push({\n interruptId: interrupt.interruptId,\n status: 'cancelled',\n })\n }\n }\n if (interrupts.commitBatch) {\n await interrupts.commitBatch(entries)\n return\n }\n const ids = new Set<string>()\n for (const entry of entries) {\n if (ids.has(entry.interruptId)) {\n throw new Error(\n `Interrupt batch contains duplicate id: ${entry.interruptId}.`,\n )\n }\n ids.add(entry.interruptId)\n const existing = await interrupts.get(entry.interruptId)\n if (!existing) {\n throw new Error(\n `Interrupt batch references missing id: ${entry.interruptId}.`,\n )\n }\n if (existing.status !== 'pending') {\n throw new Error(\n `Interrupt batch references non-pending id: ${entry.interruptId}.`,\n )\n }\n }\n for (const entry of entries) {\n if (entry.status === 'resolved') {\n await interrupts.resolve(entry.interruptId, entry.response)\n } else {\n await interrupts.cancel(entry.interruptId)\n }\n }\n}\n\n/**\n * Commit the resumes stashed in `onConfig`, marking each resumed interrupt\n * resolved/cancelled. Called only from success boundaries (`onFinish`, and the\n * `onChunk` interrupt boundary) so a provider failure or abort between accepting\n * the resume and reaching a boundary leaves the interrupts pending — the\n * approval is not consumed and a retry with the same resume succeeds. Idempotent\n * and a no-op when nothing is stashed.\n */\nasync function commitPendingResumes(\n state: RunStateEntry | undefined,\n interrupts: AIPersistence['stores']['interrupts'],\n): Promise<void> {\n if (!state?.pendingResumes || !interrupts) return\n const { pending, resumeByInterruptId } = state.pendingResumes\n // Apply first; only clear the in-memory stash after every resolve/cancel\n // succeeds so a mid-loop store failure can still re-drive remaining ids\n // if the hook is retried (or a later boundary re-enters commit).\n await applyPendingResumes(pending, resumeByInterruptId, interrupts)\n state.pendingResumes = undefined\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : null\n}\n\nfunction stringField(\n value: Record<string, unknown>,\n key: string,\n): string | undefined {\n return typeof value[key] === 'string' ? value[key] : undefined\n}\n\nfunction interruptKind(interrupt: InterruptRecord): string | undefined {\n const metadata = objectValue(interrupt.payload.metadata)\n return metadata ? stringField(metadata, 'kind') : undefined\n}\n\nfunction hasReservedInterruptBinding(payload: unknown): boolean {\n const descriptor = objectValue(payload)\n const metadata = objectValue(descriptor?.metadata)\n return !!metadata && 'tanstack:interruptBinding' in metadata\n}\n\nfunction isPersistedInterruptDescriptor(\n value: unknown,\n): value is Interrupt & { reason: string; message: string } {\n const record = objectValue(value)\n return (\n !!record &&\n typeof record.id === 'string' &&\n typeof record.reason === 'string' &&\n typeof record.message === 'string'\n )\n}\n\n/**\n * Does this pending record belong to the TanStack chat resume protocol?\n *\n * An external system can persist an AG-UI descriptor in the same durable\n * thread. A descriptor without a TanStack binding or legacy tool marker stays\n * pending for its owner, but it does not make this resume incomplete. Older\n * opaque records remain owned because their provenance cannot be known.\n */\nfunction isChatOwnedPendingInterrupt(interrupt: InterruptRecord): boolean {\n const kind = interruptKind(interrupt)\n return (\n !isPersistedInterruptDescriptor(interrupt.payload) ||\n stringField(interrupt.payload, 'toolCallId') !== undefined ||\n kind === 'approval' ||\n kind === 'client_tool' ||\n hasReservedInterruptBinding(interrupt.payload)\n )\n}\n\nfunction durableGenericFailure(\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n persisted: InterruptRecord,\n message: string,\n): InterruptResumeValidationError {\n return new InterruptResumeValidationError([\n {\n scope: 'item',\n threadId: ctx.threadId,\n interruptedRunId: persisted.runId || ctx.runId,\n generation: 0,\n interruptId: persisted.interruptId,\n code: 'stale',\n message,\n source: 'server',\n retryable: false,\n },\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId: persisted.runId || ctx.runId,\n generation: 0,\n interruptIds: [persisted.interruptId],\n code: 'item-validation-failed',\n message: 'One or more persisted interrupt records are invalid.',\n source: 'server',\n retryable: false,\n },\n ])\n}\n\nasync function durableGenericResumeState(\n ctx: ChatMiddlewareContext,\n pending: Array<InterruptRecord>,\n resume: ReadonlyArray<RunAgentResumeItem>,\n tools: Array<Tool>,\n): Promise<ChatResumeToolState | undefined> {\n const registry = getGenericInterruptDefinitionRegistry(ctx, {\n optional: true,\n })\n const records: Array<PendingInterruptResumeRecord> = []\n\n for (const persisted of pending) {\n if (!isPersistedInterruptDescriptor(persisted.payload)) {\n if (hasReservedInterruptBinding(persisted.payload)) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has an invalid binding descriptor.`,\n )\n }\n continue\n }\n const descriptor = persisted.payload\n const binding = readInterruptBinding(descriptor)\n if (!binding) {\n if (hasReservedInterruptBinding(descriptor)) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has an invalid or incomplete binding.`,\n )\n }\n continue\n }\n if (\n descriptor.id !== persisted.interruptId ||\n binding.interruptId !== persisted.interruptId ||\n binding.interruptedRunId !== persisted.runId ||\n binding.generation !== 0\n ) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has stale correlation metadata.`,\n )\n }\n if (binding.kind !== 'generic') {\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n })\n continue\n }\n if (\n !binding.definitionId ||\n !binding.key ||\n binding.batchIndex === undefined\n ) {\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n })\n continue\n }\n if (!registry) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} cannot be restored because no interrupt registry is available.`,\n )\n }\n const definition = registry.definitions.get(binding.definitionId)\n if (!definition) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt definition ${binding.definitionId} is unavailable.`,\n )\n }\n const metadata = objectValue(descriptor.metadata)\n const payload = metadata?.['tanstack:interruptPayload']\n let request: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n try {\n request = rehydrateInterruptRequest(definition, {\n key: binding.key,\n reason: descriptor.reason,\n message: descriptor.message,\n ...(descriptor.expiresAt !== undefined\n ? { expiresAt: descriptor.expiresAt }\n : {}),\n ...(payload !== undefined ? { payload } : {}),\n })\n } catch (error) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} is invalid: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n const emitted = createInterruptBinding(request, {\n batchIndex: binding.batchIndex,\n })\n if (\n emitted.descriptor.responseSchemaHash !== binding.responseSchemaHash ||\n emitted.descriptor.payloadSchemaHash !== binding.payloadSchemaHash ||\n binding.interruptId !== persisted.interruptId\n ) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} is stale.`,\n )\n }\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n genericRequest: request,\n })\n }\n\n const firstRecord = records[0]\n if (firstRecord === undefined) return undefined\n const interruptedRunId = firstRecord.binding.interruptedRunId\n const generation = firstRecord.binding.generation\n const validated = await validateInterruptResumeBatch({\n threadId: ctx.threadId,\n interruptedRunId,\n generation,\n pending: records,\n resume: resume.filter((entry) =>\n records.some((record) => record.interruptId === entry.interruptId),\n ),\n tools,\n })\n if (validated.errors.length > 0 || !validated.resumeToolState) {\n throw new InterruptResumeValidationError(validated.errors)\n }\n type GenericRecord = PendingInterruptResumeRecord & {\n binding: Extract<\n PendingInterruptResumeRecord['binding'],\n { kind: 'generic' }\n >\n genericRequest: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n }\n const isGenericRecord = (\n record: PendingInterruptResumeRecord,\n ): record is GenericRecord =>\n record.binding.kind === 'generic' && record.genericRequest !== undefined\n const genericRecords: Array<{ record: GenericRecord; batchIndex: number }> =\n []\n const batchIndexes = new Set<number>()\n for (const record of records) {\n if (!isGenericRecord(record)) continue\n const batchIndex = record.binding.batchIndex\n if (batchIndex === undefined || batchIndexes.has(batchIndex)) {\n throw new InterruptResumeValidationError([\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId,\n generation,\n interruptIds: records.map((item) => item.interruptId),\n code: 'stale',\n message:\n 'Persisted generic interrupts have duplicate or invalid batch indexes.',\n source: 'server',\n retryable: false,\n },\n ])\n }\n batchIndexes.add(batchIndex)\n genericRecords.push({ record, batchIndex })\n }\n genericRecords.sort((left, right) => left.batchIndex - right.batchIndex)\n return {\n ...validated.resumeToolState,\n genericInterruptRequests: new Map(\n genericRecords.flatMap(({ record }) =>\n record.genericRequest\n ? [[record.interruptId, record.genericRequest] as const]\n : [],\n ),\n ),\n }\n}\n\nfunction resolvedApprovalDecision(entry: RunAgentResumeItem): boolean {\n if (entry.status === 'cancelled') return false\n const payload = objectValue(entry.payload)\n // Fail closed: persisted resume payloads may be malformed or truncated, so a\n // missing/non-boolean `approved` denies the tool rather than running it.\n return typeof payload?.approved === 'boolean' ? payload.approved : false\n}\n\n/**\n * Translate the persisted pending interrupts + the resume batch into the\n * `ChatResumeToolState` the chat engine consumes. This is the server-authoritative\n * counterpart to the engine's ephemeral (client-history) reconstruction: because\n * the persistence flow sends empty client messages, the engine has no history to\n * rebuild from, so persistence supplies the resume state directly (and clears\n * `config.resume` so the ephemeral path is skipped — see `onConfig`).\n */\nfunction resumeToolStateFromPending(\n pending: Array<InterruptRecord>,\n resumeByInterruptId: Map<string, RunAgentResumeItem>,\n): ChatResumeToolState | undefined {\n const approvals = new Map<string, ToolApprovalResolution>()\n const clientToolResults = new Map<string, unknown>()\n const cancelledToolCallIds = new Set<string>()\n\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) continue\n\n const kind = interruptKind(interrupt)\n const reason = stringField(interrupt.payload, 'reason')\n const toolCallId = stringField(interrupt.payload, 'toolCallId')\n\n if (entry.status === 'cancelled' && toolCallId) {\n cancelledToolCallIds.add(toolCallId)\n }\n\n if (kind === 'approval' || reason === 'approval_required') {\n approvals.set(interrupt.interruptId, resolvedApprovalDecision(entry))\n continue\n }\n\n if (\n entry.status === 'resolved' &&\n toolCallId &&\n (kind === 'client_tool' || reason === 'client_tool_input')\n ) {\n clientToolResults.set(toolCallId, entry.payload)\n }\n }\n\n if (\n approvals.size === 0 &&\n clientToolResults.size === 0 &&\n cancelledToolCallIds.size === 0\n ) {\n return undefined\n }\n return { approvals, clientToolResults, cancelledToolCallIds }\n}\n\nfunction interruptPayload(interrupt: unknown): Record<string, unknown> {\n return interrupt && typeof interrupt === 'object'\n ? { ...(interrupt as Record<string, unknown>) }\n : { value: interrupt }\n}\n\n// ---------------------------------------------------------------------------\n// Generation artifact extraction / persistence\n// ---------------------------------------------------------------------------\n\nfunction isArtifactRef(value: unknown): value is PersistedArtifactRef {\n const record = objectValue(value)\n return !!record && typeof record.artifactId === 'string'\n}\n\nfunction mediaActivity(\n activity: GenerationMiddlewareContext['activity'],\n): PersistedArtifactActivity | undefined {\n return activity === 'image' ||\n activity === 'audio' ||\n activity === 'tts' ||\n activity === 'video' ||\n activity === 'transcription'\n ? activity\n : undefined\n}\n\nfunction parseDataUrl(\n value: string,\n): { mimeType: string; bytes: Uint8Array } | undefined {\n const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(value)\n if (!match) return undefined\n const mimeType = match[1] || 'application/octet-stream'\n const raw = match[3] ?? ''\n // A plain (non-base64) data URL may carry a bare `%` (`data:text/plain,100%`),\n // which makes `decodeURIComponent` throw. Fall back to the literal payload so\n // a malformed escape doesn't fail the whole generation.\n let payload: string\n try {\n payload = decodeURIComponent(raw)\n } catch {\n payload = raw\n }\n return {\n mimeType,\n bytes: match[2]\n ? base64ToUint8Array(payload)\n : new TextEncoder().encode(payload),\n }\n}\n\nfunction extensionForMime(mimeType: string | undefined): string {\n if (mimeType === undefined) return 'bin'\n\n switch (mimeType) {\n case 'image/png':\n return 'png'\n case 'image/jpeg':\n return 'jpg'\n case 'audio/wav':\n return 'wav'\n case 'audio/mpeg':\n return 'mp3'\n case 'audio/mp3':\n return 'mp3'\n case 'video/mp4':\n return 'mp4'\n case 'application/json':\n return 'json'\n default:\n return 'bin'\n }\n}\n\nfunction defaultArtifactName(\n descriptor: GenerationArtifactDescriptor,\n activity: PersistedArtifactActivity,\n index: number,\n): string {\n const ext = extensionForMime(descriptor.mimeType)\n return `${activity}-${descriptor.role}-${descriptor.mediaType ?? 'artifact'}-${index}.${ext}`\n}\n\nfunction sourcePartDescriptors(\n part: unknown,\n role: PersistedArtifactRole,\n path: string,\n): Array<GenerationArtifactDescriptor> {\n const record = objectValue(part)\n const type = stringField(record ?? {}, 'type')\n const source = objectValue(record?.source)\n if (\n !record ||\n !source ||\n (type !== 'image' && type !== 'audio' && type !== 'video')\n ) {\n return []\n }\n const sourceType = stringField(source, 'type')\n const mimeType = stringField(source, 'mimeType') ?? `${type}/mpeg`\n if (sourceType === 'data') {\n const value = stringField(source, 'value')\n if (!value) return []\n return [\n {\n role,\n path,\n mediaType: type,\n mimeType,\n bytes: base64ToUint8Array(value),\n },\n ]\n }\n if (sourceType === 'url') {\n const value = stringField(source, 'value')\n if (!value) return []\n return [{ role, path, mediaType: type, mimeType, url: value }]\n }\n return []\n}\n\nfunction promptInputDescriptors(\n inputs: unknown,\n): Array<GenerationArtifactDescriptor> {\n const prompt = objectValue(inputs)?.prompt\n if (!Array.isArray(prompt)) return []\n\n const counts: Record<string, number> = { image: 0, audio: 0, video: 0 }\n const descriptors: Array<GenerationArtifactDescriptor> = []\n for (const part of prompt) {\n const type = stringField(objectValue(part) ?? {}, 'type')\n if (type !== 'image' && type !== 'audio' && type !== 'video') continue\n const index = counts[type] ?? 0\n counts[type] = index + 1\n descriptors.push(\n ...sourcePartDescriptors(part, 'input', `prompt.${type}s.${index}`),\n )\n }\n return descriptors\n}\n\nfunction generatedMediaDescriptor(args: {\n role: PersistedArtifactRole\n path: string\n mediaType: 'image' | 'audio' | 'video'\n mimeType: string\n media: unknown\n jobId?: string\n expiresAt?: string | Date\n}): GenerationArtifactDescriptor | undefined {\n const media = objectValue(args.media)\n if (!media) return undefined\n const b64Json = stringField(media, 'b64Json')\n if (b64Json) {\n return {\n role: args.role,\n path: args.path,\n mediaType: args.mediaType,\n mimeType: stringField(media, 'contentType') ?? args.mimeType,\n bytes: base64ToUint8Array(b64Json),\n jobId: args.jobId,\n expiresAt: args.expiresAt,\n }\n }\n const url = stringField(media, 'url')\n if (url) {\n return {\n role: args.role,\n path: args.path,\n mediaType: args.mediaType,\n mimeType: stringField(media, 'contentType') ?? args.mimeType,\n url,\n jobId: args.jobId,\n expiresAt: args.expiresAt,\n }\n }\n return undefined\n}\n\nfunction builtInArtifactDescriptors(\n activity: PersistedArtifactActivity,\n inputs: unknown,\n result: unknown,\n): Array<GenerationArtifactDescriptor> {\n const descriptors = promptInputDescriptors(inputs)\n const output = objectValue(result)\n if (!output) return descriptors\n\n if (activity === 'image' && Array.isArray(output.images)) {\n output.images.forEach((image, index) => {\n const descriptor = generatedMediaDescriptor({\n role: 'output',\n path: `images.${index}`,\n mediaType: 'image',\n mimeType: 'image/png',\n media: image,\n })\n if (descriptor) descriptors.push(descriptor)\n })\n }\n\n if (activity === 'audio') {\n const descriptor = generatedMediaDescriptor({\n role: 'output',\n path: 'audio',\n mediaType: 'audio',\n mimeType: 'audio/mpeg',\n media: output.audio,\n })\n if (descriptor) descriptors.push(descriptor)\n }\n\n if (activity === 'tts') {\n const audio = stringField(output, 'audio')\n if (audio) {\n const format = stringField(output, 'format')\n descriptors.push({\n role: 'output',\n path: 'audio',\n mediaType: 'audio',\n mimeType:\n stringField(output, 'contentType') ??\n (format ? `audio/${format}` : 'audio/mpeg'),\n bytes: base64ToUint8Array(audio),\n })\n }\n }\n\n if (activity === 'video' && typeof output.url === 'string') {\n descriptors.push({\n role: 'output',\n path: 'video',\n mediaType: 'video',\n mimeType: 'video/mp4',\n url: output.url,\n jobId: stringField(output, 'jobId'),\n expiresAt:\n output.expiresAt instanceof Date ? output.expiresAt : undefined,\n })\n }\n\n if (activity === 'transcription') {\n const audio = objectValue(inputs)?.audio\n if (typeof audio === 'string') {\n const data = parseDataUrl(audio)\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: data?.mimeType ?? 'audio/mpeg',\n bytes: data?.bytes ?? base64ToUint8Array(audio),\n })\n } else if (audio instanceof ArrayBuffer) {\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: 'audio/mpeg',\n bytes: audio.slice(0),\n })\n } else if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: audio.type || 'audio/mpeg',\n bytes: audio,\n })\n }\n if (Array.isArray(output.segments) || Array.isArray(output.words)) {\n descriptors.push({\n role: 'output',\n path: 'transcription',\n mediaType: 'json',\n mimeType: 'application/json',\n json: output,\n })\n }\n }\n\n return descriptors\n}\n\n/**\n * Reject hosts that only make sense as an SSRF target: loopback, link-local\n * (including the cloud metadata address), private, and unique-local ranges.\n *\n * Applied to caller-supplied input URLs only. Provider result URLs skip it on\n * purpose — a self-hosted or local provider legitimately returns a `localhost`\n * URL, and those live inside the same trust boundary as the adapter itself.\n *\n * This checks IP *literals*. A hostname that resolves to a private address\n * passes, which is why `allowInputUrl` is required rather than optional.\n */\nfunction isBlockedInputHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '')\n if (host === 'localhost' || host.endsWith('.localhost')) return true\n\n const ipv4 = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(host)\n if (ipv4) {\n const [a, b] = [Number(ipv4[1]), Number(ipv4[2])]\n if (a === 127 || a === 0 || a === 10) return true\n if (a === 169 && b === 254) return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n return false\n }\n\n if (host === '::' || host === '::1') return true\n if (host.startsWith('fe80:')) return true // link-local\n if (/^f[cd][0-9a-f]{2}:/.test(host)) return true // unique-local\n // IPv4-mapped IPv6 — re-check the embedded address. `new URL()` normalizes\n // `::ffff:127.0.0.1` to the hex form `::ffff:7f00:1`, so accept both.\n const mappedDotted = /^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/.exec(\n host,\n )\n if (mappedDotted?.[1]) return isBlockedInputHost(mappedDotted[1])\n const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host)\n if (mappedHex?.[1] && mappedHex[2]) {\n const high = Number.parseInt(mappedHex[1], 16)\n const low = Number.parseInt(mappedHex[2], 16)\n return isBlockedInputHost(\n `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`,\n )\n }\n return false\n}\n\n/**\n * Fail the stream once more than `maxBytes` have passed through, so an\n * unexpectedly huge artifact can't fill the blob store.\n *\n * Only used when the response does NOT already bound itself — a chunked reply,\n * or a content-encoded one whose declared length describes the compressed\n * bytes. When `content-length` describes the body the store will drain, HTTP\n * framing is the bound and wrapping would only cost the caller the declared\n * length: a `TransformStream`'s readable side carries none, which is what\n * pushes a length-strict runtime (workerd + R2) onto a multipart upload.\n */\nfunction capBodySize(\n body: ReadableStream<Uint8Array>,\n maxBytes: number,\n url: string,\n): ReadableStream<Uint8Array> {\n let seen = 0\n return body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n seen += chunk.byteLength\n if (seen > maxBytes) {\n controller.error(\n new Error(\n `Artifact at ${url} exceeds maxArtifactBytes (${maxBytes}).`,\n ),\n )\n return\n }\n controller.enqueue(chunk)\n },\n }),\n )\n}\n\n/**\n * Resolve a descriptor to the bytes to store. Returns `undefined` when the\n * descriptor is deliberately not persisted — today that means a caller-supplied\n * input URL without an `allowInputUrl` opt-in.\n */\nasync function descriptorBody(\n descriptor: GenerationArtifactDescriptor,\n opts: ArtifactPersistenceOptions | undefined,\n): Promise<\n | {\n body: BlobBody\n size: number\n /**\n * Exact byte length of a streamed body, when the origin declared one\n * that survives decoding — forwarded to `BlobStore.put` as\n * `BlobPutOptions.expectedLength`. Undefined when unknown.\n */\n expectedLength?: number\n mimeType: string\n sourceUrl?: string\n }\n | undefined\n> {\n if (descriptor.json !== undefined) {\n const body = JSON.stringify(descriptor.json)\n return {\n body,\n size: new TextEncoder().encode(body).byteLength,\n mimeType: descriptor.mimeType ?? 'application/json',\n }\n }\n\n if (descriptor.bytes !== undefined) {\n const body = descriptor.bytes\n let size: number\n if (typeof body === 'string') {\n size = new TextEncoder().encode(body).byteLength\n } else if (body instanceof ArrayBuffer) {\n size = body.byteLength\n } else if (ArrayBuffer.isView(body)) {\n size = body.byteLength\n } else if (typeof Blob !== 'undefined' && body instanceof Blob) {\n size = body.size\n } else {\n size = 0\n }\n return {\n body,\n size,\n mimeType: descriptor.mimeType ?? 'application/octet-stream',\n }\n }\n\n if (descriptor.url) {\n const data = parseDataUrl(descriptor.url)\n if (data) {\n return {\n body: data.bytes,\n size: data.bytes.byteLength,\n mimeType: descriptor.mimeType ?? data.mimeType,\n }\n }\n // A caller-controlled input URL is never fetched unless the app opted in\n // with a validating predicate. Skipped, not thrown: not mirroring someone\n // else's URL is the intended default, and the run itself is fine.\n const isCallerSupplied = descriptor.role === 'input'\n const allowInputUrl = opts?.allowInputUrl\n if (isCallerSupplied && !allowInputUrl) return undefined\n\n let target: URL\n try {\n target = new URL(descriptor.url)\n } catch {\n throw new Error(\n `Failed to persist artifact: ${descriptor.url} is not a valid URL.`,\n )\n }\n if (target.protocol !== 'https:' && target.protocol !== 'http:') {\n throw new Error(\n `Refusing to fetch artifact over ${target.protocol} (${descriptor.path}).`,\n )\n }\n if (allowInputUrl && isCallerSupplied) {\n if (isBlockedInputHost(target.hostname)) {\n throw new Error(\n `Refusing to fetch input artifact from internal host ${target.hostname}.`,\n )\n }\n if (!(await allowInputUrl({ url: target, descriptor }))) {\n throw new Error(\n `Refusing to fetch input artifact from ${target.hostname}: rejected by allowInputUrl.`,\n )\n }\n }\n\n const maxBytes = opts?.maxArtifactBytes ?? DEFAULT_MAX_ARTIFACT_BYTES\n const fetchArtifact = opts?.artifactFetch ?? globalThis.fetch\n const response = await fetchArtifact(target, {\n // Provider CDNs redirect routinely, so output fetches follow. An input\n // fetch must not: a 302 would land on a host neither check ever saw.\n redirect: isCallerSupplied ? 'manual' : 'follow',\n signal: AbortSignal.timeout(\n opts?.artifactFetchTimeoutMs ?? DEFAULT_ARTIFACT_FETCH_TIMEOUT_MS,\n ),\n })\n if (isCallerSupplied && response.status >= 300 && response.status < 400) {\n throw new Error(\n `Refusing to follow a redirect for input artifact ${descriptor.path}.`,\n )\n }\n if (!response.ok) {\n throw new Error(\n `Failed to persist artifact from ${descriptor.url}: HTTP ${response.status}`,\n )\n }\n // `headers.get` returns null when the header is absent, and\n // `Number(null) === 0` — parse only a present header, or a chunked reply\n // would read as a declared length of 0 (harmless, but the early-reject\n // below would silently never be reachable for it).\n const contentLength = response.headers.get('content-length')\n const declaredLength =\n contentLength === null ? undefined : Number(contentLength)\n if (\n maxBytes !== false &&\n declaredLength !== undefined &&\n Number.isFinite(declaredLength) &&\n declaredLength > maxBytes\n ) {\n throw new Error(\n `Artifact at ${descriptor.url} exceeds maxArtifactBytes (${maxBytes}).`,\n )\n }\n const mimeType =\n descriptor.mimeType ??\n response.headers.get('content-type') ??\n 'application/octet-stream'\n // A declared length is the DECODED body's length only when the response is\n // not content-encoded: fetch transparently decompresses, so on a gzipped\n // reply `content-length` measures the compressed bytes and the decoded\n // stream can be arbitrarily longer. Only trust it when it provably\n // describes what the store will drain.\n const encoding = response.headers.get('content-encoding')\n const decodedLengthIsKnown =\n declaredLength !== undefined &&\n Number.isFinite(declaredLength) &&\n (encoding === null || encoding === 'identity')\n const expectedLength = decodedLengthIsKnown ? declaredLength : undefined\n // Stream the body straight into the blob store instead of buffering the\n // whole artifact in memory. `size` is left 0 (unknown up front); the store\n // records the actual byte length as it drains the stream. Fall back to\n // buffering only when the response has no body to stream.\n if (response.body) {\n return {\n // Wrap ONLY when the response does not already bound itself. A\n // trustworthy `content-length` was checked against the cap above, and\n // HTTP framing holds the origin to it — a body cannot exceed a length\n // it declared — so the counter would add nothing and cost everything:\n // it is a TransformStream, whose readable side has no declared length,\n // and that missing length is precisely what breaks `R2Bucket.put`.\n // Unwrapped, the runtime's own length rides along and R2 single-shots\n // the stream. What still needs the counter: a chunked reply (no\n // declared length at all) and a content-encoded one (declared length\n // measures the compressed bytes, so the decoded stream is a\n // decompression bomb waiting to happen).\n body:\n maxBytes === false || decodedLengthIsKnown\n ? response.body\n : capBodySize(response.body, maxBytes, descriptor.url),\n size: 0,\n expectedLength,\n mimeType,\n sourceUrl: descriptor.url,\n }\n }\n const body = await response.arrayBuffer()\n if (maxBytes !== false && body.byteLength > maxBytes) {\n throw new Error(\n `Artifact at ${descriptor.url} exceeds maxArtifactBytes (${maxBytes}).`,\n )\n }\n return {\n body,\n size: body.byteLength,\n mimeType,\n sourceUrl: descriptor.url,\n }\n }\n\n throw new Error(\n `Artifact descriptor ${descriptor.path} has no bytes, url, or json.`,\n )\n}\n\nasync function persistGenerationArtifacts(\n persistence: AIPersistence,\n opts: WithGenerationPersistenceOptions,\n ctx: GenerationMiddlewareContext,\n result: unknown,\n): Promise<Array<PersistedArtifactRef>> {\n const activity = mediaActivity(ctx.activity)\n if (!activity) return []\n\n // Resolved the same way the run record is, so an artifact always lands in the\n // same slot as the run that produced it.\n const threadId = generationScope(ctx, opts)\n const runId = ctx.runId ?? ctx.requestId\n const extractionInput: GenerationArtifactExtractionInput = {\n activity,\n provider: ctx.provider,\n model: ctx.model,\n threadId,\n runId,\n inputs: ctx.artifactInputs,\n result,\n }\n const extracted =\n opts?.extractArtifacts !== undefined\n ? await opts.extractArtifacts(extractionInput)\n : builtInArtifactDescriptors(activity, ctx.artifactInputs, result)\n\n if (extracted.length === 0) return []\n\n const existingRefs = extracted.filter(isArtifactRef)\n const descriptors = extracted.filter(\n (item): item is GenerationArtifactDescriptor => !isArtifactRef(item),\n )\n if (descriptors.length === 0) return existingRefs\n\n if (!persistence.stores.artifacts || !persistence.stores.blobs) {\n throw new Error(\n 'Generation artifact persistence requires stores.artifacts and stores.blobs.',\n )\n }\n\n const refs: Array<PersistedArtifactRef> = [...existingRefs]\n for (const [index, descriptor] of descriptors.entries()) {\n const artifactId = ctx.createId('artifact')\n const resolved = await descriptorBody(descriptor, opts)\n // Deliberately not persisted (an input URL with no `allowInputUrl` opt-in):\n // no blob, no record, no ref — the rest of the run is unaffected.\n if (!resolved) continue\n const { body, size, expectedLength, mimeType, sourceUrl } = resolved\n // Resolved before the blob write so `storageKey` can build a path from the\n // final filename (extensions, slugs) rather than guessing at one.\n const name =\n opts?.nameArtifact?.({\n descriptor: { ...descriptor, mimeType },\n activity,\n provider: ctx.provider,\n model: ctx.model,\n threadId,\n runId,\n index,\n }) ??\n descriptor.name ??\n defaultArtifactName({ ...descriptor, mimeType }, activity, index)\n const key =\n opts?.storageKey?.({\n artifactId,\n runId,\n threadId,\n role: descriptor.role,\n activity,\n path: descriptor.path,\n mimeType,\n name,\n }) ?? artifactBlobKey({ runId, artifactId })\n const stored = await persistence.stores.blobs.put(key, body, {\n contentType: mimeType,\n // Exact decoded length when the origin declared one — lets a store\n // single-shot the stream (e.g. R2 via FixedLengthStream) instead of\n // buffering or going multipart. Absent when unknown.\n ...(expectedLength !== undefined ? { expectedLength } : {}),\n customMetadata: {\n runId,\n threadId,\n role: descriptor.role,\n activity,\n path: descriptor.path,\n },\n })\n // For streamed downloads the descriptor size is unknown (0); the store\n // reports the real byte length once it has drained the stream.\n const resolvedSize = size || stored.size || 0\n const createdAtMs = Date.now()\n const record: ArtifactRecord = {\n artifactId,\n runId,\n threadId,\n // Always recorded: with a custom `storageKey` the path is no longer\n // derivable from the record, so the reader has to be told where it went.\n blobKey: key,\n name,\n mimeType,\n size: resolvedSize,\n sourceUrl,\n createdAt: createdAtMs,\n }\n await persistence.stores.artifacts.save(record)\n refs.push({\n role: descriptor.role,\n artifactId,\n threadId,\n runId,\n name,\n mimeType,\n size: resolvedSize,\n createdAt: new Date(createdAtMs).toISOString(),\n ...(sourceUrl ? { sourceUrl } : {}),\n source: {\n activity,\n path: descriptor.path,\n provider: ctx.provider,\n model: ctx.model,\n mediaType: descriptor.mediaType,\n jobId: descriptor.jobId,\n expiresAt:\n descriptor.expiresAt instanceof Date\n ? descriptor.expiresAt.toISOString()\n : descriptor.expiresAt,\n },\n })\n }\n\n // Stamp the durable app-origin serve URL onto every ref that lacks one, so\n // clients render + restore media from your own origin, not the provider link.\n if (opts?.artifactUrl) {\n for (let i = 0; i < refs.length; i++) {\n const ref = refs[i]\n if (ref && !ref.url) {\n const url = opts.artifactUrl(ref)\n if (url) refs[i] = { ...ref, url }\n }\n }\n }\n\n return refs\n}\n\n/**\n * Rewrite the live result's media fields to each output ref's durable serve URL\n * (`ref.url`), so the live result matches what a reload restores. Keyed off the\n * ref's `source.path`: `images.<i>` → `result.images[i].url`, `video` →\n * `result.url`, `audio` (object) → `result.audio.url`. tts (a base64 string) and\n * transcription (json) have no media-URL field, so they are left as-is; their\n * durable bytes are reachable via `result.artifacts`. A no-op when no ref has a\n * `url`.\n */\nfunction applyDurableMediaUrls(\n result: Record<string, unknown>,\n refs: Array<PersistedArtifactRef>,\n): Record<string, unknown> {\n let next = result\n for (const ref of refs) {\n if (ref.role !== 'output' || !ref.url) continue\n const path = ref.source.path\n if (path.startsWith('images.')) {\n const index = Number(path.slice('images.'.length))\n const images = next.images\n if (Array.isArray(images) && objectValue(images[index])) {\n const cloned = [...images]\n cloned[index] = { ...objectValue(images[index]), url: ref.url }\n next = { ...next, images: cloned }\n }\n } else if (path === 'video') {\n next = { ...next, url: ref.url }\n } else if (path === 'audio' && objectValue(next.audio)) {\n next = { ...next, audio: { ...objectValue(next.audio), url: ref.url } }\n }\n }\n return next\n}\n\n// ---------------------------------------------------------------------------\n// Shared store / feature plan\n// ---------------------------------------------------------------------------\n\ninterface PersistencePlan {\n wantsInterrupts: boolean\n wantsArtifactPersistence: boolean\n runs: AIPersistence['stores']['runs']\n}\n\nfunction resolvePersistencePlan(persistence: AIPersistence): PersistencePlan {\n return {\n wantsInterrupts: persistence.stores.interrupts !== undefined,\n wantsArtifactPersistence:\n persistence.stores.artifacts !== undefined &&\n persistence.stores.blobs !== undefined,\n runs: persistence.stores.runs,\n }\n}\n\ntype StoreIsDefinitelyPresent<\n TStores extends AIPersistenceStores,\n TKey extends keyof AIPersistenceStores,\n> = TKey extends keyof TStores\n ? object extends Pick<TStores, TKey>\n ? false\n : [Exclude<TStores[TKey], undefined>] extends [never]\n ? false\n : true\n : false\n\ntype StoreIsDefinitelyAbsent<\n TStores extends AIPersistenceStores,\n TKey extends keyof AIPersistenceStores,\n> = TKey extends keyof TStores\n ? [Exclude<TStores[TKey], undefined>] extends [never]\n ? true\n : false\n : true\n\n/**\n * Chat entrypoint invalid when:\n * - `messages` is known-absent, or\n * - `interrupts` is known-present without `runs`.\n *\n * Fully optional bags (`AIPersistence` with all `?` keys) stay assignable and\n * are checked at runtime by {@link validateChatPersistenceStores}.\n */\ntype InvalidChatPersistence<TStores extends AIPersistenceStores> =\n StoreIsDefinitelyAbsent<TStores, 'messages'> extends true\n ? true\n : StoreIsDefinitelyPresent<TStores, 'interrupts'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'runs'>\n : false\n\n/**\n * Generation entrypoint invalid when `generationRuns` is known-absent, or when\n * exactly one of `artifacts` / `blobs` is present (artifact persistence needs\n * both).\n */\ntype InvalidGenerationPersistence<TStores extends AIPersistenceStores> =\n StoreIsDefinitelyAbsent<TStores, 'generationRuns'> extends true\n ? true\n : StoreIsDefinitelyPresent<TStores, 'artifacts'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'blobs'>\n : StoreIsDefinitelyPresent<TStores, 'blobs'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'artifacts'>\n : false\n\ntype ValidChatPersistence<TStores extends AIPersistenceStores> =\n InvalidChatPersistence<TStores> extends true ? never : unknown\n\ntype ValidGenerationPersistence<TStores extends AIPersistenceStores> =\n InvalidGenerationPersistence<TStores> extends true ? never : unknown\n\nasync function createOrResumeRun(\n runs: RunStore | undefined,\n runId: string,\n threadId: string,\n): Promise<TokenUsage | undefined> {\n const run = await runs?.createOrResume({\n runId,\n threadId,\n startedAt: Date.now(),\n })\n return run?.usage\n}\n\nfunction sumOptionalNumber(\n current: number | undefined,\n next: number | undefined,\n): number | undefined {\n if (current === undefined) return next\n if (next === undefined) return current\n return current + next\n}\n\nfunction sumNumberFields<T extends object>(\n current: T | undefined,\n next: T | undefined,\n): T | undefined {\n if (!current) return next\n if (!next) return current\n\n const result = { ...current }\n for (const key of Object.keys(next) as Array<keyof T>) {\n const currentValue = current[key]\n const nextValue = next[key]\n if (typeof nextValue === 'number') {\n result[key] = ((typeof currentValue === 'number' ? currentValue : 0) +\n nextValue) as T[keyof T]\n }\n }\n return result\n}\n\nfunction tokenUsageFromChunk(chunk: StreamChunk): TokenUsage | undefined {\n if (chunk.type !== 'RUN_FINISHED' && chunk.type !== 'RUN_ERROR') {\n return undefined\n }\n const usage = chunk.usage\n if (\n usage != null &&\n typeof usage === 'object' &&\n !Array.isArray(usage) &&\n 'promptTokens' in usage\n ) {\n return usage\n }\n const metadata = chunk.metadata\n const tanstack =\n metadata != null && typeof metadata === 'object' && 'tanstack' in metadata\n ? metadata.tanstack\n : undefined\n const leftover =\n tanstack != null && typeof tanstack === 'object' && !Array.isArray(tanstack)\n ? (tanstack as { usage?: TokenUsage }).usage\n : undefined\n return fromSpecTokenUsage(Array.isArray(usage) ? usage : undefined, leftover)\n}\n\nfunction accumulateTokenUsage(\n current: TokenUsage | undefined,\n next: TokenUsage,\n): TokenUsage {\n if (!current) return { ...next }\n\n const promptTokensDetails = sumNumberFields(\n current.promptTokensDetails,\n next.promptTokensDetails,\n )\n const completionTokensDetails = sumNumberFields(\n current.completionTokensDetails,\n next.completionTokensDetails,\n )\n const costDetails = sumNumberFields(current.costDetails, next.costDetails)\n // Provider-specific details are opaque, so retain the latest reported bag.\n const providerUsageDetails =\n next.providerUsageDetails ?? current.providerUsageDetails\n const durationSeconds = sumOptionalNumber(\n current.durationSeconds,\n next.durationSeconds,\n )\n const unitsBilled = sumOptionalNumber(current.unitsBilled, next.unitsBilled)\n const billed = accumulateBilled(current.billed, next.billed)\n const cost = sumOptionalNumber(current.cost, next.cost)\n\n return {\n ...current,\n ...next,\n promptTokens: current.promptTokens + next.promptTokens,\n completionTokens: current.completionTokens + next.completionTokens,\n totalTokens: current.totalTokens + next.totalTokens,\n ...(promptTokensDetails ? { promptTokensDetails } : {}),\n ...(completionTokensDetails ? { completionTokensDetails } : {}),\n ...(durationSeconds !== undefined ? { durationSeconds } : {}),\n ...(unitsBilled !== undefined ? { unitsBilled } : {}),\n ...(billed !== undefined ? { billed } : {}),\n ...(cost !== undefined ? { cost } : {}),\n ...(costDetails ? { costDetails } : {}),\n ...(providerUsageDetails ? { providerUsageDetails } : {}),\n }\n}\n\n/**\n * Sum billed quantities when both reports use the same unit. Different units\n * cannot be added, so the later report wins.\n */\nfunction accumulateBilled(\n current: BilledUsage | undefined,\n next: BilledUsage | undefined,\n): BilledUsage | undefined {\n if (!current) return next\n if (!next) return current\n if (current.unit !== next.unit) return next\n return { quantity: current.quantity + next.quantity, unit: current.unit }\n}\n\nasync function completeRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n // A late detach stamp from a superseded host can land after takeover\n // claimed the run. A terminal run is not detached.\n await runs?.update(runId, {\n status: 'completed',\n finishedAt: Date.now(),\n detachedSince: undefined,\n ...(usage ? { usage } : {}),\n })\n}\n\nasync function failRun(\n runs: RunStore | undefined,\n runId: string,\n error: unknown,\n usage?: TokenUsage,\n): Promise<void> {\n const runError = toRunErrorPayload(error)\n await runs?.update(runId, {\n status: 'failed',\n finishedAt: Date.now(),\n detachedSince: undefined,\n error: {\n message: runError.message,\n ...(runError.code !== undefined ? { code: runError.code } : {}),\n },\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Record a human-in-the-loop PAUSE.\n *\n * Deliberately writes NO `finishedAt`: `'interrupted'` is not a terminal status\n * (`isTerminalRunStatus('interrupted')` is `false`), and stamping a terminal\n * timestamp on it told every reader the run was over while it was in fact\n * waiting for a human. Only `abortRun`/`completeRun`/`failRun` finish a run.\n */\nexport async function interruptRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n await runs?.update(runId, {\n status: 'interrupted',\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Record that the run has ended for good — an explicit cancel, or a disconnect\n * on a run that has nothing to reattach to. Terminal, so it carries\n * `finishedAt`.\n */\nexport async function abortRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n await runs?.update(runId, {\n status: 'aborted',\n finishedAt: Date.now(),\n detachedSince: undefined,\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Whether some middleware has declared this run detachable — i.e. it has a\n * durable event log and a run store, so a disconnect can be survived and the\n * run picked back up rather than destroyed.\n *\n * The capability is read from CORE, never from `@tanstack/ai-sandbox`: sandbox\n * provides it, persistence consumes it, and a persistence → sandbox import\n * would invert the layering.\n */\nfunction detachableRun(ctx: ChatMiddlewareContext): boolean {\n return getDetachableRun(ctx, { optional: true }) === true\n}\n\n// ---------------------------------------------------------------------------\n// Chat middleware\n// ---------------------------------------------------------------------------\n\nfunction threadMessages(\n loaded: Array<ModelMessage> | MessagePage,\n): Array<ModelMessage> {\n return Array.isArray(loaded) ? loaded : loaded.messages\n}\n\n// Empty incoming keeps stored. Non-empty: the last incoming id that already\n// exists in stored is a cutoff (reload drops the old assistant after that\n// user). Same id is replaced in place. New ids and messages with no id are\n// appended.\nfunction mergeStoredMessages(\n stored: ReadonlyArray<ModelMessage>,\n incoming: ReadonlyArray<ModelMessage>,\n) {\n if (incoming.length === 0) {\n return stored.slice()\n }\n\n let cutoff = stored.length\n for (let index = incoming.length - 1; index >= 0; index--) {\n const id = incoming[index]?.id\n if (id === undefined) continue\n const storedIndex = stored.findIndex((message) => message.id === id)\n if (storedIndex >= 0) {\n cutoff = storedIndex + 1\n break\n }\n }\n const prefix = stored.slice(0, cutoff)\n\n const incomingById = new Map<string, ModelMessage>()\n for (const message of incoming) {\n const id = message.id\n if (id) incomingById.set(id, message)\n }\n\n const storedIds = new Set<string>()\n const merged: Array<ModelMessage> = []\n for (const message of prefix) {\n const id = message.id\n if (id) {\n storedIds.add(id)\n merged.push(incomingById.get(id) ?? message)\n continue\n }\n merged.push(message)\n }\n\n for (let index = 0; index < incoming.length; index++) {\n const message = incoming[index]\n if (!message) continue\n const id = message.id\n if (id && storedIds.has(id)) continue\n // Attach/reload can post the stored transcript again with no ids. Keep the\n // prefix row instead of appending a second copy of the same turn.\n if (!id) {\n const existing = merged[index]\n if (\n existing &&\n existing.id === undefined &&\n existing.role === message.role &&\n existing.content === message.content\n ) {\n continue\n }\n }\n merged.push(message)\n }\n\n return merged\n}\n\nexport interface WithPersistenceOptions {\n /**\n * Also persist a throttled snapshot of the in-progress assistant reply while\n * it streams. Off by default — the transcript is otherwise persisted at the\n * pending turn (`onStart`), interrupt boundaries, and completion (`onFinish`).\n * Enable it to recover partial output if the process dies mid-generation, at\n * the cost of extra writes. Snapshots are throttled to at most one per\n * {@link WithPersistenceOptions.snapshotIntervalMs}.\n */\n snapshotStreaming?: boolean\n /**\n * Minimum milliseconds between streaming snapshots when `snapshotStreaming`\n * is on. Defaults to 1000.\n */\n snapshotIntervalMs?: number\n}\n\n/**\n * Chat-only **state** persistence middleware. Provides durable transcript,\n * run records, and interrupts for `chat()`. Does **not** provide locks —\n * use `withLocks` from `@tanstack/ai` for multi-instance coordination.\n *\n * This middleware never mutates the chunk stream; delivery durability\n * (replaying a disconnected/reloaded stream) is a separate transport-layer\n * concern (see the resumable-streams docs).\n *\n * Requires `stores.messages`. When `stores.interrupts` is present,\n * `stores.runs` is also required.\n *\n * Incoming `messages` merge into the stored thread by id. An empty list loads\n * the stored thread. The last incoming id that already exists in stored is a\n * cutoff; stored messages after it are dropped (reload). If no incoming id is\n * in stored, every stored message stays. Same id: incoming wins. New ids and\n * messages with no id are appended. `saveThread` still replaces the thread\n * with that merged list.\n *\n * @param persistence - Must satisfy {@link ChatTranscriptStores} (messages\n * required). Known-absent `messages` or `interrupts` without `runs` fail at\n * compile time; fully dynamic bags are checked at runtime.\n */\nexport function withPersistence<TStores extends ChatTranscriptStores>(\n persistence: AIPersistence<TStores> & ValidChatPersistence<TStores>,\n options: WithPersistenceOptions = {},\n): ChatMiddleware {\n // Runtime validation covers dynamic bags that bypass the generic constraint.\n validateChatPersistenceStores(persistence)\n const snapshotStreaming = options.snapshotStreaming ?? false\n const snapshotIntervalMs = options.snapshotIntervalMs ?? 1000\n const plan = resolvePersistencePlan(persistence)\n const { wantsInterrupts, runs } = plan\n const messageStore = persistence.stores.messages\n if (!messageStore) {\n // validateChatPersistenceStores already throws; this narrows for TypeScript.\n throw new Error('Chat persistence requires stores.messages.')\n }\n\n const provides = [\n PersistenceCapability,\n PersistenceCompletionCapability,\n ...(persistence.stores.metadata ? [MetadataCapability] : []),\n ...(wantsInterrupts ? [InterruptsCapability] : []),\n ]\n\n return defineChatMiddleware({\n name: 'chat-persistence',\n provides,\n setup(ctx: ChatMiddlewareContext) {\n providePersistence(ctx, persistence)\n if (persistence.stores.metadata) {\n provideMetadata(ctx, persistence.stores.metadata)\n }\n\n let resolveCompletion: () => void = () => undefined\n let rejectCompletion: (error: unknown) => void = () => undefined\n const completion = new Promise<void>((resolve, reject) => {\n resolveCompletion = resolve\n rejectCompletion = reject\n })\n // Consumers may not need this capability. Mark the rejection handled while\n // preserving the original promise for callers that do await it.\n void completion.catch(() => undefined)\n\n runState.set(ctx, {\n merged: false,\n interrupted: false,\n completion: {\n promise: completion,\n resolve: resolveCompletion,\n reject: rejectCompletion,\n },\n })\n providePersistenceCompletion(ctx, {\n waitForRunCompletion: () => completion,\n })\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n provideInterrupts(ctx, persistence.stores.interrupts)\n }\n\n // Offer the pending-turn seam so a middleware that is about to be SLOW can\n // have the user's turn stored before it starts. Only `onStart` stores the\n // turn otherwise, and `onStart` runs after every middleware `setup` — which\n // is milliseconds for a normal run and MINUTES for one that builds a sandbox.\n // For that whole window the thread reads as empty, so a reload or a second\n // device shows no sign of the message the user just sent.\n //\n // Offering it changes nothing on its own: a run whose middleware never calls\n // it behaves exactly as before. See `PendingTurnCapability`.\n providePendingTurn(ctx, {\n snapshot: async () => {\n const stored = threadMessages(\n await messageStore.loadThread(ctx.threadId),\n )\n // Same merge as onConfig. saveThread replaces the thread, so a short\n // incoming list must not drop stored extras.\n const list = mergeStoredMessages(stored, ctx.messages)\n await messageStore.saveThread(ctx.threadId, list)\n },\n })\n },\n\n async onConfig(ctx: ChatMiddlewareContext, config: ChatMiddlewareConfig) {\n if (ctx.phase !== 'init') return\n\n const patch: Partial<ChatMiddlewareConfig> = {}\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n const pending = await persistence.stores.interrupts.listPending(\n ctx.threadId,\n )\n // Gate only records that this chat owns. A foreign AG-UI interrupt can\n // share the durable thread, but its owner resolves it outside this\n // resume protocol. Including it would deadlock this chat resume.\n const ownedPending = pending.filter(isChatOwnedPendingInterrupt)\n rejectMixedRunPending(ownedPending, ctx)\n const resumeByInterruptId = validatePendingResumes(\n ownedPending,\n config.resume,\n ctx,\n )\n // Persistence is the server-authoritative resume path: translate the\n // persisted interrupts into the engine's resume tool state and CLEAR\n // `config.resume`, so the engine skips its ephemeral reconstruction\n // (which needs a parentRunId and the client message history the\n // persistence flow deliberately omits).\n if ((config.resume?.length ?? 0) > 0) {\n const resumeToolState = resumeToolStateFromPending(\n ownedPending,\n resumeByInterruptId,\n )\n const genericResumeState = await durableGenericResumeState(\n ctx,\n ownedPending,\n config.resume ?? [],\n config.tools,\n )\n patch.resume = []\n if (resumeToolState || genericResumeState) {\n patch.resumeToolState = mergeResumeToolState(\n resumeToolState,\n genericResumeState,\n )\n }\n }\n // Defer marking these interrupts resolved/cancelled until the run\n // succeeds (see commitPendingResumes). Committing here would consume the\n // approval even if the run then failed, breaking a retry.\n const state = runState.get(ctx)\n if (state && ownedPending.length > 0) {\n state.pendingResumes = { pending: ownedPending, resumeByInterruptId }\n }\n }\n\n const storedUsage = await createOrResumeRun(runs, ctx.runId, ctx.threadId)\n\n const state = runState.get(ctx)\n // A continuation has a fresh middleware context but resumes the same run.\n if (state && storedUsage) state.usage = storedUsage\n if (!state?.merged) {\n if (state) state.merged = true\n const stored = threadMessages(\n await messageStore.loadThread(ctx.threadId),\n )\n patch.messages = mergeStoredMessages(stored, config.messages)\n }\n\n return Object.keys(patch).length > 0 ? patch : undefined\n },\n\n async onStart(ctx: ChatMiddlewareContext) {\n // (A) Persist the pending turn (the just-submitted user message plus any\n // prior history) as soon as the run starts, so a reload mid-run rehydrates\n // it before the assistant reply exists. Best-effort: a failed eager\n // snapshot must not abort the run — the authoritative save is `onFinish`.\n try {\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n } catch {\n // Eager pre-save is best-effort; the run continues and onFinish saves.\n }\n },\n\n async onChunk(ctx: ChatMiddlewareContext, chunk: StreamChunk) {\n // Capture the current assistant turn's identity for optional in-progress\n // snapshots. Completed messages already live in `ctx.messages`.\n if (snapshotStreaming && ctx.phase === 'modelStream') {\n const s = runState.get(ctx)\n if (s && chunk.type === 'TEXT_MESSAGE_START') {\n // An empty/malformed messageId means \"no identity\" (matching the\n // engine's convention), leaving room for the TOOL_CALL_START\n // parentMessageId fallback below — but the per-turn accumulator\n // still resets so snapshots never mix text across turns.\n s.streamingMessageId =\n typeof chunk.messageId === 'string' && chunk.messageId !== ''\n ? chunk.messageId\n : undefined\n s.streamingMessageCreatedAt = new Date()\n s.streamingText = ''\n } else if (\n s &&\n chunk.type === 'TOOL_CALL_START' &&\n typeof chunk.parentMessageId === 'string' &&\n chunk.parentMessageId !== '' &&\n s.streamingMessageId === undefined\n ) {\n s.streamingMessageId = chunk.parentMessageId\n s.streamingMessageCreatedAt ??= new Date()\n }\n }\n\n // (B) Optional throttled snapshot of the in-progress assistant reply, so\n // partial output survives a crash/reload before onFinish. Off unless\n // `snapshotStreaming` is set. The completed turn enters `ctx.messages`\n // only after streaming ends, so accumulate its text here and persist\n // `ctx.messages` + that partial assistant message (tagged with its id).\n if (\n snapshotStreaming &&\n chunk.type === 'TEXT_MESSAGE_CONTENT' &&\n typeof chunk.delta === 'string'\n ) {\n const snapshotState = runState.get(ctx)\n if (snapshotState) {\n snapshotState.streamingText =\n (snapshotState.streamingText ?? '') + chunk.delta\n const now = Date.now()\n if (now - (snapshotState.lastSnapshotAt ?? 0) >= snapshotIntervalMs) {\n snapshotState.lastSnapshotAt = now\n try {\n await messageStore.saveThread(ctx.threadId, [\n ...ctx.messages,\n {\n role: 'assistant',\n content: snapshotState.streamingText,\n ...(snapshotState.streamingMessageId\n ? { id: snapshotState.streamingMessageId }\n : {}),\n ...(snapshotState.streamingMessageCreatedAt\n ? { createdAt: snapshotState.streamingMessageCreatedAt }\n : {}),\n },\n ])\n } catch {\n // Streaming snapshots are best-effort; onFinish persists final.\n }\n }\n }\n }\n\n // State-only: react to the interrupt boundary (create interrupt records,\n // mark the run interrupted, snapshot thread messages). The chunk stream is\n // never mutated — delivery durability is a transport-layer concern.\n if (\n chunk.type !== 'RUN_FINISHED' ||\n chunk.outcome?.type !== 'interrupt'\n ) {\n return\n }\n const state = runState.get(ctx)\n if (!state) return\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n // The run reached a new interrupt boundary, so the resumes it consumed\n // are committed before the fresh interrupts are recorded.\n await commitPendingResumes(state, persistence.stores.interrupts)\n for (const interrupt of chunk.outcome.interrupts) {\n await persistence.stores.interrupts.create({\n interruptId: interrupt.id,\n runId: ctx.runId,\n threadId: ctx.threadId,\n requestedAt: Date.now(),\n payload: interruptPayload(interrupt),\n })\n }\n }\n // Adapter terminals arrive before `onUsage`; synthesized tool boundaries\n // arrive after it with the same usage already in state.\n const chunkUsage = tokenUsageFromChunk(chunk)\n const usage =\n ctx.phase === 'modelStream' && chunkUsage\n ? accumulateTokenUsage(state.usage, chunkUsage)\n : (state.usage ?? chunkUsage)\n state.usage = usage\n await interruptRun(runs, ctx.runId, usage)\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n state.interrupted = true\n },\n\n onUsage(ctx: ChatMiddlewareContext, usage: TokenUsage) {\n const state = runState.get(ctx)\n if (!state || state.interrupted) return\n state.usage = accumulateTokenUsage(state.usage, usage)\n },\n\n async onFinish(ctx: ChatMiddlewareContext, info: FinishInfo) {\n const state = runState.get(ctx)\n if (state?.interrupted) return\n // Transcript first: if saveThread fails the run stays non-completed and\n // resumes stay pending so a retry can re-apply them. Completing the run\n // or consuming approvals before the durable history lands leaves a\n // \"finished\" run whose transcript is missing the terminal turn.\n try {\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n await commitPendingResumes(state, persistence.stores.interrupts)\n await completeRun(runs, ctx.runId, state?.usage ?? info.usage)\n state?.completion?.resolve()\n } catch (error) {\n // Core has already selected its terminal hook. Persist the failed run\n // here, so a failed transcript save or batch write does not leave an\n // interrupted or completed run whose pending records need retrying.\n try {\n await failRun(runs, ctx.runId, error, state?.usage)\n } finally {\n state?.completion?.reject(error)\n }\n throw error\n }\n },\n\n async onError(ctx: ChatMiddlewareContext, info: ErrorInfo) {\n try {\n await failRun(runs, ctx.runId, info.error, runState.get(ctx)?.usage)\n } finally {\n runState.get(ctx)?.completion?.reject(info.error)\n }\n },\n\n async onAbort(ctx: ChatMiddlewareContext, info: AbortInfo) {\n // A user pressing Stop and a user closing the tab produce the IDENTICAL\n // connection close, so intent is not inferable from the abort. It arrives\n // out of band in two bands, and either is authoritative: in-process\n // (`info.cancelRequested`, set when the cancel aborted this host's signal)\n // and durable (`RunRecord.cancelRequested`, the only channel that reaches\n // a run being driven elsewhere).\n // A run paused at an interrupt boundary is waiting for a HUMAN, not for\n // this socket. `chat()` skips its terminal hook at an actionable-wait\n // boundary, so its `finally` routes the disconnect here — and\n // terminalizing then produced a record claiming the run finished while\n // the interrupt rows stayed `'pending'` and `validatePendingResumes`\n // still threw on the next request. An explicit cancel is different: the\n // user gave up on the approval, so the cancel band stays authoritative.\n const state = runState.get(ctx)\n let terminal = false\n try {\n // The durable cancel read is best-effort. It must not bypass the\n // terminal persistence path or prevent the completion promise from\n // settling when the run store is unavailable.\n const cancelled =\n info.cancelRequested === true ||\n (runs !== undefined && (await wasCancelRequested(runs, ctx.runId)))\n terminal =\n cancelled || (!detachableRun(ctx) && state?.interrupted !== true)\n if (terminal) {\n await abortRun(runs, ctx.runId, state?.usage)\n }\n } finally {\n if (terminal) state?.completion?.reject(info.reason)\n }\n // A plain disconnect on a detachable or interrupted run: write NOTHING.\n // Either the agent is still running and a later attach can take it over\n // (the record stays `'running'`; the detach path records `detachedSince`\n // for the reaper), or the run is paused at an interrupt and the record\n // must stay `'interrupted'` so the pending resumes can still be applied.\n },\n })\n}\n\n// ---------------------------------------------------------------------------\n// Generation middleware\n// ---------------------------------------------------------------------------\n\n/**\n * Generation-only persistence middleware. Tracks generation run status (run\n * records keyed by `runId`) and, when `stores.artifacts` + `stores.blobs` are\n * both provided, persists the generated media for image, audio, TTS, video, and\n * transcription activities.\n *\n * Requires `stores.generationRuns`. A generation activity has no conversation,\n * so the run is keyed on its own `runId` (`ctx.runId ?? ctx.requestId`), which\n * is never faked from anything else.\n *\n * A `threadId` is REQUIRED alongside it — not as a link to a chat, but as the\n * stable app-chosen slot successive runs of the same thing fill\n * (`product-123-hero`, `video-9-start-frame`). It is what\n * `stores.generationRuns.findLatestForThread` keys on, and therefore the only\n * way a run is ever hydrated again. It comes from the `threadId` passed to the\n * activity, or from {@link WithGenerationPersistenceOptions.threadId} when that\n * overrides it; supplying neither throws at `onStart` rather than filing a run\n * nothing can find.\n *\n * On success the terminal result metadata (ids, urls — never media bytes) and,\n * when artifact persistence is on, the persisted artifact refs are captured onto\n * the run record so a server-authoritative client can hydrate the last\n * generation for a thread via {@link reconstructGeneration}.\n */\nexport function withGenerationPersistence<TStores extends AIPersistenceStores>(\n persistence: AIPersistence<TStores> & ValidGenerationPersistence<TStores>,\n opts?: WithGenerationPersistenceOptions,\n): GenerationMiddleware\nexport function withGenerationPersistence(\n persistence: AIPersistence,\n opts: WithGenerationPersistenceOptions = {},\n): GenerationMiddleware {\n validateGenerationPersistenceStores(persistence)\n const { wantsArtifactPersistence } = resolvePersistencePlan(persistence)\n const generationRuns = persistence.stores.generationRuns\n if (!generationRuns) {\n // validateGenerationPersistenceStores already throws; this narrows for TypeScript.\n throw new Error('Generation persistence requires stores.generationRuns.')\n }\n\n const runIdOf = (ctx: GenerationMiddlewareContext): string =>\n ctx.runId ?? ctx.requestId\n\n return {\n name: 'generation-persistence',\n\n async onStart(ctx: GenerationMiddlewareContext) {\n const runId = runIdOf(ctx)\n await generationRuns.createOrResume({\n runId,\n activity: ctx.activity,\n provider: ctx.provider,\n model: ctx.model,\n startedAt: Date.now(),\n threadId: generationScope(ctx, opts),\n })\n\n // Extract + persist artifact bytes (media → blobs, metadata → artifacts)\n // and merge the resulting refs onto the result. Gated on artifact stores.\n if (wantsArtifactPersistence) {\n ctx.resultTransforms?.push(async (result) => {\n const refs = await persistGenerationArtifacts(\n persistence,\n opts,\n ctx,\n result,\n )\n if (refs.length === 0) return undefined\n const base = objectValue(result) ?? {}\n const existing = base.artifacts\n const withArtifacts = {\n ...base,\n artifacts: [...(Array.isArray(existing) ? existing : []), ...refs],\n }\n // Point the live result's media at the durable serve URL (when\n // `artifactUrl` stamped one), so live and restored results match.\n return applyDurableMediaUrls(withArtifacts, refs)\n })\n }\n\n // Always capture the terminal result metadata + any artifact refs onto the\n // run record. Registered AFTER the artifact transform so it observes the\n // fully-merged result (with the artifact refs attached). `result` is\n // metadata/urls only — the media bytes already live in the blob store.\n ctx.resultTransforms?.push(async (result) => {\n const rawArtifacts = objectValue(result)?.artifacts\n const artifacts = Array.isArray(rawArtifacts)\n ? rawArtifacts.filter(isArtifactRef)\n : []\n await generationRuns.update(runId, {\n result,\n ...(artifacts.length > 0 ? { artifacts } : {}),\n })\n return undefined\n })\n },\n\n async onFinish(\n ctx: GenerationMiddlewareContext,\n info: GenerationFinishInfo,\n ) {\n await generationRuns.update(runIdOf(ctx), {\n status: 'completed',\n finishedAt: Date.now(),\n ...(info.usage ? { usage: info.usage } : {}),\n })\n },\n\n async onError(ctx: GenerationMiddlewareContext, info: GenerationErrorInfo) {\n await generationRuns.update(runIdOf(ctx), {\n status: 'failed',\n finishedAt: Date.now(),\n error: {\n message:\n info.error instanceof Error\n ? info.error.message\n : String(info.error),\n },\n })\n },\n\n async onAbort(\n ctx: GenerationMiddlewareContext,\n _info: GenerationAbortInfo,\n ) {\n // Unconditional, unlike chat's: a generation job has no journal and no\n // agent loop, so there is nothing to reattach to. An aborted generation is\n // over, full stop — hence `'aborted'` (terminal) rather than\n // `'interrupted'`, which now means \"parked, waiting for a human\" and is\n // deliberately NOT terminal-shaped, so pairing it with `finishedAt` would\n // leave the run looking permanently active.\n await generationRuns.update(runIdOf(ctx), {\n status: 'aborted',\n finishedAt: Date.now(),\n })\n },\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"middleware.js","names":[],"sources":["../../src/middleware.ts"],"sourcesContent":["import {\n defineChatMiddleware,\n fromSpecTokenUsage,\n getDetachableRun,\n InterruptResumeValidationError,\n MetadataCapability,\n provideMetadata,\n readInterruptBinding,\n validateInterruptResumeBatch,\n wasCancelRequested,\n} from '@tanstack/ai'\nimport {\n createInterruptBinding,\n getGenericInterruptDefinitionRegistry,\n providePendingTurn,\n rehydrateInterruptRequest,\n toRunErrorPayload,\n} from '@tanstack/ai/adapter-internals'\nimport type {\n GenericInterruptRequest,\n InterruptDefinition,\n} from '@tanstack/ai/adapter-internals'\nimport { base64ToUint8Array } from '@tanstack/ai-utils'\nimport {\n InterruptsCapability,\n PersistenceCapability,\n PersistenceCompletionCapability,\n provideInterrupts,\n providePersistence,\n providePersistenceCompletion,\n} from './capabilities'\nimport { mergeStoredMessages } from './merge-stored'\nimport { createSubagentRunRecorder } from './subagent-runs'\nimport {\n validateChatPersistenceStores,\n validateGenerationPersistenceStores,\n} from './types'\nimport type {\n AbortInfo,\n ChatMiddleware,\n ChatMiddlewareConfig,\n ChatMiddlewareContext,\n ChatResumeToolState,\n ErrorInfo,\n FinishInfo,\n GenerationAbortInfo,\n GenerationErrorInfo,\n GenerationFinishInfo,\n GenerationMiddleware,\n GenerationMiddlewareContext,\n Interrupt,\n ModelMessage,\n PendingInterruptResumeRecord,\n PersistedArtifactActivity,\n PersistedArtifactRef,\n PersistedArtifactRole,\n RunAgentResumeItem,\n StreamChunk,\n Tool,\n ToolApprovalResolution,\n BilledUsage,\n TokenUsage,\n} from '@tanstack/ai'\nimport type {\n AIPersistence,\n AIPersistenceStores,\n ArtifactRecord,\n BlobBody,\n ChatTranscriptStores,\n InterruptCommitEntry,\n InterruptRecord,\n MessagePage,\n RunStore,\n} from './types'\nimport { artifactBlobKey } from './retrieve'\n\n/**\n * How generated media is turned into durable artifacts: which pieces of a\n * result become artifacts, what they are named, where their bytes land, and how\n * the bytes are fetched when the provider returns a URL rather than inline data.\n *\n * Consumed by {@link withGenerationPersistence} through\n * {@link WithGenerationPersistenceOptions}. Chat persistence has no artifacts —\n * its options are {@link WithPersistenceOptions}.\n */\nexport interface ArtifactPersistenceOptions {\n extractArtifacts?: (\n input: GenerationArtifactExtractionInput,\n ) =>\n | Array<GenerationArtifactDescriptor | PersistedArtifactRef>\n | Promise<Array<GenerationArtifactDescriptor | PersistedArtifactRef>>\n nameArtifact?: (input: GenerationArtifactNameInput) => string\n /**\n * Map a freshly-persisted artifact ref to the durable app-origin URL that\n * serves its bytes (your `GET` route around `retrieveArtifact` /\n * `retrieveBlob`). The returned URL is stamped onto `ref.url` and written into\n * the result's media field, so both the live and the restored result render\n * durable media from your own origin instead of the provider's expiring link.\n * Return `undefined` to leave a ref without a durable URL.\n */\n artifactUrl?: (ref: PersistedArtifactRef) => string | undefined\n /**\n * Choose the blob-store key each artifact's bytes are written under, so\n * generated media can land in your own folder structure rather than the\n * default `artifacts/<runId>/<artifactId>`.\n *\n * ```ts\n * storageKey: ({ runId, artifactId, mimeType }) =>\n * `video/${videoId}/frames/${runId}-${artifactId}.png`\n * ```\n *\n * Server-side only, and deliberately so: a key supplied by the browser would\n * be a path-traversal and cross-tenant-write vector.\n *\n * The resolved key is recorded on `ArtifactRecord.blobKey`, because once the\n * path is arbitrary a reader can no longer recompute it. Returning a\n * non-unique key overwrites — include `artifactId` (or something equally\n * unique) unless you intend that.\n */\n storageKey?: (input: {\n artifactId: string\n runId: string\n threadId: string\n role: PersistedArtifactRole\n activity: PersistedArtifactActivity\n path: string\n mimeType: string\n name: string\n }) => string\n /**\n * Opt in to fetching prompt media referenced by URL (`role: 'input'`).\n *\n * Off by default, and deliberately expressed as a predicate rather than a\n * boolean: input URLs come from the caller, so fetching them server-side\n * turns your server into a proxy for whatever the caller names — cloud\n * metadata endpoints, `localhost` admin services, anything your network can\n * reach. The bytes are also redundant in the common case, since the client\n * already had the media it referenced.\n *\n * Enable this only when you genuinely need a durable copy of caller-supplied\n * media (a \"paste an image URL\" input box, say), and validate the target:\n *\n * ```ts\n * allowInputUrl: ({ url }) => url.hostname.endsWith('.cdn.example.com')\n * ```\n *\n * Requests are additionally forced through the same baseline checks every\n * artifact fetch gets (http/https only, timeout, size cap), plus — because\n * the target is untrusted — a loopback/private/link-local host block and\n * `redirect: 'manual'` so a 302 cannot hop to an internal address. Those are\n * a backstop, not a substitute for a narrow predicate: a hostname that\n * resolves to a private address still passes a literal-IP check.\n */\n allowInputUrl?: (input: {\n url: URL\n descriptor: GenerationArtifactDescriptor\n }) => boolean | Promise<boolean>\n /** Abort an artifact fetch after this many ms. Default 30_000. */\n artifactFetchTimeoutMs?: number\n /**\n * Refuse an artifact body larger than this many bytes. Default 1 GiB.\n *\n * This is a bound on TRANSFER, not on memory: the URL path streams into the\n * blob store and never buffers, so a 1 GiB artifact costs a streaming store\n * (R2, S3, filesystem) flat memory. What the cap buys is a ceiling on what a\n * broken or hostile origin can make you pull and store — `content-length` is\n * advisory, so without it an artifact fetch is an unbounded transfer billed\n * to you.\n *\n * Pass `false` to remove the ceiling entirely. That also removes the\n * cap-enforcing `TransformStream` wrapper, so the fetched body reaches your\n * store exactly as `fetch` produced it — on workerd that means it keeps its\n * native declared length and `R2Bucket.put` can single-shot it with no hint,\n * no multipart, and nothing buffered. Do that when you trust the origins you\n * fetch from (your provider's CDN); keep the cap when `allowInputUrl` lets\n * callers name the URL.\n */\n maxArtifactBytes?: number | false\n /**\n * `fetch` used to download artifact bytes. Defaults to the global. Inject to\n * route downloads through a proxy or an egress-restricted agent — the most\n * robust SSRF control available here, since it can resolve and check the\n * address actually connected to.\n */\n artifactFetch?: typeof globalThis.fetch\n}\n\n/**\n * Options for {@link withGenerationPersistence}: everything in\n * {@link ArtifactPersistenceOptions}, plus an optional scope override.\n */\nexport interface WithGenerationPersistenceOptions extends ArtifactPersistenceOptions {\n /**\n * Override the scope runs are filed under. Defaults to the `threadId` you\n * passed the activity, which is normally what you want, so leave this unset\n * unless the record belongs somewhere other than the activity's own scope.\n */\n threadId?: string\n}\n\n/**\n * The slot this generation's runs are filed under: `ctx.threadId` (the\n * `threadId` the caller passed the activity), or the option when it overrides.\n *\n * Throws when neither supplies one. A run filed under no scope can never be\n * hydrated by one, so `persistence: true` would restore nothing, forever. That\n * is worth failing loudly for, since the alternative is a silent hole a reader\n * cannot diagnose from behavior.\n */\nfunction generationScope(\n ctx: GenerationMiddlewareContext,\n opts: WithGenerationPersistenceOptions,\n): string {\n const threadId = opts.threadId ?? ctx.threadId\n if (threadId === undefined || threadId.length === 0) {\n throw new Error(\n 'Generation persistence requires a `threadId`, the stable scope successive ' +\n 'runs are filed under. Pass it to the activity, e.g. ' +\n '`generateImage({ threadId, middleware: [withGenerationPersistence(p)] })`, ' +\n 'or override it with `withGenerationPersistence(p, { threadId })`.',\n )\n }\n return threadId\n}\n\nconst DEFAULT_ARTIFACT_FETCH_TIMEOUT_MS = 30_000\n// 1 GiB, because generated video clips routinely run to a few hundred MB and\n// the old 100 MiB default silently failed them. The cap is a drain-time\n// counter, not a buffer: the URL path streams into the store, so raising it\n// costs a streaming store nothing in memory. It still earns its keep as the\n// only ceiling on what a runaway or hostile origin can make you transfer and\n// store (`content-length` is advisory, and on a compressed reply it measures\n// the compressed body). `maxArtifactBytes: false` removes it — and the wrapper\n// with it, which is the zero-copy path onto workerd + R2.\nconst DEFAULT_MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024\n\nexport interface GenerationArtifactDescriptor {\n role: PersistedArtifactRole\n path: string\n mediaType?: PersistedArtifactRef['source']['mediaType']\n mimeType?: string\n bytes?: BlobBody\n url?: string\n json?: unknown\n name?: string\n jobId?: string\n expiresAt?: string | Date\n}\n\nexport interface GenerationArtifactExtractionInput {\n activity: PersistedArtifactActivity\n provider: string\n model: string\n threadId: string\n runId: string\n inputs: unknown\n result: unknown\n}\n\nexport interface GenerationArtifactNameInput {\n descriptor: GenerationArtifactDescriptor\n activity: PersistedArtifactActivity\n provider: string\n model: string\n threadId: string\n runId: string\n index: number\n}\n\ninterface RunStateEntry {\n merged: boolean\n interrupted: boolean\n /**\n * Resumes accepted in `onConfig` but not yet committed to the interrupt\n * store. They are applied (resolve/cancel) only once the run reaches a\n * successful boundary — see {@link commitPendingResumes}. Left uncommitted\n * (still pending in the store) if the run fails or aborts first.\n */\n pendingResumes?: {\n pending: Array<InterruptRecord>\n resumeByInterruptId: Map<string, RunAgentResumeItem>\n }\n /** Usage accumulated across every model call in this chat invocation. */\n usage?: TokenUsage\n /** Accumulated terminal-turn text, for throttled streaming snapshots (B). */\n streamingText?: string\n /** Epoch ms of the last streaming snapshot, to throttle writes (B). */\n lastSnapshotAt?: number\n /**\n * The current assistant turn's stream messageId, captured from\n * `TEXT_MESSAGE_START`. Persisted onto the assistant message so its identity\n * survives the persist → hydrate round-trip and a reload can resume the same\n * bubble in place.\n */\n streamingMessageId?: string\n streamingMessageCreatedAt?: Date\n completion?: {\n promise: Promise<void>\n resolve: () => void\n reject: (error: unknown) => void\n }\n}\n\nconst runState = new WeakMap<object, RunStateEntry>()\n\nconst validResumeStatuses = new Set(['resolved', 'cancelled'])\n\nfunction mergeMaps<K, V>(\n left?: ReadonlyMap<K, V>,\n right?: ReadonlyMap<K, V>,\n): Map<K, V> | undefined {\n if (!left && !right) return undefined\n return new Map([...(left ?? []), ...(right ?? [])])\n}\n\nfunction mergeSets<T>(\n left?: ReadonlySet<T>,\n right?: ReadonlySet<T>,\n): Set<T> | undefined {\n if (!left && !right) return undefined\n return new Set([...(left ?? []), ...(right ?? [])])\n}\n\nfunction mergeResumeToolState(\n left: ChatResumeToolState | undefined,\n right: ChatResumeToolState | undefined,\n): ChatResumeToolState | undefined {\n if (!left) return right\n if (!right) return left\n return {\n approvals: mergeMaps(left.approvals, right.approvals),\n clientToolResults: mergeMaps(\n left.clientToolResults,\n right.clientToolResults,\n ),\n genericInterrupts: mergeMaps(\n left.genericInterrupts,\n right.genericInterrupts,\n ),\n genericInterruptRequests: mergeMaps(\n left.genericInterruptRequests,\n right.genericInterruptRequests,\n ),\n deniedToolResults: mergeMaps(\n left.deniedToolResults,\n right.deniedToolResults,\n ),\n cancelledToolCallIds: mergeSets(\n left.cancelledToolCallIds,\n right.cancelledToolCallIds,\n ),\n }\n}\n\nfunction rejectMixedRunPending(\n pending: Array<InterruptRecord>,\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n): void {\n const runIds = new Set(pending.map((interrupt) => interrupt.runId))\n if (runIds.size <= 1) return\n throw new InterruptResumeValidationError([\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId: ctx.runId,\n generation: 0,\n interruptIds: pending.map((interrupt) => interrupt.interruptId),\n code: 'stale',\n message: 'Thread has pending interrupts from more than one run.',\n source: 'server',\n retryable: false,\n },\n ])\n}\n\nfunction validatePendingResumes(\n pending: Array<InterruptRecord>,\n resume: Array<RunAgentResumeItem> | undefined,\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n): Map<string, RunAgentResumeItem> {\n const interruptedRunId = pending[0]?.runId ?? ctx.runId\n const failure = (\n interruptId: string,\n code: 'conflict' | 'unknown-interrupt',\n message: string,\n ): never => {\n throw new InterruptResumeValidationError([\n {\n scope: 'item',\n threadId: ctx.threadId,\n interruptedRunId,\n generation: 0,\n interruptId,\n code,\n message,\n source: 'client',\n retryable: false,\n },\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId,\n generation: 0,\n interruptIds: pending.map((interrupt) => interrupt.interruptId),\n code: code === 'conflict' ? 'conflict' : 'incomplete-batch',\n message:\n 'Resume entries must resolve or cancel the complete interrupt batch.',\n source: 'client',\n retryable: false,\n },\n ])\n }\n const pendingInterruptIds = new Set(\n pending.map((interrupt) => interrupt.interruptId),\n )\n const resumeByInterruptId = new Map<string, RunAgentResumeItem>()\n for (const entry of resume ?? []) {\n if (resumeByInterruptId.has(entry.interruptId)) {\n return failure(\n entry.interruptId,\n 'conflict',\n `Interrupt ${entry.interruptId} has duplicate resume entries.`,\n )\n }\n resumeByInterruptId.set(entry.interruptId, entry)\n }\n if (pending.length === 0) {\n const staleEntry = resume?.[0]\n if (staleEntry) {\n return failure(\n staleEntry.interruptId,\n 'unknown-interrupt',\n `Resume entry references non-pending interrupt ${staleEntry.interruptId}.`,\n )\n }\n return resumeByInterruptId\n }\n const firstPending = pending[0]\n if (firstPending === undefined) return resumeByInterruptId\n if (!resume || resume.length === 0) {\n return failure(\n firstPending.interruptId,\n 'unknown-interrupt',\n `Thread has pending interrupts; resume is required before accepting new input.`,\n )\n }\n\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) {\n return failure(\n interrupt.interruptId,\n 'unknown-interrupt',\n `Missing resume entry for pending interrupt ${interrupt.interruptId}.`,\n )\n }\n if (!validResumeStatuses.has(entry.status)) {\n return failure(\n interrupt.interruptId,\n 'unknown-interrupt',\n `Invalid resume status for pending interrupt ${interrupt.interruptId}: ${entry.status}.`,\n )\n }\n }\n for (const entry of resume) {\n if (!pendingInterruptIds.has(entry.interruptId)) {\n return failure(\n entry.interruptId,\n 'unknown-interrupt',\n `Resume entry references non-pending interrupt ${entry.interruptId}.`,\n )\n }\n }\n return resumeByInterruptId\n}\n\nasync function applyPendingResumes(\n pending: Array<InterruptRecord>,\n resumeByInterruptId: Map<string, RunAgentResumeItem>,\n interrupts: NonNullable<AIPersistence['stores']['interrupts']>,\n): Promise<void> {\n const entries: Array<InterruptCommitEntry> = []\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) continue\n if (entry.status === 'resolved') {\n entries.push({\n interruptId: interrupt.interruptId,\n status: 'resolved',\n response: entry.payload,\n })\n } else {\n entries.push({\n interruptId: interrupt.interruptId,\n status: 'cancelled',\n })\n }\n }\n if (interrupts.commitBatch) {\n await interrupts.commitBatch(entries)\n return\n }\n const ids = new Set<string>()\n for (const entry of entries) {\n if (ids.has(entry.interruptId)) {\n throw new Error(\n `Interrupt batch contains duplicate id: ${entry.interruptId}.`,\n )\n }\n ids.add(entry.interruptId)\n const existing = await interrupts.get(entry.interruptId)\n if (!existing) {\n throw new Error(\n `Interrupt batch references missing id: ${entry.interruptId}.`,\n )\n }\n if (existing.status !== 'pending') {\n throw new Error(\n `Interrupt batch references non-pending id: ${entry.interruptId}.`,\n )\n }\n }\n for (const entry of entries) {\n if (entry.status === 'resolved') {\n await interrupts.resolve(entry.interruptId, entry.response)\n } else {\n await interrupts.cancel(entry.interruptId)\n }\n }\n}\n\n/**\n * Commit the resumes stashed in `onConfig`, marking each resumed interrupt\n * resolved/cancelled. Called only from success boundaries (`onFinish`, and the\n * `onChunk` interrupt boundary) so a provider failure or abort between accepting\n * the resume and reaching a boundary leaves the interrupts pending — the\n * approval is not consumed and a retry with the same resume succeeds. Idempotent\n * and a no-op when nothing is stashed.\n */\nasync function commitPendingResumes(\n state: RunStateEntry | undefined,\n interrupts: AIPersistence['stores']['interrupts'],\n): Promise<void> {\n if (!state?.pendingResumes || !interrupts) return\n const { pending, resumeByInterruptId } = state.pendingResumes\n // Apply first; only clear the in-memory stash after every resolve/cancel\n // succeeds so a mid-loop store failure can still re-drive remaining ids\n // if the hook is retried (or a later boundary re-enters commit).\n await applyPendingResumes(pending, resumeByInterruptId, interrupts)\n state.pendingResumes = undefined\n}\n\nfunction objectValue(value: unknown): Record<string, unknown> | null {\n return value && typeof value === 'object'\n ? (value as Record<string, unknown>)\n : null\n}\n\nfunction stringField(\n value: Record<string, unknown>,\n key: string,\n): string | undefined {\n return typeof value[key] === 'string' ? value[key] : undefined\n}\n\nfunction interruptKind(interrupt: InterruptRecord): string | undefined {\n const metadata = objectValue(interrupt.payload.metadata)\n return metadata ? stringField(metadata, 'kind') : undefined\n}\n\nfunction hasReservedInterruptBinding(payload: unknown): boolean {\n const descriptor = objectValue(payload)\n const metadata = objectValue(descriptor?.metadata)\n return !!metadata && 'tanstack:interruptBinding' in metadata\n}\n\nfunction isPersistedInterruptDescriptor(\n value: unknown,\n): value is Interrupt & { reason: string; message: string } {\n const record = objectValue(value)\n return (\n !!record &&\n typeof record.id === 'string' &&\n typeof record.reason === 'string' &&\n typeof record.message === 'string'\n )\n}\n\n/**\n * Does this pending record belong to the TanStack chat resume protocol?\n *\n * An external system can persist an AG-UI descriptor in the same durable\n * thread. A descriptor without a TanStack binding or legacy tool marker stays\n * pending for its owner, but it does not make this resume incomplete. Older\n * opaque records remain owned because their provenance cannot be known.\n */\nfunction isChatOwnedPendingInterrupt(interrupt: InterruptRecord): boolean {\n const kind = interruptKind(interrupt)\n return (\n !isPersistedInterruptDescriptor(interrupt.payload) ||\n stringField(interrupt.payload, 'toolCallId') !== undefined ||\n kind === 'approval' ||\n kind === 'client_tool' ||\n hasReservedInterruptBinding(interrupt.payload)\n )\n}\n\nfunction durableGenericFailure(\n ctx: Pick<ChatMiddlewareContext, 'threadId' | 'runId'>,\n persisted: InterruptRecord,\n message: string,\n): InterruptResumeValidationError {\n return new InterruptResumeValidationError([\n {\n scope: 'item',\n threadId: ctx.threadId,\n interruptedRunId: persisted.runId || ctx.runId,\n generation: 0,\n interruptId: persisted.interruptId,\n code: 'stale',\n message,\n source: 'server',\n retryable: false,\n },\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId: persisted.runId || ctx.runId,\n generation: 0,\n interruptIds: [persisted.interruptId],\n code: 'item-validation-failed',\n message: 'One or more persisted interrupt records are invalid.',\n source: 'server',\n retryable: false,\n },\n ])\n}\n\nasync function durableGenericResumeState(\n ctx: ChatMiddlewareContext,\n pending: Array<InterruptRecord>,\n resume: ReadonlyArray<RunAgentResumeItem>,\n tools: Array<Tool>,\n): Promise<ChatResumeToolState | undefined> {\n const registry = getGenericInterruptDefinitionRegistry(ctx, {\n optional: true,\n })\n const records: Array<PendingInterruptResumeRecord> = []\n\n for (const persisted of pending) {\n if (!isPersistedInterruptDescriptor(persisted.payload)) {\n if (hasReservedInterruptBinding(persisted.payload)) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has an invalid binding descriptor.`,\n )\n }\n continue\n }\n const descriptor = persisted.payload\n const binding = readInterruptBinding(descriptor)\n if (!binding) {\n if (hasReservedInterruptBinding(descriptor)) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has an invalid or incomplete binding.`,\n )\n }\n continue\n }\n if (\n descriptor.id !== persisted.interruptId ||\n binding.interruptId !== persisted.interruptId ||\n binding.interruptedRunId !== persisted.runId ||\n binding.generation !== 0\n ) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted interrupt ${persisted.interruptId} has stale correlation metadata.`,\n )\n }\n if (binding.kind !== 'generic') {\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n })\n continue\n }\n if (\n !binding.definitionId ||\n !binding.key ||\n binding.batchIndex === undefined\n ) {\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n })\n continue\n }\n if (!registry) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} cannot be restored because no interrupt registry is available.`,\n )\n }\n const definition = registry.definitions.get(binding.definitionId)\n if (!definition) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt definition ${binding.definitionId} is unavailable.`,\n )\n }\n const metadata = objectValue(descriptor.metadata)\n const payload = metadata?.['tanstack:interruptPayload']\n let request: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n try {\n request = rehydrateInterruptRequest(definition, {\n key: binding.key,\n reason: descriptor.reason,\n message: descriptor.message,\n ...(descriptor.expiresAt !== undefined\n ? { expiresAt: descriptor.expiresAt }\n : {}),\n ...(payload !== undefined ? { payload } : {}),\n })\n } catch (error) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} is invalid: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n const emitted = createInterruptBinding(request, {\n batchIndex: binding.batchIndex,\n })\n if (\n emitted.descriptor.responseSchemaHash !== binding.responseSchemaHash ||\n emitted.descriptor.payloadSchemaHash !== binding.payloadSchemaHash ||\n binding.interruptId !== persisted.interruptId\n ) {\n throw durableGenericFailure(\n ctx,\n persisted,\n `Persisted generic interrupt ${persisted.interruptId} is stale.`,\n )\n }\n records.push({\n interruptId: persisted.interruptId,\n payload: descriptor,\n binding,\n genericRequest: request,\n })\n }\n\n const firstRecord = records[0]\n if (firstRecord === undefined) return undefined\n const interruptedRunId = firstRecord.binding.interruptedRunId\n const generation = firstRecord.binding.generation\n const validated = await validateInterruptResumeBatch({\n threadId: ctx.threadId,\n interruptedRunId,\n generation,\n pending: records,\n resume: resume.filter((entry) =>\n records.some((record) => record.interruptId === entry.interruptId),\n ),\n tools,\n })\n if (validated.errors.length > 0 || !validated.resumeToolState) {\n throw new InterruptResumeValidationError(validated.errors)\n }\n type GenericRecord = PendingInterruptResumeRecord & {\n binding: Extract<\n PendingInterruptResumeRecord['binding'],\n { kind: 'generic' }\n >\n genericRequest: GenericInterruptRequest<\n InterruptDefinition<any, any, any, any>\n >\n }\n const isGenericRecord = (\n record: PendingInterruptResumeRecord,\n ): record is GenericRecord =>\n record.binding.kind === 'generic' && record.genericRequest !== undefined\n const genericRecords: Array<{ record: GenericRecord; batchIndex: number }> =\n []\n const batchIndexes = new Set<number>()\n for (const record of records) {\n if (!isGenericRecord(record)) continue\n const batchIndex = record.binding.batchIndex\n if (batchIndex === undefined || batchIndexes.has(batchIndex)) {\n throw new InterruptResumeValidationError([\n {\n scope: 'batch',\n threadId: ctx.threadId,\n interruptedRunId,\n generation,\n interruptIds: records.map((item) => item.interruptId),\n code: 'stale',\n message:\n 'Persisted generic interrupts have duplicate or invalid batch indexes.',\n source: 'server',\n retryable: false,\n },\n ])\n }\n batchIndexes.add(batchIndex)\n genericRecords.push({ record, batchIndex })\n }\n genericRecords.sort((left, right) => left.batchIndex - right.batchIndex)\n return {\n ...validated.resumeToolState,\n genericInterruptRequests: new Map(\n genericRecords.flatMap(({ record }) =>\n record.genericRequest\n ? [[record.interruptId, record.genericRequest] as const]\n : [],\n ),\n ),\n }\n}\n\nfunction resolvedApprovalDecision(entry: RunAgentResumeItem): boolean {\n if (entry.status === 'cancelled') return false\n const payload = objectValue(entry.payload)\n // Fail closed: persisted resume payloads may be malformed or truncated, so a\n // missing/non-boolean `approved` denies the tool rather than running it.\n return typeof payload?.approved === 'boolean' ? payload.approved : false\n}\n\n/**\n * Translate the persisted pending interrupts + the resume batch into the\n * `ChatResumeToolState` the chat engine consumes. This is the server-authoritative\n * counterpart to the engine's ephemeral (client-history) reconstruction: because\n * the persistence flow sends empty client messages, the engine has no history to\n * rebuild from, so persistence supplies the resume state directly (and clears\n * `config.resume` so the ephemeral path is skipped — see `onConfig`).\n */\nfunction resumeToolStateFromPending(\n pending: Array<InterruptRecord>,\n resumeByInterruptId: Map<string, RunAgentResumeItem>,\n): ChatResumeToolState | undefined {\n const approvals = new Map<string, ToolApprovalResolution>()\n const clientToolResults = new Map<string, unknown>()\n const cancelledToolCallIds = new Set<string>()\n\n for (const interrupt of pending) {\n const entry = resumeByInterruptId.get(interrupt.interruptId)\n if (!entry) continue\n\n const kind = interruptKind(interrupt)\n const reason = stringField(interrupt.payload, 'reason')\n const toolCallId = stringField(interrupt.payload, 'toolCallId')\n\n if (entry.status === 'cancelled' && toolCallId) {\n cancelledToolCallIds.add(toolCallId)\n }\n\n if (kind === 'approval' || reason === 'approval_required') {\n approvals.set(interrupt.interruptId, resolvedApprovalDecision(entry))\n continue\n }\n\n if (\n entry.status === 'resolved' &&\n toolCallId &&\n (kind === 'client_tool' || reason === 'client_tool_input')\n ) {\n clientToolResults.set(toolCallId, entry.payload)\n }\n }\n\n if (\n approvals.size === 0 &&\n clientToolResults.size === 0 &&\n cancelledToolCallIds.size === 0\n ) {\n return undefined\n }\n return { approvals, clientToolResults, cancelledToolCallIds }\n}\n\nfunction interruptPayload(interrupt: unknown): Record<string, unknown> {\n return interrupt && typeof interrupt === 'object'\n ? { ...(interrupt as Record<string, unknown>) }\n : { value: interrupt }\n}\n\n// ---------------------------------------------------------------------------\n// Generation artifact extraction / persistence\n// ---------------------------------------------------------------------------\n\nfunction isArtifactRef(value: unknown): value is PersistedArtifactRef {\n const record = objectValue(value)\n return !!record && typeof record.artifactId === 'string'\n}\n\nfunction mediaActivity(\n activity: GenerationMiddlewareContext['activity'],\n): PersistedArtifactActivity | undefined {\n return activity === 'image' ||\n activity === 'audio' ||\n activity === 'tts' ||\n activity === 'video' ||\n activity === 'transcription'\n ? activity\n : undefined\n}\n\nfunction parseDataUrl(\n value: string,\n): { mimeType: string; bytes: Uint8Array } | undefined {\n const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(value)\n if (!match) return undefined\n const mimeType = match[1] || 'application/octet-stream'\n const raw = match[3] ?? ''\n // A plain (non-base64) data URL may carry a bare `%` (`data:text/plain,100%`),\n // which makes `decodeURIComponent` throw. Fall back to the literal payload so\n // a malformed escape doesn't fail the whole generation.\n let payload: string\n try {\n payload = decodeURIComponent(raw)\n } catch {\n payload = raw\n }\n return {\n mimeType,\n bytes: match[2]\n ? base64ToUint8Array(payload)\n : new TextEncoder().encode(payload),\n }\n}\n\nfunction extensionForMime(mimeType: string | undefined): string {\n if (mimeType === undefined) return 'bin'\n\n switch (mimeType) {\n case 'image/png':\n return 'png'\n case 'image/jpeg':\n return 'jpg'\n case 'audio/wav':\n return 'wav'\n case 'audio/mpeg':\n return 'mp3'\n case 'audio/mp3':\n return 'mp3'\n case 'video/mp4':\n return 'mp4'\n case 'application/json':\n return 'json'\n default:\n return 'bin'\n }\n}\n\nfunction defaultArtifactName(\n descriptor: GenerationArtifactDescriptor,\n activity: PersistedArtifactActivity,\n index: number,\n): string {\n const ext = extensionForMime(descriptor.mimeType)\n return `${activity}-${descriptor.role}-${descriptor.mediaType ?? 'artifact'}-${index}.${ext}`\n}\n\nfunction sourcePartDescriptors(\n part: unknown,\n role: PersistedArtifactRole,\n path: string,\n): Array<GenerationArtifactDescriptor> {\n const record = objectValue(part)\n const type = stringField(record ?? {}, 'type')\n const source = objectValue(record?.source)\n if (\n !record ||\n !source ||\n (type !== 'image' && type !== 'audio' && type !== 'video')\n ) {\n return []\n }\n const sourceType = stringField(source, 'type')\n const mimeType = stringField(source, 'mimeType') ?? `${type}/mpeg`\n if (sourceType === 'data') {\n const value = stringField(source, 'value')\n if (!value) return []\n return [\n {\n role,\n path,\n mediaType: type,\n mimeType,\n bytes: base64ToUint8Array(value),\n },\n ]\n }\n if (sourceType === 'url') {\n const value = stringField(source, 'value')\n if (!value) return []\n return [{ role, path, mediaType: type, mimeType, url: value }]\n }\n return []\n}\n\nfunction promptInputDescriptors(\n inputs: unknown,\n): Array<GenerationArtifactDescriptor> {\n const prompt = objectValue(inputs)?.prompt\n if (!Array.isArray(prompt)) return []\n\n const counts: Record<string, number> = { image: 0, audio: 0, video: 0 }\n const descriptors: Array<GenerationArtifactDescriptor> = []\n for (const part of prompt) {\n const type = stringField(objectValue(part) ?? {}, 'type')\n if (type !== 'image' && type !== 'audio' && type !== 'video') continue\n const index = counts[type] ?? 0\n counts[type] = index + 1\n descriptors.push(\n ...sourcePartDescriptors(part, 'input', `prompt.${type}s.${index}`),\n )\n }\n return descriptors\n}\n\nfunction generatedMediaDescriptor(args: {\n role: PersistedArtifactRole\n path: string\n mediaType: 'image' | 'audio' | 'video'\n mimeType: string\n media: unknown\n jobId?: string\n expiresAt?: string | Date\n}): GenerationArtifactDescriptor | undefined {\n const media = objectValue(args.media)\n if (!media) return undefined\n const b64Json = stringField(media, 'b64Json')\n if (b64Json) {\n return {\n role: args.role,\n path: args.path,\n mediaType: args.mediaType,\n mimeType: stringField(media, 'contentType') ?? args.mimeType,\n bytes: base64ToUint8Array(b64Json),\n jobId: args.jobId,\n expiresAt: args.expiresAt,\n }\n }\n const url = stringField(media, 'url')\n if (url) {\n return {\n role: args.role,\n path: args.path,\n mediaType: args.mediaType,\n mimeType: stringField(media, 'contentType') ?? args.mimeType,\n url,\n jobId: args.jobId,\n expiresAt: args.expiresAt,\n }\n }\n return undefined\n}\n\nfunction builtInArtifactDescriptors(\n activity: PersistedArtifactActivity,\n inputs: unknown,\n result: unknown,\n): Array<GenerationArtifactDescriptor> {\n const descriptors = promptInputDescriptors(inputs)\n const output = objectValue(result)\n if (!output) return descriptors\n\n if (activity === 'image' && Array.isArray(output.images)) {\n output.images.forEach((image, index) => {\n const descriptor = generatedMediaDescriptor({\n role: 'output',\n path: `images.${index}`,\n mediaType: 'image',\n mimeType: 'image/png',\n media: image,\n })\n if (descriptor) descriptors.push(descriptor)\n })\n }\n\n if (activity === 'audio') {\n const descriptor = generatedMediaDescriptor({\n role: 'output',\n path: 'audio',\n mediaType: 'audio',\n mimeType: 'audio/mpeg',\n media: output.audio,\n })\n if (descriptor) descriptors.push(descriptor)\n }\n\n if (activity === 'tts') {\n const audio = stringField(output, 'audio')\n if (audio) {\n const format = stringField(output, 'format')\n descriptors.push({\n role: 'output',\n path: 'audio',\n mediaType: 'audio',\n mimeType:\n stringField(output, 'contentType') ??\n (format ? `audio/${format}` : 'audio/mpeg'),\n bytes: base64ToUint8Array(audio),\n })\n }\n }\n\n if (activity === 'video' && typeof output.url === 'string') {\n descriptors.push({\n role: 'output',\n path: 'video',\n mediaType: 'video',\n mimeType: 'video/mp4',\n url: output.url,\n jobId: stringField(output, 'jobId'),\n expiresAt:\n output.expiresAt instanceof Date ? output.expiresAt : undefined,\n })\n }\n\n if (activity === 'transcription') {\n const audio = objectValue(inputs)?.audio\n if (typeof audio === 'string') {\n const data = parseDataUrl(audio)\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: data?.mimeType ?? 'audio/mpeg',\n bytes: data?.bytes ?? base64ToUint8Array(audio),\n })\n } else if (audio instanceof ArrayBuffer) {\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: 'audio/mpeg',\n bytes: audio.slice(0),\n })\n } else if (typeof Blob !== 'undefined' && audio instanceof Blob) {\n descriptors.push({\n role: 'input',\n path: 'audio',\n mediaType: 'audio',\n mimeType: audio.type || 'audio/mpeg',\n bytes: audio,\n })\n }\n if (Array.isArray(output.segments) || Array.isArray(output.words)) {\n descriptors.push({\n role: 'output',\n path: 'transcription',\n mediaType: 'json',\n mimeType: 'application/json',\n json: output,\n })\n }\n }\n\n return descriptors\n}\n\n/**\n * Reject hosts that only make sense as an SSRF target: loopback, link-local\n * (including the cloud metadata address), private, and unique-local ranges.\n *\n * Applied to caller-supplied input URLs only. Provider result URLs skip it on\n * purpose — a self-hosted or local provider legitimately returns a `localhost`\n * URL, and those live inside the same trust boundary as the adapter itself.\n *\n * This checks IP *literals*. A hostname that resolves to a private address\n * passes, which is why `allowInputUrl` is required rather than optional.\n */\nfunction isBlockedInputHost(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/^\\[|\\]$/g, '')\n if (host === 'localhost' || host.endsWith('.localhost')) return true\n\n const ipv4 = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(host)\n if (ipv4) {\n const [a, b] = [Number(ipv4[1]), Number(ipv4[2])]\n if (a === 127 || a === 0 || a === 10) return true\n if (a === 169 && b === 254) return true // link-local + cloud metadata\n if (a === 172 && b >= 16 && b <= 31) return true\n if (a === 192 && b === 168) return true\n return false\n }\n\n if (host === '::' || host === '::1') return true\n if (host.startsWith('fe80:')) return true // link-local\n if (/^f[cd][0-9a-f]{2}:/.test(host)) return true // unique-local\n // IPv4-mapped IPv6 — re-check the embedded address. `new URL()` normalizes\n // `::ffff:127.0.0.1` to the hex form `::ffff:7f00:1`, so accept both.\n const mappedDotted = /^::ffff:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/.exec(\n host,\n )\n if (mappedDotted?.[1]) return isBlockedInputHost(mappedDotted[1])\n const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host)\n if (mappedHex?.[1] && mappedHex[2]) {\n const high = Number.parseInt(mappedHex[1], 16)\n const low = Number.parseInt(mappedHex[2], 16)\n return isBlockedInputHost(\n `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`,\n )\n }\n return false\n}\n\n/**\n * Fail the stream once more than `maxBytes` have passed through, so an\n * unexpectedly huge artifact can't fill the blob store.\n *\n * Only used when the response does NOT already bound itself — a chunked reply,\n * or a content-encoded one whose declared length describes the compressed\n * bytes. When `content-length` describes the body the store will drain, HTTP\n * framing is the bound and wrapping would only cost the caller the declared\n * length: a `TransformStream`'s readable side carries none, which is what\n * pushes a length-strict runtime (workerd + R2) onto a multipart upload.\n */\nfunction capBodySize(\n body: ReadableStream<Uint8Array>,\n maxBytes: number,\n url: string,\n): ReadableStream<Uint8Array> {\n let seen = 0\n return body.pipeThrough(\n new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n seen += chunk.byteLength\n if (seen > maxBytes) {\n controller.error(\n new Error(\n `Artifact at ${url} exceeds maxArtifactBytes (${maxBytes}).`,\n ),\n )\n return\n }\n controller.enqueue(chunk)\n },\n }),\n )\n}\n\n/**\n * Resolve a descriptor to the bytes to store. Returns `undefined` when the\n * descriptor is deliberately not persisted — today that means a caller-supplied\n * input URL without an `allowInputUrl` opt-in.\n */\nasync function descriptorBody(\n descriptor: GenerationArtifactDescriptor,\n opts: ArtifactPersistenceOptions | undefined,\n): Promise<\n | {\n body: BlobBody\n size: number\n /**\n * Exact byte length of a streamed body, when the origin declared one\n * that survives decoding — forwarded to `BlobStore.put` as\n * `BlobPutOptions.expectedLength`. Undefined when unknown.\n */\n expectedLength?: number\n mimeType: string\n sourceUrl?: string\n }\n | undefined\n> {\n if (descriptor.json !== undefined) {\n const body = JSON.stringify(descriptor.json)\n return {\n body,\n size: new TextEncoder().encode(body).byteLength,\n mimeType: descriptor.mimeType ?? 'application/json',\n }\n }\n\n if (descriptor.bytes !== undefined) {\n const body = descriptor.bytes\n let size: number\n if (typeof body === 'string') {\n size = new TextEncoder().encode(body).byteLength\n } else if (body instanceof ArrayBuffer) {\n size = body.byteLength\n } else if (ArrayBuffer.isView(body)) {\n size = body.byteLength\n } else if (typeof Blob !== 'undefined' && body instanceof Blob) {\n size = body.size\n } else {\n size = 0\n }\n return {\n body,\n size,\n mimeType: descriptor.mimeType ?? 'application/octet-stream',\n }\n }\n\n if (descriptor.url) {\n const data = parseDataUrl(descriptor.url)\n if (data) {\n return {\n body: data.bytes,\n size: data.bytes.byteLength,\n mimeType: descriptor.mimeType ?? data.mimeType,\n }\n }\n // A caller-controlled input URL is never fetched unless the app opted in\n // with a validating predicate. Skipped, not thrown: not mirroring someone\n // else's URL is the intended default, and the run itself is fine.\n const isCallerSupplied = descriptor.role === 'input'\n const allowInputUrl = opts?.allowInputUrl\n if (isCallerSupplied && !allowInputUrl) return undefined\n\n let target: URL\n try {\n target = new URL(descriptor.url)\n } catch {\n throw new Error(\n `Failed to persist artifact: ${descriptor.url} is not a valid URL.`,\n )\n }\n if (target.protocol !== 'https:' && target.protocol !== 'http:') {\n throw new Error(\n `Refusing to fetch artifact over ${target.protocol} (${descriptor.path}).`,\n )\n }\n if (allowInputUrl && isCallerSupplied) {\n if (isBlockedInputHost(target.hostname)) {\n throw new Error(\n `Refusing to fetch input artifact from internal host ${target.hostname}.`,\n )\n }\n if (!(await allowInputUrl({ url: target, descriptor }))) {\n throw new Error(\n `Refusing to fetch input artifact from ${target.hostname}: rejected by allowInputUrl.`,\n )\n }\n }\n\n const maxBytes = opts?.maxArtifactBytes ?? DEFAULT_MAX_ARTIFACT_BYTES\n const fetchArtifact = opts?.artifactFetch ?? globalThis.fetch\n const response = await fetchArtifact(target, {\n // Provider CDNs redirect routinely, so output fetches follow. An input\n // fetch must not: a 302 would land on a host neither check ever saw.\n redirect: isCallerSupplied ? 'manual' : 'follow',\n signal: AbortSignal.timeout(\n opts?.artifactFetchTimeoutMs ?? DEFAULT_ARTIFACT_FETCH_TIMEOUT_MS,\n ),\n })\n if (isCallerSupplied && response.status >= 300 && response.status < 400) {\n throw new Error(\n `Refusing to follow a redirect for input artifact ${descriptor.path}.`,\n )\n }\n if (!response.ok) {\n throw new Error(\n `Failed to persist artifact from ${descriptor.url}: HTTP ${response.status}`,\n )\n }\n // `headers.get` returns null when the header is absent, and\n // `Number(null) === 0` — parse only a present header, or a chunked reply\n // would read as a declared length of 0 (harmless, but the early-reject\n // below would silently never be reachable for it).\n const contentLength = response.headers.get('content-length')\n const declaredLength =\n contentLength === null ? undefined : Number(contentLength)\n if (\n maxBytes !== false &&\n declaredLength !== undefined &&\n Number.isFinite(declaredLength) &&\n declaredLength > maxBytes\n ) {\n throw new Error(\n `Artifact at ${descriptor.url} exceeds maxArtifactBytes (${maxBytes}).`,\n )\n }\n const mimeType =\n descriptor.mimeType ??\n response.headers.get('content-type') ??\n 'application/octet-stream'\n // A declared length is the DECODED body's length only when the response is\n // not content-encoded: fetch transparently decompresses, so on a gzipped\n // reply `content-length` measures the compressed bytes and the decoded\n // stream can be arbitrarily longer. Only trust it when it provably\n // describes what the store will drain.\n const encoding = response.headers.get('content-encoding')\n const decodedLengthIsKnown =\n declaredLength !== undefined &&\n Number.isFinite(declaredLength) &&\n (encoding === null || encoding === 'identity')\n const expectedLength = decodedLengthIsKnown ? declaredLength : undefined\n // Stream the body straight into the blob store instead of buffering the\n // whole artifact in memory. `size` is left 0 (unknown up front); the store\n // records the actual byte length as it drains the stream. Fall back to\n // buffering only when the response has no body to stream.\n if (response.body) {\n return {\n // Wrap ONLY when the response does not already bound itself. A\n // trustworthy `content-length` was checked against the cap above, and\n // HTTP framing holds the origin to it — a body cannot exceed a length\n // it declared — so the counter would add nothing and cost everything:\n // it is a TransformStream, whose readable side has no declared length,\n // and that missing length is precisely what breaks `R2Bucket.put`.\n // Unwrapped, the runtime's own length rides along and R2 single-shots\n // the stream. What still needs the counter: a chunked reply (no\n // declared length at all) and a content-encoded one (declared length\n // measures the compressed bytes, so the decoded stream is a\n // decompression bomb waiting to happen).\n body:\n maxBytes === false || decodedLengthIsKnown\n ? response.body\n : capBodySize(response.body, maxBytes, descriptor.url),\n size: 0,\n expectedLength,\n mimeType,\n sourceUrl: descriptor.url,\n }\n }\n const body = await response.arrayBuffer()\n if (maxBytes !== false && body.byteLength > maxBytes) {\n throw new Error(\n `Artifact at ${descriptor.url} exceeds maxArtifactBytes (${maxBytes}).`,\n )\n }\n return {\n body,\n size: body.byteLength,\n mimeType,\n sourceUrl: descriptor.url,\n }\n }\n\n throw new Error(\n `Artifact descriptor ${descriptor.path} has no bytes, url, or json.`,\n )\n}\n\nasync function persistGenerationArtifacts(\n persistence: AIPersistence,\n opts: WithGenerationPersistenceOptions,\n ctx: GenerationMiddlewareContext,\n result: unknown,\n): Promise<Array<PersistedArtifactRef>> {\n const activity = mediaActivity(ctx.activity)\n if (!activity) return []\n\n // Resolved the same way the run record is, so an artifact always lands in the\n // same slot as the run that produced it.\n const threadId = generationScope(ctx, opts)\n const runId = ctx.runId ?? ctx.requestId\n const extractionInput: GenerationArtifactExtractionInput = {\n activity,\n provider: ctx.provider,\n model: ctx.model,\n threadId,\n runId,\n inputs: ctx.artifactInputs,\n result,\n }\n const extracted =\n opts?.extractArtifacts !== undefined\n ? await opts.extractArtifacts(extractionInput)\n : builtInArtifactDescriptors(activity, ctx.artifactInputs, result)\n\n if (extracted.length === 0) return []\n\n const existingRefs = extracted.filter(isArtifactRef)\n const descriptors = extracted.filter(\n (item): item is GenerationArtifactDescriptor => !isArtifactRef(item),\n )\n if (descriptors.length === 0) return existingRefs\n\n if (!persistence.stores.artifacts || !persistence.stores.blobs) {\n throw new Error(\n 'Generation artifact persistence requires stores.artifacts and stores.blobs.',\n )\n }\n\n const refs: Array<PersistedArtifactRef> = [...existingRefs]\n for (const [index, descriptor] of descriptors.entries()) {\n const artifactId = ctx.createId('artifact')\n const resolved = await descriptorBody(descriptor, opts)\n // Deliberately not persisted (an input URL with no `allowInputUrl` opt-in):\n // no blob, no record, no ref — the rest of the run is unaffected.\n if (!resolved) continue\n const { body, size, expectedLength, mimeType, sourceUrl } = resolved\n // Resolved before the blob write so `storageKey` can build a path from the\n // final filename (extensions, slugs) rather than guessing at one.\n const name =\n opts?.nameArtifact?.({\n descriptor: { ...descriptor, mimeType },\n activity,\n provider: ctx.provider,\n model: ctx.model,\n threadId,\n runId,\n index,\n }) ??\n descriptor.name ??\n defaultArtifactName({ ...descriptor, mimeType }, activity, index)\n const key =\n opts?.storageKey?.({\n artifactId,\n runId,\n threadId,\n role: descriptor.role,\n activity,\n path: descriptor.path,\n mimeType,\n name,\n }) ?? artifactBlobKey({ runId, artifactId })\n const stored = await persistence.stores.blobs.put(key, body, {\n contentType: mimeType,\n // Exact decoded length when the origin declared one — lets a store\n // single-shot the stream (e.g. R2 via FixedLengthStream) instead of\n // buffering or going multipart. Absent when unknown.\n ...(expectedLength !== undefined ? { expectedLength } : {}),\n customMetadata: {\n runId,\n threadId,\n role: descriptor.role,\n activity,\n path: descriptor.path,\n },\n })\n // For streamed downloads the descriptor size is unknown (0); the store\n // reports the real byte length once it has drained the stream.\n const resolvedSize = size || stored.size || 0\n const createdAtMs = Date.now()\n const record: ArtifactRecord = {\n artifactId,\n runId,\n threadId,\n // Always recorded: with a custom `storageKey` the path is no longer\n // derivable from the record, so the reader has to be told where it went.\n blobKey: key,\n name,\n mimeType,\n size: resolvedSize,\n sourceUrl,\n createdAt: createdAtMs,\n }\n await persistence.stores.artifacts.save(record)\n refs.push({\n role: descriptor.role,\n artifactId,\n threadId,\n runId,\n name,\n mimeType,\n size: resolvedSize,\n createdAt: new Date(createdAtMs).toISOString(),\n ...(sourceUrl ? { sourceUrl } : {}),\n source: {\n activity,\n path: descriptor.path,\n provider: ctx.provider,\n model: ctx.model,\n mediaType: descriptor.mediaType,\n jobId: descriptor.jobId,\n expiresAt:\n descriptor.expiresAt instanceof Date\n ? descriptor.expiresAt.toISOString()\n : descriptor.expiresAt,\n },\n })\n }\n\n // Stamp the durable app-origin serve URL onto every ref that lacks one, so\n // clients render + restore media from your own origin, not the provider link.\n if (opts?.artifactUrl) {\n for (let i = 0; i < refs.length; i++) {\n const ref = refs[i]\n if (ref && !ref.url) {\n const url = opts.artifactUrl(ref)\n if (url) refs[i] = { ...ref, url }\n }\n }\n }\n\n return refs\n}\n\n/**\n * Rewrite the live result's media fields to each output ref's durable serve URL\n * (`ref.url`), so the live result matches what a reload restores. Keyed off the\n * ref's `source.path`: `images.<i>` → `result.images[i].url`, `video` →\n * `result.url`, `audio` (object) → `result.audio.url`. tts (a base64 string) and\n * transcription (json) have no media-URL field, so they are left as-is; their\n * durable bytes are reachable via `result.artifacts`. A no-op when no ref has a\n * `url`.\n */\nfunction applyDurableMediaUrls(\n result: Record<string, unknown>,\n refs: Array<PersistedArtifactRef>,\n): Record<string, unknown> {\n let next = result\n for (const ref of refs) {\n if (ref.role !== 'output' || !ref.url) continue\n const path = ref.source.path\n if (path.startsWith('images.')) {\n const index = Number(path.slice('images.'.length))\n const images = next.images\n if (Array.isArray(images) && objectValue(images[index])) {\n const cloned = [...images]\n cloned[index] = { ...objectValue(images[index]), url: ref.url }\n next = { ...next, images: cloned }\n }\n } else if (path === 'video') {\n next = { ...next, url: ref.url }\n } else if (path === 'audio' && objectValue(next.audio)) {\n next = { ...next, audio: { ...objectValue(next.audio), url: ref.url } }\n }\n }\n return next\n}\n\n// ---------------------------------------------------------------------------\n// Shared store / feature plan\n// ---------------------------------------------------------------------------\n\ninterface PersistencePlan {\n wantsInterrupts: boolean\n wantsArtifactPersistence: boolean\n runs: AIPersistence['stores']['runs']\n}\n\nfunction resolvePersistencePlan(persistence: AIPersistence): PersistencePlan {\n return {\n wantsInterrupts: persistence.stores.interrupts !== undefined,\n wantsArtifactPersistence:\n persistence.stores.artifacts !== undefined &&\n persistence.stores.blobs !== undefined,\n runs: persistence.stores.runs,\n }\n}\n\ntype StoreIsDefinitelyPresent<\n TStores extends AIPersistenceStores,\n TKey extends keyof AIPersistenceStores,\n> = TKey extends keyof TStores\n ? object extends Pick<TStores, TKey>\n ? false\n : [Exclude<TStores[TKey], undefined>] extends [never]\n ? false\n : true\n : false\n\ntype StoreIsDefinitelyAbsent<\n TStores extends AIPersistenceStores,\n TKey extends keyof AIPersistenceStores,\n> = TKey extends keyof TStores\n ? [Exclude<TStores[TKey], undefined>] extends [never]\n ? true\n : false\n : true\n\n/**\n * Chat entrypoint invalid when:\n * - `messages` is known-absent, or\n * - `interrupts` is known-present without `runs`.\n *\n * Fully optional bags (`AIPersistence` with all `?` keys) stay assignable and\n * are checked at runtime by {@link validateChatPersistenceStores}.\n */\ntype InvalidChatPersistence<TStores extends AIPersistenceStores> =\n StoreIsDefinitelyAbsent<TStores, 'messages'> extends true\n ? true\n : StoreIsDefinitelyPresent<TStores, 'interrupts'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'runs'>\n : false\n\n/**\n * Generation entrypoint invalid when `generationRuns` is known-absent, or when\n * exactly one of `artifacts` / `blobs` is present (artifact persistence needs\n * both).\n */\ntype InvalidGenerationPersistence<TStores extends AIPersistenceStores> =\n StoreIsDefinitelyAbsent<TStores, 'generationRuns'> extends true\n ? true\n : StoreIsDefinitelyPresent<TStores, 'artifacts'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'blobs'>\n : StoreIsDefinitelyPresent<TStores, 'blobs'> extends true\n ? StoreIsDefinitelyAbsent<TStores, 'artifacts'>\n : false\n\ntype ValidChatPersistence<TStores extends AIPersistenceStores> =\n InvalidChatPersistence<TStores> extends true ? never : unknown\n\ntype ValidGenerationPersistence<TStores extends AIPersistenceStores> =\n InvalidGenerationPersistence<TStores> extends true ? never : unknown\n\nasync function createOrResumeRun(\n runs: RunStore | undefined,\n runId: string,\n threadId: string,\n): Promise<TokenUsage | undefined> {\n const run = await runs?.createOrResume({\n runId,\n threadId,\n startedAt: Date.now(),\n })\n return run?.usage\n}\n\nfunction sumOptionalNumber(\n current: number | undefined,\n next: number | undefined,\n): number | undefined {\n if (current === undefined) return next\n if (next === undefined) return current\n return current + next\n}\n\nfunction sumNumberFields<T extends object>(\n current: T | undefined,\n next: T | undefined,\n): T | undefined {\n if (!current) return next\n if (!next) return current\n\n const result = { ...current }\n for (const key of Object.keys(next) as Array<keyof T>) {\n const currentValue = current[key]\n const nextValue = next[key]\n if (typeof nextValue === 'number') {\n result[key] = ((typeof currentValue === 'number' ? currentValue : 0) +\n nextValue) as T[keyof T]\n }\n }\n return result\n}\n\nfunction tokenUsageFromChunk(chunk: StreamChunk): TokenUsage | undefined {\n if (chunk.type !== 'RUN_FINISHED' && chunk.type !== 'RUN_ERROR') {\n return undefined\n }\n const usage = chunk.usage\n if (\n usage != null &&\n typeof usage === 'object' &&\n !Array.isArray(usage) &&\n 'promptTokens' in usage\n ) {\n return usage\n }\n const metadata = chunk.metadata\n const tanstack =\n metadata != null && typeof metadata === 'object' && 'tanstack' in metadata\n ? metadata.tanstack\n : undefined\n const leftover =\n tanstack != null && typeof tanstack === 'object' && !Array.isArray(tanstack)\n ? (tanstack as { usage?: TokenUsage }).usage\n : undefined\n return fromSpecTokenUsage(Array.isArray(usage) ? usage : undefined, leftover)\n}\n\nfunction accumulateTokenUsage(\n current: TokenUsage | undefined,\n next: TokenUsage,\n): TokenUsage {\n if (!current) return { ...next }\n\n const promptTokensDetails = sumNumberFields(\n current.promptTokensDetails,\n next.promptTokensDetails,\n )\n const completionTokensDetails = sumNumberFields(\n current.completionTokensDetails,\n next.completionTokensDetails,\n )\n const costDetails = sumNumberFields(current.costDetails, next.costDetails)\n // Provider-specific details are opaque, so retain the latest reported bag.\n const providerUsageDetails =\n next.providerUsageDetails ?? current.providerUsageDetails\n const durationSeconds = sumOptionalNumber(\n current.durationSeconds,\n next.durationSeconds,\n )\n const unitsBilled = sumOptionalNumber(current.unitsBilled, next.unitsBilled)\n const billed = accumulateBilled(current.billed, next.billed)\n const cost = sumOptionalNumber(current.cost, next.cost)\n\n return {\n ...current,\n ...next,\n promptTokens: current.promptTokens + next.promptTokens,\n completionTokens: current.completionTokens + next.completionTokens,\n totalTokens: current.totalTokens + next.totalTokens,\n ...(promptTokensDetails ? { promptTokensDetails } : {}),\n ...(completionTokensDetails ? { completionTokensDetails } : {}),\n ...(durationSeconds !== undefined ? { durationSeconds } : {}),\n ...(unitsBilled !== undefined ? { unitsBilled } : {}),\n ...(billed !== undefined ? { billed } : {}),\n ...(cost !== undefined ? { cost } : {}),\n ...(costDetails ? { costDetails } : {}),\n ...(providerUsageDetails ? { providerUsageDetails } : {}),\n }\n}\n\n/**\n * Sum billed quantities when both reports use the same unit. Different units\n * cannot be added, so the later report wins.\n */\nfunction accumulateBilled(\n current: BilledUsage | undefined,\n next: BilledUsage | undefined,\n): BilledUsage | undefined {\n if (!current) return next\n if (!next) return current\n if (current.unit !== next.unit) return next\n return { quantity: current.quantity + next.quantity, unit: current.unit }\n}\n\nasync function completeRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n // A late detach stamp from a superseded host can land after takeover\n // claimed the run. A terminal run is not detached.\n await runs?.update(runId, {\n status: 'completed',\n finishedAt: Date.now(),\n detachedSince: undefined,\n ...(usage ? { usage } : {}),\n })\n}\n\nasync function failRun(\n runs: RunStore | undefined,\n runId: string,\n error: unknown,\n usage?: TokenUsage,\n): Promise<void> {\n const runError = toRunErrorPayload(error)\n await runs?.update(runId, {\n status: 'failed',\n finishedAt: Date.now(),\n detachedSince: undefined,\n error: {\n message: runError.message,\n ...(runError.code !== undefined ? { code: runError.code } : {}),\n },\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Record a human-in-the-loop PAUSE.\n *\n * Deliberately writes NO `finishedAt`: `'interrupted'` is not a terminal status\n * (`isTerminalRunStatus('interrupted')` is `false`), and stamping a terminal\n * timestamp on it told every reader the run was over while it was in fact\n * waiting for a human. Only `abortRun`/`completeRun`/`failRun` finish a run.\n */\nexport async function interruptRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n await runs?.update(runId, {\n status: 'interrupted',\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Record that the run has ended for good — an explicit cancel, or a disconnect\n * on a run that has nothing to reattach to. Terminal, so it carries\n * `finishedAt`.\n */\nexport async function abortRun(\n runs: RunStore | undefined,\n runId: string,\n usage?: TokenUsage,\n): Promise<void> {\n await runs?.update(runId, {\n status: 'aborted',\n finishedAt: Date.now(),\n detachedSince: undefined,\n ...(usage ? { usage } : {}),\n })\n}\n\n/**\n * Whether some middleware has declared this run detachable — i.e. it has a\n * durable event log and a run store, so a disconnect can be survived and the\n * run picked back up rather than destroyed.\n *\n * The capability is read from CORE, never from `@tanstack/ai-sandbox`: sandbox\n * provides it, persistence consumes it, and a persistence → sandbox import\n * would invert the layering.\n */\nfunction detachableRun(ctx: ChatMiddlewareContext): boolean {\n return getDetachableRun(ctx, { optional: true }) === true\n}\n\n// ---------------------------------------------------------------------------\n// Chat middleware\n// ---------------------------------------------------------------------------\n\nfunction threadMessages(\n loaded: Array<ModelMessage> | MessagePage,\n): Array<ModelMessage> {\n return Array.isArray(loaded) ? loaded : loaded.messages\n}\n\nexport interface WithPersistenceOptions {\n /**\n * Also persist a throttled snapshot of the in-progress assistant reply while\n * it streams. Off by default — the transcript is otherwise persisted at the\n * pending turn (`onStart`), interrupt boundaries, and completion (`onFinish`).\n * Enable it to recover partial output if the process dies mid-generation, at\n * the cost of extra writes. Snapshots are throttled to at most one per\n * {@link WithPersistenceOptions.snapshotIntervalMs}.\n */\n snapshotStreaming?: boolean\n /**\n * Minimum milliseconds between streaming snapshots when `snapshotStreaming`\n * is on. Defaults to 1000. A streaming subagent child always uses the same\n * interval for its transcript writes, with or without `snapshotStreaming`.\n */\n snapshotIntervalMs?: number\n}\n\n/**\n * Chat-only **state** persistence middleware. Provides durable transcript,\n * run records, and interrupts for `chat()`. Does **not** provide locks —\n * use `withLocks` from `@tanstack/ai` for multi-instance coordination.\n *\n * This middleware never mutates the chunk stream; delivery durability\n * (replaying a disconnected/reloaded stream) is a separate transport-layer\n * concern (see the resumable-streams docs).\n *\n * Requires `stores.messages`. When `stores.interrupts` is present,\n * `stores.runs` is also required.\n *\n * Incoming `messages` merge into the stored thread by id. An empty list loads\n * the stored thread. The last incoming id that already exists in stored is a\n * cutoff; stored messages after it are dropped (reload). If no incoming id is\n * in stored, every stored message stays. Same id: incoming wins. New ids and\n * messages with no id are appended. `saveThread` still replaces the thread\n * with that merged list.\n *\n * @param persistence - Must satisfy {@link ChatTranscriptStores} (messages\n * required). Known-absent `messages` or `interrupts` without `runs` fail at\n * compile time; fully dynamic bags are checked at runtime.\n */\nexport function withPersistence<TStores extends ChatTranscriptStores>(\n persistence: AIPersistence<TStores> & ValidChatPersistence<TStores>,\n options: WithPersistenceOptions = {},\n): ChatMiddleware {\n // Runtime validation covers dynamic bags that bypass the generic constraint.\n validateChatPersistenceStores(persistence)\n const snapshotStreaming = options.snapshotStreaming ?? false\n const snapshotIntervalMs = options.snapshotIntervalMs ?? 1000\n const plan = resolvePersistencePlan(persistence)\n const { wantsInterrupts, runs } = plan\n const messageStore = persistence.stores.messages\n if (!messageStore) {\n // validateChatPersistenceStores already throws; this narrows for TypeScript.\n throw new Error('Chat persistence requires stores.messages.')\n }\n\n const provides = [\n PersistenceCapability,\n PersistenceCompletionCapability,\n ...(persistence.stores.metadata ? [MetadataCapability] : []),\n ...(wantsInterrupts ? [InterruptsCapability] : []),\n ]\n\n const subagentRuns = createSubagentRunRecorder({\n messages: messageStore,\n runs,\n intervalMs: snapshotIntervalMs,\n ...(wantsInterrupts && persistence.stores.interrupts\n ? { interrupts: persistence.stores.interrupts }\n : {}),\n })\n\n return defineChatMiddleware({\n name: 'chat-persistence',\n routedSubagentPersistence: subagentRuns,\n provides,\n setup(ctx: ChatMiddlewareContext) {\n providePersistence(ctx, persistence)\n if (persistence.stores.metadata) {\n provideMetadata(ctx, persistence.stores.metadata)\n }\n\n let resolveCompletion: () => void = () => undefined\n let rejectCompletion: (error: unknown) => void = () => undefined\n const completion = new Promise<void>((resolve, reject) => {\n resolveCompletion = resolve\n rejectCompletion = reject\n })\n // Consumers may not need this capability. Mark the rejection handled while\n // preserving the original promise for callers that do await it.\n void completion.catch(() => undefined)\n\n runState.set(ctx, {\n merged: false,\n interrupted: false,\n completion: {\n promise: completion,\n resolve: resolveCompletion,\n reject: rejectCompletion,\n },\n })\n providePersistenceCompletion(ctx, {\n waitForRunCompletion: () => completion,\n })\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n provideInterrupts(ctx, persistence.stores.interrupts)\n }\n\n // Offer the pending-turn seam so a middleware that is about to be SLOW can\n // have the user's turn stored before it starts. Only `onStart` stores the\n // turn otherwise, and `onStart` runs after every middleware `setup` — which\n // is milliseconds for a normal run and MINUTES for one that builds a sandbox.\n // For that whole window the thread reads as empty, so a reload or a second\n // device shows no sign of the message the user just sent.\n //\n // Offering it changes nothing on its own: a run whose middleware never calls\n // it behaves exactly as before. See `PendingTurnCapability`.\n providePendingTurn(ctx, {\n snapshot: async () => {\n const stored = threadMessages(\n await messageStore.loadThread(ctx.threadId),\n )\n // Same merge as onConfig. saveThread replaces the thread, so a short\n // incoming list must not drop stored extras.\n const list = mergeStoredMessages(stored, ctx.messages)\n await messageStore.saveThread(ctx.threadId, list)\n },\n })\n },\n\n async onConfig(ctx: ChatMiddlewareContext, config: ChatMiddlewareConfig) {\n if (ctx.phase !== 'init') return\n\n const patch: Partial<ChatMiddlewareConfig> = {}\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n const pending = await persistence.stores.interrupts.listPending(\n ctx.threadId,\n )\n // Gate only records that this chat owns. A foreign AG-UI interrupt can\n // share the durable thread, but its owner resolves it outside this\n // resume protocol. Including it would deadlock this chat resume.\n const ownedPending = pending.filter(isChatOwnedPendingInterrupt)\n rejectMixedRunPending(ownedPending, ctx)\n const resumeByInterruptId = validatePendingResumes(\n ownedPending,\n config.resume,\n ctx,\n )\n // Persistence is the server-authoritative resume path: translate the\n // persisted interrupts into the engine's resume tool state and CLEAR\n // `config.resume`, so the engine skips its ephemeral reconstruction\n // (which needs a parentRunId and the client message history the\n // persistence flow deliberately omits).\n if ((config.resume?.length ?? 0) > 0) {\n const resumeToolState = resumeToolStateFromPending(\n ownedPending,\n resumeByInterruptId,\n )\n const genericResumeState = await durableGenericResumeState(\n ctx,\n ownedPending,\n config.resume ?? [],\n config.tools,\n )\n patch.resume = []\n if (resumeToolState || genericResumeState) {\n patch.resumeToolState = mergeResumeToolState(\n resumeToolState,\n genericResumeState,\n )\n }\n }\n // Defer marking these interrupts resolved/cancelled until the run\n // succeeds (see commitPendingResumes). Committing here would consume the\n // approval even if the run then failed, breaking a retry.\n const state = runState.get(ctx)\n if (state && ownedPending.length > 0) {\n state.pendingResumes = { pending: ownedPending, resumeByInterruptId }\n }\n }\n\n const storedUsage = await createOrResumeRun(runs, ctx.runId, ctx.threadId)\n\n const state = runState.get(ctx)\n // A continuation has a fresh middleware context but resumes the same run.\n if (state && storedUsage) state.usage = storedUsage\n if (!state?.merged) {\n if (state) state.merged = true\n const stored = threadMessages(\n await messageStore.loadThread(ctx.threadId),\n )\n patch.messages = mergeStoredMessages(stored, config.messages)\n }\n\n return Object.keys(patch).length > 0 ? patch : undefined\n },\n\n async onStart(ctx: ChatMiddlewareContext) {\n // (A) Persist the pending turn (the just-submitted user message plus any\n // prior history) as soon as the run starts, so a reload mid-run rehydrates\n // it before the assistant reply exists. Best-effort: a failed eager\n // snapshot must not abort the run — the authoritative save is `onFinish`.\n try {\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n } catch {\n // Eager pre-save is best-effort; the run continues and onFinish saves.\n }\n },\n\n async onChunk(ctx: ChatMiddlewareContext, chunk: StreamChunk) {\n await subagentRuns.chunk({\n threadId: ctx.threadId,\n runId: ctx.runId,\n chunk,\n })\n // Capture the current assistant turn's identity for optional in-progress\n // snapshots. Completed messages already live in `ctx.messages`.\n if (snapshotStreaming && ctx.phase === 'modelStream') {\n const s = runState.get(ctx)\n if (s && chunk.type === 'TEXT_MESSAGE_START') {\n // An empty/malformed messageId means \"no identity\" (matching the\n // engine's convention), leaving room for the TOOL_CALL_START\n // parentMessageId fallback below — but the per-turn accumulator\n // still resets so snapshots never mix text across turns.\n s.streamingMessageId =\n typeof chunk.messageId === 'string' && chunk.messageId !== ''\n ? chunk.messageId\n : undefined\n s.streamingMessageCreatedAt = new Date()\n s.streamingText = ''\n } else if (\n s &&\n chunk.type === 'TOOL_CALL_START' &&\n typeof chunk.parentMessageId === 'string' &&\n chunk.parentMessageId !== '' &&\n s.streamingMessageId === undefined\n ) {\n s.streamingMessageId = chunk.parentMessageId\n s.streamingMessageCreatedAt ??= new Date()\n }\n }\n\n // (B) Optional throttled snapshot of the in-progress assistant reply, so\n // partial output survives a crash/reload before onFinish. Off unless\n // `snapshotStreaming` is set. The completed turn enters `ctx.messages`\n // only after streaming ends, so accumulate its text here and persist\n // `ctx.messages` + that partial assistant message (tagged with its id).\n if (\n snapshotStreaming &&\n chunk.type === 'TEXT_MESSAGE_CONTENT' &&\n typeof chunk.delta === 'string'\n ) {\n const snapshotState = runState.get(ctx)\n if (snapshotState) {\n snapshotState.streamingText =\n (snapshotState.streamingText ?? '') + chunk.delta\n const now = Date.now()\n if (now - (snapshotState.lastSnapshotAt ?? 0) >= snapshotIntervalMs) {\n snapshotState.lastSnapshotAt = now\n try {\n await messageStore.saveThread(ctx.threadId, [\n ...ctx.messages,\n {\n role: 'assistant',\n content: snapshotState.streamingText,\n ...(snapshotState.streamingMessageId\n ? { id: snapshotState.streamingMessageId }\n : {}),\n ...(snapshotState.streamingMessageCreatedAt\n ? { createdAt: snapshotState.streamingMessageCreatedAt }\n : {}),\n },\n ])\n } catch {\n // Streaming snapshots are best-effort; onFinish persists final.\n }\n }\n }\n }\n\n // State-only: react to the interrupt boundary (create interrupt records,\n // mark the run interrupted, snapshot thread messages). The chunk stream is\n // never mutated — delivery durability is a transport-layer concern.\n if (\n chunk.type !== 'RUN_FINISHED' ||\n chunk.outcome?.type !== 'interrupt'\n ) {\n return\n }\n const state = runState.get(ctx)\n if (!state) return\n\n if (wantsInterrupts && persistence.stores.interrupts) {\n // The run reached a new interrupt boundary, so the resumes it consumed\n // are committed before the fresh interrupts are recorded.\n await commitPendingResumes(state, persistence.stores.interrupts)\n for (const interrupt of chunk.outcome.interrupts) {\n await persistence.stores.interrupts.create({\n interruptId: interrupt.id,\n runId: ctx.runId,\n threadId: ctx.threadId,\n requestedAt: Date.now(),\n payload: interruptPayload(interrupt),\n })\n }\n }\n // Adapter terminals arrive before `onUsage`; synthesized tool boundaries\n // arrive after it with the same usage already in state.\n const chunkUsage = tokenUsageFromChunk(chunk)\n const usage =\n ctx.phase === 'modelStream' && chunkUsage\n ? accumulateTokenUsage(state.usage, chunkUsage)\n : (state.usage ?? chunkUsage)\n state.usage = usage\n await interruptRun(runs, ctx.runId, usage)\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n state.interrupted = true\n },\n\n onUsage(ctx: ChatMiddlewareContext, usage: TokenUsage) {\n const state = runState.get(ctx)\n if (!state || state.interrupted) return\n state.usage = accumulateTokenUsage(state.usage, usage)\n },\n\n async onFinish(ctx: ChatMiddlewareContext, info: FinishInfo) {\n const state = runState.get(ctx)\n if (state?.interrupted) return\n // Transcript first: if saveThread fails the run stays non-completed and\n // resumes stay pending so a retry can re-apply them. Completing the run\n // or consuming approvals before the durable history lands leaves a\n // \"finished\" run whose transcript is missing the terminal turn.\n try {\n await messageStore.saveThread(ctx.threadId, [...ctx.messages])\n await commitPendingResumes(state, persistence.stores.interrupts)\n await completeRun(runs, ctx.runId, state?.usage ?? info.usage)\n state?.completion?.resolve()\n } catch (error) {\n // Core has already selected its terminal hook. Persist the failed run\n // here, so a failed transcript save or batch write does not leave an\n // interrupted or completed run whose pending records need retrying.\n try {\n await failRun(runs, ctx.runId, error, state?.usage)\n } finally {\n state?.completion?.reject(error)\n }\n throw error\n }\n },\n\n async onError(ctx: ChatMiddlewareContext, info: ErrorInfo) {\n try {\n await failRun(runs, ctx.runId, info.error, runState.get(ctx)?.usage)\n } finally {\n runState.get(ctx)?.completion?.reject(info.error)\n }\n },\n\n async onAbort(ctx: ChatMiddlewareContext, info: AbortInfo) {\n // A user pressing Stop and a user closing the tab produce the IDENTICAL\n // connection close, so intent is not inferable from the abort. It arrives\n // out of band in two bands, and either is authoritative: in-process\n // (`info.cancelRequested`, set when the cancel aborted this host's signal)\n // and durable (`RunRecord.cancelRequested`, the only channel that reaches\n // a run being driven elsewhere).\n // A run paused at an interrupt boundary is waiting for a HUMAN, not for\n // this socket. `chat()` skips its terminal hook at an actionable-wait\n // boundary, so its `finally` routes the disconnect here — and\n // terminalizing then produced a record claiming the run finished while\n // the interrupt rows stayed `'pending'` and `validatePendingResumes`\n // still threw on the next request. An explicit cancel is different: the\n // user gave up on the approval, so the cancel band stays authoritative.\n const state = runState.get(ctx)\n let terminal = false\n try {\n // The durable cancel read is best-effort. It must not bypass the\n // terminal persistence path or prevent the completion promise from\n // settling when the run store is unavailable.\n const cancelled =\n info.cancelRequested === true ||\n (runs !== undefined && (await wasCancelRequested(runs, ctx.runId)))\n terminal =\n cancelled || (!detachableRun(ctx) && state?.interrupted !== true)\n if (terminal) {\n await abortRun(runs, ctx.runId, state?.usage)\n }\n } finally {\n if (terminal) state?.completion?.reject(info.reason)\n }\n // A plain disconnect on a detachable or interrupted run: write NOTHING.\n // Either the agent is still running and a later attach can take it over\n // (the record stays `'running'`; the detach path records `detachedSince`\n // for the reaper), or the run is paused at an interrupt and the record\n // must stay `'interrupted'` so the pending resumes can still be applied.\n },\n })\n}\n\n// ---------------------------------------------------------------------------\n// Generation middleware\n// ---------------------------------------------------------------------------\n\n/**\n * Generation-only persistence middleware. Tracks generation run status (run\n * records keyed by `runId`) and, when `stores.artifacts` + `stores.blobs` are\n * both provided, persists the generated media for image, audio, TTS, video, and\n * transcription activities.\n *\n * Requires `stores.generationRuns`. A generation activity has no conversation,\n * so the run is keyed on its own `runId` (`ctx.runId ?? ctx.requestId`), which\n * is never faked from anything else.\n *\n * A `threadId` is REQUIRED alongside it — not as a link to a chat, but as the\n * stable app-chosen slot successive runs of the same thing fill\n * (`product-123-hero`, `video-9-start-frame`). It is what\n * `stores.generationRuns.findLatestForThread` keys on, and therefore the only\n * way a run is ever hydrated again. It comes from the `threadId` passed to the\n * activity, or from {@link WithGenerationPersistenceOptions.threadId} when that\n * overrides it; supplying neither throws at `onStart` rather than filing a run\n * nothing can find.\n *\n * On success the terminal result metadata (ids, urls — never media bytes) and,\n * when artifact persistence is on, the persisted artifact refs are captured onto\n * the run record so a server-authoritative client can hydrate the last\n * generation for a thread via {@link reconstructGeneration}.\n */\nexport function withGenerationPersistence<TStores extends AIPersistenceStores>(\n persistence: AIPersistence<TStores> & ValidGenerationPersistence<TStores>,\n opts?: WithGenerationPersistenceOptions,\n): GenerationMiddleware\nexport function withGenerationPersistence(\n persistence: AIPersistence,\n opts: WithGenerationPersistenceOptions = {},\n): GenerationMiddleware {\n validateGenerationPersistenceStores(persistence)\n const { wantsArtifactPersistence } = resolvePersistencePlan(persistence)\n const generationRuns = persistence.stores.generationRuns\n if (!generationRuns) {\n // validateGenerationPersistenceStores already throws; this narrows for TypeScript.\n throw new Error('Generation persistence requires stores.generationRuns.')\n }\n\n const runIdOf = (ctx: GenerationMiddlewareContext): string =>\n ctx.runId ?? ctx.requestId\n\n return {\n name: 'generation-persistence',\n\n async onStart(ctx: GenerationMiddlewareContext) {\n const runId = runIdOf(ctx)\n await generationRuns.createOrResume({\n runId,\n activity: ctx.activity,\n provider: ctx.provider,\n model: ctx.model,\n startedAt: Date.now(),\n threadId: generationScope(ctx, opts),\n })\n\n // Extract + persist artifact bytes (media → blobs, metadata → artifacts)\n // and merge the resulting refs onto the result. Gated on artifact stores.\n if (wantsArtifactPersistence) {\n ctx.resultTransforms?.push(async (result) => {\n const refs = await persistGenerationArtifacts(\n persistence,\n opts,\n ctx,\n result,\n )\n if (refs.length === 0) return undefined\n const base = objectValue(result) ?? {}\n const existing = base.artifacts\n const withArtifacts = {\n ...base,\n artifacts: [...(Array.isArray(existing) ? existing : []), ...refs],\n }\n // Point the live result's media at the durable serve URL (when\n // `artifactUrl` stamped one), so live and restored results match.\n return applyDurableMediaUrls(withArtifacts, refs)\n })\n }\n\n // Always capture the terminal result metadata + any artifact refs onto the\n // run record. Registered AFTER the artifact transform so it observes the\n // fully-merged result (with the artifact refs attached). `result` is\n // metadata/urls only — the media bytes already live in the blob store.\n ctx.resultTransforms?.push(async (result) => {\n const rawArtifacts = objectValue(result)?.artifacts\n const artifacts = Array.isArray(rawArtifacts)\n ? rawArtifacts.filter(isArtifactRef)\n : []\n await generationRuns.update(runId, {\n result,\n ...(artifacts.length > 0 ? { artifacts } : {}),\n })\n return undefined\n })\n },\n\n async onFinish(\n ctx: GenerationMiddlewareContext,\n info: GenerationFinishInfo,\n ) {\n await generationRuns.update(runIdOf(ctx), {\n status: 'completed',\n finishedAt: Date.now(),\n ...(info.usage ? { usage: info.usage } : {}),\n })\n },\n\n async onError(ctx: GenerationMiddlewareContext, info: GenerationErrorInfo) {\n await generationRuns.update(runIdOf(ctx), {\n status: 'failed',\n finishedAt: Date.now(),\n error: {\n message:\n info.error instanceof Error\n ? info.error.message\n : String(info.error),\n },\n })\n },\n\n async onAbort(\n ctx: GenerationMiddlewareContext,\n _info: GenerationAbortInfo,\n ) {\n // Unconditional, unlike chat's: a generation job has no journal and no\n // agent loop, so there is nothing to reattach to. An aborted generation is\n // over, full stop — hence `'aborted'` (terminal) rather than\n // `'interrupted'`, which now means \"parked, waiting for a human\" and is\n // deliberately NOT terminal-shaped, so pairing it with `finishedAt` would\n // leave the run looking permanently active.\n await generationRuns.update(runIdOf(ctx), {\n status: 'aborted',\n finishedAt: Date.now(),\n })\n },\n }\n}\n"],"mappings":"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"}
|