codex-copilot-dx 0.9.22 → 0.9.24
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 +13 -14
- package/bin/cli.mjs +47 -5
- package/docs/REFERENCE.md +76 -42
- package/package.json +1 -1
- package/src/adapter.mjs +3 -1
- package/src/cli-options.mjs +13 -4
- package/src/launcher.mjs +7 -2
- package/src/native-prediction-overlay.mjs +92 -0
- package/src/native-prediction-profile.mjs +181 -0
- package/src/native-prediction-transport.mjs +242 -0
- package/src/native-prediction.mjs +146 -0
- package/src/prediction-retirement.mjs +75 -0
- package/src/suggestion-auto.mjs +7 -0
- package/src/suggestion-mcp.mjs +65 -111
- package/src/cli-suggestion.mjs +0 -101
- package/src/suggestion-config.mjs +0 -77
- package/src/suggestion-context.mjs +0 -107
- package/src/suggestion-provider.mjs +0 -41
- package/src/suggestion-ui.html +0 -26
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ Use Codex App with GitHub Copilot through a local adapter.
|
|
|
5
5
|
CCDX targets Codex App. It discovers Copilot models, adapts the client's model
|
|
6
6
|
catalog, and preserves Responses streaming, tool calls and conversation history.
|
|
7
7
|
Default Auto-review: **GPT-6.1 Sol / low**. Image generation is **off by default**.
|
|
8
|
+
Native predictions are also **off by default**.
|
|
8
9
|
|
|
9
10
|
## Install and run
|
|
10
11
|
|
|
@@ -38,14 +39,13 @@ The dashboard does not poll in the background; refresh when needed.
|
|
|
38
39
|
| `ccdx models` | Fresh upstream model list |
|
|
39
40
|
| `ccdx usage` | Recorded tokens and upstream prompt-cache hit rate |
|
|
40
41
|
| `ccdx auto-review-model` | Select model, then reasoning effort; `0` follows client |
|
|
41
|
-
| `ccdx suggestion on` / `ccdx suggestion off` | Enable or disable the optional next-message panel |
|
|
42
|
-
| `ccdx suggestion model` / `ccdx suggestion status` | Choose its model by number, or check settings |
|
|
43
42
|
| `ccdx animation` | Preview and select terminal animation |
|
|
44
43
|
| `ccdx cache --limit 128` | Set the history-cache budget in MiB |
|
|
45
44
|
| `ccdx cache --clean` | Clear the rebuildable image-transform cache |
|
|
46
45
|
| `ccdx limits --decoded 256` | Set the decoded request-body budget; restart to apply |
|
|
47
46
|
| `ccdx enable-image` | Opt in to image generation/editing with your own API |
|
|
48
47
|
| `ccdx image-status` / `ccdx disable-image` | Check or disable optional images |
|
|
48
|
+
| `ccdx prediction on` / `off` / `status` | Opt in to native next-message predictions (macOS) |
|
|
49
49
|
| `ccdx update` | Update the global package |
|
|
50
50
|
|
|
51
51
|
## Compatibility and safety
|
|
@@ -64,18 +64,17 @@ The dashboard does not poll in the background; refresh when needed.
|
|
|
64
64
|
break continuations. Generated images and Codex transcripts are not deleted.
|
|
65
65
|
- Optional image setup/maintenance never blocks the primary service. Disabled
|
|
66
66
|
users get no CCDX image skill or MCP installation.
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
reliably detected. Use `ccdx suggestion off` when preferring an official feature.
|
|
67
|
+
- The manual prediction MCP, background hook and dashboard panel are retired.
|
|
68
|
+
Startup removes only unchanged CCDX registrations and disables saved prediction
|
|
69
|
+
settings. The optional image MCP remains unchanged. Modified old registrations
|
|
70
|
+
can use inert compatibility entrypoints; they no longer generate suggestions.
|
|
71
|
+
- Native predictions are a separate, opt-in compatibility feature. Quit Codex,
|
|
72
|
+
run `ccdx prediction on`, then restart CCDX. This adds App-profile certificate
|
|
73
|
+
trust and an App-only route for `chatgpt.com` and `ab.chatgpt.com`; HTTPS on those
|
|
74
|
+
hosts is decrypted locally, but only prediction configuration is changed.
|
|
75
|
+
Other domains and the CCDX model endpoint stay on their existing routes.
|
|
76
|
+
No program files or OS trust are changed. Existing proxies are not replaced.
|
|
77
|
+
See the reference for restart, rollback and client-version boundaries.
|
|
79
78
|
- CCDX adds experimental local history compression only when installed App
|
|
80
79
|
runtimes support it and report it off. Existing choices are preserved; an
|
|
81
80
|
already-enabled default, unavailable runtime or failed check is skipped.
|
package/bin/cli.mjs
CHANGED
|
@@ -77,8 +77,21 @@ if (CLI.command === "image") {
|
|
|
77
77
|
}
|
|
78
78
|
if (CLI.command === "suggestion") {
|
|
79
79
|
try {
|
|
80
|
-
const {
|
|
81
|
-
|
|
80
|
+
const { retirePredictionIntegration } = await import("../src/prediction-retirement.mjs");
|
|
81
|
+
const result = retirePredictionIntegration({ dryRun: CLI.action === "status" });
|
|
82
|
+
console.log("The CCDX prediction panel has been retired and cannot be enabled.");
|
|
83
|
+
if (CLI.action === "status" && result.changed) console.log(`Legacy settings remain; run ${CLI_NAME} suggestion off to clean them up.`);
|
|
84
|
+
if (result.preserved) console.warn("Modified prediction settings were preserved.");
|
|
85
|
+
process.exit(0);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(status("err", error.message));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (CLI.command === "prediction") {
|
|
92
|
+
try {
|
|
93
|
+
const { runPredictionCommand } = await import("../src/native-prediction.mjs");
|
|
94
|
+
await runPredictionCommand({ action: CLI.action });
|
|
82
95
|
process.exit(0);
|
|
83
96
|
} catch (error) {
|
|
84
97
|
console.error(status("err", error.message));
|
|
@@ -251,6 +264,7 @@ const LOGGING = configureLogging();
|
|
|
251
264
|
let activeServer = null;
|
|
252
265
|
let modelRefreshTimer = null;
|
|
253
266
|
let shuttingDown = false;
|
|
267
|
+
let nativePrediction = null;
|
|
254
268
|
|
|
255
269
|
if (LOGGING.filePath) {
|
|
256
270
|
console.log(status("info", `Debug log: ${LOGGING.filePath}`));
|
|
@@ -272,7 +286,18 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
272
286
|
console.log(status("ok", `Using existing adapter at ${running.baseUrl}`));
|
|
273
287
|
const { compressionSetup } = await configureCodexClient();
|
|
274
288
|
|
|
275
|
-
|
|
289
|
+
let predictionPacUrl = null;
|
|
290
|
+
let predictionApp = null;
|
|
291
|
+
try {
|
|
292
|
+
const { readPredictionSettings } = await import("../src/native-prediction-profile.mjs");
|
|
293
|
+
const preference = readPredictionSettings();
|
|
294
|
+
if (preference.enabled) {
|
|
295
|
+
predictionPacUrl = running.data?.native_prediction_pac || null;
|
|
296
|
+
predictionApp = preference.app?.app;
|
|
297
|
+
if (!predictionPacUrl) console.warn(status("warn", "Restart ccdx to activate the newly enabled prediction bridge"));
|
|
298
|
+
}
|
|
299
|
+
} catch (error) { console.warn(status("warn", error.message)); }
|
|
300
|
+
await openCodex({ predictionPacUrl, predictionApp });
|
|
276
301
|
console.log(`
|
|
277
302
|
${status("ok", "Ready, using the existing ccdx adapter")}
|
|
278
303
|
|
|
@@ -285,6 +310,14 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
285
310
|
}
|
|
286
311
|
|
|
287
312
|
async function configureCodexClient() {
|
|
313
|
+
try {
|
|
314
|
+
const { retirePredictionIntegration } = await import("../src/prediction-retirement.mjs");
|
|
315
|
+
const result = retirePredictionIntegration();
|
|
316
|
+
if (result.changed) console.log(status("info", "Removed the retired CCDX prediction integration"));
|
|
317
|
+
if (result.preserved) console.warn(status("warn", "Modified prediction settings were preserved"));
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.warn(status("warn", `${error.message}. Core startup will continue.`));
|
|
320
|
+
}
|
|
288
321
|
const imageProviderEnabled = Boolean(readImageProviderConfig());
|
|
289
322
|
const { imageMcpSkipped } = ensureCodexConfig(ADAPTER_PORT, { host: ADAPTER_HOST, imageProviderEnabled });
|
|
290
323
|
if (imageProviderEnabled && !imageMcpSkipped) {
|
|
@@ -369,13 +402,20 @@ try {
|
|
|
369
402
|
streamHandshakeTimeoutMs: RUNTIME.streamHandshakeTimeoutMs,
|
|
370
403
|
streamIdleTimeoutMs: RUNTIME.streamIdleTimeoutMs,
|
|
371
404
|
terminalAnimationTheme,
|
|
405
|
+
nativePredictionPac: () => nativePrediction?.pacUrl || null,
|
|
372
406
|
});
|
|
373
407
|
|
|
374
408
|
// Point Codex App at the adapter.
|
|
375
409
|
const { compressionSetup } = await configureCodexClient();
|
|
376
410
|
|
|
377
|
-
//
|
|
378
|
-
|
|
411
|
+
// Optional native prediction setup must never prevent the core adapter from running.
|
|
412
|
+
try {
|
|
413
|
+
const { startNativePrediction } = await import("../src/native-prediction.mjs");
|
|
414
|
+
nativePrediction = await startNativePrediction({ warning: (message) => console.log(status("info", message)) });
|
|
415
|
+
} catch (error) { console.warn(status("warn", `${error.message}. Core startup will continue.`)); }
|
|
416
|
+
|
|
417
|
+
// Launch Codex when available. A running App is never forcibly restarted.
|
|
418
|
+
await openCodex({ predictionPacUrl: nativePrediction?.pacUrl, predictionApp: nativePrediction?.app });
|
|
379
419
|
|
|
380
420
|
// Periodically refresh the model list + endpoint cache so a long-running
|
|
381
421
|
// adapter picks up newly released models without a restart.
|
|
@@ -404,6 +444,7 @@ try {
|
|
|
404
444
|
let exitCode = 0;
|
|
405
445
|
try {
|
|
406
446
|
if (modelRefreshTimer) clearInterval(modelRefreshTimer);
|
|
447
|
+
await nativePrediction?.close();
|
|
407
448
|
const result = await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs });
|
|
408
449
|
if (result.forced) console.warn(status("warn", "Forced remaining adapter connections closed"));
|
|
409
450
|
await flushUsageWrites();
|
|
@@ -420,6 +461,7 @@ try {
|
|
|
420
461
|
await compressionSetup;
|
|
421
462
|
} catch (e) {
|
|
422
463
|
console.error(status("err", e.message));
|
|
464
|
+
await nativePrediction?.close().catch(() => {});
|
|
423
465
|
await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs }).catch(() => {});
|
|
424
466
|
await flushUsageWrites();
|
|
425
467
|
await LOGGING.cleanup();
|
package/docs/REFERENCE.md
CHANGED
|
@@ -6,52 +6,86 @@ CCDX is a local compatibility adapter for Codex App. It configures Codex to use
|
|
|
6
6
|
|
|
7
7
|
## How it works
|
|
8
8
|
|
|
9
|
-
###
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
### Retired prediction integration
|
|
10
|
+
|
|
11
|
+
The manual prediction MCP, automatic Stop hook and dashboard panel are removed.
|
|
12
|
+
CCDX does not install a prediction skill or replacement predictor. Startup disables
|
|
13
|
+
the old `suggestion.json` preference and removes only unchanged CCDX declarations
|
|
14
|
+
from the active `CODEX_HOME/config.toml` (or `~/.codex/config.toml`).
|
|
15
|
+
`CCDX_SUGGESTION_CONFIG` still identifies the legacy settings path for cleanup.
|
|
16
|
+
For an immediate cleanup without starting the adapter, run `ccdx suggestion off`.
|
|
17
|
+
The old `suggestion on` and `suggestion model` commands cannot re-enable it.
|
|
18
|
+
|
|
19
|
+
Modified, inline and profile-specific declarations are preserved. Cleanup failures
|
|
20
|
+
do not block the core adapter. Saved model preferences and old candidate files are
|
|
21
|
+
not deleted; they are inactive. Native Codex preference settings, hook trust records,
|
|
22
|
+
other hooks and the optional image MCP are untouched. Two inert compatibility
|
|
23
|
+
entrypoints prevent user-modified registrations from invoking a removed module:
|
|
24
|
+
the old Hook exits with an empty result, and the old MCP exposes no tools or panel.
|
|
25
|
+
|
|
26
|
+
### Optional native predictions
|
|
27
|
+
|
|
28
|
+
Native next-message predictions are **off by default** and use no MCP, Hook, skill,
|
|
29
|
+
panel, renderer injection, or client-program modification. This macOS-only
|
|
30
|
+
compatibility feature uses the App's existing composer UI and the conversation's
|
|
31
|
+
selected model through CCDX. Suggestions are not sent automatically. Native
|
|
32
|
+
eligibility rules still apply; availability is not promised for every thread type.
|
|
13
33
|
|
|
14
34
|
```bash
|
|
15
|
-
|
|
16
|
-
ccdx
|
|
17
|
-
|
|
18
|
-
ccdx
|
|
19
|
-
ccdx
|
|
35
|
+
# Fully quit Codex App first.
|
|
36
|
+
ccdx prediction on
|
|
37
|
+
# Restart the running CCDX process, then launch Codex through ccdx.
|
|
38
|
+
ccdx
|
|
39
|
+
ccdx prediction status
|
|
40
|
+
|
|
41
|
+
# To remove the optional setup, fully quit Codex first:
|
|
42
|
+
ccdx prediction off
|
|
20
43
|
```
|
|
21
44
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
The
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
Enabling explicitly adds a private certificate trust record to the selected App
|
|
46
|
+
profile and a process-scoped PAC route for `chatgpt.com` and `ab.chatgpt.com` only.
|
|
47
|
+
HTTPS traffic for **all paths on those two hosts** is decrypted and forwarded
|
|
48
|
+
locally; PAC cannot select HTTPS paths before TLS. Only the bootstrap prediction
|
|
49
|
+
flag, prediction configuration, and corresponding Statsig refresh values are
|
|
50
|
+
overlaid. Other paths preserve upstream status, cookies, streaming and WebSocket
|
|
51
|
+
traffic. Upstream TLS verification stays enabled; no OS trust, global proxy, login,
|
|
52
|
+
account identity or account-permission value is rewritten. Credentials are not
|
|
53
|
+
logged or persisted by the bridge. The local private key has owner-only permissions;
|
|
54
|
+
its signing key is discarded after certificate creation.
|
|
55
|
+
|
|
56
|
+
Existing App proxy/PAC/environment routes cause setup to skip with an explanation.
|
|
57
|
+
This initial version does not compose enterprise proxies. The default App profile
|
|
58
|
+
is `~/Library/Application Support/Codex`; an explicit
|
|
59
|
+
`CODEX_ELECTRON_USER_DATA_PATH` is respected. Quit the App before trust changes;
|
|
60
|
+
CCDX never kills it. The launcher uses the exact App that passed the compatibility
|
|
61
|
+
check. Updating the App invalidates that check: quit it and run `prediction on`
|
|
62
|
+
again. Unknown client/store/configuration formats are not forcibly rewritten.
|
|
63
|
+
These are checked compatibility boundaries, not a promise for every future App.
|
|
64
|
+
|
|
65
|
+
The bridge changes only its own prediction fields and preserves already-enabled
|
|
66
|
+
official prediction configuration. A missing prediction-config route can receive
|
|
67
|
+
CCDX's own short JSON-suggestion prompt using low reasoning effort. Authentication,
|
|
68
|
+
permission, rate-limit and server errors are returned unchanged. Full and live
|
|
69
|
+
refresh schemas are supported; delta requests ask for complete values to avoid
|
|
70
|
+
merging upstream checksums with locally changed fields. Configuration bodies are
|
|
71
|
+
bounded at 16 MiB; larger or unknown bodies pass through without an overlay.
|
|
72
|
+
No extra inference or bridge exists for users who leave the feature disabled.
|
|
73
|
+
When enabled, prediction requests consume Copilot usage; the setting does not
|
|
74
|
+
provide a separate prediction-model picker.
|
|
75
|
+
|
|
76
|
+
`prediction off` immediately disables new local overlays. Quit/reopen Codex to
|
|
77
|
+
clear its cached predictions and temporary route. If the App is still running,
|
|
78
|
+
trust removal is deferred; quit it and run `prediction off` again. Only the exact
|
|
79
|
+
owned certificate record is removed, never the whole store. Failed activation
|
|
80
|
+
rolls back newly added trust; certificate replacement keeps old trust until the
|
|
81
|
+
new preference is committed. If the bridge stops, new connections can fall back
|
|
82
|
+
to direct routing; already-interrupted requests are not replayed. Run `ccdx`
|
|
83
|
+
after quitting Codex to attach a newly started bridge. `CCDX_AUTO_LAUNCH=0` keeps
|
|
84
|
+
automatic launch disabled and does not attach the route to a manually opened App.
|
|
85
|
+
Optional prediction failures never prevent the core adapter or image service from
|
|
86
|
+
starting. `prediction status` reports the saved preference, not proof of live UI
|
|
87
|
+
availability. Prediction settings are stored separately in
|
|
88
|
+
`~/.config/codex-copilot-dx/native-prediction/` (or under `XDG_CONFIG_HOME`).
|
|
55
89
|
|
|
56
90
|
### Core adapter
|
|
57
91
|
|
package/package.json
CHANGED
package/src/adapter.mjs
CHANGED
|
@@ -242,7 +242,9 @@ export function createAdapterHandler(options = {}) {
|
|
|
242
242
|
|
|
243
243
|
if (req.method === "GET" && pathname === ADAPTER_HEALTH_PATH) {
|
|
244
244
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
245
|
-
|
|
245
|
+
const predictionPac = isLoopbackAddress(req.socket?.remoteAddress) && isLoopbackHostHeader(req.headers?.host)
|
|
246
|
+
? options.nativePredictionPac?.() : null;
|
|
247
|
+
res.end(JSON.stringify({ ...adapterHealthPayload(), ...(predictionPac ? { native_prediction_pac: predictionPac } : {}) }));
|
|
246
248
|
return;
|
|
247
249
|
}
|
|
248
250
|
|
package/src/cli-options.mjs
CHANGED
|
@@ -28,6 +28,7 @@ const HELP_TOPICS = new Set([
|
|
|
28
28
|
"image-status",
|
|
29
29
|
"auto-review-model",
|
|
30
30
|
"suggestion",
|
|
31
|
+
"prediction",
|
|
31
32
|
"update",
|
|
32
33
|
"version",
|
|
33
34
|
]);
|
|
@@ -252,11 +253,18 @@ export function parseCliArgs(args = []) {
|
|
|
252
253
|
if (rest.length) unexpectedArgs(rest);
|
|
253
254
|
return baseCommand("animation");
|
|
254
255
|
}
|
|
256
|
+
if (command === "prediction") {
|
|
257
|
+
const [action = "status", ...extra] = rest;
|
|
258
|
+
if (!["on", "off", "status"].includes(action)) throw new Error("Prediction action must be on, off, or status");
|
|
259
|
+
if (extra.length) unexpectedArgs(extra);
|
|
260
|
+
return baseCommand("prediction", { action });
|
|
261
|
+
}
|
|
255
262
|
if (command === "suggestion") {
|
|
256
263
|
const [action = "status", model, ...extra] = rest;
|
|
257
264
|
if (!["on", "off", "model", "status"].includes(action)) throw new Error("Suggestion action must be on, off, model, or status");
|
|
258
265
|
if (extra.length || (model !== undefined && action !== "model")) unexpectedArgs(rest.slice(1));
|
|
259
|
-
|
|
266
|
+
if (["on", "model"].includes(action)) throw new Error("The CCDX prediction panel has been retired; use ccdx prediction for the separate optional native feature");
|
|
267
|
+
return baseCommand("suggestion", { action });
|
|
260
268
|
}
|
|
261
269
|
if (command === "enable-image" || command === "disable-image" || command === "image-status") {
|
|
262
270
|
if (rest.length) unexpectedArgs(rest);
|
|
@@ -363,7 +371,8 @@ function topicHelp(name, topic) {
|
|
|
363
371
|
"disable-image": `Usage:\n ${name} disable-image\n\nDisables the local CCDX image tool and removes its stored API credential and unmodified CCDX image guidance.`,
|
|
364
372
|
"image-status": `Usage:\n ${name} image-status\n\nShows provider configuration and locally advertised generation/editing tools without generating an image or exposing its API key.`,
|
|
365
373
|
"auto-review-model": `Usage:\n ${name} auto-review-model\n\nSelect a Responses model, then an advertised reasoning effort. Enter keeps the default/current choice; q cancels both steps without saving. Effort 0 follows the client (low when omitted). Settings apply only to Auto-review on the next request; no client patch or restart is required for adapters running 0.9.13 or newer.`,
|
|
366
|
-
"suggestion": `
|
|
374
|
+
"suggestion": `The CCDX prediction panel has been retired. Run ${name} suggestion off to safely remove unchanged CCDX registrations. This cleanup does not change native predictions; see ${name} prediction --help for that separate opt-in feature.`,
|
|
375
|
+
prediction: `Usage:\n ${name} prediction [status|on|off]\n\nOptional native next-message predictions; off by default. macOS only. Quit Codex before on/off, then restart ccdx. On adds private App-profile certificate trust and an App-only route for chatgpt.com and ab.chatgpt.com. HTTPS on those hosts is decrypted locally; only prediction configuration is changed. No client program files, OS trust, account permissions, MCP or Hooks are changed. Existing proxies are not replaced. Official prediction configuration is preserved; the conversation's model is used. App updates require another compatibility check.`,
|
|
367
376
|
update: `Usage:\n ${name} update [npm|github]\n\nUpdates the global package from the configured npm registry or the latest stable GitHub release. With no source, an interactive terminal prompts for one.`,
|
|
368
377
|
version: `Usage:\n ${name} --version`,
|
|
369
378
|
};
|
|
@@ -389,7 +398,7 @@ export function cliHelp(commandName = "ccdx", topic = "") {
|
|
|
389
398
|
${name} disable-image
|
|
390
399
|
${name} image-status
|
|
391
400
|
${name} auto-review-model
|
|
392
|
-
${name}
|
|
401
|
+
${name} prediction [status|on|off]
|
|
393
402
|
${name} update [npm|github]
|
|
394
403
|
${name} --version
|
|
395
404
|
${name} --help
|
|
@@ -408,7 +417,7 @@ Commands:
|
|
|
408
417
|
disable-image Disable the image provider and remove its credential
|
|
409
418
|
image-status Show the optional image provider status
|
|
410
419
|
auto-review-model Select the Codex Auto-review model and reasoning effort
|
|
411
|
-
|
|
420
|
+
prediction Toggle optional native next-message predictions
|
|
412
421
|
update Update the global package from npm or GitHub
|
|
413
422
|
|
|
414
423
|
Run ${name} <command> --help for command details.`;
|
package/src/launcher.mjs
CHANGED
|
@@ -36,7 +36,7 @@ export function launchAttempts() {
|
|
|
36
36
|
return [["-b", CODEX_BUNDLE_ID], ...APP_PATH_CANDIDATES.map((path) => [path])];
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export async function openCodex({ env = process.env, spawnImpl = spawn, platform = process.platform } = {}) {
|
|
39
|
+
export async function openCodex({ env = process.env, spawnImpl = spawn, platform = process.platform, predictionPacUrl = null, predictionApp = null } = {}) {
|
|
40
40
|
if (platform !== "darwin") {
|
|
41
41
|
console.log(status("warn", "Auto-launch is only supported on macOS"));
|
|
42
42
|
console.log(status("info", "Open Codex App manually; it will connect to the adapter."));
|
|
@@ -48,7 +48,12 @@ export async function openCodex({ env = process.env, spawnImpl = spawn, platform
|
|
|
48
48
|
return false;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
const extraArgs = typeof predictionPacUrl === "string" && /^http:\/\/127\.0\.0\.1:\d{1,5}\/proxy\.pac$/.test(predictionPacUrl)
|
|
52
|
+
&& APP_PATH_CANDIDATES.includes(predictionApp)
|
|
53
|
+
? ["--args", `--proxy-pac-url=${predictionPacUrl}`] : [];
|
|
54
|
+
const attempts = extraArgs.length ? [[predictionApp]] : launchAttempts();
|
|
55
|
+
for (const attempt of attempts) {
|
|
56
|
+
const args = [...attempt, ...extraArgs];
|
|
52
57
|
if (await tryOpen(args, { spawnImpl })) {
|
|
53
58
|
console.log(status("ok", "Codex app launched"));
|
|
54
59
|
return true;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export const NATIVE_PREDICTION_GATE = "4290031608";
|
|
2
|
+
|
|
3
|
+
const LOCAL_PREDICTION_CONFIG = Object.freeze({
|
|
4
|
+
is_enabled: true,
|
|
5
|
+
prompt: 'Predict one useful next message the user may send, based on the conversation. Write from the user\'s perspective and use the user\'s language. Do not answer the message, call tools, or invent new requests, consent, or permission. Prefer a short, natural follow-up. If no useful follow-up is apparent, use an empty string. Return only a JSON object with exactly one key: {"suggestion":"the predicted user message"}.',
|
|
6
|
+
prompt_version: "ccdx-native-prediction-v1",
|
|
7
|
+
prediction_reasoning_effort: "low",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
function isObject(value) {
|
|
11
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseBody(body) {
|
|
15
|
+
if (typeof body !== "string" && !Buffer.isBuffer(body)) return null;
|
|
16
|
+
try { return JSON.parse(body.toString()); } catch { return null; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function encodeBody(value, original) {
|
|
20
|
+
const text = JSON.stringify(value);
|
|
21
|
+
return Buffer.isBuffer(original) ? Buffer.from(text) : text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function classifyNativePredictionPath({ hostname, pathname } = {}) {
|
|
25
|
+
const host = typeof hostname === "string" ? hostname.toLowerCase() : "";
|
|
26
|
+
if (host === "chatgpt.com") {
|
|
27
|
+
if (pathname === "/backend-api/wham/statsig/bootstrap") return "bootstrap";
|
|
28
|
+
if (pathname === "/backend-api/wham/predictions/config") return "config";
|
|
29
|
+
}
|
|
30
|
+
if (host === "ab.chatgpt.com" && pathname === "/v1/initialize") return "refresh";
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function enablePredictionGate(payload) {
|
|
35
|
+
if (!isObject(payload) || (payload.response_mode === "live_overlay" ? payload.has_updates === false : payload.has_updates !== true) ||
|
|
36
|
+
(payload.is_delta !== undefined && payload.is_delta !== false) ||
|
|
37
|
+
(payload.response_mode !== undefined && !["full", "live_overlay"].includes(payload.response_mode)) ||
|
|
38
|
+
!isObject(payload.feature_gates)) return false;
|
|
39
|
+
// Statsig V1 first resolves the literal name, then its djb2 lookup key.
|
|
40
|
+
let hash = 0;
|
|
41
|
+
for (const char of NATIVE_PREDICTION_GATE) hash = ((hash << 5) - hash + char.charCodeAt(0)) | 0;
|
|
42
|
+
const hashedKey = String(hash >>> 0);
|
|
43
|
+
const key = Object.hasOwn(payload.feature_gates, NATIVE_PREDICTION_GATE) ? NATIVE_PREDICTION_GATE
|
|
44
|
+
: Object.hasOwn(payload.feature_gates, hashedKey) ? hashedKey : NATIVE_PREDICTION_GATE;
|
|
45
|
+
const gate = payload.feature_gates[key];
|
|
46
|
+
if (gate !== undefined && (!isObject(gate) || typeof gate.value !== "boolean" || gate.value === true)) return false;
|
|
47
|
+
if (payload.response_mode === "live_overlay" && !Array.isArray(payload.live_entity_names?.feature_gates)) return false;
|
|
48
|
+
if (gate) gate.value = true;
|
|
49
|
+
else payload.feature_gates[key] = { name: key, value: true, rule_id: "ccdx-local-compatibility", secondary_exposures: [] };
|
|
50
|
+
if (payload.response_mode === "live_overlay" && !payload.live_entity_names.feature_gates.includes(key)) {
|
|
51
|
+
payload.live_entity_names.feature_gates.push(key);
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// No-op results retain the original bytes; callers own HTTP headers and decoding.
|
|
57
|
+
export function applyNativePredictionOverlay({ hostname, pathname, statusCode, body, enabled = false } = {}) {
|
|
58
|
+
const kind = classifyNativePredictionPath({ hostname, pathname });
|
|
59
|
+
const result = { body, statusCode, changed: false, official: false, kind };
|
|
60
|
+
if (!enabled || !kind) return result;
|
|
61
|
+
if (kind === "config" && statusCode === 404) {
|
|
62
|
+
return { ...result, statusCode: 200, body: encodeBody(LOCAL_PREDICTION_CONFIG, body), changed: true };
|
|
63
|
+
}
|
|
64
|
+
if (statusCode !== 200) return result;
|
|
65
|
+
const parsed = parseBody(body);
|
|
66
|
+
if (!isObject(parsed)) return result;
|
|
67
|
+
if (kind === "config") {
|
|
68
|
+
if (parsed.is_enabled === true) return { ...result, official: true };
|
|
69
|
+
if (parsed.is_enabled !== false || typeof parsed.prompt !== "string" || !parsed.prompt.trim()) return result;
|
|
70
|
+
parsed.is_enabled = true;
|
|
71
|
+
} else if (kind === "bootstrap") {
|
|
72
|
+
const stringPayload = typeof parsed.statsigPayload === "string";
|
|
73
|
+
const payload = stringPayload ? parseBody(parsed.statsigPayload) : parsed.statsigPayload;
|
|
74
|
+
if (!enablePredictionGate(payload)) return result;
|
|
75
|
+
if (stringPayload) parsed.statsigPayload = JSON.stringify(payload);
|
|
76
|
+
} else if (!enablePredictionGate(parsed)) return result;
|
|
77
|
+
return { ...result, body: encodeBody(parsed, body), changed: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Overlaying a delta would invalidate its checksum. Ask for a full response instead.
|
|
81
|
+
export function normalizeStatsigRequest({ hostname, pathname, body, enabled = false } = {}) {
|
|
82
|
+
const result = { body, changed: false };
|
|
83
|
+
if (!enabled || classifyNativePredictionPath({ hostname, pathname }) !== "refresh") return result;
|
|
84
|
+
const parsed = parseBody(body);
|
|
85
|
+
if (!isObject(parsed) || !isObject(parsed.user) ||
|
|
86
|
+
(parsed.responseMode !== undefined && !["full", "live_overlay"].includes(parsed.responseMode))) return result;
|
|
87
|
+
if (parsed.deltasResponseRequested === false && parsed.full_checksum === null && parsed.sinceTime === 0) return result;
|
|
88
|
+
parsed.deltasResponseRequested = false;
|
|
89
|
+
parsed.full_checksum = null;
|
|
90
|
+
parsed.sinceTime = 0;
|
|
91
|
+
return { body: encodeBody(parsed, body), changed: true };
|
|
92
|
+
}
|