UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

952 lines (951 loc) • 44.7 kB
import { l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-6kR-86XC.mjs"; import { v as parseDateFirstTimestampMs } from "./number-coercion-Dq1l5muL.mjs"; import { d as normalizeStringEntries } from "./string-normalization-YMwDjufr.mjs"; import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { A as formatRawAssistantErrorForUi, D as extractLeadingHttpStatus, E as extractErrorHttpStatus, F as parseApiErrorInfo, L as extractHttpResponseBody, N as isGenericProviderInternalError, P as isKnownTransportErrorCode, j as formatTransportErrorCopy, k as formatProviderRefusalText } from "./redact-BEIE6NCT.mjs"; import { t as classifyGatewayStorageFailure } from "./sqlite-error-diagnostics-mpIpHclY.mjs"; import { t as createSubsystemLogger } from "./subsystem-BlrgtxyM.mjs"; import { r as normalizeProviderId } from "./provider-id-BIyeXQag.mjs"; import { s as normalizeThinkLevel } from "./thinking.shared-Dc3B6FDH.mjs"; import { o as extractToolCallsFromAssistant, s as extractToolResultId } from "./tool-result-pairing-Cxh1CJVG.mjs"; import { o as classifyOAuthRefreshFailure } from "./oauth-refresh-failure-Go2BQdE9.mjs"; import "./thinking-DeQ8EFQz.mjs"; import { h as matchesFormatErrorPattern, l as isProviderCompletedErrorFinishReasonMessage, m as isTimeoutErrorMessage } from "./message-patterns-QVXLboHm.mjs"; import { t as sanitizeContentBlocksImages } from "./tool-images-7bLaVXq8.mjs"; import { i as isThinkingLikeBlock, n as rewriteToolResultIds, r as sanitizeToolCallIdsForCloudCodeAssist } from "./tool-call-id-D6Bs5Pq7.mjs"; import { E as formatExecDeniedUserMessage, c as formatDiskSpaceErrorCopy, d as isLikelyHttpErrorText, f as isRawApiErrorPayload, p as isStreamingJsonParseError, r as AUTH_INVALID_TOKEN_USER_TEXT, s as formatBillingErrorMessage, u as isInvalidStreamingEventOrderError, x as renderRateLimitOrOverloadedCopy } from "./user-facing-text-D-rB13dP.mjs"; import { a as isContextOverflowErrorFromTables, o as isReasoningConstraintErrorMessage } from "./classify-core-CFIP01lJ.mjs"; import { c as isExactUnknownNoDetailsError, o as inferSignalStatus, u as isReplayInvalidErrorMessage } from "./classification-rules-lOx6QHfn.mjs"; import { a as renderFormatErrorCopy, i as renderAssistantRequestFailureCopy, r as renderAssistantFormatFailureCopy } from "./assistant-request-failure-copy-DW9ctsQh.mjs"; import { a as isLikelyContextOverflowError, n as classifyFailoverSignal, t as classifyFailoverReason } from "./classify-Ds2nZqCN.mjs"; import { o as stripThoughtSignatures } from "./bootstrap-Q-AcdcrE.mjs"; import { t as buildAssistantFailoverSignal } from "./assistant-message-failures-CImiKv64.mjs"; import { t as formatSandboxToolPolicyBlockedMessage } from "./runtime-status-Bfel3YpH.mjs"; import { t as isAnthropicApi } from "./anthropic-api-DJ0dgQAx.mjs"; import { normalizeOpenAIResponsesFunctionCallId, replaceCompactionReplayOwnerContent, shouldNormalizeOpenAIResponsesToolCallId, splitOpenAIFunctionCallPairing } from "@openclaw/ai/transports"; //#region src/agents/embedded-agent-helpers/context-overflow-observation.ts function isCompactionFailureError(errorMessage) { if (!errorMessage) return false; const lower = normalizeLowercaseStringOrEmpty(errorMessage); if (!(lower.includes("summarization failed") || lower.includes("auto-compaction") || lower.includes("compaction failed") || lower.includes("compaction"))) return false; if (isLikelyContextOverflowError(errorMessage)) return true; return lower.includes("context overflow"); } const OBSERVED_OVERFLOW_TOKEN_PATTERNS = [ /prompt is too long:\s*([\d,]+)\s+tokens\s*>\s*[\d,]+\s+maximum/i, /prompt is too long:\s*([\d,]+)\s*,\s*model maximum context length\s*:\s*[\d,]+/i, /requested\s+([\d,]+)\s+tokens/i, /token limit\s*:\s*[\d,]+\s*\(requested\s*:\s*([\d,]+)\)/i, /resulted in\s+([\d,]+)\s+tokens/i ]; const OBSERVED_OVERFLOW_TOKEN_SUM_PATTERNS = [/input length(?:\s+and\s+max_tokens)?\s+exceed\s+context(?:\s+limit|\s+window)?\s*\(i\.e\s*([\d,]+)\s*\+\s*([\d,]+)\s*>\s*[\d,]+\)/i, /input length\s+and\s+`max_tokens`\s+exceed\s+context\s+limit:\s*([\d,]+)\s*\+\s*([\d,]+)\s*>\s*[\d,]+/i]; function extractObservedOverflowTokenCount(errorMessage) { if (!errorMessage) return; for (const pattern of OBSERVED_OVERFLOW_TOKEN_SUM_PATTERNS) { const match = errorMessage.match(pattern); const rawLeft = match?.[1]?.replaceAll(",", ""); const rawRight = match?.[2]?.replaceAll(",", ""); if (!rawLeft || !rawRight) continue; const left = Number(rawLeft); const right = Number(rawRight); if (Number.isFinite(left) && left > 0 && Number.isFinite(right) && right >= 0) return Math.floor(left + right); } for (const pattern of OBSERVED_OVERFLOW_TOKEN_PATTERNS) { const rawCount = errorMessage.match(pattern)?.[1]?.replaceAll(",", ""); if (!rawCount) continue; const parsed = Number(rawCount); if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed); } } //#endregion //#region src/agents/embedded-agent-helpers/provider-runtime-failure.ts const AUTH_SCOPE_HINT_RE = /\b(?:missing|required|requires|insufficient)\s+(?:the\s+following\s+)?scopes?\b|\bmissing\s+scope\b/i; const AUTH_SCOPE_NAME_RE = /\b(?:api\.responses\.write|model\.request)\b/i; const AUTH_INVALID_TOKEN_HINT_RE = /\bunauthorized\b|\b(?:invalid|incorrect|expired|stale)[_\s-]?api[_\s-]?key\b|\b(?:invalid|incorrect|expired|stale)\s+(?:token|jwt|credential|api[_\s-]?key)\b|\b(?:token|jwt|credential|api[_\s-]?key)\s+(?:is\s+)?(?:invalid|incorrect|expired|stale)\b/i; const HTML_BODY_RE = /^\s*(?:<!doctype\s+html\b|<html\b)/i; const HTML_CLOSE_RE = /<\/html>/i; const CLOUDFLARE_CHALLENGE_RE = /Enable\s+JavaScript\s+and\s+cookies\s+to\s+continue|cf-browser-verification|__cf_challenge|cdn-cgi\/challenge-platform|challenge-error-text/i; const PROXY_ERROR_RE = /\bproxyconnect\b|\bhttps?_proxy\b|\b407\b|\bproxy authentication required\b|\btunnel connection failed\b|\bconnect tunnel\b|\bsocks proxy\b|\bproxy error\b/i; const DNS_ERROR_RE = /\benotfound\b|\beai_again\b|\bgetaddrinfo\b|\bno such host\b|\bdns\b/i; const INTERRUPTED_NETWORK_ERROR_RE = /\beconnrefused\b|\beconnreset\b|\beconnaborted\b|\benetreset\b|\behostunreach\b|\behostdown\b|\benetunreach\b|\bepipe\b|\bsocket hang up\b|\bconnection refused\b|\bconnection reset\b|\bconnection aborted\b|\bnetwork is unreachable\b|\bhost is unreachable\b|\bfetch failed\b|\bconnection error\b|\bnetwork request failed\b/i; const SANDBOX_BLOCKED_RE = /\bapproval is required\b|\bapproval timed out\b|\bapproval was denied\b|\bblocked by sandbox\b|\bsandbox\b.*\b(?:blocked|denied|forbidden|disabled|not allowed)\b|\bexec denied\s*\(/i; function stripErrorPrefix(raw) { return raw.replace(/^error:\s*/i, "").trim(); } function isHtmlErrorResponse(raw, status) { const trimmed = raw.trim(); if (!trimmed) return false; const candidate = extractLeadingHttpStatus(trimmed) ? trimmed : stripErrorPrefix(trimmed); const inferred = typeof status === "number" && Number.isFinite(status) ? status : extractLeadingHttpStatus(candidate)?.code; if (typeof inferred !== "number" || inferred < 400) return false; const rest = extractHttpResponseBody(extractLeadingHttpStatus(candidate))?.body ?? candidate; return HTML_BODY_RE.test(rest) && HTML_CLOSE_RE.test(rest); } function isCloudflareChallengeResponse(message) { return CLOUDFLARE_CHALLENGE_RE.test(message); } function isOpenAICodexScopeContext(raw, provider) { return normalizeLowercaseStringOrEmpty(provider) === "openai" || /\bopenai\s+codex\b/i.test(raw) || /\bcodex\b.*\bscopes?\b/i.test(raw); } function isAuthScopeErrorMessage(raw, status, provider) { if (!raw) return false; if (!isOpenAICodexScopeContext(raw, provider)) return false; const inferred = typeof status === "number" && Number.isFinite(status) ? status : extractLeadingHttpStatus(raw.trim())?.code; const hasScopeHint = AUTH_SCOPE_HINT_RE.test(raw); const hasKnownScopeName = AUTH_SCOPE_NAME_RE.test(raw); if (!hasScopeHint && !hasKnownScopeName) return false; if (typeof inferred !== "number") return hasScopeHint; if (inferred !== 401 && inferred !== 403) return false; return true; } function isProxyErrorMessage(raw, status) { if (!raw) return false; if (status === 407) return true; return PROXY_ERROR_RE.test(raw); } function isDnsTransportErrorMessage(raw) { return DNS_ERROR_RE.test(raw); } function isSandboxBlockedErrorMessage(raw) { return Boolean(formatExecDeniedUserMessage(raw)) || SANDBOX_BLOCKED_RE.test(raw); } function isSchemaErrorMessage(raw, opts) { if (!raw || isReplayInvalidErrorMessage(raw) || isContextOverflowErrorFromTables(raw)) return false; return classifyFailoverReason(raw, opts) === "format" || matchesFormatErrorPattern(raw); } function isTimeoutTransportErrorMessage(raw, status) { if (!raw) return false; if (isTimeoutErrorMessage(raw) || INTERRUPTED_NETWORK_ERROR_RE.test(raw)) return true; if (typeof status === "number" && [ 408, 499, 500, 502, 503, 504, 521, 522, 523, 524, 529 ].includes(status)) return true; return false; } function isOAuthRefreshTimeoutMessage(raw) { return /\boauth refresh call\b.*\bexceeded hard timeout\b/i.test(raw); } function isOAuthRefreshContentionMessage(raw) { return /\brefresh_contention\b/i.test(raw) || /\bfile lock timeout\b/i.test(raw) && /(?:\/|\\|^)(?:oauth-refresh|openclaw-oauth-refresh)[^/\n\\]*?(?:\.lock)?\b/i.test(raw); } function isOAuthCallbackTimeoutMessage(raw) { return /\bcallback_timeout\b/i.test(raw); } function isOAuthCallbackValidationMessage(raw) { return /\bcallback_validation_failed\b/i.test(raw); } function classifyProviderRuntimeFailureKind(signal, opts) { const normalizedSignal = typeof signal === "string" ? { message: signal } : signal; if (classifyGatewayStorageFailure(normalizedSignal)) return "gateway_storage"; const message = normalizedSignal.message?.trim() ?? ""; const status = inferSignalStatus(normalizedSignal); const hasStructuredErrorSignal = Boolean(normalizedSignal.code || normalizedSignal.errorType); if (!message && typeof status !== "number" && !hasStructuredErrorSignal) return "empty_response"; if (normalizedSignal.code === "refresh_contention") return "refresh_contention"; if (message && isOAuthRefreshContentionMessage(message)) return "refresh_contention"; if (message && isOAuthRefreshTimeoutMessage(message)) return "refresh_timeout"; if (message && isOAuthCallbackTimeoutMessage(message)) return "callback_timeout"; if (message && isOAuthCallbackValidationMessage(message)) return "callback_validation"; if (message && classifyOAuthRefreshFailure(message)) return "auth_refresh"; if (message && isAuthScopeErrorMessage(message, status, normalizedSignal.provider)) return "auth_scope"; if (message && isProxyErrorMessage(message, status)) return "proxy"; if (message && isHtmlErrorResponse(message, status)) { if (status === 403 && isCloudflareChallengeResponse(message)) return "upstream_html"; return status === 401 || status === 403 ? "auth_html" : "upstream_html"; } const failoverClassification = classifyFailoverSignal({ ...normalizedSignal, status, message: message || void 0 }, opts); const failoverReason = failoverClassification?.kind === "reason" ? failoverClassification.reason : void 0; switch (failoverReason) { case "tls_certificate": case "rate_limit": case "model_not_found": return failoverReason; } if (message && isDnsTransportErrorMessage(message)) return "dns"; if (message && isSandboxBlockedErrorMessage(message)) return "sandbox_blocked"; if (message && isReplayInvalidErrorMessage(message)) return "replay_invalid"; if (message && isSchemaErrorMessage(message, { ...opts, provider: normalizedSignal.provider })) return "schema"; const messageMentions401 = /\b401\b/.test(message); const messageMentions403 = /\b403\b/.test(message); const has401Evidence = status === 401 || status === void 0 && messageMentions401 && !messageMentions403; const hasPermissionScopeSignal = AUTH_SCOPE_HINT_RE.test(message) || AUTH_SCOPE_NAME_RE.test(message); if (failoverReason === "auth" && has401Evidence && AUTH_INVALID_TOKEN_HINT_RE.test(message) && !hasPermissionScopeSignal) return "auth_invalid_token"; if (failoverReason === "timeout" || failoverReason === "overloaded") return "timeout"; if (message && isTimeoutTransportErrorMessage(message, status)) return "timeout"; if (message && isExactUnknownNoDetailsError(message)) return "no_error_details"; return "unclassified"; } //#endregion //#region src/agents/embedded-agent-helpers/error-text.ts const log = createSubsystemLogger("errors"); const sandboxToolPolicyAuditMessages = /* @__PURE__ */ new WeakSet(); const GENERIC_ASSISTANT_ERROR_TEXT = "LLM request failed."; const SYNTHESIZED_TIMEOUT_ERROR_TEXT = "LLM request timed out."; const RUNTIME_FAILURE_COPY = { auth_refresh: "Authentication refresh failed. Re-authenticate this provider and try again.", refresh_contention: "Authentication refresh is already in progress elsewhere and this attempt timed out waiting for it. Retry in a moment.", refresh_timeout: "Authentication refresh timed out before the provider completed. Retry in a moment; re-authenticate only if it keeps failing.", callback_timeout: "Browser OAuth did not complete before manual fallback kicked in. Retry the login flow and paste the redirect URL if prompted.", callback_validation: "Browser OAuth returned an invalid or incomplete callback. Retry the login flow and make sure the full redirect URL is pasted if prompted.", auth_scope: "Authentication is missing the required OpenAI ChatGPT scopes. Re-run OpenAI login and try again.", auth_html: "Authentication failed at the provider. Re-authenticate and verify your provider credentials and account access.", auth_invalid_token: AUTH_INVALID_TOKEN_USER_TEXT, upstream_html: "The provider returned an HTML error page instead of an API response. This usually means a CDN or gateway (e.g. Cloudflare) blocked the request. Retry in a moment or check provider status.", proxy: "LLM request failed: proxy or tunnel configuration blocked the provider request.", tls_certificate: "LLM request failed: TLS certificate validation rejected the provider endpoint. Check the endpoint hostname, proxy, and local certificate trust.", model_not_found: "The selected model was not found by the provider. Check the model id or choose a different model." }; const TOOL_CALL_INPUT_MISSING_RE = /tool_(?:use|call)\.(?:input|arguments).*?(?:field required|required)/i; const TOOL_CALL_INPUT_PATH_RE = /messages\.\d+\.content\.\d+\.tool_(?:use|call)\.(?:input|arguments)/i; function classifyAssistantErrorFacts(msg, opts) { const signal = buildAssistantFailoverSignal(msg, { provider: opts?.providerOwner?.id ?? opts?.provider }); const providerPlugin = opts?.providerOwner ?? null; const classification = classifyFailoverSignal(signal, { providerPlugin }); return { provider: opts?.provider ?? msg.provider ?? opts?.providerOwner?.id, model: opts?.model ?? msg.model, reason: classification?.kind === "reason" ? classification.reason : classification ? "context_overflow" : null, status: signal.status ?? extractErrorHttpStatus(signal.message ?? "")?.code, providerRuntimeFailureKind: classifyProviderRuntimeFailureKind(signal, { providerPlugin }), storageFailure: classifyGatewayStorageFailure(msg), code: signal.code }; } function isMissingToolCallInputError(raw) { return Boolean(raw) && (TOOL_CALL_INPUT_MISSING_RE.test(raw) || TOOL_CALL_INPUT_PATH_RE.test(raw)); } function formatAssistantErrorText(msg, opts, facts) { const raw = (msg.errorMessage ?? "").trim(); if (msg.stopReason !== "error" && !raw) return; const providerRefusalText = formatProviderRefusalText(msg); if (providerRefusalText) return providerRefusalText; const formatCopy = renderFormatErrorCopy(raw); const classifiedFacts = facts ?? classifyAssistantErrorFacts(msg, opts); if (classifiedFacts.storageFailure) return renderAssistantRequestFailureCopy(classifiedFacts); const { reason: failoverReason, status: formatStatus, providerRuntimeFailureKind } = classifiedFacts; const unknownTool = raw.match(/unknown tool[:\s]+["']?([a-z0-9_-]+)["']?/i) ?? raw.match(/tool\s+["']?([a-z0-9_-]+)["']?\s+(?:not found|is not available)/i); if (unknownTool?.[1]) { const audit = !sandboxToolPolicyAuditMessages.has(msg); const rewritten = formatSandboxToolPolicyBlockedMessage({ cfg: opts?.cfg, sessionKey: opts?.sessionKey, agentId: opts?.agentId, toolName: unknownTool[1], audit }); if (rewritten) { if (audit) sandboxToolPolicyAuditMessages.add(msg); return rewritten; } } const diskSpaceCopy = formatDiskSpaceErrorCopy(raw); if (diskSpaceCopy) return diskSpaceCopy; const runtimeCopy = RUNTIME_FAILURE_COPY[providerRuntimeFailureKind]; if (runtimeCopy) return runtimeCopy; if (failoverReason === "billing") return formatBillingErrorMessage(opts?.provider, opts?.model ?? msg.model, opts?.authMode); const transientCopy = failoverReason === "rate_limit" || failoverReason === "overloaded" ? renderRateLimitOrOverloadedCopy({ reason: failoverReason, raw }) : void 0; if (transientCopy) return transientCopy; if ((formatStatus === 400 || formatStatus === 422) && formatCopy !== "LLM request failed: provider rejected the request schema or tool payload.") return formatCopy; if (failoverReason === "context_overflow") return "Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model."; if (isReasoningConstraintErrorMessage(raw)) return "Reasoning is required for this model endpoint. Use /think minimal (or any non-off level) and try again."; if (isInvalidStreamingEventOrderError(raw)) return "LLM request failed: provider returned an invalid streaming response. Please try again."; if (/incorrect role information|roles must alternate|400.*role|"message".*role.*information/i.test(raw)) return "Message ordering conflict - please try again. If this persists, use /new to start a fresh session."; if (isMissingToolCallInputError(raw)) return "Session history looks corrupted (tool call input missing). Use /new to start a fresh session. If this keeps happening, reset the session or delete the corrupted session transcript."; if (providerRuntimeFailureKind === "replay_invalid") return "Session history or replay state is invalid. Use /new to start a fresh session and try again."; const apiError = parseApiErrorInfo(raw); if (providerRuntimeFailureKind === "schema" && apiError?.type?.toLowerCase().includes("invalid_request") && apiError.message?.trim()) return `LLM request rejected: ${apiError.message.trim()}`; if (isGenericProviderInternalError(raw)) return formatRawAssistantErrorForUi(raw); const transportCopy = formatTransportErrorCopy(msg.errorCode && isKnownTransportErrorCode(msg.errorCode) ? `${raw} ${msg.errorCode}` : raw); if (transportCopy) return transportCopy; if (isProviderCompletedErrorFinishReasonMessage(raw)) return formatRawAssistantErrorForUi(raw); if (isTimeoutErrorMessage(raw) && !(facts?.status !== void 0 && facts.status >= 500)) return SYNTHESIZED_TIMEOUT_ERROR_TEXT; if (providerRuntimeFailureKind === "schema" || failoverReason === "format") return formatCopy; if (!raw) return failoverReason ? renderAssistantRequestFailureCopy(classifiedFacts) : "LLM request failed with an unknown error."; if (isRawApiErrorPayload(raw) || isLikelyHttpErrorText(raw)) return formatRawAssistantErrorForUi(raw); if (isStreamingJsonParseError(raw)) return "LLM streaming response contained a malformed fragment. Please try again."; if (raw.length > 600) log.warn(`Long error truncated: ${truncateUtf16Safe(raw, 200)}`); return raw.length > 600 ? `${truncateUtf16Safe(raw, 600)}…` : raw; } function isRawAssistantErrorPassthrough(params) { const friendlyError = params.friendlyError?.trim(); const rawError = params.rawError?.trim(); if (!friendlyError || !rawError) return false; const parsedMessage = parseApiErrorInfo(rawError)?.message?.trim(); const leadingStatusRest = extractLeadingHttpStatus(rawError)?.rest?.trim(); const hasRawDerivedProviderPrefix = friendlyError.startsWith("LLM request rejected:") || friendlyError.startsWith("LLM error") || friendlyError.startsWith("HTTP "); return friendlyError === rawError && friendlyError !== "LLM request timed out." || rawError.length > 600 && friendlyError === `${truncateUtf16Safe(rawError, 600)}…` || Boolean(parsedMessage && hasRawDerivedProviderPrefix) || Boolean(leadingStatusRest && friendlyError.startsWith("HTTP ")); } function formatUserFacingAssistantErrorText(msg, opts) { const rawError = msg.errorMessage?.trim(); const facts = classifyAssistantErrorFacts(msg, opts); const friendlyError = formatAssistantErrorText(msg, opts, facts); const rawPassthrough = isRawAssistantErrorPassthrough({ friendlyError, rawError }); const schemaFriendlyError = friendlyError === "LLM request failed: provider rejected the request schema or tool payload." || friendlyError?.startsWith("LLM request rejected:"); const safeFriendlyError = (schemaFriendlyError ? renderAssistantFormatFailureCopy(msg) : void 0) ?? (rawPassthrough ? schemaFriendlyError ? "LLM request failed: provider rejected the request schema or tool payload." : void 0 : friendlyError); if (safeFriendlyError) return safeFriendlyError.trim(); return renderAssistantRequestFailureCopy(facts) ?? "LLM request failed."; } //#endregion //#region src/agents/embedded-agent-helpers/openai.ts /** * Normalizes OpenAI Responses reasoning/tool-call history for safe replay. */ function parseOpenAIReasoningSignature(value) { if (!value) return null; let candidate = null; if (typeof value === "string") { const trimmed = value.trim(); if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null; try { candidate = JSON.parse(trimmed); } catch { return null; } } else if (typeof value === "object") candidate = value; if (!candidate) return null; const id = typeof candidate.id === "string" ? candidate.id : ""; const type = typeof candidate.type === "string" ? candidate.type : ""; if (!id.startsWith("rs_")) return null; if (type === "reasoning" || type.startsWith("reasoning.")) return { id, type }; return null; } function parseTimestampMs(value) { return parseDateFirstTimestampMs(value) ?? null; } function isOpenAIToolCallType(type) { return type === "toolCall" || type === "toolUse" || type === "functionCall"; } function createOpenAIResponsesToolCallIdResolver() { const rewrittenByOriginalId = /* @__PURE__ */ new Map(); return (id) => { const rewritten = rewrittenByOriginalId.get(id); if (rewritten) return rewritten; if (!shouldNormalizeOpenAIResponsesToolCallId(id)) return id; const normalized = normalizeOpenAIResponsesFunctionCallId(id); rewrittenByOriginalId.set(id, normalized); return normalized; }; } /** * OpenAI Responses rejects replayed `function_call.call_id`, * `function_call.id`, and matching `function_call_output.call_id` values * that exceed its 64-char `call_*` / `fc_*` shape. pi-ai skips its own * normalizer for same-model replay, then splits persisted `call_id|fc_id` * pairs directly into the provider payload, so OpenClaw must normalize here. */ function normalizeOpenAIResponsesToolCallIds(messages) { let changed = false; const resolveId = createOpenAIResponsesToolCallIdResolver(); const rewrittenMessages = []; for (const msg of messages) { if (!msg || typeof msg !== "object") { rewrittenMessages.push(msg); continue; } const role = msg.role; if (role === "assistant") { const assistantMsg = msg; if (!Array.isArray(assistantMsg.content)) { rewrittenMessages.push(msg); continue; } let assistantChanged = false; const nextContent = assistantMsg.content.map((block) => { if (!block || typeof block !== "object") return block; const toolCallBlock = block; if (!isOpenAIToolCallType(toolCallBlock.type) || typeof toolCallBlock.id !== "string") return block; const nextId = resolveId(toolCallBlock.id); if (nextId === toolCallBlock.id) return block; assistantChanged = true; return { ...block, id: nextId }; }); if (!assistantChanged) { rewrittenMessages.push(msg); continue; } changed = true; rewrittenMessages.push(replaceCompactionReplayOwnerContent(assistantMsg, nextContent)); continue; } if (role === "toolResult") { const next = rewriteToolResultIds({ message: msg, resolveId }); if (next !== msg) changed = true; rewrittenMessages.push(next); continue; } rewrittenMessages.push(msg); } return changed ? rewrittenMessages : messages; } /** * OpenAI can reject replayed `function_call` items with an `fc_*` id if the * matching `reasoning` item is absent in the same assistant turn. * * When that pairing is missing, strip the `|fc_*` suffix from tool call ids so * shared model runtime omits `function_call.id` on replay. */ function downgradeOpenAIFunctionCallReasoningPairs(messages) { let changed = false; const rewrittenMessages = []; let pendingRewrittenIds = null; for (const msg of messages) { if (!msg || typeof msg !== "object") { pendingRewrittenIds = null; rewrittenMessages.push(msg); continue; } const role = msg.role; if (role === "assistant") { const assistantMsg = msg; if (!Array.isArray(assistantMsg.content)) { pendingRewrittenIds = null; rewrittenMessages.push(msg); continue; } const localRewrittenIds = /* @__PURE__ */ new Map(); let seenReplayableReasoning = false; let assistantChanged = false; const nextContent = assistantMsg.content.map((block) => { if (!block || typeof block !== "object") return block; const thinkingBlock = block; if (thinkingBlock.type === "thinking" && parseOpenAIReasoningSignature(thinkingBlock.thinkingSignature)) { seenReplayableReasoning = true; return block; } const toolCallBlock = block; if (!isOpenAIToolCallType(toolCallBlock.type) || typeof toolCallBlock.id !== "string") return block; const pairing = splitOpenAIFunctionCallPairing(toolCallBlock.id); if (seenReplayableReasoning || !pairing.itemId || !pairing.itemId.startsWith("fc_")) return block; assistantChanged = true; localRewrittenIds.set(toolCallBlock.id, pairing.callId); return { ...block, id: pairing.callId }; }); pendingRewrittenIds = localRewrittenIds.size > 0 ? localRewrittenIds : null; if (!assistantChanged) { rewrittenMessages.push(msg); continue; } changed = true; rewrittenMessages.push(replaceCompactionReplayOwnerContent(assistantMsg, nextContent)); continue; } if (role === "toolResult" && pendingRewrittenIds && pendingRewrittenIds.size > 0) { const toolResult = msg; let toolResultChanged = false; const updates = {}; if (typeof toolResult.toolCallId === "string") { const nextToolCallId = pendingRewrittenIds.get(toolResult.toolCallId); if (nextToolCallId && nextToolCallId !== toolResult.toolCallId) { updates.toolCallId = nextToolCallId; toolResultChanged = true; } } if (typeof toolResult.toolUseId === "string") { const nextToolUseId = pendingRewrittenIds.get(toolResult.toolUseId); if (nextToolUseId && nextToolUseId !== toolResult.toolUseId) { updates.toolUseId = nextToolUseId; toolResultChanged = true; } } if (!toolResultChanged) { rewrittenMessages.push(msg); continue; } changed = true; rewrittenMessages.push({ ...toolResult, ...updates }); continue; } pendingRewrittenIds = null; rewrittenMessages.push(msg); } return changed ? rewrittenMessages : messages; } /** * Extracts the Responses `phase` (commentary/final_answer) from a v1 textSignature, if present. * Used when dropping the paired msg_* id so phase metadata can be preserved independently. */ function extractTextSignaturePhase(signature) { if (!signature.startsWith("{")) return; try { const parsed = JSON.parse(signature); if (parsed.v === 1 && (parsed.phase === "commentary" || parsed.phase === "final_answer")) return parsed.phase; } catch {} } /** * Drops reasoning from before a model route switch and clears paired message ids. * The transport owns orphan detection after preparing the actual replay payload. */ function dropStaleOpenAIReasoning(messages, dropBefore) { if (dropBefore === void 0) return messages; let anyChanged = false; const out = []; for (const msg of messages) { if (!msg || typeof msg !== "object") { out.push(msg); continue; } if (msg.role !== "assistant") { out.push(msg); continue; } const assistantMsg = msg; if (!Array.isArray(assistantMsg.content)) { out.push(msg); continue; } const messageTimestamp = parseTimestampMs(assistantMsg.timestamp); if (messageTimestamp !== null && messageTimestamp > dropBefore) { out.push(msg); continue; } let changed = false; let droppedReplayableReasoning = false; const nextContent = []; for (const block of assistantMsg.content) { if (!block) { changed = true; continue; } if (typeof block !== "object") { nextContent.push(block); continue; } const record = block; if (record.type !== "thinking") { nextContent.push(block); continue; } if (!parseOpenAIReasoningSignature(record.thinkingSignature)) { nextContent.push(block); continue; } changed = true; droppedReplayableReasoning = true; } if (!changed) { out.push(msg); continue; } anyChanged = true; if (nextContent.length === 0) continue; const finalContent = droppedReplayableReasoning ? nextContent.map((contentBlock) => { if (!contentBlock || typeof contentBlock !== "object") return contentBlock; if (contentBlock.type !== "text" || contentBlock.textSignature === void 0) return contentBlock; const phase = extractTextSignaturePhase(contentBlock.textSignature); const { textSignature: _droppedTextSignature, ...rest } = contentBlock; return phase !== void 0 ? { ...rest, textSignature: JSON.stringify({ v: 1, phase }) } : rest; }) : nextContent; out.push(replaceCompactionReplayOwnerContent(assistantMsg, finalContent)); } return anyChanged ? out : messages; } //#endregion //#region src/agents/embedded-agent-helpers/images.ts /** * Sanitizes historical embedded-agent message images and empty content blocks. */ const EMPTY_CONTENT_PLACEHOLDER = "[empty content omitted]"; function dropEmptyTextBlocks(content) { return content.filter((block) => { const rec = block; return !block || typeof block !== "object" || rec.type !== "text" || typeof rec.text !== "string" || rec.text.trim().length > 0; }); } function ensureNonEmptyContent(content) { if (content.length > 0) return content; return [{ type: "text", text: EMPTY_CONTENT_PLACEHOLDER }]; } /** Resize/remove unsafe image payloads while keeping transcript turns valid. */ async function sanitizeSessionMessagesImages(messages, label, options) { const imageSanitization = { maxDimensionPx: options?.maxDimensionPx, maxBytes: options?.maxBytes }; const sanitizedIds = options?.sanitizeToolCallIds === true ? sanitizeToolCallIdsForCloudCodeAssist(messages, options.toolCallIdMode, { preserveNativeAnthropicToolUseIds: options?.preserveNativeAnthropicToolUseIds, duplicateToolCallIdStyle: options?.duplicateToolCallIdStyle }) : messages; const out = []; for (const msg of sanitizedIds) { if (!msg || typeof msg !== "object") { out.push(msg); continue; } const role = msg.role; if (role === "toolResult") { const toolMsg = msg; const content = Array.isArray(toolMsg.content) ? toolMsg.content : []; const nextContent = await sanitizeContentBlocksImages(content, label, imageSanitization); out.push({ ...toolMsg, content: ensureNonEmptyContent(dropEmptyTextBlocks(nextContent)) }); continue; } if (role === "user") { const userMsg = msg; const content = userMsg.content; if (Array.isArray(content)) { const nextContent = await sanitizeContentBlocksImages(content, label, imageSanitization); out.push({ ...userMsg, content: ensureNonEmptyContent(dropEmptyTextBlocks(nextContent)) }); continue; } } if (role === "assistant") { const assistantMsg = msg; const content = assistantMsg.content; if (Array.isArray(content)) { const strippedContent = assistantMsg.stopReason === "error" || options?.preserveSignatures ? content : stripThoughtSignatures(content, options?.sanitizeThoughtSignatures); const finalContent = await sanitizeContentBlocksImages(dropEmptyTextBlocks(strippedContent), label, imageSanitization); if (finalContent.length > 0 || assistantMsg.providerReplay) out.push(replaceCompactionReplayOwnerContent(assistantMsg, finalContent)); continue; } } out.push(msg); } return out; } //#endregion //#region src/agents/embedded-agent-helpers/thinking.ts /** * Resolves fallback thinking levels for providers that require reasoning. */ function extractSupportedValues(raw) { const fragment = raw.match(/supported values(?: are)?:\s*([^\n.]+)/i)?.[1]; if (!fragment) return []; const quoted = Array.from(fragment.matchAll(/['"]([^'"]+)['"]/g), ([, value]) => value); return normalizeStringEntries(quoted.length > 0 ? quoted : fragment.split(/,|\band\b/gi).map((entry) => entry.replace(/^[^a-zA-Z]+|[^a-zA-Z]+$/g, ""))); } /** Pick a configured or provider-safe reasoning level for fallback attempts. */ function pickFallbackThinkingLevel(params) { const raw = params.message?.trim() ?? ""; const requiresReasoning = isReasoningConstraintErrorMessage(raw); if (!requiresReasoning && !/(?<![\w./-])(?:think(?:ing)?(?:[._]|\s+)(?:value|level|budget)|reasoning(?:[._]|\s+)(?:effort|level|budget))(?![\w/-]|\.[\w/-])/i.test(raw)) return; if (requiresReasoning && !params.attempted.has("minimal")) return "minimal"; const supported = extractSupportedValues(raw); if (supported.length === 0) return /not supported/i.test(raw) && !params.attempted.has("off") ? "off" : void 0; for (const entry of supported) { const normalized = normalizeThinkLevel(entry); if (normalized && !params.attempted.has(normalized)) return normalized; } } //#endregion //#region src/agents/embedded-agent-helpers/turns.ts /** * Normalizes embedded-agent conversation turn ordering for provider contracts. */ const SIGNED_THINKING_PROVIDERS = /* @__PURE__ */ new Set([ "anthropic", "amazon-bedrock", "anthropic-vertex" ]); /** Return true when a provider family owns signed thinking blocks. */ function providerRequiresSignedThinking(provider) { return SIGNED_THINKING_PROVIDERS.has(normalizeProviderId(provider ?? "")); } /** Decide whether signed thinking can be replayed under the current provider policy. */ function shouldAllowProviderOwnedThinkingReplay(params) { const hasProviderOwnedSignedThinking = params.policy.preserveSignatures || providerRequiresSignedThinking(params.provider); return isAnthropicApi(params.modelApi) && params.policy.validateAnthropicTurns && hasProviderOwnedSignedThinking && !params.policy.dropThinkingBlocks; } /** * Bedrock Converse still requires strict role alternation, so only the direct * Messages API keeps consecutive user turns separate under append-only replay. */ function shouldMergeConsecutiveUserTurns(policy, modelApi) { return !(policy.appendOnlyRuntimeContext && modelApi === "anthropic-messages"); } function isToolCallBlock(block) { return block.type === "toolUse" || block.type === "toolCall" || block.type === "functionCall"; } function isAbortedAssistantTurn(message) { const stopReason = message.stopReason; return stopReason === "aborted" || stopReason === "error"; } function extractToolResultMatchIds(record) { const ids = /* @__PURE__ */ new Set(); for (const value of [ Reflect.get(record, "toolUseId"), Reflect.get(record, "toolCallId"), Reflect.get(record, "tool_use_id"), Reflect.get(record, "tool_call_id"), Reflect.get(record, "callId"), Reflect.get(record, "call_id") ]) { const id = normalizeOptionalString(value); if (id) ids.add(id); } return ids; } function extractToolResultMatchName(record) { return normalizeOptionalString(Reflect.get(record, "toolName")) ?? normalizeOptionalString(Reflect.get(record, "name")) ?? null; } function collectAnyToolResultIds(message) { const ids = /* @__PURE__ */ new Set(); const role = message.role; if (role === "toolResult") { const toolResultId = extractToolResultId(message); if (toolResultId) ids.add(toolResultId); } else if (role === "tool") for (const id of extractToolResultMatchIds(message)) ids.add(id); const content = message.content; if (!Array.isArray(content)) return ids; for (const block of content) { if (!block || typeof block !== "object") continue; const record = block; if (record.type !== "toolResult" && record.type !== "tool") continue; for (const id of extractToolResultMatchIds(record)) ids.add(id); } return ids; } function collectTrustedToolResultMatches(message) { const matches = /* @__PURE__ */ new Map(); const role = message.role; const addMatch = (ids, toolName) => { for (const id of ids) { const bucket = matches.get(id) ?? /* @__PURE__ */ new Set(); if (toolName) bucket.add(toolName); matches.set(id, bucket); } }; if (role === "toolResult") addMatch([...extractToolResultMatchIds(message), ...(() => { const canonicalId = extractToolResultId(message); return canonicalId ? [canonicalId] : []; })()], extractToolResultMatchName(message)); else if (role === "tool") addMatch(extractToolResultMatchIds(message), extractToolResultMatchName(message)); return matches; } function collectFutureToolResultMatches(messages, startIndex) { const matches = /* @__PURE__ */ new Map(); for (let index = startIndex + 1; index < messages.length; index += 1) { const candidate = messages[index]; if (!candidate || typeof candidate !== "object") continue; if (candidate.role === "assistant") break; for (const [id, toolNames] of collectTrustedToolResultMatches(candidate)) { const bucket = matches.get(id) ?? /* @__PURE__ */ new Set(); for (const toolName of toolNames) bucket.add(toolName); matches.set(id, bucket); } } return matches; } function collectFutureToolResultIds(messages, startIndex) { const ids = /* @__PURE__ */ new Set(); for (let index = startIndex + 1; index < messages.length; index += 1) { const candidate = messages[index]; if (!candidate || typeof candidate !== "object") continue; if (candidate.role === "assistant") break; for (const id of collectAnyToolResultIds(candidate)) ids.add(id); } return ids; } /** * Strips dangling tool-call blocks from assistant messages when no later * tool-result span before the next assistant turn resolves them. * This fixes the "tool_use ids found without tool_result blocks" error from Anthropic. */ function stripDanglingAnthropicToolUses(messages) { const result = []; for (const [i, msg] of messages.entries()) { if (!msg) continue; if (typeof msg !== "object") { result.push(msg); continue; } if (msg.role !== "assistant") { result.push(msg); continue; } const assistantMsg = msg; const originalContent = Array.isArray(assistantMsg.content) ? assistantMsg.content : []; if (originalContent.length === 0) { result.push(msg); continue; } if (extractToolCallsFromAssistant(msg).length === 0) { result.push(msg); continue; } const hasThinking = originalContent.some((block) => isThinkingLikeBlock(block)); const validToolResultMatches = collectFutureToolResultMatches(messages, i); const validToolUseIds = collectFutureToolResultIds(messages, i); if (hasThinking) { if (originalContent.every((block) => { if (!block || !isToolCallBlock(block)) return true; const blockId = normalizeOptionalString(block.id); const blockName = normalizeOptionalString(block.name); if (!blockId || !blockName) return false; const matchingToolNames = validToolResultMatches.get(blockId); if (!matchingToolNames) return false; return matchingToolNames.size === 0 || matchingToolNames.has(blockName); })) result.push(msg); else result.push({ ...assistantMsg, content: isAbortedAssistantTurn(msg) ? [] : [{ type: "text", text: "[tool calls omitted]" }] }); continue; } const filteredContent = originalContent.filter((block) => { if (!block) return false; if (!isToolCallBlock(block)) return true; const blockId = normalizeOptionalString(block.id); return blockId ? validToolUseIds.has(blockId) : false; }); if (filteredContent.length === originalContent.length) { result.push(msg); continue; } if (originalContent.length > 0 && filteredContent.length === 0) result.push({ ...assistantMsg, content: isAbortedAssistantTurn(msg) ? [] : [{ type: "text", text: "[tool calls omitted]" }] }); else result.push({ ...assistantMsg, content: filteredContent }); } return result; } function validateTurnsWithConsecutiveMerge(params) { const { messages, role, merge } = params; if (!Array.isArray(messages) || messages.length === 0) return messages; const result = []; let lastRole; for (const msg of messages) { if (!msg || typeof msg !== "object") { result.push(msg); continue; } const msgRole = msg.role; if (!msgRole) { result.push(msg); continue; } if (msgRole === lastRole && lastRole === role) { const lastMsg = result[result.length - 1]; const currentMsg = msg; if (lastMsg && typeof lastMsg === "object") { const lastTyped = lastMsg; result[result.length - 1] = merge(lastTyped, currentMsg); continue; } } result.push(msg); lastRole = msgRole; } return result.length === messages.length ? messages : result; } function mergeConsecutiveAssistantTurns(previous, current) { const mergedContent = [...Array.isArray(previous.content) ? previous.content : [], ...Array.isArray(current.content) ? current.content : []]; return { ...previous, content: mergedContent, ...current.usage && { usage: current.usage }, ...current.stopReason && { stopReason: current.stopReason }, ...current.errorMessage && { errorMessage: current.errorMessage } }; } /** * Validates and fixes conversation turn sequences for Gemini API. * Gemini requires strict alternating user→assistant→tool→user pattern. * Merges consecutive assistant messages together. */ function validateGeminiTurns(messages) { return validateTurnsWithConsecutiveMerge({ messages, role: "assistant", merge: mergeConsecutiveAssistantTurns }); } /** Merge adjacent user turns into a single provider-compatible user message. */ function mergeConsecutiveUserTurns(previous, current) { const mergedContent = [...normalizeUserContentForMerge(previous.content), ...normalizeUserContentForMerge(current.content)]; return { ...current, content: mergedContent, timestamp: current.timestamp ?? previous.timestamp }; } function normalizeUserContentForMerge(content) { if (Array.isArray(content)) return content; if (typeof content === "string") return [{ type: "text", text: content }]; return []; } const mergeConsecutiveUserMessages = (messages) => validateTurnsWithConsecutiveMerge({ messages, role: "user", merge: mergeConsecutiveUserTurns }); /** * Validates and fixes conversation turn sequences for Anthropic API. * Anthropic requires strict alternating user→assistant pattern. * Merges consecutive user messages together. * Also strips dangling tool_use blocks that lack corresponding tool_result blocks. */ function validateAnthropicTurns(messages, options = {}) { const stripped = stripDanglingAnthropicToolUses(validateTurnsWithConsecutiveMerge({ messages, role: "assistant", merge: mergeConsecutiveAssistantTurns })); if (options.mergeConsecutiveUserTurns === false) return stripped; return mergeConsecutiveUserMessages(stripped); } //#endregion export { extractObservedOverflowTokenCount as _, validateAnthropicTurns as a, sanitizeSessionMessagesImages as c, normalizeOpenAIResponsesToolCallIds as d, GENERIC_ASSISTANT_ERROR_TEXT as f, classifyProviderRuntimeFailureKind as g, formatUserFacingAssistantErrorText as h, shouldMergeConsecutiveUserTurns as i, downgradeOpenAIFunctionCallReasoningPairs as l, formatAssistantErrorText as m, providerRequiresSignedThinking as n, validateGeminiTurns as o, SYNTHESIZED_TIMEOUT_ERROR_TEXT as p, shouldAllowProviderOwnedThinkingReplay as r, pickFallbackThinkingLevel as s, mergeConsecutiveUserMessages as t, dropStaleOpenAIReasoning as u, isCompactionFailureError as v };