@@ -56,9 +56,6 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth" as const;
|
||||
const DEFAULT_MAX_RETRIES = 0;
|
||||
const BASE_DELAY_MS = 1000;
|
||||
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
||||
// Keep a bounded pre-header timeout so zero-event Codex SSE stalls fail instead of
|
||||
// leaving callers stuck on "Working..." indefinitely. See #4945.
|
||||
const DEFAULT_SSE_HEADER_TIMEOUT_MS = 20_000;
|
||||
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
|
||||
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
||||
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
||||
@@ -179,20 +176,6 @@ function normalizeTimeoutMs(value: number | undefined): number | undefined {
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function createSSEHeaderTimeout(): { signal: AbortSignal; clear: () => void; error: () => Error | undefined } {
|
||||
const controller = new AbortController();
|
||||
let error: Error | undefined;
|
||||
const timeout = setTimeout(() => {
|
||||
error = new Error(`Codex SSE response headers timed out after ${DEFAULT_SSE_HEADER_TIMEOUT_MS}ms`);
|
||||
controller.abort(error);
|
||||
}, DEFAULT_SSE_HEADER_TIMEOUT_MS);
|
||||
return {
|
||||
signal: controller.signal,
|
||||
clear: () => clearTimeout(timeout),
|
||||
error: () => error,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Stream Function
|
||||
// ============================================================================
|
||||
@@ -245,7 +228,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
websocketRequestId,
|
||||
);
|
||||
const bodyJson = JSON.stringify(body);
|
||||
const idleTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
|
||||
const httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
|
||||
const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
|
||||
const transport = options?.transport || "auto";
|
||||
const websocketDisabledForSession = transport !== "sse" && isWebSocketSseFallbackActive(options?.sessionId);
|
||||
@@ -269,7 +252,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
() => {
|
||||
websocketStarted = true;
|
||||
},
|
||||
idleTimeoutMs,
|
||||
httpTimeoutMs,
|
||||
websocketConnectTimeoutMs,
|
||||
options,
|
||||
);
|
||||
@@ -325,8 +308,9 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
}
|
||||
|
||||
try {
|
||||
const headerTimeout = createSSEHeaderTimeout();
|
||||
const combinedSignal = combineAbortSignals([options?.signal, headerTimeout.signal]);
|
||||
const headerTimeoutSignal =
|
||||
httpTimeoutMs !== undefined && httpTimeoutMs > 0 ? AbortSignal.timeout(httpTimeoutMs) : undefined;
|
||||
const combinedSignal = combineAbortSignals([options?.signal, headerTimeoutSignal]);
|
||||
try {
|
||||
response = await fetch(resolveCodexUrl(model.baseUrl), {
|
||||
method: "POST",
|
||||
@@ -335,11 +319,12 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
signal: combinedSignal.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
const timeoutError = headerTimeout.error();
|
||||
throw timeoutError && !options?.signal?.aborted ? timeoutError : error;
|
||||
if (headerTimeoutSignal?.aborted && !options?.signal?.aborted) {
|
||||
throw new Error(`Codex SSE response headers timed out after ${httpTimeoutMs}ms`);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
combinedSignal.cleanup();
|
||||
headerTimeout.clear();
|
||||
}
|
||||
await options?.onResponse?.(
|
||||
{ status: response.status, headers: headersToRecord(response.headers) },
|
||||
|
||||
Reference in New Issue
Block a user