import type { MessageBlock } from "@rakazo/contracts"; type PresentableMessage = { runId?: string; blocks: readonly MessageBlock[]; }; export type UserVisibleMessagesOptions = { /** * Keep `bot_message_sent` / `bot_message_received` rows as compact chips * (web CollaborationMarker; mobile AgentEventLabel). Peer bodies stay hidden. */ includePeerReceipts?: boolean; /** Peer-run ids from `run.trigger === "bot_message"` when receipts may be out of window. */ knownPeerRunIds?: Iterable; /** * Surface a peer-run's own `text` reply (e.g. a delegating bot's summary to the user) * alongside `ask` cards. Defaults to true for chat-thread rendering; set false for * contexts like sidebar previews that should stay ask-only and never echo peer chatter. */ includeDelegatedReplyText?: boolean; }; export function isPeerReceiptBlocks(blocks: readonly MessageBlock[]): boolean { return blocks.some( (block) => block.kind === "bot_message_sent" || block.kind === "bot_message_received", ); } /** Drop peer-run activity/replies; optionally keep sent/received receipt rows. */ export function userVisibleMessages( messages: readonly T[], options: UserVisibleMessagesOptions = {}, ): T[] { const peerRunIds = new Set([ ...(options.knownPeerRunIds ?? []), ...messages .filter((message) => message.blocks.some((block) => block.kind === "bot_message_received")) .flatMap((message) => (message.runId ? [message.runId] : [])), ]); const includePeerReceipts = options.includePeerReceipts === true; return messages.filter((message) => { if (isPeerReceiptBlocks(message.blocks)) return includePeerReceipts; if (!message.runId || !peerRunIds.has(message.runId)) return true; // Keep peer-run ask cards, and (unless the caller opts out) the bot's own text reply. const includeText = options.includeDelegatedReplyText !== false; return message.blocks.some( (block) => block.kind === "ask" || (includeText && block.kind === "text"), ); }); }