/** * Vercel Chat SDK channel adapter. * * Default `ChannelAdapter` implementation for `human-in-the-loop`. Wraps the * `chat` package (https://github.com/vercel/chat — npm: `chat`) which is a * unified TypeScript SDK that abstracts Slack, Microsoft Teams, Google Chat, * Discord, Telegram, etc. behind a single Thread/Message API. Per-platform * adapters (`@chat-adapter/slack`, `@chat-adapter/teams`, etc.) are installed * separately by the consuming app — Chat SDK itself is provider-agnostic so * the bundle stays small. * * SVO co-design step 7 (aip-gvh0). See * `docs/plans/2026-05-05-svo-co-design.md` § `human-in-the-loop.ChannelAdapter`. * * ## Dispatch * * `dispatch(task, worker)` resolves a thread for the worker and posts the * Task body to it: * * 1. If the Task's metadata carries an existing `chatThreadId`, the adapter * re-acquires that thread via `bot.thread(threadId)` and posts there. * This is how follow-up Tasks reach the same surfaced conversation. * 2. Otherwise, the adapter resolves the Worker to a chat-sdk recipient by * `worker.contacts.slack.user`, `worker.contacts.teams.user`, or * `worker.contacts.discord.user` and opens a DM via `bot.openDM(userId)`. * 3. The Task title and body are formatted as a markdown post; if a `body` * field is absent the post falls back to the Task's `verb` and any * `instruction`/`description` from legacy `HumanRequest`-shaped tasks. * * The returned `Subscription` cancels the dispatched post on `unsubscribe()` * by issuing a follow-up cancellation message in the thread (Chat SDK does * not expose a delete-message API uniformly across all adapters; the * cancellation is therefore semantic, not destructive). * * ## Receive * * `receive(callback)` registers `bot.onSubscribedMessage` so that any * message in a thread we previously dispatched into surfaces as an * `Action` of verb `'replied'`. * * The dispatched Task's Action id is carried in `action.roles.cause` * (per `digital-objects.Action.roles` from `aip-akqb` — cause is a * frame role, not a top-level field). The caller correlates * `roles.cause` back to outstanding Tasks to handle the response. * * Approval-shaped actions (button clicks, slash commands) are handled * separately by `bot.onAction` and are not registered through `receive` * by default — those should be wired in the consuming app where the * specific action ids are known. * * @packageDocumentation */ import type { Worker } from 'digital-workers'; import type { Task } from 'digital-tasks'; import type { ChannelAdapter } from './types.js'; /** * Minimal Chat SDK surface we depend on. * * Mirrored locally so the adapter compiles without `chat`'s types resolving * (the package is a hard runtime dep but its `.d.ts` may not be available * in a stripped-down install). At runtime callers pass a real `Chat` * instance from the `chat` package. */ export interface ChatBotLike { thread(threadId: string): ChatThreadLike; openDM(userId: string): Promise; onSubscribedMessage(handler: (thread: ChatThreadLike, message: ChatMessageLike) => Promise | void): void; } export interface ChatThreadLike { readonly id: string; readonly channelId: string; post(message: string): Promise; } export interface ChatMessageLike { readonly id?: string; readonly text?: string; readonly user?: { id: string; name?: string; } | string; } /** * Options for the Chat SDK adapter. */ export interface ChatSdkAdapterOptions { /** * Pre-constructed `Chat` instance from the `chat` package. * * The adapter does not construct the bot itself because configuration * (adapters, state backend, userName) is application-specific. Pass a * lazily-resolving function to avoid eager construction. */ bot?: ChatBotLike | (() => ChatBotLike | Promise); /** * Optional resolver mapping a `Worker` to a chat-sdk user id. * * Defaults to looking at `worker.contacts.slack.user`, * `worker.contacts.teams.user`, then `worker.contacts.discord.user`. */ resolveUserId?: (worker: Worker) => string | undefined; /** * Optional formatter mapping a `Task` to the markdown body posted to * the thread. Defaults to `### {title}\n\n{body}` with sensible * fallbacks. */ formatTask?: (task: Task) => string; } /** * Build a Chat SDK channel adapter. * * Returns a `ChannelAdapter` with `kind: 'chat-sdk'`. The adapter is * stateless beyond the bound `bot` reference and the in-process * thread-id -> task-id correlation map populated on `dispatch`. */ export declare function chatSdkAdapter(options?: ChatSdkAdapterOptions): ChannelAdapter; /** * Default Chat SDK adapter instance. * * No bot is bound — the consuming app must call `chatSdkAdapter({ bot })` * with a configured `Chat` instance for the adapter to function. This * default exists so that `import { defaultChannelAdapter } from * 'human-in-the-loop'` yields a stable `ChannelAdapter` reference whose * `kind` and method shapes are predictable for type-checking and * registry lookups; it throws on `dispatch`/`receive` until configured. * * For real use, prefer: * * ```ts * import { Chat } from 'chat' * import { createSlackAdapter } from '@chat-adapter/slack' * import { chatSdkAdapter } from 'human-in-the-loop' * * const bot = new Chat({ userName: 'mybot', adapters: { slack: createSlackAdapter() }, state }) * const channel = chatSdkAdapter({ bot }) * ``` */ export declare const defaultChannelAdapter: ChannelAdapter; //# sourceMappingURL=chat-sdk-adapter.d.ts.map