codex-copilot-dx 0.6.14 → 0.6.18
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 +30 -16
- package/bin/cli.mjs +5 -0
- package/package.json +1 -1
- package/src/adapter.mjs +34 -9
- package/src/anthropic-handler.mjs +54 -4
- package/src/anthropic.mjs +64 -26
- package/src/atomic-file.mjs +76 -2
- package/src/auth-profile.mjs +11 -34
- package/src/auth.mjs +265 -52
- package/src/cli-auth.mjs +28 -71
- package/src/cli-options.mjs +8 -5
- package/src/copilot-client.mjs +5 -1
- package/src/copilot-responses-compat.mjs +80 -0
- package/src/copilot-responses-policy.mjs +78 -0
- package/src/copilot.mjs +2 -1
- package/src/http-transport.mjs +91 -0
- package/src/lock.mjs +179 -17
- package/src/model-cache.mjs +36 -6
- package/src/models.mjs +17 -0
- package/src/observability.mjs +13 -1
- package/src/pm-studio-asar.mjs +399 -19
- package/src/pm-studio-relay.mjs +6 -3
- package/src/pm-studio-setup.mjs +319 -14
- package/src/pm-studio-status.mjs +5 -1
- package/src/profile-model-runtime.mjs +87 -13
- package/src/profile-routing.mjs +45 -7
- package/src/response-history.mjs +142 -16
- package/src/responses-bridge.mjs +22 -4
- package/src/responses-handler.mjs +154 -39
- package/src/responses-image-pressure.mjs +30 -0
- package/src/responses-proxy.mjs +10 -2
- package/src/responses-request.mjs +109 -103
- package/src/route-plan.mjs +51 -0
- package/src/startup.mjs +8 -2
- package/src/usage.mjs +25 -7
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { getCachedModelEndpoints } from "./copilot.mjs";
|
|
2
|
+
import { applyCopilotResponsesRequestPolicies } from "./copilot-responses-policy.mjs";
|
|
2
3
|
import {
|
|
3
4
|
createRequestAbort,
|
|
4
5
|
logRequestFailure,
|
|
6
|
+
MAX_UPSTREAM_ERROR_BODY_BYTES,
|
|
5
7
|
readJsonBody,
|
|
8
|
+
readBoundedResponseText,
|
|
6
9
|
sendJsonError,
|
|
7
10
|
sendUpstreamError,
|
|
8
11
|
writeOrDrain,
|
|
@@ -11,6 +14,7 @@ import {
|
|
|
11
14
|
CODEX_AUTO_REVIEW_MODEL,
|
|
12
15
|
modelIsResponsesOnly,
|
|
13
16
|
modelSupportsChatCompletions,
|
|
17
|
+
resolveCopilotPriorityTierModel,
|
|
14
18
|
resolveOpenAIModel,
|
|
15
19
|
} from "./models.mjs";
|
|
16
20
|
import { chatToResponses, forwardToChat, responsesBodyUsesCustomTools } from "./responses-bridge.mjs";
|
|
@@ -18,12 +22,15 @@ import { prepareResponsesChatPayload } from "./responses-chat-payload.mjs";
|
|
|
18
22
|
import { prepareResponsesCompactionRequest } from "./responses-compaction.mjs";
|
|
19
23
|
import { clearResponsesToolOutputPartsCache } from "./responses-content.mjs";
|
|
20
24
|
import { proxyCopilotResponses } from "./responses-proxy.mjs";
|
|
21
|
-
import {
|
|
25
|
+
import { acquireResponseHistorySnapshot } from "./response-history.mjs";
|
|
22
26
|
import {
|
|
27
|
+
applyResponseHistoryRoutePlan,
|
|
23
28
|
dropMaterializedResponseHistory,
|
|
24
29
|
prepareResponsesRequest,
|
|
25
30
|
rememberResponseHistory,
|
|
31
|
+
responseHistoryPressureRootId,
|
|
26
32
|
} from "./responses-request.mjs";
|
|
33
|
+
import { createRoutePlan } from "./route-plan.mjs";
|
|
27
34
|
import { status } from "./status.mjs";
|
|
28
35
|
import { endStreamWithError } from "./stream-errors.mjs";
|
|
29
36
|
import { safeUpstreamResponseHeaders } from "./upstream-headers.mjs";
|
|
@@ -86,6 +93,7 @@ export function createResponsesHandler(options) {
|
|
|
86
93
|
chatCompletionsFn,
|
|
87
94
|
getCachedModelEndpointsFn = getCachedModelEndpoints,
|
|
88
95
|
imagePressure,
|
|
96
|
+
modelRegistry,
|
|
89
97
|
openAIModelEnv,
|
|
90
98
|
requestBodyTimeoutMs,
|
|
91
99
|
responsesPayloadOptions,
|
|
@@ -101,10 +109,15 @@ export function createResponsesHandler(options) {
|
|
|
101
109
|
let releaseRequest = () => {};
|
|
102
110
|
let prepared = null;
|
|
103
111
|
let imagePressureResult = null;
|
|
112
|
+
let historySnapshot = null;
|
|
104
113
|
let parsed = null;
|
|
105
114
|
let upstreamPayloadReleased = false;
|
|
106
115
|
let activeDeadline = Number.POSITIVE_INFINITY;
|
|
107
116
|
let activeTimeoutReason = "responses_prepare_timeout";
|
|
117
|
+
const releaseHistorySnapshot = () => {
|
|
118
|
+
historySnapshot?.release();
|
|
119
|
+
historySnapshot = null;
|
|
120
|
+
};
|
|
108
121
|
const assertPrepareActive = () => {
|
|
109
122
|
if (abort.signal.aborted) throw abort.signal.reason;
|
|
110
123
|
if (now() < activeDeadline) return;
|
|
@@ -141,13 +154,14 @@ export function createResponsesHandler(options) {
|
|
|
141
154
|
};
|
|
142
155
|
const markAdaptiveHttpTimeout = (statusCode) => {
|
|
143
156
|
if (statusCode !== 408) return false;
|
|
144
|
-
return imagePressure?.markTimeout?.(prepared
|
|
157
|
+
return imagePressure?.markTimeout?.(responseHistoryPressureRootId(prepared), {
|
|
145
158
|
eligible: imagePressureResult?.pressureEligible === true,
|
|
146
159
|
}) || false;
|
|
147
160
|
};
|
|
148
161
|
const releaseUpstreamPayload = (finalContext) => {
|
|
149
162
|
if (upstreamPayloadReleased) return;
|
|
150
163
|
upstreamPayloadReleased = true;
|
|
164
|
+
if (finalContext?.historyParentId === null) releaseHistorySnapshot();
|
|
151
165
|
clearResponsesToolOutputPartsCache(prepared?.body?.input);
|
|
152
166
|
clearResponsesToolOutputPartsCache(prepared?.historyInputItems);
|
|
153
167
|
if (finalContext && finalContext !== prepared) {
|
|
@@ -166,13 +180,21 @@ export function createResponsesHandler(options) {
|
|
|
166
180
|
activeTimeoutReason = "responses_prepare_timeout";
|
|
167
181
|
abort.setTimeout(upstreamTimeoutMs, "responses_prepare_timeout");
|
|
168
182
|
if (parsed.previous_response_id !== undefined && parsed.previous_response_id !== null) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
historySnapshot = acquireResponseHistorySnapshot(parsed.previous_response_id, {
|
|
184
|
+
assertActive: assertPrepareActive,
|
|
185
|
+
signal: abort.signal,
|
|
186
|
+
});
|
|
187
|
+
if (historySnapshot.bytes > 0) {
|
|
188
|
+
await releaseRequest.reserveResponseHistory?.(historySnapshot.bytes, { signal: abort.signal });
|
|
172
189
|
}
|
|
173
190
|
assertPrepareActive();
|
|
174
191
|
}
|
|
175
|
-
prepared = prepareResponsesRequest(parsed, {
|
|
192
|
+
prepared = prepareResponsesRequest(parsed, {
|
|
193
|
+
assertActive: assertPrepareActive,
|
|
194
|
+
copilotBoundary: false,
|
|
195
|
+
historySnapshot,
|
|
196
|
+
mutate: true,
|
|
197
|
+
});
|
|
176
198
|
parsed = null;
|
|
177
199
|
assertPrepareActive();
|
|
178
200
|
imagePressureResult = imagePressure?.apply?.(prepared, { assertActive: assertPrepareActive }) || null;
|
|
@@ -183,16 +205,37 @@ export function createResponsesHandler(options) {
|
|
|
183
205
|
prepared.surface = "responses";
|
|
184
206
|
const model = prepared.body.model || "unknown";
|
|
185
207
|
const streaming = prepared.body.stream === true;
|
|
186
|
-
const
|
|
208
|
+
const resolvedModel = resolveRequestModel(model, openAIModelEnv, autoReviewModelResolver);
|
|
209
|
+
const { requestedModel } = resolvedModel;
|
|
210
|
+
let { upstreamModel } = resolvedModel;
|
|
211
|
+
const priorityTierModel = requestedModel === upstreamModel
|
|
212
|
+
? resolveCopilotPriorityTierModel(upstreamModel, prepared.body.service_tier, modelRegistry?.models)
|
|
213
|
+
: null;
|
|
214
|
+
if (priorityTierModel) {
|
|
215
|
+
upstreamModel = priorityTierModel;
|
|
216
|
+
delete prepared.body.service_tier;
|
|
217
|
+
}
|
|
187
218
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
|
188
219
|
const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
|
|
189
220
|
console.log(status("info", `responses model=${requestedModel}${upstreamLog} stream=${streaming}`));
|
|
190
221
|
const usesCustomTools = responsesBodyUsesCustomTools(prepared.body);
|
|
191
|
-
const useNativeResponses =
|
|
222
|
+
const useNativeResponses = Boolean(priorityTierModel)
|
|
223
|
+
|| requestedModel === CODEX_AUTO_REVIEW_MODEL
|
|
192
224
|
|| isResponsesOnlyModel(upstreamModel, getCachedModelEndpointsFn)
|
|
193
225
|
|| (usesCustomTools && cachedModelSupportsResponses(upstreamModel, getCachedModelEndpointsFn));
|
|
194
226
|
if (usesCustomTools && !useNativeResponses) throw unsupportedCustomToolsError(upstreamModel);
|
|
195
|
-
|
|
227
|
+
const routePlan = createRoutePlan({
|
|
228
|
+
disposition: "relay",
|
|
229
|
+
origin: "ccdx",
|
|
230
|
+
profile: "codex",
|
|
231
|
+
protocol: useNativeResponses ? "openai-responses" : "openai-chat-completions",
|
|
232
|
+
model: upstreamModel,
|
|
233
|
+
surface: "responses",
|
|
234
|
+
});
|
|
235
|
+
prepared = applyResponseHistoryRoutePlan(prepared, routePlan);
|
|
236
|
+
if (prepared.historyParentId === null) releaseHistorySnapshot();
|
|
237
|
+
applyCopilotResponsesRequestPolicies(prepared.body);
|
|
238
|
+
if (routePlan.protocol === "openai-responses") {
|
|
196
239
|
const result = await proxyCopilotResponses(prepared, req, res, responsesFn, {
|
|
197
240
|
assertPrepareActive,
|
|
198
241
|
signal: abort.signal,
|
|
@@ -201,9 +244,9 @@ export function createResponsesHandler(options) {
|
|
|
201
244
|
releaseRequest: releaseUpstreamPayload,
|
|
202
245
|
streamIdleTimeoutMs,
|
|
203
246
|
});
|
|
204
|
-
if (result?.successful) imagePressure?.markSuccess?.(prepared
|
|
247
|
+
if (result?.successful) imagePressure?.markSuccess?.(responseHistoryPressureRootId(prepared));
|
|
205
248
|
else markAdaptiveHttpTimeout(result?.upstreamStatus);
|
|
206
|
-
} else {
|
|
249
|
+
} else if (routePlan.protocol === "openai-chat-completions") {
|
|
207
250
|
let { chatReq, bodyText } = await prepareResponsesChatPayload(prepared, {
|
|
208
251
|
assertActive: assertPrepareActive,
|
|
209
252
|
payloadOptions: responsesPayloadOptions,
|
|
@@ -223,11 +266,13 @@ export function createResponsesHandler(options) {
|
|
|
223
266
|
"Cache-Control": "no-cache",
|
|
224
267
|
Connection: "keep-alive",
|
|
225
268
|
});
|
|
226
|
-
await writeOrDrain(res, `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
269
|
+
const written = await writeOrDrain(res, `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
270
|
+
if (!written) return false;
|
|
227
271
|
if (event === "response.completed") {
|
|
228
272
|
rememberResponseHistory(prepared, data.response);
|
|
229
273
|
recordResponsesUsage({ surface: prepared.surface, mode: "stream", model, response: data.response, event: data });
|
|
230
274
|
}
|
|
275
|
+
return true;
|
|
231
276
|
}, () => { if (!res.writableEnded) res.end(); }, async (statusCode, errMsg, error, upstreamResponse) => {
|
|
232
277
|
if (!res.headersSent) {
|
|
233
278
|
if (upstreamResponse) {
|
|
@@ -254,7 +299,7 @@ export function createResponsesHandler(options) {
|
|
|
254
299
|
});
|
|
255
300
|
},
|
|
256
301
|
});
|
|
257
|
-
if (successful) imagePressure?.markSuccess?.(prepared
|
|
302
|
+
if (successful) imagePressure?.markSuccess?.(responseHistoryPressureRootId(prepared));
|
|
258
303
|
} else {
|
|
259
304
|
try {
|
|
260
305
|
startUpstreamTimeout();
|
|
@@ -264,7 +309,12 @@ export function createResponsesHandler(options) {
|
|
|
264
309
|
onUpstreamStart: startUpstreamTimeout,
|
|
265
310
|
});
|
|
266
311
|
releaseChatPayload();
|
|
267
|
-
const data =
|
|
312
|
+
const data = upstream.ok
|
|
313
|
+
? await upstream.text()
|
|
314
|
+
: await readBoundedResponseText(upstream, {
|
|
315
|
+
maxBytes: MAX_UPSTREAM_ERROR_BODY_BYTES,
|
|
316
|
+
label: "Copilot Chat error body",
|
|
317
|
+
});
|
|
268
318
|
if (!upstream.ok) {
|
|
269
319
|
markAdaptiveHttpTimeout(upstream.status);
|
|
270
320
|
sendUpstreamError(res, upstream, data);
|
|
@@ -272,7 +322,7 @@ export function createResponsesHandler(options) {
|
|
|
272
322
|
}
|
|
273
323
|
const response = chatToResponses(JSON.parse(data), model);
|
|
274
324
|
rememberResponseHistory(prepared, response);
|
|
275
|
-
imagePressure?.markSuccess?.(prepared
|
|
325
|
+
imagePressure?.markSuccess?.(responseHistoryPressureRootId(prepared));
|
|
276
326
|
recordResponsesUsage({ surface: prepared.surface, mode: "json", model, response, event: response });
|
|
277
327
|
res.writeHead(200, safeUpstreamResponseHeaders(upstream.headers, {
|
|
278
328
|
contentType: "application/json",
|
|
@@ -284,6 +334,8 @@ export function createResponsesHandler(options) {
|
|
|
284
334
|
sendJsonError(res, responseError, 502);
|
|
285
335
|
}
|
|
286
336
|
}
|
|
337
|
+
} else {
|
|
338
|
+
throw new Error(`Unsupported Responses target protocol: ${routePlan.protocol}`);
|
|
287
339
|
}
|
|
288
340
|
} catch (error) {
|
|
289
341
|
const responseError = applyAdaptiveTimeout(error);
|
|
@@ -291,6 +343,7 @@ export function createResponsesHandler(options) {
|
|
|
291
343
|
sendJsonError(res, responseError, 502);
|
|
292
344
|
} finally {
|
|
293
345
|
releaseUpstreamPayload();
|
|
346
|
+
releaseHistorySnapshot();
|
|
294
347
|
abort.cleanup();
|
|
295
348
|
}
|
|
296
349
|
};
|
|
@@ -301,6 +354,7 @@ export function createResponsesCompactHandler(options) {
|
|
|
301
354
|
acquireRequest,
|
|
302
355
|
autoReviewModelResolver,
|
|
303
356
|
imagePressure,
|
|
357
|
+
modelRegistry,
|
|
304
358
|
openAIModelEnv,
|
|
305
359
|
requestBodyTimeoutMs,
|
|
306
360
|
responsesCompactFn,
|
|
@@ -312,7 +366,47 @@ export function createResponsesCompactHandler(options) {
|
|
|
312
366
|
const abort = createRequestAbort(req, res);
|
|
313
367
|
let releaseRequest = () => {};
|
|
314
368
|
let prepared = null;
|
|
369
|
+
let imagePressureResult = null;
|
|
370
|
+
let historySnapshot = null;
|
|
315
371
|
let upstreamPayloadReleased = false;
|
|
372
|
+
let activeDeadline = Number.POSITIVE_INFINITY;
|
|
373
|
+
let activeTimeoutReason = "responses_prepare_timeout";
|
|
374
|
+
const releaseHistorySnapshot = () => {
|
|
375
|
+
historySnapshot?.release();
|
|
376
|
+
historySnapshot = null;
|
|
377
|
+
};
|
|
378
|
+
const assertPrepareActive = () => {
|
|
379
|
+
if (abort.signal.aborted) throw abort.signal.reason;
|
|
380
|
+
if (now() < activeDeadline) return;
|
|
381
|
+
abort.abort(activeTimeoutReason);
|
|
382
|
+
throw abort.signal.reason;
|
|
383
|
+
};
|
|
384
|
+
const startUpstreamTimeout = () => {
|
|
385
|
+
assertPrepareActive();
|
|
386
|
+
activeDeadline = now() + upstreamTimeoutMs;
|
|
387
|
+
activeTimeoutReason = "upstream_timeout";
|
|
388
|
+
abort.setTimeout(upstreamTimeoutMs, "upstream_timeout");
|
|
389
|
+
};
|
|
390
|
+
const pressureRootId = () => responseHistoryPressureRootId(prepared);
|
|
391
|
+
const markCompactTimeout = () => {
|
|
392
|
+
const eligible = imagePressure?.isEligible?.(prepared) === true;
|
|
393
|
+
return imagePressure?.markTimeout?.(pressureRootId(), { eligible }) || false;
|
|
394
|
+
};
|
|
395
|
+
const applyAdaptiveTimeout = (error) => {
|
|
396
|
+
if (!["responses_prepare_timeout", "upstream_timeout"].includes(abort.reason)) return error;
|
|
397
|
+
if (!markCompactTimeout()) return error;
|
|
398
|
+
const message = "Responses compact timed out while processing a large visual history. Retry the request; ccdx will keep fewer earlier images automatically. Restart is not required.";
|
|
399
|
+
error.statusCode = 504;
|
|
400
|
+
error.jsonBody = {
|
|
401
|
+
error: {
|
|
402
|
+
message,
|
|
403
|
+
type: "timeout_error",
|
|
404
|
+
code: "ccdx_visual_history_timeout",
|
|
405
|
+
retryable: true,
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
return error;
|
|
409
|
+
};
|
|
316
410
|
const releaseUpstreamPayload = (finalContext) => {
|
|
317
411
|
if (upstreamPayloadReleased) return;
|
|
318
412
|
upstreamPayloadReleased = true;
|
|
@@ -328,38 +422,56 @@ export function createResponsesCompactHandler(options) {
|
|
|
328
422
|
releaseRequest = await acquireRequest(req, { signal: abort.signal });
|
|
329
423
|
abort.setTimeout(requestBodyTimeoutMs, "request_body_timeout");
|
|
330
424
|
const parsed = await readJsonBody(req, { admission: releaseRequest, signal: abort.signal });
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const assertPrepareActive = () => {
|
|
334
|
-
if (abort.signal.aborted) throw abort.signal.reason;
|
|
335
|
-
if (now() < activeDeadline) return;
|
|
336
|
-
abort.abort(activeTimeoutReason);
|
|
337
|
-
throw abort.signal.reason;
|
|
338
|
-
};
|
|
339
|
-
const startUpstreamTimeout = () => {
|
|
340
|
-
assertPrepareActive();
|
|
341
|
-
activeDeadline = now() + upstreamTimeoutMs;
|
|
342
|
-
activeTimeoutReason = "upstream_timeout";
|
|
343
|
-
abort.setTimeout(upstreamTimeoutMs, "upstream_timeout");
|
|
344
|
-
};
|
|
425
|
+
activeDeadline = now() + upstreamTimeoutMs;
|
|
426
|
+
activeTimeoutReason = "responses_prepare_timeout";
|
|
345
427
|
abort.setTimeout(upstreamTimeoutMs, "responses_prepare_timeout");
|
|
346
428
|
if (parsed.previous_response_id !== undefined && parsed.previous_response_id !== null) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
429
|
+
historySnapshot = acquireResponseHistorySnapshot(parsed.previous_response_id, {
|
|
430
|
+
assertActive: assertPrepareActive,
|
|
431
|
+
signal: abort.signal,
|
|
432
|
+
});
|
|
433
|
+
if (historySnapshot.bytes > 0) {
|
|
434
|
+
await releaseRequest.reserveResponseHistory?.(historySnapshot.bytes, { signal: abort.signal });
|
|
350
435
|
}
|
|
351
436
|
assertPrepareActive();
|
|
352
437
|
}
|
|
353
|
-
prepared =
|
|
354
|
-
|
|
355
|
-
|
|
438
|
+
prepared = prepareResponsesRequest(parsed, {
|
|
439
|
+
assertActive: assertPrepareActive,
|
|
440
|
+
copilotBoundary: false,
|
|
441
|
+
historySnapshot,
|
|
442
|
+
mutate: true,
|
|
443
|
+
});
|
|
444
|
+
imagePressureResult = imagePressure?.applyRecovery?.(prepared, { assertActive: assertPrepareActive }) || null;
|
|
445
|
+
if (imagePressureResult?.adapted) {
|
|
446
|
+
console.warn(status("warn", `responses compact visual history mode=recovery historical_images=${imagePressureResult.initialHistoricalImages}->${imagePressureResult.historicalImages} omitted=${imagePressureResult.imagesOmitted} bytes=${imagePressureResult.initialBodyBytes}->${imagePressureResult.bodyBytes}`));
|
|
447
|
+
}
|
|
448
|
+
prepared = prepareResponsesCompactionRequest(prepared);
|
|
356
449
|
assertPrepareActive();
|
|
357
450
|
prepared.surface = "responses_compact";
|
|
358
451
|
const model = parsed.model || "unknown";
|
|
359
|
-
const
|
|
452
|
+
const resolvedModel = resolveRequestModel(model, openAIModelEnv, autoReviewModelResolver);
|
|
453
|
+
const { requestedModel } = resolvedModel;
|
|
454
|
+
let { upstreamModel } = resolvedModel;
|
|
455
|
+
const priorityTierModel = requestedModel === upstreamModel
|
|
456
|
+
? resolveCopilotPriorityTierModel(upstreamModel, prepared.body.service_tier, modelRegistry?.models)
|
|
457
|
+
: null;
|
|
458
|
+
if (priorityTierModel) {
|
|
459
|
+
upstreamModel = priorityTierModel;
|
|
460
|
+
delete prepared.body.service_tier;
|
|
461
|
+
}
|
|
360
462
|
if (upstreamModel !== requestedModel) prepared.body.model = upstreamModel;
|
|
361
463
|
const upstreamLog = upstreamModel === requestedModel ? "" : ` upstream_model=${upstreamModel}`;
|
|
362
464
|
console.log(status("info", `responses compact model=${requestedModel}${upstreamLog} stream=false`));
|
|
465
|
+
prepared = applyResponseHistoryRoutePlan(prepared, createRoutePlan({
|
|
466
|
+
disposition: "relay",
|
|
467
|
+
origin: "ccdx",
|
|
468
|
+
profile: "codex",
|
|
469
|
+
protocol: "openai-responses",
|
|
470
|
+
model: upstreamModel,
|
|
471
|
+
surface: "responses-compact",
|
|
472
|
+
}));
|
|
473
|
+
releaseHistorySnapshot();
|
|
474
|
+
applyCopilotResponsesRequestPolicies(prepared.body);
|
|
363
475
|
const result = await proxyCopilotResponses(prepared, req, res, responsesCompactFn, {
|
|
364
476
|
assertPrepareActive,
|
|
365
477
|
signal: abort.signal,
|
|
@@ -367,12 +479,15 @@ export function createResponsesCompactHandler(options) {
|
|
|
367
479
|
onUpstreamStart: startUpstreamTimeout,
|
|
368
480
|
releaseRequest: releaseUpstreamPayload,
|
|
369
481
|
});
|
|
370
|
-
if (result?.compacted) imagePressure?.clear?.(prepared
|
|
482
|
+
if (result?.compacted) imagePressure?.clear?.(responseHistoryPressureRootId(prepared));
|
|
483
|
+
else if (result?.upstreamStatus === 408) markCompactTimeout();
|
|
371
484
|
} catch (error) {
|
|
372
|
-
|
|
373
|
-
|
|
485
|
+
const responseError = applyAdaptiveTimeout(error);
|
|
486
|
+
logRequestFailure("Responses compact", responseError, abort);
|
|
487
|
+
sendJsonError(res, responseError, 502);
|
|
374
488
|
} finally {
|
|
375
489
|
releaseUpstreamPayload();
|
|
490
|
+
releaseHistorySnapshot();
|
|
376
491
|
abort.cleanup();
|
|
377
492
|
}
|
|
378
493
|
};
|
|
@@ -25,6 +25,20 @@ function bodyPayload(body, assertActive) {
|
|
|
25
25
|
return { bodyText, bodyBytes: Buffer.byteLength(bodyText) };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function responsesImagePressureEligible(reqContext, {
|
|
29
|
+
assertActive,
|
|
30
|
+
policy: policyOverrides,
|
|
31
|
+
} = {}) {
|
|
32
|
+
const policy = normalizedPolicy(policyOverrides);
|
|
33
|
+
const currentInputStart = Number.isFinite(reqContext?.currentInputStart)
|
|
34
|
+
? reqContext.currentInputStart
|
|
35
|
+
: 0;
|
|
36
|
+
const images = responsesHistoricalImageStats(reqContext?.body?.input, currentInputStart, { assertActive });
|
|
37
|
+
if (images.historicalImages === 0) return false;
|
|
38
|
+
if (images.historicalImages > policy.triggerHistoricalImages) return true;
|
|
39
|
+
return bodyPayload(reqContext?.body, assertActive).bodyBytes > policy.triggerBodyBytes;
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
export function applyResponsesImagePressure(reqContext, {
|
|
29
43
|
assertActive,
|
|
30
44
|
mode = "normal",
|
|
@@ -143,6 +157,22 @@ export function createResponsesImagePressureController({
|
|
|
143
157
|
}
|
|
144
158
|
return result;
|
|
145
159
|
},
|
|
160
|
+
isEligible(reqContext, { assertActive } = {}) {
|
|
161
|
+
return responsesImagePressureEligible(reqContext, { assertActive, policy });
|
|
162
|
+
},
|
|
163
|
+
applyRecovery(reqContext, { assertActive } = {}) {
|
|
164
|
+
if (modeFor(reqContext?.historyRootId) !== "recovery") return null;
|
|
165
|
+
const result = applyResponsesImagePressure(reqContext, {
|
|
166
|
+
assertActive,
|
|
167
|
+
mode: "recovery",
|
|
168
|
+
policy,
|
|
169
|
+
});
|
|
170
|
+
if (result.adapted) {
|
|
171
|
+
counters.adaptedRequests += 1;
|
|
172
|
+
counters.imagesOmitted += result.imagesOmitted;
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
},
|
|
146
176
|
markTimeout(rootId, { eligible = false } = {}) {
|
|
147
177
|
if (!rootId || !eligible) return false;
|
|
148
178
|
prune();
|
package/src/responses-proxy.mjs
CHANGED
|
@@ -2,10 +2,13 @@ import { responses as copilotResponses } from "./copilot.mjs";
|
|
|
2
2
|
import {
|
|
3
3
|
httpError,
|
|
4
4
|
logRequestFailure,
|
|
5
|
+
MAX_UPSTREAM_ERROR_BODY_BYTES,
|
|
6
|
+
readBoundedResponseText,
|
|
5
7
|
sendUpstreamError,
|
|
6
8
|
writeOrDrain,
|
|
7
9
|
} from "./http-transport.mjs";
|
|
8
|
-
import { openCopilotResponse
|
|
10
|
+
import { openCopilotResponse } from "./copilot-responses-compat.mjs";
|
|
11
|
+
import { rememberResponseHistory } from "./responses-request.mjs";
|
|
9
12
|
import {
|
|
10
13
|
compactionInputWithoutTrigger,
|
|
11
14
|
parseResponsesCompactionResult,
|
|
@@ -261,7 +264,12 @@ export async function proxyCopilotResponses(reqContext, req, res, upstream = cop
|
|
|
261
264
|
reader.releaseLock();
|
|
262
265
|
}
|
|
263
266
|
} else {
|
|
264
|
-
const data =
|
|
267
|
+
const data = resp.ok
|
|
268
|
+
? await resp.text()
|
|
269
|
+
: await readBoundedResponseText(resp, {
|
|
270
|
+
maxBytes: MAX_UPSTREAM_ERROR_BODY_BYTES,
|
|
271
|
+
label: "Copilot Responses error body",
|
|
272
|
+
});
|
|
265
273
|
if (resp.ok && reqContext.surface === "responses_compact") {
|
|
266
274
|
const response = parseResponsesCompactionResult(
|
|
267
275
|
compactionInputWithoutTrigger(reqContext.body.input),
|