Files
vercel__chat/examples
OSS Polar Bear 169788b65a feat(chat): introduce unified History API with user, thread, and chan… (#592)
Adds `bot.history` as the canonical entry point for message history,
with three scopes: `user`, `thread`, and `channel`. `bot.transcripts`
stays as a deprecated alias, so nothing breaks.

## Why

History access was spread across `bot.transcripts`, `thread.messages` /
`thread.allMessages`, and per-adapter calls. `bot.history` puts the
promise-based read paths in one place, and the AI tools
(`fetchMessages`, `fetchChannelMessages`, `listThreads`) now route
through it.

## User scope

Cross-platform per-user persistence, identical in surface to
`bot.transcripts`:

```typescript
const bot = new Chat({
  adapters: { slack, telegram },
  state,
  history: {
    user: {
      identity: ({ author }) => author.email ?? null,
      retention: "30d",
      maxPerUser: 200,
    },
  },
});

await bot.history.user.append(thread, message);
const entries = await bot.history.user.list({ userKey, limit: 20 });
await bot.history.user.delete({ userKey });
```

The new `toPromptEntries` helper turns those entries into `{ role,
content }` messages for an LLM call:

```typescript
import { toPromptEntries } from "chat";

const entries = await bot.history.user.list({ userKey });
const { text } = await generateText({
  model,
  messages: toPromptEntries(entries),
});
```

## Thread scope

Single-page reads and an auto-paginating generator:

```typescript
// One page, newest messages by default
const { messages, nextCursor } = await bot.history.thread.list(thread.id, {
  limit: 20,
});

// Everything, oldest first, pagination handled for you
for await (const msg of bot.history.thread.collect(thread.id, { limit: 50 })) {
  console.log(msg.text);
}
```

## Channel scope

```typescript
// Top-level channel messages (not thread replies)
const { messages } = await bot.history.channel.listMessages("slack:C123", {
  limit: 20,
});

// Thread listings
const { threads } = await bot.history.channel.listThreads("slack:C123");

// Threads together with a page of messages each
const result = await bot.history.channel.listThreadsWithMessages("slack:C123", {
  maxThreads: 5,
  messagesPerThread: 10,
});
```

## Semantics

The read paths are strict about where data comes from:

- The adapter named in the ID prefix must be registered. A typo'd or
unknown prefix throws instead of reading as an empty conversation.
- The SDK-side `ThreadHistoryCache` only serves adapters that persist
history there (`persistThreadHistory: true`, e.g. Telegram, WhatsApp).
For every other adapter the platform response is authoritative, so an
empty page is a real empty page, and a `cursor` always returns the
adapter's response as-is.
- Cache reads honor the same windows as adapter reads: backward
(default) gives the newest N, forward the oldest N, and `collect()`
yields the oldest N on both paths.
- `channel.listMessages` throws a capability error on adapters without
`fetchChannelMessages` (persisting adapters are served from the
channel-keyed cache instead), and `listThreadsWithMessages` fetches
per-thread pages through `history.thread.list` a few threads at a time
to stay inside platform rate limits.

## Migration

```typescript
// Before
const bot = new Chat({
  identity: ({ author }) => author.email ?? null,
  transcripts: { retention: "30d", maxPerUser: 200 },
});
await bot.transcripts.append(thread, msg);

// After
const bot = new Chat({
  history: {
    user: {
      identity: ({ author }) => author.email ?? null,
      retention: "30d",
      maxPerUser: 200,
    },
  },
});
await bot.history.user.append(thread, msg);
```

You can migrate one field at a time: when both `history.user` and the
legacy `transcripts` block are set they merge, with `history.user`
winning field by field, so settings left on `transcripts` keep applying
until you move them. `TranscriptEntry` is deprecated in favour of
`HistoryEntry` (also exported as `UserHistoryEntry`); all deprecated
names keep working in the current major version.

## Included

- New `packages/chat/src/history/` module with unit tests for every
scope
- AI tools rewired to `bot.history`, keeping their scope guards
- The nextjs example uses the new APIs throughout, with Thread History
and Channel History test buttons that exercise every scope
- Docs: `/docs/history` guide, `/docs/api/history` reference,
deprecation callouts on the transcripts pages
- Changeset (`minor` for `chat`)

---------

Co-authored-by: Ben Sabic <bensabic@users.noreply.github.com>
2026-08-28 18:47:26 +10:00
..