@tanstack/ai-persistence 0.1.4 → 0.1.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.
@@ -303,7 +303,7 @@ var MemoryBlobStore = class {
303
303
  * `@tanstack/ai` when a test or single-process app needs coordination.
304
304
  */
305
305
  function memoryPersistence() {
306
- return defineAIPersistence({ stores: {
306
+ const stores = {
307
307
  messages: new MemoryMessageStore(),
308
308
  runs: new MemoryRunStore(),
309
309
  generationRuns: new MemoryGenerationRunStore(),
@@ -311,7 +311,8 @@ function memoryPersistence() {
311
311
  metadata: new MemoryMetadataStore(),
312
312
  artifacts: new MemoryArtifactStore(),
313
313
  blobs: new MemoryBlobStore()
314
- } });
314
+ };
315
+ return defineAIPersistence({ stores });
315
316
  }
316
317
  //#endregion
317
318
  export { memoryPersistence };
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","names":[],"sources":["../../src/memory.ts"],"sourcesContent":["import { defineAIPersistence } from './types'\nimport { resolveBlobRange } from './blob-range'\nimport type { ModelMessage } from '@tanstack/ai'\nimport type {\n ArtifactRecord,\n ArtifactStore,\n BlobBody,\n BlobGetOptions,\n BlobListOptions,\n BlobObject,\n BlobPutOptions,\n BlobRange,\n BlobRecord,\n BlobStore,\n GenerationRunRecord,\n GenerationRunStore,\n InterruptRecord,\n InterruptStore,\n MessageStore,\n MetadataStore,\n RunRecord,\n RunStore,\n} from './types'\n\nclass MemoryMessageStore implements MessageStore {\n private readonly threads = new Map<string, Array<ModelMessage>>()\n loadThread(threadId: string): Promise<Array<ModelMessage>> {\n return Promise.resolve(this.threads.get(threadId)?.slice() ?? [])\n }\n saveThread(threadId: string, messages: Array<ModelMessage>): Promise<void> {\n this.threads.set(threadId, messages.slice())\n return Promise.resolve()\n }\n}\n\nclass MemoryRunStore implements RunStore {\n private readonly runs = new Map<string, RunRecord>()\n createOrResume(input: {\n runId: string\n threadId: string\n status?: RunRecord['status']\n startedAt: number\n }): Promise<RunRecord> {\n const existing = this.runs.get(input.runId)\n if (existing) return Promise.resolve(existing)\n const record: RunRecord = {\n runId: input.runId,\n threadId: input.threadId,\n status: input.status ?? 'running',\n startedAt: input.startedAt,\n }\n this.runs.set(record.runId, record)\n return Promise.resolve(record)\n }\n update(\n runId: string,\n patch: Partial<\n Pick<\n RunRecord,\n | 'status'\n | 'finishedAt'\n | 'error'\n | 'usage'\n | 'sandboxKey'\n | 'detachedSince'\n | 'cancelRequested'\n | 'driverEpoch'\n >\n >,\n ): Promise<void> {\n const existing = this.runs.get(runId)\n if (existing) this.runs.set(runId, { ...existing, ...patch })\n return Promise.resolve()\n }\n get(runId: string): Promise<RunRecord | null> {\n return Promise.resolve(this.runs.get(runId) ?? null)\n }\n findActiveRun(threadId: string): Promise<RunRecord | null> {\n const active = [...this.runs.values()]\n .filter((run) => run.threadId === threadId && run.status === 'running')\n .sort((a, b) => b.startedAt - a.startedAt)\n return Promise.resolve(active[0] ?? null)\n }\n listByThread(threadId: string): Promise<Array<RunRecord>> {\n const matching = [...this.runs.values()]\n .filter((run) => run.threadId === threadId)\n .sort((a, b) => a.startedAt - b.startedAt)\n return Promise.resolve(matching)\n }\n listReclaimable(opts: {\n now: number\n ttlMs: number\n }): Promise<Array<RunRecord>> {\n const cutoff = opts.now - opts.ttlMs\n const matching = [...this.runs.values()].filter(\n (run) =>\n run.status === 'running' &&\n run.detachedSince !== undefined &&\n run.detachedSince <= cutoff,\n )\n return Promise.resolve(matching)\n }\n}\n\nclass MemoryGenerationRunStore implements GenerationRunStore {\n private readonly generationRuns = new Map<string, GenerationRunRecord>()\n createOrResume(\n input: Pick<\n GenerationRunRecord,\n 'runId' | 'threadId' | 'activity' | 'provider' | 'model' | 'startedAt'\n > & { status?: GenerationRunRecord['status'] },\n ): Promise<GenerationRunRecord> {\n const existing = this.generationRuns.get(input.runId)\n if (existing) return Promise.resolve(existing)\n const record: GenerationRunRecord = {\n runId: input.runId,\n threadId: input.threadId,\n activity: input.activity,\n provider: input.provider,\n model: input.model,\n status: input.status ?? 'running',\n startedAt: input.startedAt,\n }\n this.generationRuns.set(record.runId, record)\n return Promise.resolve(record)\n }\n update(\n runId: string,\n patch: Partial<\n Pick<\n GenerationRunRecord,\n 'status' | 'finishedAt' | 'error' | 'result' | 'artifacts' | 'usage'\n >\n >,\n ): Promise<void> {\n const existing = this.generationRuns.get(runId)\n if (existing) this.generationRuns.set(runId, { ...existing, ...patch })\n return Promise.resolve()\n }\n get(runId: string): Promise<GenerationRunRecord | null> {\n return Promise.resolve(this.generationRuns.get(runId) ?? null)\n }\n findLatestForThread(threadId: string): Promise<GenerationRunRecord | null> {\n const linked = [...this.generationRuns.values()]\n .filter((run) => run.threadId === threadId)\n .sort((a, b) => b.startedAt - a.startedAt)\n return Promise.resolve(linked[0] ?? null)\n }\n}\n\nfunction byRequestedAt(a: InterruptRecord, b: InterruptRecord): number {\n return a.requestedAt - b.requestedAt\n}\n\nclass MemoryInterruptStore implements InterruptStore {\n private readonly interrupts = new Map<string, InterruptRecord>()\n create(\n record: Omit<InterruptRecord, 'status' | 'resolvedAt'>,\n ): Promise<void> {\n // Insert-if-absent (canonical semantics, matching the SQL backends'\n // ON CONFLICT DO NOTHING): a duplicate id must never clobber an existing —\n // possibly already resolved — interrupt back to pending.\n if (!this.interrupts.has(record.interruptId)) {\n this.interrupts.set(record.interruptId, { ...record, status: 'pending' })\n }\n return Promise.resolve()\n }\n resolve(interruptId: string, response?: unknown): Promise<void> {\n const existing = this.interrupts.get(interruptId)\n if (existing) {\n this.interrupts.set(interruptId, {\n ...existing,\n status: 'resolved',\n resolvedAt: Date.now(),\n response,\n })\n }\n return Promise.resolve()\n }\n cancel(interruptId: string): Promise<void> {\n const existing = this.interrupts.get(interruptId)\n if (existing) {\n this.interrupts.set(interruptId, {\n ...existing,\n status: 'cancelled',\n resolvedAt: Date.now(),\n })\n }\n return Promise.resolve()\n }\n get(interruptId: string): Promise<InterruptRecord | null> {\n return Promise.resolve(this.interrupts.get(interruptId) ?? null)\n }\n list(threadId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter((interrupt) => interrupt.threadId === threadId)\n .sort(byRequestedAt),\n )\n }\n listPending(threadId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter(\n (interrupt) =>\n interrupt.threadId === threadId && interrupt.status === 'pending',\n )\n .sort(byRequestedAt),\n )\n }\n listByRun(runId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter((interrupt) => interrupt.runId === runId)\n .sort(byRequestedAt),\n )\n }\n listPendingByRun(runId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter(\n (interrupt) =>\n interrupt.runId === runId && interrupt.status === 'pending',\n )\n .sort(byRequestedAt),\n )\n }\n}\n\nclass MemoryMetadataStore implements MetadataStore {\n // Nested maps so composite identity is `(namespace, key)` without the\n // `${namespace}:${key}` collision where `('a:b','c')` aliases `('a','b:c')`.\n // (This parameter is an app-defined metadata namespace string — not the\n // shared `Scope` identity type from `@tanstack/ai`.)\n private readonly values = new Map<string, Map<string, unknown>>()\n get(namespace: string, key: string): Promise<unknown | null> {\n const bucket = this.values.get(namespace)\n if (!bucket || !bucket.has(key)) return Promise.resolve(null)\n return Promise.resolve(bucket.get(key))\n }\n set(namespace: string, key: string, value: unknown): Promise<void> {\n let bucket = this.values.get(namespace)\n if (!bucket) {\n bucket = new Map()\n this.values.set(namespace, bucket)\n }\n bucket.set(key, value)\n return Promise.resolve()\n }\n delete(namespace: string, key: string): Promise<void> {\n const bucket = this.values.get(namespace)\n if (!bucket) return Promise.resolve()\n bucket.delete(key)\n if (bucket.size === 0) this.values.delete(namespace)\n return Promise.resolve()\n }\n}\n\nclass MemoryArtifactStore implements ArtifactStore {\n private readonly artifacts = new Map<string, ArtifactRecord>()\n save(record: ArtifactRecord): Promise<void> {\n this.artifacts.set(record.artifactId, { ...record })\n return Promise.resolve()\n }\n get(artifactId: string): Promise<ArtifactRecord | null> {\n return Promise.resolve(this.artifacts.get(artifactId) ?? null)\n }\n list(runId: string): Promise<Array<ArtifactRecord>> {\n return Promise.resolve(\n [...this.artifacts.values()].filter((a) => a.runId === runId),\n )\n }\n delete(artifactId: string): Promise<void> {\n this.artifacts.delete(artifactId)\n return Promise.resolve()\n }\n deleteForRun(runId: string): Promise<void> {\n for (const artifact of this.artifacts.values()) {\n if (artifact.runId === runId) this.artifacts.delete(artifact.artifactId)\n }\n return Promise.resolve()\n }\n}\n\ninterface MemoryBlobEntry {\n record: BlobRecord\n bytes: Uint8Array\n}\n\nconst textEncoder = new TextEncoder()\nconst textDecoder = new TextDecoder()\n\nfunction copyBytes(bytes: Uint8Array): Uint8Array {\n return new Uint8Array(bytes)\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n const buffer = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(buffer).set(bytes)\n return buffer\n}\n\nasync function bytesFromStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Uint8Array> {\n const reader = stream.getReader()\n const chunks: Array<Uint8Array> = []\n let total = 0\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n chunks.push(copyBytes(value))\n total += value.byteLength\n }\n } finally {\n reader.releaseLock()\n }\n\n const bytes = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return bytes\n}\n\nasync function bytesFromBlobBody(body: BlobBody): Promise<Uint8Array> {\n if (typeof body === 'string') {\n return textEncoder.encode(body)\n }\n if (body instanceof ArrayBuffer) {\n return new Uint8Array(body.slice(0))\n }\n if (ArrayBuffer.isView(body)) {\n return copyBytes(\n new Uint8Array(body.buffer, body.byteOffset, body.byteLength),\n )\n }\n if (typeof Blob !== 'undefined' && body instanceof Blob) {\n return new Uint8Array(await body.arrayBuffer())\n }\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n return bytesFromStream(body)\n }\n throw new TypeError('Unsupported blob body.')\n}\n\nfunction blobRecordSnapshot(record: BlobRecord): BlobRecord {\n return {\n ...record,\n ...(record.customMetadata\n ? { customMetadata: { ...record.customMetadata } }\n : {}),\n }\n}\n\nfunction blobObject(\n record: BlobRecord,\n bytes: Uint8Array,\n range?: BlobRange,\n): BlobObject {\n // `size` keeps reporting the whole object; only the bytes narrow.\n const served = range\n ? resolveBlobRange(bytes.byteLength, range)\n : { offset: 0, length: bytes.byteLength }\n const copied = copyBytes(\n bytes.subarray(served.offset, served.offset + served.length),\n )\n return {\n ...blobRecordSnapshot(record),\n ...(range ? { range: served } : {}),\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(copyBytes(copied))\n controller.close()\n },\n }),\n arrayBuffer: () => Promise.resolve(bytesToArrayBuffer(copied)),\n text: () => Promise.resolve(textDecoder.decode(copied)),\n }\n}\n\nclass MemoryBlobStore implements BlobStore {\n private readonly blobs = new Map<string, MemoryBlobEntry>()\n private nextEtag = 1\n\n async put(\n key: string,\n body: BlobBody,\n options?: BlobPutOptions,\n ): Promise<BlobRecord> {\n const bytes = await bytesFromBlobBody(body)\n const existing = this.blobs.get(key)\n const now = Date.now()\n const record: BlobRecord = {\n key,\n size: bytes.byteLength,\n etag: String(this.nextEtag++),\n contentType:\n options?.contentType ??\n (typeof Blob !== 'undefined' && body instanceof Blob\n ? body.type || undefined\n : undefined),\n customMetadata: options?.customMetadata\n ? { ...options.customMetadata }\n : undefined,\n createdAt: existing?.record.createdAt ?? now,\n updatedAt: now,\n }\n this.blobs.set(key, { record, bytes: copyBytes(bytes) })\n return blobRecordSnapshot(record)\n }\n\n get(key: string, options?: BlobGetOptions): Promise<BlobObject | null> {\n const entry = this.blobs.get(key)\n return Promise.resolve(\n entry ? blobObject(entry.record, entry.bytes, options?.range) : null,\n )\n }\n\n head(key: string): Promise<BlobRecord | null> {\n const entry = this.blobs.get(key)\n return Promise.resolve(entry ? blobRecordSnapshot(entry.record) : null)\n }\n\n delete(key: string): Promise<void> {\n this.blobs.delete(key)\n return Promise.resolve()\n }\n\n list(options?: BlobListOptions): Promise<{\n objects: Array<BlobRecord>\n cursor?: string\n truncated?: boolean\n }> {\n const limit = options?.limit\n if (limit === 0) {\n return Promise.resolve({ objects: [], truncated: false })\n }\n const keys = [...this.blobs.keys()]\n .filter((key) => key.startsWith(options?.prefix ?? ''))\n .filter((key) => options?.cursor === undefined || key > options.cursor)\n .sort()\n const pageKeys = limit === undefined ? keys : keys.slice(0, limit)\n const objects = pageKeys.map((key) => {\n const blob = this.blobs.get(key)\n if (blob === undefined) {\n throw new Error(`Missing blob for listed key: ${key}`)\n }\n return blobRecordSnapshot(blob.record)\n })\n const truncated = limit !== undefined && keys.length > limit\n return Promise.resolve({\n objects,\n ...(truncated ? { cursor: pageKeys.at(-1), truncated } : {}),\n })\n }\n}\n\ninterface MemoryPersistenceStores {\n messages: MessageStore\n runs: RunStore\n generationRuns: GenerationRunStore\n interrupts: InterruptStore\n metadata: MetadataStore\n artifacts: ArtifactStore\n blobs: BlobStore\n}\n\n/**\n * In-process reference backend for the full state + generation store set.\n *\n * Returns messages + runs + generationRuns + interrupts + metadata + artifacts\n * + blobs. Locks are not included — use `InMemoryLockStore` + `withLocks` from\n * `@tanstack/ai` when a test or single-process app needs coordination.\n */\nexport function memoryPersistence() {\n const stores: MemoryPersistenceStores = {\n messages: new MemoryMessageStore(),\n runs: new MemoryRunStore(),\n generationRuns: new MemoryGenerationRunStore(),\n interrupts: new MemoryInterruptStore(),\n metadata: new MemoryMetadataStore(),\n artifacts: new MemoryArtifactStore(),\n blobs: new MemoryBlobStore(),\n }\n return defineAIPersistence({ stores })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory.js","names":[],"sources":["../../src/memory.ts"],"sourcesContent":["import { defineAIPersistence } from './types'\nimport { resolveBlobRange } from './blob-range'\nimport type { ModelMessage } from '@tanstack/ai'\nimport type {\n ArtifactRecord,\n ArtifactStore,\n BlobBody,\n BlobGetOptions,\n BlobListOptions,\n BlobObject,\n BlobPutOptions,\n BlobRange,\n BlobRecord,\n BlobStore,\n GenerationRunRecord,\n GenerationRunStore,\n InterruptRecord,\n InterruptStore,\n MessageStore,\n MetadataStore,\n RunRecord,\n RunStore,\n} from './types'\n\nclass MemoryMessageStore implements MessageStore {\n private readonly threads = new Map<string, Array<ModelMessage>>()\n loadThread(threadId: string): Promise<Array<ModelMessage>> {\n return Promise.resolve(this.threads.get(threadId)?.slice() ?? [])\n }\n saveThread(threadId: string, messages: Array<ModelMessage>): Promise<void> {\n this.threads.set(threadId, messages.slice())\n return Promise.resolve()\n }\n}\n\nclass MemoryRunStore implements RunStore {\n private readonly runs = new Map<string, RunRecord>()\n createOrResume(input: {\n runId: string\n threadId: string\n status?: RunRecord['status']\n startedAt: number\n }): Promise<RunRecord> {\n const existing = this.runs.get(input.runId)\n if (existing) return Promise.resolve(existing)\n const record: RunRecord = {\n runId: input.runId,\n threadId: input.threadId,\n status: input.status ?? 'running',\n startedAt: input.startedAt,\n }\n this.runs.set(record.runId, record)\n return Promise.resolve(record)\n }\n update(\n runId: string,\n patch: Partial<\n Pick<\n RunRecord,\n | 'status'\n | 'finishedAt'\n | 'error'\n | 'usage'\n | 'sandboxKey'\n | 'detachedSince'\n | 'cancelRequested'\n | 'driverEpoch'\n >\n >,\n ): Promise<void> {\n const existing = this.runs.get(runId)\n if (existing) this.runs.set(runId, { ...existing, ...patch })\n return Promise.resolve()\n }\n get(runId: string): Promise<RunRecord | null> {\n return Promise.resolve(this.runs.get(runId) ?? null)\n }\n findActiveRun(threadId: string): Promise<RunRecord | null> {\n const active = [...this.runs.values()]\n .filter((run) => run.threadId === threadId && run.status === 'running')\n .sort((a, b) => b.startedAt - a.startedAt)\n return Promise.resolve(active[0] ?? null)\n }\n listByThread(threadId: string): Promise<Array<RunRecord>> {\n const matching = [...this.runs.values()]\n .filter((run) => run.threadId === threadId)\n .sort((a, b) => a.startedAt - b.startedAt)\n return Promise.resolve(matching)\n }\n listReclaimable(opts: {\n now: number\n ttlMs: number\n }): Promise<Array<RunRecord>> {\n const cutoff = opts.now - opts.ttlMs\n const matching = [...this.runs.values()].filter(\n (run) =>\n run.status === 'running' &&\n run.detachedSince !== undefined &&\n run.detachedSince <= cutoff,\n )\n return Promise.resolve(matching)\n }\n}\n\nclass MemoryGenerationRunStore implements GenerationRunStore {\n private readonly generationRuns = new Map<string, GenerationRunRecord>()\n createOrResume(\n input: Pick<\n GenerationRunRecord,\n 'runId' | 'threadId' | 'activity' | 'provider' | 'model' | 'startedAt'\n > & { status?: GenerationRunRecord['status'] },\n ): Promise<GenerationRunRecord> {\n const existing = this.generationRuns.get(input.runId)\n if (existing) return Promise.resolve(existing)\n const record: GenerationRunRecord = {\n runId: input.runId,\n threadId: input.threadId,\n activity: input.activity,\n provider: input.provider,\n model: input.model,\n status: input.status ?? 'running',\n startedAt: input.startedAt,\n }\n this.generationRuns.set(record.runId, record)\n return Promise.resolve(record)\n }\n update(\n runId: string,\n patch: Partial<\n Pick<\n GenerationRunRecord,\n 'status' | 'finishedAt' | 'error' | 'result' | 'artifacts' | 'usage'\n >\n >,\n ): Promise<void> {\n const existing = this.generationRuns.get(runId)\n if (existing) this.generationRuns.set(runId, { ...existing, ...patch })\n return Promise.resolve()\n }\n get(runId: string): Promise<GenerationRunRecord | null> {\n return Promise.resolve(this.generationRuns.get(runId) ?? null)\n }\n findLatestForThread(threadId: string): Promise<GenerationRunRecord | null> {\n const linked = [...this.generationRuns.values()]\n .filter((run) => run.threadId === threadId)\n .sort((a, b) => b.startedAt - a.startedAt)\n return Promise.resolve(linked[0] ?? null)\n }\n}\n\nfunction byRequestedAt(a: InterruptRecord, b: InterruptRecord): number {\n return a.requestedAt - b.requestedAt\n}\n\nclass MemoryInterruptStore implements InterruptStore {\n private readonly interrupts = new Map<string, InterruptRecord>()\n create(\n record: Omit<InterruptRecord, 'status' | 'resolvedAt'>,\n ): Promise<void> {\n // Insert-if-absent (canonical semantics, matching the SQL backends'\n // ON CONFLICT DO NOTHING): a duplicate id must never clobber an existing —\n // possibly already resolved — interrupt back to pending.\n if (!this.interrupts.has(record.interruptId)) {\n this.interrupts.set(record.interruptId, { ...record, status: 'pending' })\n }\n return Promise.resolve()\n }\n resolve(interruptId: string, response?: unknown): Promise<void> {\n const existing = this.interrupts.get(interruptId)\n if (existing) {\n this.interrupts.set(interruptId, {\n ...existing,\n status: 'resolved',\n resolvedAt: Date.now(),\n response,\n })\n }\n return Promise.resolve()\n }\n cancel(interruptId: string): Promise<void> {\n const existing = this.interrupts.get(interruptId)\n if (existing) {\n this.interrupts.set(interruptId, {\n ...existing,\n status: 'cancelled',\n resolvedAt: Date.now(),\n })\n }\n return Promise.resolve()\n }\n get(interruptId: string): Promise<InterruptRecord | null> {\n return Promise.resolve(this.interrupts.get(interruptId) ?? null)\n }\n list(threadId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter((interrupt) => interrupt.threadId === threadId)\n .sort(byRequestedAt),\n )\n }\n listPending(threadId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter(\n (interrupt) =>\n interrupt.threadId === threadId && interrupt.status === 'pending',\n )\n .sort(byRequestedAt),\n )\n }\n listByRun(runId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter((interrupt) => interrupt.runId === runId)\n .sort(byRequestedAt),\n )\n }\n listPendingByRun(runId: string): Promise<Array<InterruptRecord>> {\n return Promise.resolve(\n [...this.interrupts.values()]\n .filter(\n (interrupt) =>\n interrupt.runId === runId && interrupt.status === 'pending',\n )\n .sort(byRequestedAt),\n )\n }\n}\n\nclass MemoryMetadataStore implements MetadataStore {\n // Nested maps so composite identity is `(namespace, key)` without the\n // `${namespace}:${key}` collision where `('a:b','c')` aliases `('a','b:c')`.\n // (This parameter is an app-defined metadata namespace string — not the\n // shared `Scope` identity type from `@tanstack/ai`.)\n private readonly values = new Map<string, Map<string, unknown>>()\n get(namespace: string, key: string): Promise<unknown | null> {\n const bucket = this.values.get(namespace)\n if (!bucket || !bucket.has(key)) return Promise.resolve(null)\n return Promise.resolve(bucket.get(key))\n }\n set(namespace: string, key: string, value: unknown): Promise<void> {\n let bucket = this.values.get(namespace)\n if (!bucket) {\n bucket = new Map()\n this.values.set(namespace, bucket)\n }\n bucket.set(key, value)\n return Promise.resolve()\n }\n delete(namespace: string, key: string): Promise<void> {\n const bucket = this.values.get(namespace)\n if (!bucket) return Promise.resolve()\n bucket.delete(key)\n if (bucket.size === 0) this.values.delete(namespace)\n return Promise.resolve()\n }\n}\n\nclass MemoryArtifactStore implements ArtifactStore {\n private readonly artifacts = new Map<string, ArtifactRecord>()\n save(record: ArtifactRecord): Promise<void> {\n this.artifacts.set(record.artifactId, { ...record })\n return Promise.resolve()\n }\n get(artifactId: string): Promise<ArtifactRecord | null> {\n return Promise.resolve(this.artifacts.get(artifactId) ?? null)\n }\n list(runId: string): Promise<Array<ArtifactRecord>> {\n return Promise.resolve(\n [...this.artifacts.values()].filter((a) => a.runId === runId),\n )\n }\n delete(artifactId: string): Promise<void> {\n this.artifacts.delete(artifactId)\n return Promise.resolve()\n }\n deleteForRun(runId: string): Promise<void> {\n for (const artifact of this.artifacts.values()) {\n if (artifact.runId === runId) this.artifacts.delete(artifact.artifactId)\n }\n return Promise.resolve()\n }\n}\n\ninterface MemoryBlobEntry {\n record: BlobRecord\n bytes: Uint8Array\n}\n\nconst textEncoder = new TextEncoder()\nconst textDecoder = new TextDecoder()\n\nfunction copyBytes(bytes: Uint8Array): Uint8Array {\n return new Uint8Array(bytes)\n}\n\nfunction bytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n const buffer = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(buffer).set(bytes)\n return buffer\n}\n\nasync function bytesFromStream(\n stream: ReadableStream<Uint8Array>,\n): Promise<Uint8Array> {\n const reader = stream.getReader()\n const chunks: Array<Uint8Array> = []\n let total = 0\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n chunks.push(copyBytes(value))\n total += value.byteLength\n }\n } finally {\n reader.releaseLock()\n }\n\n const bytes = new Uint8Array(total)\n let offset = 0\n for (const chunk of chunks) {\n bytes.set(chunk, offset)\n offset += chunk.byteLength\n }\n return bytes\n}\n\nasync function bytesFromBlobBody(body: BlobBody): Promise<Uint8Array> {\n if (typeof body === 'string') {\n return textEncoder.encode(body)\n }\n if (body instanceof ArrayBuffer) {\n return new Uint8Array(body.slice(0))\n }\n if (ArrayBuffer.isView(body)) {\n return copyBytes(\n new Uint8Array(body.buffer, body.byteOffset, body.byteLength),\n )\n }\n if (typeof Blob !== 'undefined' && body instanceof Blob) {\n return new Uint8Array(await body.arrayBuffer())\n }\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n return bytesFromStream(body)\n }\n throw new TypeError('Unsupported blob body.')\n}\n\nfunction blobRecordSnapshot(record: BlobRecord): BlobRecord {\n return {\n ...record,\n ...(record.customMetadata\n ? { customMetadata: { ...record.customMetadata } }\n : {}),\n }\n}\n\nfunction blobObject(\n record: BlobRecord,\n bytes: Uint8Array,\n range?: BlobRange,\n): BlobObject {\n // `size` keeps reporting the whole object; only the bytes narrow.\n const served = range\n ? resolveBlobRange(bytes.byteLength, range)\n : { offset: 0, length: bytes.byteLength }\n const copied = copyBytes(\n bytes.subarray(served.offset, served.offset + served.length),\n )\n return {\n ...blobRecordSnapshot(record),\n ...(range ? { range: served } : {}),\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(copyBytes(copied))\n controller.close()\n },\n }),\n arrayBuffer: () => Promise.resolve(bytesToArrayBuffer(copied)),\n text: () => Promise.resolve(textDecoder.decode(copied)),\n }\n}\n\nclass MemoryBlobStore implements BlobStore {\n private readonly blobs = new Map<string, MemoryBlobEntry>()\n private nextEtag = 1\n\n async put(\n key: string,\n body: BlobBody,\n options?: BlobPutOptions,\n ): Promise<BlobRecord> {\n const bytes = await bytesFromBlobBody(body)\n const existing = this.blobs.get(key)\n const now = Date.now()\n const record: BlobRecord = {\n key,\n size: bytes.byteLength,\n etag: String(this.nextEtag++),\n contentType:\n options?.contentType ??\n (typeof Blob !== 'undefined' && body instanceof Blob\n ? body.type || undefined\n : undefined),\n customMetadata: options?.customMetadata\n ? { ...options.customMetadata }\n : undefined,\n createdAt: existing?.record.createdAt ?? now,\n updatedAt: now,\n }\n this.blobs.set(key, { record, bytes: copyBytes(bytes) })\n return blobRecordSnapshot(record)\n }\n\n get(key: string, options?: BlobGetOptions): Promise<BlobObject | null> {\n const entry = this.blobs.get(key)\n return Promise.resolve(\n entry ? blobObject(entry.record, entry.bytes, options?.range) : null,\n )\n }\n\n head(key: string): Promise<BlobRecord | null> {\n const entry = this.blobs.get(key)\n return Promise.resolve(entry ? blobRecordSnapshot(entry.record) : null)\n }\n\n delete(key: string): Promise<void> {\n this.blobs.delete(key)\n return Promise.resolve()\n }\n\n list(options?: BlobListOptions): Promise<{\n objects: Array<BlobRecord>\n cursor?: string\n truncated?: boolean\n }> {\n const limit = options?.limit\n if (limit === 0) {\n return Promise.resolve({ objects: [], truncated: false })\n }\n const keys = [...this.blobs.keys()]\n .filter((key) => key.startsWith(options?.prefix ?? ''))\n .filter((key) => options?.cursor === undefined || key > options.cursor)\n .sort()\n const pageKeys = limit === undefined ? keys : keys.slice(0, limit)\n const objects = pageKeys.map((key) => {\n const blob = this.blobs.get(key)\n if (blob === undefined) {\n throw new Error(`Missing blob for listed key: ${key}`)\n }\n return blobRecordSnapshot(blob.record)\n })\n const truncated = limit !== undefined && keys.length > limit\n return Promise.resolve({\n objects,\n ...(truncated ? { cursor: pageKeys.at(-1), truncated } : {}),\n })\n }\n}\n\ninterface MemoryPersistenceStores {\n messages: MessageStore\n runs: RunStore\n generationRuns: GenerationRunStore\n interrupts: InterruptStore\n metadata: MetadataStore\n artifacts: ArtifactStore\n blobs: BlobStore\n}\n\n/**\n * In-process reference backend for the full state + generation store set.\n *\n * Returns messages + runs + generationRuns + interrupts + metadata + artifacts\n * + blobs. Locks are not included — use `InMemoryLockStore` + `withLocks` from\n * `@tanstack/ai` when a test or single-process app needs coordination.\n */\nexport function memoryPersistence() {\n const stores: MemoryPersistenceStores = {\n messages: new MemoryMessageStore(),\n runs: new MemoryRunStore(),\n generationRuns: new MemoryGenerationRunStore(),\n interrupts: new MemoryInterruptStore(),\n metadata: new MemoryMetadataStore(),\n artifacts: new MemoryArtifactStore(),\n blobs: new MemoryBlobStore(),\n }\n return defineAIPersistence({ stores })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { ChatMiddleware, GenerationMiddleware, PersistedArtifactActivity, PersistedArtifactRef, PersistedArtifactRole } from '@tanstack/ai';
1
+ import { ChatMiddleware, GenerationMiddleware, PersistedArtifactActivity, PersistedArtifactRef, PersistedArtifactRole, TokenUsage } from '@tanstack/ai';
2
2
  import { AIPersistence, AIPersistenceStores, BlobBody, ChatTranscriptStores, RunStore } from './types.js';
3
3
  /**
4
4
  * How generated media is turned into durable artifacts: which pieces of a
@@ -175,13 +175,13 @@ type ValidGenerationPersistence<TStores extends AIPersistenceStores> = InvalidGe
175
175
  * timestamp on it told every reader the run was over while it was in fact
176
176
  * waiting for a human. Only `abortRun`/`completeRun`/`failRun` finish a run.
177
177
  */
178
- export declare function interruptRun(runs: RunStore | undefined, runId: string): Promise<void>;
178
+ export declare function interruptRun(runs: RunStore | undefined, runId: string, usage?: TokenUsage): Promise<void>;
179
179
  /**
180
180
  * Record that the run has ended for good — an explicit cancel, or a disconnect
181
181
  * on a run that has nothing to reattach to. Terminal, so it carries
182
182
  * `finishedAt`.
183
183
  */
184
- export declare function abortRun(runs: RunStore | undefined, runId: string): Promise<void>;
184
+ export declare function abortRun(runs: RunStore | undefined, runId: string, usage?: TokenUsage): Promise<void>;
185
185
  /**
186
186
  * Chat-only **state** persistence middleware. Provides durable transcript,
187
187
  * run records, and interrupts for `chat()`. Does **not** provide locks —
@@ -20,7 +20,7 @@ function generationScope(ctx, opts) {
20
20
  return threadId;
21
21
  }
22
22
  var DEFAULT_ARTIFACT_FETCH_TIMEOUT_MS = 3e4;
23
- var DEFAULT_MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024;
23
+ var DEFAULT_MAX_ARTIFACT_BYTES = 1073741824;
24
24
  var runState = /* @__PURE__ */ new WeakMap();
25
25
  var validResumeStatuses = /* @__PURE__ */ new Set(["resolved", "cancelled"]);
26
26
  function validatePendingResumes(pending, resume) {
@@ -634,11 +634,51 @@ function resolvePersistencePlan(persistence) {
634
634
  };
635
635
  }
636
636
  async function createOrResumeRun(runs, runId, threadId) {
637
- await runs?.createOrResume({
637
+ return (await runs?.createOrResume({
638
638
  runId,
639
639
  threadId,
640
640
  startedAt: Date.now()
641
- });
641
+ }))?.usage;
642
+ }
643
+ function sumOptionalNumber(current, next) {
644
+ if (current === void 0) return next;
645
+ if (next === void 0) return current;
646
+ return current + next;
647
+ }
648
+ function sumNumberFields(current, next) {
649
+ if (!current) return next;
650
+ if (!next) return current;
651
+ const result = { ...current };
652
+ for (const key of Object.keys(next)) {
653
+ const currentValue = current[key];
654
+ const nextValue = next[key];
655
+ if (typeof nextValue === "number") result[key] = (typeof currentValue === "number" ? currentValue : 0) + nextValue;
656
+ }
657
+ return result;
658
+ }
659
+ function accumulateTokenUsage(current, next) {
660
+ if (!current) return { ...next };
661
+ const promptTokensDetails = sumNumberFields(current.promptTokensDetails, next.promptTokensDetails);
662
+ const completionTokensDetails = sumNumberFields(current.completionTokensDetails, next.completionTokensDetails);
663
+ const costDetails = sumNumberFields(current.costDetails, next.costDetails);
664
+ const providerUsageDetails = next.providerUsageDetails ?? current.providerUsageDetails;
665
+ const durationSeconds = sumOptionalNumber(current.durationSeconds, next.durationSeconds);
666
+ const unitsBilled = sumOptionalNumber(current.unitsBilled, next.unitsBilled);
667
+ const cost = sumOptionalNumber(current.cost, next.cost);
668
+ return {
669
+ ...current,
670
+ ...next,
671
+ promptTokens: current.promptTokens + next.promptTokens,
672
+ completionTokens: current.completionTokens + next.completionTokens,
673
+ totalTokens: current.totalTokens + next.totalTokens,
674
+ ...promptTokensDetails ? { promptTokensDetails } : {},
675
+ ...completionTokensDetails ? { completionTokensDetails } : {},
676
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
677
+ ...unitsBilled !== void 0 ? { unitsBilled } : {},
678
+ ...cost !== void 0 ? { cost } : {},
679
+ ...costDetails ? { costDetails } : {},
680
+ ...providerUsageDetails ? { providerUsageDetails } : {}
681
+ };
642
682
  }
643
683
  async function completeRun(runs, runId, usage) {
644
684
  await runs?.update(runId, {
@@ -647,11 +687,12 @@ async function completeRun(runs, runId, usage) {
647
687
  ...usage ? { usage } : {}
648
688
  });
649
689
  }
650
- async function failRun(runs, runId, error) {
690
+ async function failRun(runs, runId, error, usage) {
651
691
  await runs?.update(runId, {
652
692
  status: "failed",
653
693
  finishedAt: Date.now(),
654
- error: { message: error instanceof Error ? error.message : String(error) }
694
+ error: { message: error instanceof Error ? error.message : String(error) },
695
+ ...usage ? { usage } : {}
655
696
  });
656
697
  }
657
698
  /**
@@ -662,18 +703,22 @@ async function failRun(runs, runId, error) {
662
703
  * timestamp on it told every reader the run was over while it was in fact
663
704
  * waiting for a human. Only `abortRun`/`completeRun`/`failRun` finish a run.
664
705
  */
665
- async function interruptRun(runs, runId) {
666
- await runs?.update(runId, { status: "interrupted" });
706
+ async function interruptRun(runs, runId, usage) {
707
+ await runs?.update(runId, {
708
+ status: "interrupted",
709
+ ...usage ? { usage } : {}
710
+ });
667
711
  }
668
712
  /**
669
713
  * Record that the run has ended for good — an explicit cancel, or a disconnect
670
714
  * on a run that has nothing to reattach to. Terminal, so it carries
671
715
  * `finishedAt`.
672
716
  */
673
- async function abortRun(runs, runId) {
717
+ async function abortRun(runs, runId, usage) {
674
718
  await runs?.update(runId, {
675
719
  status: "aborted",
676
- finishedAt: Date.now()
720
+ finishedAt: Date.now(),
721
+ ...usage ? { usage } : {}
677
722
  });
678
723
  }
679
724
  /**
@@ -700,9 +745,10 @@ function withPersistence(persistence, options = {}) {
700
745
  const { wantsInterrupts, runs } = resolvePersistencePlan(persistence);
701
746
  const messageStore = persistence.stores.messages;
702
747
  if (!messageStore) throw new Error("Chat persistence requires stores.messages.");
748
+ const provides = [PersistenceCapability, ...wantsInterrupts ? [InterruptsCapability] : []];
703
749
  return defineChatMiddleware({
704
750
  name: "chat-persistence",
705
- provides: [PersistenceCapability, ...wantsInterrupts ? [InterruptsCapability] : []],
751
+ provides,
706
752
  setup(ctx) {
707
753
  providePersistence(ctx, persistence);
708
754
  runState.set(ctx, {
@@ -733,14 +779,13 @@ function withPersistence(persistence, options = {}) {
733
779
  resumeByInterruptId
734
780
  };
735
781
  }
736
- await createOrResumeRun(runs, ctx.runId, ctx.threadId);
737
- {
738
- const state = runState.get(ctx);
739
- if (!state?.merged) {
740
- if (state) state.merged = true;
741
- const stored = await messageStore.loadThread(ctx.threadId);
742
- patch.messages = config.messages.length > 0 ? config.messages : stored;
743
- }
782
+ const storedUsage = await createOrResumeRun(runs, ctx.runId, ctx.threadId);
783
+ const state = runState.get(ctx);
784
+ if (state && storedUsage) state.usage = storedUsage;
785
+ if (!state?.merged) {
786
+ if (state) state.merged = true;
787
+ const stored = await messageStore.loadThread(ctx.threadId);
788
+ patch.messages = config.messages.length > 0 ? config.messages : stored;
744
789
  }
745
790
  return Object.keys(patch).length > 0 ? patch : void 0;
746
791
  },
@@ -753,7 +798,7 @@ function withPersistence(persistence, options = {}) {
753
798
  if (ctx.phase === "modelStream") {
754
799
  const s = runState.get(ctx);
755
800
  if (s && chunk.type === "TEXT_MESSAGE_START") {
756
- s.streamingMessageId = chunk.messageId;
801
+ s.streamingMessageId = typeof chunk.messageId === "string" && chunk.messageId !== "" ? chunk.messageId : void 0;
757
802
  s.streamingMessageCreatedAt = /* @__PURE__ */ new Date();
758
803
  s.streamingText = "";
759
804
  } else if (s && chunk.type === "TOOL_CALL_START" && typeof chunk.parentMessageId === "string" && chunk.parentMessageId !== "" && s.streamingMessageId === void 0) {
@@ -792,25 +837,32 @@ function withPersistence(persistence, options = {}) {
792
837
  payload: interruptPayload(interrupt)
793
838
  });
794
839
  }
795
- await interruptRun(runs, ctx.runId);
840
+ const usage = ctx.phase === "modelStream" && chunk.usage ? accumulateTokenUsage(state.usage, chunk.usage) : state.usage ?? chunk.usage;
841
+ state.usage = usage;
842
+ await interruptRun(runs, ctx.runId, usage);
796
843
  await messageStore.saveThread(ctx.threadId, [...ctx.messages]);
797
844
  state.interrupted = true;
798
845
  },
846
+ onUsage(ctx, usage) {
847
+ const state = runState.get(ctx);
848
+ if (!state || state.interrupted) return;
849
+ state.usage = accumulateTokenUsage(state.usage, usage);
850
+ },
799
851
  async onFinish(ctx, info) {
800
852
  const state = runState.get(ctx);
801
853
  if (state?.interrupted) return;
802
854
  await messageStore.saveThread(ctx.threadId, finishedTranscript(ctx.messages, info, state?.streamingMessageId, state?.streamingMessageCreatedAt));
803
- await completeRun(runs, ctx.runId, info.usage);
855
+ await completeRun(runs, ctx.runId, state?.usage ?? info.usage);
804
856
  await commitPendingResumes(state, persistence.stores.interrupts);
805
857
  },
806
858
  async onError(ctx, info) {
807
- await failRun(runs, ctx.runId, info.error);
859
+ await failRun(runs, ctx.runId, info.error, runState.get(ctx)?.usage);
808
860
  },
809
861
  async onAbort(ctx, info) {
810
862
  const cancelled = info.cancelRequested === true || runs !== void 0 && await wasCancelRequested(runs, ctx.runId);
811
863
  const state = runState.get(ctx);
812
864
  if (cancelled || !detachableRun(ctx) && state?.interrupted !== true) {
813
- await abortRun(runs, ctx.runId);
865
+ await abortRun(runs, ctx.runId, state?.usage);
814
866
  return;
815
867
  }
816
868
  }