codex-copilot-dx 0.6.0 → 0.6.2
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 +100 -8
- package/bin/cli.mjs +23 -4
- package/package.json +1 -1
- package/scripts/benchmark.mjs +34 -0
- package/src/adapter.mjs +16 -0
- package/src/auth.mjs +5 -0
- package/src/cli-auth.mjs +43 -12
- package/src/cli-options.mjs +143 -47
- package/src/cli-status.mjs +35 -2
- package/src/doctor.mjs +56 -3
- package/src/observability.mjs +10 -4
- package/src/pm-studio-asar.mjs +392 -0
- package/src/pm-studio-relay.mjs +523 -0
- package/src/pm-studio-setup.mjs +734 -0
- package/src/pm-studio-status.mjs +130 -0
- package/src/profile-model-runtime.mjs +3 -2
- package/src/profile-routing.mjs +74 -0
- package/src/profile-runtime.mjs +13 -0
- package/src/running-adapter.mjs +16 -0
package/src/cli-options.mjs
CHANGED
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
const HELP_COMMANDS = new Set(["help", "--help", "-h"]);
|
|
2
2
|
const VERSION_COMMANDS = new Set(["version", "--version", "-v"]);
|
|
3
|
-
const START_OPTIONS = new
|
|
3
|
+
const START_OPTIONS = new Map([
|
|
4
|
+
["--configure-claude-desktop", "configure-claude-app"],
|
|
5
|
+
["--configure-claude-app", "configure-claude-app"],
|
|
6
|
+
["--show-request-id", "show-request-id"],
|
|
7
|
+
]);
|
|
4
8
|
const CLI_COMMANDS = new Set(["ccdx", "codex-copilot-dx"]);
|
|
9
|
+
const PM_COMMANDS = new Set(["pms", "pm-studio"]);
|
|
10
|
+
const HELP_TOPICS = new Set([
|
|
11
|
+
"",
|
|
12
|
+
"start",
|
|
13
|
+
"auth",
|
|
14
|
+
"auth status",
|
|
15
|
+
"auth login",
|
|
16
|
+
"doctor",
|
|
17
|
+
"status",
|
|
18
|
+
"models",
|
|
19
|
+
"pms",
|
|
20
|
+
"pms setup",
|
|
21
|
+
"pms status",
|
|
22
|
+
"usage",
|
|
23
|
+
"auto-review-model",
|
|
24
|
+
"update",
|
|
25
|
+
"version",
|
|
26
|
+
]);
|
|
5
27
|
|
|
6
28
|
function unexpectedArgs(args) {
|
|
7
29
|
throw new Error(`Unexpected argument${args.length === 1 ? "" : "s"}: ${args.join(" ")}`);
|
|
@@ -39,33 +61,97 @@ function checkedProfile(value, { allowAll = false } = {}) {
|
|
|
39
61
|
return profile;
|
|
40
62
|
}
|
|
41
63
|
|
|
64
|
+
function baseCommand(command, extra = {}) {
|
|
65
|
+
return {
|
|
66
|
+
command,
|
|
67
|
+
configureClaudeDesktop: false,
|
|
68
|
+
showRequestId: false,
|
|
69
|
+
online: false,
|
|
70
|
+
compat: false,
|
|
71
|
+
...extra,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkedHelpTopic(parts = []) {
|
|
76
|
+
const normalized = parts.map((part, index) => {
|
|
77
|
+
if (index === 0 && part === "pm-studio") return "pms";
|
|
78
|
+
return part;
|
|
79
|
+
}).join(" ");
|
|
80
|
+
if (!HELP_TOPICS.has(normalized)) throw new Error(`Unknown help topic: ${parts.join(" ")}`);
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function helpCommand(parts = []) {
|
|
85
|
+
return baseCommand("help", { helpTopic: checkedHelpTopic(parts) });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nestedHelp(command, rest) {
|
|
89
|
+
if (!rest.length || !HELP_COMMANDS.has(rest.at(-1))) return null;
|
|
90
|
+
const args = rest.slice(0, -1);
|
|
91
|
+
if (PM_COMMANDS.has(command)) {
|
|
92
|
+
if (args.length > 1) unexpectedArgs(args.slice(1));
|
|
93
|
+
return helpCommand(["pms", ...args]);
|
|
94
|
+
}
|
|
95
|
+
if (command === "auth") {
|
|
96
|
+
if (args[0] === "login" && args[1] === "claude" && args.length === 2) {
|
|
97
|
+
return helpCommand(["auth", "login"]);
|
|
98
|
+
}
|
|
99
|
+
if (args.length > 1) unexpectedArgs(args.slice(1));
|
|
100
|
+
return helpCommand(["auth", ...args]);
|
|
101
|
+
}
|
|
102
|
+
if (args.length) unexpectedArgs(args);
|
|
103
|
+
return helpCommand([command]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseStartOptions(args) {
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const option of args) {
|
|
109
|
+
const kind = START_OPTIONS.get(option);
|
|
110
|
+
if (!kind || seen.has(kind)) unexpectedArgs([option]);
|
|
111
|
+
seen.add(kind);
|
|
112
|
+
}
|
|
113
|
+
return baseCommand("start", {
|
|
114
|
+
configureClaudeDesktop: seen.has("configure-claude-app"),
|
|
115
|
+
showRequestId: seen.has("show-request-id"),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
42
119
|
export function parseCliArgs(args = []) {
|
|
43
120
|
const [command, ...rest] = args;
|
|
44
|
-
if (!command) return
|
|
121
|
+
if (!command) return baseCommand("start");
|
|
45
122
|
if (HELP_COMMANDS.has(command)) {
|
|
46
|
-
|
|
47
|
-
|
|
123
|
+
const topic = rest[0] === "auth" && rest[1] === "login" && rest[2] === "claude"
|
|
124
|
+
? ["auth", "login"]
|
|
125
|
+
: rest;
|
|
126
|
+
if (topic.length > 2) unexpectedArgs(topic.slice(2));
|
|
127
|
+
return helpCommand(topic);
|
|
48
128
|
}
|
|
129
|
+
const requestedHelp = nestedHelp(command, rest);
|
|
130
|
+
if (requestedHelp) return requestedHelp;
|
|
49
131
|
if (VERSION_COMMANDS.has(command)) {
|
|
50
132
|
if (rest.length) unexpectedArgs(rest);
|
|
51
|
-
return
|
|
133
|
+
return baseCommand("version");
|
|
52
134
|
}
|
|
135
|
+
if (command === "start") return parseStartOptions(rest);
|
|
53
136
|
if (command === "status" || command === "--status") {
|
|
54
137
|
if (rest.length) unexpectedArgs(rest);
|
|
55
|
-
return
|
|
138
|
+
return baseCommand("status");
|
|
56
139
|
}
|
|
57
140
|
if (command === "usage") {
|
|
58
141
|
if (rest.length) unexpectedArgs(rest);
|
|
59
|
-
return
|
|
142
|
+
return baseCommand("usage");
|
|
143
|
+
}
|
|
144
|
+
if (PM_COMMANDS.has(command)) {
|
|
145
|
+
const [action, ...pmsArgs] = rest;
|
|
146
|
+
if (!action) throw new Error("Missing pms action: expected setup or status");
|
|
147
|
+
if (!new Set(["setup", "status"]).has(action)) throw new Error(`Unknown pms action: ${action}`);
|
|
148
|
+
if (pmsArgs.length) unexpectedArgs(pmsArgs);
|
|
149
|
+
return baseCommand("pms", { action });
|
|
60
150
|
}
|
|
61
151
|
if (command === "models") {
|
|
62
152
|
const options = parseOptions(rest, new Map([["--profile", "value"]]));
|
|
63
153
|
return {
|
|
64
|
-
|
|
65
|
-
configureClaudeDesktop: false,
|
|
66
|
-
showRequestId: false,
|
|
67
|
-
online: false,
|
|
68
|
-
compat: false,
|
|
154
|
+
...baseCommand("models"),
|
|
69
155
|
profile: checkedProfile(options["--profile"]),
|
|
70
156
|
};
|
|
71
157
|
}
|
|
@@ -75,13 +161,10 @@ export function parseCliArgs(args = []) {
|
|
|
75
161
|
if (action === "status") {
|
|
76
162
|
const options = parseOptions(authArgs, new Map([["--online", "flag"]]));
|
|
77
163
|
return {
|
|
78
|
-
|
|
164
|
+
...baseCommand("auth"),
|
|
79
165
|
action: "status",
|
|
80
166
|
profile: "",
|
|
81
|
-
configureClaudeDesktop: false,
|
|
82
|
-
showRequestId: false,
|
|
83
167
|
online: Boolean(options["--online"]),
|
|
84
|
-
compat: false,
|
|
85
168
|
expectedLogin: "",
|
|
86
169
|
reauth: false,
|
|
87
170
|
};
|
|
@@ -95,13 +178,9 @@ export function parseCliArgs(args = []) {
|
|
|
95
178
|
["--reauth", "flag"],
|
|
96
179
|
]));
|
|
97
180
|
return {
|
|
98
|
-
|
|
181
|
+
...baseCommand("auth"),
|
|
99
182
|
action: "login",
|
|
100
183
|
profile: "claude",
|
|
101
|
-
configureClaudeDesktop: false,
|
|
102
|
-
showRequestId: false,
|
|
103
|
-
online: false,
|
|
104
|
-
compat: false,
|
|
105
184
|
expectedLogin: options["--github-login"] || "",
|
|
106
185
|
reauth: Boolean(options["--reauth"]),
|
|
107
186
|
};
|
|
@@ -110,7 +189,7 @@ export function parseCliArgs(args = []) {
|
|
|
110
189
|
}
|
|
111
190
|
if (command === "auto-review-model") {
|
|
112
191
|
if (rest.length) unexpectedArgs(rest);
|
|
113
|
-
return
|
|
192
|
+
return baseCommand("auto-review-model");
|
|
114
193
|
}
|
|
115
194
|
if (command === "update") {
|
|
116
195
|
if (rest.length > 1) unexpectedArgs(rest.slice(1));
|
|
@@ -119,11 +198,7 @@ export function parseCliArgs(args = []) {
|
|
|
119
198
|
throw new Error(`Update source must be npm or github: ${source}`);
|
|
120
199
|
}
|
|
121
200
|
return {
|
|
122
|
-
|
|
123
|
-
configureClaudeDesktop: false,
|
|
124
|
-
showRequestId: false,
|
|
125
|
-
online: false,
|
|
126
|
-
compat: false,
|
|
201
|
+
...baseCommand("update"),
|
|
127
202
|
updateSource: source === "gh" ? "github" : source,
|
|
128
203
|
};
|
|
129
204
|
}
|
|
@@ -134,29 +209,13 @@ export function parseCliArgs(args = []) {
|
|
|
134
209
|
["--profile", "value"],
|
|
135
210
|
]));
|
|
136
211
|
return {
|
|
137
|
-
|
|
138
|
-
configureClaudeDesktop: false,
|
|
139
|
-
showRequestId: false,
|
|
212
|
+
...baseCommand("doctor"),
|
|
140
213
|
online: Boolean(options["--online"]),
|
|
141
214
|
compat: Boolean(options["--compat"]),
|
|
142
215
|
profile: checkedProfile(options["--profile"], { allowAll: true }),
|
|
143
216
|
};
|
|
144
217
|
}
|
|
145
|
-
if (START_OPTIONS.has(command))
|
|
146
|
-
const options = [command, ...rest];
|
|
147
|
-
const invalid = options.filter((arg) => !START_OPTIONS.has(arg));
|
|
148
|
-
if (invalid.length) unexpectedArgs(invalid);
|
|
149
|
-
for (const option of START_OPTIONS) {
|
|
150
|
-
if (options.filter((arg) => arg === option).length > 1) unexpectedArgs([option]);
|
|
151
|
-
}
|
|
152
|
-
return {
|
|
153
|
-
command: "start",
|
|
154
|
-
configureClaudeDesktop: options.includes("--configure-claude-desktop"),
|
|
155
|
-
showRequestId: options.includes("--show-request-id"),
|
|
156
|
-
online: false,
|
|
157
|
-
compat: false,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
218
|
+
if (START_OPTIONS.has(command)) return parseStartOptions([command, ...rest]);
|
|
160
219
|
throw new Error(`Unknown command or option: ${command}`);
|
|
161
220
|
}
|
|
162
221
|
|
|
@@ -200,21 +259,58 @@ export function cliCommandName(executable = process.argv[1]) {
|
|
|
200
259
|
return CLI_COMMANDS.has(name) ? name : "ccdx";
|
|
201
260
|
}
|
|
202
261
|
|
|
203
|
-
|
|
262
|
+
function topicHelp(name, topic) {
|
|
263
|
+
const sections = {
|
|
264
|
+
start: `Usage:\n ${name} [start] [--configure-claude-app] [--show-request-id]\n\nStarts or reuses the local adapter, updates Codex and Claude Code configuration, and opens Codex.\n--configure-claude-app also creates or updates the managed Claude App gateway profile.`,
|
|
265
|
+
auth: `Usage:\n ${name} auth status [--online]\n ${name} auth login claude [--github-login <login>] [--reauth]\n\nShows the two account profiles or configures the isolated Claude account. Device login starts only when no reusable local Copilot credential is available or --reauth is used.`,
|
|
266
|
+
"auth status": `Usage:\n ${name} auth status [--online]\n\nShows account routing without exposing credentials. --online also verifies both configured Copilot entitlements and model catalogs.`,
|
|
267
|
+
"auth login": `Usage:\n ${name} auth login claude [--github-login <login>] [--reauth]\n\nReuses a compatible local Copilot credential when possible. --reauth forces GitHub Device Flow.`,
|
|
268
|
+
doctor: `Usage:\n ${name} doctor [--online] [--compat] [--profile codex|claude|all]\n\nChecks local credentials, client configuration, PM Studio state, and the adapter. --online validates account entitlement; --compat sends minimal inference requests and consumes a small amount of Copilot usage.`,
|
|
269
|
+
status: `Usage:\n ${name} status\n\nShows bounded runtime, routing, performance, cache, queue, and PM relay metrics from a running adapter.`,
|
|
270
|
+
models: `Usage:\n ${name} models [--profile codex|claude]\n\nPerforms a fresh, read-only Copilot model-directory lookup for one saved profile.`,
|
|
271
|
+
pms: `Usage:\n ${name} pms status\n ${name} pms setup\n ${name} pm-studio status\n ${name} pm-studio setup\n\nInspects or installs the version-locked PM Studio loopback patch. setup modifies the installed app only after all preflight checks pass.`,
|
|
272
|
+
"pms setup": `Usage:\n ${name} pms setup\n\nBacks up, patches, verifies, and ad-hoc signs an exactly supported PM Studio bundle. Unknown versions and drift are never modified.`,
|
|
273
|
+
"pms status": `Usage:\n ${name} pms status\n\nRead-only inspection of the installed PM Studio version, patch integrity, Claude profile, and relay availability.`,
|
|
274
|
+
usage: `Usage:\n ${name} usage\n\nSummarizes local token usage metadata without reading prompt or completion content.`,
|
|
275
|
+
"auto-review-model": `Usage:\n ${name} auto-review-model\n\nInteractively selects an enabled Responses model for Codex Auto-review.`,
|
|
276
|
+
update: `Usage:\n ${name} update [npm|github]\n\nUpdates the global package from the configured npm registry or GitHub main. With no source, an interactive terminal prompts for one.`,
|
|
277
|
+
version: `Usage:\n ${name} --version`,
|
|
278
|
+
};
|
|
279
|
+
return sections[topic];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function cliHelp(commandName = "ccdx", topic = "") {
|
|
204
283
|
const name = CLI_COMMANDS.has(commandName) ? commandName : "ccdx";
|
|
205
284
|
const alias = name === "ccdx" ? "codex-copilot-dx" : "ccdx";
|
|
285
|
+
const normalizedTopic = checkedHelpTopic(topic ? topic.split(" ") : []);
|
|
286
|
+
if (normalizedTopic) return `${topicHelp(name, normalizedTopic)}\n\nEquivalent command: ${alias}`;
|
|
206
287
|
return `Usage:
|
|
207
|
-
${name} [--configure-claude-
|
|
288
|
+
${name} [start] [--configure-claude-app] [--show-request-id]
|
|
208
289
|
${name} auth status [--online]
|
|
209
290
|
${name} auth login claude [--github-login <login>] [--reauth]
|
|
210
291
|
${name} doctor [--online] [--compat] [--profile codex|claude|all]
|
|
211
292
|
${name} status
|
|
212
293
|
${name} models [--profile codex|claude]
|
|
294
|
+
${name} pms status
|
|
295
|
+
${name} pms setup
|
|
213
296
|
${name} usage
|
|
214
297
|
${name} auto-review-model
|
|
215
298
|
${name} update [npm|github]
|
|
216
299
|
${name} --version
|
|
217
300
|
${name} --help
|
|
218
301
|
|
|
302
|
+
Commands:
|
|
303
|
+
start Start or reuse the adapter and configure local clients
|
|
304
|
+
auth Inspect or configure the Codex and Claude accounts
|
|
305
|
+
doctor Diagnose local config; optional live and inference checks
|
|
306
|
+
status Show runtime routing, performance, queue, and cache health
|
|
307
|
+
models Query a saved account's live Copilot model catalog
|
|
308
|
+
pms, pm-studio Inspect or install the PM Studio patch
|
|
309
|
+
usage Summarize locally recorded token usage
|
|
310
|
+
auto-review-model Select the Codex Auto-review Responses model
|
|
311
|
+
update Update the global package from npm or GitHub
|
|
312
|
+
|
|
313
|
+
Run ${name} <command> --help for command details.
|
|
314
|
+
|
|
219
315
|
Equivalent command: ${alias}`;
|
|
220
316
|
}
|
package/src/cli-status.mjs
CHANGED
|
@@ -70,6 +70,33 @@ function routingTarget(value) {
|
|
|
70
70
|
return "unknown";
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
function pmRouteSummary(requests = {}) {
|
|
74
|
+
const routes = requests.by_route;
|
|
75
|
+
if (!routes || typeof routes !== "object") return null;
|
|
76
|
+
const names = ["pm_models", "pm_chat_completions", "pm_responses", "pm_embeddings"];
|
|
77
|
+
const totals = names.reduce((summary, name) => {
|
|
78
|
+
const route = routes[name] || {};
|
|
79
|
+
summary.total += finiteNumber(route.total) || 0;
|
|
80
|
+
summary.active += finiteNumber(route.active) || 0;
|
|
81
|
+
summary.errors += finiteNumber(route.errors) || 0;
|
|
82
|
+
return summary;
|
|
83
|
+
}, { total: 0, active: 0, errors: 0 });
|
|
84
|
+
return `PM relay: ${count(totals.total)} requests, ${count(totals.active)} active, ${count(totals.errors)} errors; models ${count(routes.pm_models?.total)}, chat ${count(routes.pm_chat_completions?.total)}, responses ${count(routes.pm_responses?.total)}, embeddings ${count(routes.pm_embeddings?.total)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function errorCode(error) {
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const pending = [error];
|
|
90
|
+
while (pending.length) {
|
|
91
|
+
const current = pending.shift();
|
|
92
|
+
if (!current || typeof current !== "object" || seen.has(current)) continue;
|
|
93
|
+
seen.add(current);
|
|
94
|
+
if (typeof current.code === "string") return current.code;
|
|
95
|
+
pending.push(current.cause, ...(Array.isArray(current.errors) ? current.errors : []));
|
|
96
|
+
}
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
|
|
73
100
|
export async function readAdapterStatus({
|
|
74
101
|
host = "127.0.0.1",
|
|
75
102
|
port = 2026,
|
|
@@ -101,12 +128,16 @@ export async function readAdapterStatus({
|
|
|
101
128
|
}
|
|
102
129
|
return { baseUrl, data };
|
|
103
130
|
} catch (error) {
|
|
131
|
+
const code = errorCode(error);
|
|
104
132
|
const reason = ctrl.signal.aborted
|
|
105
133
|
? `timed out after ${timeoutMs}ms`
|
|
106
|
-
:
|
|
134
|
+
: code === "ECONNREFUSED" || code === "ENETUNREACH"
|
|
107
135
|
? "adapter is not running"
|
|
108
136
|
: error?.message || String(error);
|
|
109
|
-
|
|
137
|
+
const nextStep = reason === "adapter is not running"
|
|
138
|
+
? `. Run ccdx start, then retry ccdx status`
|
|
139
|
+
: "";
|
|
140
|
+
throw new Error(`Could not read adapter status at ${baseUrl}: ${reason}${nextStep}`);
|
|
110
141
|
} finally {
|
|
111
142
|
clearTimeout(timer);
|
|
112
143
|
}
|
|
@@ -143,6 +174,8 @@ export function formatAdapterStatus({ baseUrl, data }, { commandName = "ccdx", c
|
|
|
143
174
|
if (data.routing) {
|
|
144
175
|
lines.push(status("info", `Routing: /v1/responses -> ${routingTarget(data.routing.responses)}; /v1/messages -> ${routingTarget(data.routing.messages)}`));
|
|
145
176
|
}
|
|
177
|
+
const pmSummary = pmRouteSummary(requests);
|
|
178
|
+
if (pmSummary) lines.push(status("info", pmSummary));
|
|
146
179
|
lines.push(status("info", `Limits: request body ${mebibytes(limits.max_body_bytes)}, decoded body ${mebibytes(limits.max_decoded_body_bytes)}`));
|
|
147
180
|
return lines.join("\n");
|
|
148
181
|
}
|
package/src/doctor.mjs
CHANGED
|
@@ -251,7 +251,11 @@ export function inspectAuthProfiles({ home = os.homedir() } = {}) {
|
|
|
251
251
|
if (!claude.configured) {
|
|
252
252
|
checks.push({ kind: "ok", message: "Claude authentication profile is not isolated and inherits Codex" });
|
|
253
253
|
} else if (!claude.valid) {
|
|
254
|
-
checks.push({
|
|
254
|
+
checks.push({
|
|
255
|
+
kind: "err",
|
|
256
|
+
message: `Claude isolated authentication profile is invalid: ${claude.reason}`,
|
|
257
|
+
fix: "ccdx auth login claude --reauth",
|
|
258
|
+
});
|
|
255
259
|
} else {
|
|
256
260
|
const account = claude.identity?.login ? ` for ${claude.identity.login}` : "";
|
|
257
261
|
checks.push({ kind: "ok", message: `Claude isolated authentication profile is configured${account}` });
|
|
@@ -455,7 +459,11 @@ export function inspectClaudeAppConfig({
|
|
|
455
459
|
const meta = readJson(paths.metaPath);
|
|
456
460
|
const appliedId = meta.ok ? String(meta.json?.appliedId || "").trim() : "";
|
|
457
461
|
if (!appliedId) {
|
|
458
|
-
checks.push({
|
|
462
|
+
checks.push({
|
|
463
|
+
kind: "warn",
|
|
464
|
+
message: "Claude App gateway profile is not configured",
|
|
465
|
+
fix: "ccdx start --configure-claude-app",
|
|
466
|
+
});
|
|
459
467
|
return checks;
|
|
460
468
|
}
|
|
461
469
|
|
|
@@ -508,6 +516,7 @@ export async function collectDoctorChecks({
|
|
|
508
516
|
host = "127.0.0.1",
|
|
509
517
|
port = 2026,
|
|
510
518
|
checkAdapter = true,
|
|
519
|
+
checkPmStudio = checkAdapter,
|
|
511
520
|
checkAdapterListeningFn = checkAdapterListening,
|
|
512
521
|
checkRunningAdapterFn = checkRunningAdapter,
|
|
513
522
|
online = false,
|
|
@@ -517,6 +526,7 @@ export async function collectDoctorChecks({
|
|
|
517
526
|
onlineTimeoutMs = 10000,
|
|
518
527
|
compatTimeoutMs = 120000,
|
|
519
528
|
inspectAdapterCompatibilityFn = inspectAdapterCompatibility,
|
|
529
|
+
inspectPmStudioHealthFn,
|
|
520
530
|
} = {}) {
|
|
521
531
|
const checks = [
|
|
522
532
|
...inspectAuthProfiles({ home }),
|
|
@@ -552,7 +562,43 @@ export async function collectDoctorChecks({
|
|
|
552
562
|
const listening = await checkAdapterListeningFn({ host, port });
|
|
553
563
|
checks.push(listening
|
|
554
564
|
? { kind: "warn", message: `A service is listening on ${localGatewayBaseUrl(host, port)}, but it is not a compatible codex-copilot-dx adapter` }
|
|
555
|
-
: {
|
|
565
|
+
: {
|
|
566
|
+
kind: "warn",
|
|
567
|
+
message: `Adapter is not listening on ${localGatewayBaseUrl(host, port)}`,
|
|
568
|
+
fix: "ccdx start",
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (checkPmStudio && platform === "darwin") {
|
|
574
|
+
const inspect = inspectPmStudioHealthFn || (async (options) => {
|
|
575
|
+
const module = await import("./pm-studio-status.mjs");
|
|
576
|
+
return module.inspectPmStudioStatus(options);
|
|
577
|
+
});
|
|
578
|
+
const pm = await inspect({ home });
|
|
579
|
+
const version = pm.app.metadata ? `${pm.app.metadata.version} build ${pm.app.metadata.build}` : "";
|
|
580
|
+
if (pm.app.state === "patched") {
|
|
581
|
+
checks.push({ kind: "ok", message: `PM Studio ${version} patch is verified` });
|
|
582
|
+
if (!pm.claude.valid) {
|
|
583
|
+
checks.push({
|
|
584
|
+
kind: "warn",
|
|
585
|
+
message: "PM Studio Claude routing requires a valid isolated Claude profile",
|
|
586
|
+
fix: "ccdx auth login claude --reauth",
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
if (!pm.adapter?.ok) {
|
|
590
|
+
checks.push({ kind: "warn", message: "PM Studio relay is not currently available", fix: "ccdx start" });
|
|
591
|
+
}
|
|
592
|
+
} else if (pm.app.state === "clean") {
|
|
593
|
+
checks.push({
|
|
594
|
+
kind: "warn",
|
|
595
|
+
message: `PM Studio ${version} is supported but not patched`,
|
|
596
|
+
fix: "ccdx pms setup",
|
|
597
|
+
});
|
|
598
|
+
} else if (pm.app.state === "unsupported") {
|
|
599
|
+
checks.push({ kind: "warn", message: `PM Studio ${version} has no exact patch recipe; no files will be changed` });
|
|
600
|
+
} else if (pm.app.state === "drift" || pm.app.state === "error") {
|
|
601
|
+
checks.push({ kind: "err", message: `PM Studio integrity check failed: ${pm.app.issues.join("; ")}` });
|
|
556
602
|
}
|
|
557
603
|
}
|
|
558
604
|
|
|
@@ -604,5 +650,12 @@ export async function runDoctor(options = {}) {
|
|
|
604
650
|
log(`${options.commandName || "ccdx"} doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
|
|
605
651
|
const checks = await collectDoctorChecks(options);
|
|
606
652
|
for (const check of checks) log(status(check.kind, check.message));
|
|
653
|
+
const totals = { ok: 0, warn: 0, err: 0 };
|
|
654
|
+
for (const check of checks) totals[check.kind] = (totals[check.kind] || 0) + 1;
|
|
655
|
+
log(status(totals.err ? "err" : totals.warn ? "warn" : "ok",
|
|
656
|
+
`Summary: ${totals.ok} passed, ${totals.warn} warning(s), ${totals.err} error(s)`));
|
|
657
|
+
for (const fix of new Set(checks.map((check) => check.fix).filter(Boolean))) {
|
|
658
|
+
log(status("info", `Next step: ${fix}`));
|
|
659
|
+
}
|
|
607
660
|
return checks;
|
|
608
661
|
}
|
package/src/observability.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { copilotRuntimeStatus } from "./copilot.mjs";
|
|
|
4
4
|
import { imageOptimizationStats } from "./image-optimization.mjs";
|
|
5
5
|
import { responseHistoryStats } from "./response-history.mjs";
|
|
6
6
|
import { loadRuntimeConfig } from "./runtime-config.mjs";
|
|
7
|
+
import { profileRouting } from "./profile-routing.mjs";
|
|
7
8
|
|
|
8
9
|
export { ADAPTER_STATUS_PATH };
|
|
9
10
|
const OBSERVABILITY_RUNTIME_CONFIG = loadRuntimeConfig();
|
|
@@ -14,6 +15,10 @@ const ROUTE_NAMES = Object.freeze([
|
|
|
14
15
|
"models",
|
|
15
16
|
"messages",
|
|
16
17
|
"messages_count_tokens",
|
|
18
|
+
"pm_models",
|
|
19
|
+
"pm_chat_completions",
|
|
20
|
+
"pm_responses",
|
|
21
|
+
"pm_embeddings",
|
|
17
22
|
"not_found",
|
|
18
23
|
]);
|
|
19
24
|
|
|
@@ -97,6 +102,10 @@ export function classifyAdapterRoute(method, pathname) {
|
|
|
97
102
|
if (method === "GET" && pathname === "/v1/models") return "models";
|
|
98
103
|
if (method === "POST" && pathname === "/v1/messages") return "messages";
|
|
99
104
|
if (method === "POST" && pathname === "/v1/messages/count_tokens") return "messages_count_tokens";
|
|
105
|
+
if (method === "GET" && pathname === "/pm-ccdx/models") return "pm_models";
|
|
106
|
+
if (method === "POST" && pathname === "/pm-ccdx/chat/completions") return "pm_chat_completions";
|
|
107
|
+
if (method === "POST" && pathname === "/pm-ccdx/responses") return "pm_responses";
|
|
108
|
+
if (method === "POST" && pathname === "/pm-ccdx/embeddings") return "pm_embeddings";
|
|
100
109
|
return "not_found";
|
|
101
110
|
}
|
|
102
111
|
|
|
@@ -183,10 +192,7 @@ export function runtimeStatusPayload({
|
|
|
183
192
|
codex: { mode: "legacy", client: codexRuntime, models: codexModels },
|
|
184
193
|
claude: { mode: resolvedClaudeMode, client: claudeRuntime, models: claudeModels },
|
|
185
194
|
},
|
|
186
|
-
routing: {
|
|
187
|
-
responses: "codex",
|
|
188
|
-
messages: resolvedClaudeMode === "isolated" ? "claude" : "codex",
|
|
189
|
-
},
|
|
195
|
+
routing: profileRouting({ claudeMode: resolvedClaudeMode }),
|
|
190
196
|
limits: {
|
|
191
197
|
max_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxBodyBytes,
|
|
192
198
|
max_decoded_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxDecodedBodyBytes,
|