codex-copilot-dx 0.4.33 → 0.4.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,10 @@ import { status } from "./status.mjs";
4
4
 
5
5
  const DEFAULT_MAX_BODY_BYTES = 64 * 1024 * 1024;
6
6
  const DEFAULT_MAX_DECODED_BODY_BYTES = 128 * 1024 * 1024;
7
+ const DEFAULT_MAX_INFLIGHT_BODY_BYTES = 32 * 1024 * 1024;
8
+ const DEFAULT_MAX_QUEUED_REQUESTS = 16;
9
+ const DEFAULT_REQUEST_QUEUE_TIMEOUT_MS = 120 * 1000;
10
+ const COMPRESSED_BODY_WEIGHT_MULTIPLIER = 4;
7
11
  const gunzipAsync = promisify(zlib.gunzip);
8
12
  const inflateAsync = promisify(zlib.inflate);
9
13
  const brotliDecompressAsync = promisify(zlib.brotliDecompress);
@@ -16,6 +20,9 @@ function positiveInt(value, fallback) {
16
20
 
17
21
  const MAX_BODY_BYTES = positiveInt(process.env.CCDX_MAX_BODY_BYTES, DEFAULT_MAX_BODY_BYTES);
18
22
  const MAX_DECODED_BODY_BYTES = positiveInt(process.env.CCDX_MAX_DECODED_BODY_BYTES, DEFAULT_MAX_DECODED_BODY_BYTES);
23
+ const MAX_INFLIGHT_BODY_BYTES = positiveInt(process.env.CCDX_MAX_INFLIGHT_BODY_BYTES, DEFAULT_MAX_INFLIGHT_BODY_BYTES);
24
+ const MAX_QUEUED_REQUESTS = positiveInt(process.env.CCDX_MAX_QUEUED_REQUESTS, DEFAULT_MAX_QUEUED_REQUESTS);
25
+ const REQUEST_QUEUE_TIMEOUT_MS = positiveInt(process.env.CCDX_REQUEST_QUEUE_TIMEOUT_MS, DEFAULT_REQUEST_QUEUE_TIMEOUT_MS);
19
26
 
20
27
  export function httpError(message, statusCode) {
21
28
  const err = new Error(message);
@@ -31,6 +38,108 @@ function requestContentLength(req) {
31
38
  return Number.parseInt(req.headers?.["content-length"] || "", 10);
32
39
  }
33
40
 
41
+ function requestAdmissionWeight(req, maxBytes) {
42
+ const encodings = contentEncodings(req.headers?.["content-encoding"]);
43
+ const contentLength = requestContentLength(req);
44
+ if (!Number.isFinite(contentLength) || contentLength < 0) return maxBytes;
45
+ const compressed = encodings.some((encoding) => encoding !== "identity");
46
+ const weightedLength = compressed ? contentLength * COMPRESSED_BODY_WEIGHT_MULTIPLIER : contentLength;
47
+ return Math.max(1, Math.min(weightedLength, maxBytes));
48
+ }
49
+
50
+ function admissionAbortError(signal) {
51
+ if (signal?.reason instanceof Error) return signal.reason;
52
+ const error = new Error("The operation was aborted");
53
+ error.name = "AbortError";
54
+ return error;
55
+ }
56
+
57
+ export function createRequestAdmission({
58
+ maxBytes = MAX_INFLIGHT_BODY_BYTES,
59
+ maxQueued = MAX_QUEUED_REQUESTS,
60
+ waitTimeoutMs = REQUEST_QUEUE_TIMEOUT_MS,
61
+ } = {}) {
62
+ const byteLimit = positiveInt(maxBytes, MAX_INFLIGHT_BODY_BYTES);
63
+ const queueLimit = positiveInt(maxQueued, MAX_QUEUED_REQUESTS);
64
+ const timeoutMs = positiveInt(waitTimeoutMs, REQUEST_QUEUE_TIMEOUT_MS);
65
+ const queue = [];
66
+ let activeBytes = 0;
67
+
68
+ const remove = (entry) => {
69
+ const index = queue.indexOf(entry);
70
+ if (index >= 0) queue.splice(index, 1);
71
+ };
72
+
73
+ const activate = (entry) => {
74
+ entry.cleanup();
75
+ activeBytes += entry.weight;
76
+ let released = false;
77
+ entry.resolve(() => {
78
+ if (released) return;
79
+ released = true;
80
+ activeBytes = Math.max(0, activeBytes - entry.weight);
81
+ drain();
82
+ });
83
+ };
84
+
85
+ const drain = () => {
86
+ for (let index = 0; index < queue.length;) {
87
+ const entry = queue[index];
88
+ if (entry.cancelled) {
89
+ queue.splice(index, 1);
90
+ continue;
91
+ }
92
+ if (activeBytes + entry.weight > byteLimit) {
93
+ index += 1;
94
+ continue;
95
+ }
96
+ queue.splice(index, 1);
97
+ activate(entry);
98
+ }
99
+ };
100
+
101
+ const acquire = (req, { signal } = {}) => {
102
+ if (signal?.aborted) return Promise.reject(admissionAbortError(signal));
103
+ const weight = requestAdmissionWeight(req, byteLimit);
104
+ if (queue.length >= queueLimit && activeBytes + weight > byteLimit) {
105
+ return Promise.reject(httpError(`Request queue is full (${queueLimit} waiting)`, 503));
106
+ }
107
+
108
+ return new Promise((resolve, reject) => {
109
+ let timer;
110
+ const entry = {
111
+ cancelled: false,
112
+ weight,
113
+ resolve,
114
+ cleanup: () => {
115
+ if (timer) clearTimeout(timer);
116
+ signal?.removeEventListener("abort", onAbort);
117
+ },
118
+ };
119
+ const cancel = (error) => {
120
+ if (entry.cancelled) return;
121
+ entry.cancelled = true;
122
+ entry.cleanup();
123
+ remove(entry);
124
+ reject(error);
125
+ drain();
126
+ };
127
+ const onAbort = () => cancel(admissionAbortError(signal));
128
+ signal?.addEventListener("abort", onAbort, { once: true });
129
+ timer = setTimeout(() => {
130
+ cancel(httpError(`Request admission timed out after ${timeoutMs}ms`, 503));
131
+ }, timeoutMs);
132
+ timer.unref?.();
133
+ queue.push(entry);
134
+ if (signal?.aborted) onAbort();
135
+ else drain();
136
+ });
137
+ };
138
+
139
+ acquire.stats = () => ({ activeBytes, queued: queue.length, maxBytes: byteLimit });
140
+ return acquire;
141
+ }
142
+
34
143
  async function readRequestBuffer(req, maxBytes) {
35
144
  const contentLength = requestContentLength(req);
36
145
  if (Number.isFinite(contentLength) && contentLength > maxBytes) {
@@ -130,13 +130,16 @@ export async function optimizeImageDataUrl(dataUrl, {
130
130
  const match = /^data:(image\/[a-z+.-]+);base64,(.+)$/i.exec(dataUrl);
131
131
  if (!match) return dataUrl;
132
132
  const mime = match[1].toLowerCase();
133
- const raw = Buffer.from(match[2], "base64");
134
- if (raw.length < IMG_MIN_BYTES || mime.includes("gif")) return dataUrl;
135
- const digest = imageDigest(raw);
136
- if (!force && touchOptimizedImage(digest)) return dataUrl;
133
+ const encoded = match[2];
134
+ const inputBytes = Buffer.byteLength(encoded, "base64");
135
+ if (inputBytes < IMG_MIN_BYTES || mime.includes("gif")) return dataUrl;
137
136
 
138
137
  try {
139
138
  return await runGlobalImageTask(async () => {
139
+ const raw = Buffer.from(encoded, "base64");
140
+ if (raw.length < IMG_MIN_BYTES) return dataUrl;
141
+ const digest = imageDigest(raw);
142
+ if (!force && touchOptimizedImage(digest)) return dataUrl;
140
143
  const resize = await sharp();
141
144
  const image = resize(raw, { failOn: "none", limitInputPixels: IMG_MAX_INPUT_PIXELS });
142
145
  const out = await image
@@ -156,7 +159,7 @@ export async function optimizeImageDataUrl(dataUrl, {
156
159
  }, { signal });
157
160
  } catch (e) {
158
161
  if (signal?.aborted || e?.name === "AbortError") throw e;
159
- console.warn(status("warn", `image optimize failed (${mime}, ${raw.length}b): ${e.message}`));
162
+ console.warn(status("warn", `image optimize failed (${mime}, ${inputBytes}b): ${e.message}`));
160
163
  return dataUrl;
161
164
  }
162
165
  }
package/src/models.mjs CHANGED
@@ -225,6 +225,19 @@ export function resolveOpenAIModel(model, env = process.env) {
225
225
  };
226
226
  }
227
227
 
228
+ export function codexAutoReviewModelStatus(models, env = process.env) {
229
+ const { upstreamModel } = resolveOpenAIModel(CODEX_AUTO_REVIEW_MODEL, env);
230
+ const model = copilotModelData(models).find((entry) => String(entry?.id || "").trim() === upstreamModel);
231
+ if (!model) return { available: false, upstreamModel, reason: "model is not advertised" };
232
+ const endpoints = modelEndpoints(model);
233
+ const available = endpoints.includes("/responses") || endpoints.includes("/v1/responses");
234
+ return {
235
+ available,
236
+ upstreamModel,
237
+ reason: available ? "" : "model does not advertise a Responses endpoint",
238
+ };
239
+ }
240
+
228
241
  export function anthropicModelInfo(model) {
229
242
  return {
230
243
  id: model.id,