codex-copilot-dx 0.4.10 → 0.4.12

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/bin/cli.mjs CHANGED
@@ -5,7 +5,8 @@ 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 { refreshVSCodeVersion } from "../src/copilot.mjs";
8
+ import { listModels, refreshVSCodeVersion } from "../src/copilot.mjs";
9
+ import { claudeDesktopModelDefsFromCopilotModels, claudeDesktopModelIds, parseModelAliasEnv } from "../src/models.mjs";
9
10
  import { status } from "../src/status.mjs";
10
11
  import { printUsageSummary } from "../src/usage.mjs";
11
12
  import { checkForUpdate, localPackageVersion } from "../src/version.mjs";
@@ -13,6 +14,7 @@ import { runDoctor } from "../src/doctor.mjs";
13
14
 
14
15
  const ADAPTER_PORT = parseInt(process.env.ADAPTER_PORT || "2026");
15
16
  const ADAPTER_HOST = process.env.ADAPTER_HOST || "127.0.0.1";
17
+ const MODEL_REFRESH_TIMEOUT_MS = parseInt(process.env.CCDX_MODEL_REFRESH_TIMEOUT_MS || "5000", 10);
16
18
  const LOCAL_VERSION = localPackageVersion();
17
19
  const command = process.argv[2];
18
20
  const CONFIGURE_CLAUDE_DESKTOP = command === "--configure-claude-desktop" || process.env.CCDX_CONFIGURE_CLAUDE_DESKTOP === "1";
@@ -21,6 +23,31 @@ function isLoopbackHost(host) {
21
23
  return ["127.0.0.1", "localhost", "::1"].includes(String(host).toLowerCase());
22
24
  }
23
25
 
26
+ async function refreshClaudeDesktopModelDefs() {
27
+ if (parseModelAliasEnv(process.env.CCDX_CLAUDE_MODEL_ALIASES).length) {
28
+ console.log(status("info", "Using CCDX_CLAUDE_MODEL_ALIASES for Claude models"));
29
+ return undefined;
30
+ }
31
+
32
+ const ctrl = new AbortController();
33
+ const timer = setTimeout(() => ctrl.abort(), MODEL_REFRESH_TIMEOUT_MS);
34
+ try {
35
+ const { status: httpStatus, body } = await listModels({ signal: ctrl.signal });
36
+ if (httpStatus < 200 || httpStatus >= 300) {
37
+ throw new Error(`Copilot models returned HTTP ${httpStatus}`);
38
+ }
39
+ const modelDefs = claudeDesktopModelDefsFromCopilotModels(JSON.parse(body));
40
+ if (!modelDefs.length) throw new Error("Copilot models response contained no Claude models");
41
+ console.log(status("ok", `Refreshed Claude models from GitHub Copilot: ${modelDefs.map((model) => model.id).join(", ")}`));
42
+ return modelDefs;
43
+ } catch (e) {
44
+ console.log(status("warn", `Could not refresh Claude models; using built-in model list (${e.message})`));
45
+ return undefined;
46
+ } finally {
47
+ clearTimeout(timer);
48
+ }
49
+ }
50
+
24
51
  if (command === "--version" || command === "-v" || command === "version") {
25
52
  console.log(`codex-copilot-dx v${LOCAL_VERSION}`);
26
53
  process.exit(0);
@@ -53,6 +80,7 @@ try {
53
80
 
54
81
  // Refresh the VS Code version in the background; fallback is non-blocking.
55
82
  refreshVSCodeVersion();
83
+ const claudeDesktopModelDefs = await refreshClaudeDesktopModelDefs();
56
84
 
57
85
  if (!isLoopbackHost(ADAPTER_HOST)) {
58
86
  console.log(status("warn", `ADAPTER_HOST=${ADAPTER_HOST} exposes the adapter beyond loopback. Use only on trusted networks.`));
@@ -63,7 +91,7 @@ try {
63
91
  : (process.env.CCDX_CLAUDE_DESKTOP_API_KEY || process.env.CCDX_PROXY_API_KEY || "");
64
92
 
65
93
  // Start the in-process adapter.
66
- await startAdapter(ADAPTER_PORT, ADAPTER_HOST, { claudeDesktopApiKey });
94
+ await startAdapter(ADAPTER_PORT, ADAPTER_HOST, { claudeDesktopApiKey, claudeDesktopModelDefs });
67
95
 
68
96
  // Point Codex and Claude Code at the adapter.
69
97
  ensureCodexConfig(ADAPTER_PORT);
@@ -73,6 +101,7 @@ try {
73
101
  port: ADAPTER_PORT,
74
102
  host: ADAPTER_HOST,
75
103
  gatewayApiKey: claudeDesktopApiKey,
104
+ modelIds: claudeDesktopModelIds(process.env, { modelDefs: claudeDesktopModelDefs }),
76
105
  });
77
106
  console.log(status("ok", `Configured Claude App gateway profile at ${result.baseUrl}`));
78
107
  console.log(formatClaudeDesktopApplyResult(result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-copilot-dx",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "Use Codex Desktop, Claude Code, and Claude App with GitHub Copilot",
5
5
  "type": "module",
6
6
  "bin": {
package/src/adapter.mjs CHANGED
@@ -228,6 +228,17 @@ function responsesInputItems(input) {
228
228
  return Array.isArray(input) ? cloneJson(input) : [cloneJson(input)];
229
229
  }
230
230
 
231
+ export function stripInternalResponsesInputFields(inputItems) {
232
+ if (!Array.isArray(inputItems)) return inputItems;
233
+ for (const item of inputItems) {
234
+ if (!item || typeof item !== "object") continue;
235
+ for (const key of Object.keys(item)) {
236
+ if (key.startsWith("internal_")) delete item[key];
237
+ }
238
+ }
239
+ return inputItems;
240
+ }
241
+
231
242
  function responsesOutputItems(output) {
232
243
  if (!Array.isArray(output)) return [];
233
244
  return cloneJson(output.filter((item) => item?.type === "message" || item?.type === "function_call"));
@@ -335,6 +346,7 @@ export function prepareResponsesRequest(reqBody) {
335
346
  body.tools = body.tools.filter((tool) => tool?.type !== "image_generation");
336
347
  if (!body.tools.length) delete body.tools;
337
348
  }
349
+ stripInternalResponsesInputFields(body.input);
338
350
 
339
351
  return { body, inputItems: body.input };
340
352
  }
@@ -555,6 +567,9 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
555
567
  || process.env.CCDX_CLAUDE_DESKTOP_API_KEY
556
568
  || process.env.CCDX_PROXY_API_KEY
557
569
  || "";
570
+ const claudeDesktopModelOptions = Array.isArray(options.claudeDesktopModelDefs)
571
+ ? { modelDefs: options.claudeDesktopModelDefs }
572
+ : {};
558
573
 
559
574
  const server = http.createServer((req, res) => {
560
575
  const pathname = requestPath(req.url);
@@ -636,7 +651,7 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
636
651
  if (req.method === "GET" && pathname === "/v1/models") {
637
652
  if (shouldServeClaudeDesktopModels(req, claudeDesktopApiKey)) {
638
653
  res.writeHead(200, { "Content-Type": "application/json" });
639
- res.end(JSON.stringify(claudeDesktopModelsResponse()));
654
+ res.end(JSON.stringify(claudeDesktopModelsResponse(process.env, claudeDesktopModelOptions)));
640
655
  return;
641
656
  }
642
657
  const abort = createRequestAbort(req, res);
@@ -676,7 +691,7 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
676
691
  try {
677
692
  const parsed = await readJsonBody(req);
678
693
  if (!parsed.stream) abort.setTimeout(UPSTREAM_TIMEOUT_MS);
679
- const { requestedModel, upstreamModel } = resolveAnthropicModel(parsed.model || "unknown");
694
+ const { requestedModel, upstreamModel } = resolveAnthropicModel(parsed.model || "unknown", process.env, claudeDesktopModelOptions);
680
695
  const modelNote = upstreamModel === requestedModel ? requestedModel : `${requestedModel} -> ${upstreamModel}`;
681
696
  console.log(status("info", `messages model=${modelNote} stream=${!!parsed.stream}`));
682
697
  const chatReq = anthropicToChat(parsed, { upstreamModel });
package/src/models.mjs CHANGED
@@ -1,4 +1,12 @@
1
1
  const DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS = [
2
+ {
3
+ id: "claude-sonnet-5",
4
+ upstream: "claude-sonnet-5",
5
+ displayName: "Claude Sonnet 5",
6
+ createdAt: "2026-06-30T00:00:00Z",
7
+ maxInputTokens: 1000000,
8
+ maxOutputTokens: 64000,
9
+ },
2
10
  {
3
11
  id: "claude-sonnet-4.6",
4
12
  upstream: "claude-sonnet-4.6",
@@ -73,18 +81,81 @@ export function parseModelAliasEnv(value) {
73
81
  return aliases;
74
82
  }
75
83
 
76
- export function claudeDesktopModelDefs(env = process.env) {
84
+ function cloneModelDefs(modelDefs) {
85
+ return modelDefs.map((model) => ({ ...model }));
86
+ }
87
+
88
+ function numericLimit(value, fallback) {
89
+ return Number.isFinite(value) && value > 0 ? value : fallback;
90
+ }
91
+
92
+ function hasMessagesEndpoint(model) {
93
+ const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
94
+ return endpoints.includes("/v1/messages") || endpoints.includes("/chat/completions");
95
+ }
96
+
97
+ function isClaudeCopilotModel(model) {
98
+ const id = String(model?.id || "").trim();
99
+ const vendor = String(model?.vendor || "").toLowerCase();
100
+ return id
101
+ && (id.startsWith("claude-") || vendor === "anthropic")
102
+ && model?.model_picker_enabled !== false
103
+ && hasMessagesEndpoint(model);
104
+ }
105
+
106
+ export function claudeDesktopModelDefsFromCopilotModels(models) {
107
+ const data = Array.isArray(models) ? models : models?.data;
108
+ if (!Array.isArray(data)) return [];
109
+
110
+ const defs = [];
111
+ const seen = new Set();
112
+ for (const model of data) {
113
+ if (!isClaudeCopilotModel(model)) continue;
114
+ const id = String(model.id || "").trim();
115
+ if (seen.has(id)) continue;
116
+ seen.add(id);
117
+
118
+ const limits = model.capabilities?.limits || {};
119
+ defs.push({
120
+ id,
121
+ upstream: id,
122
+ displayName: String(model.name || id),
123
+ createdAt: "2025-01-01T00:00:00Z",
124
+ maxInputTokens: numericLimit(limits.max_context_window_tokens, numericLimit(limits.max_prompt_tokens, 200000)),
125
+ maxOutputTokens: numericLimit(limits.max_output_tokens, 8192),
126
+ });
127
+ }
128
+
129
+ const upstreamIds = new Set(defs.map((model) => model.upstream));
130
+ for (const alias of DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS) {
131
+ if (alias.id === alias.upstream || seen.has(alias.id) || !upstreamIds.has(alias.upstream)) continue;
132
+ const upstream = defs.find((model) => model.upstream === alias.upstream);
133
+ defs.push({
134
+ ...alias,
135
+ displayName: upstream?.displayName || alias.displayName,
136
+ maxInputTokens: upstream?.maxInputTokens || alias.maxInputTokens,
137
+ maxOutputTokens: upstream?.maxOutputTokens || alias.maxOutputTokens,
138
+ });
139
+ seen.add(alias.id);
140
+ }
141
+
142
+ return defs;
143
+ }
144
+
145
+ export function claudeDesktopModelDefs(env = process.env, options = {}) {
77
146
  const custom = parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES);
78
- return custom.length ? custom : DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS.map((model) => ({ ...model }));
147
+ if (custom.length) return custom;
148
+ if (Array.isArray(options.modelDefs) && options.modelDefs.length) return cloneModelDefs(options.modelDefs);
149
+ return cloneModelDefs(DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS);
79
150
  }
80
151
 
81
- export function claudeDesktopModelIds(env = process.env) {
82
- return claudeDesktopModelDefs(env).map((model) => model.id);
152
+ export function claudeDesktopModelIds(env = process.env, options = {}) {
153
+ return claudeDesktopModelDefs(env, options).map((model) => model.id);
83
154
  }
84
155
 
85
- export function resolveAnthropicModel(model, env = process.env) {
156
+ export function resolveAnthropicModel(model, env = process.env, options = {}) {
86
157
  const requestedModel = String(model || "");
87
- const match = claudeDesktopModelDefs(env).find((entry) => entry.id === requestedModel);
158
+ const match = claudeDesktopModelDefs(env, options).find((entry) => entry.id === requestedModel);
88
159
  return {
89
160
  requestedModel,
90
161
  upstreamModel: match?.upstream || requestedModel,
@@ -102,9 +173,9 @@ export function anthropicModelInfo(model) {
102
173
  };
103
174
  }
104
175
 
105
- export function claudeDesktopModelsResponse(env = process.env) {
176
+ export function claudeDesktopModelsResponse(env = process.env, options = {}) {
106
177
  return {
107
178
  object: "list",
108
- data: claudeDesktopModelDefs(env).map(anthropicModelInfo),
179
+ data: claudeDesktopModelDefs(env, options).map(anthropicModelInfo),
109
180
  };
110
181
  }