Agents: log lifecycle error text for embedded run failures

This commit is contained in:
Vignesh Natarajan
2026-02-21 19:24:45 -08:00
parent 35fe33aa90
commit 68b92e80f7
3 changed files with 84 additions and 4 deletions

View File

@@ -0,0 +1,76 @@
import { describe, expect, it, vi } from "vitest";
import { createInlineCodeState } from "../markdown/code-spans.js";
import { handleAgentEnd } from "./pi-embedded-subscribe.handlers.lifecycle.js";
import type { EmbeddedPiSubscribeContext } from "./pi-embedded-subscribe.handlers.types.js";
vi.mock("../infra/agent-events.js", () => ({
emitAgentEvent: vi.fn(),
}));
function createContext(
lastAssistant: unknown,
overrides?: { onAgentEvent?: (event: unknown) => void },
): EmbeddedPiSubscribeContext {
return {
params: {
runId: "run-1",
config: {},
sessionKey: "agent:main:main",
onAgentEvent: overrides?.onAgentEvent,
},
state: {
lastAssistant: lastAssistant as EmbeddedPiSubscribeContext["state"]["lastAssistant"],
pendingCompactionRetry: 0,
blockState: {
thinking: true,
final: true,
inlineCode: createInlineCodeState(),
},
},
log: {
debug: vi.fn(),
warn: vi.fn(),
},
flushBlockReplyBuffer: vi.fn(),
resolveCompactionRetry: vi.fn(),
maybeResolveCompactionWait: vi.fn(),
} as unknown as EmbeddedPiSubscribeContext;
}
describe("handleAgentEnd", () => {
it("logs the resolved error message when run ends with assistant error", () => {
const onAgentEvent = vi.fn();
const ctx = createContext(
{
role: "assistant",
stopReason: "error",
errorMessage: "connection refused",
content: [{ type: "text", text: "" }],
},
{ onAgentEvent },
);
handleAgentEnd(ctx);
const warn = vi.mocked(ctx.log.warn);
expect(warn).toHaveBeenCalledTimes(1);
expect(warn.mock.calls[0]?.[0]).toContain("runId=run-1");
expect(warn.mock.calls[0]?.[0]).toContain("error=connection refused");
expect(onAgentEvent).toHaveBeenCalledWith({
stream: "lifecycle",
data: {
phase: "error",
error: "connection refused",
},
});
});
it("keeps non-error run-end logging on debug only", () => {
const ctx = createContext(undefined);
handleAgentEnd(ctx);
expect(ctx.log.warn).not.toHaveBeenCalled();
expect(ctx.log.debug).toHaveBeenCalledWith("embedded run agent end: runId=run-1 isError=false");
});
});

View File

@@ -29,8 +29,6 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
const lastAssistant = ctx.state.lastAssistant;
const isError = isAssistantMessage(lastAssistant) && lastAssistant.stopReason === "error";
ctx.log.debug(`embedded run agent end: runId=${ctx.params.runId} isError=${isError}`);
if (isError && lastAssistant) {
const friendlyError = formatAssistantErrorText(lastAssistant, {
cfg: ctx.params.config,
@@ -38,12 +36,16 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
provider: lastAssistant.provider,
model: lastAssistant.model,
});
const errorText = (friendlyError || lastAssistant.errorMessage || "LLM request failed.").trim();
ctx.log.warn(
`embedded run agent end: runId=${ctx.params.runId} isError=true error=${errorText}`,
);
emitAgentEvent({
runId: ctx.params.runId,
stream: "lifecycle",
data: {
phase: "error",
error: friendlyError || lastAssistant.errorMessage || "LLM request failed.",
error: errorText,
endedAt: Date.now(),
},
});
@@ -51,10 +53,11 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
stream: "lifecycle",
data: {
phase: "error",
error: friendlyError || lastAssistant.errorMessage || "LLM request failed.",
error: errorText,
},
});
} else {
ctx.log.debug(`embedded run agent end: runId=${ctx.params.runId} isError=${isError}`);
emitAgentEvent({
runId: ctx.params.runId,
stream: "lifecycle",