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 +31 -2
- package/package.json +1 -1
- package/src/adapter.mjs +17 -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
|
@@ -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
|
-
|
|
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
|
}
|