← Public packages
@kentcdodds/grok-bot
Wake a named Grok Bot from Kody by posting a prompt to that bot's own Cursor webhook.
src/thread.ts
116 lines · 3.1 KB · TypeScriptimport { 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<string>
cc_addresses?: Array<string>
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<RecentThreadResult> {
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<string, RecentThreadMessage>()
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,
}
}