codex-copilot-dx 0.4.13 → 0.4.15
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 +7 -0
- package/bin/cli.mjs +11 -5
- package/package.json +1 -1
- package/src/auth.mjs +8 -0
- package/src/copilot.mjs +12 -6
- package/src/models.mjs +33 -2
package/README.md
CHANGED
|
@@ -48,6 +48,13 @@ codex-copilot-dx doctor
|
|
|
48
48
|
|
|
49
49
|
The doctor checks the GitHub token, Codex config, Claude Code settings, Claude App gateway profile, and whether the local adapter port is listening.
|
|
50
50
|
|
|
51
|
+
If Copilot token refresh fails with `401` or `403`, the saved GitHub token may be expired, revoked, or missing Copilot access. Delete the saved token and start the tool again to trigger GitHub device login:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
rm ~/.local/share/copilot-api/github_token
|
|
55
|
+
codex-copilot-dx
|
|
56
|
+
```
|
|
57
|
+
|
|
51
58
|
### Claude App opt-in
|
|
52
59
|
|
|
53
60
|
Claude App support is opt-in so the default Codex Desktop and Claude Code setup stays unchanged:
|
package/bin/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ 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
8
|
import { listModels, refreshVSCodeVersion } from "../src/copilot.mjs";
|
|
9
|
-
import { claudeDesktopModelDefsFromCopilotModels, claudeDesktopModelIds, parseModelAliasEnv } from "../src/models.mjs";
|
|
9
|
+
import { claudeDesktopModelDefsFromCopilotModels, claudeDesktopModelIds, gptModelIdsFromCopilotModels, parseModelAliasEnv } from "../src/models.mjs";
|
|
10
10
|
import { status } from "../src/status.mjs";
|
|
11
11
|
import { printUsageSummary } from "../src/usage.mjs";
|
|
12
12
|
import { checkForUpdate, localPackageVersion } from "../src/version.mjs";
|
|
@@ -36,7 +36,15 @@ async function refreshClaudeDesktopModelDefs() {
|
|
|
36
36
|
if (httpStatus < 200 || httpStatus >= 300) {
|
|
37
37
|
throw new Error(`Copilot models returned HTTP ${httpStatus}`);
|
|
38
38
|
}
|
|
39
|
-
const
|
|
39
|
+
const models = JSON.parse(body);
|
|
40
|
+
const gptModelIds = gptModelIdsFromCopilotModels(models);
|
|
41
|
+
if (gptModelIds.length) {
|
|
42
|
+
console.log(status("ok", `Refreshed GPT models from GitHub Copilot: ${gptModelIds.join(", ")}`));
|
|
43
|
+
} else {
|
|
44
|
+
console.log(status("warn", "Copilot models response contained no GPT models"));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const modelDefs = claudeDesktopModelDefsFromCopilotModels(models);
|
|
40
48
|
if (!modelDefs.length) throw new Error("Copilot models response contained no Claude models");
|
|
41
49
|
console.log(status("ok", `Refreshed Claude models from GitHub Copilot: ${modelDefs.map((model) => model.id).join(", ")}`));
|
|
42
50
|
return modelDefs;
|
|
@@ -113,9 +121,7 @@ try {
|
|
|
113
121
|
openCodex();
|
|
114
122
|
|
|
115
123
|
console.log(`
|
|
116
|
-
${status("ok",
|
|
117
|
-
? "Ready. Codex, Claude Code, and Claude App are using your GitHub Copilot subscription."
|
|
118
|
-
: "Ready. Codex and Claude Code are using your GitHub Copilot subscription. Claude App support is available with --configure-claude-desktop.")}
|
|
124
|
+
${status("ok", "Ready, Claude Code, Claude App and Codex App are now ready to use")}
|
|
119
125
|
|
|
120
126
|
Adapter: http://${ADAPTER_HOST}:${ADAPTER_PORT}
|
|
121
127
|
|
package/package.json
CHANGED
package/src/auth.mjs
CHANGED
|
@@ -11,6 +11,14 @@ export function githubTokenPath(home = os.homedir()) {
|
|
|
11
11
|
return path.join(home, ".local", "share", "copilot-api", "github_token");
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export function githubReauthMessage(reason, home = os.homedir()) {
|
|
15
|
+
const tokenPath = githubTokenPath(home);
|
|
16
|
+
return `${reason}
|
|
17
|
+
Delete the saved GitHub token, then run codex-copilot-dx again to log in:
|
|
18
|
+
rm '${tokenPath}'
|
|
19
|
+
codex-copilot-dx`;
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
// Map GitHub polling responses to a small local state machine.
|
|
15
23
|
export function interpretPoll(data) {
|
|
16
24
|
if (typeof data.access_token === "string" && data.access_token) return { state: "done", token: data.access_token };
|
package/src/copilot.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
2
|
import { randomUUID } from "node:crypto";
|
|
5
3
|
import { status } from "./status.mjs";
|
|
4
|
+
import { githubReauthMessage, githubTokenPath } from "./auth.mjs";
|
|
6
5
|
|
|
7
|
-
const GITHUB_TOKEN_PATH = path.join(os.homedir(), ".local", "share", "copilot-api", "github_token");
|
|
8
6
|
export const DEFAULT_API_BASE = "https://api.githubcopilot.com";
|
|
9
7
|
let apiBase = DEFAULT_API_BASE;
|
|
10
8
|
const IMG_MAX_DIM = parseInt(process.env.CCDX_IMG_MAX_DIM || "2048", 10);
|
|
@@ -249,10 +247,13 @@ let copilotToken = null;
|
|
|
249
247
|
let copilotTokenExpiry = 0;
|
|
250
248
|
|
|
251
249
|
function getGithubToken() {
|
|
250
|
+
const GITHUB_TOKEN_PATH = githubTokenPath();
|
|
252
251
|
if (!fs.existsSync(GITHUB_TOKEN_PATH)) {
|
|
253
|
-
throw new Error("GitHub token not found. Run
|
|
252
|
+
throw new Error("GitHub token not found. Run codex-copilot-dx again to log in.");
|
|
254
253
|
}
|
|
255
|
-
|
|
254
|
+
const token = fs.readFileSync(GITHUB_TOKEN_PATH, "utf-8").trim();
|
|
255
|
+
if (!token) throw new Error(githubReauthMessage("GitHub token file is empty."));
|
|
256
|
+
return token;
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
export async function getCopilotToken({ signal } = {}) {
|
|
@@ -262,7 +263,12 @@ export async function getCopilotToken({ signal } = {}) {
|
|
|
262
263
|
headers: { Authorization: `token ${ghToken}`, Accept: "application/json" },
|
|
263
264
|
signal,
|
|
264
265
|
});
|
|
265
|
-
if (!resp.ok)
|
|
266
|
+
if (!resp.ok) {
|
|
267
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
268
|
+
throw new Error(githubReauthMessage(`Failed to get Copilot token: ${resp.status}. The saved GitHub token may be expired, revoked, or missing Copilot access.`));
|
|
269
|
+
}
|
|
270
|
+
throw new Error(`Failed to get Copilot token: ${resp.status}`);
|
|
271
|
+
}
|
|
266
272
|
const data = await resp.json();
|
|
267
273
|
if (!data.token) throw new Error("Copilot token response missing token field");
|
|
268
274
|
copilotToken = data.token;
|
package/src/models.mjs
CHANGED
|
@@ -97,6 +97,29 @@ function hasMessagesEndpoint(model) {
|
|
|
97
97
|
return endpoints.includes("/v1/messages") || endpoints.includes("/chat/completions");
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function hasOpenAIEndpoint(model) {
|
|
101
|
+
const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
102
|
+
return endpoints.includes("/responses") || endpoints.includes("/v1/responses") || endpoints.includes("/chat/completions");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function copilotModelData(models) {
|
|
106
|
+
const data = Array.isArray(models) ? models : models?.data;
|
|
107
|
+
return Array.isArray(data) ? data : [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function uniqueIds(models, predicate) {
|
|
111
|
+
const ids = [];
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
for (const model of models) {
|
|
114
|
+
if (!predicate(model)) continue;
|
|
115
|
+
const id = String(model.id || "").trim();
|
|
116
|
+
if (!id || seen.has(id)) continue;
|
|
117
|
+
ids.push(id);
|
|
118
|
+
seen.add(id);
|
|
119
|
+
}
|
|
120
|
+
return ids;
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
function isClaudeCopilotModel(model) {
|
|
101
124
|
const id = String(model?.id || "").trim();
|
|
102
125
|
const vendor = String(model?.vendor || "").toLowerCase();
|
|
@@ -106,9 +129,17 @@ function isClaudeCopilotModel(model) {
|
|
|
106
129
|
&& hasMessagesEndpoint(model);
|
|
107
130
|
}
|
|
108
131
|
|
|
132
|
+
export function gptModelIdsFromCopilotModels(models) {
|
|
133
|
+
return uniqueIds(copilotModelData(models), (model) => {
|
|
134
|
+
const id = String(model?.id || "").trim();
|
|
135
|
+
return id.startsWith("gpt-")
|
|
136
|
+
&& model?.model_picker_enabled !== false
|
|
137
|
+
&& hasOpenAIEndpoint(model);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
109
141
|
export function claudeDesktopModelDefsFromCopilotModels(models) {
|
|
110
|
-
const data =
|
|
111
|
-
if (!Array.isArray(data)) return [];
|
|
142
|
+
const data = copilotModelData(models);
|
|
112
143
|
|
|
113
144
|
const defs = [];
|
|
114
145
|
const seen = new Set();
|