


















@@ -1075,6 +1075,67 @@ describe("buildGuardedModelFetch", () => {
10751075expect(response.headers.get("x-should-retry")).toBe("false");
10761076});
107710771078+function formatObsoleteHttpDates(date: Date): Array<[string, string]> {
1079+const dayNames = [
1080+["Sun", "Sunday"],
1081+["Mon", "Monday"],
1082+["Tue", "Tuesday"],
1083+["Wed", "Wednesday"],
1084+["Thu", "Thursday"],
1085+["Fri", "Friday"],
1086+["Sat", "Saturday"],
1087+] as const;
1088+const monthNames = [
1089+"Jan",
1090+"Feb",
1091+"Mar",
1092+"Apr",
1093+"May",
1094+"Jun",
1095+"Jul",
1096+"Aug",
1097+"Sep",
1098+"Oct",
1099+"Nov",
1100+"Dec",
1101+] as const;
1102+const [shortDay, longDay] = dayNames[date.getUTCDay()] ?? dayNames[0];
1103+const month = monthNames[date.getUTCMonth()] ?? monthNames[0];
1104+const day = String(date.getUTCDate()).padStart(2, "0");
1105+const shortYear = String(date.getUTCFullYear() % 100).padStart(2, "0");
1106+const hours = String(date.getUTCHours()).padStart(2, "0");
1107+const minutes = String(date.getUTCMinutes()).padStart(2, "0");
1108+const seconds = String(date.getUTCSeconds()).padStart(2, "0");
1109+const time = `${hours}:${minutes}:${seconds}`;
1110+return [
1111+["RFC 850", `${longDay}, ${day}-${month}-${shortYear} ${time} GMT`],
1112+[
1113+"asctime",
1114+`${shortDay} ${month} ${day.padStart(2, " ")} ${time} ${date.getUTCFullYear()}`,
1115+],
1116+];
1117+}
1118+1119+it.each([...formatObsoleteHttpDates(new Date(Date.now() + 120_000))])(
1120+"parses obsolete HTTP-date retry-after values: %s",
1121+async (_label, retryAfter) => {
1122+fetchWithSsrFGuardMock.mockResolvedValue({
1123+response: new Response(null, {
1124+status: 503,
1125+headers: { "retry-after": retryAfter },
1126+}),
1127+finalUrl: "https://api.anthropic.com/v1/messages",
1128+release: vi.fn(async () => undefined),
1129+});
1130+const response = await buildGuardedModelFetch(anthropicModel)(
1131+"https://api.anthropic.com/v1/messages",
1132+{ method: "POST" },
1133+);
1134+1135+expect(response.headers.get("x-should-retry")).toBe("false");
1136+},
1137+);
1138+10781139it("respects OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS", async () => {
10791140process.env.OPENCLAW_SDK_RETRY_MAX_WAIT_SECONDS = "10";
10801141fetchWithSsrFGuardMock.mockResolvedValue({
@@ -1203,22 +1264,25 @@ describe("buildGuardedModelFetch", () => {
12031264expect(response.headers.get("x-should-retry")).toBeNull();
12041265});
120512661206-it("treats malformed 429 retry-after values as terminal", async () => {
1207-fetchWithSsrFGuardMock.mockResolvedValue({
1208-response: new Response(null, {
1209-status: 429,
1210-headers: { "retry-after": "soon" },
1211-}),
1212-finalUrl: "https://api.anthropic.com/v1/messages",
1213-release: vi.fn(async () => undefined),
1214-});
1215-const response = await buildGuardedModelFetch(anthropicModel)(
1216-"https://api.anthropic.com/v1/messages",
1217-{ method: "POST" },
1218-);
1267+it.each(["soon", "1.5", "0x10"])(
1268+"treats malformed 429 retry-after values as terminal: %s",
1269+async (retryAfter) => {
1270+fetchWithSsrFGuardMock.mockResolvedValue({
1271+response: new Response(null, {
1272+status: 429,
1273+headers: { "retry-after": retryAfter },
1274+}),
1275+finalUrl: "https://api.anthropic.com/v1/messages",
1276+release: vi.fn(async () => undefined),
1277+});
1278+const response = await buildGuardedModelFetch(anthropicModel)(
1279+"https://api.anthropic.com/v1/messages",
1280+{ method: "POST" },
1281+);
121912821220-expect(response.headers.get("x-should-retry")).toBe("false");
1221-});
1283+expect(response.headers.get("x-should-retry")).toBe("false");
1284+},
1285+);
1222128612231287it("ignores retry-after on non-retryable responses", async () => {
12241288fetchWithSsrFGuardMock.mockResolvedValue({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。