codex-copilot-dx 0.4.38 → 0.4.40

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 CHANGED
@@ -107,7 +107,7 @@ Or set:
107
107
  CCDX_CONFIGURE_CLAUDE_DESKTOP=1 npx codex-copilot-dx@latest
108
108
  ```
109
109
 
110
- This writes a local Claude App 3P gateway profile that points to the adapter root URL, such as `http://127.0.0.1:2026`. The profile uses a generated local bearer key unless `CCDX_CLAUDE_DESKTOP_API_KEY` is set. Later starts restore that key only from the active managed profile when its gateway URL matches the adapter. Restart Claude App after running the command.
110
+ This writes a local Claude App 3P gateway profile that points to the adapter root URL, such as `http://127.0.0.1:2026`. The profile uses a generated local bearer key unless `CCDX_CLAUDE_DESKTOP_API_KEY` is set. Later starts restore that key only from the active managed profile when its gateway URL matches the adapter. Once that managed profile is active, successful Copilot model refreshes automatically keep its `inferenceModels` list current without changing the gateway key or other profile settings. Restart Claude App after the initial setup. Later model changes require no profile reconfiguration, although a running Claude App may need to be reopened if it has not reloaded the updated profile yet.
111
111
 
112
112
  When reusing an already-running adapter, Claude App profile updates require `CCDX_CLAUDE_DESKTOP_API_KEY` or `CCDX_PROXY_API_KEY` so the profile key matches the running process. Otherwise the existing adapter is left untouched.
113
113
 
package/bin/cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  import { ensureAuth, openCodex } from "../src/launcher.mjs";
4
4
  import { ensureCodexConfig } from "../src/config.mjs";
5
5
  import { ensureClaudeConfig } from "../src/claude-config.mjs";
6
- import { applyClaudeDesktopConfig, formatClaudeDesktopApplyResult, generatedClaudeDesktopApiKey, loadManagedClaudeDesktopApiKey } from "../src/claude-desktop-config.mjs";
6
+ import { applyClaudeDesktopConfig, formatClaudeDesktopApplyResult, generatedClaudeDesktopApiKey, loadManagedClaudeDesktopApiKey, syncManagedClaudeDesktopModels } from "../src/claude-desktop-config.mjs";
7
7
  import { startAdapter } from "../src/adapter.mjs";
8
8
  import { cacheModelEndpoints, listModels, refreshVSCodeVersion } from "../src/copilot.mjs";
9
9
  import { claudeDesktopModelDefsFromCopilotModels, claudeDesktopModelIds, codexAutoReviewModelStatus, gptModelIdsFromCopilotModels, parseModelAliasEnv } from "../src/models.mjs";
@@ -129,10 +129,14 @@ async function refreshClaudeDesktopModelDefs() {
129
129
  } else {
130
130
  console.log(status("warn", "Copilot models response contained no Claude models; using built-in Claude models"));
131
131
  }
132
+ syncClaudeDesktopProfileModels(MODEL_REGISTRY.modelDefs);
132
133
  return modelDefs;
133
134
  } catch (e) {
134
135
  const fallback = MODEL_REGISTRY.modelDefs?.length ? `${MODEL_REGISTRY.source} model list` : "built-in model list";
135
- console.log(status("warn", `Could not refresh Claude models; using ${fallback} (${e.message})`));
136
+ const message = ctrl.signal.aborted
137
+ ? `Model refresh timed out after ${MODEL_REFRESH_TIMEOUT_MS}ms; using ${fallback}`
138
+ : `Could not refresh model list; using ${fallback} (${e.message})`;
139
+ console.log(status("warn", message));
136
140
  return MODEL_REGISTRY.modelDefs;
137
141
  } finally {
138
142
  clearTimeout(timer);
@@ -143,6 +147,20 @@ function currentClaudeDesktopApiKey() {
143
147
  return String(process.env.CCDX_CLAUDE_DESKTOP_API_KEY || process.env.CCDX_PROXY_API_KEY || "").trim();
144
148
  }
145
149
 
150
+ function syncClaudeDesktopProfileModels(modelDefs = MODEL_REGISTRY.modelDefs) {
151
+ const result = syncManagedClaudeDesktopModels({
152
+ port: ADAPTER_PORT,
153
+ host: ADAPTER_HOST,
154
+ modelIds: claudeDesktopModelIds(process.env, { modelDefs }),
155
+ });
156
+ if (result.updated) {
157
+ console.log(status("ok", `Updated Claude App profile models: ${result.modelIds.join(", ")}`));
158
+ } else if (result.error) {
159
+ console.log(status("warn", `Could not update Claude App profile models (${result.error.message})`));
160
+ }
161
+ return result;
162
+ }
163
+
146
164
  async function reuseRunningAdapterIfAvailable() {
147
165
  const running = await checkRunningAdapter({
148
166
  port: ADAPTER_PORT,
@@ -228,6 +246,7 @@ try {
228
246
  refresh: refreshClaudeDesktopModelDefs,
229
247
  });
230
248
  const claudeDesktopModelDefs = modelInitialization.modelDefs;
249
+ syncClaudeDesktopProfileModels(claudeDesktopModelDefs);
231
250
 
232
251
  if (!isLoopbackHost(ADAPTER_HOST)) {
233
252
  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.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.4.38",
3
+ "version": "0.4.40",
4
4
  "description": "Use Codex Desktop, Claude Code, and Claude App with GitHub Copilot",
5
5
  "author": "Dale Xiao",
6
6
  "type": "module",
@@ -159,34 +159,92 @@ function readActiveProfile(paths) {
159
159
  return readJsonFile(path.join(paths.configLibraryPath, `${appliedId}.json`), {});
160
160
  }
161
161
 
162
- export function loadManagedClaudeDesktopApiKey({
162
+ function activeManagedProfile({
163
163
  port = 2026,
164
164
  host = "127.0.0.1",
165
165
  home = os.homedir(),
166
166
  platform = process.platform,
167
167
  env = process.env,
168
168
  } = {}) {
169
- try {
170
- const paths = claudeDesktopPaths(home, platform, env);
171
- const meta = readJsonFile(paths.metaPath, {});
172
- if (String(meta.appliedId || "").trim() !== DEFAULT_CLAUDE_DESKTOP_PROFILE_ID) return "";
169
+ const paths = claudeDesktopPaths(home, platform, env);
170
+ const meta = readJsonFile(paths.metaPath, {});
171
+ if (String(meta.appliedId || "").trim() !== DEFAULT_CLAUDE_DESKTOP_PROFILE_ID) return null;
172
+
173
+ const profilePath = path.join(paths.configLibraryPath, `${DEFAULT_CLAUDE_DESKTOP_PROFILE_ID}.json`);
174
+ const profile = readJsonFile(profilePath, {});
175
+ if (profile.inferenceProvider !== "gateway") return null;
176
+ if (String(profile.inferenceGatewayAuthScheme || "").trim().toLowerCase() !== "bearer") return null;
173
177
 
174
- const profilePath = path.join(paths.configLibraryPath, `${DEFAULT_CLAUDE_DESKTOP_PROFILE_ID}.json`);
175
- const profile = readJsonFile(profilePath, {});
176
- if (profile.inferenceProvider !== "gateway") return "";
177
- if (String(profile.inferenceGatewayAuthScheme || "").trim().toLowerCase() !== "bearer") return "";
178
+ const profileBaseUrl = String(profile.inferenceGatewayBaseUrl || "").trim();
179
+ validateClaudeDesktopBaseUrl(profileBaseUrl);
180
+ if (new URL(profileBaseUrl).href !== new URL(localGatewayBaseUrl(host, port)).href) return null;
181
+
182
+ return { profilePath, profile };
183
+ }
178
184
 
179
- const profileBaseUrl = String(profile.inferenceGatewayBaseUrl || "").trim();
180
- validateClaudeDesktopBaseUrl(profileBaseUrl);
181
- if (new URL(profileBaseUrl).href !== new URL(localGatewayBaseUrl(host, port)).href) return "";
185
+ function normalizedModelIds(modelIds) {
186
+ const ids = [];
187
+ const seen = new Set();
188
+ for (const value of Array.isArray(modelIds) ? modelIds : []) {
189
+ const id = String(value || "").trim();
190
+ if (!id || seen.has(id)) continue;
191
+ ids.push(id);
192
+ seen.add(id);
193
+ }
194
+ return ids;
195
+ }
182
196
 
183
- const apiKey = String(profile.inferenceGatewayApiKey || "").trim();
197
+ export function loadManagedClaudeDesktopApiKey({
198
+ port = 2026,
199
+ host = "127.0.0.1",
200
+ home = os.homedir(),
201
+ platform = process.platform,
202
+ env = process.env,
203
+ } = {}) {
204
+ try {
205
+ const managed = activeManagedProfile({ port, host, home, platform, env });
206
+ const apiKey = String(managed?.profile.inferenceGatewayApiKey || "").trim();
184
207
  return apiKey && apiKey !== "dummy" ? apiKey : "";
185
208
  } catch {
186
209
  return "";
187
210
  }
188
211
  }
189
212
 
213
+ export function syncManagedClaudeDesktopModels({
214
+ modelIds,
215
+ port = 2026,
216
+ host = "127.0.0.1",
217
+ home = os.homedir(),
218
+ platform = process.platform,
219
+ env = process.env,
220
+ } = {}) {
221
+ const normalizedIds = normalizedModelIds(modelIds);
222
+ if (!normalizedIds.length) return { updated: false, reason: "empty-model-list", modelIds: normalizedIds };
223
+
224
+ try {
225
+ const managed = activeManagedProfile({ port, host, home, platform, env });
226
+ if (!managed) return { updated: false, reason: "not-managed", modelIds: normalizedIds };
227
+
228
+ const inferenceModels = JSON.stringify(normalizedIds);
229
+ if (managed.profile.inferenceModels === inferenceModels) {
230
+ return { updated: false, reason: "unchanged", profilePath: managed.profilePath, modelIds: normalizedIds };
231
+ }
232
+
233
+ const updated = writeJsonFile(managed.profilePath, {
234
+ ...managed.profile,
235
+ inferenceModels,
236
+ });
237
+ return {
238
+ updated,
239
+ reason: updated ? "updated" : "unchanged",
240
+ profilePath: managed.profilePath,
241
+ modelIds: normalizedIds,
242
+ };
243
+ } catch (error) {
244
+ return { updated: false, reason: "error", error, modelIds: normalizedIds };
245
+ }
246
+ }
247
+
190
248
  export function applyClaudeDesktopConfig({
191
249
  port = 2026,
192
250
  host = "127.0.0.1",
package/src/startup.mjs CHANGED
@@ -2,16 +2,12 @@ export async function initializeModelRegistry({
2
2
  loadCached,
3
3
  currentModelDefs,
4
4
  refresh,
5
- onBackgroundError = () => {},
6
5
  }) {
7
- if (!loadCached()) {
8
- return { modelDefs: await refresh(), source: "live", backgroundRefresh: null };
6
+ if (loadCached()) {
7
+ return { modelDefs: currentModelDefs(), source: "cache", backgroundRefresh: null };
9
8
  }
10
9
 
11
- const backgroundRefresh = Promise.resolve()
12
- .then(refresh)
13
- .catch(onBackgroundError);
14
- return { modelDefs: currentModelDefs(), source: "cache", backgroundRefresh };
10
+ return { modelDefs: await refresh(), source: "live", backgroundRefresh: null };
15
11
  }
16
12
 
17
13
  export function runInBackground(task, onError = () => {}) {