codex-copilot-dx 0.4.42 → 0.4.44

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.
@@ -1,15 +1,22 @@
1
1
  import { createHash } from "node:crypto";
2
+ import {
3
+ canonicalInlineImageIdentity,
4
+ readResponsesImagePart,
5
+ readResponsesToolOutputParts,
6
+ } from "./responses-content.mjs";
7
+ import { createBoundedResultCache } from "./bounded-result-cache.mjs";
2
8
  import { status } from "./status.mjs";
3
9
 
4
10
  const DEFAULT_IMG_CONCURRENCY = 2;
5
11
  const IMG_MAX_CONCURRENCY = 12;
6
12
  const DEFAULT_IMG_MAX_INPUT_PIXELS = 40 * 1000 * 1000;
13
+ const DEFAULT_IMG_CACHE_MAX_BYTES = 64 * 1024 * 1024;
7
14
  const DEFAULT_MAX_UPSTREAM_BODY_BYTES = 30 * 1024 * 1024;
8
- const MAX_OPTIMIZED_IMAGE_DIGESTS = 4096;
9
15
  const IMG_MAX_DIM = positiveInt(process.env.CCDX_IMG_MAX_DIM, 2048);
10
- const IMG_QUALITY = positiveInt(process.env.CCDX_IMG_QUALITY, 85, 100);
16
+ const IMG_QUALITY = positiveInt(process.env.CCDX_IMG_QUALITY, 82, 100);
11
17
  const IMG_MIN_BYTES = nonNegativeInt(process.env.CCDX_IMG_MIN_BYTES, 100000);
12
18
  const IMG_MAX_INPUT_PIXELS = positiveInt(process.env.CCDX_IMG_MAX_INPUT_PIXELS, DEFAULT_IMG_MAX_INPUT_PIXELS);
19
+ const IMG_CACHE_MAX_BYTES = nonNegativeInt(process.env.CCDX_IMG_CACHE_MAX_BYTES, DEFAULT_IMG_CACHE_MAX_BYTES);
13
20
  const MAX_UPSTREAM_BODY_BYTES = positiveInt(process.env.CCDX_MAX_UPSTREAM_BODY_BYTES, DEFAULT_MAX_UPSTREAM_BODY_BYTES);
14
21
  const IMG_OPT_DISABLED = process.env.CCDX_DISABLE_IMG_OPT === "1";
15
22
  const IMG_CONCURRENCY = parseImageConcurrency(process.env.CCDX_IMG_CONCURRENCY);
@@ -17,7 +24,8 @@ const OVERSIZE_IMAGE_PROFILES = [
17
24
  { maxDim: Math.min(IMG_MAX_DIM, 1600), quality: Math.min(IMG_QUALITY, 75) },
18
25
  { maxDim: Math.min(IMG_MAX_DIM, 1280), quality: Math.min(IMG_QUALITY, 65) },
19
26
  ];
20
- const optimizedImageDigests = new Set();
27
+ const IMAGE_ENCODER_CONFIG = "webp:v1:effort=4:autorotate=1:fit=inside:without_enlargement=1";
28
+ const imageResultCache = createBoundedResultCache({ maxBytes: IMG_CACHE_MAX_BYTES });
21
29
  let sharpImport = null;
22
30
 
23
31
  function positiveInt(value, fallback, max = Number.MAX_SAFE_INTEGER) {
@@ -30,6 +38,39 @@ function nonNegativeInt(value, fallback) {
30
38
  return Number.isFinite(n) && n >= 0 ? n : fallback;
31
39
  }
32
40
 
41
+ export function imageConstraintsForModel(modelId, { maxDim = IMG_MAX_DIM } = {}) {
42
+ const model = String(modelId || "").trim().toLowerCase();
43
+ const maxLongEdge = Math.min(positiveInt(maxDim, IMG_MAX_DIM), IMG_MAX_DIM, 2048);
44
+ const fullSizeGpt4 = /^(?:gpt-4o|gpt-4\.1)(?:$|-)/.test(model)
45
+ && !/(?:^|-)(?:mini|nano)(?:$|-)/.test(model);
46
+ const gpt5Mini = /^gpt-5-mini(?:$|-)/.test(model);
47
+
48
+ if (fullSizeGpt4 || gpt5Mini) {
49
+ return { maxLongEdge, maxShortEdge: Math.min(maxLongEdge, 768) };
50
+ }
51
+ if (/^gpt-5\.(?:4|5|6)(?:$|-)/.test(model)) {
52
+ return { maxLongEdge, maxArea: 2_560_000 };
53
+ }
54
+ return { maxLongEdge };
55
+ }
56
+
57
+ export function fitImageDimensions(width, height, constraints = {}) {
58
+ const sourceWidth = Number(width);
59
+ const sourceHeight = Number(height);
60
+ if (!(sourceWidth > 0) || !(sourceHeight > 0)) return null;
61
+
62
+ const longEdge = Math.max(sourceWidth, sourceHeight);
63
+ const shortEdge = Math.min(sourceWidth, sourceHeight);
64
+ let scale = 1;
65
+ if (constraints.maxLongEdge > 0) scale = Math.min(scale, constraints.maxLongEdge / longEdge);
66
+ if (constraints.maxShortEdge > 0) scale = Math.min(scale, constraints.maxShortEdge / shortEdge);
67
+ if (constraints.maxArea > 0) scale = Math.min(scale, Math.sqrt(constraints.maxArea / (sourceWidth * sourceHeight)));
68
+ return {
69
+ width: Math.max(1, Math.floor(sourceWidth * scale)),
70
+ height: Math.max(1, Math.floor(sourceHeight * scale)),
71
+ };
72
+ }
73
+
33
74
  export function parseImageConcurrency(value) {
34
75
  const n = Number.parseInt(value, 10);
35
76
  return Number.isFinite(n) && n > 0 ? Math.min(n, IMG_MAX_CONCURRENCY) : DEFAULT_IMG_CONCURRENCY;
@@ -102,42 +143,51 @@ export function createTaskLimiter(concurrency) {
102
143
  const runGlobalImageTask = createTaskLimiter(IMG_CONCURRENCY);
103
144
 
104
145
  export function imageOptimizationStats() {
146
+ const cache = imageResultCache.stats();
105
147
  return {
106
148
  ...runGlobalImageTask.stats(),
107
149
  disabled: IMG_OPT_DISABLED,
108
- cache_entries: optimizedImageDigests.size,
150
+ cache_entries: cache.entries,
151
+ cache_bytes: cache.bytes,
152
+ cache_max_bytes: cache.max_bytes,
153
+ cache_hits: cache.hits,
154
+ cache_misses: cache.misses,
155
+ cache_evictions: cache.evictions,
156
+ cache_inflight: cache.inflight,
109
157
  sharp_loaded: sharpImport !== null,
110
158
  };
111
159
  }
112
160
 
161
+ export function resetImageOptimizationCacheForTests() {
162
+ imageResultCache.clear();
163
+ }
164
+
113
165
  async function sharp() {
114
166
  sharpImport ||= import("sharp");
115
167
  const mod = await sharpImport;
116
168
  return mod.default || mod;
117
169
  }
118
170
 
119
- function imageDigest(raw) {
120
- return createHash("sha256").update(raw).digest("base64url");
171
+ function imageDigest(value, encoding) {
172
+ return createHash("sha256").update(value, encoding).digest("base64url");
121
173
  }
122
174
 
123
- function touchOptimizedImage(digest) {
124
- if (!optimizedImageDigests.delete(digest)) return false;
125
- optimizedImageDigests.add(digest);
126
- return true;
127
- }
128
-
129
- function rememberOptimizedImage(digest) {
130
- optimizedImageDigests.delete(digest);
131
- optimizedImageDigests.add(digest);
132
- if (optimizedImageDigests.size > MAX_OPTIMIZED_IMAGE_DIGESTS) {
133
- optimizedImageDigests.delete(optimizedImageDigests.values().next().value);
134
- }
175
+ function imageOptimizationKey(digest, mime, constraints, quality) {
176
+ return [
177
+ digest,
178
+ mime,
179
+ constraints.maxLongEdge || "",
180
+ constraints.maxShortEdge || "",
181
+ constraints.maxArea || "",
182
+ quality,
183
+ IMAGE_ENCODER_CONFIG,
184
+ ].join(":");
135
185
  }
136
186
 
137
187
  export async function optimizeImageDataUrl(dataUrl, {
138
188
  maxDim = IMG_MAX_DIM,
139
189
  quality = IMG_QUALITY,
140
- force = false,
190
+ model,
141
191
  signal,
142
192
  } = {}) {
143
193
  if (IMG_OPT_DISABLED) return dataUrl;
@@ -147,34 +197,66 @@ export async function optimizeImageDataUrl(dataUrl, {
147
197
  const mime = match[1].toLowerCase();
148
198
  const encoded = match[2];
149
199
  const inputBytes = Buffer.byteLength(encoded, "base64");
150
- if (inputBytes < IMG_MIN_BYTES || mime.includes("gif")) return dataUrl;
200
+ if (mime.includes("gif")) return dataUrl;
201
+ if (signal?.aborted) throw abortError(signal);
202
+ const constraints = imageConstraintsForModel(model, { maxDim });
203
+ const outputQuality = positiveInt(quality, IMG_QUALITY, 100);
204
+ // Hash decoded source bytes without retaining a decoded Buffer. Cache hits
205
+ // therefore bypass both Sharp and the global image work queue.
206
+ const digest = imageDigest(encoded, "base64");
207
+ const optimizationKey = imageOptimizationKey(digest, mime, constraints, outputQuality);
151
208
 
152
209
  try {
153
- return await runGlobalImageTask(async () => {
154
- const raw = Buffer.from(encoded, "base64");
155
- if (raw.length < IMG_MIN_BYTES) return dataUrl;
156
- const digest = imageDigest(raw);
157
- if (!force && touchOptimizedImage(digest)) return dataUrl;
158
- const resize = await sharp();
159
- const image = resize(raw, { failOn: "none", limitInputPixels: IMG_MAX_INPUT_PIXELS });
160
- const out = await image
161
- .rotate()
162
- .resize(positiveInt(maxDim, IMG_MAX_DIM), positiveInt(maxDim, IMG_MAX_DIM), { fit: "inside", withoutEnlargement: true })
163
- .webp({ quality: positiveInt(quality, IMG_QUALITY, 100), effort: 4 })
164
- .toBuffer();
165
- if (signal?.aborted) throw abortError(signal);
166
- if (out.length >= raw.length) {
167
- rememberOptimizedImage(digest);
168
- return dataUrl;
169
- }
170
- rememberOptimizedImage(imageDigest(out));
171
- const ratio = ((out.length / raw.length) * 100).toFixed(1);
172
- console.log(status("info", `image ${(raw.length / 1024).toFixed(0)}KB ${mime} -> ${(out.length / 1024).toFixed(0)}KB webp (${ratio}%)`));
173
- return `data:image/webp;base64,${out.toString("base64")}`;
174
- }, { signal });
210
+ return await imageResultCache.getOrCreate(optimizationKey, (sharedSignal) => (
211
+ runGlobalImageTask(async () => {
212
+ const raw = Buffer.from(encoded, "base64");
213
+ const resize = await sharp();
214
+ const image = resize(raw, { failOn: "none", limitInputPixels: IMG_MAX_INPUT_PIXELS });
215
+ const metadata = await image.metadata();
216
+ if (sharedSignal.aborted) throw abortError(sharedSignal);
217
+ const orientation = Number(metadata.orientation);
218
+ const rotated = orientation >= 5 && orientation <= 8;
219
+ const sourceWidth = rotated ? metadata.height : metadata.width;
220
+ const sourceHeight = rotated ? metadata.width : metadata.height;
221
+ const target = fitImageDimensions(sourceWidth, sourceHeight, constraints);
222
+ const resizeRequired = target
223
+ && (target.width < sourceWidth || target.height < sourceHeight);
224
+ if (raw.length < IMG_MIN_BYTES && !resizeRequired) return dataUrl;
225
+ const resizeOptions = target
226
+ ? { ...target, fit: "inside", withoutEnlargement: true }
227
+ : {
228
+ width: constraints.maxLongEdge,
229
+ height: constraints.maxLongEdge,
230
+ fit: "inside",
231
+ withoutEnlargement: true,
232
+ };
233
+ const out = await image
234
+ .rotate()
235
+ .resize(resizeOptions)
236
+ .webp({ quality: outputQuality, effort: 4 })
237
+ .toBuffer();
238
+ if (sharedSignal.aborted) throw abortError(sharedSignal);
239
+ if (out.length >= raw.length) return dataUrl;
240
+ const outputBase64 = out.toString("base64");
241
+ const outputDataUrl = `data:image/webp;base64,${outputBase64}`;
242
+ const outputKey = imageOptimizationKey(
243
+ imageDigest(outputBase64, "base64"),
244
+ "image/webp",
245
+ constraints,
246
+ outputQuality,
247
+ );
248
+ // Preserve idempotence when optimized history is submitted again.
249
+ imageResultCache.set(outputKey, outputDataUrl);
250
+ const ratio = ((out.length / raw.length) * 100).toFixed(1);
251
+ console.log(status("info", `image ${(raw.length / 1024).toFixed(0)}KB ${mime} -> ${(out.length / 1024).toFixed(0)}KB webp (${ratio}%)`));
252
+ return outputDataUrl;
253
+ }, { signal: sharedSignal })
254
+ ), { signal });
175
255
  } catch (e) {
176
256
  if (signal?.aborted || e?.name === "AbortError") throw e;
177
- console.warn(status("warn", `image optimize failed (${mime}, ${inputBytes}b): ${e.message}`));
257
+ if (inputBytes >= IMG_MIN_BYTES) {
258
+ console.warn(status("warn", `image optimize failed (${mime}, ${inputBytes}b): ${e.message}`));
259
+ }
178
260
  return dataUrl;
179
261
  }
180
262
  }
@@ -191,102 +273,225 @@ export async function runWithConcurrency(taskFns, concurrency) {
191
273
  }));
192
274
  }
193
275
 
194
- function visitImageParts(parts, tasks, optimizeImage) {
276
+ function visitImageParts(parts, references, commit = null) {
195
277
  if (!Array.isArray(parts)) return;
196
278
  for (const part of parts) {
197
- if (!part) continue;
198
- if (part.type === "input_image" && typeof part.image_url === "string") {
199
- tasks.push(async () => { part.image_url = await optimizeImage(part.image_url); });
200
- } else if (part.type === "image" && part.source?.type === "base64" && part.source?.data) {
201
- tasks.push(async () => {
202
- const dataUrl = `data:${part.source.media_type || "image/png"};base64,${part.source.data}`;
203
- const optimized = await optimizeImage(dataUrl);
204
- const match = /^data:([^;]+);base64,(.+)$/.exec(optimized);
205
- if (match) {
206
- part.source.media_type = match[1];
207
- part.source.data = match[2];
208
- }
209
- });
279
+ const image = readResponsesImagePart(part);
280
+ if (!image?.identity) continue;
281
+ references.push({
282
+ commit,
283
+ get: () => image.dataUrl,
284
+ set: (value) => image.setDataUrl(value),
285
+ });
286
+ }
287
+ }
288
+
289
+ function collectImageReferences(reqBody) {
290
+ const references = [];
291
+ const dirtyCommits = new Set();
292
+ if (!Array.isArray(reqBody?.input)) {
293
+ return { references, markDirty() {}, commitDirty() {} };
294
+ }
295
+
296
+ for (const item of reqBody.input) {
297
+ if (!item) continue;
298
+ visitImageParts([item], references);
299
+ if (item.type === "message" && Array.isArray(item.content)) {
300
+ visitImageParts(item.content, references);
301
+ }
302
+ const toolOutput = readResponsesToolOutputParts(item);
303
+ if (toolOutput) {
304
+ visitImageParts(toolOutput.parts, references, toolOutput.commit);
210
305
  }
211
306
  }
307
+
308
+ return {
309
+ references,
310
+ markDirty(changedReferences) {
311
+ for (const reference of changedReferences) {
312
+ if (reference.commit) dirtyCommits.add(reference.commit);
313
+ }
314
+ },
315
+ commitDirty() {
316
+ for (const apply of dirtyCommits) apply();
317
+ dirtyCommits.clear();
318
+ },
319
+ };
212
320
  }
213
321
 
214
- export async function optimizeImagesInBody(reqBody, {
215
- concurrency = IMG_CONCURRENCY,
216
- optimizeImage = optimizeImageDataUrl,
322
+ async function optimizeImageReferences(collection, {
323
+ concurrency,
324
+ model,
325
+ optimizeImage,
217
326
  signal,
218
- } = {}) {
219
- if (IMG_OPT_DISABLED || !Array.isArray(reqBody.input)) return reqBody;
220
- const tasks = [];
221
- const finalize = [];
327
+ }) {
222
328
  const optimizedByDataUrl = new Map();
223
329
  const optimizeOnce = (dataUrl) => {
224
- if (!optimizedByDataUrl.has(dataUrl)) {
225
- optimizedByDataUrl.set(dataUrl, Promise.resolve().then(() => optimizeImage(dataUrl, { signal })));
330
+ const identity = canonicalInlineImageIdentity(dataUrl) || dataUrl;
331
+ if (!optimizedByDataUrl.has(identity)) {
332
+ optimizedByDataUrl.set(identity, Promise.resolve().then(() => optimizeImage(dataUrl, { model, signal })));
226
333
  }
227
- return optimizedByDataUrl.get(dataUrl);
334
+ return optimizedByDataUrl.get(identity);
228
335
  };
336
+ const tasks = collection.references.map((reference) => async () => {
337
+ if (reference.set(await optimizeOnce(reference.get()))) collection.markDirty([reference]);
338
+ });
339
+ if (tasks.length) await runWithConcurrency(tasks, concurrency);
340
+ collection.commitDirty();
341
+ }
229
342
 
230
- for (const item of reqBody.input) {
231
- if (!item) continue;
232
- if (item.type === "message" && Array.isArray(item.content)) {
233
- visitImageParts(item.content, tasks, optimizeOnce);
234
- }
235
- if (item.type === "function_call_output") {
236
- if (Array.isArray(item.output)) {
237
- visitImageParts(item.output, tasks, optimizeOnce);
238
- } else if (typeof item.output === "string") {
239
- const trimmed = item.output.trim();
240
- if (trimmed.startsWith("[")) {
241
- try {
242
- const parsed = JSON.parse(trimmed);
243
- if (Array.isArray(parsed)) {
244
- const taskCount = tasks.length;
245
- visitImageParts(parsed, tasks, optimizeOnce);
246
- if (tasks.length > taskCount) finalize.push(() => { item.output = JSON.stringify(parsed); });
247
- }
248
- } catch {
249
- // Leave non-JSON tool output untouched.
250
- }
251
- }
252
- }
343
+ function imageInputBytes(dataUrl) {
344
+ if (typeof dataUrl !== "string") return 0;
345
+ const match = /^data:image\/[a-z+.-]+;base64,(.+)$/i.exec(dataUrl);
346
+ return match ? Buffer.byteLength(match[1], "base64") : 0;
347
+ }
348
+
349
+ function uniqueOriginalImages(references) {
350
+ const unique = new Map();
351
+ for (const reference of references) {
352
+ const dataUrl = reference.get();
353
+ const identity = canonicalInlineImageIdentity(dataUrl);
354
+ if (!identity) continue;
355
+ const inputBytes = imageInputBytes(dataUrl);
356
+ if (inputBytes <= 0) continue;
357
+ let image = unique.get(identity);
358
+ if (!image) {
359
+ image = { dataUrl, inputBytes, references: [] };
360
+ unique.set(identity, image);
253
361
  }
362
+ image.references.push(reference);
363
+ }
364
+ return [...unique.values()];
365
+ }
366
+
367
+ function currentImageWireBytes(image) {
368
+ return image.references.reduce(
369
+ (total, reference) => total + Buffer.byteLength(reference.get()),
370
+ 0,
371
+ );
372
+ }
373
+
374
+ function orderImagesByCurrentWireBytes(images) {
375
+ return [...images].sort((left, right) => (
376
+ currentImageWireBytes(right) - currentImageWireBytes(left)
377
+ || right.inputBytes - left.inputBytes
378
+ ));
379
+ }
380
+
381
+ function applyImageCandidate(collection, image, candidate, bodyBytes) {
382
+ if (!canonicalInlineImageIdentity(candidate)) {
383
+ return { bodyBytes, changed: false };
384
+ }
385
+ const previous = image.references.map((reference) => reference.get());
386
+ if (previous.every((value) => value === candidate)) return { bodyBytes, changed: false };
387
+ // Base64 data URLs need no JSON escaping, so their byte delta also applies to the serialized body.
388
+ const candidateBytes = Buffer.byteLength(candidate);
389
+ const byteDelta = previous.reduce(
390
+ (delta, value) => delta + candidateBytes - Buffer.byteLength(value),
391
+ 0,
392
+ );
393
+ if (byteDelta >= 0) return { bodyBytes, changed: false };
394
+ const changedReferences = [];
395
+ for (const reference of image.references) {
396
+ if (reference.set(candidate)) changedReferences.push(reference);
254
397
  }
398
+ collection.markDirty(changedReferences);
399
+ return { bodyBytes: bodyBytes + byteDelta, changed: true };
400
+ }
255
401
 
256
- if (tasks.length) await runWithConcurrency(tasks, concurrency);
257
- for (const apply of finalize) apply();
402
+ function normalizeCurrentInputStart(value, reqBody) {
403
+ const parsed = Number.parseInt(value, 10);
404
+ const itemCount = Array.isArray(reqBody?.input) ? reqBody.input.length : 0;
405
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.min(parsed, itemCount) : 0;
406
+ }
407
+
408
+ export async function optimizeImagesInBody(reqBody, {
409
+ concurrency = IMG_CONCURRENCY,
410
+ optimizeImage = optimizeImageDataUrl,
411
+ signal,
412
+ } = {}) {
413
+ if (IMG_OPT_DISABLED || !Array.isArray(reqBody.input)) return reqBody;
414
+ const collection = collectImageReferences(reqBody);
415
+ await optimizeImageReferences(collection, {
416
+ concurrency,
417
+ model: reqBody.model,
418
+ optimizeImage,
419
+ signal,
420
+ });
258
421
  return reqBody;
259
422
  }
260
423
 
261
424
  export async function prepareResponsesPayload(reqBody, {
425
+ currentInputStart = 0,
262
426
  maxBytes = MAX_UPSTREAM_BODY_BYTES,
427
+ optimizeImage = optimizeImageDataUrl,
263
428
  profiles = OVERSIZE_IMAGE_PROFILES,
429
+ skipInitialOptimization = false,
264
430
  signal,
265
431
  } = {}) {
266
- await optimizeImagesInBody(reqBody, { signal });
432
+ const shouldOptimizeImages = !IMG_OPT_DISABLED && !skipInitialOptimization;
433
+ const collection = shouldOptimizeImages ? collectImageReferences(reqBody) : null;
434
+ const originalImages = collection ? uniqueOriginalImages(collection.references) : [];
435
+ if (collection) {
436
+ await optimizeImageReferences(collection, {
437
+ concurrency: IMG_CONCURRENCY,
438
+ model: reqBody.model,
439
+ optimizeImage: (dataUrl, options) => optimizeImage(dataUrl, { ...options, quality: IMG_QUALITY }),
440
+ signal,
441
+ });
442
+ }
267
443
  let bodyText = JSON.stringify(reqBody);
268
444
  let bodyBytes = Buffer.byteLength(bodyText);
269
445
  const summary = summarizeReqBody(reqBody);
270
446
  const targetBytes = positiveInt(maxBytes, MAX_UPSTREAM_BODY_BYTES);
271
447
  let adapted = false;
448
+ let stage = skipInitialOptimization ? "preoptimized" : `q${IMG_QUALITY}`;
272
449
 
273
- if (!IMG_OPT_DISABLED && summary.images > 0 && bodyBytes > targetBytes) {
274
- for (const profile of profiles) {
450
+ if (collection && originalImages.length > 0 && bodyBytes > targetBytes) {
451
+ const batchSize = Math.max(1, IMG_CONCURRENCY);
452
+ for (const profile of Array.isArray(profiles) ? profiles : []) {
275
453
  const beforeBytes = bodyBytes;
276
- bodyText = "";
277
- await optimizeImagesInBody(reqBody, {
278
- optimizeImage: (dataUrl) => optimizeImageDataUrl(dataUrl, { ...profile, force: true, signal }),
279
- signal,
280
- });
454
+ let processed = 0;
455
+ stage = `q${positiveInt(profile.quality, IMG_QUALITY, 100)}`;
456
+ const orderedImages = orderImagesByCurrentWireBytes(originalImages);
457
+ for (let index = 0; index < orderedImages.length && bodyBytes > targetBytes; index += batchSize) {
458
+ const batch = orderedImages.slice(index, index + batchSize);
459
+ const candidates = await Promise.all(batch.map((image) => optimizeImage(image.dataUrl, {
460
+ ...profile,
461
+ force: true,
462
+ model: reqBody.model,
463
+ signal,
464
+ })));
465
+ for (let offset = 0; offset < batch.length && bodyBytes > targetBytes; offset += 1) {
466
+ processed += 1;
467
+ const applied = applyImageCandidate(collection, batch[offset], candidates[offset], bodyBytes);
468
+ bodyBytes = applied.bodyBytes;
469
+ adapted ||= applied.changed;
470
+ if (bodyBytes <= targetBytes) {
471
+ collection.commitDirty();
472
+ bodyText = JSON.stringify(reqBody);
473
+ bodyBytes = Buffer.byteLength(bodyText);
474
+ }
475
+ }
476
+ }
477
+ collection.commitDirty();
281
478
  bodyText = JSON.stringify(reqBody);
282
479
  bodyBytes = Buffer.byteLength(bodyText);
283
- adapted ||= bodyBytes < beforeBytes;
284
- console.warn(status("warn", `responses payload ${beforeBytes}b exceeds ${targetBytes}b; image profile max_dim=${profile.maxDim} quality=${profile.quality} -> ${bodyBytes}b`));
480
+ console.warn(status("warn", `responses payload ${beforeBytes}b exceeds ${targetBytes}b; image profile max_dim=${profile.maxDim} quality=${profile.quality} processed=${processed}/${orderedImages.length} -> ${bodyBytes}b`));
285
481
  if (bodyBytes <= targetBytes) break;
286
482
  }
287
483
  }
288
484
 
289
- return { bodyText, bodyBytes, summary, adapted };
485
+ return {
486
+ bodyText,
487
+ bodyBytes,
488
+ summary,
489
+ adapted,
490
+ stage,
491
+ targetBytes,
492
+ overBudget: bodyBytes > targetBytes,
493
+ currentInputStart: normalizeCurrentInputStart(currentInputStart, reqBody),
494
+ };
290
495
  }
291
496
 
292
497
  export function summarizeReqBody(reqBody) {
@@ -297,24 +502,15 @@ export function summarizeReqBody(reqBody) {
297
502
  const countImages = (parts) => {
298
503
  if (!Array.isArray(parts)) return;
299
504
  for (const part of parts) {
300
- if (part?.type === "input_image" || part?.type === "image") images += 1;
505
+ if (["input_image", "image", "image_url"].includes(part?.type)) images += 1;
301
506
  }
302
507
  };
303
508
 
304
509
  for (const item of input) {
510
+ if (["input_image", "image", "image_url"].includes(item?.type)) countImages([item]);
305
511
  if (item?.type === "message") countImages(item.content);
306
- if (item?.type === "function_call_output") {
307
- if (Array.isArray(item.output)) {
308
- countImages(item.output);
309
- } else if (typeof item.output === "string" && item.output.trim().startsWith("[")) {
310
- try {
311
- const parsed = JSON.parse(item.output);
312
- if (Array.isArray(parsed)) countImages(parsed);
313
- } catch {
314
- // Ignore non-JSON tool output.
315
- }
316
- }
317
- }
512
+ const toolOutput = readResponsesToolOutputParts(item);
513
+ if (toolOutput) countImages(toolOutput.parts);
318
514
  }
319
515
 
320
516
  return { items: input.length, images };
@@ -118,7 +118,7 @@ function modelRegistryStatus(modelRegistry) {
118
118
  };
119
119
  }
120
120
 
121
- export function runtimeStatusPayload({ metrics, admission, modelRegistry } = {}) {
121
+ export function runtimeStatusPayload({ metrics, streamPerformance, admission, modelRegistry } = {}) {
122
122
  const memory = process.memoryUsage();
123
123
  return {
124
124
  ...adapterHealthPayload(),
@@ -131,6 +131,7 @@ export function runtimeStatusPayload({ metrics, admission, modelRegistry } = {})
131
131
  array_buffers_bytes: memory.arrayBuffers,
132
132
  },
133
133
  requests: metrics?.snapshot?.() || createRequestMetrics().snapshot(),
134
+ stream_performance: streamPerformance?.snapshot?.() || null,
134
135
  admission: admission?.diagnostics?.() || admission?.stats?.() || null,
135
136
  response_history: responseHistoryStats(),
136
137
  image_optimization: imageOptimizationStats(),