openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
291 lines (290 loc) • 12.2 kB
JavaScript
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { y as uniqueStrings } from "./string-normalization-DsCfAx8q.js";
import { n as MESSAGE_TOOL_DELIVERY_HINTS } from "./message-tool-delivery-hints-8OSBEg_c.js";
import "./strip-inbound-meta-D5F_RINt.js";
import { n as SILENT_REPLY_TOKEN, t as HEARTBEAT_TOKEN } from "./tokens-DbeAFqg7.js";
import { i as HEARTBEAT_RESPONSE_TOOL_PROMPT, l as resolveHeartbeatPromptForResponseTool, o as isHeartbeatAcknowledgementText, r as HEARTBEAT_RESPONSE_TOOL_INSTRUCTIONS } from "./heartbeat-ofet59jF.js";
import { t as HEARTBEAT_RESPONSE_TOOL_NAME } from "./heartbeat-tool-response-B4VYc0rg.js";
//#region src/auto-reply/heartbeat-filter.ts
const HEARTBEAT_TASK_PROMPT_PREFIX = "Run the following periodic tasks (only those due based on their intervals):";
const HEARTBEAT_TASK_PROMPT_COMPLETIONS = [
...[HEARTBEAT_TOKEN, SILENT_REPLY_TOKEN].map((token) => `After completing all due tasks, reply ${token}.`),
HEARTBEAT_RESPONSE_TOOL_INSTRUCTIONS,
`After completing all due tasks, use ${HEARTBEAT_RESPONSE_TOOL_NAME}`
];
const TOOL_CALL_BLOCK_TYPES = /* @__PURE__ */ new Set([
"toolCall",
"functionCall",
"toolUse",
"tool_call",
"function_call",
"tool_use"
]);
const TOOL_RESULT_BLOCK_TYPES = /* @__PURE__ */ new Set([
"toolResult",
"tool_result",
"tool_result_error",
"function_call_output"
]);
function readNestedString(record, key) {
const value = record[key];
const direct = normalizeOptionalString(value);
if (direct) return direct;
if (!isRecord(value)) return;
return normalizeOptionalString(value.name);
}
function collectToolCallBlocks(content) {
if (!Array.isArray(content)) return [];
return content.filter((block) => isRecord(block) && TOOL_CALL_BLOCK_TYPES.has(normalizeOptionalString(block.type) ?? ""));
}
function collectToolResultBlocks(content) {
if (!Array.isArray(content)) return [];
return content.filter((block) => isRecord(block) && TOOL_RESULT_BLOCK_TYPES.has(normalizeOptionalString(block.type) ?? ""));
}
function readToolCallName(block) {
return normalizeOptionalString(block.name) ?? readNestedString(block, "function");
}
function collectToolCallIds(block) {
const ids = [
normalizeOptionalString(block.call_id),
normalizeOptionalString(block.tool_call_id),
normalizeOptionalString(block.toolCallId),
normalizeOptionalString(block.tool_use_id),
normalizeOptionalString(block.toolUseId),
normalizeOptionalString(block.id)
].filter((id) => Boolean(id));
return uniqueStrings(ids);
}
function readNestedToolCallArguments(record) {
const value = record.function;
if (!isRecord(value)) return;
return value.arguments ?? value.args ?? value.input;
}
function readToolCallArguments(block) {
return block.arguments ?? block.args ?? block.input ?? readNestedToolCallArguments(block);
}
function parseToolCallArguments(value) {
if (isRecord(value)) return value;
if (typeof value !== "string" || !value.trim()) return;
try {
const parsed = JSON.parse(value);
return isRecord(parsed) ? parsed : void 0;
} catch {
return;
}
}
function isVisibleHeartbeatResponseToolCall(block) {
const args = parseToolCallArguments(readToolCallArguments(block));
if (!args) return false;
return args.notify === true || args.notify === "true";
}
function collectVisibleHeartbeatResponseToolCalls(message) {
if (message.role !== "assistant") return [];
return [...collectMessageToolCalls(message), ...collectToolCallBlocks(message.content)].filter((block) => readToolCallName(block) === "heartbeat_respond" && isVisibleHeartbeatResponseToolCall(block));
}
function collectMessageToolCalls(message) {
const toolCalls = message.tool_calls;
if (!Array.isArray(toolCalls)) return [];
return toolCalls.filter((call) => isRecord(call));
}
function hasAssistantToolCall(message) {
return message.role === "assistant" && (collectMessageToolCalls(message).length > 0 || collectToolCallBlocks(message.content).length > 0);
}
function isRemovableHeartbeatResponseToolCall(message) {
if (message.role !== "assistant") return false;
for (const call of collectMessageToolCalls(message)) if (readToolCallName(call) === "heartbeat_respond" && !isVisibleHeartbeatResponseToolCall(call)) return true;
return collectToolCallBlocks(message.content).some((block) => readToolCallName(block) === "heartbeat_respond" && !isVisibleHeartbeatResponseToolCall(block));
}
function hasVisibleHeartbeatResponseToolCall(message) {
return collectVisibleHeartbeatResponseToolCalls(message).length > 0;
}
function isEmbeddedToolResultOnlyContent(content) {
return Array.isArray(content) && content.length > 0 && content.every((block) => isRecord(block) && TOOL_RESULT_BLOCK_TYPES.has(normalizeOptionalString(block.type) ?? ""));
}
function isToolResultMessage(message) {
return message.role === "toolResult" || message.role === "tool" || message.role === "user" && isEmbeddedToolResultOnlyContent(message.content);
}
function isFailedToolResultRecord(record) {
return record.isError === true || record.is_error === true || normalizeOptionalString(record.type) === "tool_result_error";
}
function hasSuccessfulToolResultMessage(message) {
const resultBlocks = collectToolResultBlocks(message.content);
if (resultBlocks.length > 0) return resultBlocks.some((block) => !isFailedToolResultRecord(block));
if (!isToolResultMessage(message)) return false;
return !isFailedToolResultRecord(message);
}
function collectSuccessfulToolResultCallIds(message) {
const record = message;
const resultBlocks = collectToolResultBlocks(message.content);
const ids = [];
if (resultBlocks.length === 0) {
if (!isFailedToolResultRecord(record)) ids.push(...[
normalizeOptionalString(record.toolCallId),
normalizeOptionalString(record.tool_call_id),
normalizeOptionalString(record.toolUseId),
normalizeOptionalString(record.tool_use_id),
normalizeOptionalString(record.call_id),
normalizeOptionalString(record.id)
].filter((id) => Boolean(id)));
} else for (const block of resultBlocks) {
if (isFailedToolResultRecord(block)) continue;
ids.push(...collectToolCallIds(block));
}
return uniqueStrings(ids);
}
function isRealNonHeartbeatUserMessage(message, heartbeatPrompt) {
return message.role === "user" && !isEmbeddedToolResultOnlyContent(message.content) && !isHeartbeatUserMessage(message, heartbeatPrompt);
}
function matchesHeartbeatPromptText(text, prompt) {
const normalized = prompt?.trim();
return Boolean(normalized) && (text === normalized || text.startsWith(`${normalized}\n`));
}
function resolveMessageText(content) {
if (typeof content === "string") return {
text: content,
hasNonTextContent: false
};
if (!Array.isArray(content)) return {
text: "",
hasNonTextContent: content != null
};
let hasNonTextContent = false;
let text = "";
for (const block of content) {
if (typeof block !== "object" || block === null || !("type" in block)) {
hasNonTextContent = true;
continue;
}
if (block.type === "thinking" || block.type === "reasoning" || block.type === "redacted_thinking") continue;
if (block.type !== "text" && block.type !== "input_text" && block.type !== "output_text") {
hasNonTextContent = true;
continue;
}
const blockText = block.text;
if (typeof blockText !== "string") {
hasNonTextContent = true;
continue;
}
text += blockText;
}
return {
text,
hasNonTextContent
};
}
/** Return whether a user message is an internal heartbeat prompt. */
function isHeartbeatUserMessage(message, heartbeatPrompt) {
if (message.role !== "user") return false;
const { text } = resolveMessageText(message.content);
const trimmed = text.trim();
if (!trimmed) return false;
const normalizedHeartbeatPrompt = heartbeatPrompt?.trim();
if (trimmed === "[OpenClaw heartbeat poll]") return true;
if (MESSAGE_TOOL_DELIVERY_HINTS.some((prefix) => trimmed.startsWith(prefix)) && trimmed.endsWith("[OpenClaw heartbeat poll]")) return true;
if (matchesHeartbeatPromptText(trimmed, normalizedHeartbeatPrompt)) return true;
if (matchesHeartbeatPromptText(trimmed, HEARTBEAT_RESPONSE_TOOL_PROMPT)) return true;
if (normalizedHeartbeatPrompt && matchesHeartbeatPromptText(trimmed, resolveHeartbeatPromptForResponseTool(normalizedHeartbeatPrompt))) return true;
return trimmed.startsWith(HEARTBEAT_TASK_PROMPT_PREFIX) && HEARTBEAT_TASK_PROMPT_COMPLETIONS.some((completion) => trimmed.includes(completion));
}
/** Return whether an assistant message is only a heartbeat acknowledgement. */
function isHeartbeatOkResponse(message, ackMaxChars) {
if (message.role !== "assistant") return false;
if (hasAssistantToolCall(message)) return false;
const { text, hasNonTextContent } = resolveMessageText(message.content);
if (hasNonTextContent) return false;
return isHeartbeatAcknowledgementText(text, ackMaxChars);
}
function advancePastAdjacentToolResults(messages, startIndex) {
let index = startIndex;
while (index < messages.length) {
const message = messages.at(index);
if (!message || !isToolResultMessage(message)) break;
index++;
}
return index;
}
function isToolResultCompletionCandidate(message) {
return isToolResultMessage(message) || collectToolResultBlocks(message.content).length > 0;
}
function hasCompletedVisibleHeartbeatResponseToolCall(messages, index) {
const message = messages.at(index);
if (!message) return false;
const visibleCalls = collectVisibleHeartbeatResponseToolCalls(message);
if (visibleCalls.length === 0) return false;
const callIds = new Set(visibleCalls.flatMap((call) => collectToolCallIds(call)));
for (const result of messages.slice(index + 1)) {
if (!isToolResultCompletionCandidate(result)) break;
if (!hasSuccessfulToolResultMessage(result)) continue;
if (callIds.size === 0) return true;
for (const resultId of collectSuccessfulToolResultCallIds(result)) if (callIds.has(resultId)) return true;
}
return false;
}
function resolveHeartbeatArtifactSpanEnd(messages, startIndex, ackMaxChars, heartbeatPrompt) {
let index = startIndex + 1;
let sawTerminalHeartbeatArtifact = false;
let sawNonTerminalAssistantOutput = false;
while (index < messages.length) {
const message = messages.at(index);
if (!message) break;
if (isRealNonHeartbeatUserMessage(message, heartbeatPrompt)) break;
if (isHeartbeatUserMessage(message, heartbeatPrompt)) break;
if (isHeartbeatOkResponse(message, ackMaxChars)) {
sawTerminalHeartbeatArtifact = true;
index = advancePastAdjacentToolResults(messages, index + 1);
continue;
}
if (hasVisibleHeartbeatResponseToolCall(message)) {
if (hasCompletedVisibleHeartbeatResponseToolCall(messages, index)) return;
index++;
continue;
}
if (isRemovableHeartbeatResponseToolCall(message)) {
sawTerminalHeartbeatArtifact = true;
index = advancePastAdjacentToolResults(messages, index + 1);
continue;
}
if (sawTerminalHeartbeatArtifact) {
index++;
continue;
}
if (isToolResultMessage(message) || hasAssistantToolCall(message)) {
index++;
continue;
}
if (message.role === "assistant") {
sawNonTerminalAssistantOutput = true;
index++;
continue;
}
return;
}
if (sawNonTerminalAssistantOutput && !sawTerminalHeartbeatArtifact) return;
return index;
}
/** Remove heartbeat-only prompt, ack, and silent tool artifacts from a transcript. */
function filterHeartbeatTranscriptArtifacts(messages, ackMaxChars, heartbeatPrompt) {
if (messages.length === 0) return messages;
const result = [];
let i = 0;
while (i < messages.length) {
if (!isHeartbeatUserMessage(expectDefined(messages[i], "messages entry at i"), heartbeatPrompt)) {
result.push(expectDefined(messages[i], "messages entry at i"));
i++;
continue;
}
const next = resolveHeartbeatArtifactSpanEnd(messages, i, ackMaxChars, heartbeatPrompt);
if (next === void 0) {
result.push(expectDefined(messages[i], "messages entry at i"));
i++;
continue;
}
i = next;
}
return result;
}
//#endregion
export { isRealNonHeartbeatUserMessage as i, isHeartbeatOkResponse as n, isHeartbeatUserMessage as r, filterHeartbeatTranscriptArtifacts as t };