Skip to content

Latest commit

 

History

History
220 lines (181 loc) · 6.03 KB

File metadata and controls

220 lines (181 loc) · 6.03 KB
title Transcripts
description Cross-platform per-user transcript persistence — configuration, methods, and entry shape.
type reference

bot.transcripts provides per-user message persistence keyed by a stable cross-platform identifier. See the Conversation history guide for usage patterns.

import { Chat } from "chat";

Configuration

transcripts and identity are configured on ChatConfig. Both must be set together — passing transcripts without identity throws at construction.

ChatConfig.transcripts

<TypeTable type={{ retention: { description: 'List TTL, refreshed on every append. Accepts ms or a duration string ("45s", "30m", "6h", "7d"). Omit for no expiry.', type: 'number | DurationString | undefined', }, maxPerUser: { description: 'Hard cap per user. Older entries are evicted on append.', type: 'number', default: '200', }, storeFormatted: { description: 'Persist the mdast formatted field alongside text. Off by default to keep storage small.', type: 'boolean', default: 'false', }, }} />

ChatConfig.identity

identity: (context: IdentityContext) => string | null | Promise<string | null>;

Called once per inbound message during dispatch. The result is attached to the Message instance as message.userKey. Return null to skip persistence for an event.

IdentityContext

<TypeTable type={{ adapter: { description: 'Adapter name (e.g. "slack", "discord").', type: 'string', }, author: { description: 'Message author info.', type: 'Author', }, message: { description: 'The inbound message.', type: 'Message', }, }} />

Methods

Access via bot.transcripts. Throws if transcripts was not configured on the Chat instance.

append

Persist a Message (typically the inbound user message) or an AppendInput (typically a bot reply you just posted).

append(
  thread: Postable,
  message: Message | AppendInput,
  options?: AppendOptions,
): Promise<TranscriptEntry | null>;

When message is a Message, userKey is read from the instance. If it's undefined (the resolver returned null), the call is a no-op and returns null. When message is an AppendInput, options.userKey is required.

AppendInput

<TypeTable type={{ role: { description: 'Role tag for the entry.', type: '"user" | "assistant" | "system"', }, text: { description: 'Plain-text body.', type: 'string', }, formatted: { description: 'Optional mdast AST. Only stored when transcripts.storeFormatted is true.', type: 'FormattedContent | undefined', }, platformMessageId: { description: 'Platform-native message ID, when known.', type: 'string | undefined', }, }} />

AppendOptions

<TypeTable type={{ userKey: { description: 'Required when appending an AppendInput (assistant or system role); ignored when appending a Message.', type: 'string | undefined', }, }} />

list

Returns entries in chronological order (oldest first). When limit is set, returns the newest N entries — still chronologically.

list(query: ListQuery): Promise<TranscriptEntry[]>;

ListQuery

<TypeTable type={{ userKey: { description: 'Cross-platform user key.', type: 'string', }, limit: { description: 'Maximum entries returned. Cannot exceed maxPerUser because that is the storage cap.', type: 'number', default: '50', }, platforms: { description: 'Filter to a subset of adapter names.', type: 'string[] | undefined', }, threadId: { description: 'Filter to a single thread.', type: 'string | undefined', }, roles: { description: 'Filter to specific roles.', type: '("user" | "assistant" | "system")[] | undefined', }, }} />

count

count(query: CountQuery): Promise<number>;

Returns the total number of entries stored under the user key. CountQuery has a single field, userKey: string.

delete

delete(target: { userKey: string }): Promise<{ deleted: number }>;

Wipes every entry stored under the user key. Returns the count that was removed. Single-entry and time-range deletes are not supported — the underlying appendToList primitive can't support them safely under concurrent writes.

TranscriptEntry

Returned by append and list.

<TypeTable type={{ id: { description: 'UUID assigned by the SDK at append time.', type: 'string', }, userKey: { description: 'Cross-platform user key from the IdentityResolver.', type: 'string', }, role: { description: 'Role tag.', type: '"user" | "assistant" | "system"', }, text: { description: 'Plain-text body — canonical for prompt building.', type: 'string', }, formatted: { description: 'mdast AST. Only present when transcripts.storeFormatted is true.', type: 'FormattedContent | undefined', }, platform: { description: 'Originating adapter name.', type: 'string', }, threadId: { description: 'Originating thread ID.', type: 'string', }, platformMessageId: { description: 'Platform-native message ID, when known.', type: 'string | undefined', }, timestamp: { description: 'ms-since-epoch, set at append time on the SDK side.', type: 'number', }, }} />

Storage

Backed by StateAdapter.appendToList / getList / delete. Every built-in state adapter (memory, redis, ioredis, pg) supports these primitives.

Entries are stored under transcripts:user:{userKey} as a capped list. appendToList is atomic, so concurrent inbound messages don't race.

The retention value is applied as the list TTL and refreshed on every append. With retention: "30d", a user who hasn't talked to the bot in 30 days has their transcript expire automatically.