Skip to content

Commit ae83f94

Browse files
committed
fix(shared): validate all parsed HTTP status prefixes
1 parent 35d9be0 commit ae83f94

2 files changed

Lines changed: 38 additions & 10 deletions

File tree

src/shared/assistant-error-format.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { describe, expect, it } from "vitest";
22
import {
33
extractLeadingHttpStatus,
44
extractProviderWrappedHttpStatus,
5+
formatRawAssistantErrorForUi,
6+
parseApiErrorInfo,
57
} from "./assistant-error-format.js";
68

79
describe("extractLeadingHttpStatus", () => {
@@ -55,3 +57,31 @@ describe("extractProviderWrappedHttpStatus", () => {
5557
expect(extractProviderWrappedHttpStatus("API error (600): something")).toBeNull();
5658
});
5759
});
60+
61+
describe("HTTP status consumers", () => {
62+
it("formats only status lines inside the HTTP range", () => {
63+
expect(formatRawAssistantErrorForUi("100 Continue")).toBe("HTTP 100: Continue");
64+
expect(formatRawAssistantErrorForUi("599 Provider Error")).toBe("HTTP 599: Provider Error");
65+
expect(formatRawAssistantErrorForUi("000 Invalid")).toBe("000 Invalid");
66+
expect(formatRawAssistantErrorForUi("600 Invalid")).toBe("600 Invalid");
67+
expect(formatRawAssistantErrorForUi("999 Invalid")).toBe("999 Invalid");
68+
});
69+
70+
it("does not attach invalid status prefixes to API payloads", () => {
71+
const payload = '{"type":"error","error":{"type":"server_error","message":"Provider failed."}}';
72+
73+
expect(parseApiErrorInfo(`599 ${payload}`)).toMatchObject({
74+
httpCode: "599",
75+
type: "server_error",
76+
message: "Provider failed.",
77+
});
78+
expect(formatRawAssistantErrorForUi(`599 ${payload}`)).toBe(
79+
"HTTP 599 server_error: Provider failed.",
80+
);
81+
82+
for (const code of ["000", "600", "999"]) {
83+
expect(parseApiErrorInfo(`${code} ${payload}`)).toBeNull();
84+
expect(formatRawAssistantErrorForUi(`${code} ${payload}`)).toBe(`${code} ${payload}`);
85+
}
86+
});
87+
});

src/shared/assistant-error-format.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { expectDefined } from "@openclaw/normalization-core";
21
// Assistant error formatting helpers normalize assistant-visible error payloads.
32
import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice";
43
const ERROR_PAYLOAD_PREFIX_RE =
@@ -107,7 +106,7 @@ function extractHttpStatusMatch(
107106
return null;
108107
}
109108
const code = Number(match[1]);
110-
if (!Number.isFinite(code) || code < 100 || code > 599) {
109+
if (!Number.isInteger(code) || code < 100 || code > 599) {
111110
return null;
112111
}
113112
return { code, rest: (match[2] ?? "").trim() };
@@ -174,10 +173,10 @@ export function parseApiErrorInfo(raw?: string): ApiErrorInfo | null {
174173
let httpCode: string | undefined;
175174
let candidate = trimmed;
176175

177-
const httpPrefixMatch = candidate.match(/^(\d{3})\s+(.+)$/s);
178-
if (httpPrefixMatch) {
179-
httpCode = httpPrefixMatch[1];
180-
candidate = expectDefined(httpPrefixMatch[2], "http prefix match capture group 2").trim();
176+
const httpPrefix = extractHttpStatusMatch(candidate.match(/^(\d{3})\s+(.+)$/s));
177+
if (httpPrefix) {
178+
httpCode = String(httpPrefix.code);
179+
candidate = httpPrefix.rest;
181180
}
182181

183182
const payload = parseApiErrorPayload(candidate);
@@ -249,11 +248,10 @@ export function formatRawAssistantErrorForUi(raw?: string): string {
249248
);
250249
}
251250

252-
const httpMatch = trimmed.match(HTTP_STATUS_PREFIX_RE);
251+
const httpMatch = extractHttpStatusMatch(trimmed.match(HTTP_STATUS_PREFIX_RE));
253252
if (httpMatch) {
254-
const rest = expectDefined(httpMatch[2], "http match capture group 2").trim();
255-
if (!rest.startsWith("{")) {
256-
return `HTTP ${httpMatch[1]}: ${rest}`;
253+
if (!httpMatch.rest.startsWith("{")) {
254+
return `HTTP ${httpMatch.code}: ${httpMatch.rest}`;
257255
}
258256
}
259257

0 commit comments

Comments
 (0)