UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

110 lines (109 loc) 4.9 kB
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 };