Merge f93ea57eec into 09be5d45d5
This commit is contained in:
commit
3c4b59d0d9
@ -10,6 +10,7 @@ import {
|
|||||||
updateSessionStore,
|
updateSessionStore,
|
||||||
} from "../../config/sessions.js";
|
} from "../../config/sessions.js";
|
||||||
import { registerAgentRunContext } from "../../infra/agent-events.js";
|
import { registerAgentRunContext } from "../../infra/agent-events.js";
|
||||||
|
import { describeNetworkError } from "../../infra/errors.js";
|
||||||
import {
|
import {
|
||||||
resolveAgentDeliveryPlan,
|
resolveAgentDeliveryPlan,
|
||||||
resolveAgentOutboundTarget,
|
resolveAgentOutboundTarget,
|
||||||
@ -398,11 +399,12 @@ export const agentHandlers: GatewayRequestHandlers = {
|
|||||||
respond(true, payload, undefined, { runId });
|
respond(true, payload, undefined, { runId });
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
const error = errorShape(ErrorCodes.UNAVAILABLE, String(err));
|
const summary = describeNetworkError(err);
|
||||||
|
const error = errorShape(ErrorCodes.UNAVAILABLE, summary);
|
||||||
const payload = {
|
const payload = {
|
||||||
runId,
|
runId,
|
||||||
status: "error" as const,
|
status: "error" as const,
|
||||||
summary: String(err),
|
summary,
|
||||||
};
|
};
|
||||||
context.dedupe.set(`agent:${idem}`, {
|
context.dedupe.set(`agent:${idem}`, {
|
||||||
ts: Date.now(),
|
ts: Date.now(),
|
||||||
|
|||||||
91
src/infra/errors.test.ts
Normal file
91
src/infra/errors.test.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
describeNetworkError,
|
||||||
|
extractErrorCode,
|
||||||
|
formatErrorMessage,
|
||||||
|
formatUncaughtError,
|
||||||
|
} from "./errors.js";
|
||||||
|
|
||||||
|
describe("extractErrorCode", () => {
|
||||||
|
it("returns string code", () => {
|
||||||
|
expect(extractErrorCode({ code: "ECONNREFUSED" })).toBe("ECONNREFUSED");
|
||||||
|
});
|
||||||
|
it("returns number code as string", () => {
|
||||||
|
expect(extractErrorCode({ code: 42 })).toBe("42");
|
||||||
|
});
|
||||||
|
it("returns undefined for missing code", () => {
|
||||||
|
expect(extractErrorCode({})).toBeUndefined();
|
||||||
|
});
|
||||||
|
it("returns undefined for non-object", () => {
|
||||||
|
expect(extractErrorCode(null)).toBeUndefined();
|
||||||
|
expect(extractErrorCode("str")).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatErrorMessage", () => {
|
||||||
|
it("extracts Error.message", () => {
|
||||||
|
expect(formatErrorMessage(new Error("boom"))).toBe("boom");
|
||||||
|
});
|
||||||
|
it("returns string as-is", () => {
|
||||||
|
expect(formatErrorMessage("oops")).toBe("oops");
|
||||||
|
});
|
||||||
|
it("stringifies primitives", () => {
|
||||||
|
expect(formatErrorMessage(123)).toBe("123");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatUncaughtError", () => {
|
||||||
|
it("returns message for INVALID_CONFIG", () => {
|
||||||
|
const err = Object.assign(new Error("bad config"), { code: "INVALID_CONFIG" });
|
||||||
|
expect(formatUncaughtError(err)).toBe("bad config");
|
||||||
|
});
|
||||||
|
it("returns stack for generic errors", () => {
|
||||||
|
const err = new Error("fail");
|
||||||
|
expect(formatUncaughtError(err)).toContain("fail");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("describeNetworkError", () => {
|
||||||
|
it("returns plain message when no cause", () => {
|
||||||
|
const err = new Error("Connection error.");
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends cause error code", () => {
|
||||||
|
const cause = Object.assign(new Error("fetch failed"), {
|
||||||
|
code: "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
||||||
|
});
|
||||||
|
const err = Object.assign(new Error("Connection error."), { cause });
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error. (UNABLE_TO_VERIFY_LEAF_SIGNATURE)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends cause message when no code", () => {
|
||||||
|
const cause = new Error("fetch failed");
|
||||||
|
const err = Object.assign(new Error("Connection error."), { cause });
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error. (fetch failed)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prefers code over message", () => {
|
||||||
|
const cause = Object.assign(new Error("connect timeout"), {
|
||||||
|
code: "UND_ERR_CONNECT_TIMEOUT",
|
||||||
|
});
|
||||||
|
const err = Object.assign(new Error("Connection error."), { cause });
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error. (UND_ERR_CONNECT_TIMEOUT)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not duplicate detail already in message", () => {
|
||||||
|
const cause = new Error("Connection error.");
|
||||||
|
const err = Object.assign(new Error("Connection error."), { cause });
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles non-Error cause with code", () => {
|
||||||
|
const cause = { code: "ECONNREFUSED" };
|
||||||
|
const err = Object.assign(new Error("Connection error."), { cause });
|
||||||
|
expect(describeNetworkError(err)).toBe("Connection error. (ECONNREFUSED)");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles non-error input", () => {
|
||||||
|
expect(describeNetworkError("plain string")).toBe("plain string");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -21,6 +21,18 @@ export function formatErrorMessage(err: unknown): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Extract diagnostic detail from a network error's cause chain (e.g. APIConnectionError). */
|
||||||
|
export function describeNetworkError(err: unknown): string {
|
||||||
|
const msg = formatErrorMessage(err);
|
||||||
|
const cause = (err as { cause?: unknown })?.cause;
|
||||||
|
if (!cause) return msg;
|
||||||
|
const code = extractErrorCode(cause);
|
||||||
|
const causeMsg = cause instanceof Error ? cause.message : undefined;
|
||||||
|
const detail = code ?? causeMsg;
|
||||||
|
if (!detail || msg.includes(detail)) return msg;
|
||||||
|
return `${msg} (${detail})`;
|
||||||
|
}
|
||||||
|
|
||||||
export function formatUncaughtError(err: unknown): string {
|
export function formatUncaughtError(err: unknown): string {
|
||||||
if (extractErrorCode(err) === "INVALID_CONFIG") {
|
if (extractErrorCode(err) === "INVALID_CONFIG") {
|
||||||
return formatErrorMessage(err);
|
return formatErrorMessage(err);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user