codex-copilot-dx 0.4.11 → 0.4.13
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 +31 -2
- package/package.json +1 -1
- package/src/adapter.mjs +16 -3
- package/src/models.mjs +78 -8
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
package/src/adapter.mjs
CHANGED
|
@@ -567,6 +567,9 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
|
|
|
567
567
|
|| process.env.CCDX_CLAUDE_DESKTOP_API_KEY
|
|
568
568
|
|| process.env.CCDX_PROXY_API_KEY
|
|
569
569
|
|| "";
|
|
570
|
+
const claudeDesktopModelOptions = Array.isArray(options.claudeDesktopModelDefs)
|
|
571
|
+
? { modelDefs: options.claudeDesktopModelDefs }
|
|
572
|
+
: {};
|
|
570
573
|
|
|
571
574
|
const server = http.createServer((req, res) => {
|
|
572
575
|
const pathname = requestPath(req.url);
|
|
@@ -648,7 +651,7 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
|
|
|
648
651
|
if (req.method === "GET" && pathname === "/v1/models") {
|
|
649
652
|
if (shouldServeClaudeDesktopModels(req, claudeDesktopApiKey)) {
|
|
650
653
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
651
|
-
res.end(JSON.stringify(claudeDesktopModelsResponse()));
|
|
654
|
+
res.end(JSON.stringify(claudeDesktopModelsResponse(process.env, claudeDesktopModelOptions)));
|
|
652
655
|
return;
|
|
653
656
|
}
|
|
654
657
|
const abort = createRequestAbort(req, res);
|
|
@@ -688,7 +691,7 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
|
|
|
688
691
|
try {
|
|
689
692
|
const parsed = await readJsonBody(req);
|
|
690
693
|
if (!parsed.stream) abort.setTimeout(UPSTREAM_TIMEOUT_MS);
|
|
691
|
-
const { requestedModel, upstreamModel } = resolveAnthropicModel(parsed.model || "unknown");
|
|
694
|
+
const { requestedModel, upstreamModel } = resolveAnthropicModel(parsed.model || "unknown", process.env, claudeDesktopModelOptions);
|
|
692
695
|
const modelNote = upstreamModel === requestedModel ? requestedModel : `${requestedModel} -> ${upstreamModel}`;
|
|
693
696
|
console.log(status("info", `messages model=${modelNote} stream=${!!parsed.stream}`));
|
|
694
697
|
const chatReq = anthropicToChat(parsed, { upstreamModel });
|
|
@@ -747,8 +750,18 @@ export function startAdapter(port = 2026, host = "127.0.0.1", options = {}) {
|
|
|
747
750
|
socket.destroy();
|
|
748
751
|
});
|
|
749
752
|
|
|
750
|
-
return new Promise((resolve) => {
|
|
753
|
+
return new Promise((resolve, reject) => {
|
|
754
|
+
const onListenError = (e) => {
|
|
755
|
+
if (e?.code === "EADDRINUSE") {
|
|
756
|
+
reject(new Error(`Adapter address http://${host}:${port} is already in use. Stop the existing codex-copilot-dx process or set ADAPTER_PORT to another port.`));
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
reject(e);
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
server.once("error", onListenError);
|
|
751
763
|
server.listen(port, host, () => {
|
|
764
|
+
server.off("error", onListenError);
|
|
752
765
|
const actualPort = server.address()?.port || port;
|
|
753
766
|
console.log(status("ok", `Adapter listening on http://${host}:${actualPort}`));
|
|
754
767
|
resolve(server);
|
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",
|
|
@@ -23,6 +31,9 @@ const DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS = [
|
|
|
23
31
|
maxInputTokens: 200000,
|
|
24
32
|
maxOutputTokens: 8192,
|
|
25
33
|
},
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const DEFAULT_CLAUDE_MODEL_ALIAS_DEFS = [
|
|
26
37
|
{
|
|
27
38
|
id: "claude-sonnet-4-6",
|
|
28
39
|
upstream: "claude-sonnet-4.6",
|
|
@@ -73,18 +84,77 @@ export function parseModelAliasEnv(value) {
|
|
|
73
84
|
return aliases;
|
|
74
85
|
}
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
function cloneModelDefs(modelDefs) {
|
|
88
|
+
return modelDefs.map((model) => ({ ...model }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function numericLimit(value, fallback) {
|
|
92
|
+
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasMessagesEndpoint(model) {
|
|
96
|
+
const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
97
|
+
return endpoints.includes("/v1/messages") || endpoints.includes("/chat/completions");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isClaudeCopilotModel(model) {
|
|
101
|
+
const id = String(model?.id || "").trim();
|
|
102
|
+
const vendor = String(model?.vendor || "").toLowerCase();
|
|
103
|
+
return id
|
|
104
|
+
&& (id.startsWith("claude-") || vendor === "anthropic")
|
|
105
|
+
&& model?.model_picker_enabled !== false
|
|
106
|
+
&& hasMessagesEndpoint(model);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function claudeDesktopModelDefsFromCopilotModels(models) {
|
|
110
|
+
const data = Array.isArray(models) ? models : models?.data;
|
|
111
|
+
if (!Array.isArray(data)) return [];
|
|
112
|
+
|
|
113
|
+
const defs = [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
for (const model of data) {
|
|
116
|
+
if (!isClaudeCopilotModel(model)) continue;
|
|
117
|
+
const id = String(model.id || "").trim();
|
|
118
|
+
if (seen.has(id)) continue;
|
|
119
|
+
seen.add(id);
|
|
120
|
+
|
|
121
|
+
const limits = model.capabilities?.limits || {};
|
|
122
|
+
defs.push({
|
|
123
|
+
id,
|
|
124
|
+
upstream: id,
|
|
125
|
+
displayName: String(model.name || id),
|
|
126
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
127
|
+
maxInputTokens: numericLimit(limits.max_context_window_tokens, numericLimit(limits.max_prompt_tokens, 200000)),
|
|
128
|
+
maxOutputTokens: numericLimit(limits.max_output_tokens, 8192),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return defs;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function claudeDesktopModelDefs(env = process.env, options = {}) {
|
|
77
136
|
const custom = parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES);
|
|
78
|
-
|
|
137
|
+
if (custom.length) return custom;
|
|
138
|
+
if (Array.isArray(options.modelDefs) && options.modelDefs.length) return cloneModelDefs(options.modelDefs);
|
|
139
|
+
return cloneModelDefs(DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS);
|
|
79
140
|
}
|
|
80
141
|
|
|
81
|
-
export function claudeDesktopModelIds(env = process.env) {
|
|
82
|
-
return claudeDesktopModelDefs(env).map((model) => model.id);
|
|
142
|
+
export function claudeDesktopModelIds(env = process.env, options = {}) {
|
|
143
|
+
return claudeDesktopModelDefs(env, options).map((model) => model.id);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function claudeModelResolutionDefs(env = process.env, options = {}) {
|
|
147
|
+
const defs = claudeDesktopModelDefs(env, options);
|
|
148
|
+
if (parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES).length) return defs;
|
|
149
|
+
|
|
150
|
+
const upstreamIds = new Set(defs.map((model) => model.upstream));
|
|
151
|
+
const aliases = DEFAULT_CLAUDE_MODEL_ALIAS_DEFS.filter((alias) => upstreamIds.has(alias.upstream));
|
|
152
|
+
return [...defs, ...aliases];
|
|
83
153
|
}
|
|
84
154
|
|
|
85
|
-
export function resolveAnthropicModel(model, env = process.env) {
|
|
155
|
+
export function resolveAnthropicModel(model, env = process.env, options = {}) {
|
|
86
156
|
const requestedModel = String(model || "");
|
|
87
|
-
const match =
|
|
157
|
+
const match = claudeModelResolutionDefs(env, options).find((entry) => entry.id === requestedModel);
|
|
88
158
|
return {
|
|
89
159
|
requestedModel,
|
|
90
160
|
upstreamModel: match?.upstream || requestedModel,
|
|
@@ -102,9 +172,9 @@ export function anthropicModelInfo(model) {
|
|
|
102
172
|
};
|
|
103
173
|
}
|
|
104
174
|
|
|
105
|
-
export function claudeDesktopModelsResponse(env = process.env) {
|
|
175
|
+
export function claudeDesktopModelsResponse(env = process.env, options = {}) {
|
|
106
176
|
return {
|
|
107
177
|
object: "list",
|
|
108
|
-
data: claudeDesktopModelDefs(env).map(anthropicModelInfo),
|
|
178
|
+
data: claudeDesktopModelDefs(env, options).map(anthropicModelInfo),
|
|
109
179
|
};
|
|
110
180
|
}
|