openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
110 lines (109 loc) • 4.9 kB
JavaScript
import { a as isGpt5ModelId } from "./gpt5-prompt-overlay-D4Aews98.js";
import { i as isSilentReplyPayloadText } from "./tokens-U6o7_k27.js";
import { n as classifyFailoverReason } from "./errors-B5Da2wE-.js";
import { d as hasVisibleAgentPayload, u as hasOutboundDeliveryEvidence } from "./subagent-announce-delivery-B2BgoPnM.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; delivered messages, deliberate silent
* replies, hook blocks, and aborts must not trigger another model attempt.
*/
const EMPTY_TERMINAL_REPLY_RE = /Agent couldn't generate a response/i;
const PLAN_ONLY_TERMINAL_REPLY_RE = /Agent stopped after repeated plan-only turns/i;
function isEmbeddedAgentRunResult(value) {
return Boolean(value && typeof value === "object" && "meta" in value && value.meta && typeof value.meta === "object");
}
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 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} exhausted plan-only retries without taking action`,
reason: "format",
code: "planning_only_result"
};
default: return null;
}
}
/** Maps provider error payloads to fallback-safe business reasons. */
function classifyBusinessDenialErrorPayloadReason(errorText, provider) {
if (!errorText.trim()) return null;
const failoverReason = classifyFailoverReason(errorText, { provider });
switch (failoverReason) {
case "auth":
case "auth_permanent":
case "billing": 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 (params.result.meta.aborted || params.hasDirectlySentBlockReply === true || params.hasBlockReplyPipelineOutput === true || hasVisibleAgentPayload(params.result, {
includeErrorPayloads: false,
includeReasoningPayloads: false
})) return null;
if (hasOutboundDeliveryEvidence(params.result)) return null;
if (params.result.meta.error?.kind === "hook_block") return null;
const harnessClassification = classifyHarnessResult({
provider: params.provider,
model: params.model,
result: params.result
});
if (harnessClassification) return harnessClassification;
const payloads = params.result.payloads ?? [];
const errorText = payloads.filter((payload) => payload?.isError === true).map((payload) => typeof payload.text === "string" ? payload.text : "").join("\n");
if (EMPTY_TERMINAL_REPLY_RE.test(errorText)) return {
message: `${params.provider}/${params.model} ended with an incomplete terminal response`,
reason: "format",
code: "incomplete_result"
};
const failoverReason = classifyBusinessDenialErrorPayloadReason(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 (!isGpt5ModelId(params.model)) return null;
if (payloads.length === 0 && hasDeliberateSilentTerminalReply(params.result)) return null;
if (payloads.length === 0) return {
message: `${params.provider}/${params.model} ended without a visible assistant reply`,
reason: "format",
code: "empty_result"
};
if (payloads.every((payload) => payload.isReasoning === true)) return {
message: `${params.provider}/${params.model} ended with reasoning only`,
reason: "format",
code: "reasoning_only_result"
};
if (PLAN_ONLY_TERMINAL_REPLY_RE.test(errorText)) return {
message: `${params.provider}/${params.model} exhausted plan-only retries without taking action`,
reason: "format",
code: "planning_only_result"
};
if (!EMPTY_TERMINAL_REPLY_RE.test(errorText)) return null;
return {
message: `${params.provider}/${params.model} ended with an incomplete terminal response`,
reason: "format",
code: "incomplete_result"
};
}
//#endregion
export { classifyEmbeddedAgentRunResultForModelFallback as t };