mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-22 07:20:59 +00:00
Agent: clarify embedded transport errors
This commit is contained in:
@@ -125,6 +125,27 @@ describe("formatAssistantErrorText", () => {
|
||||
const msg = makeAssistantError("request ended without sending any chunks");
|
||||
expect(formatAssistantErrorText(msg)).toBe("LLM request timed out.");
|
||||
});
|
||||
|
||||
it("returns a connection-refused message for ECONNREFUSED failures", () => {
|
||||
const msg = makeAssistantError("connect ECONNREFUSED 127.0.0.1:443 during upstream call");
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM request failed: connection refused by the provider endpoint.",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns a DNS-specific message for provider lookup failures", () => {
|
||||
const msg = makeAssistantError("dial tcp: lookup api.example.com: no such host (ENOTFOUND)");
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM request failed: DNS lookup for the provider endpoint failed.",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns an interrupted-connection message for socket hang ups", () => {
|
||||
const msg = makeAssistantError("socket hang up");
|
||||
expect(formatAssistantErrorText(msg)).toBe(
|
||||
"LLM request failed: network connection was interrupted.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatRawAssistantErrorForUi", () => {
|
||||
|
||||
@@ -88,6 +88,14 @@ describe("sanitizeUserFacingText", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("returns a transport-specific message for prefixed ECONNREFUSED errors", () => {
|
||||
expect(
|
||||
sanitizeUserFacingText("Error: connect ECONNREFUSED 127.0.0.1:443", {
|
||||
errorContext: true,
|
||||
}),
|
||||
).toBe("LLM request failed: connection refused by the provider endpoint.");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
input: "Hello there!\n\nHello there!",
|
||||
|
||||
@@ -65,6 +65,57 @@ function formatRateLimitOrOverloadedErrorCopy(raw: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function formatTransportErrorCopy(raw: string): string | undefined {
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
const lower = raw.toLowerCase();
|
||||
|
||||
if (
|
||||
/\beconnrefused\b/i.test(raw) ||
|
||||
lower.includes("connection refused") ||
|
||||
lower.includes("actively refused")
|
||||
) {
|
||||
return "LLM request failed: connection refused by the provider endpoint.";
|
||||
}
|
||||
|
||||
if (
|
||||
/\beconnreset\b|\beconnaborted\b|\benetreset\b|\bepipe\b/i.test(raw) ||
|
||||
lower.includes("socket hang up") ||
|
||||
lower.includes("connection reset") ||
|
||||
lower.includes("connection aborted")
|
||||
) {
|
||||
return "LLM request failed: network connection was interrupted.";
|
||||
}
|
||||
|
||||
if (
|
||||
/\benotfound\b|\beai_again\b/i.test(raw) ||
|
||||
lower.includes("getaddrinfo") ||
|
||||
lower.includes("no such host") ||
|
||||
lower.includes("dns")
|
||||
) {
|
||||
return "LLM request failed: DNS lookup for the provider endpoint failed.";
|
||||
}
|
||||
|
||||
if (
|
||||
/\benetunreach\b|\behostunreach\b|\behostdown\b/i.test(raw) ||
|
||||
lower.includes("network is unreachable") ||
|
||||
lower.includes("host is unreachable")
|
||||
) {
|
||||
return "LLM request failed: the provider endpoint is unreachable from this host.";
|
||||
}
|
||||
|
||||
if (
|
||||
lower.includes("fetch failed") ||
|
||||
lower.includes("connection error") ||
|
||||
lower.includes("network request failed")
|
||||
) {
|
||||
return "LLM request failed: network connection error.";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isReasoningConstraintErrorMessage(raw: string): boolean {
|
||||
if (!raw) {
|
||||
return false;
|
||||
@@ -566,6 +617,11 @@ export function formatAssistantErrorText(
|
||||
return transientCopy;
|
||||
}
|
||||
|
||||
const transportCopy = formatTransportErrorCopy(raw);
|
||||
if (transportCopy) {
|
||||
return transportCopy;
|
||||
}
|
||||
|
||||
if (isTimeoutErrorMessage(raw)) {
|
||||
return "LLM request timed out.";
|
||||
}
|
||||
@@ -626,6 +682,10 @@ export function sanitizeUserFacingText(text: string, opts?: { errorContext?: boo
|
||||
if (prefixedCopy) {
|
||||
return prefixedCopy;
|
||||
}
|
||||
const transportCopy = formatTransportErrorCopy(trimmed);
|
||||
if (transportCopy) {
|
||||
return transportCopy;
|
||||
}
|
||||
if (isTimeoutErrorMessage(trimmed)) {
|
||||
return "LLM request timed out.";
|
||||
}
|
||||
|
||||
@@ -58,14 +58,16 @@ describe("handleAgentEnd", () => {
|
||||
expect(warn.mock.calls[0]?.[1]).toMatchObject({
|
||||
event: "embedded_run_agent_end",
|
||||
runId: "run-1",
|
||||
error: "connection refused",
|
||||
error: "LLM request failed: connection refused by the provider endpoint.",
|
||||
rawErrorPreview: "connection refused",
|
||||
consoleMessage:
|
||||
"embedded run agent end: runId=run-1 isError=true model=unknown provider=unknown error=LLM request failed: connection refused by the provider endpoint. rawError=connection refused",
|
||||
});
|
||||
expect(onAgentEvent).toHaveBeenCalledWith({
|
||||
stream: "lifecycle",
|
||||
data: {
|
||||
phase: "error",
|
||||
error: "connection refused",
|
||||
error: "LLM request failed: connection refused by the provider endpoint.",
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -92,7 +94,7 @@ describe("handleAgentEnd", () => {
|
||||
failoverReason: "overloaded",
|
||||
providerErrorType: "overloaded_error",
|
||||
consoleMessage:
|
||||
"embedded run agent end: runId=run-1 isError=true model=claude-test provider=anthropic error=The AI service is temporarily overloaded. Please try again in a moment.",
|
||||
'embedded run agent end: runId=run-1 isError=true model=claude-test provider=anthropic error=The AI service is temporarily overloaded. Please try again in a moment. rawError={"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -112,7 +114,7 @@ describe("handleAgentEnd", () => {
|
||||
const meta = warn.mock.calls[0]?.[1];
|
||||
expect(meta).toMatchObject({
|
||||
consoleMessage:
|
||||
"embedded run agent end: runId=run-1 isError=true model=claude sonnet 4 provider=anthropic]8;;https://evil.test error=connection refused",
|
||||
"embedded run agent end: runId=run-1 isError=true model=claude sonnet 4 provider=anthropic]8;;https://evil.test error=LLM request failed: connection refused by the provider endpoint. rawError=connection refused",
|
||||
});
|
||||
expect(meta?.consoleMessage).not.toContain("\n");
|
||||
expect(meta?.consoleMessage).not.toContain("\r");
|
||||
|
||||
@@ -50,6 +50,8 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
|
||||
const safeRunId = sanitizeForConsole(ctx.params.runId) ?? "-";
|
||||
const safeModel = sanitizeForConsole(lastAssistant.model) ?? "unknown";
|
||||
const safeProvider = sanitizeForConsole(lastAssistant.provider) ?? "unknown";
|
||||
const safeRawErrorPreview = sanitizeForConsole(observedError.rawErrorPreview);
|
||||
const rawErrorConsoleSuffix = safeRawErrorPreview ? ` rawError=${safeRawErrorPreview}` : "";
|
||||
ctx.log.warn("embedded run agent end", {
|
||||
event: "embedded_run_agent_end",
|
||||
tags: ["error_handling", "lifecycle", "agent_end", "assistant_error"],
|
||||
@@ -60,7 +62,7 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
|
||||
model: lastAssistant.model,
|
||||
provider: lastAssistant.provider,
|
||||
...observedError,
|
||||
consoleMessage: `embedded run agent end: runId=${safeRunId} isError=true model=${safeModel} provider=${safeProvider} error=${safeErrorText}`,
|
||||
consoleMessage: `embedded run agent end: runId=${safeRunId} isError=true model=${safeModel} provider=${safeProvider} error=${safeErrorText}${rawErrorConsoleSuffix}`,
|
||||
});
|
||||
emitAgentEvent({
|
||||
runId: ctx.params.runId,
|
||||
|
||||
Reference in New Issue
Block a user