feat(ai): use zstd compression for codex sse transport
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type * as NodeOs from "node:os";
|
||||
import type * as NodeZlib from "node:zlib";
|
||||
import type {
|
||||
Tool as OpenAITool,
|
||||
ResponseCreateParamsStreaming,
|
||||
@@ -58,6 +59,9 @@ const DEFAULT_MAX_RETRIES = 0;
|
||||
const BASE_DELAY_MS = 1000;
|
||||
const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
|
||||
const DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS = 15_000;
|
||||
// The Codex backend accepts zstd-compressed request bodies on the SSE responses
|
||||
// endpoint (the same endpoint the official Codex client compresses against).
|
||||
const REQUEST_COMPRESSION_ZSTD_LEVEL = 3;
|
||||
const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
||||
const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
|
||||
const WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE = "websocket_connection_limit_reached";
|
||||
@@ -177,6 +181,39 @@ function normalizeTimeoutMs(value: number | undefined): number | undefined {
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Request Compression
|
||||
// ============================================================================
|
||||
|
||||
type ProcessWithBuiltinModule = typeof process & {
|
||||
getBuiltinModule?: (id: "node:zlib") => typeof NodeZlib;
|
||||
};
|
||||
|
||||
function loadNodeZlib(): typeof NodeZlib | null {
|
||||
if (typeof process === "undefined" || !(process.versions?.node || process.versions?.bun)) {
|
||||
return null;
|
||||
}
|
||||
return (process as ProcessWithBuiltinModule).getBuiltinModule?.("node:zlib") ?? null;
|
||||
}
|
||||
|
||||
// Returns the zstd-compressed body bytes, or null when compression is
|
||||
// unavailable (browser/Vite builds). Callers fall back to sending the
|
||||
// uncompressed JSON when this returns null.
|
||||
function compressRequestBodyZstd(bodyJson: string): Uint8Array | null {
|
||||
const zlib = loadNodeZlib();
|
||||
if (!zlib || typeof zlib.zstdCompressSync !== "function") {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const compressed = zlib.zstdCompressSync(bodyJson, {
|
||||
params: { [zlib.constants.ZSTD_c_compressionLevel]: REQUEST_COMPRESSION_ZSTD_LEVEL },
|
||||
});
|
||||
return new Uint8Array(compressed.buffer, compressed.byteOffset, compressed.byteLength);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Main Stream Function
|
||||
// ============================================================================
|
||||
@@ -298,6 +335,15 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
}
|
||||
}
|
||||
|
||||
// Compress the request body once for the SSE path. The Codex backend
|
||||
// decodes Content-Encoding: zstd; the WebSocket transport above sends the
|
||||
// uncompressed JSON frame, matching the official Codex client.
|
||||
const compressedBody = compressRequestBodyZstd(bodyJson);
|
||||
if (compressedBody) {
|
||||
sseHeaders.set("content-encoding", "zstd");
|
||||
}
|
||||
const sseBody: Uint8Array | string = compressedBody ?? bodyJson;
|
||||
|
||||
// Fetch with retry logic for rate limits and transient errors
|
||||
let response: Response | undefined;
|
||||
let lastError: Error | undefined;
|
||||
@@ -316,7 +362,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
||||
response = await fetch(resolveCodexUrl(model.baseUrl), {
|
||||
method: "POST",
|
||||
headers: sseHeaders,
|
||||
body: bodyJson,
|
||||
body: sseBody,
|
||||
signal: combinedSignal.signal,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user