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.
@@ -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 { responseHistoryMaterializedBytes } from "./response-history.mjs";
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?.historyRootId, {
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
- const historyBytes = responseHistoryMaterializedBytes(parsed.previous_response_id);
170
- if (historyBytes > 0) {
171
- await releaseRequest.reserveResponseHistory?.(historyBytes, { signal: abort.signal });
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, { assertActive: assertPrepareActive, mutate: true });
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 { requestedModel, upstreamModel } = resolveRequestModel(model, openAIModelEnv, autoReviewModelResolver);
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 = requestedModel === CODEX_AUTO_REVIEW_MODEL
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
- if (useNativeResponses) {
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.historyRootId);
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.historyRootId);
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 = await upstream.text();
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.historyRootId);
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
- let activeDeadline = now() + upstreamTimeoutMs;
332
- let activeTimeoutReason = "responses_prepare_timeout";
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
- const historyBytes = responseHistoryMaterializedBytes(parsed.previous_response_id);
348
- if (historyBytes > 0) {
349
- await releaseRequest.reserveResponseHistory?.(historyBytes, { signal: abort.signal });
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 = prepareResponsesCompactionRequest(
354
- prepareResponsesRequest(parsed, { assertActive: assertPrepareActive, mutate: true }),
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 { requestedModel, upstreamModel } = resolveRequestModel(model, openAIModelEnv, autoReviewModelResolver);
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.historyRootId);
482
+ if (result?.compacted) imagePressure?.clear?.(responseHistoryPressureRootId(prepared));
483
+ else if (result?.upstreamStatus === 408) markCompactTimeout();
371
484
  } catch (error) {
372
- logRequestFailure("Responses compact", error, abort);
373
- sendJsonError(res, error, 502);
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();
@@ -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, rememberResponseHistory } from "./responses-request.mjs";
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 = await resp.text();
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),