openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
160 lines (159 loc) • 7.94 kB
JavaScript
import { i as isSilentReplyPayloadText } from "./tokens-DbeAFqg7.js";
import { t as classifyFailoverReason } from "./classify-GlkuT1ur.js";
import "./user-copy-CxiPNpae.js";
import { a as hasVisibleAgentPayload } from "./message-visibility-DVJxwfNj.js";
import { l as hasCommittedOutboundDeliveryEvidence } from "./delivery-evidence-5HyXxfSi.js";
//#region src/agents/embedded-agent-runner/result-fallback-classifier.ts
/** Classifies embedded-agent run results for model fallback decisions. */
/**
* Classifies embedded-agent terminal results for model fallback decisions.
*
* The classifier only flags failed invisible outcomes or exact generic external-runner failure
* copy; delivered messages, deliberate silent replies, hook blocks, and aborts must not trigger
* another model attempt.
*/
function isEmbeddedAgentRunResult(value) {
return Boolean(value && typeof value === "object" && "meta" in value && value.meta && typeof value.meta === "object");
}
/** Keeps final-candidate bookkeeping while surfacing the best trusted terminal payload. */
function mergeEmbeddedAgentRunResultForModelFallbackExhaustion(params) {
const executionTrace = params.latestResult.meta.executionTrace;
const filteredAttempts = executionTrace?.attempts?.filter((attempt) => attempt.result !== "success");
const traceNeedsNormalization = executionTrace !== void 0 && (executionTrace.winnerProvider !== void 0 || executionTrace.winnerModel !== void 0 || filteredAttempts?.length !== executionTrace.attempts?.length);
if (params.latestResult === params.preferredResult && !traceNeedsNormalization) return params.latestResult;
return {
...params.latestResult,
payloads: params.preferredResult.payloads,
meta: {
...params.latestResult.meta,
error: params.preferredResult.meta.error,
...traceNeedsNormalization ? { executionTrace: {
...executionTrace,
winnerProvider: void 0,
winnerModel: void 0,
attempts: filteredAttempts?.length ? filteredAttempts : void 0
} } : {}
}
};
}
function hasDeliberateSilentTerminalReply(result) {
if (result.meta.error?.kind === "hook_block") return true;
return [result.meta.finalAssistantRawText, result.meta.finalAssistantVisibleText].some((text) => typeof text === "string" && isSilentReplyPayloadText(text));
}
function hasIntentionalTerminalCompletion(result) {
return result.meta.intentionalTerminalCompletion === "tool-batch";
}
function hasDeliverableAssistantPayload(result) {
const finalVisibleText = result.meta?.finalAssistantVisibleText;
return typeof finalVisibleText === "string" && finalVisibleText.trim().length > 0 && !isSilentReplyPayloadText(finalVisibleText) || hasVisibleAgentPayload(result, {
includeErrorPayloads: false,
includeReasoningPayloads: false,
requireTerminalContent: true
});
}
function hasNonTextVisiblePayloadContent(payload) {
const { isError: _isError, text: _text, ...payloadWithoutText } = payload;
return hasDeliverableAssistantPayload({ payloads: [payloadWithoutText] });
}
function classifyGenericExternalRunFailurePayload(params) {
const payloads = params.result.payloads;
if (!Array.isArray(payloads) || payloads.length !== 1) return null;
const [payload] = payloads;
const text = payload?.text;
if (payload?.isError === true || payload?.isReasoning === true || typeof text !== "string" || text.trim() !== "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session." || !payload || hasNonTextVisiblePayloadContent(payload)) return null;
return {
message: `${params.provider}/${params.model} ended with a generic external runner failure: ${text}`,
reason: "format",
code: "generic_external_run_failure",
rawError: text
};
}
function classifyHarnessResult(params) {
switch (params.result.meta.agentHarnessResultClassification) {
case "empty": return {
message: `${params.provider}/${params.model} ended without a visible assistant reply`,
reason: "format",
code: "empty_result"
};
case "reasoning-only": return {
message: `${params.provider}/${params.model} ended with reasoning only`,
reason: "format",
code: "reasoning_only_result"
};
case "planning-only": return {
message: `${params.provider}/${params.model} ended with a structured plan but no final answer`,
reason: "format",
code: "planning_only_result"
};
default: return null;
}
}
function classifyProviderErrorPayloadReason(errorText, provider) {
if (!errorText.trim()) return null;
const failoverReason = classifyFailoverReason(errorText, { provider });
switch (failoverReason) {
case "auth":
case "auth_permanent":
case "billing":
case "rate_limit":
case "server_error":
case "overloaded": return failoverReason;
default: return null;
}
}
/** Returns a fallback classification when an embedded run failed without user-visible output. */
function classifyEmbeddedAgentRunResultForModelFallback(params) {
if (!isEmbeddedAgentRunResult(params.result)) return null;
if (hasIntentionalTerminalCompletion(params.result) || params.result.meta.aborted || params.hasDirectlySentBlockReply === true || params.hasBlockReplyPipelineOutput === true) return null;
const incompleteTurn = params.result.meta.error?.kind === "incomplete_turn";
if (incompleteTurn && params.result.meta.error?.fallbackSafe !== true) return null;
const fallbackSafeIncompleteTurn = incompleteTurn;
if (params.result.meta.replayInvalid === true && !fallbackSafeIncompleteTurn) return null;
if (hasCommittedOutboundDeliveryEvidence(params.result)) return null;
if (params.result.meta.error?.kind === "hook_block") return null;
const payloads = params.result.payloads ?? [];
const genericExternalFailureClassification = classifyGenericExternalRunFailurePayload({
provider: params.provider,
model: params.model,
result: params.result
});
if (genericExternalFailureClassification) return genericExternalFailureClassification;
if (hasDeliverableAssistantPayload(params.result)) return null;
if (fallbackSafeIncompleteTurn) return {
message: payloads.find((payload) => payload.isError === true && typeof payload.text === "string")?.text ?? `${params.provider}/${params.model} ended with an incomplete terminal response`,
reason: "format",
code: "incomplete_result",
preserveResultOnExhaustion: true,
preserveResultPriority: params.result.meta.error?.terminalPresentation === true ? 1 : 0
};
const harnessClassification = classifyHarnessResult({
provider: params.provider,
model: params.model,
result: params.result
});
if (harnessClassification) return harnessClassification;
const errorText = payloads.filter((payload) => payload?.isError === true).map((payload) => typeof payload.text === "string" ? payload.text : "").join("\n");
const failoverReason = classifyProviderErrorPayloadReason(errorText, params.provider);
if (failoverReason) return {
message: `${params.provider}/${params.model} ended with a provider error: ${errorText}`,
reason: failoverReason,
code: "embedded_error_payload",
rawError: errorText
};
if (hasDeliberateSilentTerminalReply(params.result)) return null;
if (errorText.trim()) return null;
if (payloads.some((payload) => payload.isError === true && hasNonTextVisiblePayloadContent(payload))) return null;
const assistantPayloads = payloads.filter((payload) => payload.isError !== true);
if (assistantPayloads.length > 0 && assistantPayloads.every((payload) => payload.isReasoning === true)) return {
message: `${params.provider}/${params.model} ended with reasoning only`,
reason: "format",
code: "reasoning_only_result"
};
return {
message: `${params.provider}/${params.model} ended without a visible assistant reply`,
reason: "format",
code: "empty_result"
};
}
//#endregion
export { mergeEmbeddedAgentRunResultForModelFallbackExhaustion as i, hasDeliberateSilentTerminalReply as n, hasIntentionalTerminalCompletion as r, classifyEmbeddedAgentRunResultForModelFallback as t };