import { kody } from 'kody:runtime' const LIST_LIMIT = 100 const DEFAULT_RECENT = 8 const MAX_RECENT = 25 export type RecentThreadInput = { /** Kody inbox thread id. */ threadId?: string thread_id?: string /** How many newest messages to hydrate with bodies. 0 = count only. Defaults to 8. */ limit?: number /** Optional inbox filter. */ inbox_id?: string } export type RecentThreadMessage = { id: string direction?: string thread_id?: string | null from_address?: string | null to_addresses?: Array cc_addresses?: Array subject?: string | null received_at?: string | null sent_at?: string | null created_at?: string text_body?: string | null html_body?: string | null [key: string]: unknown } export type RecentThreadResult = { threadId: string count: number limit: number capped: boolean messages: RecentThreadMessage[] } function stamp(row: RecentThreadMessage): number { const raw = row.received_at || row.sent_at || row.created_at || '' const ms = Date.parse(String(raw)) return Number.isFinite(ms) ? ms : 0 } async function listDirection( direction: 'inbound' | 'outbound', inboxId?: string, ): Promise<{ rows: RecentThreadMessage[]; capped: boolean }> { const listed = await kody.emailMessageList({ ...(inboxId ? { inbox_id: inboxId } : {}), direction, limit: LIST_LIMIT, }) const rows = Array.isArray(listed?.messages) ? listed.messages : [] return { rows: rows as RecentThreadMessage[], capped: rows.length >= LIST_LIMIT, } } /** * Count messages on a Kody inbox thread and hydrate the newest `limit` with bodies. * * @example * import recentThread from 'kody:@kentcdodds/grok-bot/thread' * const { count, messages } = await recentThread({ * threadId: 'thread-id', * limit: 8, * }) */ export default async function recentThread( input: RecentThreadInput = {}, ): Promise { const threadId = String(input.threadId || input.thread_id || '').trim() if (!threadId) { throw new Error('threadId is required') } const requested = typeof input.limit === 'number' && Number.isFinite(input.limit) ? Math.trunc(input.limit) : DEFAULT_RECENT const limit = Math.max(0, Math.min(MAX_RECENT, requested)) const inboxId = typeof input.inbox_id === 'string' && input.inbox_id.trim() ? input.inbox_id.trim() : undefined const [inbound, outbound] = await Promise.all([ listDirection('inbound', inboxId), listDirection('outbound', inboxId), ]) const byId = new Map() for (const row of [...inbound.rows, ...outbound.rows]) { if (row?.id && row.thread_id === threadId) byId.set(row.id, row) } const inThread = [...byId.values()].sort((a, b) => stamp(b) - stamp(a)) const recent = limit > 0 ? inThread.slice(0, limit) : [] const messages: RecentThreadMessage[] = [] for (const row of recent) { try { const full = await kody.emailMessageGet({ message_id: row.id }) messages.push((full || row) as RecentThreadMessage) } catch { messages.push(row) } } return { threadId, count: inThread.length, limit, capped: inbound.capped || outbound.capped, messages, } }