fix: surface external agent errors

This commit is contained in:
Peter Steinberger
2026-04-25 18:29:54 +01:00
parent f545872cbc
commit 5f4bc6ec02
3 changed files with 66 additions and 4 deletions

View File

@@ -1662,7 +1662,9 @@ describe("runAgentTurnWithFallback", () => {
expect(result.kind).toBe("final");
if (result.kind === "final") {
expect(result.payload.text).toContain("Something went wrong while processing your request");
expect(result.payload.text).toContain("Agent failed before reply");
expect(result.payload.text).toContain("All models failed");
expect(result.payload.text).toContain("402 (billing)");
expect(result.payload.text).not.toContain("Rate-limited");
}
});
@@ -1877,7 +1879,7 @@ describe("runAgentTurnWithFallback", () => {
expect(failMock).not.toHaveBeenCalled();
});
it("returns a friendly generic error on external chat channels", async () => {
it("forwards sanitized generic errors on external chat channels", async () => {
state.runEmbeddedPiAgentMock.mockRejectedValueOnce(
new Error("INVALID_ARGUMENT: some other failure"),
);
@@ -1910,7 +1912,47 @@ describe("runAgentTurnWithFallback", () => {
expect(result.kind).toBe("final");
if (result.kind === "final") {
expect(result.payload.text).toBe(
"⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.",
"⚠️ Agent failed before reply: INVALID_ARGUMENT: some other failure. Please try again, or use /new to start a fresh session.",
);
}
});
it("formats raw Codex API payloads before forwarding external errors", async () => {
state.runEmbeddedPiAgentMock.mockRejectedValueOnce(
new Error(
'Codex error: {"type":"error","error":{"type":"server_error","message":"Something exploded"},"sequence_number":2}',
),
);
const runAgentTurnWithFallback = await getRunAgentTurnWithFallback();
const result = await runAgentTurnWithFallback({
commandBody: "hello",
followupRun: createFollowupRun(),
sessionCtx: {
Provider: "whatsapp",
MessageSid: "msg",
} as unknown as TemplateContext,
opts: {},
typingSignals: createMockTypingSignaler(),
blockReplyPipeline: null,
blockStreamingEnabled: false,
resolvedBlockStreamingBreak: "message_end",
applyReplyToMode: (payload) => payload,
shouldEmitToolResult: () => true,
shouldEmitToolOutput: () => false,
pendingToolTasks: new Set(),
resetSessionAfterCompactionFailure: async () => false,
resetSessionAfterRoleOrderingConflict: async () => false,
isHeartbeat: false,
sessionKey: "main",
getActiveSessionEntry: () => undefined,
resolvedVerboseLevel: "off",
});
expect(result.kind).toBe("final");
if (result.kind === "final") {
expect(result.payload.text).toBe(
"⚠️ Agent failed before reply: LLM error server_error: Something exploded. Please try again, or use /new to start a fresh session.",
);
}
});

View File

@@ -352,6 +352,7 @@ function collapseRepeatedFailureDetail(message: string): string {
}
const SAFE_MISSING_API_KEY_PROVIDERS = new Set(["anthropic", "google", "openai", "openai-codex"]);
const EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS = 900;
function buildMissingApiKeyFailureText(message: string): string | null {
const normalizedMessage = collapseRepeatedFailureDetail(message);
@@ -369,6 +370,22 @@ function buildMissingApiKeyFailureText(message: string): string | null {
return "⚠️ Missing API key for the selected provider on the gateway. Configure provider auth, then try again.";
}
function formatForwardedExternalRunFailureText(message: string): string {
const sanitized = sanitizeUserFacingText(message, { errorContext: true })
.trim()
.replace(/^\s*/u, "")
.replace(/\s+/gu, " ");
if (!sanitized) {
return "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.";
}
const detail =
sanitized.length > EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS
? `${sanitized.slice(0, EXTERNAL_RUN_FAILURE_DETAIL_MAX_CHARS - 1).trimEnd()}`
: sanitized;
const suffix = /[.!?]$/u.test(detail) ? "" : ".";
return `⚠️ Agent failed before reply: ${detail}${suffix} Please try again, or use /new to start a fresh session.`;
}
function buildExternalRunFailureText(message: string): string {
const normalizedMessage = collapseRepeatedFailureDetail(message);
if (isToolResultTurnMismatchError(normalizedMessage)) {
@@ -386,7 +403,7 @@ function buildExternalRunFailureText(message: string): string {
}
return `⚠️ Model login failed on the gateway${oauthRefreshFailure.provider ? ` for ${oauthRefreshFailure.provider}` : ""}. Please try again. If this keeps happening, re-auth with \`${loginCommand}\`.`;
}
return "⚠️ Something went wrong while processing your request. Please try again, or use /new to start a fresh session.";
return formatForwardedExternalRunFailureText(normalizedMessage);
}
function shouldApplyOpenAIGptChatGuard(params: { provider?: string; model?: string }): boolean {