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.
- package/README.md +2 -2
- package/bin/cli.mjs +1 -0
- package/package.json +1 -1
- package/scripts/benchmark.mjs +58 -1
- package/src/adapter.mjs +1 -0
- package/src/cli-auth.mjs +2 -11
- package/src/cli-models.mjs +14 -7
- package/src/copilot-client.mjs +3 -1
- package/src/doctor.mjs +9 -8
- package/src/http-transport.mjs +114 -13
- package/src/image-optimization.mjs +33 -11
- package/src/legacy-command-warning.mjs +3 -2
- package/src/models.mjs +14 -10
- package/src/observability.mjs +18 -1
- package/src/pm-studio-setup.mjs +28 -4
- package/src/pm-studio-status.mjs +103 -9
- package/src/profile-routing.mjs +7 -16
- package/src/profile-runtime.mjs +12 -3
- package/src/response-history.mjs +12 -2
- package/src/responses-byte-budget.mjs +53 -26
- package/src/responses-chat-payload.mjs +35 -16
- package/src/responses-content.mjs +36 -4
- package/src/responses-handler.mjs +47 -27
- package/src/responses-image-limit.mjs +16 -2
- package/src/responses-image-pressure.mjs +10 -4
- package/src/responses-request.mjs +28 -10
|
@@ -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.
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
78
|
-
if (!Array.isArray(parts))
|
|
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: () => {
|
|
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
|
|
106
|
-
let
|
|
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() <
|
|
110
|
-
abort.abort(
|
|
110
|
+
if (now() < activeDeadline) return;
|
|
111
|
+
abort.abort(activeTimeoutReason);
|
|
111
112
|
throw abort.signal.reason;
|
|
112
113
|
};
|
|
113
114
|
const startUpstreamTimeout = () => {
|
|
114
|
-
|
|
115
|
-
assertPrepareActive();
|
|
116
|
-
upstreamStarted = true;
|
|
117
|
-
}
|
|
115
|
+
assertPrepareActive();
|
|
118
116
|
const streaming = prepared?.body?.stream === true;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() <
|
|
315
|
-
abort.abort(
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
375
|
+
releaseUpstreamPayload();
|
|
356
376
|
abort.cleanup();
|
|
357
377
|
}
|
|
358
378
|
};
|