codex-copilot-dx 0.5.5 → 0.5.6
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 +15 -0
- package/bin/cli.mjs +11 -0
- package/package.json +1 -1
- package/src/cli-models.mjs +164 -0
- package/src/cli-options.mjs +5 -0
package/README.md
CHANGED
|
@@ -33,6 +33,21 @@ global install, use `npx codex-copilot-dx@latest`.
|
|
|
33
33
|
Both installed commands expose the same options and subcommands; help, version,
|
|
34
34
|
doctor, status, and argument-error output use the command name that was invoked.
|
|
35
35
|
|
|
36
|
+
To query the models currently advertised as selectable for the saved Copilot
|
|
37
|
+
account, run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ccdx models
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This performs a fresh, read-only GitHub Copilot model-directory lookup without
|
|
44
|
+
starting the adapter, opening device login, reading the local model cache, or
|
|
45
|
+
consuming an inference request. It lists model IDs by provider with their
|
|
46
|
+
advertised API capabilities and reports the live Claude/Anthropic count. A model
|
|
47
|
+
being advertised does not guarantee that a later inference request will avoid
|
|
48
|
+
quota, rate-limit, or policy enforcement. `codex-copilot-dx models` is fully
|
|
49
|
+
equivalent.
|
|
50
|
+
|
|
36
51
|
To change the model used by Codex Auto-review, run:
|
|
37
52
|
|
|
38
53
|
```bash
|
package/bin/cli.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { formatAdapterStatus, readAdapterStatus } from "../src/cli-status.mjs";
|
|
|
21
21
|
import { closeHttpServer } from "../src/shutdown.mjs";
|
|
22
22
|
import { runAutoReviewModelCommand } from "../src/auto-review-model.mjs";
|
|
23
23
|
import { autoReviewModelPreference } from "../src/user-settings.mjs";
|
|
24
|
+
import { fetchLiveCopilotModels, formatLiveCopilotModels } from "../src/cli-models.mjs";
|
|
24
25
|
|
|
25
26
|
const LOCAL_VERSION = localPackageVersion();
|
|
26
27
|
const CLI_NAME = cliCommandName();
|
|
@@ -68,6 +69,16 @@ if (CLI.command === "status") {
|
|
|
68
69
|
process.exit(1);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
if (CLI.command === "models") {
|
|
73
|
+
try {
|
|
74
|
+
const catalog = await fetchLiveCopilotModels();
|
|
75
|
+
console.log(formatLiveCopilotModels(catalog, { commandName: CLI_NAME }));
|
|
76
|
+
process.exit(0);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error(status("err", e.message));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
71
82
|
if (CLI.command === "auto-review-model") {
|
|
72
83
|
try {
|
|
73
84
|
await runAutoReviewModelCommand({ commandName: CLI_NAME });
|
package/package.json
CHANGED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { githubTokenPath, validateGithubToken } from "./auth.mjs";
|
|
4
|
+
import { buildHeaders, DEFAULT_API_BASE, FALLBACK_VSCODE_VERSION } from "./copilot.mjs";
|
|
5
|
+
import { status } from "./status.mjs";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MODELS_TIMEOUT_MS = 10000;
|
|
8
|
+
const SUPPORTED_ENDPOINTS = [
|
|
9
|
+
["responses", new Set(["/responses", "/v1/responses"])],
|
|
10
|
+
["chat", new Set(["/chat/completions"])],
|
|
11
|
+
["messages", new Set(["/v1/messages"])],
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
function safeText(value, fallback = "") {
|
|
15
|
+
const text = String(value ?? "")
|
|
16
|
+
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
return text || fallback;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function endpointLabels(model) {
|
|
23
|
+
const advertised = new Set(Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : []);
|
|
24
|
+
return SUPPORTED_ENDPOINTS
|
|
25
|
+
.filter(([, values]) => [...values].some((value) => advertised.has(value)))
|
|
26
|
+
.map(([label]) => label);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function selectableCopilotModels(payload) {
|
|
30
|
+
const data = Array.isArray(payload) ? payload : payload?.data;
|
|
31
|
+
if (!Array.isArray(data)) throw new Error("Copilot models response contained no model list");
|
|
32
|
+
|
|
33
|
+
const models = [];
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
for (const model of data) {
|
|
36
|
+
if (model?.model_picker_enabled !== true) continue;
|
|
37
|
+
const policyState = safeText(model?.policy?.state).toLowerCase();
|
|
38
|
+
if (policyState && policyState !== "enabled") continue;
|
|
39
|
+
const capabilityType = safeText(model?.capabilities?.type).toLowerCase();
|
|
40
|
+
if (capabilityType && capabilityType !== "chat") continue;
|
|
41
|
+
|
|
42
|
+
const id = safeText(model?.id);
|
|
43
|
+
const endpoints = endpointLabels(model);
|
|
44
|
+
if (!id || id === "codex-auto-review" || !endpoints.length || seen.has(id)) continue;
|
|
45
|
+
seen.add(id);
|
|
46
|
+
models.push({
|
|
47
|
+
id,
|
|
48
|
+
vendor: safeText(model?.vendor || model?.owned_by, "Unknown"),
|
|
49
|
+
endpoints,
|
|
50
|
+
preview: model?.preview === true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
models.sort((left, right) => left.vendor.localeCompare(right.vendor, "en")
|
|
55
|
+
|| left.id.localeCompare(right.id, "en"));
|
|
56
|
+
return { advertised: data.length, models };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validationFailure(validation) {
|
|
60
|
+
const httpStatus = validation?.status ? ` (HTTP ${validation.status})` : "";
|
|
61
|
+
return `GitHub Copilot authentication failed: ${validation?.reason || "unknown error"}${httpStatus}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function responseText(response) {
|
|
65
|
+
try {
|
|
66
|
+
return await response.text();
|
|
67
|
+
} catch {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function fetchLiveCopilotModels({
|
|
73
|
+
home = os.homedir(),
|
|
74
|
+
fetchImpl = fetch,
|
|
75
|
+
timeoutMs = DEFAULT_MODELS_TIMEOUT_MS,
|
|
76
|
+
vscodeVersion = FALLBACK_VSCODE_VERSION,
|
|
77
|
+
} = {}) {
|
|
78
|
+
const tokenPath = githubTokenPath(home);
|
|
79
|
+
let githubToken;
|
|
80
|
+
try {
|
|
81
|
+
githubToken = fs.readFileSync(tokenPath, "utf8").trim();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (error?.code === "ENOENT") throw new Error("GitHub token not found. Start ccdx once to log in.");
|
|
84
|
+
throw new Error(`Could not read the GitHub token: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
if (!githubToken) throw new Error("GitHub token file is empty. Start ccdx again to log in.");
|
|
87
|
+
|
|
88
|
+
const controller = new AbortController();
|
|
89
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
90
|
+
try {
|
|
91
|
+
const validation = await validateGithubToken(githubToken, {
|
|
92
|
+
fetchImpl,
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
});
|
|
95
|
+
if (controller.signal.aborted) throw new Error(`Live model lookup timed out after ${timeoutMs}ms`);
|
|
96
|
+
if (!validation.ok) throw new Error(validationFailure(validation));
|
|
97
|
+
|
|
98
|
+
const tokenData = validation.copilotTokenData;
|
|
99
|
+
const apiBase = tokenData.endpoints?.api || DEFAULT_API_BASE;
|
|
100
|
+
const headers = buildHeaders({
|
|
101
|
+
token: tokenData.token,
|
|
102
|
+
version: vscodeVersion,
|
|
103
|
+
initiator: "user",
|
|
104
|
+
vision: false,
|
|
105
|
+
});
|
|
106
|
+
const response = await fetchImpl(`${apiBase}/models`, {
|
|
107
|
+
headers,
|
|
108
|
+
redirect: "error",
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
});
|
|
111
|
+
const body = await responseText(response);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
const detail = safeText(body).slice(0, 240);
|
|
114
|
+
throw new Error(`Copilot models endpoint returned HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let payload;
|
|
118
|
+
try {
|
|
119
|
+
payload = JSON.parse(body);
|
|
120
|
+
} catch {
|
|
121
|
+
throw new Error("Copilot models endpoint returned invalid JSON");
|
|
122
|
+
}
|
|
123
|
+
const catalog = selectableCopilotModels(payload);
|
|
124
|
+
let upstreamHost = "GitHub Copilot";
|
|
125
|
+
try { upstreamHost = new URL(apiBase).hostname; } catch {}
|
|
126
|
+
return { ...catalog, upstreamHost: safeText(upstreamHost, "GitHub Copilot") };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (controller.signal.aborted && !/timed out/.test(error?.message || "")) {
|
|
129
|
+
throw new Error(`Live model lookup timed out after ${timeoutMs}ms`);
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
} finally {
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function formatLiveCopilotModels(catalog, { commandName = "ccdx" } = {}) {
|
|
138
|
+
const models = Array.isArray(catalog?.models) ? catalog.models : [];
|
|
139
|
+
const responses = models.filter((model) => model.endpoints.includes("responses")).length;
|
|
140
|
+
const chat = models.filter((model) => model.endpoints.includes("chat")).length;
|
|
141
|
+
const claude = models.filter((model) => model.id.toLowerCase().startsWith("claude-")
|
|
142
|
+
|| model.vendor.toLowerCase() === "anthropic").length;
|
|
143
|
+
const lines = [
|
|
144
|
+
`${commandName} models`,
|
|
145
|
+
status("ok", `Live catalog from ${safeText(catalog?.upstreamHost, "GitHub Copilot")}: ${models.length} selectable of ${Number(catalog?.advertised) || 0} advertised`),
|
|
146
|
+
status("info", `Responses: ${responses}; Chat: ${chat}; Claude/Anthropic: ${claude}`),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
if (!models.length) {
|
|
150
|
+
lines.push(status("warn", "No selectable models were advertised for this account"));
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let vendor = "";
|
|
155
|
+
for (const model of models) {
|
|
156
|
+
if (model.vendor !== vendor) {
|
|
157
|
+
vendor = model.vendor;
|
|
158
|
+
lines.push("", `${vendor}:`);
|
|
159
|
+
}
|
|
160
|
+
const flags = [...model.endpoints, ...(model.preview ? ["preview"] : [])];
|
|
161
|
+
lines.push(` ${model.id} [${flags.join(", ")}]`);
|
|
162
|
+
}
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
package/src/cli-options.mjs
CHANGED
|
@@ -26,6 +26,10 @@ export function parseCliArgs(args = []) {
|
|
|
26
26
|
if (rest.length) unexpectedArgs(rest);
|
|
27
27
|
return { command: "usage", configureClaudeDesktop: false, showRequestId: false, online: false, compat: false };
|
|
28
28
|
}
|
|
29
|
+
if (command === "models") {
|
|
30
|
+
if (rest.length) unexpectedArgs(rest);
|
|
31
|
+
return { command: "models", configureClaudeDesktop: false, showRequestId: false, online: false, compat: false };
|
|
32
|
+
}
|
|
29
33
|
if (command === "auto-review-model") {
|
|
30
34
|
if (rest.length) unexpectedArgs(rest);
|
|
31
35
|
return { command: "auto-review-model", configureClaudeDesktop: false, showRequestId: false, online: false, compat: false };
|
|
@@ -125,6 +129,7 @@ export function cliHelp(commandName = "ccdx") {
|
|
|
125
129
|
${name} [--configure-claude-desktop] [--show-request-id]
|
|
126
130
|
${name} doctor [--online] [--compat]
|
|
127
131
|
${name} status
|
|
132
|
+
${name} models
|
|
128
133
|
${name} usage
|
|
129
134
|
${name} auto-review-model
|
|
130
135
|
${name} update [npm|github]
|