UNPKG

chat

Version:

Unified chat abstraction for Slack, Teams, Google Chat, and Discord

221 lines (181 loc) 6.18 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](/docs/conversation-history) guide for usage patterns. ```typescript 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 ```typescript 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). ```typescript 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. ```typescript 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 ```typescript count(query: CountQuery): Promise<number>; ``` Returns the total number of entries stored under the user key. `CountQuery` has a single field, `userKey: string`. ### delete ```typescript 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.