fix: parse codex retry headers strictly · openclaw/openclaw@bcf354e
steipete
·
2026-05-29
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -245,4 +245,43 @@ describe("streamOpenAICodexResponses transport", () => {
|
245 | 245 | |
246 | 246 | expect(payload).toMatchObject({ prompt_cache_key: "stable-cache-key" }); |
247 | 247 | }); |
| 248 | + |
| 249 | +it.each(["1.5", "0x10"])( |
| 250 | +"ignores invalid Retry-After header delay values: %s", |
| 251 | +async (retryAfter) => { |
| 252 | +const fetchMock = vi |
| 253 | +.fn<typeof fetch>() |
| 254 | +.mockResolvedValueOnce( |
| 255 | +new Response("rate limited", { |
| 256 | +status: 429, |
| 257 | +headers: { "retry-after": retryAfter }, |
| 258 | +}), |
| 259 | +) |
| 260 | +.mockRejectedValueOnce(new Error("usage limit: stop after retry delay")); |
| 261 | +vi.stubGlobal("fetch", fetchMock); |
| 262 | +const setTimeoutSpy = vi |
| 263 | +.spyOn(globalThis, "setTimeout") |
| 264 | +.mockImplementation((callback: TimerHandler) => { |
| 265 | +if (typeof callback === "function") { |
| 266 | +callback(); |
| 267 | +} |
| 268 | +return 0 as unknown as ReturnType<typeof setTimeout>; |
| 269 | +}); |
| 270 | + |
| 271 | +const stream = streamOpenAICodexResponses(model, context, { |
| 272 | +apiKey: createJwt({ |
| 273 | +"https://api.openai.com/auth": { |
| 274 | +chatgpt_account_id: "acct-1", |
| 275 | +}, |
| 276 | +}), |
| 277 | +transport: "sse", |
| 278 | +}); |
| 279 | + |
| 280 | +const result = await stream.result(); |
| 281 | + |
| 282 | +expect(result.stopReason).toBe("error"); |
| 283 | +expect(fetchMock).toHaveBeenCalledTimes(2); |
| 284 | +expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 1000); |
| 285 | +}, |
| 286 | +); |
248 | 287 | }); |
| Original file line number | Diff line number | Diff line change |
|---|
@@ -56,6 +56,8 @@ import { buildBaseOptions } from "./simple-options.js";
|
56 | 56 | const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api"; |
57 | 57 | const MAX_RETRIES = 3; |
58 | 58 | const BASE_DELAY_MS = 1000; |
| 59 | +const RETRY_AFTER_HTTP_DATE_RE = |
| 60 | +/^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2} \d{2}:\d{2}:\d{2} GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [ \d]\d \d{2}:\d{2}:\d{2} \d{4})$/; |
59 | 61 | const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]); |
60 | 62 | const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009; |
61 | 63 | |
@@ -347,9 +349,9 @@ export const streamOpenAICodexResponses: StreamFunction<
|
347 | 349 | if (retryAfter) { |
348 | 350 | const trimmedRetryAfter = retryAfter.trim(); |
349 | 351 | const seconds = Number(trimmedRetryAfter); |
350 | | -if (/^\d+(?:\.\d+)?$/.test(trimmedRetryAfter) && Number.isFinite(seconds)) { |
| 352 | +if (/^\d+$/.test(trimmedRetryAfter) && Number.isFinite(seconds)) { |
351 | 353 | delayMs = Math.max(0, seconds * 1000); |
352 | | -} else { |
| 354 | +} else if (RETRY_AFTER_HTTP_DATE_RE.test(trimmedRetryAfter)) { |
353 | 355 | const date = Date.parse(trimmedRetryAfter); |
354 | 356 | if (!Number.isNaN(date)) { |
355 | 357 | delayMs = Math.max(0, date - Date.now()); |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。