/** * Pure functions that translate inbound Ably events into store mutations. * * No DOM, no fetch, no Ably types — only the wire shapes of the messages * Ably delivers to us. Easy to unit-test against a fake `BaseRealtime`. * * See: docs/modules/chat/widget-architecture.md (Message-arrival contract) */ import type { ChatMessage } from "../../../utils/globals"; import type { ChatStore } from "../store/chat-store"; export interface NotificationPayload { type: string; channel_id: string; data?: Record; } export interface TypingPayload { sender_type: string; sender_name: string | null; is_typing: boolean; } export interface ReadCursorPayload { reader_type: "user" | "agent"; reader_id: string | null; read_at: string; } /** * Insert or replace a message in the array. Pure id-based reconciliation: * because the SDK mints the user message id client-side and the AI bubble * adopts the persisted id from the streaming response header, every * inbound payload arrives with the same identity the bubble already * displays — no content+sender heuristic, no temp-id matching. * * Two special cases preserved: * * 1. Streaming `_widgets` metadata: if the local bubble accumulated * `_widgets` (from inline tool-call markers) and the Ably echo doesn't * carry an authoritative `widgets`, we merge the local `_widgets` * into the persisted row so the form / button doesn't disappear. * * 2. Legacy `temp-ai-*` fallback: very old servers (or a stream that * failed to pre-create its row) leave the SDK with a `temp-ai-*` id. * When a persisted AI message arrives we swap the first such row so * the user still sees a single bubble. * * Returns the **same array reference** when nothing actually changed * (incoming matches an existing row by deep field equality). This lets * memoised components (MessageBubble) skip re-renders on duplicate * echoes, which is critical for streaming throughput where the same * message id can be re-published multiple times. */ export declare function upsertMessage(messages: ChatMessage[], incoming: ChatMessage): ChatMessage[]; /** Returns true when the message id is already in the array. */ export declare function hasMessage(messages: ChatMessage[], id: string): boolean; /** * Apply an inbound chat message to the store. Performs the upsert, updates * unread counts (skipping when the user is currently viewing this channel), * and clears the typing indicator. * * `currentUserDistinctId` is consulted only to suppress duplicate side * effects on the user's own echoes — the upsert itself is pure id-based. * * `onAgentMessageWhileOpen` is invoked when an agent/AI message arrives * while the widget is open and viewing this channel — used by the * controller to schedule `markAsRead`. * * `onTrack` is invoked for agent/AI messages so the controller can forward * to the SDK's `capture()` pipeline and `config.onMessageReceived` callback. */ export declare function applyMessage(store: ChatStore, incoming: ChatMessage, currentUserDistinctId: string | null, callbacks: { onAgentMessageWhileOpen: () => void; onTrack: (m: ChatMessage) => void; onMessageDispatched: (m: ChatMessage) => void; }): void; /** * Handle project-level notifications (new channels, channel updates, closes). * Updates the channel list and total badge count in real time without * touching `messages` — message-list updates flow through the per-channel * Ably channel. */ export declare function applyNotification(store: ChatStore, notification: NotificationPayload, callbacks: { onNewChannel: () => void; }): void; /** * Apply an inbound typing event. The user's own typing events are echoed * back from Ably and must be ignored here so the indicator only reflects * the other party. */ export declare function applyTyping(store: ChatStore, event: TypingPayload, callbacks: { onTyping: (isTyping: boolean, senderName: string) => void; }): void; /** * Apply an agent read-cursor advance. User cursors are not needed in the * widget because the widget *is* the user. */ export declare function applyReadCursor(store: ChatStore, event: ReadCursorPayload): void;