codex-copilot-dx 0.6.20 → 0.7.0
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 +123 -326
- package/bin/cli.mjs +13 -129
- package/package.json +2 -8
- package/scripts/benchmark.mjs +0 -114
- package/src/adapter.mjs +19 -106
- package/src/auth-profile.mjs +18 -130
- package/src/cli-auth.mjs +44 -398
- package/src/cli-models.mjs +21 -49
- package/src/cli-options.mjs +45 -75
- package/src/cli-status.mjs +6 -23
- package/src/config.mjs +2 -8
- package/src/copilot.mjs +2 -2
- package/src/doctor.mjs +44 -291
- package/src/model-cache.mjs +4 -11
- package/src/models.mjs +0 -184
- package/src/observability.mjs +1 -38
- package/src/profile-model-runtime.mjs +61 -189
- package/src/profile-routing.mjs +2 -102
- package/src/profile-runtime.mjs +15 -68
- package/src/responses-handler.mjs +3 -1
- package/src/responses-proxy.mjs +1 -1
- package/src/running-adapter.mjs +2 -4
- package/src/stream-errors.mjs +3 -6
- package/src/stream-performance.mjs +1 -1
- package/src/usage.mjs +0 -31
- package/src/anthropic-handler.mjs +0 -195
- package/src/anthropic.mjs +0 -387
- package/src/claude-config.mjs +0 -57
- package/src/claude-desktop-config.mjs +0 -317
- package/src/cli-error.mjs +0 -50
- package/src/pm-studio-asar.mjs +0 -1244
- package/src/pm-studio-bundle-content.mjs +0 -165
- package/src/pm-studio-relay.mjs +0 -563
- package/src/pm-studio-setup-lock.mjs +0 -336
- package/src/pm-studio-setup.mjs +0 -1581
- package/src/pm-studio-status.mjs +0 -369
- package/src/token-counter-service.mjs +0 -211
- package/src/token-counter-worker.mjs +0 -16
package/src/doctor.mjs
CHANGED
|
@@ -4,16 +4,13 @@ import os from "node:os";
|
|
|
4
4
|
import net from "node:net";
|
|
5
5
|
import { githubTokenPath, readGithubTokenMetadata, validateGithubToken } from "./auth.mjs";
|
|
6
6
|
import {
|
|
7
|
-
AUTH_PROFILE_CLAUDE,
|
|
8
7
|
AUTH_PROFILE_CODEX,
|
|
9
8
|
readAuthProfileCredentials,
|
|
10
9
|
} from "./auth-profile.mjs";
|
|
11
|
-
import { claudeDesktopPaths } from "./claude-desktop-config.mjs";
|
|
12
10
|
import { status } from "./status.mjs";
|
|
13
11
|
import { buildHeaders, FALLBACK_VSCODE_VERSION, parseApiBase } from "./copilot.mjs";
|
|
14
12
|
import { adapterBaseUrl, checkRunningAdapter } from "./running-adapter.mjs";
|
|
15
|
-
import { CODEX_AUTO_REVIEW_MODEL
|
|
16
|
-
import { loadModelCache } from "./model-cache.mjs";
|
|
13
|
+
import { CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
|
|
17
14
|
|
|
18
15
|
function localGatewayBaseUrl(host, port) {
|
|
19
16
|
const safeHost = String(host || "127.0.0.1");
|
|
@@ -24,18 +21,8 @@ function localGatewayBaseUrl(host, port) {
|
|
|
24
21
|
function readText(filePath) {
|
|
25
22
|
try {
|
|
26
23
|
return { ok: true, text: fs.readFileSync(filePath, "utf8") };
|
|
27
|
-
} catch (
|
|
28
|
-
return { ok: false, missing:
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readJson(filePath) {
|
|
33
|
-
const raw = readText(filePath);
|
|
34
|
-
if (!raw.ok) return raw;
|
|
35
|
-
try {
|
|
36
|
-
return { ok: true, json: JSON.parse(raw.text) };
|
|
37
|
-
} catch (e) {
|
|
38
|
-
return { ok: false, parseError: true, error: e };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return { ok: false, missing: error?.code === "ENOENT", error };
|
|
39
26
|
}
|
|
40
27
|
}
|
|
41
28
|
|
|
@@ -64,21 +51,18 @@ function modelEndpoints(model) {
|
|
|
64
51
|
return Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
65
52
|
}
|
|
66
53
|
|
|
67
|
-
export function selectCompatibilityModels(models
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
54
|
+
export function selectCompatibilityModels(models) {
|
|
55
|
+
const candidates = copilotModelData(models)
|
|
56
|
+
.filter((model) => model?.model_picker_enabled !== false)
|
|
57
|
+
.filter((model) => {
|
|
58
|
+
const id = String(model?.id || "");
|
|
59
|
+
const endpoints = modelEndpoints(model);
|
|
60
|
+
return id.startsWith("gpt-")
|
|
61
|
+
&& (endpoints.includes("/responses") || endpoints.includes("/v1/responses"));
|
|
62
|
+
});
|
|
63
|
+
const responsesOnly = [...candidates].reverse()
|
|
76
64
|
.find((model) => !modelEndpoints(model).includes("/chat/completions"));
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
responsesModel: String((responsesOnly || responsesCandidates[0])?.id || ""),
|
|
80
|
-
claudeModel: String(claude?.id || ""),
|
|
81
|
-
};
|
|
65
|
+
return { responsesModel: String((responsesOnly || candidates[0])?.id || "") };
|
|
82
66
|
}
|
|
83
67
|
|
|
84
68
|
async function fetchTextWithTimeout(fetchImpl, url, init, timeoutMs) {
|
|
@@ -113,8 +97,8 @@ async function runCompatibilityCheck(checks, label, task) {
|
|
|
113
97
|
const value = await task();
|
|
114
98
|
checks.push({ kind: "ok", message: `${label} passed (${Date.now() - started}ms)` });
|
|
115
99
|
return value;
|
|
116
|
-
} catch (
|
|
117
|
-
const reason =
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const reason = error?.name === "AbortError" ? "timed out" : error?.message || "unknown error";
|
|
118
102
|
checks.push({ kind: "err", message: `${label} failed: ${reason}` });
|
|
119
103
|
return null;
|
|
120
104
|
}
|
|
@@ -140,8 +124,6 @@ export async function inspectAdapterCompatibility({
|
|
|
140
124
|
port = 2026,
|
|
141
125
|
fetchImpl = fetch,
|
|
142
126
|
timeoutMs = 120000,
|
|
143
|
-
claudeMode = "inherited",
|
|
144
|
-
claudeModels,
|
|
145
127
|
} = {}) {
|
|
146
128
|
const baseUrl = localGatewayBaseUrl(connectHost(host), port);
|
|
147
129
|
const checks = [];
|
|
@@ -154,9 +136,7 @@ export async function inspectAdapterCompatibility({
|
|
|
154
136
|
});
|
|
155
137
|
if (!models) return checks;
|
|
156
138
|
|
|
157
|
-
const { responsesModel
|
|
158
|
-
claudeModels: claudeMode === "isolated" ? (claudeModels || { data: [] }) : models,
|
|
159
|
-
});
|
|
139
|
+
const { responsesModel } = selectCompatibilityModels(models);
|
|
160
140
|
if (!responsesModel) {
|
|
161
141
|
checks.push({ kind: "err", message: "Compatibility Responses check failed: no GPT model advertises /responses" });
|
|
162
142
|
return checks;
|
|
@@ -201,23 +181,6 @@ export async function inspectAdapterCompatibility({
|
|
|
201
181
|
parseCompactionResponse(text);
|
|
202
182
|
});
|
|
203
183
|
|
|
204
|
-
if (!claudeModel) {
|
|
205
|
-
const reason = claudeMode === "isolated"
|
|
206
|
-
? "the isolated Claude model cache is unavailable or advertises no Claude chat model; restart ccdx to refresh it"
|
|
207
|
-
: "no Claude chat model was advertised";
|
|
208
|
-
checks.push({ kind: "warn", message: `Anthropic stream compatibility was not verified because ${reason}` });
|
|
209
|
-
return checks;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
await runCompatibilityCheck(checks, `Anthropic Messages stream (${claudeModel})`, async () => {
|
|
213
|
-
const text = await compatibilityRequest(fetchImpl, `${baseUrl}/v1/messages`, {
|
|
214
|
-
model: claudeModel,
|
|
215
|
-
max_tokens: 16,
|
|
216
|
-
stream: true,
|
|
217
|
-
messages: [{ role: "user", content: "Reply with OK only." }],
|
|
218
|
-
}, timeoutMs);
|
|
219
|
-
if (!/^event:\s*message_stop\s*$/m.test(text)) throw new Error("stream did not contain message_stop");
|
|
220
|
-
});
|
|
221
184
|
return checks;
|
|
222
185
|
}
|
|
223
186
|
|
|
@@ -236,73 +199,36 @@ export function inspectGitHubToken({ home = os.homedir() } = {}) {
|
|
|
236
199
|
}
|
|
237
200
|
|
|
238
201
|
export function inspectAuthProfiles({ home = os.homedir() } = {}) {
|
|
239
|
-
|
|
202
|
+
return [
|
|
240
203
|
...inspectGitHubToken({ home }),
|
|
241
204
|
{ kind: "ok", message: "Codex authentication profile uses the legacy path" },
|
|
242
205
|
];
|
|
243
|
-
try {
|
|
244
|
-
const claude = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
245
|
-
if (!claude.configured) {
|
|
246
|
-
checks.push({ kind: "ok", message: "Claude authentication profile is not isolated and inherits Codex" });
|
|
247
|
-
} else if (!claude.valid) {
|
|
248
|
-
checks.push({
|
|
249
|
-
kind: "err",
|
|
250
|
-
message: `Claude isolated authentication profile is invalid: ${claude.reason}`,
|
|
251
|
-
fix: "ccdx auth login claude --reauth",
|
|
252
|
-
});
|
|
253
|
-
} else {
|
|
254
|
-
const account = claude.identity?.login ? ` for ${claude.identity.login}` : "";
|
|
255
|
-
checks.push({ kind: "ok", message: `Claude isolated authentication profile is configured${account}` });
|
|
256
|
-
}
|
|
257
|
-
} catch {
|
|
258
|
-
checks.push({ kind: "err", message: "Claude isolated authentication profile could not be inspected" });
|
|
259
|
-
}
|
|
260
|
-
return checks;
|
|
261
206
|
}
|
|
262
207
|
|
|
263
208
|
function checkedDoctorProfile(profile = AUTH_PROFILE_CODEX) {
|
|
264
209
|
const value = String(profile || AUTH_PROFILE_CODEX).trim().toLowerCase();
|
|
265
|
-
if (
|
|
266
|
-
throw new Error(`Doctor profile must be codex
|
|
210
|
+
if (value !== AUTH_PROFILE_CODEX) {
|
|
211
|
+
throw new Error(`Doctor profile must be codex: ${profile}`);
|
|
267
212
|
}
|
|
268
213
|
return value;
|
|
269
214
|
}
|
|
270
215
|
|
|
271
|
-
function onlineMessage(profile, message, inherited = false) {
|
|
272
|
-
if (profile === AUTH_PROFILE_CODEX) return message;
|
|
273
|
-
return `${inherited ? "Claude inherited Codex" : "Claude profile"}: ${message}`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
216
|
export async function inspectGitHubTokenOnline({
|
|
277
217
|
home = os.homedir(),
|
|
278
218
|
profile = AUTH_PROFILE_CODEX,
|
|
279
219
|
fetchImpl = fetch,
|
|
280
220
|
timeoutMs = 10000,
|
|
281
221
|
} = {}) {
|
|
282
|
-
|
|
283
|
-
if (requestedProfile === "all") throw new Error("inspectGitHubTokenOnline accepts one profile at a time");
|
|
284
|
-
let inherited = false;
|
|
222
|
+
checkedDoctorProfile(profile);
|
|
285
223
|
let credentials;
|
|
286
224
|
try {
|
|
287
|
-
credentials = readAuthProfileCredentials(
|
|
288
|
-
if (requestedProfile === AUTH_PROFILE_CLAUDE && !credentials.configured) {
|
|
289
|
-
inherited = true;
|
|
290
|
-
credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
291
|
-
}
|
|
225
|
+
credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
292
226
|
} catch {
|
|
293
|
-
return [{ kind: "warn", message:
|
|
227
|
+
return [{ kind: "warn", message: "Online Copilot check failed while reading the local credential" }];
|
|
294
228
|
}
|
|
295
229
|
|
|
296
|
-
const checks = [];
|
|
297
|
-
if (inherited) {
|
|
298
|
-
checks.push({ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" });
|
|
299
|
-
}
|
|
300
230
|
if (!credentials.valid) {
|
|
301
|
-
|
|
302
|
-
? "the GitHub token is missing"
|
|
303
|
-
: credentials.reason;
|
|
304
|
-
checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check skipped because ${reason}`, inherited) });
|
|
305
|
-
return checks;
|
|
231
|
+
return [{ kind: "warn", message: "Online Copilot check skipped because the GitHub token is missing" }];
|
|
306
232
|
}
|
|
307
233
|
|
|
308
234
|
const controller = new AbortController();
|
|
@@ -314,11 +240,10 @@ export async function inspectGitHubTokenOnline({
|
|
|
314
240
|
});
|
|
315
241
|
if (!validation.ok) {
|
|
316
242
|
const statusText = validation.status ? ` (HTTP ${validation.status})` : "";
|
|
317
|
-
|
|
243
|
+
return [{
|
|
318
244
|
kind: validation.transient ? "warn" : "err",
|
|
319
|
-
message:
|
|
320
|
-
}
|
|
321
|
-
return checks;
|
|
245
|
+
message: `GitHub Copilot authentication failed: ${validation.reason}${statusText}`,
|
|
246
|
+
}];
|
|
322
247
|
}
|
|
323
248
|
|
|
324
249
|
const tokenData = validation.copilotTokenData;
|
|
@@ -331,64 +256,35 @@ export async function inspectGitHubTokenOnline({
|
|
|
331
256
|
});
|
|
332
257
|
const modelResp = await fetchImpl(`${apiBase}/models`, { headers, signal: controller.signal });
|
|
333
258
|
if (!modelResp.ok) {
|
|
334
|
-
|
|
335
|
-
{ kind: "ok", message:
|
|
336
|
-
{ kind: "err", message:
|
|
337
|
-
|
|
338
|
-
return checks;
|
|
259
|
+
return [
|
|
260
|
+
{ kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
|
|
261
|
+
{ kind: "err", message: `Copilot models endpoint returned HTTP ${modelResp.status}` },
|
|
262
|
+
];
|
|
339
263
|
}
|
|
340
264
|
const models = await modelResp.json();
|
|
341
265
|
const data = Array.isArray(models) ? models : models?.data;
|
|
342
|
-
|
|
343
|
-
{ kind: "ok", message:
|
|
344
|
-
{ kind: "ok", message:
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
const reason = e?.name === "AbortError"
|
|
266
|
+
return [
|
|
267
|
+
{ kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}` },
|
|
268
|
+
{ kind: "ok", message: `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models` },
|
|
269
|
+
];
|
|
270
|
+
} catch (error) {
|
|
271
|
+
const errorCode = error?.cause?.code || error?.code || "";
|
|
272
|
+
const reason = error?.name === "AbortError"
|
|
350
273
|
? `timed out after ${timeoutMs}ms`
|
|
351
274
|
: `request failed${errorCode ? ` (${errorCode})` : ""}`;
|
|
352
|
-
|
|
353
|
-
return checks;
|
|
275
|
+
return [{ kind: "warn", message: `Online Copilot check failed: ${reason}` }];
|
|
354
276
|
} finally {
|
|
355
277
|
clearTimeout(timer);
|
|
356
278
|
}
|
|
357
279
|
}
|
|
358
280
|
|
|
359
|
-
export async function inspectAuthProfilesOnline({
|
|
360
|
-
|
|
361
|
-
profile = AUTH_PROFILE_CODEX,
|
|
362
|
-
fetchImpl = fetch,
|
|
363
|
-
timeoutMs = 10000,
|
|
364
|
-
} = {}) {
|
|
365
|
-
const selected = checkedDoctorProfile(profile);
|
|
366
|
-
if (selected !== "all") {
|
|
367
|
-
return inspectGitHubTokenOnline({ home, profile: selected, fetchImpl, timeoutMs });
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
let claudeConfigured = false;
|
|
371
|
-
let claudeReadFailed = false;
|
|
372
|
-
try {
|
|
373
|
-
claudeConfigured = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home }).configured;
|
|
374
|
-
} catch {
|
|
375
|
-
claudeReadFailed = true;
|
|
376
|
-
}
|
|
377
|
-
const [codexChecks, claudeChecks] = await Promise.all([
|
|
378
|
-
inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CODEX, fetchImpl, timeoutMs }),
|
|
379
|
-
claudeReadFailed
|
|
380
|
-
? Promise.resolve([{ kind: "err", message: "Claude isolated authentication profile could not be inspected" }])
|
|
381
|
-
: claudeConfigured
|
|
382
|
-
? inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CLAUDE, fetchImpl, timeoutMs })
|
|
383
|
-
: Promise.resolve([{ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" }]),
|
|
384
|
-
]);
|
|
385
|
-
return [...codexChecks, ...claudeChecks];
|
|
281
|
+
export async function inspectAuthProfilesOnline(options = {}) {
|
|
282
|
+
return inspectGitHubTokenOnline(options);
|
|
386
283
|
}
|
|
387
284
|
|
|
388
285
|
export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
|
|
389
286
|
const filePath = path.join(home, ".codex", "config.toml");
|
|
390
|
-
const
|
|
391
|
-
const expectedBaseUrl = `${expectedAnthropicBaseUrl}/v1`;
|
|
287
|
+
const expectedBaseUrl = `${adapterBaseUrl(host, port)}/v1`;
|
|
392
288
|
const config = readText(filePath);
|
|
393
289
|
if (!config.ok) {
|
|
394
290
|
return [{ kind: "warn", message: `Codex config not found at ${displayPath(home, filePath)}` }];
|
|
@@ -403,84 +299,12 @@ export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", po
|
|
|
403
299
|
const missing = [];
|
|
404
300
|
if (tomlString(config.text, "OPENAI_BASE_URL") !== expectedBaseUrl) missing.push("OPENAI_BASE_URL");
|
|
405
301
|
if (tomlString(config.text, "OPENAI_API_KEY") !== "dummy") missing.push("OPENAI_API_KEY");
|
|
406
|
-
if (tomlString(config.text, "ANTHROPIC_BASE_URL") !== expectedAnthropicBaseUrl) missing.push("ANTHROPIC_BASE_URL");
|
|
407
|
-
if (tomlString(config.text, "ANTHROPIC_AUTH_TOKEN") !== "dummy") missing.push("ANTHROPIC_AUTH_TOKEN");
|
|
408
302
|
checks.push(missing.length === 0
|
|
409
303
|
? { kind: "ok", message: "Codex shell env local API keys are configured" }
|
|
410
304
|
: { kind: "warn", message: `Codex shell env local API keys need update: ${missing.join(", ")}` });
|
|
411
305
|
return checks;
|
|
412
306
|
}
|
|
413
307
|
|
|
414
|
-
export function inspectClaudeCodeConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
|
|
415
|
-
const filePath = path.join(home, ".claude", "settings.json");
|
|
416
|
-
const expectedBaseUrl = adapterBaseUrl(host, port);
|
|
417
|
-
const settings = readJson(filePath);
|
|
418
|
-
if (!settings.ok) {
|
|
419
|
-
const reason = settings.parseError ? `could not parse: ${settings.error.message}` : `not found at ${displayPath(home, filePath)}`;
|
|
420
|
-
return [{ kind: settings.parseError ? "err" : "warn", message: `Claude Code settings ${reason}` }];
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const env = settings.json?.env || {};
|
|
424
|
-
const missing = [];
|
|
425
|
-
if (env.ANTHROPIC_BASE_URL !== expectedBaseUrl) missing.push("ANTHROPIC_BASE_URL");
|
|
426
|
-
if (env.ANTHROPIC_AUTH_TOKEN !== "dummy") missing.push("ANTHROPIC_AUTH_TOKEN");
|
|
427
|
-
return [missing.length === 0
|
|
428
|
-
? { kind: "ok", message: `Claude Code points to ${expectedBaseUrl}` }
|
|
429
|
-
: { kind: "warn", message: `Claude Code settings need update: ${missing.join(", ")}` }];
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export function inspectClaudeAppConfig({
|
|
433
|
-
home = os.homedir(),
|
|
434
|
-
platform = process.platform,
|
|
435
|
-
env = process.env,
|
|
436
|
-
host = "127.0.0.1",
|
|
437
|
-
port = 2026,
|
|
438
|
-
} = {}) {
|
|
439
|
-
const paths = claudeDesktopPaths(home, platform, env);
|
|
440
|
-
const expectedBaseUrl = localGatewayBaseUrl(host, port);
|
|
441
|
-
const checks = [];
|
|
442
|
-
|
|
443
|
-
const normalConfig = readJson(paths.normalConfigPath);
|
|
444
|
-
const threepConfig = readJson(paths.threepConfigPath);
|
|
445
|
-
if (normalConfig.ok && threepConfig.ok
|
|
446
|
-
&& normalConfig.json?.deploymentMode === "3p"
|
|
447
|
-
&& threepConfig.json?.deploymentMode === "3p") {
|
|
448
|
-
checks.push({ kind: "ok", message: "Claude App deployment mode is 3p" });
|
|
449
|
-
} else {
|
|
450
|
-
checks.push({ kind: "warn", message: "Claude App deployment mode is not fully configured for 3p" });
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const meta = readJson(paths.metaPath);
|
|
454
|
-
const appliedId = meta.ok ? String(meta.json?.appliedId || "").trim() : "";
|
|
455
|
-
if (!appliedId) {
|
|
456
|
-
checks.push({
|
|
457
|
-
kind: "warn",
|
|
458
|
-
message: "Claude App gateway profile is not configured",
|
|
459
|
-
fix: "ccdx start --configure-claude-app",
|
|
460
|
-
});
|
|
461
|
-
return checks;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const profilePath = path.join(paths.configLibraryPath, `${appliedId}.json`);
|
|
465
|
-
const profile = readJson(profilePath);
|
|
466
|
-
if (!profile.ok) {
|
|
467
|
-
checks.push({ kind: "warn", message: `Claude App active gateway profile not found at ${displayPath(home, profilePath)}` });
|
|
468
|
-
return checks;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
const p = profile.json || {};
|
|
472
|
-
const missing = [];
|
|
473
|
-
if (p.inferenceProvider !== "gateway") missing.push("inferenceProvider");
|
|
474
|
-
if (p.inferenceGatewayBaseUrl !== expectedBaseUrl) missing.push(`inferenceGatewayBaseUrl expected "${expectedBaseUrl}"`);
|
|
475
|
-
if (p.inferenceGatewayAuthScheme !== "bearer") missing.push("inferenceGatewayAuthScheme");
|
|
476
|
-
if (!String(p.inferenceGatewayApiKey || "").trim()) missing.push("inferenceGatewayApiKey");
|
|
477
|
-
if (!String(p.inferenceModels || "").trim()) missing.push("inferenceModels");
|
|
478
|
-
checks.push(missing.length === 0
|
|
479
|
-
? { kind: "ok", message: `Claude App gateway profile points to ${expectedBaseUrl}` }
|
|
480
|
-
: { kind: "warn", message: `Claude App gateway profile needs update: ${missing.join(", ")}` });
|
|
481
|
-
return checks;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
308
|
function connectHost(host) {
|
|
485
309
|
const normalized = String(host || "127.0.0.1").replace(/^\[(.*)\]$/, "$1");
|
|
486
310
|
if (normalized === "0.0.0.0") return "127.0.0.1";
|
|
@@ -505,12 +329,9 @@ export function checkAdapterListening({ host = "127.0.0.1", port = 2026, timeout
|
|
|
505
329
|
|
|
506
330
|
export async function collectDoctorChecks({
|
|
507
331
|
home = os.homedir(),
|
|
508
|
-
platform = process.platform,
|
|
509
|
-
env = process.env,
|
|
510
332
|
host = "127.0.0.1",
|
|
511
333
|
port = 2026,
|
|
512
334
|
checkAdapter = true,
|
|
513
|
-
checkPmStudio = checkAdapter,
|
|
514
335
|
checkAdapterListeningFn = checkAdapterListening,
|
|
515
336
|
checkRunningAdapterFn = checkRunningAdapter,
|
|
516
337
|
online = false,
|
|
@@ -520,13 +341,11 @@ export async function collectDoctorChecks({
|
|
|
520
341
|
onlineTimeoutMs = 10000,
|
|
521
342
|
compatTimeoutMs = 120000,
|
|
522
343
|
inspectAdapterCompatibilityFn = inspectAdapterCompatibility,
|
|
523
|
-
inspectPmStudioHealthFn,
|
|
524
344
|
} = {}) {
|
|
345
|
+
checkedDoctorProfile(profile);
|
|
525
346
|
const checks = [
|
|
526
347
|
...inspectAuthProfiles({ home }),
|
|
527
348
|
...inspectCodexConfig({ home, host, port }),
|
|
528
|
-
...inspectClaudeCodeConfig({ home, host, port }),
|
|
529
|
-
...inspectClaudeAppConfig({ home, platform, env, host, port }),
|
|
530
349
|
];
|
|
531
350
|
|
|
532
351
|
if (online) {
|
|
@@ -564,80 +383,15 @@ export async function collectDoctorChecks({
|
|
|
564
383
|
}
|
|
565
384
|
}
|
|
566
385
|
|
|
567
|
-
if (checkPmStudio && platform === "darwin") {
|
|
568
|
-
const inspect = inspectPmStudioHealthFn || (async (options) => {
|
|
569
|
-
const module = await import("./pm-studio-status.mjs");
|
|
570
|
-
return module.inspectPmStudioStatus(options);
|
|
571
|
-
});
|
|
572
|
-
const pm = await inspect({ home });
|
|
573
|
-
const version = pm.app.metadata ? `${pm.app.metadata.version} build ${pm.app.metadata.build}` : "";
|
|
574
|
-
if (pm.app.state === "patched") {
|
|
575
|
-
checks.push({ kind: "ok", message: `PM Studio ${version} patch is verified` });
|
|
576
|
-
if (!pm.claude.valid) {
|
|
577
|
-
checks.push({
|
|
578
|
-
kind: "warn",
|
|
579
|
-
message: "PM Studio Claude routing requires a valid isolated Claude profile",
|
|
580
|
-
fix: "ccdx auth login claude --reauth",
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
if (!pm.adapter?.ok) {
|
|
584
|
-
checks.push({ kind: "warn", message: "PM Studio relay is not currently available", fix: "ccdx start" });
|
|
585
|
-
} else if (!pm.runtime?.ok) {
|
|
586
|
-
checks.push({
|
|
587
|
-
kind: "warn",
|
|
588
|
-
message: "PM Studio relay is running, but its isolated routing is not ready",
|
|
589
|
-
fix: "stop the running adapter, then run ccdx start",
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
} else if (pm.app.state === "predecessor") {
|
|
593
|
-
checks.push({
|
|
594
|
-
kind: "warn",
|
|
595
|
-
message: `PM Studio ${version} has a verified predecessor split patch`,
|
|
596
|
-
fix: "ccdx pms setup",
|
|
597
|
-
});
|
|
598
|
-
} else if (pm.app.state === "clean") {
|
|
599
|
-
checks.push({
|
|
600
|
-
kind: "warn",
|
|
601
|
-
message: `PM Studio ${version} has a compatible local patch structure but is not patched`,
|
|
602
|
-
fix: "ccdx pms setup",
|
|
603
|
-
});
|
|
604
|
-
} else if (pm.app.state === "unsupported") {
|
|
605
|
-
checks.push({ kind: "warn", message: `PM Studio ${version} does not expose one uniquely compatible patch structure; no files will be changed` });
|
|
606
|
-
} else if (pm.app.state === "drift" || pm.app.state === "error") {
|
|
607
|
-
checks.push({ kind: "err", message: `PM Studio integrity check failed: ${pm.app.issues.join("; ")}` });
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
386
|
if (compat) {
|
|
612
387
|
if (!running?.ok) {
|
|
613
388
|
checks.push({ kind: "err", message: "Compatibility checks require a running, version-compatible codex-copilot-dx adapter" });
|
|
614
389
|
} else {
|
|
615
|
-
let claudeMode = "inherited";
|
|
616
|
-
let claudeModels;
|
|
617
|
-
try {
|
|
618
|
-
const claudeCredentials = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
619
|
-
if (claudeCredentials.configured) {
|
|
620
|
-
claudeMode = "isolated";
|
|
621
|
-
if (claudeCredentials.valid) {
|
|
622
|
-
claudeModels = loadModelCache({
|
|
623
|
-
home,
|
|
624
|
-
profile: AUTH_PROFILE_CLAUDE,
|
|
625
|
-
credentialFingerprint: claudeCredentials.metadata?.token_fingerprint,
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
} catch {
|
|
630
|
-
// A configured-but-unreadable optional profile must not borrow the
|
|
631
|
-
// Codex catalog. Compatibility will report the Claude path unverified.
|
|
632
|
-
claudeMode = "isolated";
|
|
633
|
-
}
|
|
634
390
|
checks.push(...await inspectAdapterCompatibilityFn({
|
|
635
391
|
host,
|
|
636
392
|
port,
|
|
637
393
|
fetchImpl,
|
|
638
394
|
timeoutMs: compatTimeoutMs,
|
|
639
|
-
claudeMode,
|
|
640
|
-
claudeModels,
|
|
641
395
|
}));
|
|
642
396
|
}
|
|
643
397
|
}
|
|
@@ -647,11 +401,10 @@ export async function collectDoctorChecks({
|
|
|
647
401
|
|
|
648
402
|
export async function runDoctor(options = {}) {
|
|
649
403
|
const log = options.log || console.log;
|
|
650
|
-
|
|
404
|
+
checkedDoctorProfile(options.profile);
|
|
651
405
|
const flags = [
|
|
652
406
|
options.online ? "--online" : "",
|
|
653
407
|
options.compat ? "--compat" : "",
|
|
654
|
-
profile !== AUTH_PROFILE_CODEX ? `--profile ${profile}` : "",
|
|
655
408
|
].filter(Boolean);
|
|
656
409
|
log(`${options.commandName || "ccdx"} doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
|
|
657
410
|
const checks = await collectDoctorChecks(options);
|
package/src/model-cache.mjs
CHANGED
|
@@ -4,14 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
export const MODEL_CACHE_SOFT_TTL_MS = 2 * 60 * 60 * 1000;
|
|
6
6
|
export const MODEL_CACHE_HARD_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
7
|
-
const MODEL_CACHE_PROFILES = new Set(["codex", "claude"]);
|
|
8
7
|
|
|
9
|
-
export function modelCachePath(home = os.homedir()
|
|
10
|
-
|
|
11
|
-
const root = path.join(home, ".local", "share", "codex-copilot-dx");
|
|
12
|
-
return profile === "codex"
|
|
13
|
-
? path.join(root, "models.json")
|
|
14
|
-
: path.join(root, "profiles", profile, "models.json");
|
|
8
|
+
export function modelCachePath(home = os.homedir()) {
|
|
9
|
+
return path.join(home, ".local", "share", "codex-copilot-dx", "models.json");
|
|
15
10
|
}
|
|
16
11
|
|
|
17
12
|
function modelData(models) {
|
|
@@ -30,7 +25,6 @@ function normalizedTtl(value, fallback) {
|
|
|
30
25
|
|
|
31
26
|
export function loadModelCacheEntry({
|
|
32
27
|
home = os.homedir(),
|
|
33
|
-
profile = "codex",
|
|
34
28
|
credentialFingerprint = "",
|
|
35
29
|
softTtlMs = MODEL_CACHE_SOFT_TTL_MS,
|
|
36
30
|
hardTtlMs,
|
|
@@ -38,7 +32,7 @@ export function loadModelCacheEntry({
|
|
|
38
32
|
now = Date.now,
|
|
39
33
|
} = {}) {
|
|
40
34
|
try {
|
|
41
|
-
const parsed = JSON.parse(fs.readFileSync(modelCachePath(home
|
|
35
|
+
const parsed = JSON.parse(fs.readFileSync(modelCachePath(home), "utf8"));
|
|
42
36
|
const savedAt = Date.parse(parsed.saved_at);
|
|
43
37
|
if (!Number.isFinite(savedAt)) return null;
|
|
44
38
|
const expectedFingerprint = String(credentialFingerprint || "").trim();
|
|
@@ -71,14 +65,13 @@ export function loadModelCache(options = {}) {
|
|
|
71
65
|
|
|
72
66
|
export function saveModelCache(models, {
|
|
73
67
|
home = os.homedir(),
|
|
74
|
-
profile = "codex",
|
|
75
68
|
credentialFingerprint = "",
|
|
76
69
|
isCurrent = () => true,
|
|
77
70
|
now = Date.now,
|
|
78
71
|
} = {}) {
|
|
79
72
|
if (!isValidModelList(models)) return false;
|
|
80
73
|
if (typeof isCurrent === "function" && !isCurrent()) return false;
|
|
81
|
-
const filePath = modelCachePath(home
|
|
74
|
+
const filePath = modelCachePath(home);
|
|
82
75
|
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
83
76
|
const payload = { saved_at: new Date(now()).toISOString(), models };
|
|
84
77
|
const fingerprint = String(credentialFingerprint || "").trim();
|