import { DEFAULT_HEARTBEAT_ACK_MAX_CHARS, stripHeartbeatToken, } from "../../auto-reply/heartbeat.js"; import { truncateUtf16Safe } from "../../utils.js"; type DeliveryPayload = { text?: string; mediaUrl?: string; mediaUrls?: string[]; }; export function pickSummaryFromOutput(text: string | undefined) { const clean = (text ?? "").trim(); if (!clean) return undefined; const limit = 2000; return clean.length > limit ? `${truncateUtf16Safe(clean, limit)}…` : clean; } export function pickSummaryFromPayloads(payloads: Array<{ text?: string | undefined }>) { for (let i = payloads.length - 1; i >= 0; i--) { const summary = pickSummaryFromOutput(payloads[i]?.text); if (summary) return summary; } return undefined; } export function pickLastNonEmptyTextFromPayloads(payloads: Array<{ text?: string | undefined }>) { for (let i = payloads.length - 1; i >= 0; i--) { const clean = (payloads[i]?.text ?? "").trim(); if (clean) return clean; } return undefined; } /** * Check if all payloads are just heartbeat ack responses (HEARTBEAT_OK). * Returns true if delivery should be skipped because there's no real content. */ export function isHeartbeatOnlyResponse(payloads: DeliveryPayload[], ackMaxChars: number) { if (payloads.length === 0) return true; return payloads.every((payload) => { // If there's media, we should deliver regardless of text content. const hasMedia = (payload.mediaUrls?.length ?? 0) > 0 || Boolean(payload.mediaUrl); if (hasMedia) return false; // Use heartbeat mode to check if text is just HEARTBEAT_OK or short ack. const result = stripHeartbeatToken(payload.text, { mode: "heartbeat", maxAckChars: ackMaxChars, }); return result.shouldSkip; }); } export function resolveHeartbeatAckMaxChars(agentCfg?: { heartbeat?: { ackMaxChars?: number } }) { const raw = agentCfg?.heartbeat?.ackMaxChars ?? DEFAULT_HEARTBEAT_ACK_MAX_CHARS; return Math.max(0, raw); }