{"_id":"ai-resumable-stream","_rev":"10-ffba6d530527922105cf6caab8e11955","name":"ai-resumable-stream","dist-tags":{"latest":"3.2.0"},"versions":{"1.0.0":{"name":"ai-resumable-stream","version":"1.0.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.0.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"ccfea6c1ddeb3d84349dbdebb5be760c1e433d39","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.0.0.tgz","fileCount":4,"integrity":"sha512-uEgfYKJhfDHd/2zVZhHHtuQdgihtoN8N1c2sIULuEoZBiwob7rrq7jPq6ZlcxLWYM6YMaZehj2zefJQorESdbQ==","signatures":[{"sig":"MEQCIGYu6sek+pTC9dluRPFet1inYRq2pDYmfJ2ifcoNpermAiA7ihHFrAxqUZOD39sITSwGaV4kxdyBDOu/DQHZn7wPYQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":16866},"type":"module","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"gitHead":"0304708249a1c4fd2cfe581d014e0c6bdc13a442","scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","prepare":"husky","format:ci":"oxfmt --check","prepublishOnly":"pnpm build"},"_npmUser":{"name":"chriszirkel","email":"chris.zirkel@gmail.com"},"repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.6.2","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.11.1","dependencies":{"ai-stream-utils":"^1.5.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.0.0","devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.0.0_1770817857686_0.9404923769580409","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"ai-resumable-stream","version":"1.1.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.1.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"bebb1087744c2fea704f58c8ae58d442710687dd","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.1.0.tgz","fileCount":5,"integrity":"sha512-8Oou2XM4uLOei6vapfbSqo2fYWSHy4tUopJSsI7mdrqGO+EN1C1v1efjiVypP5kLd5xnULLHeSXFhSR07Vn7AQ==","signatures":[{"sig":"MEUCIGYm5Qwh2VQLKWoCtyxtdmveiOsIw6AgOvXP7FlUoWdXAiEAgMPy4RkulGb8E2DbdiVO13zUheZIgyRrOpWkKE5FenY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@1.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":18622},"type":"module","_from":"file:ai-resumable-stream-1.1.0.tgz","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/21a4cb4b2dfbab187b8475cbfc90eeb1/ai-resumable-stream-1.1.0.tgz","_integrity":"sha512-8Oou2XM4uLOei6vapfbSqo2fYWSHy4tUopJSsI7mdrqGO+EN1C1v1efjiVypP5kLd5xnULLHeSXFhSR07Vn7AQ==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.6.2","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.13.0","dependencies":{"ai-stream-utils":"^1.6.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^6.0.0","redis":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.1.0_1770991655736_0.5176870018250757","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"ai-resumable-stream","version":"1.1.1","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.1.1","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"7318fef35eb1c59ad1bab75933c5fb5c9b159755","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.1.1.tgz","fileCount":5,"integrity":"sha512-VAbf//KZx7jsfEMuw0GIWrYLzt4JN+DYAVrBEaiicqi5pvKSH/AzmrdPspCkiCQPR/VeFqvrLHDLk/LU0NuvCw==","signatures":[{"sig":"MEYCIQCPYM0RWaOb0eGi8PfzwZc6QL1AyqUnwgrVE3ky1lid0gIhAIOmVKSP9NzLUPSwhYlFji88jVMPPCbY0dyRy8P92b0C","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@1.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":18655},"type":"module","_from":"file:ai-resumable-stream-1.1.1.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/aa08abe7086f459bc4d4d6f336c810ee/ai-resumable-stream-1.1.1.tgz","_integrity":"sha512-VAbf//KZx7jsfEMuw0GIWrYLzt4JN+DYAVrBEaiicqi5pvKSH/AzmrdPspCkiCQPR/VeFqvrLHDLk/LU0NuvCw==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.6.2","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.13.0","dependencies":{"ai-stream-utils":"^1.6.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^6.0.0","redis":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.1.1_1770992559597_0.5002806887859035","host":"s3://npm-registry-packages-npm-production"}},"1.2.1":{"name":"ai-resumable-stream","version":"1.2.1","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.2.1","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"41476182e055c7faca20089d34d844a8593c283c","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.2.1.tgz","fileCount":5,"integrity":"sha512-N/4YPZL9KNXVmZPGoZGFHbArsefL3RmXLp1a4YXn4PWs5BUu2Rp81a1oyloawQ1TOI9QhCnMREwnkDGsR3kb2w==","signatures":[{"sig":"MEYCIQD4xYeV+KUiBS6Km/bHZnh7y1Y1Tufqe1XiOuDrR6nNbgIhANpy0gt7ZFRaJJW4dTGiZqWhgW/d9ga7ShfcGDzdwtYb","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@1.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":21321},"type":"module","_from":"file:ai-resumable-stream-1.2.1.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/64138b446f9d181ec5997342d72deadb/ai-resumable-stream-1.2.1.tgz","_integrity":"sha512-N/4YPZL9KNXVmZPGoZGFHbArsefL3RmXLp1a4YXn4PWs5BUu2Rp81a1oyloawQ1TOI9QhCnMREwnkDGsR3kb2w==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.9.0","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.14.0","dependencies":{"ai-stream-utils":"^2.0.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","zod":"^4.3.6","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","mitata":"^1.0.34","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^6.0.0","redis":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.2.1_1774358133812_0.36811480670023067","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"ai-resumable-stream","version":"1.3.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.3.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"ee863e6672caa9488f676b148f9821f24d71794e","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.3.0.tgz","fileCount":5,"integrity":"sha512-jMeED4CafP7hGp1LaRQGHGjAqnr4yZ0H7Kjy5ruc+rIvSkaUG4bZmYK8i2lMFGsDAodFVBoaAyfzgNY8Hu3GEA==","signatures":[{"sig":"MEUCIQDW6iSm7oi3h7GIw8/oZ18mQelgU96auzz0uewq7gyhUwIgLfY2kGasJT7gvFqmKlb4bGvKiCDO2Z2uZ1nMY7NyfBw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@1.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":21883},"type":"module","_from":"file:ai-resumable-stream-1.3.0.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/5f7e5603e755736c625d79c546df1913/ai-resumable-stream-1.3.0.tgz","_integrity":"sha512-jMeED4CafP7hGp1LaRQGHGjAqnr4yZ0H7Kjy5ruc+rIvSkaUG4bZmYK8i2lMFGsDAodFVBoaAyfzgNY8Hu3GEA==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.11.0","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.14.1","dependencies":{"ai-stream-utils":"^2.0.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","zod":"^4.3.6","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","mitata":"^1.0.34","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^6.0.0","redis":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.3.0_1775136615475_0.11447770479853792","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"ai-resumable-stream","version":"1.4.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@1.4.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"c815fc71e92e6b319152f5fcb8153f21e1636d63","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-1.4.0.tgz","fileCount":5,"integrity":"sha512-lYNcoyk2nM/cS8oXexMX9B1nMvP5Gpm59uc7Z73hwDzLHy20kPOUg99BUmoIldrMAXCq7dCIu6ALySiXAkojng==","signatures":[{"sig":"MEUCIAPNmRMnAhFiYBArk5zLuS2sCtejHXgfnf099d2/n52iAiEAm7HNWFddOd3Fuyjw7byOXItHKqrWkarUrwY+MTxZNrA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@1.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":25739},"type":"module","_from":"file:ai-resumable-stream-1.4.0.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/97216019cd26485b82883c2b5e31b9ca/ai-resumable-stream-1.4.0.tgz","_integrity":"sha512-lYNcoyk2nM/cS8oXexMX9B1nMvP5Gpm59uc7Z73hwDzLHy20kPOUg99BUmoIldrMAXCq7dCIu6ALySiXAkojng==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.11.0","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.14.1","dependencies":{"ai-stream-utils":"^2.0.0","resumable-stream":"^2.2.10"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^6.0.79","tsx":"^4.21.0","zod":"^4.3.6","husky":"^9.1.7","oxfmt":"^0.31.0","redis":"^5.10.0","mitata":"^1.0.34","oxlint":"^1.46.0","tsdown":"^0.20.3","vitest":"^4.0.18","publint":"^0.3.17","pkg-pr-new":"^0.0.63","typescript":"^5.9.3","@types/node":"^25.2.3","lint-staged":"^16.2.7","@ai-sdk/provider":"^3.0.8","@vitest/coverage-v8":"^4.0.18","redis-memory-server":"^0.16.0","@arethetypeswrong/cli":"^0.18.2","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^6.0.0","redis":"^5.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_1.4.0_1775893275801_0.3775057702633182","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"ai-resumable-stream","version":"2.0.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@2.0.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"a350845a4c9ca3ad437ffa8b506f16cf68203764","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-2.0.0.tgz","fileCount":5,"integrity":"sha512-j/jpNVpGp/BATBy+AiaDxwoOdnkDB+ohlW5PdthMEMB4fiBEa/6UPTP3dlhEC0h+p4AlGeMVJhgjr4ltAleH+g==","signatures":[{"sig":"MEYCIQDsQurHl4GlAbNw3Kt83qgww8ipW0WT/cGA4452OICdKAIhAPEHvxVdYpP9KeebmFkaQsaBMwaIMy6ogb2FqaH3zeUl","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@2.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":26971},"type":"module","_from":"file:ai-resumable-stream-2.0.0.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./package.json":"./package.json"},"scripts":{"lint":"oxlint --fix","test":"vitest","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check","typecheck":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/93a3e6c81d7b10055d4d034f0e0045ba/ai-resumable-stream-2.0.0.tgz","_integrity":"sha512-j/jpNVpGp/BATBy+AiaDxwoOdnkDB+ohlW5PdthMEMB4fiBEa/6UPTP3dlhEC0h+p4AlGeMVJhgjr4ltAleH+g==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.16.0","description":"AI SDK: resume and stop UI message streams","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.18.0","dependencies":{"ai-stream-utils":"^3.1.1","resumable-stream":"^2.2.12"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.22","tsx":"^4.23.1","zod":"^4.4.3","husky":"^9.1.7","oxfmt":"^0.58.0","redis":"^6.1.0","mitata":"^1.0.34","oxlint":"^1.73.0","tsdown":"^0.22.7","vitest":"^4.1.10","publint":"^0.3.21","redis-v5":"npm:redis@^5.12.1","redis-v6":"npm:redis@^6.1.0","pkg-pr-new":"^0.0.75","typescript":"^7.0.2","@types/node":"^26.1.1","ai-test-kit":"^3.0.1","lint-staged":"^17.0.8","@ai-sdk/provider":"^4.0.3","@vitest/coverage-v8":"^4.1.10","redis-memory-server":"^0.17.0","@arethetypeswrong/cli":"^0.18.5","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^7.0.0","redis":"^5.0.0 || ^6.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_2.0.0_1784041300928_0.8678006403654841","host":"s3://npm-registry-packages-npm-production"}},"3.0.0":{"name":"ai-resumable-stream","version":"3.0.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","s3","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@3.0.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"0a919f8bfddece0b3f4a26c343729936ef87ef3c","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-3.0.0.tgz","fileCount":14,"integrity":"sha512-F3c+hkMemjULVMKa9fFv8o1Zz4Uz+z3ou0w4A51NHOzXp4z1/gBeBQn2FBWh+uIE/SUP32N6BsB50Vu+LJaP5Q==","signatures":[{"sig":"MEUCIC45MBLlNcluymeSFzGlkMFAXI4AjE9nqlp9NCSswKj1AiEAlDfVeducg6FosCuQs/d1uf3N8AhabEXArjmgPbEJlJQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@3.0.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":78519},"type":"module","_from":"file:ai-resumable-stream-3.0.0.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./ai-sdk":"./dist/ai-sdk/index.mjs","./package.json":"./package.json","./adapters/redis":"./dist/adapters/redis/index.mjs","./adapters/s3-express":"./dist/adapters/s3-express/index.mjs"},"scripts":{"lint":"oxlint --fix","test":"vitest run","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check","typecheck":"tsc --noEmit","test:watch":"vitest","example:redis":"tsx examples/redis.ts","example:s3-express":"tsx examples/s3-express.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/a8ea342715472cd03407303cab250982/ai-resumable-stream-3.0.0.tgz","_integrity":"sha512-F3c+hkMemjULVMKa9fFv8o1Zz4Uz+z3ou0w4A51NHOzXp4z1/gBeBQn2FBWh+uIE/SUP32N6BsB50Vu+LJaP5Q==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.19.0","description":"Resume and stop AI SDK streams, backed by S3, Redis, or any store you provide","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.20.0","dependencies":{"ai-stream-utils":"^3.1.1","resumable-stream":"^2.2.12"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.22","tsx":"^4.23.13","zod":"^4.4.3","husky":"^9.1.7","oxfmt":"^0.58.0","redis":"^6.1.0","oxlint":"^1.73.0","tsdown":"^0.22.7","vitest":"^4.1.10","publint":"^0.3.21","redis-v5":"npm:redis@^5.12.1","redis-v6":"npm:redis@^6.1.0","pkg-pr-new":"^0.0.75","typescript":"^7.0.2","@types/node":"^26.1.1","ai-test-kit":"^3.0.1","lint-staged":"^17.0.8","@ai-sdk/provider":"^4.0.3","@aws-sdk/client-s3":"^3.1131.0","@vitest/coverage-v8":"^4.1.10","redis-memory-server":"^0.17.0","@arethetypeswrong/cli":"^0.18.5","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^7.0.0","redis":"^5.0.0 || ^6.0.0","@aws-sdk/client-s3":"^3.700.0"},"peerDependenciesMeta":{"ai":{"optional":true},"redis":{"optional":true},"@aws-sdk/client-s3":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_3.0.0_1789646504964_0.7905735136751113","host":"s3://npm-registry-packages-npm-production"}},"3.1.0":{"name":"ai-resumable-stream","version":"3.1.0","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","s3","stream"],"author":{"name":"Chris Cook"},"license":"MIT","_id":"ai-resumable-stream@3.1.0","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"64bf835bf3b000e8efc69c700512486ce059461f","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-3.1.0.tgz","fileCount":14,"integrity":"sha512-8M2TSc79jif9AZkFpeEOlDZ/etzdrY9347sgZniBtwXbp/n4bSgprz71rlr8ZRXmVA2el/WjcYzGk9GWy4z7Zg==","signatures":[{"sig":"MEYCIQDB8XkGYtTwIpoNzcr83xbSoJpNuciagIbhsKCnO0DdvQIhAOKVe9s61OKhDBHFgog3W8SCGaMuLQ5mBYhQMrp0q9Bt","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIFUE6nUIsoJHATf72dTIbh+4zkm7N5PfbyA8pa0VMZdwAiEA4ivrJRVhib3nvaiHM+xnrTsiyQ73TXqSm1mskLbj4eg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@3.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":88430},"type":"module","_from":"file:ai-resumable-stream-3.1.0.tgz","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./ai-sdk":"./dist/ai-sdk/index.mjs","./package.json":"./package.json","./adapters/redis":"./dist/adapters/redis/index.mjs","./adapters/s3-express":"./dist/adapters/s3-express/index.mjs"},"scripts":{"lint":"oxlint --fix","test":"vitest run","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check","typecheck":"tsc --noEmit","test:watch":"vitest","example:redis":"tsx examples/redis.ts","example:s3-express":"tsx examples/s3-express.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"_resolved":"/tmp/45bd510ac187fec699d4404ae200cf3d/ai-resumable-stream-3.1.0.tgz","_integrity":"sha512-8M2TSc79jif9AZkFpeEOlDZ/etzdrY9347sgZniBtwXbp/n4bSgprz71rlr8ZRXmVA2el/WjcYzGk9GWy4z7Zg==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.19.0","description":"Resume and stop AI SDK streams, backed by S3, Redis, or any store you provide","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"_nodeVersion":"24.20.0","dependencies":{"ai-stream-utils":"^3.1.1","resumable-stream":"^2.2.12"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.22","tsx":"^4.23.13","zod":"^4.4.3","husky":"^9.1.7","oxfmt":"^0.58.0","redis":"^6.1.0","oxlint":"^1.73.0","tsdown":"^0.22.7","vitest":"^4.1.10","publint":"^0.3.21","redis-v5":"npm:redis@^5.12.1","redis-v6":"npm:redis@^6.1.0","pkg-pr-new":"^0.0.75","typescript":"^7.0.2","@types/node":"^26.1.1","ai-test-kit":"^3.0.1","lint-staged":"^17.0.8","@ai-sdk/provider":"^4.0.3","@aws-sdk/client-s3":"^3.1131.0","@vitest/coverage-v8":"^4.1.10","redis-memory-server":"^0.17.0","@arethetypeswrong/cli":"^0.18.5","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^7.0.0","redis":"^5.0.0 || ^6.0.0","@aws-sdk/client-s3":"^3.700.0"},"peerDependenciesMeta":{"ai":{"optional":true},"redis":{"optional":true},"@aws-sdk/client-s3":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-resumable-stream_3.1.0_1789725567271_0.4455179776015199","host":"s3://npm-registry-packages-npm-production"}},"3.2.0":{"_id":"ai-resumable-stream@3.2.0","bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"dist":{"shasum":"3fc2a4b6da2363844fa93f3288380fcf92f9eab6","tarball":"https://registry.npmjs.org/ai-resumable-stream/-/ai-resumable-stream-3.2.0.tgz","fileCount":14,"integrity":"sha512-0ncHOjlS2jAxr/LQy/eT8QTMc3BiMBma1hrrJsNESb3PPkAx6Ib69obfowFZNY1RQEYW+OmEsIlNYPZfEfKarg==","signatures":[{"sig":"MEUCIHmS0A/QgLCQ5lAG6QxWB+lDV0S/UWx/peEszGMh+cemAiEA5unVw2QAV+WB7ENOJvT9NhjtdnTVjoPYzQv5QUopMMM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIH4jd6mpq9yMZEviWkeSIbaxOQa3YOGLymmmCWcgPt7XAiA6AmjundUBMYJOELOtKfVPoNDY3aP55CI0hjGmCKCZVA=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/ai-resumable-stream@3.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":91178},"name":"ai-resumable-stream","type":"module","_from":"file:ai-resumable-stream-3.2.0.tgz","types":"./dist/index.d.mts","author":{"name":"Chris Cook"},"exports":{".":"./dist/index.mjs","./ai-sdk":"./dist/ai-sdk/index.mjs","./package.json":"./package.json","./adapters/redis":"./dist/adapters/redis/index.mjs","./adapters/s3-express":"./dist/adapters/s3-express/index.mjs"},"license":"MIT","scripts":{"lint":"oxlint --fix","test":"vitest run","build":"tsdown","format":"oxfmt --write","lint:ci":"oxlint","format:ci":"oxfmt --check","typecheck":"tsc --noEmit","test:watch":"vitest","example:redis":"tsx examples/redis.ts","example:s3-express":"tsx examples/s3-express.ts"},"version":"3.2.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:bd67421b-b0ff-4618-b009-a80609389788"}},"homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","s3","stream"],"_resolved":"/tmp/624a1d589b7baa1c1e6e4a74caa70dc1/ai-resumable-stream-3.2.0.tgz","_integrity":"sha512-0ncHOjlS2jAxr/LQy/eT8QTMc3BiMBma1hrrJsNESb3PPkAx6Ib69obfowFZNY1RQEYW+OmEsIlNYPZfEfKarg==","repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"_npmVersion":"11.19.0","description":"Resume and stop AI SDK streams, backed by S3, Redis, or any store you provide","directories":{},"lint-staged":{"*.{json,jsonc}":["pnpm format"],"*.{js,ts,tsx,jsx}":["pnpm lint","pnpm format"]},"maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"_nodeVersion":"24.20.0","dependencies":{"ai-stream-utils":"^3.1.1","resumable-stream":"^2.2.12"},"_hasShrinkwrap":false,"devDependencies":{"ai":"^7.0.22","tsx":"^4.23.13","zod":"^4.4.3","husky":"^9.1.7","oxfmt":"^0.58.0","redis":"^6.1.0","oxlint":"^1.73.0","tsdown":"^0.22.7","vitest":"^4.1.10","publint":"^0.3.21","redis-v5":"npm:redis@^5.12.1","redis-v6":"npm:redis@^6.1.0","pkg-pr-new":"^0.0.75","typescript":"^7.0.2","@types/node":"^26.1.1","ai-test-kit":"^3.0.1","lint-staged":"^17.0.8","@ai-sdk/provider":"^4.0.3","@aws-sdk/client-s3":"^3.1131.0","@vitest/coverage-v8":"^4.1.10","redis-memory-server":"^0.17.0","@arethetypeswrong/cli":"^0.18.5","@total-typescript/tsconfig":"^1.0.4"},"peerDependencies":{"ai":"^7.0.0","redis":"^5.0.0 || ^6.0.0","@aws-sdk/client-s3":"^3.700.0"},"peerDependenciesMeta":{"ai":{"optional":true},"redis":{"optional":true},"@aws-sdk/client-s3":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ai-resumable-stream_3.2.0_1789986977606_0.5807543143051943"}}},"time":{"created":"2026-02-11T13:50:57.685Z","modified":"2026-09-21T10:36:18.035Z","1.0.0":"2026-02-11T13:50:57.831Z","1.1.0":"2026-02-13T14:07:35.872Z","1.1.1":"2026-02-13T14:22:39.751Z","1.2.1":"2026-03-24T13:15:33.973Z","1.3.0":"2026-04-02T13:30:15.649Z","1.4.0":"2026-04-11T07:41:15.948Z","2.0.0":"2026-07-14T15:01:41.078Z","3.0.0":"2026-09-17T12:01:45.154Z","3.1.0":"2026-09-18T09:59:27.349Z","3.2.0":"2026-09-21T10:36:17.711Z"},"bugs":{"url":"https://github.com/zirkelc/ai-resumable-stream/issues"},"author":{"name":"Chris Cook"},"license":"MIT","homepage":"https://github.com/zirkelc/ai-resumable-stream#readme","keywords":["ai","ai-sdk","redis","resumable","resumable-stream","s3","stream"],"repository":{"url":"git+https://github.com/zirkelc/ai-resumable-stream.git","type":"git"},"description":"Resume and stop AI SDK streams, backed by S3, Redis, or any store you provide","maintainers":[{"name":"chriszirkel","email":"chris.zirkel@gmail.com"}],"readme":"<div align='center'>\n\n# ai-resumable-stream\n\n<p align=\"center\">AI SDK: Resume and stop UI message streams</p>\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/ai-resumable-stream\" alt=\"ai-resumable-stream\"><img src=\"https://img.shields.io/npm/dt/ai-resumable-stream?label=ai-resumable-stream\"></a> <a href=\"https://github.com/zirkelc/ai-resumable-stream/actions/workflows/ci.yml\" alt=\"CI\"><img src=\"https://img.shields.io/github/actions/workflow/status/zirkelc/ai-resumable-stream/ci.yml?branch=main\"></a>\n</p>\n\n</div>\n\nThis library provides resumable streaming for UI message streams created by [`streamText()`](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text) in the AI SDK. The library stores chunks as they are produced. Clients can then resume an interrupted stream, and any request can stop an active stream.\n\nYou choose where the chunks are stored. The library includes adapters for Redis and S3 Express. You can also write your own adapter by implementing four methods.\n\n**Why?**\n\nA stream does not keep its data. After a chunk is sent, it is gone. This causes two problems.\n\n**Resume:** The server does not record which chunks it has sent. When a client disconnects (network drop, page reload, tab switch), the stream continues on the server, but the client misses the chunks that are sent during that time. The server can only replay the missed chunks if they are stored somewhere.\n\n**Stop:** The request that stops a stream is not the request that started it. When the user clicks \"Stop generating\", the client sends a new HTTP request, but the stream runs in a different request or process. The two requests need a shared place to pass the stop signal.\n\n## Install\n\n```sh\nnpm install ai-resumable-stream\n```\n\nInstall the optional peer dependencies for the features you use:\n\n| Package              | Needed for                                |\n| -------------------- | ----------------------------------------- |\n| `redis`              | `ai-resumable-stream/adapters/redis`      |\n| `@aws-sdk/client-s3` | `ai-resumable-stream/adapters/s3-express` |\n| `ai`                 | `ai-resumable-stream/ai-sdk`              |\n\n> [!NOTE]\n> Version compatibility:\n>\n> - Use [`ai-resumable-stream@1.x`](https://github.com/zirkelc/ai-resumable-stream/tree/v1.x) for AI SDK v6\n> - Use [`ai-resumable-stream@2.x`](https://github.com/zirkelc/ai-resumable-stream/tree/v2.x) or [`ai-resumable-stream@3.x`](https://github.com/zirkelc/ai-resumable-stream/tree/v3.x) and later for AI SDK v7\n\n## Quick start\n\nCreate one context and reuse it for every stream.\n\n```ts\nimport { createClient } from \"redis\";\nimport { createRedisAdapter } from \"ai-resumable-stream/adapters/redis\";\nimport { createResumableUIMessageStream } from \"ai-resumable-stream/ai-sdk\";\n\nconst publisher = createClient({ url: process.env.REDIS_URL });\nconst subscriber = createClient({ url: process.env.REDIS_URL });\n\n// Optional: connect clients immediately or let the library connect on demand\nawait publisher.connect();\nawait subscriber.connect();\n\nconst context = createResumableUIMessageStream({\n  adapter: createRedisAdapter({ publisher, subscriber }),\n});\n```\n\nThen use the context in your three routes:\n\n```ts\nimport { streamText, toUIMessageStream, type UIMessage } from \"ai\";\n\n// POST /chat/:chatId\nexport async function send(chatId: string, messages: Array<UIMessage>) {\n  const abortController = new AbortController();\n\n  const result = streamText({\n    model: openai(`gpt-4o`),\n    messages,\n    abortSignal: abortController.signal,\n  });\n\n  const { stream } = await context.startStream(toUIMessageStream({ stream: result.stream }), {\n    streamId: chatId,\n    abortController,\n  });\n\n  return stream;\n}\n\n// GET /chat/:chatId/stream\nexport async function resume(chatId: string) {\n  const stream = await context.resumeStream({ streamId: chatId });\n\n  return stream ?? new Response(null, { status: 204 });\n}\n\n// POST /chat/:chatId/stop\nexport async function stop(chatId: string) {\n  await context.stopStream({ streamId: chatId });\n}\n```\n\n## Usage\n\n### `startStream`\n\nStarts a stream and stores the chunks as they are produced. Returns the stream for the client, the stream id and the generation id.\n\n> [!TIP]\n> The returned stream is both a `ReadableStream` and an async iterable, so `return stream` and `yield* stream` both work.\n\n```ts\nimport { streamText, toUIMessageStream } from \"ai\";\n\nasync function sendMessage(chatId: string, messages: UIMessage[]) {\n  // Optional: `streamText` sees the stop signal and aborts the HTTP request\n  const abortController = new AbortController();\n\n  const result = streamText({\n    model: openai(`gpt-4o`),\n    messages,\n    abortSignal: abortController.signal,\n  });\n\n  const { stream } = await context.startStream(toUIMessageStream({ stream: result.stream }), {\n    // Optional: generates a stream id if not supplied\n    streamId: chatId,\n    abortController,\n  });\n\n  // Return stream to client\n  return stream;\n}\n```\n\n| Option                    | Type                          | Description                                                                                                                                                       |\n| ------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `streamId`                | `string`                      | Id of the stream. Use it to resume or stop the stream. Defaults to a generated id, returned as `streamId`                                                         |\n| `generationId`            | `string`                      | Id of this generation. Use it to resume or stop only this generation. Must be unique within the stream id. Defaults to a generated id, returned as `generationId` |\n| `abortController`         | `AbortController`             | Aborted when the stream is stopped. Created by the library if you do not pass one                                                                                 |\n| `stopTimeoutMs`           | `number`                      | Time that a stop waits for the source to end before it cancels the source. Defaults to `1000` with a supplied `abortController`, `0` without one                  |\n| `onStopSubscriptionError` | `(error: unknown) => void`    | Called when the subscription to stop requests fails. The stream continues, but it cannot be stopped                                                               |\n| `onFinish`                | `() => void \\| Promise<void>` | Called after the source stream has ended, also after an error or a stop. Errors thrown by the callback are ignored                                                |\n\n#### Abort controller\n\nYou can always stop a stream. If you do not pass an `abortController`, the library creates one.\n\n```ts\n// Without an abortController: a stop cancels the source stream\nawait context.startStream(toUIMessageStream({ stream: result.stream }), { streamId });\n\n// With abortController: `streamText` sees the signal and aborts the HTTP request\nconst abortController = new AbortController();\nconst result = streamText({ model, messages, abortSignal: abortController.signal });\nawait context.startStream(toUIMessageStream({ stream: result.stream }), {\n  streamId,\n  abortController,\n});\n```\n\nWe recommend that you pass your own `abortController` and give its signal to `streamText`. A stop then aborts the request to the provider directly, and the AI SDK emits its `abort` chunk and calls `onAbort`. Without it, a stop only cancels the source stream, and the cancellation must propagate back to `streamText`.\n\n#### Stop timeout\n\nA stop aborts the `abortController` and waits up to `stopTimeoutMs` for the source to end. This lets `streamText` emit its `abort` chunk, so `onEnd` and `onFinish` get `isAborted: true`. A source that does not end in time is cancelled.\n\nWith `stopTimeoutMs: 0`, or without an `abortController`, a stop cancels the source immediately and `isAborted` is `false`.\n\n```ts\nconst abortController = new AbortController();\nconst result = streamText({ model, messages, abortSignal: abortController.signal });\nconst source = toUIMessageStream({\n  stream: result.stream,\n  onFinish: ({ isAborted, responseMessage }) => {\n    // `isAborted` is `true` after a stop\n  },\n});\nawait context.startStream(source, { streamId, abortController });\n```\n\n### `resumeStream`\n\nResumes an existing stream. The returned stream first replays all chunks produced so far and then continues with the new chunks. Pass `generationId` to resume a specific generation. Omit it to resume the current generation of the stream id. Returns `null` when there is nothing to resume.\n\n```ts\nasync function resumeMessage(chatId: string) {\n  const stream = await context.resumeStream({ streamId: chatId });\n\n  // If no stream exists, return early\n  if (!stream) {\n    console.log(\"No active stream to resume\");\n    return;\n  }\n\n  // Return resumed stream to client\n  return stream;\n}\n```\n\nThe result is `null` in three cases. The library does not tell you which case applies:\n\n- no stream was started with that id\n- the stream has already completed\n- the producer died or was stopped before the stream completed\n\nIf your application must know whether a stream completed or was cut off, store that information together with the message.\n\n### `stopStream`\n\nStops a running stream. Pass `generationId` to stop a specific generation. Omit it to stop the current generation of the stream id.\n\n```ts\nasync function stopMessage(chatId: string, messageId?: string) {\n  await context.stopStream({ streamId: chatId, generationId: messageId });\n}\n```\n\nA stop request with a generation id can arrive before that generation listens for stop requests, or before it starts. Both included adapters store the request, and the generation stops as soon as it starts to listen. A stop request without a generation id applies to the generation that is current at the time of the request. If there is no current generation, the request is not stored and has no effect.\n\nSee [Identifiers](#identifiers) for details about stream ids and generation ids.\n\n## Identifiers\n\nA stream has two ids. If you do not pass an id, `startStream` generates it and returns both ids.\n\n- **`streamId`** identifies the stream. A stream can run more than once. Each run is a generation.\n- **`generationId`** identifies one generation of the stream.\n\nEach call to `startStream` creates a new generation. The generation that started last is the current generation of the stream id.\n\n> [!TIP]\n> Most applications only need the stream id. Use an id that the client already knows, for example the chat id or the session id. A client that reconnects can then call `resumeStream({ streamId: chatId })` without any other id.\n\n### Stream ID\n\nWith only a stream id, `resumeStream` and `stopStream` apply to the current generation.\n\n```\nstartStream({ streamId: \"chat-1\" })   ──▶ generation A\nstartStream({ streamId: \"chat-1\" })   ──▶ generation B (current)\n\nresumeStream({ streamId: \"chat-1\" })  ──▶ generation B\nstopStream({ streamId: \"chat-1\" })    ──▶ generation B\n```\n\nIf you start a stream with a stream id that is already in use, the new generation becomes the current generation. A resume then returns only the chunks of the new generation. Generation A continues to run until its source stream ends, but the stream id alone no longer reaches it. When generation A ends, its cleanup does not affect generation B.\n\n### Generation ID\n\nWith a stream id and a generation id, `resumeStream` and `stopStream` apply only to that generation, also after a newer generation has started.\n\n```\nstartStream({ streamId: \"chat-1\", generationId: \"msg-1\" })   ──▶ generation msg-1\nstartStream({ streamId: \"chat-1\", generationId: \"msg-2\" })   ──▶ generation msg-2 (current)\n\nresumeStream({ streamId: \"chat-1\" })                         ──▶ generation msg-2\nresumeStream({ streamId: \"chat-1\", generationId: \"msg-1\" })  ──▶ generation msg-1\nstopStream({ streamId: \"chat-1\", generationId: \"msg-1\" })    ──▶ generation msg-1\n```\n\nUse a generation id when generations of the same stream id can overlap. Example: a client stops a generation while a new generation with the same stream id starts. The stop request must stop only the old generation, not the new one.\n\nUse an id that the client already knows, for example the id of the user message that it sent. The client can then resume or stop that generation without a server-generated id.\n\n> [!WARNING]\n> A generation id must be unique within its stream id. Do not use it again, for example when you retry the same message. The library does not check this, and resume and stop do not work correctly for a generation id that is used twice.\n\n## Adapters\n\n| Adapter                   | Import                                    | Where chunks live                   | Resume needs a live producer | Cost model                       |\n| ------------------------- | ----------------------------------------- | ----------------------------------- | ---------------------------- | -------------------------------- |\n| [Redis](#redis)           | `ai-resumable-stream/adapters/redis`      | Memory of the producing process     | Yes                          | Your Redis instance              |\n| [S3 Express](#s3-express) | `ai-resumable-stream/adapters/s3-express` | One appendable object in the bucket | No                           | One billed `PutObject` per flush |\n\n### Redis\n\nThis adapter requires two Redis clients, because pub/sub needs a separate connection. The library connects the clients if they are not connected yet, but it never disconnects them. You manage the connection lifecycle in your application and can reuse the clients for all streams.\n\n> [!NOTE]\n> You need to install `redis` to use this adapter. Both v5 and v6 are supported.\n\n```ts\nimport { createClient } from \"redis\";\nimport { createRedisAdapter } from \"ai-resumable-stream/adapters/redis\";\nimport { createResumableUIMessageStream } from \"ai-resumable-stream/ai-sdk\";\n\n// Important: publisher and subscriber must be separate clients\nconst publisher = createClient({ url: process.env.REDIS_URL });\nconst subscriber = createClient({ url: process.env.REDIS_URL });\n\nconst adapter = createRedisAdapter({ publisher, subscriber });\n\nconst context = createResumableUIMessageStream({\n  adapter,\n});\n```\n\n| Option       | Type          | Required | Description                                                                                               |\n| ------------ | ------------- | -------- | --------------------------------------------------------------------------------------------------------- |\n| `publisher`  | `RedisClient` | Yes      | Client that sends commands and publishes messages                                                         |\n| `subscriber` | `RedisClient` | Yes      | Client for subscriptions. Must be a separate client, because a subscribed connection cannot send commands |\n| `keyPrefix`  | `string`      | No       | Prefix for all keys and channels. Defaults to `ai-resumable-stream`                                       |\n\n> [!IMPORTANT]\n> **A stream is only resumable while its producer is alive.**\n\nSee [docs/adapter-redis.md](https://github.com/zirkelc/ai-resumable-stream/blob/main/docs/adapter-redis.md) for how it works, a sequence diagram and a full example.\n\n### S3 Express\n\nThis adapter requires an [S3 Express One Zone](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-one-zone.html) directory bucket and an `S3Client`. You create the client, so you control the credentials, the region and the retry behaviour.\n\nThe chunks are stored in the bucket, not in the memory of the producing process. A resume reads the chunks from the bucket. It does not need a response from the producing process, and any instance with access to the bucket can serve it.\n\n> [!NOTE]\n> You need to install `@aws-sdk/client-s3` to use this adapter.\n\n```ts\nimport { S3Client } from \"@aws-sdk/client-s3\";\nimport { createS3ExpressAdapter } from \"ai-resumable-stream/adapters/s3-express\";\n\nconst adapter = createS3ExpressAdapter({\n  client: new S3Client({ region: `us-east-1` }),\n  bucket: `my-streams--use1-az4--x-s3`,\n});\n```\n\n| Option                 | Type       | Default               | Description                                                                                         |\n| ---------------------- | ---------- | --------------------- | --------------------------------------------------------------------------------------------------- |\n| `client`               | `S3Client` |                       | The S3 client. You create and configure it                                                          |\n| `bucket`               | `string`   |                       | A directory bucket in an Availability Zone                                                          |\n| `prefix`               | `string`   | `ai-resumable-stream` | Prefix for all keys. Use it as the filter of the lifecycle rule                                     |\n| `flushIntervalMs`      | `number`   | `250`                 | Maximum time that chunks stay in memory before they are written                                     |\n| `batchSize`            | `number`   | `50`                  | Number of buffered chunks that triggers a write                                                     |\n| `resumePollIntervalMs` | `number`   | `500`                 | Interval at which a resumed stream checks for new chunks                                            |\n| `stopPollIntervalMs`   | `number`   | `1000`                | Interval at which a producer checks for a stop request                                              |\n| `heartbeatMs`          | `number`   | `5000`                | Interval at which an idle producer records that it is alive                                         |\n| `deadAfterMs`          | `number`   | `30000`               | Time without writes after which a producer is considered dead. Must be at least twice `heartbeatMs` |\n\n> [!WARNING]\n> The adapter does not delete objects when a stream finishes. Set a lifecycle expiration rule on the bucket with `prefix` as the filter, and grant `s3express:CreateSession` with `ReadWrite` to `lifecycle.s3.amazonaws.com` in the bucket policy. Without this grant, the rule does nothing and the objects are never deleted. See [Cleanup](https://github.com/zirkelc/ai-resumable-stream/blob/main/docs/adapter-s3-express.md#cleanup).\n\nSee [docs/adapter-s3-express.md](https://github.com/zirkelc/ai-resumable-stream/blob/main/docs/adapter-s3-express.md) for how it works, the storage layout, a sequence diagram, cost and liveness details, and a full example.\n\n### Custom `StreamAdapter`\n\nImplement the `StreamAdapter` interface against the store you want and pass the object as `adapter`.\n\n```ts\nimport type { StreamAdapter } from \"ai-resumable-stream\";\n\nconst adapter: StreamAdapter = {\n  createStream({ streamId, generationId, chunks, waitUntil }) { ... },\n  resumeStream({ streamId, generationId }) { ... },\n  requestStop({ streamId, generationId }) { ... },\n  onStopRequested({ streamId, generationId, onStop }) { ... },\n};\n```\n\n- **`createStream`** is called when a stream starts. It receives the stream id, the generation id and a `ReadableStream<string>` of encoded chunks. First, it discards any state left over from an earlier stream with the same id, so a reused id never replays old chunks. Then it makes `generationId` the current generation of the stream id and consumes the chunks in the background until the stream ends. The returned promise resolves as soon as the stream can be resumed, not when it is complete.\n- **`resumeStream`** is called when a client reconnects. It returns every chunk produced so far, in the original order, and then continues with the new chunks until the stream ends. With a `generationId`, it resumes that generation. Without one, it resumes the current generation of the stream id. It returns `null` when the id is unknown, the stream has already finished, or its data has expired.\n- **`requestStop`** is called by the process that wants to stop a stream. With a `generationId`, it stops that generation. Without one, it stops the current generation of the stream id. It must not fail when the stream is unknown or already finished, and it should store the request if the generation does not listen yet.\n- **`onStopRequested`** is called by the producing process when a stream starts. It registers a listener for one generation that calls `onStop` when a stop signal arrives. If a stop request was stored before the listener was registered, the listener also calls `onStop` for that request. It returns a function that removes only this listener.\n\nThe adapter must treat the chunks as opaque strings. If your store needs a special format, for example escaping or a length prefix, the adapter adds it on write and removes it on read.\n\n`requestStop` and `onStopRequested` usually run in different processes, so the stop signal must also go through the store, for example by a subscription or a poll. Store the stop signal per generation, so that a stop never reaches a different generation of the same stream id. `startStream` does not await `onStopRequested`. If `onStopRequested` rejects, the only effect is that the generation cannot be stopped.\n\nThe tests in [`src/__tests__/conformance-suite.ts`](./src/__tests__/conformance-suite.ts) run against both included adapters. They cover the behaviour that an adapter must have:\n\n- a resume replays the past chunks and then continues with the new chunks\n- chunks are still stored after the client disconnects\n- a reused stream id does not replay the chunks of the previous stream\n- a stop also ends the stream of a client that resumed\n- a stop with a generation id only stops that generation, also when it was requested before that generation started\n- an older generation can be resumed while a newer generation is current\n\n## Advanced\n\n### Chunk types\n\nThe root export works with any chunk type and does not depend on `ai`. The `ai-sdk` subpath export is preconfigured for the `UIMessageChunk` type of the AI SDK:\n\n```ts\nimport { createResumableUIMessageStream } from \"ai-resumable-stream/ai-sdk\";\n\nconst context = createResumableUIMessageStream({ adapter });\n```\n\nThis is the same as `createResumableStream({ adapter, codec: uiMessageChunkCodec })`. The codec validates each chunk when it is read. An invalid chunk, for example one written by an older version of your application, is dropped and does not fail the resume.\n\nFor other chunk types, pass your own `StreamCodec`:\n\n```ts\nimport { createResumableStream, type StreamCodec } from \"ai-resumable-stream\";\n\nconst codec: StreamCodec<MyChunk> = {\n  encode: (chunk) => JSON.stringify(chunk),\n  decode: (data) => JSON.parse(data) as MyChunk,\n};\n\nconst context = createResumableStream({ adapter, codec });\n```\n\n`decode` can return `undefined` to drop a chunk that it cannot decode. One invalid chunk then does not fail the full resume.\n\n### Serverless\n\nThe library continues to store chunks after the response has been sent. On serverless runtimes, pass `waitUntil` so that the runtime keeps the function alive until all chunks are stored:\n\n```ts\nimport { waitUntil } from \"@vercel/functions\";\n\nconst context = createResumableUIMessageStream({ adapter, waitUntil });\n```\n\n### Finish\n\nThe `onFinish` callback is invoked after the source stream has ended and the adapter stream was closed. Use it for cleanup tasks like removing the active stream ID from the database. Errors thrown by `onFinish` are caught and ignored.\n\n```typescript\nconst { stream } = await context.startStream(toUIMessageStream({ stream: result.stream }), {\n  onFinish: async () => {\n    await saveChat({ chatId, activeStreamId: null });\n  },\n});\n```\n\n## Examples\n\nThe [`examples/`](./examples) folder contains one runnable example for each adapter. Each example starts a stream, disconnects the client before the stream ends, resumes the stream by its id, and then stops a second stream with `stopStream`.\n\n```sh\npnpm example:redis        # starts a temporary Redis server\npnpm example:s3-express   # uses an in-memory bucket\n```\n\n### tRPC\n\nServer-side procedures to send, resume and stop. The chat id is used as the stream id, so you do not have to store a separate stream id.\n\n```ts\n// server/router.ts\nimport { z } from \"zod\";\nimport { streamText, toUIMessageStream, type UIMessage, type UIMessageChunk } from \"ai\";\nimport { createClient } from \"redis\";\nimport { createResumableUIMessageStream } from \"ai-resumable-stream/ai-sdk\";\nimport { createRedisAdapter } from \"ai-resumable-stream/adapters/redis\";\nimport { publicProcedure, router } from \"./trpc\";\n\nconst publisher = createClient({ url: process.env.REDIS_URL });\nconst subscriber = createClient({ url: process.env.REDIS_URL });\n\nconst context = createResumableUIMessageStream({\n  adapter: createRedisAdapter({ publisher, subscriber }),\n});\n\nexport const appRouter = router({\n  sendMessage: publicProcedure\n    .input(z.object({ chatId: z.string(), message: z.custom<UIMessage>() }))\n    .mutation(async function* ({ input }): AsyncGenerator<UIMessageChunk> {\n      const { chatId, message } = input;\n\n      const abortController = new AbortController();\n\n      const result = streamText({\n        model: openai(`gpt-4o`),\n        messages: [message],\n        abortSignal: abortController.signal,\n      });\n\n      const { stream } = await context.startStream(toUIMessageStream({ stream: result.stream }), {\n        streamId: chatId,\n        abortController,\n        onFinish: async () => {\n          await saveAssistantMessage(chatId, await result.text);\n        },\n      });\n\n      yield* stream;\n    }),\n\n  resumeMessage: publicProcedure.input(z.object({ chatId: z.string() })).mutation(async function* ({\n    input,\n  }): AsyncGenerator<UIMessageChunk> {\n    const stream = await context.resumeStream({ streamId: input.chatId });\n    if (!stream) return;\n\n    yield* stream;\n  }),\n\n  stopMessage: publicProcedure\n    .input(z.object({ chatId: z.string() }))\n    .mutation(async ({ input }) => {\n      await context.stopStream({ streamId: input.chatId });\n\n      return { success: true };\n    }),\n});\n```\n\n## API Reference\n\n### `createResumableStream`\n\n```ts\nfunction createResumableStream<CHUNK>(options: CreateResumableStreamOptions<CHUNK>): {\n  startStream: (\n    source: ReadableStream<CHUNK>,\n    options?: StartStreamOptions,\n  ) => Promise<StartStreamResult<CHUNK>>;\n  resumeStream: (options: ResumeStreamOptions) => Promise<AsyncIterableStream<CHUNK> | null>;\n  stopStream: (options: StopStreamOptions) => Promise<void>;\n};\n\ntype CreateResumableStreamOptions<CHUNK> = {\n  adapter: StreamAdapter;\n  codec: StreamCodec<CHUNK>;\n  waitUntil?: (promise: Promise<unknown>) => void;\n  generateId?: () => string;\n};\n\ntype StartStreamOptions = {\n  streamId?: string;\n  generationId?: string;\n  abortController?: AbortController;\n  stopTimeoutMs?: number;\n  onStopSubscriptionError?: (error: unknown) => void;\n  onFinish?: () => void | Promise<void>;\n};\n\ntype StartStreamResult<CHUNK> = {\n  streamId: string;\n  generationId: string;\n  stream: AsyncIterableStream<CHUNK>;\n};\n\ntype ResumeStreamOptions = {\n  streamId: string;\n  generationId?: string;\n};\n\ntype StopStreamOptions = {\n  streamId: string;\n  generationId?: string;\n};\n```\n\n| Option       | Type                 | Required | Description                                                                                                                                                  |\n| ------------ | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `adapter`    | `StreamAdapter`      | Yes      | Stores the chunks and transports stop requests                                                                                                               |\n| `codec`      | `StreamCodec<CHUNK>` | Yes      | Converts chunks to strings for the adapter, and back                                                                                                         |\n| `waitUntil`  | `(promise) => void`  | No       | Keeps the process alive until all chunks are stored. Only needed on serverless runtimes                                                                      |\n| `generateId` | `() => string`       | No       | Generates the stream id and the generation id when you do not pass them to `startStream`. Must return a new id on each call. Defaults to `crypto.randomUUID` |\n\n### `createResumableUIMessageStream`\n\nSame as `createResumableStream`, with `codec` set to `uiMessageChunkCodec`.\n\n```ts\nfunction createResumableUIMessageStream(\n  options: Omit<CreateResumableStreamOptions<UIMessageChunk>, `codec`>,\n): { startStream; resumeStream; stopStream };\n```\n\n### `StreamAdapter`\n\n```ts\ntype StreamAdapter = {\n  createStream(options: {\n    streamId: string;\n    generationId: string;\n    chunks: ReadableStream<string>;\n    waitUntil?: (promise: Promise<unknown>) => void;\n  }): Promise<void>;\n  resumeStream(options: {\n    streamId: string;\n    generationId?: string;\n  }): Promise<ReadableStream<string> | null>;\n  requestStop(options: { streamId: string; generationId?: string }): Promise<void>;\n  onStopRequested(options: {\n    streamId: string;\n    generationId: string;\n    onStop: () => void;\n  }): Promise<() => void>;\n};\n```\n\n### `StreamCodec`\n\n```ts\ntype StreamCodec<CHUNK> = {\n  encode(chunk: CHUNK): string;\n  decode(data: string): CHUNK | undefined | Promise<CHUNK | undefined>;\n};\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}