UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

127 lines (126 loc) 6.24 kB
import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { t as findCodeRegions } from "./code-regions-BXMpZZLd.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./text-chunking-BCaDmWPm.js"; //#region extensions/anthropic/cli-output.ts const CLAUDE_RAW_TOOL_OUTPUT_ERROR = "Claude CLI returned malformed tool output (invalid request format): raw tool protocol appeared as assistant text. OpenClaw refused to persist or deliver it."; const RAW_INVOKE_TAG_RE = /<invoke(?=[ \t>])[^<>\r\n]*>/gu; const RAW_PARAMETER_TAG_RE = /<parameter(?=[ \t>])[^<>\r\n]*>/gu; const RAW_INVOKE_CLOSE_TAG_RE = /<\/invoke[ \t]*>/gu; const RAW_PARAMETER_LOOKAHEAD_CHARS = 2048; const OBSERVED_TRUNCATED_LEAK_PREFIXES = /* @__PURE__ */ new Set([ "call", "count", "court" ]); function readTagNameAttribute(tag, elementName) { const attribute = /[ \t]+([^\s=/>]+)[ \t]*=[ \t]*(?:"([^"]*)"|'([^']*)')/gy; const tagEnd = tag.length - 1; let cursor = elementName.length + 1; while (cursor < tagEnd) { attribute.lastIndex = cursor; const match = attribute.exec(tag); if (!match) return ""; if (match[1] === "name") return match[2] ?? match[3] ?? ""; cursor = attribute.lastIndex; } return ""; } function isStandaloneLineTag(text, index) { const lineStart = text.lastIndexOf("\n", index - 1) + 1; return /^[ ]{0,3}$/u.test(text.slice(lineStart, index)); } function isStandaloneOpeningTagLine(text, index, tagLength) { if (!isStandaloneLineTag(text, index)) return false; const lineEnd = text.indexOf("\n", index + tagLength); const trailingText = text.slice(index + tagLength, lineEnd === -1 ? text.length : lineEnd); return /^[ \t]*\r?$/u.test(trailingText); } function isInsideCodeRegion(index, regions) { let low = 0; let high = regions.length - 1; while (low <= high) { const middle = Math.floor((low + high) / 2); const region = regions[middle]; if (!region) return false; if (index < region.start) high = middle - 1; else if (index >= region.end) low = middle + 1; else return true; } return false; } function hasObservedTruncatedLeakPrefix(text, index, toolName) { const previousNonEmptyLine = text.slice(0, index).split(/\r?\n/gu).findLast((line) => line.trim().length > 0)?.trim(); return previousNonEmptyLine === toolName || previousNonEmptyLine !== void 0 && OBSERVED_TRUNCATED_LEAK_PREFIXES.has(previousNonEmptyLine); } function findNextStandaloneTag(text, index, regions, pattern) { const scanner = new RegExp(pattern.source, pattern.flags); scanner.lastIndex = index; for (let match = scanner.exec(text); match; match = scanner.exec(text)) if (isStandaloneOpeningTagLine(text, match.index, match[0].length) && !isInsideCodeRegion(match.index, regions)) return match.index; return null; } /** Detect Claude's legacy tool protocol only when it occupies standalone assistant lines. */ function hasClaudeRawToolInvocation(text) { if (!text.includes("<invoke") || !text.includes("<parameter")) return false; const codeRegions = findCodeRegions(text); RAW_INVOKE_TAG_RE.lastIndex = 0; for (const match of text.matchAll(RAW_INVOKE_TAG_RE)) { const index = match.index; const toolName = readTagNameAttribute(match[0], "invoke"); if (!/^[A-Z]/u.test(toolName) && !toolName.startsWith("mcp__") || !isStandaloneOpeningTagLine(text, index, match[0].length) || isInsideCodeRegion(index, codeRegions)) continue; const invocationBodyStart = index + match[0].length; const invokeCloseIndex = findNextStandaloneTag(text, invocationBodyStart, codeRegions, RAW_INVOKE_CLOSE_TAG_RE); const nextInvokeIndex = findNextStandaloneTag(text, invocationBodyStart, codeRegions, RAW_INVOKE_TAG_RE); const completeInvokeCloseIndex = invokeCloseIndex !== null && (nextInvokeIndex === null || invokeCloseIndex < nextInvokeIndex) ? invokeCloseIndex : null; const parameterSearchEnd = Math.min(text.length, invocationBodyStart + RAW_PARAMETER_LOOKAHEAD_CHARS, completeInvokeCloseIndex ?? text.length, nextInvokeIndex ?? text.length); const parameterText = text.slice(invocationBodyStart, parameterSearchEnd); let parameterIndex = null; for (const parameterMatch of parameterText.matchAll(RAW_PARAMETER_TAG_RE)) { const candidateIndex = invocationBodyStart + parameterMatch.index; if (readTagNameAttribute(parameterMatch[0], "parameter") && isStandaloneLineTag(text, candidateIndex) && !isInsideCodeRegion(candidateIndex, codeRegions)) { parameterIndex = candidateIndex; break; } } if (parameterIndex === null) continue; if (completeInvokeCloseIndex !== null || completeInvokeCloseIndex === null && hasObservedTruncatedLeakPrefix(text, index, toolName)) return true; } return false; } function parseClaudeJsonlRecord(line) { try { const parsed = JSON.parse(line); return isRecord(parsed) ? parsed : null; } catch { return null; } } /** Project Claude-owned lifecycle records without widening the legacy parser event union. */ const parseClaudeCliJsonlLifecycleEvent = (line) => { if (!line.includes("compacting") && !line.includes("compact_result")) return null; const parsed = parseClaudeJsonlRecord(line); if (!parsed) return null; if (parsed.compact_result === "success" || parsed.compact_result === "failed") return { kind: "compaction", phase: "end", completed: parsed.compact_result === "success" }; if (parsed.type === "system" && parsed.subtype === "status") return parsed.status === "compacting" ? { kind: "compaction", phase: "start" } : null; return null; }; /** Reject malformed terminal Claude results before the generic CLI runner accepts them as prose. */ const parseClaudeCliJsonlEvent = (line) => { if (!((line.includes("<invoke") || line.includes("\\u003cinvoke") || line.includes("\\u003Cinvoke")) && (line.includes("<parameter") || line.includes("\\u003cparameter") || line.includes("\\u003Cparameter")))) return null; const parsed = parseClaudeJsonlRecord(line); if (!parsed) return null; if (parsed.type !== "result" || typeof parsed.result !== "string" || !hasClaudeRawToolInvocation(parsed.result)) return null; return { kind: "result", errorText: CLAUDE_RAW_TOOL_OUTPUT_ERROR }; }; //#endregion export { parseClaudeCliJsonlEvent as n, parseClaudeCliJsonlLifecycleEvent as r, hasClaudeRawToolInvocation as t };