openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
502 lines (501 loc) • 28.8 kB
JavaScript
import { i as resolveGlobalSingleton } from "./global-singleton-Dc_stLtU.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { i as isSilentReplyPayloadText, t as HEARTBEAT_TOKEN } from "./tokens-DbeAFqg7.js";
import { _ as runOncePerAgentRun } from "./agent-events-CoxiItUi.js";
import { n as formatInlineCodeSpan } from "./markdown-code-Buzx6wvi.js";
import { i as classifyOAuthRefreshFailure } from "./oauth-refresh-failure-DP-bO7C0.js";
import { f as isPluginHookAgentTrigger, t as getGlobalHookRunner } from "./hook-runner-global-0kfmMG4T.js";
import { a as getReplyPayloadMetadata, g as markReplyPayloadForSourceSuppressionDelivery, i as copyReplyPayloadMetadata, y as setReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js";
import "./classify-GlkuT1ur.js";
import { d as isTimeoutErrorMessage } from "./message-patterns-Cg93oCGB.js";
import { a as parseAssistantTextSignature, n as extractAssistantTextForPhase } from "./chat-message-content-Dws_XUEQ.js";
import { a as sanitizeAssistantVisibleText, i as sanitizeAssistantFinalAnswerText } from "./assistant-visible-text-BSYbiXdn.js";
import { o as hasReplyPayloadContent } from "./payload-DYE7bQ2k.js";
import { t as parseReplyDirectives } from "./reply-directives-DWcXX_ma.js";
import { l as formatAssistantErrorText, u as formatUserFacingAssistantErrorText } from "./embedded-agent-helpers-D0eiT3IT.js";
import { i as normalizeTextForComparison } from "./media-reference-comparison-DpA8NCy-.js";
import { t as buildCodexLoginRecovery } from "./codex-login-recovery-C1HtauQM.js";
import { o as resolveToolDisplay } from "./tool-display-CzOArp4x.js";
import { n as formatToolAggregateParts } from "./tool-meta-DUrV7YlL.js";
import { i as createHeartbeatToolResponsePayload } from "./heartbeat-tool-response-B4VYc0rg.js";
import { n as isExecLikeToolName } from "./tool-error-summary-Bw_A4yhp.js";
import { a as extractAssistantThinking, o as extractAssistantVisibleText, p as sanitizeAssistantVisibleStreamText } from "./embedded-agent-utils-DLNh77Fj.js";
import { b as resolveExplicitFinalSourceReplyDeliveryEvidence } from "./delivery-evidence-5HyXxfSi.js";
import { AsyncLocalStorage } from "node:async_hooks";
//#region src/plugins/before-agent-reply.ts
const beforeAgentReplyObserver = resolveGlobalSingleton(Symbol.for("openclaw.beforeAgentReply.observer"), () => new AsyncLocalStorage());
/** Attaches durable admission bookkeeping without moving hook ownership out of the runner. */
function withBeforeAgentReplyObserver(observer, run) {
return beforeAgentReplyObserver.run({ ...observer }, run);
}
/** Preserves the full plugin reply contract, including private payload metadata. */
function buildHandledBeforeAgentReplyPayloads(reply) {
return [reply ?? { text: "NO_REPLY" }];
}
/** Runs the reply claim hook once for one admitted turn, across model fallbacks. */
function runBeforeAgentReplyForTurn(params) {
const trigger = params.trigger;
if (!isPluginHookAgentTrigger(trigger)) return Promise.resolve(void 0);
const context = {
...params.context,
trigger
};
return runOncePerAgentRun(params.runId, "before_agent_reply", async () => {
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("before_agent_reply", context)) return;
const observerScope = beforeAgentReplyObserver.getStore();
const observer = observerScope && (!observerScope.runId || observerScope.runId === params.runId) ? observerScope : void 0;
if (observer && !observer.runId) observer.runId = params.runId;
if (await observer?.beforeDispatch() === false) return;
params.onDispatch?.();
let result = await hookRunner.runBeforeAgentReply(params.event, context);
if (!result?.handled) params.onDeclined?.();
if (observer) result = await observer.afterDispatch(result);
return result;
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/auth-profile-failure-policy.ts
/**
* Returns the subset of failover reasons that should affect shared auth-profile
* health. Local helper failures and request-shape/transport outcomes stay
* session-local so one bad transcript or connection does not cool down an
* otherwise healthy provider profile.
*/
function resolveAuthProfileFailureReason(params) {
if (params.policy === "local" || !params.failoverReason || params.failoverReason === "overloaded" || params.policy === "local_transient" && params.failoverReason === "rate_limit" && params.transientRateLimit === true || params.failoverReason === "server_error" || params.failoverReason === "tls_certificate" || params.failoverReason === "empty_response" || params.failoverReason === "context_overflow" || params.failoverReason === "format") return null;
if (params.failoverReason === "timeout" && params.providerStarted !== true) return null;
return params.failoverReason;
}
//#endregion
//#region src/agents/embedded-agent-runner/run/source-reply-payloads.ts
/** Builds transcript mirrors and completion evidence for message-tool source replies. */
function buildSourceReplyPayloadState(params) {
const sourceReplyPayloads = params.payloads ?? [];
const replyItems = sourceReplyPayloads.flatMap((payload, index) => {
const text = normalizeOptionalString(payload.text) ?? "";
const media = (payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : []).filter((value) => value.trim().length > 0);
if (!text && media.length === 0 && !payload.presentation && !payload.interactive && !payload.channelData) return [];
return [{
text,
...payload.mediaUrl ? { mediaUrl: payload.mediaUrl } : {},
...media.length ? { media } : {},
...payload.audioAsVoice ? { audioAsVoice: true } : {},
...payload.attachments?.length ? { attachments: payload.attachments } : {},
...payload.trustedLocalMedia !== void 0 ? { trustedLocalMedia: payload.trustedLocalMedia } : {},
...payload.presentation ? { presentation: payload.presentation } : {},
...payload.interactive ? { interactive: payload.interactive } : {},
...payload.channelData ? { channelData: payload.channelData } : {},
sourceReplyMirror: {
idempotencyKey: payload.idempotencyKey ?? (params.runId ? `${params.runId}:internal-source-reply:${index}` : void 0),
...payload.transcriptOwner ? { transcriptOwner: true } : {}
}
}];
});
const hasSourceReplyPayload = replyItems.length > 0;
const deliveredSourceReplyViaMessageTool = params.sourceReplyDeliveryMode === "message_tool_only" && params.didDeliverSourceReplyViaMessageTool === true;
const explicitFinalSourceReply = resolveExplicitFinalSourceReplyDeliveryEvidence({
messagingToolSentTargets: params.sentTargets,
messagingToolSourceReplyPayloads: sourceReplyPayloads
});
return {
replyItems,
hasSourceReplyPayload,
deliveredSourceReplyViaMessageTool,
explicitFinalSourceReply,
completedSourceReplyViaMessageTool: explicitFinalSourceReply ?? (hasSourceReplyPayload || deliveredSourceReplyViaMessageTool)
};
}
//#endregion
//#region src/agents/embedded-agent-runner/run/tool-error-warning.ts
function formatWarningToolLabel(toolName, metas, markdown) {
const { label } = resolveToolDisplay({ name: toolName });
const { detail } = formatToolAggregateParts(toolName, metas, { markdown });
return detail ? `${label}: ${detail}` : label;
}
function formatToolErrorWarningText(params) {
const failureVerb = params.lastToolError.executionStarted === false ? "blocked" : "failed";
const terminalDiagnostic = params.lastToolError.terminalDiagnostic;
if (terminalDiagnostic?.kind === "process") return `⚠️ ${formatWarningToolLabel("process", params.includeDetails ? [terminalDiagnostic.sessionId] : void 0, params.useMarkdown)} failed (${terminalDiagnostic.reason.kind === "exit" ? `exit ${terminalDiagnostic.reason.exitCode}` : terminalDiagnostic.reason.kind === "signal" ? `signal ${terminalDiagnostic.reason.signal}` : terminalDiagnostic.reason.timeoutKind === "no-output-timeout" ? "timed out waiting for output" : "timed out"})${params.includeDetails && params.lastToolError.error ? `: ${params.lastToolError.error}` : ""}${params.includeDetails ? "" : ". Use /verbose full for complete output"}.`;
const includeError = params.includeDetails || params.lastToolError.errorCode === "approval_timeout";
if (isExecLikeToolName(params.lastToolError.toolName)) {
const toolLabel = resolveToolDisplay({ name: params.lastToolError.toolName }).label;
const subject = params.includeDetails ? formatExecLikeFailureSubject(params.lastToolError.meta, params.useMarkdown) : "";
const conciseExitSuffix = params.includeDetails ? "" : formatConciseExecExitSuffix(params.lastToolError.error);
const errorSuffix = includeError && params.lastToolError.error ? `: ${params.lastToolError.error}` : "";
return subject ? `⚠️ ${toolLabel} ${failureVerb}: ${subject}${conciseExitSuffix}${errorSuffix}` : `⚠️ ${toolLabel} ${failureVerb}${conciseExitSuffix}${errorSuffix}`;
}
return `⚠️ ${formatWarningToolLabel(params.lastToolError.toolName, params.includeDetails && params.lastToolError.meta ? [params.lastToolError.meta] : void 0, params.useMarkdown)} ${failureVerb}${includeError && params.lastToolError.error ? `: ${params.lastToolError.error}` : ""}`;
}
function formatExecLikeFailureSubject(meta, markdown) {
const normalized = normalizeOptionalString(meta);
if (!normalized) return "";
const { flags, body } = splitExecLikeFailureMeta(normalized);
if (!body) return flags.join(" · ");
const { text, suffix } = splitDisplayContextSuffix(body);
const subject = `${maybeWrapInlineCode(extractLiteralExecCommand(text) ?? text, markdown)}${suffix}`;
return flags.length > 0 ? `${flags.join(" · ")} · ${subject}` : subject;
}
function splitExecLikeFailureMeta(meta) {
const flags = [];
const bodyParts = [];
for (const part of meta.split(" · ").map((candidate) => candidate.trim()).filter(Boolean)) {
if (part === "elevated" || part === "pty") {
flags.push(part);
continue;
}
bodyParts.push(part);
}
return {
flags,
body: bodyParts.join(" · ")
};
}
const SEMANTIC_RUN_SUMMARIES = /* @__PURE__ */ new Set([
"tests",
"build",
"lint",
"script",
"command"
]);
const LITERAL_RUN_SUMMARY_PREFIXES = /* @__PURE__ */ new Set([
"python",
"python3",
"ruby",
"php",
"git",
"npm",
"pnpm",
"yarn",
"bun",
"openclaw",
"make",
"cargo",
"go",
"docker",
"npx",
"uv",
"poetry",
"pytest",
"vitest",
"jest",
"deno"
]);
function extractLiteralExecCommand(body) {
const rawCommand = extractRawExecCommand(body);
if (rawCommand) return rawCommand;
const nodeScript = body.match(/^run node script (.+)$/u);
if (nodeScript?.[1]) return `node ${nodeScript[1]}`;
const runSubject = body.match(/^run (.+)$/u)?.[1];
if (runSubject && isKnownLiteralRunSummary(runSubject)) return runSubject;
}
function extractRawExecCommand(body) {
const codeSpan = extractTrailingMarkdownCodeSpan(body);
if (!codeSpan) return;
const context = extractRawExecContext(codeSpan.prefix, codeSpan.value);
const command = context.trailing.reduce((value, suffix) => `${value} ${suffix}`, codeSpan.value);
return context.leading.length > 0 ? `${context.leading.join(" · ")} · ${command}` : command;
}
function extractTrailingMarkdownCodeSpan(body) {
const trimmed = body.trimEnd();
if (!trimmed.endsWith("`")) return;
let delimiterLength = 0;
for (let index = trimmed.length - 1; index >= 0 && trimmed[index] === "`"; index -= 1) delimiterLength += 1;
const delimiter = "`".repeat(delimiterLength);
const valueEnd = trimmed.length - delimiterLength;
let searchIndex = 0;
while (searchIndex < valueEnd) {
const openIndex = trimmed.indexOf(delimiter, searchIndex);
if (openIndex < 0 || openIndex >= valueEnd) return;
const prefixMatch = trimmed.slice(0, openIndex).match(/^(?:(.*)(?:,\s*| · ))?$/u);
if (prefixMatch) return {
prefix: prefixMatch[1],
value: unwrapMarkdownInlineCodePadding(trimmed.slice(openIndex + delimiterLength, valueEnd))
};
searchIndex = openIndex + delimiterLength;
}
}
function unwrapMarkdownInlineCodePadding(value) {
if (value.length < 2 || !value.startsWith(" ") || !value.endsWith(" ")) return value;
const unwrapped = value.slice(1, -1);
return /\S/u.test(unwrapped) ? unwrapped : value;
}
function extractRawExecContext(prefix, inlineCode) {
const value = prefix ?? "";
return {
leading: [...value.matchAll(/(?:^|,\s*| · )(node:\s*[^,·]+)(?=,\s*| · |$)/gu)].map((match) => match[1]?.trim()).filter((part) => Boolean(part)),
trailing: [...value.matchAll(/(\((?:agent|repo|sandbox|workspace)\)|\(in [^)\r\n]+\))(?=\s*(?:,\s*| · |$))/gu)].filter((match) => shouldKeepRawExecTrailingContext(value, match, inlineCode)).map((match) => match[1]?.trim()).filter((part) => Boolean(part))
};
}
function shouldKeepRawExecTrailingContext(prefix, match, inlineCode) {
const suffix = match[1]?.trim();
if (!suffix || inlineCode.includes(suffix)) return false;
const segment = prefix.slice(0, match.index ?? 0).trimEnd().split(/,\s*| · /u).at(-1)?.trim();
if ((segment ? extractLiteralExecCommand(segment) : void 0) === inlineCode || segment === inlineCode) return true;
if (isCompactCwdSuffix(suffix)) return true;
return isPathLikeCwdSuffix(suffix);
}
function isCompactCwdSuffix(suffix) {
return /^\((?:agent|repo|workspace)\)$/u.test(suffix);
}
function isPathLikeCwdSuffix(suffix) {
const cwd = suffix.match(/^\(in ([^)\r\n]+)\)$/u)?.[1]?.trim();
return Boolean(cwd && (/^(?:\/|~|\.{1,2}(?:\/|$)|[A-Za-z]:[\\/]|\\\\)/u.test(cwd) || cwd.includes("/")));
}
function isKnownLiteralRunSummary(subject) {
if (SEMANTIC_RUN_SUMMARIES.has(subject) || subject.includes("→") || subject.includes("->") || /^(?:node|python3?|ruby|php) inline script(?: \(heredoc\))?$/u.test(subject)) return false;
const match = subject.match(/^(\S+)\s+(.+)$/u);
const command = match?.[1];
const remainder = match?.[2];
if (!command || !remainder || remainder === "command") return false;
return LITERAL_RUN_SUMMARY_PREFIXES.has(command);
}
function splitDisplayContextSuffix(value) {
const match = /^(.*?)( \((?:agent|repo|workspace|sandbox)\))$/u.exec(value);
if (!match) return {
text: value,
suffix: ""
};
return {
text: match[1] ?? value,
suffix: match[2] ?? ""
};
}
function formatConciseExecExitSuffix(error) {
const code = normalizeOptionalString(error)?.match(/\b(?:command\s+)?(?:failed\s+with\s+exit\s+code|exited\s+with\s+code|exit(?:ed)?\s+code|exit\s+status)\s+(-?\d+)\b/iu)?.[1];
return code ? ` (exit ${code})` : "";
}
function maybeWrapInlineCode(value, markdown) {
return markdown ? formatInlineCodeSpan(value) : value;
}
/** Always warn when a tool failure would otherwise leave the user with no reply. */
function buildFailureWarning(params) {
if (params.hasUserFacingReply) return;
return formatToolErrorWarningText({
lastToolError: params.lastToolError,
includeDetails: params.verboseLevel === "full",
useMarkdown: params.useMarkdown
});
}
//#endregion
//#region src/agents/embedded-agent-runner/run/payloads.ts
/**
* Builds embedded-agent payload objects from attempt inputs and outcomes.
*/
function isAssistantTextContentBlockType(value) {
return value === "text" || value === "input_text" || value === "output_text";
}
function resolveRawAssistantAnswerText(lastAssistant) {
if (!lastAssistant) return "";
const finalAnswerText = extractAssistantTextForPhase(lastAssistant, {
phase: "final_answer",
sanitizeText: sanitizeAssistantFinalAnswerText
});
if (finalAnswerText) return normalizeOptionalString(finalAnswerText) ?? "";
if (Array.isArray(lastAssistant.content)) {
if (!lastAssistant.content.some((block) => {
if (!block || typeof block !== "object") return false;
const record = block;
return isAssistantTextContentBlockType(record.type) && Boolean(parseAssistantTextSignature(record)?.phase);
})) {
const signedUnphasedParts = lastAssistant.content.map((block) => {
if (!block || typeof block !== "object") return null;
const record = block;
const signature = parseAssistantTextSignature(record);
if (!isAssistantTextContentBlockType(record.type) || typeof record.text !== "string" || !signature?.id || signature.phase) return null;
const text = sanitizeAssistantFinalAnswerText(record.text);
return text.trim() ? text : null;
}).filter((value) => typeof value === "string");
if (signedUnphasedParts.length) return normalizeOptionalString(signedUnphasedParts.join("\n")) ?? "";
}
}
return normalizeOptionalString(extractAssistantTextForPhase(lastAssistant, { sanitizeText: sanitizeAssistantVisibleText })) ?? "";
}
/**
* Converts a completed embedded attempt into reply payloads for channels. This
* is the boundary that suppresses duplicate source replies, filters raw API
* errors, preserves directive metadata, and decides when tool failures must be
* surfaced to the user.
*/
function buildEmbeddedRunPayloads(params) {
const heartbeatTerminalToolFailure = params.isHeartbeatTrigger === true && params.lastToolError && params.lastToolError.mutatingAction === true ? { toolName: params.lastToolError.toolName } : void 0;
if (params.heartbeatToolResponse && !heartbeatTerminalToolFailure) return [createHeartbeatToolResponsePayload(params.heartbeatToolResponse)];
const { replyItems, hasSourceReplyPayload, deliveredSourceReplyViaMessageTool, explicitFinalSourceReply, completedSourceReplyViaMessageTool } = buildSourceReplyPayloadState({
payloads: params.messagingToolSourceReplyPayloads,
sentTargets: params.messagingToolSentTargets,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
didDeliverSourceReplyViaMessageTool: params.didDeliverSourceReplyViaMessageTool,
runId: params.runId
});
if (params.heartbeatToolResponse) {
const heartbeatPayload = createHeartbeatToolResponsePayload(params.heartbeatToolResponse);
replyItems.push({
text: heartbeatPayload.text ?? "",
...heartbeatPayload.channelData ? { channelData: heartbeatPayload.channelData } : {}
});
}
const useMarkdown = params.toolResultFormat === "markdown";
const suppressAssistantArtifacts = params.heartbeatToolResponse !== void 0 || params.didSendDeterministicApprovalPrompt === true || params.sourceReplyDeliveryMode === "message_tool_only" && hasSourceReplyPayload || deliveredSourceReplyViaMessageTool;
const suppressFailureArtifacts = params.didSendDeterministicApprovalPrompt === true || params.sourceReplyDeliveryMode === "message_tool_only" && completedSourceReplyViaMessageTool;
const nonEmptyAssistantTexts = params.assistantTexts.map((text) => sanitizeAssistantVisibleStreamText(text)).filter((text) => text.trim().length > 0);
const assistantForPayload = params.currentAssistant ?? void 0 ?? (nonEmptyAssistantTexts.length === 1 ? void 0 : params.lastAssistant);
const storedDelivery = assistantForPayload?.openclawDelivery;
const lastAssistantStopReason = assistantForPayload?.stopReason;
const lastAssistantErrored = lastAssistantStopReason === "error";
const lastAssistantAborted = lastAssistantStopReason === "aborted";
const runAborted = params.runAborted === true || lastAssistantAborted;
const lastAssistantNeedsErrorSurface = lastAssistantErrored || lastAssistantAborted;
const rawErrorMessage = lastAssistantNeedsErrorSurface ? normalizeOptionalString(assistantForPayload?.errorMessage) : void 0;
const oauthRefreshFailure = rawErrorMessage ? classifyOAuthRefreshFailure(rawErrorMessage) : null;
const codexLoginRecovery = buildCodexLoginRecovery({
provider: oauthRefreshFailure?.provider ?? params.provider,
oauthReason: oauthRefreshFailure?.reason
});
const errorText = assistantForPayload && lastAssistantNeedsErrorSurface ? suppressFailureArtifacts ? void 0 : lastAssistantErrored || rawErrorMessage ? codexLoginRecovery?.hint ?? formatUserFacingAssistantErrorText(assistantForPayload, {
cfg: params.config,
sessionKey: params.sessionKey,
agentId: params.agentId,
provider: params.provider,
providerOwner: params.providerOwner,
model: params.model,
authMode: params.authMode
}) : formatAssistantErrorText(assistantForPayload, {
cfg: params.config,
sessionKey: params.sessionKey,
agentId: params.agentId,
provider: params.provider,
providerOwner: params.providerOwner,
model: params.model,
authMode: params.authMode
}) : void 0;
const deferAssistantTimeoutError = params.deferAssistantTimeoutError === true && rawErrorMessage !== void 0 && isTimeoutErrorMessage(rawErrorMessage) && errorText === "LLM request timed out.";
if (errorText && !deferAssistantTimeoutError) {
const errorPayload = {
text: errorText,
isError: true,
...codexLoginRecovery ? { presentation: codexLoginRecovery.presentation } : {}
};
replyItems.push(setReplyPayloadMetadata(errorPayload, { terminalProviderError: true }));
}
const reasoningText = suppressAssistantArtifacts || runAborted || lastAssistantNeedsErrorSurface ? "" : assistantForPayload && params.reasoningLevel === "on" && params.thinkingLevel !== "off" ? extractAssistantThinking(assistantForPayload) : "";
if (reasoningText) replyItems.push({
text: reasoningText,
isReasoning: true
});
const fallbackAnswerText = assistantForPayload ? extractAssistantVisibleText(assistantForPayload) : "";
const fallbackRawAnswerText = resolveRawAssistantAnswerText(assistantForPayload);
const rawAnswerDirectiveState = fallbackRawAnswerText ? parseReplyDirectives(fallbackRawAnswerText) : null;
const rawAnswerHasMedia = (rawAnswerDirectiveState?.mediaUrls?.length ?? 0) > 0 || rawAnswerDirectiveState?.audioAsVoice;
const normalizedAssistantTexts = rawAnswerHasMedia && nonEmptyAssistantTexts.length > 0 && !params.assistantTexts.some((text) => {
const parsed = parseReplyDirectives(text);
return (parsed.mediaUrls?.length ?? 0) > 0 || parsed.audioAsVoice;
}) ? normalizeTextForComparison(nonEmptyAssistantTexts.join("\n\n")) : "";
const shouldPreferRawAnswerText = rawAnswerHasMedia && (!nonEmptyAssistantTexts.length || normalizedAssistantTexts.length > 0 && normalizedAssistantTexts === normalizeTextForComparison(rawAnswerDirectiveState?.text ?? ""));
const fallbackAnswerSourceText = shouldPreferRawAnswerText && fallbackRawAnswerText ? fallbackRawAnswerText : fallbackAnswerText;
const fallbackAnswerDirectiveState = fallbackAnswerSourceText === fallbackRawAnswerText ? rawAnswerDirectiveState : fallbackAnswerSourceText ? parseReplyDirectives(fallbackAnswerSourceText) : null;
const normalizedFallbackAnswerSourceText = fallbackAnswerDirectiveState ? normalizeTextForComparison(fallbackAnswerDirectiveState.text) : "";
const shouldUseCanonicalFinalAnswer = !lastAssistantNeedsErrorSurface && fallbackAnswerSourceText.length > 0 && normalizedFallbackAnswerSourceText.length > 0;
const hasAssistantTextPayload = nonEmptyAssistantTexts.length > 0;
const answerTexts = suppressAssistantArtifacts || runAborted || lastAssistantNeedsErrorSurface ? [] : shouldUseCanonicalFinalAnswer ? [fallbackAnswerSourceText] : shouldPreferRawAnswerText && fallbackRawAnswerText ? [fallbackRawAnswerText] : hasAssistantTextPayload ? nonEmptyAssistantTexts : fallbackAnswerText ? [fallbackAnswerText] : [];
const preparedAnswerDirectives = shouldUseCanonicalFinalAnswer || shouldPreferRawAnswerText || !hasAssistantTextPayload ? fallbackAnswerDirectiveState : null;
let hasUserFacingReply = Boolean(errorText) || completedSourceReplyViaMessageTool || params.heartbeatToolResponse?.notify === true;
for (const text of answerTexts) {
const { text: cleanedText, mediaUrls, audioAsVoice, replyToId, replyToTag, replyToCurrent } = preparedAnswerDirectives ?? parseReplyDirectives(text);
const ttsFacts = shouldUseCanonicalFinalAnswer ? storedDelivery?.tts : void 0;
const delivery = shouldUseCanonicalFinalAnswer ? {
audioAsVoice: storedDelivery?.audioAsVoice,
replyToCurrent: storedDelivery?.replyToCurrent,
replyToId: storedDelivery?.replyToId,
replyToTag: Boolean(storedDelivery?.replyToCurrent || storedDelivery?.replyToId)
} : {
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent
};
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0) && !delivery.audioAsVoice && !ttsFacts) continue;
const replyPayload = {
text: cleanedText,
media: mediaUrls,
...delivery
};
replyItems.push(ttsFacts ? setReplyPayloadMetadata(replyPayload, { tts: ttsFacts }) : replyPayload);
hasUserFacingReply = true;
}
if (params.lastToolError) {
const isRestartStatus = params.runStopReason === "restart";
const warningText = isRestartStatus ? "Gateway restarting…" : buildFailureWarning({
lastToolError: params.lastToolError,
hasUserFacingReply,
verboseLevel: params.verboseLevel,
useMarkdown
});
if (warningText) {
const normalizedWarning = normalizeTextForComparison(warningText);
if (!(normalizedWarning ? replyItems.some((item) => {
if (!item.text) return false;
const normalizedExisting = normalizeTextForComparison(item.text);
return normalizedExisting.length > 0 && normalizedExisting === normalizedWarning;
}) : false)) {
const warning = {
text: warningText,
...!isRestartStatus ? { isError: true } : {}
};
if (!isRestartStatus) setReplyPayloadMetadata(warning, { toolErrorWarning: { toolName: params.lastToolError.toolName } });
replyItems.push(warning);
}
}
}
if (heartbeatTerminalToolFailure && !replyItems.some((item) => item.isReasoning !== true)) replyItems.push({ text: HEARTBEAT_TOKEN });
const hasAudioAsVoiceTag = replyItems.some((item) => item.audioAsVoice);
return replyItems.map((item) => {
const payload = copyReplyPayloadMetadata(item, { text: normalizeOptionalString(item.text) });
const mediaUrl = item.mediaUrl ?? item.media?.[0];
if (mediaUrl) payload.mediaUrl = mediaUrl;
if (item.media?.length) payload.mediaUrls = item.media;
if (item.attachments?.length) payload.attachments = item.attachments;
if (item.trustedLocalMedia !== void 0) payload.trustedLocalMedia = item.trustedLocalMedia;
if (item.isError !== void 0) payload.isError = item.isError;
if (item.isReasoning === true) payload.isReasoning = true;
if (item.isError === true && params.sourceReplyDeliveryMode === "message_tool_only" && explicitFinalSourceReply === false) markReplyPayloadForSourceSuppressionDelivery(payload);
if (heartbeatTerminalToolFailure) setReplyPayloadMetadata(payload, { heartbeatTerminalToolFailure });
if (!item.isError && !item.isReasoning && (params.assistantMessageIndex !== void 0 || params.assistantTranscriptOwned === true)) setReplyPayloadMetadata(payload, {
...params.assistantMessageIndex !== void 0 ? { assistantMessageIndex: params.assistantMessageIndex } : {},
...item.media?.length ? { assistantTranscriptMediaUrls: [...item.media] } : {},
...params.assistantTranscriptOwned === true ? { assistantTranscriptOwned: true } : {},
...params.assistantTranscriptIdempotencyKey ? { assistantTranscriptIdempotencyKey: params.assistantTranscriptIdempotencyKey } : {}
});
if (item.replyToId) payload.replyToId = item.replyToId;
if (item.replyToTag !== void 0) payload.replyToTag = item.replyToTag;
if (item.replyToCurrent !== void 0) payload.replyToCurrent = item.replyToCurrent;
if (item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length)) payload.audioAsVoice = true;
if (item.presentation) payload.presentation = item.presentation;
if (item.interactive) payload.interactive = item.interactive;
if (item.channelData) payload.channelData = item.channelData;
if (item.sourceReplyMirror) {
markReplyPayloadForSourceSuppressionDelivery(payload);
if (params.sessionKey) {
const sourceReplyTranscriptMirror = { sessionKey: params.sessionKey };
if (params.agentId) sourceReplyTranscriptMirror.agentId = params.agentId;
if (payload.text) sourceReplyTranscriptMirror.text = payload.text;
if (payload.mediaUrls?.length) sourceReplyTranscriptMirror.mediaUrls = payload.mediaUrls;
if (item.sourceReplyMirror.idempotencyKey) sourceReplyTranscriptMirror.idempotencyKey = item.sourceReplyMirror.idempotencyKey;
if (item.sourceReplyMirror.transcriptOwner) sourceReplyTranscriptMirror.transcriptOwner = true;
setReplyPayloadMetadata(payload, { sourceReplyTranscriptMirror });
}
}
if (payload.text && isSilentReplyPayloadText(payload.text, "NO_REPLY")) {
const silentText = payload.text;
payload.text = void 0;
if (hasReplyPayloadContent(payload)) return payload;
payload.text = silentText;
}
return payload;
}).filter((p) => {
if (!hasReplyPayloadContent(p) && !getReplyPayloadMetadata(p)?.tts) return false;
if (p.text && isSilentReplyPayloadText(p.text, "NO_REPLY")) return false;
return true;
});
}
//#endregion
export { withBeforeAgentReplyObserver as a, runBeforeAgentReplyForTurn as i, resolveAuthProfileFailureReason as n, buildHandledBeforeAgentReplyPayloads as r, buildEmbeddedRunPayloads as t };