openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
803 lines (802 loc) • 41 kB
JavaScript
import { v as parseDateFirstTimestampMs } from "./number-coercion-CLj0HTDM.js";
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { d as normalizeStringEntries } from "./string-normalization-DsCfAx8q.js";
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { o as sha256HexPrefixCore } from "./crypto-digest-C4hqTb_e.js";
import { s as normalizeThinkLevel } from "./thinking.shared-bHYuuc1L.js";
import { i as classifyOAuthRefreshFailure } from "./oauth-refresh-failure-DP-bO7C0.js";
import { a as resolveRetryAfterMs, n as isTerminalAssistantError } from "./retry-DXSwoVJ2.js";
import { a as formatProviderRefusalText, c as isGenericProviderInternalError, d as extractHttpResponseBody, l as parseApiErrorInfo, n as extractErrorHttpStatus, o as formatRawAssistantErrorForUi, r as extractLeadingHttpStatus } from "./assistant-error-format-oP4GVe5A.js";
import { c as isReasoningConstraintErrorMessage, d as isExactUnknownNoDetailsError, f as isReplayInvalidErrorMessage, n as classifyFailoverSignal, o as isContextOverflowErrorFromTables, s as isLikelyContextOverflowError, t as classifyFailoverReason, u as inferSignalStatus } from "./classify-GlkuT1ur.js";
import { d as isTimeoutErrorMessage, f as matchesFormatErrorPattern, i as isBillingErrorMessage, l as isRateLimitErrorMessage, n as isAuthErrorMessage, s as isProviderCompletedErrorFinishReasonMessage } from "./message-patterns-Cg93oCGB.js";
import { t as extractFailoverSignalDetails } from "./signal-details-CG2lfr-u.js";
import { a as sanitizeToolCallIdsForCloudCodeAssist, i as rewriteToolResultIds } from "./tool-call-id-DJzLQ9lS.js";
import "./thinking-DterdXhr.js";
import { t as sanitizeContentBlocksImages } from "./tool-images-BjAMsmBs.js";
import { O as formatExecDeniedUserMessage, S as renderRateLimitOrOverloadedCopy, a as PROVIDER_SCHEMA_REJECTION_USER_TEXT, c as formatTransportErrorCopy, d as isLikelyHttpErrorText, f as isRawApiErrorPayload, o as formatBillingErrorMessage, p as isStreamingJsonParseError, s as formatDiskSpaceErrorCopy, t as AUTH_INVALID_TOKEN_USER_TEXT, u as isInvalidStreamingEventOrderError, y as renderFormatErrorCopy } from "./user-copy-CxiPNpae.js";
import { o as stripThoughtSignatures } from "./bootstrap-C0kxr-xC.js";
import { t as formatSandboxToolPolicyBlockedMessage } from "./runtime-status-6c8Jb1Kg.js";
import { replaceCompactionReplayOwnerContent } from "@openclaw/ai/transports";
//#region src/agents/embedded-agent-helpers/assistant-message-failures.ts
function buildAssistantFailoverSignal(msg, opts) {
const retryAfterMs = resolveRetryAfterMs(msg.errorMessage);
return {
status: extractErrorHttpStatus(msg.errorMessage?.trim() ?? "")?.code,
...retryAfterMs === void 0 ? {} : { retryAfterMs },
code: msg.errorCode,
errorType: msg.errorType,
message: msg.errorMessage?.trim() || void 0,
provider: opts?.provider ?? msg.provider,
details: extractFailoverSignalDetails(msg.errorBody)
};
}
function classifyAssistantFailoverReason(msg, opts) {
if (!msg || msg.stopReason !== "error" || isTerminalAssistantError(msg)) return null;
const providerOwner = opts?.providerOwner;
const classification = classifyFailoverSignal(buildAssistantFailoverSignal(msg, { provider: providerOwner?.id ?? opts?.provider }), { providerPlugin: providerOwner });
return classification?.kind === "reason" ? classification.reason : classification ? "context_overflow" : null;
}
function isRateLimitAssistantError(msg) {
return msg?.stopReason === "error" && isRateLimitErrorMessage(msg.errorMessage ?? "");
}
function isBillingAssistantError(msg) {
return msg?.stopReason === "error" && isBillingErrorMessage(msg.errorMessage ?? "");
}
function isAuthAssistantError(msg) {
return msg?.stopReason === "error" && isAuthErrorMessage(msg.errorMessage ?? "");
}
function isFailoverAssistantError(msg) {
return classifyAssistantFailoverReason(msg) !== null;
}
//#endregion
//#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/failover/assistant-request-failure-copy.ts
const ASSISTANT_REQUEST_FAILURE_REASON = {
auth: "authentication failed",
auth_permanent: "authentication was rejected",
format: "request format rejected",
rate_limit: "rate limited",
overloaded: "provider overloaded",
billing: "provider billing issue",
server_error: "provider internal error",
timeout: "request timed out",
tls_certificate: "TLS certificate error",
context_overflow: "context limit exceeded",
model_not_found: "model not found",
session_expired: "provider session expired",
empty_response: "",
no_error_details: "",
unclassified: "",
unknown: ""
};
/** Render classified facts without exposing raw provider response text. */
function renderAssistantRequestFailureCopy(facts) {
const provider = facts.provider?.trim();
const model = facts.model?.trim();
const target = provider && model ? `${provider}/${model}` : provider || model;
const normalizedReason = facts.reason === "timeout" && typeof facts.status === "number" && facts.status >= 500 ? "server_error" : facts.reason;
const reason = normalizedReason ? ASSISTANT_REQUEST_FAILURE_REASON[normalizedReason] : void 0;
const httpStatus = facts.status;
const status = typeof httpStatus === "number" && Number.isInteger(httpStatus) && httpStatus >= 100 && httpStatus <= 599 ? `HTTP ${httpStatus}` : void 0;
const unclassified = !facts.reason || facts.reason === "unclassified" || facts.reason === "unknown";
if (!reason && !status && (!target || unclassified)) return target ? `⚠️ Agent run failed (${model ? "model" : "provider"}: ${target}).` : void 0;
const details = [reason, status].filter(Boolean);
const summary = `⚠️ ${target ? `${target} request failed` : "LLM request failed"}${details.length > 0 ? ` (${details.join(", ")})` : ""}.`;
if (normalizedReason === "overloaded" || normalizedReason === "server_error" || normalizedReason === "timeout" || normalizedReason === "rate_limit") return `${summary} This is usually temporary — try again shortly.`;
if (facts.reason === "auth" || facts.reason === "auth_permanent") return `${summary} Re-authenticate the provider and try again.`;
if (facts.reason === "billing") return `${summary} Check ${provider ? `${provider} billing` : "provider billing"} and try again.`;
return summary;
}
//#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;
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 MODEL_NOT_FOUND_USER_TEXT = "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 })
};
}
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);
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;
if (providerRuntimeFailureKind === "auth_refresh") return "Authentication refresh failed. Re-authenticate this provider and try again.";
if (providerRuntimeFailureKind === "refresh_contention") return "Authentication refresh is already in progress elsewhere and this attempt timed out waiting for it. Retry in a moment.";
if (providerRuntimeFailureKind === "refresh_timeout") return "Authentication refresh timed out before the provider completed. Retry in a moment; re-authenticate only if it keeps failing.";
if (providerRuntimeFailureKind === "callback_timeout") return "Browser OAuth did not complete before manual fallback kicked in. Retry the login flow and paste the redirect URL if prompted.";
if (providerRuntimeFailureKind === "callback_validation") return "Browser OAuth returned an invalid or incomplete callback. Retry the login flow and make sure the full redirect URL is pasted if prompted.";
if (providerRuntimeFailureKind === "auth_scope") return "Authentication is missing the required OpenAI ChatGPT scopes. Re-run OpenAI login and try again.";
if (providerRuntimeFailureKind === "auth_html") return "Authentication failed at the provider. Re-authenticate and verify your provider credentials and account access.";
if (providerRuntimeFailureKind === "auth_invalid_token") return AUTH_INVALID_TOKEN_USER_TEXT;
if (providerRuntimeFailureKind === "upstream_html") return "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.";
if (providerRuntimeFailureKind === "proxy") return "LLM request failed: proxy or tunnel configuration blocked the provider request.";
if (providerRuntimeFailureKind === "tls_certificate") return "LLM request failed: TLS certificate validation rejected the provider endpoint. Check the endpoint hostname, proxy, and local certificate trust.";
if (providerRuntimeFailureKind === "model_not_found") return MODEL_NOT_FOUND_USER_TEXT;
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(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 structuredSchemaDetail = [parseApiErrorInfo(rawError ?? ""), parseApiErrorInfo(typeof msg.errorBody === "string" ? msg.errorBody.trim() : "")].find((error) => error?.type?.toLowerCase().includes("invalid_request"))?.message;
const schemaFriendlyError = friendlyError === "LLM request failed: provider rejected the request schema or tool payload." || friendlyError?.startsWith("LLM request rejected:");
const safeFriendlyError = structuredSchemaDetail && schemaFriendlyError ? renderFormatErrorCopy(structuredSchemaDetail) : rawPassthrough ? schemaFriendlyError ? PROVIDER_SCHEMA_REJECTION_USER_TEXT : void 0 : friendlyError;
if (safeFriendlyError) return safeFriendlyError.trim();
return renderAssistantRequestFailureCopy(facts) ?? "LLM request failed.";
}
//#endregion
//#region src/agents/embedded-agent-helpers/google.ts
/**
* Google/Gemini-specific embedded-agent runtime helpers.
*/
/** Detects Google-owned embedded runtime APIs. */
function isGoogleModelApi(api) {
return api === "google-gemini-cli" || api === "google-generative-ai";
}
//#endregion
//#region src/agents/embedded-agent-helpers/openai.ts
/**
* Normalizes OpenAI Responses reasoning/tool-call history for safe replay.
*/
const OPENAI_RESPONSES_ID_MAX_LENGTH = 64;
const OPENAI_RESPONSES_CALL_ID_RE = /^call_[A-Za-z0-9_-]{1,59}$/;
const OPENAI_RESPONSES_FUNCTION_CALL_ITEM_ID_RE = /^fc_[A-Za-z0-9_-]{1,61}$/;
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 splitOpenAIFunctionCallPairing(id) {
const separator = id.indexOf("|");
if (separator <= 0 || separator >= id.length - 1) return { callId: id };
return {
callId: id.slice(0, separator),
itemId: id.slice(separator + 1)
};
}
function isOpenAIToolCallType(type) {
return type === "toolCall" || type === "toolUse" || type === "functionCall";
}
function shortOpenAIResponsesIdHash(id) {
return sha256HexPrefixCore(id, 10);
}
function sanitizeOpenAIResponsesIdTail(value) {
return value.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^_+|_+$/g, "");
}
function normalizeOpenAIResponsesIdPart(params) {
const trimmed = params.value.trim();
if (params.isValid(trimmed)) return trimmed;
const rawTail = trimmed.startsWith(params.prefix) ? trimmed.slice(params.prefix.length) : trimmed;
const hash = shortOpenAIResponsesIdHash(trimmed || params.prefix);
const maxTailLength = OPENAI_RESPONSES_ID_MAX_LENGTH - params.prefix.length;
const hashSuffix = `_${hash}`;
const tail = `${sanitizeOpenAIResponsesIdTail(rawTail).slice(0, Math.max(1, maxTailLength - hashSuffix.length)) || "id"}${hashSuffix}`.slice(0, maxTailLength);
return `${params.prefix}${tail}`;
}
function normalizeOpenAIResponsesFunctionCallId(id) {
const { callId, itemId } = splitOpenAIFunctionCallPairing(id);
const normalizedCallId = normalizeOpenAIResponsesIdPart({
value: itemId ? `${callId}|${itemId}` : callId,
prefix: "call_",
isValid: (value) => OPENAI_RESPONSES_CALL_ID_RE.test(value)
});
if (!itemId) return normalizedCallId;
return `${normalizedCallId}|${normalizeOpenAIResponsesIdPart({
value: itemId,
prefix: "fc_",
isValid: (value) => OPENAI_RESPONSES_FUNCTION_CALL_ITEM_ID_RE.test(value)
})}`;
}
function shouldNormalizeOpenAIResponsesToolCallId(id) {
const pairing = splitOpenAIFunctionCallPairing(id);
if (!OPENAI_RESPONSES_CALL_ID_RE.test(pairing.callId)) return true;
if (pairing.itemId === void 0) return false;
return !OPENAI_RESPONSES_FUNCTION_CALL_ITEM_ID_RE.test(pairing.itemId);
}
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 match = raw.match(/supported values are:\s*([^\n.]+)/i) ?? raw.match(/supported values:\s*([^\n.]+)/i);
if (!match?.[1]) return [];
const fragment = match[1];
const quoted = Array.from(fragment.matchAll(/['"]([^'"]+)['"]/g)).map((entry) => entry[1]?.trim());
if (quoted.length > 0) return normalizeStringEntries(quoted.filter((entry) => Boolean(entry)));
return normalizeStringEntries(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();
if (!raw) return;
if (isReasoningConstraintErrorMessage(raw) && !params.attempted.has("minimal")) return "minimal";
const supported = extractSupportedValues(raw);
if (supported.length === 0) {
if (/not supported/i.test(raw) && !params.attempted.has("off")) return "off";
return;
}
for (const entry of supported) {
const normalized = normalizeThinkLevel(entry);
if (!normalized) continue;
if (params.attempted.has(normalized)) continue;
return normalized;
}
}
//#endregion
export { isAuthAssistantError as _, normalizeOpenAIResponsesToolCallIds as a, isRateLimitAssistantError as b, SYNTHESIZED_TIMEOUT_ERROR_TEXT as c, classifyProviderRuntimeFailureKind as d, renderAssistantRequestFailureCopy as f, classifyAssistantFailoverReason as g, buildAssistantFailoverSignal as h, dropStaleOpenAIReasoning as i, formatAssistantErrorText as l, isCompactionFailureError as m, sanitizeSessionMessagesImages as n, isGoogleModelApi as o, extractObservedOverflowTokenCount as p, downgradeOpenAIFunctionCallReasoningPairs as r, GENERIC_ASSISTANT_ERROR_TEXT as s, pickFallbackThinkingLevel as t, formatUserFacingAssistantErrorText as u, isBillingAssistantError as v, isFailoverAssistantError as y };