Supyagent
SDK (Node.js)

Persistence

Save and load chat history with the Prisma adapter from @supyagent/sdk/prisma.

Persistence

The @supyagent/sdk/prisma sub-package provides a ChatAdapter for persisting chat messages using Prisma. It works with SQLite (local dev) and PostgreSQL (production).

Setup

1. Install Prisma

npm install @prisma/client prisma

2. Add the Schema

Add Chat and Message models to your Prisma schema:

prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "sqlite"   // or "postgresql"
  url      = env("DATABASE_URL")
}

model Chat {
  id        String    @id @default(cuid())
  title     String    @default("New Chat")
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  messages  Message[]
}

model Message {
  id        String   @id @default(cuid())
  chatId    String
  chat      Chat     @relation(fields: [chatId], references: [id], onDelete: Cascade)
  role      String
  parts     String
  metadata  String?
  createdAt DateTime @default(now())

  @@index([chatId])
}

3. Generate and Push

npx prisma generate
npx prisma db push

Usage

lib/prisma.ts
import { PrismaClient } from '@prisma/client';

export const prisma = new PrismaClient();
app/api/chat/route.ts
import { createPrismaAdapter } from '@supyagent/sdk/prisma';
import { prisma } from '@/lib/prisma';

const adapter = createPrismaAdapter(prisma);

ChatAdapter Interface

The adapter implements the ChatAdapter interface:

adapter.saveChat(chatId, messages)

Save or update a chat with its current messages. Uses upsert — safe to call repeatedly with the full message list.

await adapter.saveChat(chatId, messages);

The title is automatically extracted from the first user message (truncated to 100 characters).

adapter.loadChat(chatId)

Load all messages for a chat, ordered by creation time.

const messages = await adapter.loadChat(chatId);
// => UIMessageLike[]

adapter.listChats()

List all chats (most recently updated first).

const chats = await adapter.listChats();
// => Array<{ id, title, createdAt, updatedAt }>

adapter.deleteChat(chatId)

Delete a chat and all its messages (cascade).

await adapter.deleteChat(chatId);

Using with streamText

The typical pattern is to save messages on both step completion and final completion:

app/api/chat/route.ts
const result = streamText({
  model: yourModel,
  messages,
  tools,
});

return result.toUIMessageStreamResponse({
  originalMessages: messages,
  onStepFinish: async ({ messages: updated }) => {
    await adapter.saveChat(chatId, updated);
  },
  onFinish: async ({ messages: updated }) => {
    await adapter.saveChat(chatId, updated);
  },
});

Building API Routes for Chat Management

app/api/chats/route.ts
import { createPrismaAdapter } from '@supyagent/sdk/prisma';
import { prisma } from '@/lib/prisma';

const adapter = createPrismaAdapter(prisma);

// List all chats
export async function GET() {
  const chats = await adapter.listChats();
  return Response.json(chats);
}
app/api/chats/[id]/route.ts
// Load a specific chat
export async function GET(req: Request, { params }: { params: { id: string } }) {
  const messages = await adapter.loadChat(params.id);
  return Response.json(messages);
}

// Delete a chat
export async function DELETE(req: Request, { params }: { params: { id: string } }) {
  await adapter.deleteChat(params.id);
  return new Response(null, { status: 204 });
}

PostgreSQL

Switch from SQLite to PostgreSQL by changing the datasource in your Prisma schema:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

The adapter works identically with both databases — no code changes needed.

What's Next