refactor: deduplicate reply payload handling

This commit is contained in:
Peter Steinberger
2026-03-18 18:14:36 +00:00
parent 152d179302
commit 62edfdffbd
58 changed files with 704 additions and 450 deletions

View File

@@ -1,4 +1,5 @@
import { stripHeartbeatToken } from "../auto-reply/heartbeat.js";
import { resolveSendableOutboundReplyParts } from "../plugin-sdk/reply-payload.js";
export type HeartbeatDeliveryPayload = {
text?: string;
@@ -14,7 +15,7 @@ export function shouldSkipHeartbeatOnlyDelivery(
return true;
}
const hasAnyMedia = payloads.some(
(payload) => (payload.mediaUrls?.length ?? 0) > 0 || Boolean(payload.mediaUrl),
(payload) => resolveSendableOutboundReplyParts(payload).hasMedia,
);
if (hasAnyMedia) {
return false;

View File

@@ -1,5 +1,6 @@
import { DEFAULT_HEARTBEAT_ACK_MAX_CHARS } from "../../auto-reply/heartbeat.js";
import type { ReplyPayload } from "../../auto-reply/types.js";
import { hasOutboundReplyContent } from "../../plugin-sdk/reply-payload.js";
import { truncateUtf16Safe } from "../../utils.js";
import { shouldSkipHeartbeatOnlyDelivery } from "../heartbeat-policy.js";
@@ -61,11 +62,9 @@ export function pickLastNonEmptyTextFromPayloads(
export function pickLastDeliverablePayload(payloads: DeliveryPayload[]) {
const isDeliverable = (p: DeliveryPayload) => {
const text = (p?.text ?? "").trim();
const hasMedia = Boolean(p?.mediaUrl) || (p?.mediaUrls?.length ?? 0) > 0;
const hasInteractive = (p?.interactive?.blocks?.length ?? 0) > 0;
const hasChannelData = Object.keys(p?.channelData ?? {}).length > 0;
return text || hasMedia || hasInteractive || hasChannelData;
return hasOutboundReplyContent(p, { trimText: true }) || hasInteractive || hasChannelData;
};
for (let i = payloads.length - 1; i >= 0; i--) {
if (payloads[i]?.isError) {

View File

@@ -48,6 +48,7 @@ import {
import type { AgentDefaultsConfig } from "../../config/types.js";
import { registerAgentRunContext } from "../../infra/agent-events.js";
import { logWarn } from "../../logger.js";
import { resolveSendableOutboundReplyParts } from "../../plugin-sdk/reply-payload.js";
import { normalizeAgentId } from "../../routing/session-key.js";
import {
buildSafeExternalPrompt,
@@ -687,9 +688,9 @@ export async function runCronIsolatedAgentTurn(params: {
const interimPayloads = interimRunResult.payloads ?? [];
const interimDeliveryPayload = pickLastDeliverablePayload(interimPayloads);
const interimPayloadHasStructuredContent =
Boolean(interimDeliveryPayload?.mediaUrl) ||
(interimDeliveryPayload?.mediaUrls?.length ?? 0) > 0 ||
Object.keys(interimDeliveryPayload?.channelData ?? {}).length > 0;
(interimDeliveryPayload
? resolveSendableOutboundReplyParts(interimDeliveryPayload).hasMedia
: false) || Object.keys(interimDeliveryPayload?.channelData ?? {}).length > 0;
const interimText = pickLastNonEmptyTextFromPayloads(interimPayloads)?.trim() ?? "";
const hasDescendantsSinceRunStart = listDescendantRunsForRequester(agentSessionKey).some(
(entry) => {
@@ -809,8 +810,7 @@ export async function runCronIsolatedAgentTurn(params: {
? [{ text: synthesizedText }]
: [];
const deliveryPayloadHasStructuredContent =
Boolean(deliveryPayload?.mediaUrl) ||
(deliveryPayload?.mediaUrls?.length ?? 0) > 0 ||
(deliveryPayload ? resolveSendableOutboundReplyParts(deliveryPayload).hasMedia : false) ||
Object.keys(deliveryPayload?.channelData ?? {}).length > 0;
const deliveryBestEffort = resolveCronDeliveryBestEffort(params.job);
const hasErrorPayload = payloads.some((payload) => payload?.isError === true);