codex-copilot-dx 0.4.22 → 0.4.25
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 +17 -1
- package/bin/cli.mjs +54 -14
- package/package.json +1 -1
- package/src/adapter.mjs +468 -82
- package/src/auth.mjs +148 -7
- package/src/copilot.mjs +9 -4
- package/src/doctor.mjs +82 -7
- package/src/launcher.mjs +54 -9
- package/src/model-cache.mjs +45 -0
- package/src/running-adapter.mjs +22 -0
package/README.md
CHANGED
|
@@ -38,6 +38,8 @@ Claude Code picks up the new `ANTHROPIC_BASE_URL` on its next launch.
|
|
|
38
38
|
|
|
39
39
|
If an existing `codex-copilot-dx` adapter is already running on the configured host and port, a second launch reuses it instead of starting another proxy. The second launch still refreshes Codex and Claude Code config, then exits.
|
|
40
40
|
|
|
41
|
+
The running adapter reports its package and protocol versions. After upgrading `codex-copilot-dx`, stop the old process before starting the new version; the new CLI refuses to silently reuse an incompatible adapter.
|
|
42
|
+
|
|
41
43
|
Do not set Claude Code by manually exporting `ANTHROPIC_BASE_URL` or `ANTHROPIC_AUTH_TOKEN` in your shell. Let `codex-copilot-dx` write the local config files instead. If you previously exported those variables, remove them from shell startup files and restart the terminal before launching Claude Code.
|
|
42
44
|
|
|
43
45
|
### Diagnostics
|
|
@@ -50,7 +52,15 @@ codex-copilot-dx doctor
|
|
|
50
52
|
|
|
51
53
|
The doctor checks the GitHub token, Codex config, Claude Code settings, Claude App gateway profile, and whether the local adapter port is listening.
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
For a read-only live check of the saved GitHub token, Copilot entitlement, and models endpoint, run:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
codex-copilot-dx doctor --online
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The online doctor never starts device flow, scans for replacement tokens, or changes the saved token.
|
|
62
|
+
|
|
63
|
+
When the saved token is missing, `codex-copilot-dx` first looks for compatible local Copilot GitHub tokens, validates them with GitHub and Copilot, and imports a valid one before starting device login. It checks explicit token sources (`CCDX_GITHUB_TOKEN`, `CCDX_GITHUB_TOKEN_PATH`, `CCDX_GITHUB_TOKEN_PATHS`) plus common local `auth.json` layouts under application config directories. It does not rely on a specific app name. Generic discovery refuses to choose silently when valid tokens for multiple GitHub accounts are found. After an account is selected, automatic `401`/`403` recovery accepts only the same GitHub account. Explicit token variables remain the intentional way to switch accounts.
|
|
54
64
|
|
|
55
65
|
If Copilot token refresh still fails with `401` or `403`, the saved GitHub token may be expired, revoked, or missing Copilot access. Delete the saved token and start the tool again to trigger GitHub device login:
|
|
56
66
|
|
|
@@ -104,10 +114,14 @@ Environment variables:
|
|
|
104
114
|
| `CCDX_GITHUB_TOKEN` | unset | Explicit GitHub Copilot OAuth token to validate and import before device login |
|
|
105
115
|
| `CCDX_GITHUB_TOKEN_PATH` | unset | Explicit file containing a GitHub Copilot OAuth token to validate and import before device login |
|
|
106
116
|
| `CCDX_GITHUB_TOKEN_PATHS` | unset | Multiple token files separated by the platform path delimiter (`:` on macOS/Linux, `;` on Windows) |
|
|
117
|
+
| `CCDX_GITHUB_LOGIN` | saved account | Require automatic discovery and recovery to use this GitHub login |
|
|
107
118
|
| `CCDX_DISABLE_TOKEN_DISCOVERY` | unset | Set to `1` to skip local token discovery and go straight to the saved token or device flow |
|
|
108
119
|
| `CCDX_TOKEN_LOCK_TIMEOUT_MS` | `600000` | Maximum time to wait for another local process to finish GitHub token login/import |
|
|
109
120
|
| `CCDX_TOKEN_LOCK_STALE_MS` | `900000` | Age after which a stale GitHub token lock file can be removed |
|
|
110
121
|
| `CCDX_EXISTING_ADAPTER_TIMEOUT_MS` | `500` | Timeout for detecting an already-running local adapter during startup |
|
|
122
|
+
| `CCDX_MODEL_REFRESH_INTERVAL_MS` | `1800000` | Interval for refreshing Copilot model metadata; successful lists are cached locally as last-known-good data |
|
|
123
|
+
| `CCDX_RESPONSE_HISTORY_MAX_BYTES` | `67108864` | Total in-memory byte budget for locally expanded Responses history |
|
|
124
|
+
| `CCDX_RESPONSE_HISTORY_MAX_ENTRIES` | `4096` | Maximum stored incremental Responses history nodes |
|
|
111
125
|
| `CCDX_USAGE_PATH` | `~/.local/share/codex-copilot-dx/usage.jsonl` | Local JSONL token usage log |
|
|
112
126
|
| `CCDX_DISABLE_USAGE` | unset | Set to `1` to disable usage logging |
|
|
113
127
|
|
|
@@ -129,6 +143,8 @@ Long computer-use sessions can accumulate screenshots inside the conversation hi
|
|
|
129
143
|
|
|
130
144
|
The adapter automatically downsamples embedded screenshots to long-edge <= 2048 px and re-encodes them as WebP before forwarding `/v1/responses`.
|
|
131
145
|
|
|
146
|
+
Newer ChatGPT/Codex clients can advertise an `image_gen` namespace that already exists upstream. The adapter removes that exact conflicting client tool before forwarding and retries once only when Copilot explicitly reports an image namespace collision. Image inputs and screenshot optimization remain enabled.
|
|
147
|
+
|
|
132
148
|
## License
|
|
133
149
|
|
|
134
150
|
MIT
|
package/bin/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { ensureCodexConfig } from "../src/config.mjs";
|
|
|
5
5
|
import { ensureClaudeConfig } from "../src/claude-config.mjs";
|
|
6
6
|
import { applyClaudeDesktopConfig, formatClaudeDesktopApplyResult, generatedClaudeDesktopApiKey } from "../src/claude-desktop-config.mjs";
|
|
7
7
|
import { startAdapter } from "../src/adapter.mjs";
|
|
8
|
-
import { listModels, refreshVSCodeVersion } from "../src/copilot.mjs";
|
|
8
|
+
import { cacheModelEndpoints, listModels, refreshVSCodeVersion } from "../src/copilot.mjs";
|
|
9
9
|
import { claudeDesktopModelDefsFromCopilotModels, claudeDesktopModelIds, gptModelIdsFromCopilotModels, parseModelAliasEnv } from "../src/models.mjs";
|
|
10
10
|
import { status } from "../src/status.mjs";
|
|
11
11
|
import { configureLogging } from "../src/log.mjs";
|
|
@@ -14,6 +14,7 @@ import { checkForUpdate, localPackageVersion } from "../src/version.mjs";
|
|
|
14
14
|
import { runDoctor } from "../src/doctor.mjs";
|
|
15
15
|
import { checkRunningAdapter } from "../src/running-adapter.mjs";
|
|
16
16
|
import { assertSafeAdapterHost, isLoopbackHost } from "../src/security.mjs";
|
|
17
|
+
import { loadModelCache, saveModelCache } from "../src/model-cache.mjs";
|
|
17
18
|
|
|
18
19
|
const ADAPTER_PORT = parseInt(process.env.ADAPTER_PORT || "2026");
|
|
19
20
|
const ADAPTER_HOST = process.env.ADAPTER_HOST || "127.0.0.1";
|
|
@@ -23,17 +24,38 @@ const LOCAL_VERSION = localPackageVersion();
|
|
|
23
24
|
const command = process.argv[2];
|
|
24
25
|
const CONFIGURE_CLAUDE_DESKTOP = command === "--configure-claude-desktop" || process.env.CCDX_CONFIGURE_CLAUDE_DESKTOP === "1";
|
|
25
26
|
const LOGGING = configureLogging();
|
|
27
|
+
const MODEL_REGISTRY = { modelDefs: undefined, source: "built-in" };
|
|
26
28
|
|
|
27
29
|
if (LOGGING.filePath) {
|
|
28
30
|
console.log(status("info", `Debug log: ${LOGGING.filePath}`));
|
|
29
31
|
if (LOGGING.level === "debug") console.log(status("debug", "Debug logging enabled"));
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
function applyModelsToRegistry(models, source, { updateClaudeModels = true } = {}) {
|
|
35
|
+
const modelDefs = updateClaudeModels
|
|
36
|
+
? claudeDesktopModelDefsFromCopilotModels(models)
|
|
37
|
+
: MODEL_REGISTRY.modelDefs;
|
|
38
|
+
if (updateClaudeModels && !modelDefs.length) throw new Error("Copilot models response contained no Claude models");
|
|
39
|
+
cacheModelEndpoints(models);
|
|
40
|
+
if (updateClaudeModels) MODEL_REGISTRY.modelDefs = modelDefs;
|
|
41
|
+
MODEL_REGISTRY.source = source;
|
|
42
|
+
return modelDefs;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function loadCachedModelRegistry() {
|
|
46
|
+
const cached = loadModelCache();
|
|
47
|
+
if (!cached) return false;
|
|
48
|
+
try {
|
|
49
|
+
const customAliases = parseModelAliasEnv(process.env.CCDX_CLAUDE_MODEL_ALIASES).length > 0;
|
|
50
|
+
applyModelsToRegistry(cached, "cache", { updateClaudeModels: !customAliases });
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
36
54
|
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function refreshClaudeDesktopModelDefs() {
|
|
58
|
+
const customAliases = parseModelAliasEnv(process.env.CCDX_CLAUDE_MODEL_ALIASES).length > 0;
|
|
37
59
|
|
|
38
60
|
const ctrl = new AbortController();
|
|
39
61
|
const timer = setTimeout(() => ctrl.abort(), MODEL_REFRESH_TIMEOUT_MS);
|
|
@@ -50,13 +72,22 @@ async function refreshClaudeDesktopModelDefs() {
|
|
|
50
72
|
console.log(status("warn", "Copilot models response contained no GPT models"));
|
|
51
73
|
}
|
|
52
74
|
|
|
53
|
-
const modelDefs =
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
const modelDefs = applyModelsToRegistry(models, "live", { updateClaudeModels: !customAliases });
|
|
76
|
+
try {
|
|
77
|
+
saveModelCache(models);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
console.log(status("warn", `Could not persist the Copilot model cache (${e.message})`));
|
|
80
|
+
}
|
|
81
|
+
if (customAliases) {
|
|
82
|
+
console.log(status("info", "Using CCDX_CLAUDE_MODEL_ALIASES for Claude models"));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(status("ok", `Refreshed Claude models from GitHub Copilot: ${modelDefs.map((model) => model.id).join(", ")}`));
|
|
85
|
+
}
|
|
56
86
|
return modelDefs;
|
|
57
87
|
} catch (e) {
|
|
58
|
-
|
|
59
|
-
|
|
88
|
+
const fallback = MODEL_REGISTRY.modelDefs?.length ? `${MODEL_REGISTRY.source} model list` : "built-in model list";
|
|
89
|
+
console.log(status("warn", `Could not refresh Claude models; using ${fallback} (${e.message})`));
|
|
90
|
+
return MODEL_REGISTRY.modelDefs;
|
|
60
91
|
} finally {
|
|
61
92
|
clearTimeout(timer);
|
|
62
93
|
}
|
|
@@ -72,6 +103,10 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
72
103
|
host: ADAPTER_HOST,
|
|
73
104
|
timeoutMs: EXISTING_ADAPTER_TIMEOUT_MS,
|
|
74
105
|
});
|
|
106
|
+
if (running.incompatible) {
|
|
107
|
+
const found = running.data?.version || "unknown";
|
|
108
|
+
throw new Error(`Adapter ${found} is already running at ${running.baseUrl}, but this CLI is ${LOCAL_VERSION}. Stop the existing process and run codex-copilot-dx again.`);
|
|
109
|
+
}
|
|
75
110
|
if (!running.ok) return false;
|
|
76
111
|
|
|
77
112
|
console.log(status("ok", `Using existing adapter at ${running.baseUrl}`));
|
|
@@ -96,7 +131,7 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
96
131
|
console.log(status("ok", "Claude App support available with --configure-claude-desktop"));
|
|
97
132
|
}
|
|
98
133
|
|
|
99
|
-
openCodex();
|
|
134
|
+
await openCodex();
|
|
100
135
|
console.log(`
|
|
101
136
|
${status("ok", "Ready, using the existing codex-copilot-dx adapter")}
|
|
102
137
|
|
|
@@ -116,7 +151,11 @@ if (command === "usage") {
|
|
|
116
151
|
}
|
|
117
152
|
|
|
118
153
|
if (command === "doctor" || command === "--doctor") {
|
|
119
|
-
await runDoctor({
|
|
154
|
+
await runDoctor({
|
|
155
|
+
port: ADAPTER_PORT,
|
|
156
|
+
host: ADAPTER_HOST,
|
|
157
|
+
online: process.argv.slice(3).includes("--online"),
|
|
158
|
+
});
|
|
120
159
|
process.exit(0);
|
|
121
160
|
}
|
|
122
161
|
|
|
@@ -149,6 +188,7 @@ try {
|
|
|
149
188
|
|
|
150
189
|
// Refresh the VS Code version in the background; fallback is non-blocking.
|
|
151
190
|
refreshVSCodeVersion();
|
|
191
|
+
loadCachedModelRegistry();
|
|
152
192
|
const claudeDesktopModelDefs = await refreshClaudeDesktopModelDefs();
|
|
153
193
|
|
|
154
194
|
if (!isLoopbackHost(ADAPTER_HOST)) {
|
|
@@ -160,7 +200,7 @@ try {
|
|
|
160
200
|
: currentClaudeDesktopApiKey();
|
|
161
201
|
|
|
162
202
|
// Start the in-process adapter.
|
|
163
|
-
await startAdapter(ADAPTER_PORT, ADAPTER_HOST, { claudeDesktopApiKey, claudeDesktopModelDefs });
|
|
203
|
+
await startAdapter(ADAPTER_PORT, ADAPTER_HOST, { claudeDesktopApiKey, claudeDesktopModelDefs, modelRegistry: MODEL_REGISTRY });
|
|
164
204
|
|
|
165
205
|
// Point Codex and Claude Code at the adapter.
|
|
166
206
|
ensureCodexConfig(ADAPTER_PORT);
|
|
@@ -179,7 +219,7 @@ try {
|
|
|
179
219
|
}
|
|
180
220
|
|
|
181
221
|
// Launch Codex when available.
|
|
182
|
-
openCodex();
|
|
222
|
+
await openCodex();
|
|
183
223
|
|
|
184
224
|
// Periodically refresh the model list + endpoint cache so a long-running
|
|
185
225
|
// adapter picks up newly released models without a restart.
|