codex-copilot-dx 0.4.23 → 0.4.26
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 +35 -3
- package/bin/cli.mjs +137 -44
- package/package.json +9 -2
- package/src/adapter.mjs +400 -282
- package/src/atomic-file.mjs +63 -0
- package/src/auth.mjs +148 -7
- package/src/claude-config.mjs +7 -7
- package/src/claude-desktop-config.mjs +3 -4
- package/src/cli-options.mjs +69 -0
- package/src/config.mjs +10 -6
- package/src/copilot.mjs +29 -165
- package/src/doctor.mjs +82 -7
- package/src/file-rotation.mjs +30 -0
- package/src/http-transport.mjs +182 -0
- package/src/image-optimization.mjs +151 -0
- package/src/log.mjs +9 -1
- package/src/model-cache.mjs +45 -0
- package/src/response-history.mjs +195 -0
- package/src/running-adapter.mjs +22 -0
- package/src/shutdown.mjs +29 -0
- package/src/startup.mjs +19 -0
- package/src/stream.mjs +2 -1
- package/src/usage.mjs +57 -14
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Supports both HTTP SSE streaming and non-streaming.
|
|
|
15
15
|
## Prerequisites
|
|
16
16
|
|
|
17
17
|
- GitHub Copilot subscription (Individual, Business, or Enterprise)
|
|
18
|
-
- Node.js 18+
|
|
18
|
+
- Node.js 18.17+
|
|
19
19
|
- [Codex Desktop](https://openai.com/codex), [Claude Code](https://claude.com/claude-code), and/or Claude App installed
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
@@ -28,7 +28,7 @@ npx codex-copilot-dx@latest
|
|
|
28
28
|
|
|
29
29
|
On first run, it will:
|
|
30
30
|
1. Authenticate with GitHub via device flow (if needed), after first trying compatible local Copilot token sources
|
|
31
|
-
2. Print the local package version and
|
|
31
|
+
2. Print the local package version and check for a newer npm release in the background
|
|
32
32
|
3. Start the adapter on loopback (`127.0.0.1:2026`)
|
|
33
33
|
4. Configure Codex (`~/.codex/config.toml`) to use the adapter, including stale shell env base URLs if present
|
|
34
34
|
5. Configure Claude Code (`~/.claude/settings.json`) to use the adapter; it creates the file when missing, otherwise backs up `settings.json.bak` before updating the local API env keys
|
|
@@ -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
|
|
@@ -48,9 +50,19 @@ Run a read-only config check without starting the adapter or changing files:
|
|
|
48
50
|
codex-copilot-dx doctor
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
The command exits with status `1` when it finds an invalid configuration and `0` when checks contain only OK or warning results.
|
|
54
|
+
|
|
51
55
|
The doctor checks the GitHub token, Codex config, Claude Code settings, Claude App gateway profile, and whether the local adapter port is listening.
|
|
52
56
|
|
|
53
|
-
|
|
57
|
+
For a read-only live check of the saved GitHub token, Copilot entitlement, and models endpoint, run:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
codex-copilot-dx doctor --online
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The online doctor never starts device flow, scans for replacement tokens, or changes the saved token.
|
|
64
|
+
|
|
65
|
+
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
66
|
|
|
55
67
|
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
68
|
|
|
@@ -89,10 +101,13 @@ Environment variables:
|
|
|
89
101
|
| `CCDX_MAX_BODY_BYTES` | `134217728` | Maximum compressed/raw request body size |
|
|
90
102
|
| `CCDX_MAX_DECODED_BODY_BYTES` | `268435456` | Maximum decoded request body size after decompression |
|
|
91
103
|
| `CCDX_UPSTREAM_TIMEOUT_MS` | `120000` | Timeout for non-streaming upstream Copilot requests |
|
|
104
|
+
| `CCDX_STREAM_HANDSHAKE_TIMEOUT_MS` | `120000` | Timeout while waiting for upstream streaming response headers |
|
|
105
|
+
| `CCDX_STREAM_IDLE_TIMEOUT_MS` | `120000` | Maximum idle time between upstream streaming body chunks |
|
|
92
106
|
| `CCDX_UPSTREAM_RETRIES` | `2` | Retries for transient Copilot upstream network errors; capped at `5` |
|
|
93
107
|
| `CCDX_UPSTREAM_RETRY_DELAY_MS` | `300` | Initial upstream retry backoff in milliseconds; capped at `5000` |
|
|
94
108
|
| `CCDX_LOG_PATH` | unset | Mirror terminal logs to a file; set to `1` for `~/.local/share/codex-copilot-dx/debug.log` |
|
|
95
109
|
| `CCDX_LOG_LEVEL` | `info` | Set to `debug` to include upstream request attempts, status codes, retry causes, and timings |
|
|
110
|
+
| `CCDX_LOG_MAX_BYTES` | `16777216` | Rotate the debug log at this size, retaining one `.1` backup; set to `0` to disable rotation |
|
|
96
111
|
| `CCDX_IMG_MAX_DIM` | `2048` | Max long edge in pixels for image downscaling |
|
|
97
112
|
| `CCDX_IMG_QUALITY` | `85` | WebP quality used when re-encoding images |
|
|
98
113
|
| `CCDX_IMG_MIN_BYTES` | `100000` | Images smaller than this are left untouched |
|
|
@@ -104,12 +119,18 @@ Environment variables:
|
|
|
104
119
|
| `CCDX_GITHUB_TOKEN` | unset | Explicit GitHub Copilot OAuth token to validate and import before device login |
|
|
105
120
|
| `CCDX_GITHUB_TOKEN_PATH` | unset | Explicit file containing a GitHub Copilot OAuth token to validate and import before device login |
|
|
106
121
|
| `CCDX_GITHUB_TOKEN_PATHS` | unset | Multiple token files separated by the platform path delimiter (`:` on macOS/Linux, `;` on Windows) |
|
|
122
|
+
| `CCDX_GITHUB_LOGIN` | saved account | Require automatic discovery and recovery to use this GitHub login |
|
|
107
123
|
| `CCDX_DISABLE_TOKEN_DISCOVERY` | unset | Set to `1` to skip local token discovery and go straight to the saved token or device flow |
|
|
108
124
|
| `CCDX_TOKEN_LOCK_TIMEOUT_MS` | `600000` | Maximum time to wait for another local process to finish GitHub token login/import |
|
|
109
125
|
| `CCDX_TOKEN_LOCK_STALE_MS` | `900000` | Age after which a stale GitHub token lock file can be removed |
|
|
110
126
|
| `CCDX_EXISTING_ADAPTER_TIMEOUT_MS` | `500` | Timeout for detecting an already-running local adapter during startup |
|
|
127
|
+
| `CCDX_MODEL_REFRESH_INTERVAL_MS` | `1800000` | Interval for refreshing Copilot model metadata; successful lists are cached locally as last-known-good data |
|
|
128
|
+
| `CCDX_RESPONSE_HISTORY_MAX_BYTES` | `67108864` | Total in-memory byte budget for locally expanded Responses history |
|
|
129
|
+
| `CCDX_RESPONSE_HISTORY_MAX_ENTRIES` | `4096` | Maximum stored incremental Responses history nodes |
|
|
111
130
|
| `CCDX_USAGE_PATH` | `~/.local/share/codex-copilot-dx/usage.jsonl` | Local JSONL token usage log |
|
|
131
|
+
| `CCDX_USAGE_MAX_BYTES` | `33554432` | Rotate the usage log at this size, retaining one `.1` backup; set to `0` to disable rotation |
|
|
112
132
|
| `CCDX_DISABLE_USAGE` | unset | Set to `1` to disable usage logging |
|
|
133
|
+
| `CCDX_SHUTDOWN_TIMEOUT_MS` | `5000` | Time to drain active HTTP connections before forcing shutdown |
|
|
113
134
|
|
|
114
135
|
### Usage logging
|
|
115
136
|
|
|
@@ -129,6 +150,17 @@ Long computer-use sessions can accumulate screenshots inside the conversation hi
|
|
|
129
150
|
|
|
130
151
|
The adapter automatically downsamples embedded screenshots to long-edge <= 2048 px and re-encodes them as WebP before forwarding `/v1/responses`.
|
|
131
152
|
|
|
153
|
+
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.
|
|
154
|
+
|
|
132
155
|
## License
|
|
133
156
|
|
|
134
157
|
MIT
|
|
158
|
+
|
|
159
|
+
## Development
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm ci
|
|
163
|
+
npm run verify
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`npm test` runs the unit and handler-level suite. `npm run test:smoke` starts a real local HTTP adapter with fully injected offline upstreams. `npm run pack:check` verifies the npm tarball contents without publishing. The CI workflow runs all three checks on supported Node.js release lines.
|
package/bin/cli.mjs
CHANGED
|
@@ -5,35 +5,93 @@ 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";
|
|
12
|
-
import { printUsageSummary } from "../src/usage.mjs";
|
|
12
|
+
import { flushUsageWrites, printUsageSummary } from "../src/usage.mjs";
|
|
13
13
|
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";
|
|
18
|
+
import { initializeModelRegistry, runInBackground } from "../src/startup.mjs";
|
|
19
|
+
import { cliHelp, parseCliArgs, parseRuntimeOptions } from "../src/cli-options.mjs";
|
|
20
|
+
import { closeHttpServer } from "../src/shutdown.mjs";
|
|
17
21
|
|
|
18
|
-
const ADAPTER_PORT = parseInt(process.env.ADAPTER_PORT || "2026");
|
|
19
|
-
const ADAPTER_HOST = process.env.ADAPTER_HOST || "127.0.0.1";
|
|
20
|
-
const MODEL_REFRESH_TIMEOUT_MS = parseInt(process.env.CCDX_MODEL_REFRESH_TIMEOUT_MS || "5000", 10);
|
|
21
|
-
const EXISTING_ADAPTER_TIMEOUT_MS = parseInt(process.env.CCDX_EXISTING_ADAPTER_TIMEOUT_MS || "500", 10);
|
|
22
22
|
const LOCAL_VERSION = localPackageVersion();
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
let CLI;
|
|
25
|
+
try {
|
|
26
|
+
CLI = parseCliArgs(process.argv.slice(2));
|
|
27
|
+
} catch (e) {
|
|
28
|
+
console.error(e.message);
|
|
29
|
+
console.error("Run codex-copilot-dx --help for usage.");
|
|
30
|
+
process.exit(2);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (CLI.command === "help") {
|
|
34
|
+
console.log(cliHelp());
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
if (CLI.command === "version") {
|
|
38
|
+
console.log(`codex-copilot-dx v${LOCAL_VERSION}`);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
if (CLI.command === "usage") {
|
|
42
|
+
await printUsageSummary();
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let RUNTIME;
|
|
47
|
+
try {
|
|
48
|
+
RUNTIME = parseRuntimeOptions(process.env);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(e.message);
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const ADAPTER_PORT = RUNTIME.adapterPort;
|
|
55
|
+
const ADAPTER_HOST = RUNTIME.adapterHost;
|
|
56
|
+
const MODEL_REFRESH_TIMEOUT_MS = RUNTIME.modelRefreshTimeoutMs;
|
|
57
|
+
const EXISTING_ADAPTER_TIMEOUT_MS = RUNTIME.existingAdapterTimeoutMs;
|
|
58
|
+
const CONFIGURE_CLAUDE_DESKTOP = CLI.configureClaudeDesktop || process.env.CCDX_CONFIGURE_CLAUDE_DESKTOP === "1";
|
|
25
59
|
const LOGGING = configureLogging();
|
|
60
|
+
const MODEL_REGISTRY = { modelDefs: undefined, source: "built-in" };
|
|
61
|
+
let activeServer = null;
|
|
62
|
+
let modelRefreshTimer = null;
|
|
63
|
+
let shuttingDown = false;
|
|
26
64
|
|
|
27
65
|
if (LOGGING.filePath) {
|
|
28
66
|
console.log(status("info", `Debug log: ${LOGGING.filePath}`));
|
|
29
67
|
if (LOGGING.level === "debug") console.log(status("debug", "Debug logging enabled"));
|
|
30
68
|
}
|
|
31
69
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
function applyModelsToRegistry(models, source, { updateClaudeModels = true } = {}) {
|
|
71
|
+
const modelDefs = updateClaudeModels
|
|
72
|
+
? claudeDesktopModelDefsFromCopilotModels(models)
|
|
73
|
+
: MODEL_REGISTRY.modelDefs;
|
|
74
|
+
if (updateClaudeModels && !modelDefs.length) throw new Error("Copilot models response contained no Claude models");
|
|
75
|
+
cacheModelEndpoints(models);
|
|
76
|
+
if (updateClaudeModels) MODEL_REGISTRY.modelDefs = modelDefs;
|
|
77
|
+
MODEL_REGISTRY.source = source;
|
|
78
|
+
return modelDefs;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function loadCachedModelRegistry() {
|
|
82
|
+
const cached = loadModelCache();
|
|
83
|
+
if (!cached) return false;
|
|
84
|
+
try {
|
|
85
|
+
const customAliases = parseModelAliasEnv(process.env.CCDX_CLAUDE_MODEL_ALIASES).length > 0;
|
|
86
|
+
applyModelsToRegistry(cached, "cache", { updateClaudeModels: !customAliases });
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
36
90
|
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function refreshClaudeDesktopModelDefs() {
|
|
94
|
+
const customAliases = parseModelAliasEnv(process.env.CCDX_CLAUDE_MODEL_ALIASES).length > 0;
|
|
37
95
|
|
|
38
96
|
const ctrl = new AbortController();
|
|
39
97
|
const timer = setTimeout(() => ctrl.abort(), MODEL_REFRESH_TIMEOUT_MS);
|
|
@@ -50,13 +108,22 @@ async function refreshClaudeDesktopModelDefs() {
|
|
|
50
108
|
console.log(status("warn", "Copilot models response contained no GPT models"));
|
|
51
109
|
}
|
|
52
110
|
|
|
53
|
-
const modelDefs =
|
|
54
|
-
|
|
55
|
-
|
|
111
|
+
const modelDefs = applyModelsToRegistry(models, "live", { updateClaudeModels: !customAliases });
|
|
112
|
+
try {
|
|
113
|
+
saveModelCache(models);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.log(status("warn", `Could not persist the Copilot model cache (${e.message})`));
|
|
116
|
+
}
|
|
117
|
+
if (customAliases) {
|
|
118
|
+
console.log(status("info", "Using CCDX_CLAUDE_MODEL_ALIASES for Claude models"));
|
|
119
|
+
} else {
|
|
120
|
+
console.log(status("ok", `Refreshed Claude models from GitHub Copilot: ${modelDefs.map((model) => model.id).join(", ")}`));
|
|
121
|
+
}
|
|
56
122
|
return modelDefs;
|
|
57
123
|
} catch (e) {
|
|
58
|
-
|
|
59
|
-
|
|
124
|
+
const fallback = MODEL_REGISTRY.modelDefs?.length ? `${MODEL_REGISTRY.source} model list` : "built-in model list";
|
|
125
|
+
console.log(status("warn", `Could not refresh Claude models; using ${fallback} (${e.message})`));
|
|
126
|
+
return MODEL_REGISTRY.modelDefs;
|
|
60
127
|
} finally {
|
|
61
128
|
clearTimeout(timer);
|
|
62
129
|
}
|
|
@@ -72,6 +139,10 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
72
139
|
host: ADAPTER_HOST,
|
|
73
140
|
timeoutMs: EXISTING_ADAPTER_TIMEOUT_MS,
|
|
74
141
|
});
|
|
142
|
+
if (running.incompatible) {
|
|
143
|
+
const found = running.data?.version || "unknown";
|
|
144
|
+
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.`);
|
|
145
|
+
}
|
|
75
146
|
if (!running.ok) return false;
|
|
76
147
|
|
|
77
148
|
console.log(status("ok", `Using existing adapter at ${running.baseUrl}`));
|
|
@@ -105,19 +176,13 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
105
176
|
return true;
|
|
106
177
|
}
|
|
107
178
|
|
|
108
|
-
if (command === "
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
process.exit(0);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (command === "doctor" || command === "--doctor") {
|
|
119
|
-
await runDoctor({ port: ADAPTER_PORT, host: ADAPTER_HOST });
|
|
120
|
-
process.exit(0);
|
|
179
|
+
if (CLI.command === "doctor") {
|
|
180
|
+
const checks = await runDoctor({
|
|
181
|
+
port: ADAPTER_PORT,
|
|
182
|
+
host: ADAPTER_HOST,
|
|
183
|
+
online: CLI.online,
|
|
184
|
+
});
|
|
185
|
+
process.exit(checks.some((check) => check.kind === "err") ? 1 : 0);
|
|
121
186
|
}
|
|
122
187
|
|
|
123
188
|
console.log(`
|
|
@@ -136,20 +201,22 @@ async function printUpdateNotice() {
|
|
|
136
201
|
}
|
|
137
202
|
}
|
|
138
203
|
|
|
139
|
-
// Await the update check up front so the notice is shown even on the
|
|
140
|
-
// "reuse existing adapter" path, which exits early below.
|
|
141
|
-
await printUpdateNotice();
|
|
142
|
-
|
|
143
204
|
try {
|
|
144
205
|
assertSafeAdapterHost(ADAPTER_HOST, process.env);
|
|
206
|
+
void runInBackground(printUpdateNotice);
|
|
145
207
|
if (await reuseRunningAdapterIfAvailable()) process.exit(0);
|
|
146
208
|
|
|
147
209
|
// Ensure GitHub login, using device flow if no token exists.
|
|
148
210
|
await ensureAuth();
|
|
149
211
|
|
|
150
212
|
// Refresh the VS Code version in the background; fallback is non-blocking.
|
|
151
|
-
refreshVSCodeVersion();
|
|
152
|
-
const
|
|
213
|
+
void refreshVSCodeVersion();
|
|
214
|
+
const modelInitialization = await initializeModelRegistry({
|
|
215
|
+
loadCached: loadCachedModelRegistry,
|
|
216
|
+
currentModelDefs: () => MODEL_REGISTRY.modelDefs,
|
|
217
|
+
refresh: refreshClaudeDesktopModelDefs,
|
|
218
|
+
});
|
|
219
|
+
const claudeDesktopModelDefs = modelInitialization.modelDefs;
|
|
153
220
|
|
|
154
221
|
if (!isLoopbackHost(ADAPTER_HOST)) {
|
|
155
222
|
console.log(status("warn", `ADAPTER_HOST=${ADAPTER_HOST} exposes the adapter beyond loopback because CCDX_ALLOW_LAN=1 is set. Use only on trusted networks.`));
|
|
@@ -160,7 +227,14 @@ try {
|
|
|
160
227
|
: currentClaudeDesktopApiKey();
|
|
161
228
|
|
|
162
229
|
// Start the in-process adapter.
|
|
163
|
-
await startAdapter(ADAPTER_PORT, ADAPTER_HOST, {
|
|
230
|
+
activeServer = await startAdapter(ADAPTER_PORT, ADAPTER_HOST, {
|
|
231
|
+
claudeDesktopApiKey,
|
|
232
|
+
claudeDesktopModelDefs,
|
|
233
|
+
modelRegistry: MODEL_REGISTRY,
|
|
234
|
+
upstreamTimeoutMs: RUNTIME.upstreamTimeoutMs,
|
|
235
|
+
streamHandshakeTimeoutMs: RUNTIME.streamHandshakeTimeoutMs,
|
|
236
|
+
streamIdleTimeoutMs: RUNTIME.streamIdleTimeoutMs,
|
|
237
|
+
});
|
|
164
238
|
|
|
165
239
|
// Point Codex and Claude Code at the adapter.
|
|
166
240
|
ensureCodexConfig(ADAPTER_PORT);
|
|
@@ -183,12 +257,12 @@ try {
|
|
|
183
257
|
|
|
184
258
|
// Periodically refresh the model list + endpoint cache so a long-running
|
|
185
259
|
// adapter picks up newly released models without a restart.
|
|
186
|
-
const refreshIntervalMs =
|
|
260
|
+
const refreshIntervalMs = RUNTIME.modelRefreshIntervalMs;
|
|
187
261
|
if (Number.isFinite(refreshIntervalMs) && refreshIntervalMs > 0) {
|
|
188
|
-
|
|
262
|
+
modelRefreshTimer = setInterval(() => {
|
|
189
263
|
refreshClaudeDesktopModelDefs().catch(() => {});
|
|
190
264
|
}, refreshIntervalMs);
|
|
191
|
-
|
|
265
|
+
modelRefreshTimer.unref?.();
|
|
192
266
|
}
|
|
193
267
|
|
|
194
268
|
console.log(`
|
|
@@ -199,11 +273,30 @@ try {
|
|
|
199
273
|
Press Ctrl+C to stop.
|
|
200
274
|
`);
|
|
201
275
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
276
|
+
const shutdown = async (signal) => {
|
|
277
|
+
if (shuttingDown) return;
|
|
278
|
+
shuttingDown = true;
|
|
279
|
+
console.log(`\n${status("wait", `Shutting down on ${signal}...`)}`);
|
|
280
|
+
let exitCode = 0;
|
|
281
|
+
try {
|
|
282
|
+
if (modelRefreshTimer) clearInterval(modelRefreshTimer);
|
|
283
|
+
const result = await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs });
|
|
284
|
+
if (result.forced) console.warn(status("warn", "Forced remaining adapter connections closed"));
|
|
285
|
+
await flushUsageWrites();
|
|
286
|
+
} catch (e) {
|
|
287
|
+
exitCode = 1;
|
|
288
|
+
console.error(status("err", `Shutdown failed: ${e.message}`));
|
|
289
|
+
} finally {
|
|
290
|
+
LOGGING.cleanup();
|
|
291
|
+
process.exit(exitCode);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
process.once("SIGINT", () => { void shutdown("SIGINT"); });
|
|
295
|
+
process.once("SIGTERM", () => { void shutdown("SIGTERM"); });
|
|
206
296
|
} catch (e) {
|
|
207
297
|
console.error(status("err", e.message));
|
|
298
|
+
await closeHttpServer(activeServer, { timeoutMs: RUNTIME.shutdownTimeoutMs }).catch(() => {});
|
|
299
|
+
await flushUsageWrites();
|
|
300
|
+
LOGGING.cleanup();
|
|
208
301
|
process.exit(1);
|
|
209
302
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-copilot-dx",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.26",
|
|
4
4
|
"description": "Use Codex Desktop, Claude Code, and Claude App with GitHub Copilot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,15 @@
|
|
|
25
25
|
"responses-api"
|
|
26
26
|
],
|
|
27
27
|
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.17"
|
|
30
|
+
},
|
|
28
31
|
"scripts": {
|
|
29
|
-
"test": "node --test"
|
|
32
|
+
"test": "node --test",
|
|
33
|
+
"test:smoke": "node scripts/offline-smoke.mjs",
|
|
34
|
+
"test:e2e": "bash test/e2e.sh",
|
|
35
|
+
"pack:check": "npm pack --dry-run --json",
|
|
36
|
+
"verify": "npm test && npm run test:smoke && npm run pack:check"
|
|
30
37
|
},
|
|
31
38
|
"dependencies": {
|
|
32
39
|
"gpt-tokenizer": "^3.4.0",
|