UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

316 lines (315 loc) 12.6 kB
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 };