codex-copilot-dx 0.4.11 → 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 +31 -2
- package/package.json +1 -1
- package/src/adapter.mjs +5 -2
- package/src/models.mjs +79 -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 });
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|