Files
openclaw/src/agents/pi-embedded-runner/run/failover-observation.ts
Altay 87d939be79 Agents: add embedded error observations (#41336)
Merged via squash.

Prepared head SHA: 4900042298
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
2026-03-09 22:27:05 +03:00

77 lines
2.9 KiB
TypeScript

import { redactIdentifier } from "../../../logging/redact-identifier.js";
import type { AuthProfileFailureReason } from "../../auth-profiles.js";
import {
buildApiErrorObservationFields,
sanitizeForConsole,
} from "../../pi-embedded-error-observation.js";
import type { FailoverReason } from "../../pi-embedded-helpers.js";
import { log } from "../logger.js";
export type FailoverDecisionLoggerInput = {
stage: "prompt" | "assistant";
decision: "rotate_profile" | "fallback_model" | "surface_error";
runId?: string;
rawError?: string;
failoverReason: FailoverReason | null;
profileFailureReason?: AuthProfileFailureReason | null;
provider: string;
model: string;
profileId?: string;
fallbackConfigured: boolean;
timedOut?: boolean;
aborted?: boolean;
status?: number;
};
export type FailoverDecisionLoggerBase = Omit<FailoverDecisionLoggerInput, "decision" | "status">;
export function normalizeFailoverDecisionObservationBase(
base: FailoverDecisionLoggerBase,
): FailoverDecisionLoggerBase {
return {
...base,
failoverReason: base.failoverReason ?? (base.timedOut ? "timeout" : null),
profileFailureReason: base.profileFailureReason ?? (base.timedOut ? "timeout" : null),
};
}
export function createFailoverDecisionLogger(
base: FailoverDecisionLoggerBase,
): (
decision: FailoverDecisionLoggerInput["decision"],
extra?: Pick<FailoverDecisionLoggerInput, "status">,
) => void {
const normalizedBase = normalizeFailoverDecisionObservationBase(base);
const safeProfileId = normalizedBase.profileId
? redactIdentifier(normalizedBase.profileId, { len: 12 })
: undefined;
const safeRunId = sanitizeForConsole(normalizedBase.runId) ?? "-";
const safeProvider = sanitizeForConsole(normalizedBase.provider) ?? "-";
const safeModel = sanitizeForConsole(normalizedBase.model) ?? "-";
const profileText = safeProfileId ?? "-";
const reasonText = normalizedBase.failoverReason ?? "none";
return (decision, extra) => {
const observedError = buildApiErrorObservationFields(normalizedBase.rawError);
log.warn("embedded run failover decision", {
event: "embedded_run_failover_decision",
tags: ["error_handling", "failover", normalizedBase.stage, decision],
runId: normalizedBase.runId,
stage: normalizedBase.stage,
decision,
failoverReason: normalizedBase.failoverReason,
profileFailureReason: normalizedBase.profileFailureReason,
provider: normalizedBase.provider,
model: normalizedBase.model,
profileId: safeProfileId,
fallbackConfigured: normalizedBase.fallbackConfigured,
timedOut: normalizedBase.timedOut,
aborted: normalizedBase.aborted,
status: extra?.status,
...observedError,
consoleMessage:
`embedded run failover decision: runId=${safeRunId} stage=${normalizedBase.stage} decision=${decision} ` +
`reason=${reasonText} provider=${safeProvider}/${safeModel} profile=${profileText}`,
});
};
}