Conversation Store
By default, every send() call is stateless — the server calls streamText({ prompt }) with no history. ConversationStore gives the server memory: each request loads the session's prior messages, calls streamText({ messages }), and saves the result back.
How It Works
- Client sends
sessionIdalongside every request - Server loads the session's
ChatHistoryfrom the store - Server calls
streamText({ messages: [...history, newUserMessage] }) - On finish, server appends both messages and saves back
The ChatHistory used server-side is the same class used client-side — only reactivity differs.
Server Setup
// src/hooks.server.ts
import {
createStreamHandler,
MemoryConversationStore,
} from "@aibind/sveltekit/server";
import { models } from "./models.server";
export const handle = createStreamHandler({
models,
conversation: {
store: new MemoryConversationStore(), // 30-minute TTL by default
},
});// app/api/ai/[...path]/route.ts
import {
createStreamHandler,
MemoryConversationStore,
} from "@aibind/nextjs/server";
import { models } from "@/lib/models.server";
const handler = createStreamHandler({
models,
conversation: { store: new MemoryConversationStore() },
});
export const POST = (request: Request) => handler(request);
export const GET = (request: Request) => handler(request);// server/plugins/ai.ts
import {
createStreamHandler,
MemoryConversationStore,
} from "@aibind/nuxt/server";
import { models } from "~/server/models";
const handler = createStreamHandler({
models,
conversation: { store: new MemoryConversationStore() },
});
export default defineNitroPlugin((nitro) => {
nitro.router.use("/__aibind__/**", (event) => handler(event.node.req as any));
});// src/server/ai.ts
import {
createStreamHandler,
MemoryConversationStore,
} from "@aibind/solidstart/server";
import { models } from "~/server/models";
export const handler = createStreamHandler({
models,
conversation: { store: new MemoryConversationStore() },
});// src/routes/api/ai/$.ts
import {
createStreamHandler,
MemoryConversationStore,
} from "@aibind/tanstack-start/server";
import { models } from "~/lib/models.server";
const handler = createStreamHandler({
models,
conversation: { store: new MemoryConversationStore() },
});
export async function POST({ request }: { request: Request }) {
return handler(request);
}Custom Store (Redis, Postgres, KV, ...)
Implement the ConversationStore interface:
import type { ConversationStore, ConversationMessage } from "@aibind/core";
import type { ChatHistory } from "@aibind/sveltekit/history";
class RedisConversationStore implements ConversationStore {
async load(sessionId: string): Promise<ChatHistory<ConversationMessage>> {
const json = await redis.get(`conv:${sessionId}`);
return json ? ChatHistory.fromJSON(json) : new ChatHistory();
}
async save(
sessionId: string,
chat: ChatHistory<ConversationMessage>,
): Promise<void> {
await redis.setex(`conv:${sessionId}`, 1800, chat.toJSON());
}
async delete(sessionId: string): Promise<void> {
await redis.del(`conv:${sessionId}`);
}
}Sliding Window
Limit context to the last N message pairs:
createStreamHandler({
models,
conversation: {
store: new MemoryConversationStore(),
maxMessages: 20, // keep last 20 messages
},
});Client Setup
Pass sessionId once when creating the stream. All subsequent send() calls automatically include it.
<script lang="ts">
import { Stream } from "@aibind/sveltekit";
const stream = new Stream({
model: "fast",
sessionId: crypto.randomUUID(), // generate once per conversation
});
</script>
<button onclick={() => stream.send("What is 2+2?")}>Send</button>
<button onclick={() => stream.send("What did I just ask?")}>Ask again</button>
<p>{stream.text}</p>"use client";
import { useStream } from "@aibind/nextjs";
import { useState } from "react";
const SESSION_ID = crypto.randomUUID();
export default function Chat() {
const { text, loading, send } = useStream({
model: "fast",
sessionId: SESSION_ID,
});
return (
<div>
<button onClick={() => send("What is 2+2?")}>Send</button>
<button onClick={() => send("What did I just ask?")}>Ask again</button>
<p>{text}</p>
</div>
);
}<script setup lang="ts">
import { useStream } from "@aibind/nuxt";
const { text, loading, send } = useStream({
model: "fast",
sessionId: crypto.randomUUID(),
});
</script>
<template>
<button @click="send('What is 2+2?')">Send</button>
<button @click="send('What did I just ask?')">Ask again</button>
<p>{{ text }}</p>
</template>import { useStream } from "@aibind/solidstart";
const SESSION_ID = crypto.randomUUID();
function Chat() {
const { text, loading, send } = useStream({
model: "fast",
sessionId: SESSION_ID,
});
return (
<div>
<button onClick={() => send("What is 2+2?")}>Send</button>
<p>{text()}</p>
</div>
);
}import { useStream } from "@aibind/tanstack-start";
const SESSION_ID = crypto.randomUUID();
function Chat() {
const { text, loading, send } = useStream({
model: "fast",
sessionId: SESSION_ID,
});
return (
<div>
<button onClick={() => send("What is 2+2?")}>Send</button>
<button onClick={() => send("What did I just ask?")}>Ask again</button>
<p>{text}</p>
</div>
);
}Session ID Management
You are responsible for generating and persisting sessionId. Common patterns:
// New conversation per page load
const sessionId = crypto.randomUUID();
// Persistent across reloads
const sessionId =
localStorage.getItem("sessionId") ??
(() => {
const id = crypto.randomUUID();
localStorage.setItem("sessionId", id);
return id;
})();
// Per-user (server-generated, stored in DB)
const sessionId = `user-${userId}-conv-${convId}`;ConversationStore Interface
interface ConversationStore {
/** Load conversation. Returns empty ChatHistory if session not found. */
load(sessionId: string): Promise<ChatHistory<ConversationMessage>>;
/** Persist the conversation. */
save(
sessionId: string,
chat: ChatHistory<ConversationMessage>,
): Promise<void>;
/** Delete a session. */
delete(sessionId: string): Promise<void>;
}
interface ConversationMessage {
role: "user" | "assistant" | "system";
content: string;
}MemoryConversationStore
Built-in store backed by a Map. Suitable for development and single-server deployments.
import { MemoryConversationStore } from "@aibind/core";
const store = new MemoryConversationStore({
ttlMs: 30 * 60 * 1000, // 30 minutes (default)
});Sessions expire automatically after TTL ms of inactivity. The TTL resets on every save().
StreamHandlerConfig Options
createStreamHandler({
models,
conversation: {
store: new MemoryConversationStore(),
maxMessages?: number, // sliding window on active path
compactSystemPrompt?: string, // used by /compact endpoint (see Compacting)
},
});