codex-copilot-dx 0.5.5 → 0.6.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 +68 -1
- package/bin/cli.mjs +73 -91
- package/package.json +1 -1
- package/src/adapter.mjs +56 -14
- package/src/auth-profile.mjs +205 -0
- package/src/cli-auth.mjs +493 -0
- package/src/cli-models.mjs +189 -0
- package/src/cli-options.mjs +94 -9
- package/src/cli-status.mjs +29 -3
- package/src/copilot-client.mjs +226 -0
- package/src/copilot-token-session.mjs +362 -0
- package/src/copilot.mjs +45 -387
- package/src/doctor.mjs +169 -23
- package/src/model-cache.mjs +22 -6
- package/src/observability.mjs +53 -3
- package/src/profile-model-runtime.mjs +261 -0
- package/src/profile-runtime.mjs +80 -0
- package/src/responses-handler.mjs +7 -6
package/src/doctor.mjs
CHANGED
|
@@ -3,11 +3,17 @@ import path from "node:path";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import net from "node:net";
|
|
5
5
|
import { githubTokenPath, readGithubTokenMetadata, validateGithubToken } from "./auth.mjs";
|
|
6
|
+
import {
|
|
7
|
+
AUTH_PROFILE_CLAUDE,
|
|
8
|
+
AUTH_PROFILE_CODEX,
|
|
9
|
+
readAuthProfileCredentials,
|
|
10
|
+
} from "./auth-profile.mjs";
|
|
6
11
|
import { claudeDesktopPaths } from "./claude-desktop-config.mjs";
|
|
7
12
|
import { status } from "./status.mjs";
|
|
8
13
|
import { buildHeaders, DEFAULT_API_BASE, FALLBACK_VSCODE_VERSION } from "./copilot.mjs";
|
|
9
14
|
import { adapterBaseUrl, checkRunningAdapter } from "./running-adapter.mjs";
|
|
10
15
|
import { CODEX_AUTO_REVIEW_MODEL } from "./models.mjs";
|
|
16
|
+
import { loadModelCache } from "./model-cache.mjs";
|
|
11
17
|
|
|
12
18
|
function localGatewayBaseUrl(host, port) {
|
|
13
19
|
const safeHost = String(host || "127.0.0.1");
|
|
@@ -58,8 +64,9 @@ function modelEndpoints(model) {
|
|
|
58
64
|
return Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
59
65
|
}
|
|
60
66
|
|
|
61
|
-
export function selectCompatibilityModels(models) {
|
|
67
|
+
export function selectCompatibilityModels(models, { claudeModels = models } = {}) {
|
|
62
68
|
const data = copilotModelData(models).filter((model) => model?.model_picker_enabled !== false);
|
|
69
|
+
const claudeData = copilotModelData(claudeModels).filter((model) => model?.model_picker_enabled !== false);
|
|
63
70
|
const responsesCandidates = data.filter((model) => {
|
|
64
71
|
const id = String(model?.id || "");
|
|
65
72
|
const endpoints = modelEndpoints(model);
|
|
@@ -67,7 +74,7 @@ export function selectCompatibilityModels(models) {
|
|
|
67
74
|
});
|
|
68
75
|
const responsesOnly = [...responsesCandidates].reverse()
|
|
69
76
|
.find((model) => !modelEndpoints(model).includes("/chat/completions"));
|
|
70
|
-
const claude =
|
|
77
|
+
const claude = claudeData.find((model) => {
|
|
71
78
|
const id = String(model?.id || "");
|
|
72
79
|
const vendor = String(model?.vendor || "").toLowerCase();
|
|
73
80
|
return (id.startsWith("claude-") || vendor === "anthropic")
|
|
@@ -138,6 +145,8 @@ export async function inspectAdapterCompatibility({
|
|
|
138
145
|
port = 2026,
|
|
139
146
|
fetchImpl = fetch,
|
|
140
147
|
timeoutMs = 120000,
|
|
148
|
+
claudeMode = "inherited",
|
|
149
|
+
claudeModels,
|
|
141
150
|
} = {}) {
|
|
142
151
|
const baseUrl = localGatewayBaseUrl(connectHost(host), port);
|
|
143
152
|
const checks = [];
|
|
@@ -150,7 +159,9 @@ export async function inspectAdapterCompatibility({
|
|
|
150
159
|
});
|
|
151
160
|
if (!models) return checks;
|
|
152
161
|
|
|
153
|
-
const { responsesModel, claudeModel } = selectCompatibilityModels(models
|
|
162
|
+
const { responsesModel, claudeModel } = selectCompatibilityModels(models, {
|
|
163
|
+
claudeModels: claudeMode === "isolated" ? (claudeModels || { data: [] }) : models,
|
|
164
|
+
});
|
|
154
165
|
if (!responsesModel) {
|
|
155
166
|
checks.push({ kind: "err", message: "Compatibility Responses check failed: no GPT model advertises /responses" });
|
|
156
167
|
return checks;
|
|
@@ -197,7 +208,10 @@ export async function inspectAdapterCompatibility({
|
|
|
197
208
|
});
|
|
198
209
|
|
|
199
210
|
if (!claudeModel) {
|
|
200
|
-
|
|
211
|
+
const reason = claudeMode === "isolated"
|
|
212
|
+
? "the isolated Claude model cache is unavailable or advertises no Claude chat model; restart ccdx to refresh it"
|
|
213
|
+
: "no Claude chat model was advertised";
|
|
214
|
+
checks.push({ kind: "warn", message: `Anthropic stream compatibility was not verified because ${reason}` });
|
|
201
215
|
return checks;
|
|
202
216
|
}
|
|
203
217
|
|
|
@@ -227,26 +241,86 @@ export function inspectGitHubToken({ home = os.homedir() } = {}) {
|
|
|
227
241
|
return [{ kind: "ok", message: `GitHub token found${account}` }];
|
|
228
242
|
}
|
|
229
243
|
|
|
244
|
+
export function inspectAuthProfiles({ home = os.homedir() } = {}) {
|
|
245
|
+
const checks = [
|
|
246
|
+
...inspectGitHubToken({ home }),
|
|
247
|
+
{ kind: "ok", message: "Codex authentication profile uses the legacy path" },
|
|
248
|
+
];
|
|
249
|
+
try {
|
|
250
|
+
const claude = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
251
|
+
if (!claude.configured) {
|
|
252
|
+
checks.push({ kind: "ok", message: "Claude authentication profile is not isolated and inherits Codex" });
|
|
253
|
+
} else if (!claude.valid) {
|
|
254
|
+
checks.push({ kind: "err", message: `Claude isolated authentication profile is invalid: ${claude.reason}` });
|
|
255
|
+
} else {
|
|
256
|
+
const account = claude.identity?.login ? ` for ${claude.identity.login}` : "";
|
|
257
|
+
checks.push({ kind: "ok", message: `Claude isolated authentication profile is configured${account}` });
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
checks.push({ kind: "err", message: "Claude isolated authentication profile could not be inspected" });
|
|
261
|
+
}
|
|
262
|
+
return checks;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function checkedDoctorProfile(profile = AUTH_PROFILE_CODEX) {
|
|
266
|
+
const value = String(profile || AUTH_PROFILE_CODEX).trim().toLowerCase();
|
|
267
|
+
if (![AUTH_PROFILE_CODEX, AUTH_PROFILE_CLAUDE, "all"].includes(value)) {
|
|
268
|
+
throw new Error(`Doctor profile must be codex, claude, or all: ${profile}`);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function onlineMessage(profile, message, inherited = false) {
|
|
274
|
+
if (profile === AUTH_PROFILE_CODEX) return message;
|
|
275
|
+
return `${inherited ? "Claude inherited Codex" : "Claude profile"}: ${message}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
230
278
|
export async function inspectGitHubTokenOnline({
|
|
231
279
|
home = os.homedir(),
|
|
280
|
+
profile = AUTH_PROFILE_CODEX,
|
|
232
281
|
fetchImpl = fetch,
|
|
233
282
|
timeoutMs = 10000,
|
|
234
283
|
} = {}) {
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
|
|
284
|
+
const requestedProfile = checkedDoctorProfile(profile);
|
|
285
|
+
if (requestedProfile === "all") throw new Error("inspectGitHubTokenOnline accepts one profile at a time");
|
|
286
|
+
let inherited = false;
|
|
287
|
+
let credentials;
|
|
288
|
+
try {
|
|
289
|
+
credentials = readAuthProfileCredentials(requestedProfile, { home });
|
|
290
|
+
if (requestedProfile === AUTH_PROFILE_CLAUDE && !credentials.configured) {
|
|
291
|
+
inherited = true;
|
|
292
|
+
credentials = readAuthProfileCredentials(AUTH_PROFILE_CODEX, { home });
|
|
293
|
+
}
|
|
294
|
+
} catch {
|
|
295
|
+
return [{ kind: "warn", message: onlineMessage(requestedProfile, "Online Copilot check failed while reading the local credential", inherited) }];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const checks = [];
|
|
299
|
+
if (inherited) {
|
|
300
|
+
checks.push({ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" });
|
|
301
|
+
}
|
|
302
|
+
if (!credentials.valid) {
|
|
303
|
+
const reason = requestedProfile === AUTH_PROFILE_CODEX
|
|
304
|
+
? "the GitHub token is missing"
|
|
305
|
+
: credentials.reason;
|
|
306
|
+
checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check skipped because ${reason}`, inherited) });
|
|
307
|
+
return checks;
|
|
238
308
|
}
|
|
239
309
|
|
|
240
310
|
const controller = new AbortController();
|
|
241
311
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
242
312
|
try {
|
|
243
|
-
const validation = await validateGithubToken(token
|
|
313
|
+
const validation = await validateGithubToken(credentials.token, {
|
|
244
314
|
fetchImpl,
|
|
245
315
|
signal: controller.signal,
|
|
246
316
|
});
|
|
247
317
|
if (!validation.ok) {
|
|
248
318
|
const statusText = validation.status ? ` (HTTP ${validation.status})` : "";
|
|
249
|
-
|
|
319
|
+
checks.push({
|
|
320
|
+
kind: validation.transient ? "warn" : "err",
|
|
321
|
+
message: onlineMessage(requestedProfile, `GitHub Copilot authentication failed: ${validation.reason}${statusText}`, inherited),
|
|
322
|
+
});
|
|
323
|
+
return checks;
|
|
250
324
|
}
|
|
251
325
|
|
|
252
326
|
const tokenData = validation.copilotTokenData;
|
|
@@ -259,25 +333,60 @@ export async function inspectGitHubTokenOnline({
|
|
|
259
333
|
});
|
|
260
334
|
const modelResp = await fetchImpl(`${apiBase}/models`, { headers, signal: controller.signal });
|
|
261
335
|
if (!modelResp.ok) {
|
|
262
|
-
|
|
263
|
-
{ kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}
|
|
264
|
-
{ kind: "err", message: `Copilot models endpoint returned HTTP ${modelResp.status}
|
|
265
|
-
|
|
336
|
+
checks.push(
|
|
337
|
+
{ kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
|
|
338
|
+
{ kind: "err", message: onlineMessage(requestedProfile, `Copilot models endpoint returned HTTP ${modelResp.status}`, inherited) },
|
|
339
|
+
);
|
|
340
|
+
return checks;
|
|
266
341
|
}
|
|
267
342
|
const models = await modelResp.json();
|
|
268
343
|
const data = Array.isArray(models) ? models : models?.data;
|
|
269
|
-
|
|
270
|
-
{ kind: "ok", message: `GitHub Copilot access verified for ${validation.login || "current account"}
|
|
271
|
-
{ kind: "ok", message: `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models
|
|
272
|
-
|
|
344
|
+
checks.push(
|
|
345
|
+
{ kind: "ok", message: onlineMessage(requestedProfile, `GitHub Copilot access verified for ${validation.login || "current account"}`, inherited) },
|
|
346
|
+
{ kind: "ok", message: onlineMessage(requestedProfile, `Copilot models endpoint returned ${Array.isArray(data) ? data.length : 0} models`, inherited) },
|
|
347
|
+
);
|
|
348
|
+
return checks;
|
|
273
349
|
} catch (e) {
|
|
274
|
-
const
|
|
275
|
-
|
|
350
|
+
const errorCode = e?.cause?.code || e?.code || "";
|
|
351
|
+
const reason = e?.name === "AbortError"
|
|
352
|
+
? `timed out after ${timeoutMs}ms`
|
|
353
|
+
: `request failed${errorCode ? ` (${errorCode})` : ""}`;
|
|
354
|
+
checks.push({ kind: "warn", message: onlineMessage(requestedProfile, `Online Copilot check failed: ${reason}`, inherited) });
|
|
355
|
+
return checks;
|
|
276
356
|
} finally {
|
|
277
357
|
clearTimeout(timer);
|
|
278
358
|
}
|
|
279
359
|
}
|
|
280
360
|
|
|
361
|
+
export async function inspectAuthProfilesOnline({
|
|
362
|
+
home = os.homedir(),
|
|
363
|
+
profile = AUTH_PROFILE_CODEX,
|
|
364
|
+
fetchImpl = fetch,
|
|
365
|
+
timeoutMs = 10000,
|
|
366
|
+
} = {}) {
|
|
367
|
+
const selected = checkedDoctorProfile(profile);
|
|
368
|
+
if (selected !== "all") {
|
|
369
|
+
return inspectGitHubTokenOnline({ home, profile: selected, fetchImpl, timeoutMs });
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
let claudeConfigured = false;
|
|
373
|
+
let claudeReadFailed = false;
|
|
374
|
+
try {
|
|
375
|
+
claudeConfigured = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home }).configured;
|
|
376
|
+
} catch {
|
|
377
|
+
claudeReadFailed = true;
|
|
378
|
+
}
|
|
379
|
+
const [codexChecks, claudeChecks] = await Promise.all([
|
|
380
|
+
inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CODEX, fetchImpl, timeoutMs }),
|
|
381
|
+
claudeReadFailed
|
|
382
|
+
? Promise.resolve([{ kind: "err", message: "Claude isolated authentication profile could not be inspected" }])
|
|
383
|
+
: claudeConfigured
|
|
384
|
+
? inspectGitHubTokenOnline({ home, profile: AUTH_PROFILE_CLAUDE, fetchImpl, timeoutMs })
|
|
385
|
+
: Promise.resolve([{ kind: "ok", message: "Claude profile is not separately configured and inherits Codex authentication" }]),
|
|
386
|
+
]);
|
|
387
|
+
return [...codexChecks, ...claudeChecks];
|
|
388
|
+
}
|
|
389
|
+
|
|
281
390
|
export function inspectCodexConfig({ home = os.homedir(), host = "127.0.0.1", port = 2026 } = {}) {
|
|
282
391
|
const filePath = path.join(home, ".codex", "config.toml");
|
|
283
392
|
const expectedAnthropicBaseUrl = adapterBaseUrl(host, port);
|
|
@@ -402,6 +511,7 @@ export async function collectDoctorChecks({
|
|
|
402
511
|
checkAdapterListeningFn = checkAdapterListening,
|
|
403
512
|
checkRunningAdapterFn = checkRunningAdapter,
|
|
404
513
|
online = false,
|
|
514
|
+
profile = AUTH_PROFILE_CODEX,
|
|
405
515
|
compat = false,
|
|
406
516
|
fetchImpl = fetch,
|
|
407
517
|
onlineTimeoutMs = 10000,
|
|
@@ -409,14 +519,19 @@ export async function collectDoctorChecks({
|
|
|
409
519
|
inspectAdapterCompatibilityFn = inspectAdapterCompatibility,
|
|
410
520
|
} = {}) {
|
|
411
521
|
const checks = [
|
|
412
|
-
...
|
|
522
|
+
...inspectAuthProfiles({ home }),
|
|
413
523
|
...inspectCodexConfig({ home, host, port }),
|
|
414
524
|
...inspectClaudeCodeConfig({ home, host, port }),
|
|
415
525
|
...inspectClaudeAppConfig({ home, platform, env, host, port }),
|
|
416
526
|
];
|
|
417
527
|
|
|
418
528
|
if (online) {
|
|
419
|
-
checks.push(...await
|
|
529
|
+
checks.push(...await inspectAuthProfilesOnline({
|
|
530
|
+
home,
|
|
531
|
+
profile,
|
|
532
|
+
fetchImpl,
|
|
533
|
+
timeoutMs: onlineTimeoutMs,
|
|
534
|
+
}));
|
|
420
535
|
}
|
|
421
536
|
|
|
422
537
|
let running = null;
|
|
@@ -445,7 +560,33 @@ export async function collectDoctorChecks({
|
|
|
445
560
|
if (!running?.ok) {
|
|
446
561
|
checks.push({ kind: "err", message: "Compatibility checks require a running, version-compatible codex-copilot-dx adapter" });
|
|
447
562
|
} else {
|
|
448
|
-
|
|
563
|
+
let claudeMode = "inherited";
|
|
564
|
+
let claudeModels;
|
|
565
|
+
try {
|
|
566
|
+
const claudeCredentials = readAuthProfileCredentials(AUTH_PROFILE_CLAUDE, { home });
|
|
567
|
+
if (claudeCredentials.configured) {
|
|
568
|
+
claudeMode = "isolated";
|
|
569
|
+
if (claudeCredentials.valid) {
|
|
570
|
+
claudeModels = loadModelCache({
|
|
571
|
+
home,
|
|
572
|
+
profile: AUTH_PROFILE_CLAUDE,
|
|
573
|
+
credentialFingerprint: claudeCredentials.metadata?.token_fingerprint,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} catch {
|
|
578
|
+
// A configured-but-unreadable optional profile must not borrow the
|
|
579
|
+
// Codex catalog. Compatibility will report the Claude path unverified.
|
|
580
|
+
claudeMode = "isolated";
|
|
581
|
+
}
|
|
582
|
+
checks.push(...await inspectAdapterCompatibilityFn({
|
|
583
|
+
host,
|
|
584
|
+
port,
|
|
585
|
+
fetchImpl,
|
|
586
|
+
timeoutMs: compatTimeoutMs,
|
|
587
|
+
claudeMode,
|
|
588
|
+
claudeModels,
|
|
589
|
+
}));
|
|
449
590
|
}
|
|
450
591
|
}
|
|
451
592
|
|
|
@@ -454,7 +595,12 @@ export async function collectDoctorChecks({
|
|
|
454
595
|
|
|
455
596
|
export async function runDoctor(options = {}) {
|
|
456
597
|
const log = options.log || console.log;
|
|
457
|
-
const
|
|
598
|
+
const profile = checkedDoctorProfile(options.profile);
|
|
599
|
+
const flags = [
|
|
600
|
+
options.online ? "--online" : "",
|
|
601
|
+
options.compat ? "--compat" : "",
|
|
602
|
+
profile !== AUTH_PROFILE_CODEX ? `--profile ${profile}` : "",
|
|
603
|
+
].filter(Boolean);
|
|
458
604
|
log(`${options.commandName || "ccdx"} doctor${flags.length ? ` ${flags.join(" ")}` : ""}`);
|
|
459
605
|
const checks = await collectDoctorChecks(options);
|
|
460
606
|
for (const check of checks) log(status(check.kind, check.message));
|
package/src/model-cache.mjs
CHANGED
|
@@ -3,9 +3,14 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
const DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
6
|
+
const MODEL_CACHE_PROFILES = new Set(["codex", "claude"]);
|
|
6
7
|
|
|
7
|
-
export function modelCachePath(home = os.homedir()) {
|
|
8
|
-
|
|
8
|
+
export function modelCachePath(home = os.homedir(), profile = "codex") {
|
|
9
|
+
if (!MODEL_CACHE_PROFILES.has(profile)) throw new Error(`Unknown model cache profile: ${profile}`);
|
|
10
|
+
const root = path.join(home, ".local", "share", "codex-copilot-dx");
|
|
11
|
+
return profile === "codex"
|
|
12
|
+
? path.join(root, "models.json")
|
|
13
|
+
: path.join(root, "profiles", profile, "models.json");
|
|
9
14
|
}
|
|
10
15
|
|
|
11
16
|
function modelData(models) {
|
|
@@ -20,13 +25,17 @@ export function isValidModelList(models) {
|
|
|
20
25
|
|
|
21
26
|
export function loadModelCache({
|
|
22
27
|
home = os.homedir(),
|
|
28
|
+
profile = "codex",
|
|
29
|
+
credentialFingerprint = "",
|
|
23
30
|
maxAgeMs = DEFAULT_MAX_AGE_MS,
|
|
24
31
|
now = Date.now,
|
|
25
32
|
} = {}) {
|
|
26
33
|
try {
|
|
27
|
-
const parsed = JSON.parse(fs.readFileSync(modelCachePath(home), "utf8"));
|
|
34
|
+
const parsed = JSON.parse(fs.readFileSync(modelCachePath(home, profile), "utf8"));
|
|
28
35
|
const savedAt = Date.parse(parsed.saved_at);
|
|
29
36
|
if (!Number.isFinite(savedAt) || now() - savedAt > maxAgeMs) return null;
|
|
37
|
+
const expectedFingerprint = String(credentialFingerprint || "").trim();
|
|
38
|
+
if (expectedFingerprint && parsed.credential_fingerprint !== expectedFingerprint) return null;
|
|
30
39
|
if (!isValidModelList(parsed.models)) return null;
|
|
31
40
|
return parsed.models;
|
|
32
41
|
} catch {
|
|
@@ -34,13 +43,20 @@ export function loadModelCache({
|
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
export function saveModelCache(models, {
|
|
46
|
+
export function saveModelCache(models, {
|
|
47
|
+
home = os.homedir(),
|
|
48
|
+
profile = "codex",
|
|
49
|
+
credentialFingerprint = "",
|
|
50
|
+
} = {}) {
|
|
38
51
|
if (!isValidModelList(models)) return false;
|
|
39
|
-
const filePath = modelCachePath(home);
|
|
52
|
+
const filePath = modelCachePath(home, profile);
|
|
40
53
|
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
54
|
+
const payload = { saved_at: new Date().toISOString(), models };
|
|
55
|
+
const fingerprint = String(credentialFingerprint || "").trim();
|
|
56
|
+
if (fingerprint) payload.credential_fingerprint = fingerprint;
|
|
41
57
|
fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
42
58
|
try {
|
|
43
|
-
fs.writeFileSync(tempPath, `${JSON.stringify(
|
|
59
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(payload)}\n`, { mode: 0o600 });
|
|
44
60
|
fs.renameSync(tempPath, filePath);
|
|
45
61
|
fs.chmodSync(filePath, 0o600);
|
|
46
62
|
return true;
|
package/src/observability.mjs
CHANGED
|
@@ -118,8 +118,50 @@ function modelRegistryStatus(modelRegistry) {
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
function finiteRuntimeNumber(value, fallback = 0) {
|
|
122
|
+
const number = Number(value);
|
|
123
|
+
return Number.isFinite(number) ? number : fallback;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function safeClientRuntimeStatus(client) {
|
|
127
|
+
const runtime = typeof client?.runtimeStatus === "function"
|
|
128
|
+
? client.runtimeStatus()
|
|
129
|
+
: copilotRuntimeStatus();
|
|
130
|
+
return {
|
|
131
|
+
token_cached: runtime?.token_cached === true,
|
|
132
|
+
token_expires_in_ms: runtime?.token_expires_in_ms === null
|
|
133
|
+
? null
|
|
134
|
+
: finiteRuntimeNumber(runtime?.token_expires_in_ms, null),
|
|
135
|
+
token_refresh_in_flight: runtime?.token_refresh_in_flight === true,
|
|
136
|
+
token_refresh_backoff_ms: finiteRuntimeNumber(runtime?.token_refresh_backoff_ms),
|
|
137
|
+
account_bound: runtime?.account_bound === true,
|
|
138
|
+
upstream_host: typeof runtime?.upstream_host === "string" ? runtime.upstream_host : "unknown",
|
|
139
|
+
model_endpoint_cache_entries: finiteRuntimeNumber(runtime?.model_endpoint_cache_entries),
|
|
140
|
+
model_list_flights: finiteRuntimeNumber(runtime?.model_list_flights),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function runtimeStatusPayload({
|
|
145
|
+
metrics,
|
|
146
|
+
streamPerformance,
|
|
147
|
+
admission,
|
|
148
|
+
modelRegistry,
|
|
149
|
+
codexClient,
|
|
150
|
+
claudeClient,
|
|
151
|
+
codexModelRegistry,
|
|
152
|
+
claudeModelRegistry,
|
|
153
|
+
claudeMode = "inherited",
|
|
154
|
+
} = {}) {
|
|
122
155
|
const memory = process.memoryUsage();
|
|
156
|
+
const resolvedClaudeMode = claudeMode === "isolated" ? "isolated" : "inherited";
|
|
157
|
+
const codexRuntime = safeClientRuntimeStatus(codexClient);
|
|
158
|
+
const codexModels = modelRegistryStatus(codexModelRegistry || modelRegistry);
|
|
159
|
+
const claudeRuntime = resolvedClaudeMode === "isolated"
|
|
160
|
+
? safeClientRuntimeStatus(claudeClient)
|
|
161
|
+
: codexRuntime;
|
|
162
|
+
const claudeModels = resolvedClaudeMode === "isolated"
|
|
163
|
+
? modelRegistryStatus(claudeModelRegistry)
|
|
164
|
+
: codexModels;
|
|
123
165
|
return {
|
|
124
166
|
...adapterHealthPayload(),
|
|
125
167
|
uptime_ms: Math.round(process.uptime() * 1000),
|
|
@@ -135,8 +177,16 @@ export function runtimeStatusPayload({ metrics, streamPerformance, admission, mo
|
|
|
135
177
|
admission: admission?.diagnostics?.() || admission?.stats?.() || null,
|
|
136
178
|
response_history: responseHistoryStats(),
|
|
137
179
|
image_optimization: imageOptimizationStats(),
|
|
138
|
-
copilot:
|
|
139
|
-
models:
|
|
180
|
+
copilot: codexRuntime,
|
|
181
|
+
models: codexModels,
|
|
182
|
+
profiles: {
|
|
183
|
+
codex: { mode: "legacy", client: codexRuntime, models: codexModels },
|
|
184
|
+
claude: { mode: resolvedClaudeMode, client: claudeRuntime, models: claudeModels },
|
|
185
|
+
},
|
|
186
|
+
routing: {
|
|
187
|
+
responses: "codex",
|
|
188
|
+
messages: resolvedClaudeMode === "isolated" ? "claude" : "codex",
|
|
189
|
+
},
|
|
140
190
|
limits: {
|
|
141
191
|
max_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxBodyBytes,
|
|
142
192
|
max_decoded_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxDecodedBodyBytes,
|