openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
316 lines (315 loc) • 12.6 kB
JavaScript
import "./src-vebZIeLe.js";
import { n as safeParseJsonRecord } from "./json-coercion-AulM0PZ6.js";
import { g as readStringValue, o as normalizeLowercaseStringOrEmpty, t as hasNonEmptyString } from "./string-coerce-CIXf7egm.js";
import { c as makeMissingToolResult$1, l as normalizeLegacyToolResultId, r as classifyToolUseResultPairing } from "./tool-result-pairing-Bccl6DhI.js";
import { o as isThinkingLikeBlock, r as extractToolResultIds, t as extractToolCallsFromAssistant } from "./tool-call-id-DJzLQ9lS.js";
import { n as normalizeAllowedToolNames, t as isAllowedToolCallName } from "./tool-call-shared-BxbmRH0F.js";
import { replaceCompactionReplayOwnerContent } from "@openclaw/ai/transports";
//#region src/agents/session-transcript-repair.ts
/**
* Transcript repair helpers for tool-call replay.
*
* Normalizes raw tool-call blocks and synthesizes missing tool results without rewriting trusted local payloads.
*/
const RAW_TOOL_CALL_BLOCK_TYPES = /* @__PURE__ */ new Set([
"toolCall",
"toolUse",
"functionCall",
"tool_call",
"tool_use",
"function_call"
]);
function isRawToolCallBlock(block) {
if (!block || typeof block !== "object") return false;
const type = block.type;
return typeof type === "string" && RAW_TOOL_CALL_BLOCK_TYPES.has(type);
}
function hasToolCallInput(block) {
const hasInput = "input" in block ? block.input !== void 0 && block.input !== null : false;
const hasArguments = "arguments" in block ? block.arguments !== void 0 && block.arguments !== null : false;
return hasInput || hasArguments;
}
function hasToolCallId(block) {
return hasNonEmptyString(block.id) || hasNonEmptyString(block.call_id) || hasNonEmptyString(block.toolCallId) || hasNonEmptyString(block.toolUseId) || hasNonEmptyString(block.tool_call_id) || hasNonEmptyString(block.tool_use_id);
}
function hasPartialJson(block) {
return typeof block.partialJson === "string";
}
function isCompleteJsonObject(value) {
return safeParseJsonRecord(value) !== void 0;
}
function isFinalizedOpenAIResponsesToolCall(message, block) {
if (message.role !== "assistant" || !("stopReason" in message) || message.stopReason !== "toolUse" || !hasPartialJson(block) || typeof block.id !== "string" || "input" in block || !block.arguments || typeof block.arguments !== "object" || Array.isArray(block.arguments) || !isCompleteJsonObject(block.partialJson) && (block.partialJson.trim() !== "" || Object.keys(block.arguments).length > 0)) return false;
const separator = block.id.indexOf("|");
return separator > 0 && separator < block.id.length - 1;
}
function sanitizeToolCallBlock(block) {
const rawName = readStringValue(block.name);
const trimmedName = rawName?.trim();
const hasTrimmedName = typeof trimmedName === "string" && trimmedName.length > 0;
const normalizedName = hasTrimmedName ? trimmedName : void 0;
const nameChanged = hasTrimmedName && rawName !== trimmedName;
if (!nameChanged) return block;
const next = { ...block };
if (nameChanged && normalizedName) next.name = normalizedName;
return next;
}
function countRawToolCallBlocks(content) {
let count = 0;
for (const block of content) if (isRawToolCallBlock(block)) count += 1;
return count;
}
function isReplaySafeThinkingAssistantTurn(content, allowedToolNames) {
let sawToolCall = false;
const seenToolCallIds = /* @__PURE__ */ new Set();
for (const block of content) {
if (!isRawToolCallBlock(block)) continue;
sawToolCall = true;
const toolCallId = typeof block.id === "string" ? block.id.trim() : "";
if (!hasToolCallInput(block) || hasPartialJson(block) || !toolCallId || seenToolCallIds.has(toolCallId) || !isAllowedToolCallName(block.name, allowedToolNames)) return false;
seenToolCallIds.add(toolCallId);
if (sanitizeToolCallBlock(block) !== block) return false;
}
return sawToolCall;
}
function hasSessionsSpawnAttachmentToolCall(content) {
for (const block of content) {
if (!isRawToolCallBlock(block) || block.name !== "sessions_spawn") continue;
const input = block.input;
if (!input || typeof input !== "object") continue;
const attachments = input.attachments;
if (Array.isArray(attachments) && attachments.length > 0) return true;
}
return false;
}
function makeMissingToolResult(params) {
return makeMissingToolResult$1(params);
}
function collectFollowingToolResults(messages, index) {
const ids = /* @__PURE__ */ new Set();
const assistant = messages[index];
const currentToolCalls = assistant && typeof assistant === "object" && assistant.role === "assistant" ? extractToolCallsFromAssistant(assistant) : [];
let sawNonToolResult = false;
let displaced = false;
for (let nextIndex = index + 1; nextIndex < messages.length; nextIndex += 1) {
const message = messages[nextIndex];
if (!message || typeof message !== "object") {
sawNonToolResult = true;
continue;
}
if (message.role === "assistant" && extractToolCallsFromAssistant(message).length > 0) break;
if (message.role === "toolResult") {
const normalizedLegacyResult = normalizeLegacyToolResultId(message, currentToolCalls);
const resultIds = extractToolResultIds(normalizedLegacyResult);
for (const id of resultIds) ids.add(id);
displaced ||= resultIds.length > 0 && sawNonToolResult;
continue;
}
sawNonToolResult = true;
}
return {
ids,
displaced
};
}
function repairToolCallInputs(messages, options) {
let droppedToolCalls = 0;
let droppedAssistantMessages = 0;
let changed = false;
const out = [];
const allowedToolNames = normalizeAllowedToolNames(options?.allowedToolNames);
const allowProviderOwnedThinkingReplay = options?.allowProviderOwnedThinkingReplay === true;
const preservedThinkingToolCallIds = /* @__PURE__ */ new Set();
const priorToolCallIds = /* @__PURE__ */ new Set();
for (const [index, msg] of messages.entries()) {
if (!msg || typeof msg !== "object") {
changed = true;
continue;
}
if (msg.role !== "assistant" || !Array.isArray(msg.content)) {
out.push(msg);
continue;
}
if (allowProviderOwnedThinkingReplay && msg.content.some((block) => isThinkingLikeBlock(block)) && countRawToolCallBlocks(msg.content) > 0) {
const replaySafeToolCalls = extractToolCallsFromAssistant(msg);
const followingToolResults = collectFollowingToolResults(messages, index);
if (isReplaySafeThinkingAssistantTurn(msg.content, allowedToolNames) && replaySafeToolCalls.every((toolCall) => !preservedThinkingToolCallIds.has(toolCall.id) && (!hasSessionsSpawnAttachmentToolCall(msg.content) || followingToolResults.ids.has(toolCall.id)) && (!followingToolResults.displaced || !priorToolCallIds.has(toolCall.id)))) {
for (const toolCall of replaySafeToolCalls) {
preservedThinkingToolCallIds.add(toolCall.id);
priorToolCallIds.add(toolCall.id);
}
changed ||= followingToolResults.displaced;
out.push(msg);
} else {
droppedToolCalls += countRawToolCallBlocks(msg.content);
droppedAssistantMessages += 1;
changed = true;
}
continue;
}
const nextContent = [];
let droppedInMessage = 0;
let messageChanged = false;
for (const block of msg.content) {
if (isRawToolCallBlock(block)) {
if (!hasToolCallInput(block) || !hasToolCallId(block) || !isAllowedToolCallName(block.name, allowedToolNames)) {
droppedToolCalls += 1;
droppedInMessage += 1;
changed = true;
messageChanged = true;
continue;
}
}
let workBlock = block;
if (isRawToolCallBlock(block) && hasPartialJson(block)) {
if (!isFinalizedOpenAIResponsesToolCall(msg, block)) {
droppedToolCalls += 1;
droppedInMessage += 1;
changed = true;
messageChanged = true;
continue;
}
const stripped = { ...block };
delete stripped.partialJson;
workBlock = stripped;
changed = true;
messageChanged = true;
}
if (isRawToolCallBlock(workBlock)) {
if (RAW_TOOL_CALL_BLOCK_TYPES.has(workBlock.type ?? "")) {
const blockName = typeof workBlock.name === "string" ? workBlock.name.trim() : void 0;
if (normalizeLowercaseStringOrEmpty(blockName) === "sessions_spawn") {
const sanitized = sanitizeToolCallBlock(workBlock);
if (sanitized !== workBlock) {
changed = true;
messageChanged = true;
}
nextContent.push(sanitized);
} else if (typeof workBlock.name === "string") {
const rawName = workBlock.name;
const trimmedName = rawName.trim();
if (rawName !== trimmedName && trimmedName) {
const renamed = {
...workBlock,
name: trimmedName
};
nextContent.push(renamed);
changed = true;
messageChanged = true;
} else nextContent.push(workBlock);
} else nextContent.push(workBlock);
continue;
}
}
nextContent.push(workBlock);
}
if (droppedInMessage > 0) {
if (nextContent.length === 0) {
droppedAssistantMessages += 1;
changed = true;
continue;
}
const nextMessage = replaceCompactionReplayOwnerContent(msg, nextContent);
for (const toolCall of extractToolCallsFromAssistant(nextMessage)) priorToolCallIds.add(toolCall.id);
out.push(nextMessage);
continue;
}
if (messageChanged) {
const nextMessage = replaceCompactionReplayOwnerContent(msg, nextContent);
for (const toolCall of extractToolCallsFromAssistant(nextMessage)) priorToolCallIds.add(toolCall.id);
out.push(nextMessage);
continue;
}
for (const toolCall of extractToolCallsFromAssistant(msg)) priorToolCallIds.add(toolCall.id);
out.push(msg);
}
return {
messages: changed ? out : messages,
droppedToolCalls,
droppedAssistantMessages
};
}
function sanitizeToolCallInputs(messages, options) {
return repairToolCallInputs(messages, options).messages;
}
function sanitizeToolUseResultPairing(messages, options) {
return repairToolUseResultPairing(messages, options).messages;
}
function sanitizeToolUseResultPairingForModel(messages, isOpenAIResponsesApi) {
return sanitizeToolUseResultPairing(messages, {
erroredAssistantResultPolicy: "drop",
...isOpenAIResponsesApi ? { missingToolResultText: "aborted" } : {}
});
}
function shouldDropErroredAssistantResults(options) {
return options?.erroredAssistantResultPolicy === "drop";
}
function repairToolUseResultPairing(messages, options) {
const added = [];
const preserveUnframed = options?.preserveUnframedToolResults === true;
const pairing = classifyToolUseResultPairing(messages, { preserveUnframedToolResults: preserveUnframed });
const { frames } = pairing;
const droppedDuplicateCount = pairing.droppedDuplicateCount;
let droppedOrphanCount = pairing.droppedOrphanCount;
const discarded = pairing.droppedResults.map(({ message, index }) => ({
message,
index
}));
const out = [];
let cursor = 0;
const pushUnframedRange = (endIndex) => {
for (; cursor < endIndex; cursor += 1) {
const sourceIndex = cursor;
const message = messages[cursor];
if (!message || typeof message !== "object") continue;
if (message.role === "toolResult" && !preserveUnframed) {
droppedOrphanCount += 1;
discarded.push({
message,
index: sourceIndex
});
continue;
}
out.push(message);
}
};
for (const frame of frames) {
pushUnframedRange(frame.startIndex);
cursor = frame.endIndex;
if (!(frame.failed && shouldDropErroredAssistantResults(options))) {
out.push(frame.assistant);
for (const occurrence of frame.occurrences) {
if (occurrence.result) {
out.push(occurrence.result);
continue;
}
if (frame.failed) continue;
const missing = makeMissingToolResult({
toolCallId: occurrence.id,
toolName: occurrence.name,
text: options?.missingToolResultText
});
occurrence.result = missing;
added.push(missing);
out.push(missing);
}
} else for (const occurrence of frame.occurrences) if (occurrence.sourceResult) discarded.push({
message: occurrence.sourceResult,
index: occurrence.sourceResultIndex ?? messages.indexOf(occurrence.sourceResult)
});
out.push(...frame.remainder);
}
pushUnframedRange(messages.length);
const changed = out.length !== messages.length || out.some((message, index) => message !== messages[index]);
discarded.sort((left, right) => left.index - right.index);
return {
messages: changed ? out : messages,
added,
discarded: discarded.map(({ message }) => message),
droppedDuplicateCount,
droppedOrphanCount,
moved: changed
};
}
//#endregion
export { sanitizeToolUseResultPairingForModel as a, sanitizeToolUseResultPairing as i, repairToolUseResultPairing as n, sanitizeToolCallInputs as r, makeMissingToolResult as t };