diff --git a/docs.json b/docs.json index 461b5f10..9bfb799a 100644 --- a/docs.json +++ b/docs.json @@ -747,7 +747,8 @@ "group": "AgentKit", "pages": [ "redis/sdks/agentkit/ai-sdk", - "redis/sdks/agentkit/eve" + "redis/sdks/agentkit/eve", + "redis/sdks/agentkit/tanstack-ai" ] }, "redis/sdks/mcp", @@ -892,7 +893,8 @@ "group": "AgentKit", "pages": [ "redis/sdks/agentkit/ai-sdk", - "redis/sdks/agentkit/eve" + "redis/sdks/agentkit/eve", + "redis/sdks/agentkit/tanstack-ai" ] } ] diff --git a/llms-full.txt b/llms-full.txt index b55493f8..0979c170 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -86320,6 +86320,482 @@ Two complete `eve` apps live in the AgentKit repo: +# TanStack AI Persistence, Resumable Streams & Memory with Redis +Source: https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai + +[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat +persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work +inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold +across serverless instances, page reloads, and devices. + +| Import | Plugs into | Feature | +| --- | --- | --- | +| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. | +| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. | +| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. | +| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/docs/redis/search/introduction). | +| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. | +| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). | + +```bash +npm install @upstash/agentkit-tanstack-ai @tanstack/ai +``` + + + AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default. + Pass a `redis` client to any helper to use a different one. + + +## How to persist TanStack AI chats in Redis + +Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence +package: + +```bash +npm install @tanstack/ai-persistence +``` + +```ts +import { chat } from "@tanstack/ai"; +import { withPersistence } from "@tanstack/ai-persistence"; +import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence"; + +const persistence = upstashPersistence(); + +chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] }); +``` + +This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for +chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The +`blobs` store for generated bytes is added when you pass an Upstash Blob bucket: + +```ts +import { Bucket } from "@upstash/blob"; + +const persistence = upstashPersistence({ bucket: Bucket.fromEnv() }); +``` + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. +`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you +store generated files. + +Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first. +Each write is one command or one Lua script, so concurrent instances cannot interleave it. + + + ```ts + upstashPersistence({ + redis, // optional: defaults to Redis.fromEnv() + prefix: "agentkit:tanstack", // optional: base key prefix + messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never) + bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files + }); + ``` + + Pass `bucket` to also store the bytes of generated files (images, audio, video) in + [Upstash Blob](/docs/blob/overall/quickstart). Without it, there is no `blobs` store. + + +## How to resume a TanStack AI stream after a reload + +```ts +import { chat, toServerSentEventsResponse } from "@tanstack/ai"; +import { upstashStream } from "@upstash/agentkit-tanstack-ai"; + +export async function POST(request: Request) { + const stream = chat({ adapter, messages, threadId }); + return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } }); +} +``` + +Every chunk is written to a Redis Stream before it is sent. A client that reconnects with +`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever +instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashStream(request, { + ttlSeconds: 86_400, // optional: how long a run stays resumable + pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks + firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started + }); + ``` + + +## How to use distributed locks with TanStack AI + +```ts +import { withLocks } from "@tanstack/ai/locks"; +import { upstashLocks } from "@upstash/agentkit-tanstack-ai"; + +chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] }); +``` + +`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock +the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread +don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not +serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one +process, `upstashLocks()` coordinates across instances. + +Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the +section's `signal` aborts. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashLocks({ + leaseMs: 30_000, // optional: lease lifetime without renewal + acquireTimeoutMs: 30_000, // optional: how long to wait for a held key + retryDelayMs: 100, // optional: delay between attempts + }); + ``` + + +## How to add long-term memory to TanStack AI + +Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package: + +```bash +npm install @tanstack/ai-memory +``` + +```ts +import { memoryMiddleware } from "@tanstack/ai-memory"; +import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory"; + +chat({ + adapter, + messages, + middleware: [ + memoryMiddleware({ + adapter: upstashMemory(), + // derive these server-side from the session, never from the request body + scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }), + }), + ], +}); +``` + +Before each turn, the most relevant memories for the user's message are added to the system prompt, +labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each +turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashMemory({ + topK: 5, // optional: memories injected per turn + scopeBy: "user", // optional: "user" (across threads) or "thread" + captureUserMessages: true, // optional: store each turn's user message + saveTool: true, // optional: offer the save_memory tool + waitForIndexing: true, // optional: make a save recallable on the very next turn + }); + ``` + + +## How to cache tools and rate limit with TanStack AI + +```ts +import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai"; + +chat({ + adapter, + messages, + tools: [getWeather, sendEmail], + middleware: [ + rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }), + toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }), + ], +}); +``` + +`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only. +`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429 +instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`. + +Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` +from the environment. + +## How to add RAG with TanStack AI + +The schema builder `s` comes from `@upstash/redis`: + +```bash +npm install @upstash/redis +``` + +```ts +import { s } from "@upstash/redis"; +import { createSearchTools } from "@upstash/agentkit-tanstack-ai"; + +const tools = createSearchTools({ + indexName: "products", + schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }), +}); + +chat({ adapter, messages, tools }); +``` + +The tool descriptions are generated from the schema, and the index is created on first use. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + +## Telemetry + +AgentKit adds its package name and version as a header on your Redis client's requests. To turn it +off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper. + + + + Source and README for the package. + + + The framework these backends plug into. + + + +# TanStack AI Persistence, Resumable Streams & Memory with Redis +Source: https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai + +[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat +persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work +inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold +across serverless instances, page reloads, and devices. + +| Import | Plugs into | Feature | +| --- | --- | --- | +| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. | +| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. | +| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. | +| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/docs/redis/search/introduction). | +| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. | +| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). | + +```bash +npm install @upstash/agentkit-tanstack-ai @tanstack/ai +``` + + + AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default. + Pass a `redis` client to any helper to use a different one. + + +## How to persist TanStack AI chats in Redis + +Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence +package: + +```bash +npm install @tanstack/ai-persistence +``` + +```ts +import { chat } from "@tanstack/ai"; +import { withPersistence } from "@tanstack/ai-persistence"; +import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence"; + +const persistence = upstashPersistence(); + +chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] }); +``` + +This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for +chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The +`blobs` store for generated bytes is added when you pass an Upstash Blob bucket: + +```ts +import { Bucket } from "@upstash/blob"; + +const persistence = upstashPersistence({ bucket: Bucket.fromEnv() }); +``` + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. +`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you +store generated files. + +Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first. +Each write is one command or one Lua script, so concurrent instances cannot interleave it. + + + ```ts + upstashPersistence({ + redis, // optional: defaults to Redis.fromEnv() + prefix: "agentkit:tanstack", // optional: base key prefix + messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never) + bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files + }); + ``` + + Pass `bucket` to also store the bytes of generated files (images, audio, video) in + [Upstash Blob](/docs/blob/overall/quickstart). Without it, there is no `blobs` store. + + +## How to resume a TanStack AI stream after a reload + +```ts +import { chat, toServerSentEventsResponse } from "@tanstack/ai"; +import { upstashStream } from "@upstash/agentkit-tanstack-ai"; + +export async function POST(request: Request) { + const stream = chat({ adapter, messages, threadId }); + return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } }); +} +``` + +Every chunk is written to a Redis Stream before it is sent. A client that reconnects with +`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever +instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashStream(request, { + ttlSeconds: 86_400, // optional: how long a run stays resumable + pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks + firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started + }); + ``` + + +## How to use distributed locks with TanStack AI + +```ts +import { withLocks } from "@tanstack/ai/locks"; +import { upstashLocks } from "@upstash/agentkit-tanstack-ai"; + +chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] }); +``` + +`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock +the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread +don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not +serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one +process, `upstashLocks()` coordinates across instances. + +Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the +section's `signal` aborts. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashLocks({ + leaseMs: 30_000, // optional: lease lifetime without renewal + acquireTimeoutMs: 30_000, // optional: how long to wait for a held key + retryDelayMs: 100, // optional: delay between attempts + }); + ``` + + +## How to add long-term memory to TanStack AI + +Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package: + +```bash +npm install @tanstack/ai-memory +``` + +```ts +import { memoryMiddleware } from "@tanstack/ai-memory"; +import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory"; + +chat({ + adapter, + messages, + middleware: [ + memoryMiddleware({ + adapter: upstashMemory(), + // derive these server-side from the session, never from the request body + scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }), + }), + ], +}); +``` + +Before each turn, the most relevant memories for the user's message are added to the system prompt, +labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each +turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashMemory({ + topK: 5, // optional: memories injected per turn + scopeBy: "user", // optional: "user" (across threads) or "thread" + captureUserMessages: true, // optional: store each turn's user message + saveTool: true, // optional: offer the save_memory tool + waitForIndexing: true, // optional: make a save recallable on the very next turn + }); + ``` + + +## How to cache tools and rate limit with TanStack AI + +```ts +import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai"; + +chat({ + adapter, + messages, + tools: [getWeather, sendEmail], + middleware: [ + rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }), + toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }), + ], +}); +``` + +`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only. +`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429 +instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`. + +Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` +from the environment. + +## How to add RAG with TanStack AI + +The schema builder `s` comes from `@upstash/redis`: + +```bash +npm install @upstash/redis +``` + +```ts +import { s } from "@upstash/redis"; +import { createSearchTools } from "@upstash/agentkit-tanstack-ai"; + +const tools = createSearchTools({ + indexName: "products", + schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }), +}); + +chat({ adapter, messages, tools }); +``` + +The tool descriptions are generated from the schema, and the index is created on first use. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + +## Telemetry + +AgentKit adds its package name and version as a header on your Redis client's requests. To turn it +off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper. + + + + Source and README for the package. + + + The framework these backends plug into. + + + # Upstash Redis MCP Source: https://upstash.com/docs/redis/sdks/mcp diff --git a/llms.txt b/llms.txt index aad35283..e5bf8ec3 100644 --- a/llms.txt +++ b/llms.txt @@ -731,6 +731,8 @@ - [Vercel AI SDK Memory, RAG & Chat History with Redis](https://upstash.com/docs/redis/sdks/agentkit/ai-sdk.md): Add long-term memory, RAG, and chat history to the Vercel AI SDK with Upstash Redis — drop-in tools for generateText and streamText, no separate vector database. - [Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework](https://upstash.com/docs/redis/sdks/agentkit/eve.md): Add long-term memory, searchable chat history, RAG, rate limiting, tool caching, and sandboxes to Vercel's Eve agent framework with Upstash Redis — no separate vector database. - [Memory, Chat History, RAG, Rate Limiting & Sandboxes for the Vercel Eve Agent Framework](https://upstash.com/docs/redis/sdks/agentkit/eve.md): Add long-term memory, searchable chat history, RAG, rate limiting, tool caching, and sandboxes to Vercel's Eve agent framework with Upstash Redis — no separate vector database. +- [TanStack AI Persistence, Resumable Streams & Memory with Redis](https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai.md): Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools. +- [TanStack AI Persistence, Resumable Streams & Memory with Redis](https://upstash.com/docs/redis/sdks/agentkit/tanstack-ai.md): Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools. - [Upstash Redis MCP](https://upstash.com/docs/redis/sdks/mcp.md) - [ARCOUNT](https://upstash.com/docs/redis/sdks/py/commands/array/arcount.md): Count the occupied slots in an array. - [ARDEL](https://upstash.com/docs/redis/sdks/py/commands/array/ardel.md): Delete array values without shifting later indexes. diff --git a/redis/sdks/agentkit/tanstack-ai.mdx b/redis/sdks/agentkit/tanstack-ai.mdx new file mode 100644 index 00000000..c8947331 --- /dev/null +++ b/redis/sdks/agentkit/tanstack-ai.mdx @@ -0,0 +1,240 @@ +--- +title: "TanStack AI Persistence, Resumable Streams & Memory with Redis" +sidebarTitle: "TanStack AI" +description: "Production backends for TanStack AI on Upstash Redis — chat persistence, resumable streams, distributed locks, long-term memory, tool caching, rate limiting, and RAG tools." +--- + +[TanStack AI](https://tanstack.com/ai) defines the contracts for an agent's production state — chat +persistence, resumable streaming, locks, memory — and ships in-memory implementations that only work +inside one process. `@upstash/agentkit-tanstack-ai` implements them on Upstash Redis, so they hold +across serverless instances, page reloads, and devices. + +| Import | Plugs into | Feature | +| --- | --- | --- | +| `upstashPersistence` | `withPersistence()`, `withGenerationPersistence()` | Transcripts, runs, human-in-the-loop interrupts, metadata, generation jobs, and generated files. | +| `upstashStream` | `durability` on the response | Resume a stream after a reload, or open the same thread on another device. | +| `upstashLocks` | `withLocks()` | Distributed locks for TanStack AI middleware, such as its sandbox setup. | +| `upstashMemory` | `memoryMiddleware()` | Long-term memory ranked in [Redis Search](/redis/search/introduction). | +| `toolCache`, `rateLimit` | `middleware` | Skip repeated tool calls; throttle users before the model runs. | +| `createSearchTools` | `tools` | `search` / `aggregate` / `count` over your own documents (RAG). | + +```bash +npm install @upstash/agentkit-tanstack-ai @tanstack/ai +``` + + + AgentKit reads `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` from the environment by default. + Pass a `redis` client to any helper to use a different one. + + +## How to persist TanStack AI chats in Redis + +Persistence plugs into TanStack AI's `withPersistence()` middleware, which comes from its persistence +package: + +```bash +npm install @tanstack/ai-persistence +``` + +```ts +import { chat } from "@tanstack/ai"; +import { withPersistence } from "@tanstack/ai-persistence"; +import { upstashPersistence } from "@upstash/agentkit-tanstack-ai/persistence"; + +const persistence = upstashPersistence(); + +chat({ adapter, messages, threadId, middleware: [withPersistence(persistence)] }); +``` + +This covers every TanStack AI persistence store: `messages`, `runs`, `interrupts`, and `metadata` for +chats, plus `generationRuns` and `artifacts` for one-shot generation jobs such as images or speech. The +`blobs` store for generated bytes is added when you pass an Upstash Blob bucket: + +```ts +import { Bucket } from "@upstash/blob"; + +const persistence = upstashPersistence({ bucket: Bucket.fromEnv() }); +``` + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. +`Bucket.fromEnv()` reads `UPSTASH_BLOB_TOKEN` from the environment, which is only needed when you +store generated files. + +Runs are indexed by thread, so reconnecting to a live run (`findActiveRun`) is an indexed lookup, newest run first. +Each write is one command or one Lua script, so concurrent instances cannot interleave it. + + + ```ts + upstashPersistence({ + redis, // optional: defaults to Redis.fromEnv() + prefix: "agentkit:tanstack", // optional: base key prefix + messagesTtlSeconds: 60 * 60 * 24 * 30, // optional: expire idle transcripts (default: never) + bucket: Bucket.fromEnv(), // optional: Upstash Blob bucket for generated files + }); + ``` + + Pass `bucket` to also store the bytes of generated files (images, audio, video) in + [Upstash Blob](/blob/overall/quickstart). Without it, there is no `blobs` store. + + +## How to resume a TanStack AI stream after a reload + +```ts +import { chat, toServerSentEventsResponse } from "@tanstack/ai"; +import { upstashStream } from "@upstash/agentkit-tanstack-ai"; + +export async function POST(request: Request) { + const stream = chat({ adapter, messages, threadId }); + return toServerSentEventsResponse(stream, { durability: { adapter: upstashStream(request) } }); +} +``` + +Every chunk is written to a Redis Stream before it is sent. A client that reconnects with +`Last-Event-ID` (or `?offset`) replays what it missed and keeps following the live run, whichever +instance serves the request. Without a `Request`, use `upstashStream({ runId, offset })`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashStream(request, { + ttlSeconds: 86_400, // optional: how long a run stays resumable + pollIntervalMs: 150, // optional: how often a caught-up reader checks for new chunks + firstChunkDeadlineMs: 2_000, // optional: how long a join waits for a run that has not started + }); + ``` + + +## How to use distributed locks with TanStack AI + +```ts +import { withLocks } from "@tanstack/ai/locks"; +import { upstashLocks } from "@upstash/agentkit-tanstack-ai"; + +chat({ adapter, messages, middleware: [withLocks(upstashLocks()), withSandbox(sandbox)] }); +``` + +`withLocks` doesn't lock anything by itself. It gives the lock store to later middleware, which lock +the one step they must not run twice: `withSandbox` uses it so two concurrent requests for a thread +don't both create a sandbox, and your own middleware can use it through `getLocks(ctx)`. It does not +serialize whole chat turns. Unlike TanStack's `InMemoryLockStore`, which only works inside one +process, `upstashLocks()` coordinates across instances. + +Each lock is a lease that is renewed while the critical section runs. If the lease is lost, the +section's `signal` aborts. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashLocks({ + leaseMs: 30_000, // optional: lease lifetime without renewal + acquireTimeoutMs: 30_000, // optional: how long to wait for a held key + retryDelayMs: 100, // optional: delay between attempts + }); + ``` + + +## How to add long-term memory to TanStack AI + +Memory plugs into TanStack AI's `memoryMiddleware()`, which comes from its memory package: + +```bash +npm install @tanstack/ai-memory +``` + +```ts +import { memoryMiddleware } from "@tanstack/ai-memory"; +import { upstashMemory } from "@upstash/agentkit-tanstack-ai/memory"; + +chat({ + adapter, + messages, + middleware: [ + memoryMiddleware({ + adapter: upstashMemory(), + // derive these server-side from the session, never from the request body + scope: (ctx) => ({ threadId: ctx.threadId, userId: session.userId }), + }), + ], +}); +``` + +Before each turn, the most relevant memories for the user's message are added to the system prompt, +labelled by where they came from. The model gets a `save_memory` tool for durable facts, and each +turn's user message is captured too. Memory is per user across threads by default, or per thread when the scope has no `userId`. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + + + ```ts + upstashMemory({ + topK: 5, // optional: memories injected per turn + scopeBy: "user", // optional: "user" (across threads) or "thread" + captureUserMessages: true, // optional: store each turn's user message + saveTool: true, // optional: offer the save_memory tool + waitForIndexing: true, // optional: make a save recallable on the very next turn + }); + ``` + + +## How to cache tools and rate limit with TanStack AI + +```ts +import { rateLimit, Ratelimit, toolCache } from "@upstash/agentkit-tanstack-ai"; + +chat({ + adapter, + messages, + tools: [getWeather, sendEmail], + middleware: [ + rateLimit({ limiter: Ratelimit.slidingWindow(10, "60 s"), identifier: userId }), + toolCache({ tools: ["get_weather"], userId, ttlSeconds: 600 }), + ], +}); +``` + +`toolCache` only caches the tools you list — list deterministic, side-effect-free tools only. +`rateLimit` fails the run with `RateLimitExceededError` before the model is called. For an HTTP 429 +instead, call `createRateLimit({ limiter }).limit(userId)` in your route before `chat()`. + +Both middlewares and `createRateLimit` read `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` +from the environment. + +## How to add RAG with TanStack AI + +The schema builder `s` comes from `@upstash/redis`: + +```bash +npm install @upstash/redis +``` + +```ts +import { s } from "@upstash/redis"; +import { createSearchTools } from "@upstash/agentkit-tanstack-ai"; + +const tools = createSearchTools({ + indexName: "products", + schema: s.object({ name: s.string(), price: s.number(), category: s.string().noTokenize() }), +}); + +chat({ adapter, messages, tools }); +``` + +The tool descriptions are generated from the schema, and the index is created on first use. + +Reads `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` from the environment. + +## Telemetry + +AgentKit adds its package name and version as a header on your Redis client's requests. To turn it +off, set `UPSTASH_DISABLE_TELEMETRY`, or pass `enableTelemetry: false` to a helper. + + + + Source and README for the package. + + + The framework these backends plug into. + +