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.
- package/README.md +11 -5
- package/package.json +1 -1
- package/src/adapter.mjs +1 -1
- package/src/cli-animation.mjs +43 -8
- package/src/cli-image.mjs +12 -3
- package/src/cli-options.mjs +1 -1
- package/src/codex-config-doctor.mjs +8 -3
- package/src/config-toml.mjs +147 -0
- package/src/config.mjs +74 -89
- package/src/copilot-responses-compat.mjs +4 -0
- package/src/image-mcp.mjs +62 -7
- package/src/image-provider-config.mjs +4 -1
- package/src/image-provider.mjs +96 -41
- package/src/image-references.mjs +41 -0
- package/src/image-skill.mjs +11 -7
- package/src/image-tool-client.mjs +16 -7
- package/src/responses-handler.mjs +38 -0
- package/src/responses-proxy.mjs +55 -22
- package/src/responses-request.mjs +3 -1
- package/src/terminal-animation-gallery.mjs +85 -0
package/src/responses-proxy.mjs
CHANGED
|
@@ -8,7 +8,11 @@ import {
|
|
|
8
8
|
sendUpstreamError,
|
|
9
9
|
writeOrDrain,
|
|
10
10
|
} from "./http-transport.mjs";
|
|
11
|
-
import {
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
+
}
|