codex-copilot-dx 0.6.5 → 0.6.6

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.
@@ -6,8 +6,10 @@ import {
6
6
 
7
7
  const IMAGE_OMISSION_TEXT = "[CCDX: earlier image omitted to fit the upstream request byte budget.]";
8
8
 
9
- function bodyPayload(reqBody) {
9
+ function bodyPayload(reqBody, assertActive) {
10
+ assertActive?.();
10
11
  const bodyText = JSON.stringify(reqBody);
12
+ assertActive?.();
11
13
  return { bodyText, bodyBytes: Buffer.byteLength(bodyText) };
12
14
  }
13
15
 
@@ -26,7 +28,7 @@ function imageMarkerPart(record) {
26
28
  return { type: record.usesAnthropicParts ? "text" : "input_text", text: IMAGE_OMISSION_TEXT };
27
29
  }
28
30
 
29
- function collectResponseImages(inputItems, currentInputStart) {
31
+ function collectResponseImages(inputItems, currentInputStart, assertActive) {
30
32
  const records = [];
31
33
  const refs = [];
32
34
  const addContainer = (parts, options) => {
@@ -41,6 +43,7 @@ function collectResponseImages(inputItems, currentInputStart) {
41
43
  usesAnthropicParts: false,
42
44
  };
43
45
  for (let partIndex = 0; partIndex < parts.length; partIndex += 1) {
46
+ if ((partIndex & 63) === 0) assertActive?.();
44
47
  const identity = inlineImageIdentity(parts[partIndex]);
45
48
  if (!identity) continue;
46
49
  if (parts[partIndex].type === "image") record.usesAnthropicParts = true;
@@ -56,6 +59,7 @@ function collectResponseImages(inputItems, currentInputStart) {
56
59
 
57
60
  const historyBoundary = Math.min(Math.max(0, currentInputStart), inputItems.length);
58
61
  for (let itemIndex = 0; itemIndex < inputItems.length; itemIndex += 1) {
62
+ if ((itemIndex & 63) === 0) assertActive?.();
59
63
  const item = inputItems[itemIndex];
60
64
  if (!item || typeof item !== "object") continue;
61
65
  const historical = itemIndex < historyBoundary;
@@ -90,10 +94,11 @@ function collectResponseImages(inputItems, currentInputStart) {
90
94
  return { records, refs };
91
95
  }
92
96
 
93
- function orderedHistoricalImageCandidates(refs) {
97
+ function orderedHistoricalImageCandidates(refs, assertActive) {
94
98
  const seenByKind = new Map();
95
99
  const duplicates = [];
96
100
  for (let index = refs.length - 1; index >= 0; index -= 1) {
101
+ if ((index & 63) === 0) assertActive?.();
97
102
  const { kind, value } = refs[index].identity;
98
103
  let seen = seenByKind.get(kind);
99
104
  if (!seen) {
@@ -121,7 +126,7 @@ function serializedPartBytes(part, stringified) {
121
126
  : Buffer.byteLength(serialized);
122
127
  }
123
128
 
124
- function createRecordDropState(record) {
129
+ function createRecordDropState(record, assertActive) {
125
130
  if (record.kind === "top-level") {
126
131
  const marker = {
127
132
  type: "message",
@@ -134,7 +139,11 @@ function createRecordDropState(record) {
134
139
  };
135
140
  }
136
141
 
137
- const elementBytes = record.parts.map((part) => serializedPartBytes(part, record.stringified));
142
+ const elementBytes = new Array(record.parts.length);
143
+ for (let index = 0; index < record.parts.length; index += 1) {
144
+ if ((index & 63) === 0) assertActive?.();
145
+ elementBytes[index] = serializedPartBytes(record.parts[index], record.stringified);
146
+ }
138
147
  const remainingElementBytes = elementBytes.reduce((sum, bytes) => sum + bytes, 0);
139
148
  const remainingCount = record.parts.length;
140
149
  const baseBytes = record.stringified ? 4 : 2;
@@ -159,18 +168,20 @@ function recordBytesAfterDrop(record, state, partIndex) {
159
168
  return state.baseBytes + remainingElementBytes + remainingCount - 1;
160
169
  }
161
170
 
162
- function selectImageDrops(refs, bodyBytes, targetBytes, maxHistoricalImages = Number.POSITIVE_INFINITY) {
163
- const candidates = orderedHistoricalImageCandidates(refs);
171
+ function selectImageDrops(refs, bodyBytes, targetBytes, maxHistoricalImages = Number.POSITIVE_INFINITY, assertActive) {
172
+ const candidates = orderedHistoricalImageCandidates(refs, assertActive);
164
173
  const selected = new Set();
165
174
  const byRecord = new Map();
166
175
  let projectedBytes = bodyBytes;
167
176
  let historicalImages = refs.reduce((count, ref) => count + (ref.historical ? 1 : 0), 0);
168
- for (const index of candidates) {
177
+ for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex += 1) {
178
+ if ((candidateIndex & 63) === 0) assertActive?.();
179
+ const index = candidates[candidateIndex];
169
180
  if (projectedBytes <= targetBytes && historicalImages <= maxHistoricalImages) break;
170
181
  const ref = refs[index];
171
182
  let state = byRecord.get(ref.record);
172
183
  if (!state) {
173
- state = createRecordDropState(ref.record);
184
+ state = createRecordDropState(ref.record, assertActive);
174
185
  byRecord.set(ref.record, state);
175
186
  }
176
187
  const afterBytes = recordBytesAfterDrop(ref.record, state, ref.partIndex);
@@ -188,9 +199,12 @@ function selectImageDrops(refs, bodyBytes, targetBytes, maxHistoricalImages = Nu
188
199
  return { selected, historicalImages, projectedBytes };
189
200
  }
190
201
 
191
- function applyImageDrops(records, refs, selected) {
202
+ function applyImageDrops(records, refs, selected, assertActive) {
192
203
  const byRecord = new Map();
204
+ let selectedIndex = 0;
193
205
  for (const index of selected) {
206
+ if ((selectedIndex & 63) === 0) assertActive?.();
207
+ selectedIndex += 1;
194
208
  const ref = refs[index];
195
209
  let indexes = byRecord.get(ref.record);
196
210
  if (!indexes) {
@@ -200,7 +214,9 @@ function applyImageDrops(records, refs, selected) {
200
214
  indexes.add(ref.partIndex);
201
215
  }
202
216
 
203
- for (const record of records) {
217
+ for (let recordIndex = 0; recordIndex < records.length; recordIndex += 1) {
218
+ if ((recordIndex & 63) === 0) assertActive?.();
219
+ const record = records[recordIndex];
204
220
  const indexes = byRecord.get(record);
205
221
  if (!indexes) continue;
206
222
  if (record.kind === "top-level") {
@@ -220,11 +236,12 @@ function applyImageDrops(records, refs, selected) {
220
236
  }
221
237
  }
222
238
 
223
- function omitHistoricalToolOutputs(inputItems, currentInputStart, bodyBytes, targetBytes) {
239
+ function omitHistoricalToolOutputs(inputItems, currentInputStart, bodyBytes, targetBytes, assertActive) {
224
240
  let omitted = 0;
225
241
  let projectedBytes = bodyBytes;
226
242
  const historicalCount = Math.min(Math.max(0, currentInputStart), inputItems.length);
227
243
  for (let itemIndex = 0; itemIndex < historicalCount && projectedBytes > targetBytes; itemIndex += 1) {
244
+ if ((itemIndex & 63) === 0) assertActive?.();
228
245
  const item = inputItems[itemIndex];
229
246
  if (!isResponsesToolOutputItem(item) || item.output === undefined) continue;
230
247
  if (typeof item.output === "string" && item.output.startsWith("[CCDX: earlier tool output omitted")) continue;
@@ -240,12 +257,15 @@ function omitHistoricalToolOutputs(inputItems, currentInputStart, bodyBytes, tar
240
257
  }
241
258
 
242
259
  export function trimResponsesHistoryToByteBudget(reqBody, {
260
+ assertActive,
243
261
  currentInputStart = 0,
244
262
  targetBytes,
245
263
  initialBodyText,
246
264
  initialBodyBytes,
247
265
  } = {}) {
266
+ assertActive?.();
248
267
  let bodyText = initialBodyText ?? JSON.stringify(reqBody);
268
+ assertActive?.();
249
269
  let bodyBytes = initialBodyBytes ?? Buffer.byteLength(bodyText);
250
270
  const limit = Number.isFinite(targetBytes) && targetBytes > 0 ? Math.floor(targetBytes) : bodyBytes;
251
271
  const inputItems = Array.isArray(reqBody?.input) ? reqBody.input : [];
@@ -253,20 +273,22 @@ export function trimResponsesHistoryToByteBudget(reqBody, {
253
273
  let toolOutputsOmitted = 0;
254
274
 
255
275
  while (bodyBytes > limit) {
256
- const { records, refs } = collectResponseImages(inputItems, currentInputStart);
276
+ assertActive?.();
277
+ const { records, refs } = collectResponseImages(inputItems, currentInputStart, assertActive);
257
278
  if (!refs.length) break;
258
- const { selected } = selectImageDrops(refs, bodyBytes, limit);
279
+ const { selected } = selectImageDrops(refs, bodyBytes, limit, Number.POSITIVE_INFINITY, assertActive);
259
280
  if (!selected.size) break;
260
- applyImageDrops(records, refs, selected);
281
+ applyImageDrops(records, refs, selected, assertActive);
261
282
  imagesOmitted += selected.size;
262
- ({ bodyText, bodyBytes } = bodyPayload(reqBody));
283
+ ({ bodyText, bodyBytes } = bodyPayload(reqBody, assertActive));
263
284
  }
264
285
 
265
286
  while (bodyBytes > limit) {
266
- const omitted = omitHistoricalToolOutputs(inputItems, currentInputStart, bodyBytes, limit);
287
+ assertActive?.();
288
+ const omitted = omitHistoricalToolOutputs(inputItems, currentInputStart, bodyBytes, limit, assertActive);
267
289
  if (omitted === 0) break;
268
290
  toolOutputsOmitted += omitted;
269
- ({ bodyText, bodyBytes } = bodyPayload(reqBody));
291
+ ({ bodyText, bodyBytes } = bodyPayload(reqBody, assertActive));
270
292
  }
271
293
 
272
294
  return {
@@ -280,9 +302,9 @@ export function trimResponsesHistoryToByteBudget(reqBody, {
280
302
  };
281
303
  }
282
304
 
283
- export function responsesHistoricalImageStats(inputItems, currentInputStart = 0) {
305
+ export function responsesHistoricalImageStats(inputItems, currentInputStart = 0, { assertActive } = {}) {
284
306
  const items = Array.isArray(inputItems) ? inputItems : [];
285
- const { refs } = collectResponseImages(items, currentInputStart);
307
+ const { refs } = collectResponseImages(items, currentInputStart, assertActive);
286
308
  const historicalImages = refs.reduce((count, ref) => count + (ref.historical ? 1 : 0), 0);
287
309
  return {
288
310
  totalImages: refs.length,
@@ -292,13 +314,16 @@ export function responsesHistoricalImageStats(inputItems, currentInputStart = 0)
292
314
  }
293
315
 
294
316
  export function trimResponsesHistoricalImages(reqBody, {
317
+ assertActive,
295
318
  currentInputStart = 0,
296
319
  maxHistoricalImages = Number.POSITIVE_INFINITY,
297
320
  targetBytes = Number.POSITIVE_INFINITY,
298
321
  initialBodyText,
299
322
  initialBodyBytes,
300
323
  } = {}) {
324
+ assertActive?.();
301
325
  let bodyText = initialBodyText ?? JSON.stringify(reqBody);
326
+ assertActive?.();
302
327
  let bodyBytes = initialBodyBytes ?? Buffer.byteLength(bodyText);
303
328
  const byteLimit = Number.isFinite(targetBytes) && targetBytes > 0
304
329
  ? Math.floor(targetBytes)
@@ -310,18 +335,19 @@ export function trimResponsesHistoricalImages(reqBody, {
310
335
  let imagesOmitted = 0;
311
336
 
312
337
  while (true) {
313
- const { records, refs } = collectResponseImages(inputItems, currentInputStart);
338
+ assertActive?.();
339
+ const { records, refs } = collectResponseImages(inputItems, currentInputStart, assertActive);
314
340
  const historicalImages = refs.reduce((count, ref) => count + (ref.historical ? 1 : 0), 0);
315
341
  if (bodyBytes <= byteLimit && historicalImages <= imageLimit) break;
316
342
  if (!historicalImages) break;
317
- const { selected } = selectImageDrops(refs, bodyBytes, byteLimit, imageLimit);
343
+ const { selected } = selectImageDrops(refs, bodyBytes, byteLimit, imageLimit, assertActive);
318
344
  if (!selected.size) break;
319
- applyImageDrops(records, refs, selected);
345
+ applyImageDrops(records, refs, selected, assertActive);
320
346
  imagesOmitted += selected.size;
321
- ({ bodyText, bodyBytes } = bodyPayload(reqBody));
347
+ ({ bodyText, bodyBytes } = bodyPayload(reqBody, assertActive));
322
348
  }
323
349
 
324
- const stats = responsesHistoricalImageStats(inputItems, currentInputStart);
350
+ const stats = responsesHistoricalImageStats(inputItems, currentInputStart, { assertActive });
325
351
  return {
326
352
  bodyText,
327
353
  bodyBytes,
@@ -336,10 +362,11 @@ export function trimResponsesHistoricalImages(reqBody, {
336
362
  };
337
363
  }
338
364
 
339
- export function enforceResponsesPayloadByteBudget(reqBody, payload) {
365
+ export function enforceResponsesPayloadByteBudget(reqBody, payload, { assertActive } = {}) {
340
366
  let finalized = payload;
341
367
  if (payload?.overBudget) {
342
368
  const trimmed = trimResponsesHistoryToByteBudget(reqBody, {
369
+ assertActive,
343
370
  currentInputStart: payload.currentInputStart,
344
371
  targetBytes: payload.targetBytes,
345
372
  initialBodyText: payload.bodyText,
@@ -22,8 +22,10 @@ function configuredBodyLimit(payloadOptions) {
22
22
  return positiveInt(payloadOptions?.maxBytes, environmentLimit);
23
23
  }
24
24
 
25
- function jsonPayload(value) {
25
+ function jsonPayload(value, assertActive) {
26
+ assertActive?.();
26
27
  const bodyText = JSON.stringify(value);
28
+ assertActive?.();
27
29
  return { bodyText, bodyBytes: Buffer.byteLength(bodyText) };
28
30
  }
29
31
 
@@ -38,22 +40,29 @@ function adjustedResponsesTarget(limit, responsesBytes, chatBytes) {
38
40
  return Math.max(1, limit - (chatBytes - responsesBytes));
39
41
  }
40
42
 
41
- function partsHaveInlineImage(parts) {
42
- return Array.isArray(parts) && parts.some((part) => Boolean(readResponsesImagePart(part)?.identity));
43
+ function partsHaveInlineImage(parts, assertActive) {
44
+ if (!Array.isArray(parts)) return false;
45
+ for (let index = 0; index < parts.length; index += 1) {
46
+ if ((index & 63) === 0) assertActive?.();
47
+ if (readResponsesImagePart(parts[index])?.identity) return true;
48
+ }
49
+ return false;
43
50
  }
44
51
 
45
- function bodyHasInlineImage(body) {
52
+ function bodyHasInlineImage(body, assertActive) {
46
53
  if (!Array.isArray(body?.input)) return false;
47
- for (const item of body.input) {
54
+ for (let index = 0; index < body.input.length; index += 1) {
55
+ if ((index & 63) === 0) assertActive?.();
56
+ const item = body.input[index];
48
57
  if (readResponsesImagePart(item)?.identity) return true;
49
- if (item?.type === "message" && partsHaveInlineImage(item.content)) return true;
58
+ if (item?.type === "message" && partsHaveInlineImage(item.content, assertActive)) return true;
50
59
  const toolOutput = readResponsesToolOutputParts(item);
51
- if (toolOutput && partsHaveInlineImage(toolOutput.parts)) return true;
60
+ if (toolOutput && partsHaveInlineImage(toolOutput.parts, assertActive)) return true;
52
61
  }
53
62
  return false;
54
63
  }
55
64
 
56
- function checkedChatPayload(chatReq, payload, limit, stage, adapted) {
65
+ function checkedChatPayload(chatReq, payload, limit, stage, adapted, assertActive) {
57
66
  enforceResponsesPayloadByteBudget(chatReq, {
58
67
  ...payload,
59
68
  adapted,
@@ -61,29 +70,33 @@ function checkedChatPayload(chatReq, payload, limit, stage, adapted) {
61
70
  overBudget: payload.bodyBytes > limit,
62
71
  stage: stage === "chat" ? stage : `${stage}+chat`,
63
72
  targetBytes: limit,
64
- });
73
+ }, { assertActive });
65
74
  return { chatReq, ...payload, adapted, stage };
66
75
  }
67
76
 
68
77
  export async function prepareResponsesChatPayload(reqContext, {
78
+ assertActive,
69
79
  payloadOptions = {},
70
80
  signal,
71
81
  stream = false,
72
82
  } = {}) {
83
+ assertActive?.();
73
84
  const limit = configuredBodyLimit(payloadOptions);
74
85
  let finalChatReq = chatRequest(reqContext.body, stream);
75
- let finalChat = jsonPayload(finalChatReq);
76
- const hasInlineImage = bodyHasInlineImage(reqContext.body);
86
+ assertActive?.();
87
+ let finalChat = jsonPayload(finalChatReq, assertActive);
88
+ const hasInlineImage = bodyHasInlineImage(reqContext.body, assertActive);
77
89
  if (!hasInlineImage && (finalChat.bodyBytes <= limit || !(reqContext.currentInputStart > 0))) {
78
- return checkedChatPayload(finalChatReq, finalChat, limit, "chat", false);
90
+ return checkedChatPayload(finalChatReq, finalChat, limit, "chat", false, assertActive);
79
91
  }
80
92
 
81
- let responsesPayload = jsonPayload(reqContext.body);
93
+ let responsesPayload = jsonPayload(reqContext.body, assertActive);
82
94
  let adapted = false;
83
95
  let stage = "chat";
84
96
  if (hasInlineImage) {
85
97
  const prepared = await prepareResponsesPayload(reqContext.body, {
86
98
  ...payloadOptions,
99
+ assertActive,
87
100
  currentInputStart: reqContext.currentInputStart,
88
101
  maxBytes: adjustedResponsesTarget(limit, responsesPayload.bodyBytes, finalChat.bodyBytes),
89
102
  signal,
@@ -92,10 +105,12 @@ export async function prepareResponsesChatPayload(reqContext, {
92
105
  adapted = prepared.adapted;
93
106
  stage = prepared.stage;
94
107
  finalChatReq = chatRequest(reqContext.body, stream);
95
- finalChat = jsonPayload(finalChatReq);
108
+ assertActive?.();
109
+ finalChat = jsonPayload(finalChatReq, assertActive);
96
110
  }
97
111
 
98
112
  while (finalChat.bodyBytes > limit) {
113
+ assertActive?.();
99
114
  if (!(reqContext.currentInputStart > 0)) break;
100
115
  const targetBytes = adjustedResponsesTarget(
101
116
  limit,
@@ -103,8 +118,10 @@ export async function prepareResponsesChatPayload(reqContext, {
103
118
  finalChat.bodyBytes,
104
119
  );
105
120
  const bodyBeforeTrim = structuredClone(reqContext.body);
121
+ assertActive?.();
106
122
  const responsesBeforeTrim = responsesPayload;
107
123
  const trimmed = trimResponsesHistoryToByteBudget(reqContext.body, {
124
+ assertActive,
108
125
  currentInputStart: reqContext.currentInputStart,
109
126
  targetBytes,
110
127
  initialBodyText: responsesPayload.bodyText,
@@ -112,7 +129,8 @@ export async function prepareResponsesChatPayload(reqContext, {
112
129
  });
113
130
  if (!trimmed.adapted) break;
114
131
  const candidateChatReq = chatRequest(reqContext.body, stream);
115
- const candidateChat = jsonPayload(candidateChatReq);
132
+ assertActive?.();
133
+ const candidateChat = jsonPayload(candidateChatReq, assertActive);
116
134
  if (candidateChat.bodyBytes >= finalChat.bodyBytes) {
117
135
  reqContext.body = bodyBeforeTrim;
118
136
  responsesPayload = responsesBeforeTrim;
@@ -125,5 +143,6 @@ export async function prepareResponsesChatPayload(reqContext, {
125
143
  if (!stage.endsWith("+history")) stage += "+history";
126
144
  }
127
145
 
128
- return checkedChatPayload(finalChatReq, finalChat, limit, stage, adapted);
146
+ assertActive?.();
147
+ return checkedChatPayload(finalChatReq, finalChat, limit, stage, adapted, assertActive);
129
148
  }
@@ -3,6 +3,14 @@ export function isResponsesToolOutputItem(item) {
3
3
  }
4
4
 
5
5
  const INLINE_IMAGE_DATA_URL = /^data:(image\/[a-z+.-]+);base64,(.+)$/i;
6
+ const toolOutputPartsCache = new WeakMap();
7
+
8
+ export function clearResponsesToolOutputPartsCache(inputItems) {
9
+ if (!Array.isArray(inputItems)) return;
10
+ for (const item of inputItems) {
11
+ if (item && typeof item === "object") toolOutputPartsCache.delete(item);
12
+ }
13
+ }
6
14
 
7
15
  export function canonicalInlineImageIdentity(dataUrl) {
8
16
  if (typeof dataUrl !== "string") return null;
@@ -71,16 +79,40 @@ export function readResponsesImagePart(part) {
71
79
  export function readResponsesToolOutputParts(item) {
72
80
  if (!isResponsesToolOutputItem(item)) return null;
73
81
  if (Array.isArray(item.output)) return { parts: item.output, commit: null };
74
- if (typeof item.output !== "string" || !item.output.trim().startsWith("[")) return null;
82
+ const source = item.output;
83
+ if (typeof source !== "string" || !source.trim().startsWith("[")) return null;
84
+
85
+ const cached = toolOutputPartsCache.get(item);
86
+ if (cached?.source === source) {
87
+ if (!cached.parts) return null;
88
+ return {
89
+ parts: cached.parts,
90
+ commit: () => {
91
+ const nextSource = JSON.stringify(cached.parts);
92
+ item.output = nextSource;
93
+ cached.source = nextSource;
94
+ },
95
+ };
96
+ }
75
97
 
76
98
  try {
77
- const parts = JSON.parse(item.output);
78
- if (!Array.isArray(parts)) return null;
99
+ const parts = JSON.parse(source);
100
+ if (!Array.isArray(parts)) {
101
+ toolOutputPartsCache.set(item, { source, parts: null });
102
+ return null;
103
+ }
104
+ const entry = { source, parts };
105
+ toolOutputPartsCache.set(item, entry);
79
106
  return {
80
107
  parts,
81
- commit: () => { item.output = JSON.stringify(parts); },
108
+ commit: () => {
109
+ const nextSource = JSON.stringify(parts);
110
+ item.output = nextSource;
111
+ entry.source = nextSource;
112
+ },
82
113
  };
83
114
  } catch {
115
+ toolOutputPartsCache.set(item, { source, parts: null });
84
116
  return null;
85
117
  }
86
118
  }
@@ -16,6 +16,7 @@ import {
16
16
  import { chatToResponses, forwardToChat, responsesBodyUsesCustomTools } from "./responses-bridge.mjs";
17
17
  import { prepareResponsesChatPayload } from "./responses-chat-payload.mjs";
18
18
  import { prepareResponsesCompactionRequest } from "./responses-compaction.mjs";
19
+ import { clearResponsesToolOutputPartsCache } from "./responses-content.mjs";
19
20
  import { proxyCopilotResponses } from "./responses-proxy.mjs";
20
21
  import { responseHistoryMaterializedBytes } from "./response-history.mjs";
21
22
  import {
@@ -102,24 +103,21 @@ export function createResponsesHandler(options) {
102
103
  let imagePressureResult = null;
103
104
  let parsed = null;
104
105
  let upstreamPayloadReleased = false;
105
- let prepareDeadline = Number.POSITIVE_INFINITY;
106
- let upstreamStarted = false;
106
+ let activeDeadline = Number.POSITIVE_INFINITY;
107
+ let activeTimeoutReason = "responses_prepare_timeout";
107
108
  const assertPrepareActive = () => {
108
109
  if (abort.signal.aborted) throw abort.signal.reason;
109
- if (now() < prepareDeadline) return;
110
- abort.abort("responses_prepare_timeout");
110
+ if (now() < activeDeadline) return;
111
+ abort.abort(activeTimeoutReason);
111
112
  throw abort.signal.reason;
112
113
  };
113
114
  const startUpstreamTimeout = () => {
114
- if (!upstreamStarted) {
115
- assertPrepareActive();
116
- upstreamStarted = true;
117
- }
115
+ assertPrepareActive();
118
116
  const streaming = prepared?.body?.stream === true;
119
- abort.setTimeout(
120
- streaming ? streamHandshakeTimeoutMs : upstreamTimeoutMs,
121
- streaming ? "stream_handshake_timeout" : "upstream_timeout",
122
- );
117
+ const timeoutMs = streaming ? streamHandshakeTimeoutMs : upstreamTimeoutMs;
118
+ activeTimeoutReason = streaming ? "stream_handshake_timeout" : "upstream_timeout";
119
+ activeDeadline = now() + timeoutMs;
120
+ abort.setTimeout(timeoutMs, activeTimeoutReason);
123
121
  };
124
122
  const applyAdaptiveTimeout = (error) => {
125
123
  if (!["responses_prepare_timeout", "stream_handshake_timeout", "upstream_timeout"].includes(abort.reason)) {
@@ -150,6 +148,12 @@ export function createResponsesHandler(options) {
150
148
  const releaseUpstreamPayload = (finalContext) => {
151
149
  if (upstreamPayloadReleased) return;
152
150
  upstreamPayloadReleased = true;
151
+ clearResponsesToolOutputPartsCache(prepared?.body?.input);
152
+ clearResponsesToolOutputPartsCache(prepared?.historyInputItems);
153
+ if (finalContext && finalContext !== prepared) {
154
+ clearResponsesToolOutputPartsCache(finalContext.body?.input);
155
+ clearResponsesToolOutputPartsCache(finalContext.historyInputItems);
156
+ }
153
157
  if (finalContext && finalContext !== prepared) dropMaterializedResponseHistory(finalContext);
154
158
  dropMaterializedResponseHistory(prepared);
155
159
  releaseRequest();
@@ -158,7 +162,8 @@ export function createResponsesHandler(options) {
158
162
  releaseRequest = await acquireRequest(req, { signal: abort.signal });
159
163
  abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
160
164
  parsed = await readJsonBody(req, { admission: releaseRequest, signal: abort.signal });
161
- prepareDeadline = now() + upstreamTimeoutMs;
165
+ activeDeadline = now() + upstreamTimeoutMs;
166
+ activeTimeoutReason = "responses_prepare_timeout";
162
167
  abort.setTimeout(upstreamTimeoutMs, "responses_prepare_timeout");
163
168
  if (parsed.previous_response_id !== undefined && parsed.previous_response_id !== null) {
164
169
  const historyBytes = responseHistoryMaterializedBytes(parsed.previous_response_id);
@@ -167,10 +172,10 @@ export function createResponsesHandler(options) {
167
172
  }
168
173
  assertPrepareActive();
169
174
  }
170
- prepared = prepareResponsesRequest(parsed, { mutate: true });
175
+ prepared = prepareResponsesRequest(parsed, { assertActive: assertPrepareActive, mutate: true });
171
176
  parsed = null;
172
177
  assertPrepareActive();
173
- imagePressureResult = imagePressure?.apply?.(prepared) || null;
178
+ imagePressureResult = imagePressure?.apply?.(prepared, { assertActive: assertPrepareActive }) || null;
174
179
  assertPrepareActive();
175
180
  if (imagePressureResult?.adapted) {
176
181
  console.warn(status("warn", `responses visual history mode=${imagePressureResult.mode} historical_images=${imagePressureResult.initialHistoricalImages}->${imagePressureResult.historicalImages} omitted=${imagePressureResult.imagesOmitted} bytes=${imagePressureResult.initialBodyBytes}->${imagePressureResult.bodyBytes}`));
@@ -189,6 +194,7 @@ export function createResponsesHandler(options) {
189
194
  if (usesCustomTools && !useNativeResponses) throw unsupportedCustomToolsError(upstreamModel);
190
195
  if (useNativeResponses) {
191
196
  const result = await proxyCopilotResponses(prepared, req, res, responsesFn, {
197
+ assertPrepareActive,
192
198
  signal: abort.signal,
193
199
  abort,
194
200
  onUpstreamStart: startUpstreamTimeout,
@@ -199,6 +205,7 @@ export function createResponsesHandler(options) {
199
205
  else markAdaptiveHttpTimeout(result?.upstreamStatus);
200
206
  } else {
201
207
  let { chatReq, bodyText } = await prepareResponsesChatPayload(prepared, {
208
+ assertActive: assertPrepareActive,
202
209
  payloadOptions: responsesPayloadOptions,
203
210
  signal: abort.signal,
204
211
  stream: streaming,
@@ -304,23 +311,35 @@ export function createResponsesCompactHandler(options) {
304
311
  return async function handleResponsesCompact(req, res) {
305
312
  const abort = createRequestAbort(req, res);
306
313
  let releaseRequest = () => {};
314
+ let prepared = null;
315
+ let upstreamPayloadReleased = false;
316
+ const releaseUpstreamPayload = (finalContext) => {
317
+ if (upstreamPayloadReleased) return;
318
+ upstreamPayloadReleased = true;
319
+ clearResponsesToolOutputPartsCache(prepared?.body?.input);
320
+ clearResponsesToolOutputPartsCache(prepared?.historyInputItems);
321
+ if (finalContext && finalContext !== prepared) {
322
+ clearResponsesToolOutputPartsCache(finalContext.body?.input);
323
+ clearResponsesToolOutputPartsCache(finalContext.historyInputItems);
324
+ }
325
+ releaseRequest();
326
+ };
307
327
  try {
308
328
  releaseRequest = await acquireRequest(req, { signal: abort.signal });
309
329
  abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
310
330
  const parsed = await readJsonBody(req, { admission: releaseRequest, signal: abort.signal });
311
- const prepareDeadline = now() + upstreamTimeoutMs;
331
+ let activeDeadline = now() + upstreamTimeoutMs;
332
+ let activeTimeoutReason = "responses_prepare_timeout";
312
333
  const assertPrepareActive = () => {
313
334
  if (abort.signal.aborted) throw abort.signal.reason;
314
- if (now() < prepareDeadline) return;
315
- abort.abort("responses_prepare_timeout");
335
+ if (now() < activeDeadline) return;
336
+ abort.abort(activeTimeoutReason);
316
337
  throw abort.signal.reason;
317
338
  };
318
- let upstreamStarted = false;
319
339
  const startUpstreamTimeout = () => {
320
- if (!upstreamStarted) {
321
- assertPrepareActive();
322
- upstreamStarted = true;
323
- }
340
+ assertPrepareActive();
341
+ activeDeadline = now() + upstreamTimeoutMs;
342
+ activeTimeoutReason = "upstream_timeout";
324
343
  abort.setTimeout(upstreamTimeoutMs, "upstream_timeout");
325
344
  };
326
345
  abort.setTimeout(upstreamTimeoutMs, "responses_prepare_timeout");
@@ -331,8 +350,8 @@ export function createResponsesCompactHandler(options) {
331
350
  }
332
351
  assertPrepareActive();
333
352
  }
334
- const prepared = prepareResponsesCompactionRequest(
335
- prepareResponsesRequest(parsed, { mutate: true }),
353
+ prepared = prepareResponsesCompactionRequest(
354
+ prepareResponsesRequest(parsed, { assertActive: assertPrepareActive, mutate: true }),
336
355
  );
337
356
  assertPrepareActive();
338
357
  prepared.surface = "responses_compact";
@@ -342,17 +361,18 @@ export function createResponsesCompactHandler(options) {
342
361
  const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
343
362
  console.log(status("info", `responses compact model=${requestedModel}${upstreamLog} stream=false`));
344
363
  const result = await proxyCopilotResponses(prepared, req, res, responsesCompactFn, {
364
+ assertPrepareActive,
345
365
  signal: abort.signal,
346
366
  abort,
347
367
  onUpstreamStart: startUpstreamTimeout,
348
- releaseRequest,
368
+ releaseRequest: releaseUpstreamPayload,
349
369
  });
350
370
  if (result?.compacted) imagePressure?.clear?.(prepared.historyRootId);
351
371
  } catch (error) {
352
372
  logRequestFailure("Responses compact", error, abort);
353
373
  sendJsonError(res, error, 502);
354
374
  } finally {
355
- releaseRequest();
375
+ releaseUpstreamPayload();
356
376
  abort.cleanup();
357
377
  }
358
378
  };