codex-copilot-dx 0.7.9 → 0.8.0

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.
@@ -8,7 +8,11 @@ import {
8
8
  sendUpstreamError,
9
9
  writeOrDrain,
10
10
  } from "./http-transport.mjs";
11
- import { openCopilotResponse, selectCopilotResponseRetry } from "./copilot-responses-compat.mjs";
11
+ import {
12
+ hasCopilotResponseRetryPolicy,
13
+ openCopilotResponse,
14
+ selectCopilotResponseRetry,
15
+ } from "./copilot-responses-compat.mjs";
12
16
  import {
13
17
  dropMaterializedResponseHistory,
14
18
  hasEncryptedResponseState,
@@ -314,21 +318,29 @@ function streamHeaders(resp) {
314
318
  };
315
319
  }
316
320
 
317
- function selectStreamFailureRetry(reqContext, failure, usedRetryPolicies) {
321
+ async function selectStreamFailureRetry(reqContext, failure, usedRetryPolicies, options) {
318
322
  if (!failure) return null;
323
+ const errorText = responseFailureText(failure.event, failure.eventType);
324
+ if (!hasCopilotResponseRetryPolicy(400, errorText, usedRetryPolicies)) return null;
325
+ const preparation = await options.acquireRetryPreparation?.(reqContext);
326
+ let keepReservation = false;
319
327
  try {
320
- const restored = restoreMaterializedResponseHistoryForRetry(reqContext);
328
+ const restored = restoreMaterializedResponseHistoryForRetry(reqContext, {
329
+ assertActive: options.assertPrepareActive,
330
+ historySnapshot: preparation?.historySnapshot,
331
+ });
332
+ const retry = selectCopilotResponseRetry(restored, 400, errorText, usedRetryPolicies);
333
+ options.assertPrepareActive?.();
334
+ keepReservation = Boolean(retry);
321
335
  return {
322
- retry: selectCopilotResponseRetry(
323
- restored,
324
- 400,
325
- responseFailureText(failure.event, failure.eventType),
326
- usedRetryPolicies,
327
- ),
336
+ retry: retry ? { ...retry, releasePreparation: preparation?.release } : null,
328
337
  retrySkipped: null,
329
338
  };
330
339
  } catch {
340
+ options.assertPrepareActive?.();
331
341
  return { retry: null, retrySkipped: "history_unavailable" };
342
+ } finally {
343
+ if (!keepReservation) preparation?.release();
332
344
  }
333
345
  }
334
346
 
@@ -362,6 +374,7 @@ async function proxyStreamingResponses(opened, res, upstream, options) {
362
374
  return { successful: false, compacted: false, upstreamStatus: resp.status };
363
375
  }
364
376
  await requireUpstreamEventStream(resp);
377
+ options.onStreamStart?.();
365
378
  options.abort?.setTimeout(options.streamIdleTimeoutMs, "stream_idle_timeout");
366
379
  const headers = streamHeaders(resp);
367
380
  let headersSent = false;
@@ -424,7 +437,13 @@ async function proxyStreamingResponses(opened, res, upstream, options) {
424
437
 
425
438
  let retry = null;
426
439
  if (streamState.failure && holdPrelude && !streamState.sawOutput) {
427
- const selected = selectStreamFailureRetry(reqContext, streamState.failure, usedRetryPolicies);
440
+ let selected;
441
+ try {
442
+ selected = await selectStreamFailureRetry(reqContext, streamState.failure, usedRetryPolicies, options);
443
+ } catch (error) {
444
+ recordResponseFailure(options, reqContext, resp, streamState.failure, { retrySkipped: "retry_preparation_failed" });
445
+ throw error;
446
+ }
428
447
  retry = selected?.retry || null;
429
448
  if (!retry) retryDisabledReason = selected?.retrySkipped || "not_applicable";
430
449
  }
@@ -436,12 +455,16 @@ async function proxyStreamingResponses(opened, res, upstream, options) {
436
455
  }
437
456
  if (retry) {
438
457
  console.warn(status("warn", retry.warning));
439
- opened = await openCopilotResponse(retry.reqContext, upstream, {
440
- ...options,
441
- usedRetryPolicies: retry.usedPolicies,
442
- });
443
- opened.streamRetryEligible = hasEncryptedResponseState(opened.reqContext.body?.input);
444
- dropMaterializedResponseHistory(opened.reqContext);
458
+ try {
459
+ opened = await openCopilotResponse(retry.reqContext, upstream, {
460
+ ...options,
461
+ usedRetryPolicies: retry.usedPolicies,
462
+ });
463
+ opened.streamRetryEligible = hasEncryptedResponseState(opened.reqContext.body?.input);
464
+ dropMaterializedResponseHistory(opened.reqContext);
465
+ } finally {
466
+ retry.releasePreparation?.();
467
+ }
445
468
  continue;
446
469
  }
447
470
  if (streamState.failure) markStreamFailure();
@@ -515,7 +538,13 @@ export async function proxyCopilotResponses(reqContext, req, res, upstream = cop
515
538
  const response = parseSuccessfulResponsesResult(data);
516
539
  if (response.status === "failed") {
517
540
  const failure = { event: { type: "response.failed", response }, eventType: "response.failed" };
518
- const selected = selectStreamFailureRetry(reqContext, failure, usedRetryPolicies);
541
+ let selected;
542
+ try {
543
+ selected = await selectStreamFailureRetry(reqContext, failure, usedRetryPolicies, options);
544
+ } catch (error) {
545
+ recordResponseFailure(options, reqContext, resp, failure, { retrySkipped: "retry_preparation_failed" });
546
+ throw error;
547
+ }
519
548
  const retry = selected?.retry || null;
520
549
  recordResponseFailure(options, reqContext, resp, failure, {
521
550
  retry,
@@ -523,11 +552,15 @@ export async function proxyCopilotResponses(reqContext, req, res, upstream = cop
523
552
  });
524
553
  if (retry) {
525
554
  console.warn(status("warn", retry.warning));
526
- opened = await openCopilotResponse(retry.reqContext, upstream, {
527
- ...options,
528
- usedRetryPolicies: retry.usedPolicies,
529
- });
530
- dropMaterializedResponseHistory(opened.reqContext);
555
+ try {
556
+ opened = await openCopilotResponse(retry.reqContext, upstream, {
557
+ ...options,
558
+ usedRetryPolicies: retry.usedPolicies,
559
+ });
560
+ dropMaterializedResponseHistory(opened.reqContext);
561
+ } finally {
562
+ retry.releasePreparation?.();
563
+ }
531
564
  continue;
532
565
  }
533
566
  }
@@ -389,7 +389,8 @@ export function dropMaterializedResponseHistory(reqContext) {
389
389
  return true;
390
390
  }
391
391
 
392
- export function restoreMaterializedResponseHistoryForRetry(reqContext, { assertActive } = {}) {
392
+ export function restoreMaterializedResponseHistoryForRetry(reqContext, { assertActive, historySnapshot } = {}) {
393
+ assertActive?.();
393
394
  if (!reqContext?.[RELEASED_MATERIALIZED_HISTORY] || !reqContext.historyParentId) return reqContext;
394
395
  const body = cloneJson(reqContext.body);
395
396
  body.previous_response_id = reqContext.historyParentId;
@@ -397,6 +398,7 @@ export function restoreMaterializedResponseHistoryForRetry(reqContext, { assertA
397
398
  let restored = prepareResponsesRequest(body, {
398
399
  assertActive,
399
400
  copilotBoundary: false,
401
+ historySnapshot,
400
402
  mutate: true,
401
403
  });
402
404
  restored.surface = reqContext.surface;
@@ -0,0 +1,85 @@
1
+ import {
2
+ TERMINAL_ANIMATION_THEMES,
3
+ TERMINAL_ANIMATION_TRACK_WIDTH,
4
+ getTerminalAnimationFrameDelay,
5
+ renderTerminalAnimationFrame,
6
+ } from "./terminal-animation.mjs";
7
+
8
+ const ESC = "\u001b";
9
+ const TICK_MS = 32;
10
+
11
+ export function startTerminalAnimationGallery({
12
+ output,
13
+ env = process.env,
14
+ prefixWidth,
15
+ rowsBelow = 0,
16
+ getInputRows = () => 0,
17
+ timers = { setTimeout, clearTimeout },
18
+ now = () => performance.now(),
19
+ } = {}) {
20
+ const columns = Number(output?.columns);
21
+ const rows = Number(output?.rows);
22
+ const disabled = /^(0|false|no|off)$/i.test(String(env.CCDX_TERMINAL_ANIMATION || "").trim());
23
+ const ci = String(env.CI || "").trim();
24
+ if (output?.isTTY !== true || disabled || (ci && !/^(0|false|no|off)$/i.test(ci))
25
+ || String(env.TERM || "").toLowerCase() === "dumb" || !Number.isFinite(columns) || !Number.isFinite(rows)
26
+ || columns <= prefixWidth + TERMINAL_ANIMATION_TRACK_WIDTH + 2
27
+ || rows < TERMINAL_ANIMATION_THEMES.length + rowsBelow + 2) {
28
+ return { enabled: false, stop() {} };
29
+ }
30
+
31
+ const timelines = TERMINAL_ANIMATION_THEMES.map((theme) => {
32
+ let duration = 0;
33
+ const frames = Array.from({ length: theme.frameCount }, (_, offset) => {
34
+ const frame = theme.startFrame + offset;
35
+ duration += getTerminalAnimationFrameDelay(theme.id, frame);
36
+ return { frame, until: duration };
37
+ });
38
+ return { theme, frames, duration: duration + theme.loopPauseMs, lastFrame: theme.startFrame };
39
+ });
40
+ const startedAt = now();
41
+ let timer;
42
+ let stopped = false;
43
+ const stop = () => {
44
+ if (stopped) return;
45
+ stopped = true;
46
+ if (timer !== undefined) timers.clearTimeout(timer);
47
+ output.off?.("resize", stop);
48
+ output.off?.("error", stop);
49
+ output.off?.("close", stop);
50
+ };
51
+ const draw = () => {
52
+ timer = undefined;
53
+ if (stopped) return;
54
+ try {
55
+ const inputRows = getInputRows();
56
+ // Once input scrolls the menu out of view its old row anchors are no longer safe.
57
+ if (!Number.isSafeInteger(inputRows) || inputRows < 0
58
+ || Number(output.rows) < timelines.length + rowsBelow + inputRows + 2) {
59
+ stop();
60
+ return;
61
+ }
62
+ const elapsed = now() - startedAt;
63
+ let update = "";
64
+ timelines.forEach((timeline, index) => {
65
+ const position = elapsed % timeline.duration;
66
+ const frame = timeline.frames.find(({ until }) => position < until)?.frame ?? null;
67
+ if (frame === timeline.lastFrame) return;
68
+ timeline.lastFrame = frame;
69
+ const track = frame === null ? `[${" ".repeat(TERMINAL_ANIMATION_TRACK_WIDTH)}]`
70
+ : renderTerminalAnimationFrame(timeline.theme.id, frame);
71
+ const up = timelines.length - index + rowsBelow + inputRows;
72
+ update += `${ESC}7${ESC}[${up}A${ESC}[${prefixWidth + 1}G${track}${ESC}8`;
73
+ });
74
+ if (update && output.write(update) === false) { stop(); return; }
75
+ timer = timers.setTimeout(draw, TICK_MS);
76
+ timer?.unref?.();
77
+ } catch { stop(); }
78
+ };
79
+ output.on?.("resize", stop);
80
+ output.on?.("error", stop);
81
+ output.on?.("close", stop);
82
+ timer = timers.setTimeout(draw, TICK_MS);
83
+ timer?.unref?.();
84
+ return { enabled: true, stop };
85
+ }