codex-copilot-dx 0.6.21 → 0.7.1
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 +142 -323
- package/bin/cli.mjs +26 -130
- package/package.json +2 -8
- package/scripts/benchmark.mjs +0 -114
- package/src/adapter.mjs +22 -107
- package/src/auth-profile.mjs +18 -130
- package/src/cli-animation.mjs +108 -0
- package/src/cli-auth.mjs +44 -398
- package/src/cli-models.mjs +21 -49
- package/src/cli-options.mjs +53 -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 +1 -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/terminal-activity.mjs +40 -34
- package/src/terminal-animation.mjs +249 -0
- package/src/usage.mjs +0 -31
- package/src/user-settings.mjs +50 -0
- 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/models.mjs
CHANGED
|
@@ -1,100 +1,6 @@
|
|
|
1
|
-
const DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS = [
|
|
2
|
-
{
|
|
3
|
-
id: "claude-sonnet-5",
|
|
4
|
-
upstream: "claude-sonnet-5",
|
|
5
|
-
displayName: "Claude Sonnet 5",
|
|
6
|
-
createdAt: "2026-06-30T00:00:00Z",
|
|
7
|
-
maxInputTokens: 1000000,
|
|
8
|
-
maxOutputTokens: 64000,
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
id: "claude-sonnet-4.6",
|
|
12
|
-
upstream: "claude-sonnet-4.6",
|
|
13
|
-
displayName: "Claude Sonnet 4.6",
|
|
14
|
-
createdAt: "2026-02-17T00:00:00Z",
|
|
15
|
-
maxInputTokens: 200000,
|
|
16
|
-
maxOutputTokens: 8192,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "claude-opus-4.8",
|
|
20
|
-
upstream: "claude-opus-4.8",
|
|
21
|
-
displayName: "Claude Opus 4.8",
|
|
22
|
-
createdAt: "2026-05-28T00:00:00Z",
|
|
23
|
-
maxInputTokens: 200000,
|
|
24
|
-
maxOutputTokens: 8192,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: "claude-haiku-4.5",
|
|
28
|
-
upstream: "claude-haiku-4.5",
|
|
29
|
-
displayName: "Claude Haiku 4.5",
|
|
30
|
-
createdAt: "2025-10-01T00:00:00Z",
|
|
31
|
-
maxInputTokens: 200000,
|
|
32
|
-
maxOutputTokens: 8192,
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const DEFAULT_CLAUDE_MODEL_ALIAS_DEFS = [
|
|
37
|
-
{
|
|
38
|
-
id: "claude-sonnet-4-6",
|
|
39
|
-
upstream: "claude-sonnet-4.6",
|
|
40
|
-
displayName: "Claude Sonnet 4.6",
|
|
41
|
-
createdAt: "2026-02-17T00:00:00Z",
|
|
42
|
-
maxInputTokens: 200000,
|
|
43
|
-
maxOutputTokens: 8192,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: "claude-opus-4-8",
|
|
47
|
-
upstream: "claude-opus-4.8",
|
|
48
|
-
displayName: "Claude Opus 4.8",
|
|
49
|
-
createdAt: "2026-05-28T00:00:00Z",
|
|
50
|
-
maxInputTokens: 200000,
|
|
51
|
-
maxOutputTokens: 8192,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: "claude-haiku-4-5",
|
|
55
|
-
upstream: "claude-haiku-4.5",
|
|
56
|
-
displayName: "Claude Haiku 4.5",
|
|
57
|
-
createdAt: "2025-10-01T00:00:00Z",
|
|
58
|
-
maxInputTokens: 200000,
|
|
59
|
-
maxOutputTokens: 8192,
|
|
60
|
-
},
|
|
61
|
-
];
|
|
62
|
-
|
|
63
1
|
export const CODEX_AUTO_REVIEW_MODEL = "codex-auto-review";
|
|
64
2
|
export const DEFAULT_CODEX_AUTO_REVIEW_MODEL = "gpt-5.5";
|
|
65
3
|
|
|
66
|
-
function cleanList(value) {
|
|
67
|
-
return String(value || "")
|
|
68
|
-
.split(",")
|
|
69
|
-
.map((item) => item.trim())
|
|
70
|
-
.filter(Boolean);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function parseModelAliasEnv(value) {
|
|
74
|
-
const aliases = [];
|
|
75
|
-
for (const entry of cleanList(value)) {
|
|
76
|
-
const [id, upstream] = entry.split("=", 2).map((part) => part?.trim());
|
|
77
|
-
if (!id || !upstream) continue;
|
|
78
|
-
aliases.push({
|
|
79
|
-
id,
|
|
80
|
-
upstream,
|
|
81
|
-
displayName: id,
|
|
82
|
-
createdAt: "2025-01-01T00:00:00Z",
|
|
83
|
-
maxInputTokens: 200000,
|
|
84
|
-
maxOutputTokens: 8192,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return aliases;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function cloneModelDefs(modelDefs) {
|
|
91
|
-
return modelDefs.map((model) => ({ ...model }));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function numericLimit(value, fallback) {
|
|
95
|
-
return Number.isFinite(value) && value > 0 ? value : fallback;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
4
|
function hasOpenAIEndpoint(model) {
|
|
99
5
|
const endpoints = Array.isArray(model?.supported_endpoints) ? model.supported_endpoints : [];
|
|
100
6
|
return endpoints.includes("/responses") || endpoints.includes("/v1/responses") || endpoints.includes("/chat/completions");
|
|
@@ -152,24 +58,6 @@ function uniqueIds(models, predicate) {
|
|
|
152
58
|
return ids;
|
|
153
59
|
}
|
|
154
60
|
|
|
155
|
-
export function isClaudeCopilotCatalogEntry(model) {
|
|
156
|
-
const id = String(model?.id || "").trim();
|
|
157
|
-
const vendor = String(model?.vendor || model?.owned_by || "").trim().toLowerCase();
|
|
158
|
-
return Boolean(id) && (id.toLowerCase().startsWith("claude-") || vendor === "anthropic");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function isClaudeCopilotModel(model) {
|
|
162
|
-
const policy = String(model?.policy?.state || "").trim().toLowerCase();
|
|
163
|
-
const declaredOwners = [model?.vendor, model?.owned_by]
|
|
164
|
-
.map((value) => String(value || "").trim().toLowerCase())
|
|
165
|
-
.filter(Boolean);
|
|
166
|
-
return isClaudeCopilotCatalogEntry(model)
|
|
167
|
-
&& declaredOwners.every((owner) => owner === "anthropic")
|
|
168
|
-
&& model?.model_picker_enabled !== false
|
|
169
|
-
&& (!policy || policy === "enabled")
|
|
170
|
-
&& modelSupportsChatCompletions(model);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
61
|
export function gptModelIdsFromCopilotModels(models) {
|
|
174
62
|
return uniqueIds(copilotModelData(models), (model) => {
|
|
175
63
|
const id = String(model?.id || "").trim();
|
|
@@ -190,60 +78,6 @@ export function responsesModelIdsFromCopilotModels(models) {
|
|
|
190
78
|
});
|
|
191
79
|
}
|
|
192
80
|
|
|
193
|
-
export function claudeDesktopModelDefsFromCopilotModels(models) {
|
|
194
|
-
const data = copilotModelData(models);
|
|
195
|
-
|
|
196
|
-
const defs = [];
|
|
197
|
-
const seen = new Set();
|
|
198
|
-
for (const model of data) {
|
|
199
|
-
if (!isClaudeCopilotModel(model)) continue;
|
|
200
|
-
const id = String(model.id || "").trim();
|
|
201
|
-
if (seen.has(id)) continue;
|
|
202
|
-
seen.add(id);
|
|
203
|
-
|
|
204
|
-
const limits = model.capabilities?.limits || {};
|
|
205
|
-
defs.push({
|
|
206
|
-
id,
|
|
207
|
-
upstream: id,
|
|
208
|
-
displayName: String(model.name || id),
|
|
209
|
-
createdAt: "2025-01-01T00:00:00Z",
|
|
210
|
-
maxInputTokens: numericLimit(limits.max_context_window_tokens, numericLimit(limits.max_prompt_tokens, 200000)),
|
|
211
|
-
maxOutputTokens: numericLimit(limits.max_output_tokens, 8192),
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return defs;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function claudeDesktopModelDefs(env = process.env, options = {}) {
|
|
219
|
-
const custom = parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES);
|
|
220
|
-
if (custom.length) return custom;
|
|
221
|
-
if (Array.isArray(options.modelDefs) && options.modelDefs.length) return cloneModelDefs(options.modelDefs);
|
|
222
|
-
return cloneModelDefs(DEFAULT_CLAUDE_DESKTOP_MODEL_DEFS);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function claudeDesktopModelIds(env = process.env, options = {}) {
|
|
226
|
-
return claudeDesktopModelDefs(env, options).map((model) => model.id);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function claudeModelResolutionDefs(env = process.env, options = {}) {
|
|
230
|
-
const defs = claudeDesktopModelDefs(env, options);
|
|
231
|
-
if (parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES).length) return defs;
|
|
232
|
-
|
|
233
|
-
const upstreamIds = new Set(defs.map((model) => model.upstream));
|
|
234
|
-
const aliases = DEFAULT_CLAUDE_MODEL_ALIAS_DEFS.filter((alias) => upstreamIds.has(alias.upstream));
|
|
235
|
-
return [...defs, ...aliases];
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export function resolveAnthropicModel(model, env = process.env, options = {}) {
|
|
239
|
-
const requestedModel = String(model || "");
|
|
240
|
-
const match = claudeModelResolutionDefs(env, options).find((entry) => entry.id === requestedModel);
|
|
241
|
-
return {
|
|
242
|
-
requestedModel,
|
|
243
|
-
upstreamModel: match?.upstream || requestedModel,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
81
|
export function resolveOpenAIModel(model, env = process.env, options = {}) {
|
|
248
82
|
const requestedModel = String(model || "");
|
|
249
83
|
if (requestedModel !== CODEX_AUTO_REVIEW_MODEL) {
|
|
@@ -269,21 +103,3 @@ export function codexAutoReviewModelStatus(models, env = process.env, options =
|
|
|
269
103
|
reason: available ? "" : "model does not advertise a Responses endpoint",
|
|
270
104
|
};
|
|
271
105
|
}
|
|
272
|
-
|
|
273
|
-
export function anthropicModelInfo(model) {
|
|
274
|
-
return {
|
|
275
|
-
id: model.id,
|
|
276
|
-
type: "model",
|
|
277
|
-
display_name: model.displayName || model.id,
|
|
278
|
-
created_at: model.createdAt || "2025-01-01T00:00:00Z",
|
|
279
|
-
max_input_tokens: model.maxInputTokens || 200000,
|
|
280
|
-
max_tokens: model.maxOutputTokens || 8192,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function claudeDesktopModelsResponse(env = process.env, options = {}) {
|
|
285
|
-
return {
|
|
286
|
-
object: "list",
|
|
287
|
-
data: claudeDesktopModelDefs(env, options).map(anthropicModelInfo),
|
|
288
|
-
};
|
|
289
|
-
}
|
package/src/observability.mjs
CHANGED
|
@@ -13,10 +13,6 @@ const ROUTE_NAMES = Object.freeze([
|
|
|
13
13
|
"responses",
|
|
14
14
|
"responses_compact",
|
|
15
15
|
"models",
|
|
16
|
-
"messages",
|
|
17
|
-
"messages_count_tokens",
|
|
18
|
-
"pm_models",
|
|
19
|
-
"pm_chat_completions",
|
|
20
16
|
"not_found",
|
|
21
17
|
]);
|
|
22
18
|
|
|
@@ -98,10 +94,6 @@ export function classifyAdapterRoute(method, pathname) {
|
|
|
98
94
|
if (method === "POST" && pathname === "/v1/responses") return "responses";
|
|
99
95
|
if (method === "POST" && pathname === "/v1/responses/compact") return "responses_compact";
|
|
100
96
|
if (method === "GET" && pathname === "/v1/models") return "models";
|
|
101
|
-
if (method === "POST" && pathname === "/v1/messages") return "messages";
|
|
102
|
-
if (method === "POST" && pathname === "/v1/messages/count_tokens") return "messages_count_tokens";
|
|
103
|
-
if (method === "GET" && pathname === "/pm-ccdx/models") return "pm_models";
|
|
104
|
-
if (method === "POST" && pathname === "/pm-ccdx/chat/completions") return "pm_chat_completions";
|
|
105
97
|
return "not_found";
|
|
106
98
|
}
|
|
107
99
|
|
|
@@ -119,7 +111,6 @@ function modelRegistryStatus(modelRegistry) {
|
|
|
119
111
|
const result = {
|
|
120
112
|
source: String(modelRegistry?.source || "built-in"),
|
|
121
113
|
models: Array.isArray(modelData) ? modelData.length : 0,
|
|
122
|
-
claude_models: Array.isArray(modelRegistry?.modelDefs) ? modelRegistry.modelDefs.length : 0,
|
|
123
114
|
};
|
|
124
115
|
if (typeof modelRegistry?.cacheState === "string") {
|
|
125
116
|
const savedAtMs = Number(modelRegistry.cacheSavedAtMs);
|
|
@@ -158,15 +149,6 @@ function safeClientRuntimeStatus(client) {
|
|
|
158
149
|
};
|
|
159
150
|
}
|
|
160
151
|
|
|
161
|
-
function safeProfileCurrent(checkProfileCurrent) {
|
|
162
|
-
if (typeof checkProfileCurrent !== "function") return false;
|
|
163
|
-
try {
|
|
164
|
-
return checkProfileCurrent() === true;
|
|
165
|
-
} catch {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
152
|
export function runtimeStatusPayload({
|
|
171
153
|
metrics,
|
|
172
154
|
streamPerformance,
|
|
@@ -174,24 +156,11 @@ export function runtimeStatusPayload({
|
|
|
174
156
|
imagePressure,
|
|
175
157
|
modelRegistry,
|
|
176
158
|
codexClient,
|
|
177
|
-
claudeClient,
|
|
178
159
|
codexModelRegistry,
|
|
179
|
-
claudeModelRegistry,
|
|
180
|
-
claudeMode = "inherited",
|
|
181
|
-
isClaudeProfileCurrent,
|
|
182
160
|
} = {}) {
|
|
183
161
|
const memory = process.memoryUsage();
|
|
184
|
-
const resolvedClaudeMode = claudeMode === "isolated" ? "isolated" : "inherited";
|
|
185
162
|
const codexRuntime = safeClientRuntimeStatus(codexClient);
|
|
186
163
|
const codexModels = modelRegistryStatus(codexModelRegistry || modelRegistry);
|
|
187
|
-
const claudeRuntime = resolvedClaudeMode === "isolated"
|
|
188
|
-
? safeClientRuntimeStatus(claudeClient)
|
|
189
|
-
: codexRuntime;
|
|
190
|
-
const claudeModels = resolvedClaudeMode === "isolated"
|
|
191
|
-
? modelRegistryStatus(claudeModelRegistry)
|
|
192
|
-
: codexModels;
|
|
193
|
-
const claudeProfileCurrent = resolvedClaudeMode === "isolated"
|
|
194
|
-
&& safeProfileCurrent(isClaudeProfileCurrent);
|
|
195
164
|
return {
|
|
196
165
|
...adapterHealthPayload(),
|
|
197
166
|
uptime_ms: Math.round(process.uptime() * 1000),
|
|
@@ -212,14 +181,8 @@ export function runtimeStatusPayload({
|
|
|
212
181
|
models: codexModels,
|
|
213
182
|
profiles: {
|
|
214
183
|
codex: { mode: "legacy", client: codexRuntime, models: codexModels },
|
|
215
|
-
claude: {
|
|
216
|
-
mode: resolvedClaudeMode,
|
|
217
|
-
profile_current: claudeProfileCurrent,
|
|
218
|
-
client: claudeRuntime,
|
|
219
|
-
models: claudeModels,
|
|
220
|
-
},
|
|
221
184
|
},
|
|
222
|
-
routing: profileRouting(
|
|
185
|
+
routing: profileRouting(),
|
|
223
186
|
limits: {
|
|
224
187
|
max_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxBodyBytes,
|
|
225
188
|
max_decoded_body_bytes: OBSERVABILITY_RUNTIME_CONFIG.maxDecodedBodyBytes,
|
|
@@ -4,10 +4,8 @@ import {
|
|
|
4
4
|
saveModelCache,
|
|
5
5
|
} from "./model-cache.mjs";
|
|
6
6
|
import {
|
|
7
|
-
claudeDesktopModelDefsFromCopilotModels,
|
|
8
7
|
codexAutoReviewModelStatus,
|
|
9
8
|
gptModelIdsFromCopilotModels,
|
|
10
|
-
parseModelAliasEnv,
|
|
11
9
|
} from "./models.mjs";
|
|
12
10
|
import { initializeModelRegistry } from "./startup.mjs";
|
|
13
11
|
import { status } from "./status.mjs";
|
|
@@ -36,7 +34,6 @@ function parsedCatalog(result) {
|
|
|
36
34
|
|
|
37
35
|
function createModelRegistry() {
|
|
38
36
|
return {
|
|
39
|
-
modelDefs: undefined,
|
|
40
37
|
models: undefined,
|
|
41
38
|
source: "built-in",
|
|
42
39
|
cacheState: "none",
|
|
@@ -48,12 +45,6 @@ function createModelRegistry() {
|
|
|
48
45
|
};
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
function sameModelDefs(left, right) {
|
|
52
|
-
if (left === right) return true;
|
|
53
|
-
if (!Array.isArray(left) || !Array.isArray(right)) return false;
|
|
54
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
48
|
function safeRefreshError(error, { timedOut = false, timeoutMs } = {}) {
|
|
58
49
|
if (timedOut) return `Model refresh timed out after ${timeoutMs}ms`;
|
|
59
50
|
const raw = String(error?.message || error?.name || "Model refresh failed")
|
|
@@ -66,97 +57,63 @@ function safeRefreshError(error, { timedOut = false, timeoutMs } = {}) {
|
|
|
66
57
|
|
|
67
58
|
export function createProfileModelRuntime({
|
|
68
59
|
codexClient,
|
|
69
|
-
claudeClient = codexClient,
|
|
70
|
-
claudeMode = "inherited",
|
|
71
60
|
codexCredentialFingerprint = "",
|
|
72
|
-
claudeCredentialFingerprint = "",
|
|
73
61
|
home,
|
|
74
62
|
env = process.env,
|
|
75
63
|
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
76
64
|
log = defaultLog,
|
|
77
|
-
onClaudeModelsChanged = () => {},
|
|
78
65
|
autoReviewModelResolver = () => undefined,
|
|
79
66
|
commandName = "ccdx",
|
|
80
67
|
} = {}) {
|
|
81
68
|
if (!codexClient) throw new Error("codexClient is required");
|
|
82
|
-
if (claudeMode !== "inherited" && claudeMode !== "isolated") {
|
|
83
|
-
throw new Error(`Unsupported Claude model mode: ${claudeMode}`);
|
|
84
|
-
}
|
|
85
|
-
if (claudeMode === "isolated" && claudeClient === codexClient) {
|
|
86
|
-
throw new Error("Isolated Claude models require an isolated Claude client");
|
|
87
|
-
}
|
|
88
69
|
|
|
89
70
|
const codexRegistry = createModelRegistry();
|
|
90
|
-
const claudeRegistry = claudeMode === "inherited"
|
|
91
|
-
? codexRegistry
|
|
92
|
-
: createModelRegistry();
|
|
93
|
-
const customClaudeDefs = parseModelAliasEnv(env.CCDX_CLAUDE_MODEL_ALIASES);
|
|
94
|
-
const refreshFlights = new Map();
|
|
95
|
-
const refreshGenerations = new Map();
|
|
96
71
|
const refreshTimeoutMs = Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
97
72
|
? timeoutMs
|
|
98
73
|
: DEFAULT_TIMEOUT_MS;
|
|
74
|
+
let refreshFlight = null;
|
|
75
|
+
let refreshGeneration = 0;
|
|
99
76
|
|
|
100
77
|
function emit(kind, message) {
|
|
101
78
|
if (typeof log === "function") log(kind, message);
|
|
102
79
|
}
|
|
103
80
|
|
|
104
|
-
function
|
|
105
|
-
if (!Array.isArray(modelDefs) || !modelDefs.length) return;
|
|
106
|
-
try {
|
|
107
|
-
const pending = onClaudeModelsChanged(modelDefs);
|
|
108
|
-
pending?.catch?.((error) => emit("warn", `Could not apply refreshed Claude models (${error.message})`));
|
|
109
|
-
} catch (error) {
|
|
110
|
-
emit("warn", `Could not apply refreshed Claude models (${error.message})`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function applyCatalog({ client, models, registry, source, ownsClaudeModels }) {
|
|
81
|
+
function applyCatalog(models, source) {
|
|
115
82
|
if (!isValidModelList(models)) throw new Error("Copilot models response contained no valid models");
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!ownsClaudeModels) return registry.modelDefs;
|
|
121
|
-
const discovered = customClaudeDefs.length
|
|
122
|
-
? customClaudeDefs.map((model) => ({ ...model }))
|
|
123
|
-
: claudeDesktopModelDefsFromCopilotModels(models);
|
|
124
|
-
if (discovered.length) {
|
|
125
|
-
const changed = !sameModelDefs(registry.modelDefs, discovered);
|
|
126
|
-
registry.modelDefs = discovered;
|
|
127
|
-
if (changed) notifyClaudeModels(discovered);
|
|
128
|
-
}
|
|
129
|
-
return registry.modelDefs;
|
|
83
|
+
codexClient.cacheModelEndpoints(models);
|
|
84
|
+
codexRegistry.models = models;
|
|
85
|
+
codexRegistry.source = source;
|
|
86
|
+
return models;
|
|
130
87
|
}
|
|
131
88
|
|
|
132
|
-
function loadCached(
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
:
|
|
136
|
-
|
|
137
|
-
|
|
89
|
+
function loadCached() {
|
|
90
|
+
const cached = loadModelCacheEntry({
|
|
91
|
+
home,
|
|
92
|
+
profile: "codex",
|
|
93
|
+
credentialFingerprint: String(codexCredentialFingerprint || "").trim(),
|
|
94
|
+
});
|
|
138
95
|
if (!cached) return false;
|
|
139
96
|
try {
|
|
140
|
-
applyCatalog(
|
|
141
|
-
|
|
142
|
-
|
|
97
|
+
applyCatalog(cached.models, "cache");
|
|
98
|
+
codexRegistry.cacheState = cached.state;
|
|
99
|
+
codexRegistry.cacheSavedAtMs = cached.savedAtMs;
|
|
143
100
|
return { loaded: true, stale: cached.state === "stale" };
|
|
144
101
|
} catch {
|
|
145
102
|
return false;
|
|
146
103
|
}
|
|
147
104
|
}
|
|
148
105
|
|
|
149
|
-
async function requestCatalog(
|
|
106
|
+
async function requestCatalog() {
|
|
150
107
|
const controller = new AbortController();
|
|
151
108
|
const timer = setTimeout(() => controller.abort(), refreshTimeoutMs);
|
|
152
109
|
try {
|
|
153
|
-
return parsedCatalog(await
|
|
110
|
+
return parsedCatalog(await codexClient.listModels({ signal: controller.signal }));
|
|
154
111
|
} finally {
|
|
155
112
|
clearTimeout(timer);
|
|
156
113
|
}
|
|
157
114
|
}
|
|
158
115
|
|
|
159
|
-
function
|
|
116
|
+
function checkCatalog(models) {
|
|
160
117
|
const autoReview = codexAutoReviewModelStatus(models, env, {
|
|
161
118
|
autoReviewModel: autoReviewModelResolver(),
|
|
162
119
|
});
|
|
@@ -171,166 +128,81 @@ export function createProfileModelRuntime({
|
|
|
171
128
|
}
|
|
172
129
|
}
|
|
173
130
|
|
|
174
|
-
function persist(models,
|
|
175
|
-
const credentialFingerprint = profile === "claude"
|
|
176
|
-
? String(claudeCredentialFingerprint || "").trim()
|
|
177
|
-
: String(codexCredentialFingerprint || "").trim();
|
|
178
|
-
if (profile === "claude" && !credentialFingerprint) return;
|
|
131
|
+
function persist(models, generation) {
|
|
179
132
|
try {
|
|
180
133
|
const saved = saveModelCache(models, {
|
|
181
134
|
home,
|
|
182
|
-
profile,
|
|
183
|
-
credentialFingerprint,
|
|
184
|
-
isCurrent: () =>
|
|
135
|
+
profile: "codex",
|
|
136
|
+
credentialFingerprint: String(codexCredentialFingerprint || "").trim(),
|
|
137
|
+
isCurrent: () => refreshGeneration === generation,
|
|
185
138
|
});
|
|
186
139
|
if (saved) {
|
|
187
|
-
|
|
188
|
-
|
|
140
|
+
codexRegistry.cacheState = "fresh";
|
|
141
|
+
codexRegistry.cacheSavedAtMs = Date.now();
|
|
189
142
|
}
|
|
190
143
|
} catch (error) {
|
|
191
|
-
|
|
192
|
-
emit("warn", `Could not persist the ${label} model cache (${error.message})`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function logClaudeCatalog(modelDefs) {
|
|
197
|
-
if (customClaudeDefs.length) {
|
|
198
|
-
emit("info", "Using CCDX_CLAUDE_MODEL_ALIASES for Claude models");
|
|
199
|
-
} else if (modelDefs?.length) {
|
|
200
|
-
emit("ok", `Refreshed Claude models from GitHub Copilot: ${modelDefs.map((model) => model.id).join(", ")}`);
|
|
201
|
-
} else {
|
|
202
|
-
emit("warn", "Copilot models response contained no Claude models; using built-in Claude models");
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function fallbackLabel(registry, profile) {
|
|
207
|
-
if (claudeMode === "inherited") {
|
|
208
|
-
return registry.modelDefs?.length ? `${registry.source} model list` : "built-in model list";
|
|
144
|
+
emit("warn", `Could not persist the Copilot model cache (${error.message})`);
|
|
209
145
|
}
|
|
210
|
-
if (isValidModelList(registry.models)) return `${registry.source} model list`;
|
|
211
|
-
return profile === "claude" ? "built-in model list" : "no cached model list";
|
|
212
146
|
}
|
|
213
147
|
|
|
214
|
-
async function performRefresh(
|
|
215
|
-
const controllerLabel = claudeMode === "inherited"
|
|
216
|
-
? ""
|
|
217
|
-
: `${profile === "codex" ? "Codex" : "Claude"} `;
|
|
148
|
+
async function performRefresh(generation) {
|
|
218
149
|
try {
|
|
219
|
-
const models = await requestCatalog(
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
ownsClaudeModels,
|
|
228
|
-
});
|
|
229
|
-
persist(models, profile, registry, generation);
|
|
230
|
-
registry.lastError = null;
|
|
231
|
-
registry.lastErrorAtMs = null;
|
|
232
|
-
if (ownsClaudeModels) logClaudeCatalog(modelDefs);
|
|
233
|
-
return modelDefs;
|
|
150
|
+
const models = await requestCatalog();
|
|
151
|
+
if (refreshGeneration !== generation) return codexRegistry.models;
|
|
152
|
+
checkCatalog(models);
|
|
153
|
+
applyCatalog(models, "live");
|
|
154
|
+
persist(models, generation);
|
|
155
|
+
codexRegistry.lastError = null;
|
|
156
|
+
codexRegistry.lastErrorAtMs = null;
|
|
157
|
+
return models;
|
|
234
158
|
} catch (error) {
|
|
235
|
-
if (
|
|
236
|
-
const fallback = fallbackLabel(registry, profile);
|
|
159
|
+
if (refreshGeneration !== generation) return codexRegistry.models;
|
|
237
160
|
const timedOut = error?.name === "AbortError" || error?.code === "ABORT_ERR";
|
|
238
161
|
const diagnostic = safeRefreshError(error, { timedOut, timeoutMs: refreshTimeoutMs });
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
? `${
|
|
243
|
-
:
|
|
244
|
-
emit("warn",
|
|
245
|
-
|
|
162
|
+
codexRegistry.lastError = diagnostic;
|
|
163
|
+
codexRegistry.lastErrorAtMs = Date.now();
|
|
164
|
+
const fallback = isValidModelList(codexRegistry.models)
|
|
165
|
+
? `${codexRegistry.source} model list`
|
|
166
|
+
: "no cached model list";
|
|
167
|
+
emit("warn", timedOut
|
|
168
|
+
? `Model refresh timed out after ${refreshTimeoutMs}ms; using ${fallback}`
|
|
169
|
+
: `Could not refresh model list; using ${fallback} (${diagnostic})`);
|
|
170
|
+
return codexRegistry.models;
|
|
246
171
|
}
|
|
247
172
|
}
|
|
248
173
|
|
|
249
|
-
function refreshProfile(options) {
|
|
250
|
-
const { profile, registry } = options;
|
|
251
|
-
const existing = refreshFlights.get(profile);
|
|
252
|
-
if (existing) return existing.promise;
|
|
253
|
-
|
|
254
|
-
const generation = (refreshGenerations.get(profile) || 0) + 1;
|
|
255
|
-
refreshGenerations.set(profile, generation);
|
|
256
|
-
registry.generation = generation;
|
|
257
|
-
registry.refreshInFlight = true;
|
|
258
|
-
const flight = { generation, promise: null };
|
|
259
|
-
refreshFlights.set(profile, flight);
|
|
260
|
-
flight.promise = performRefresh(options, generation).finally(() => {
|
|
261
|
-
if (refreshFlights.get(profile) === flight) refreshFlights.delete(profile);
|
|
262
|
-
if (refreshGenerations.get(profile) === generation) registry.refreshInFlight = false;
|
|
263
|
-
});
|
|
264
|
-
return flight.promise;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
174
|
function refreshCodex() {
|
|
268
|
-
return
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
function refreshClaude() {
|
|
278
|
-
if (claudeMode === "inherited") return refreshCodex();
|
|
279
|
-
return refreshProfile({
|
|
280
|
-
client: claudeClient,
|
|
281
|
-
profile: "claude",
|
|
282
|
-
registry: claudeRegistry,
|
|
283
|
-
ownsClaudeModels: true,
|
|
284
|
-
checksCodex: false,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function initializeProfile({ client, profile, registry, ownsClaudeModels, refresh }) {
|
|
289
|
-
return initializeModelRegistry({
|
|
290
|
-
loadCached: () => loadCached({ client, profile, registry, ownsClaudeModels }),
|
|
291
|
-
currentModelDefs: () => registry.modelDefs,
|
|
292
|
-
refresh,
|
|
175
|
+
if (refreshFlight) return refreshFlight;
|
|
176
|
+
const generation = refreshGeneration + 1;
|
|
177
|
+
refreshGeneration = generation;
|
|
178
|
+
codexRegistry.generation = generation;
|
|
179
|
+
codexRegistry.refreshInFlight = true;
|
|
180
|
+
const flight = performRefresh(generation).finally(() => {
|
|
181
|
+
if (refreshFlight === flight) refreshFlight = null;
|
|
182
|
+
if (refreshGeneration === generation) codexRegistry.refreshInFlight = false;
|
|
293
183
|
});
|
|
184
|
+
refreshFlight = flight;
|
|
185
|
+
return flight;
|
|
294
186
|
}
|
|
295
187
|
|
|
296
188
|
async function initialize() {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
registry: codexRegistry,
|
|
301
|
-
ownsClaudeModels: claudeMode === "inherited",
|
|
189
|
+
const initialized = await initializeModelRegistry({
|
|
190
|
+
loadCached,
|
|
191
|
+
currentModelDefs: () => codexRegistry.models,
|
|
302
192
|
refresh: refreshCodex,
|
|
303
193
|
});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return { codex: shared, claude: shared };
|
|
307
|
-
}
|
|
308
|
-
const claudeInitialization = initializeProfile({
|
|
309
|
-
client: claudeClient,
|
|
310
|
-
profile: "claude",
|
|
311
|
-
registry: claudeRegistry,
|
|
312
|
-
ownsClaudeModels: true,
|
|
313
|
-
refresh: refreshClaude,
|
|
314
|
-
});
|
|
315
|
-
const [codex, claude] = await Promise.all([codexInitialization, claudeInitialization]);
|
|
316
|
-
return { codex, claude };
|
|
194
|
+
const { modelDefs: models, ...state } = initialized;
|
|
195
|
+
return { codex: { ...state, models } };
|
|
317
196
|
}
|
|
318
197
|
|
|
319
198
|
async function refreshAll() {
|
|
320
|
-
|
|
321
|
-
const shared = await refreshCodex();
|
|
322
|
-
return { codex: shared, claude: shared };
|
|
323
|
-
}
|
|
324
|
-
const [codex, claude] = await Promise.all([refreshCodex(), refreshClaude()]);
|
|
325
|
-
return { codex, claude };
|
|
199
|
+
return { codex: await refreshCodex() };
|
|
326
200
|
}
|
|
327
201
|
|
|
328
202
|
return {
|
|
329
203
|
codexRegistry,
|
|
330
|
-
claudeRegistry,
|
|
331
204
|
initialize,
|
|
332
205
|
refreshAll,
|
|
333
206
|
refreshCodex,
|
|
334
|
-
refreshClaude,
|
|
335
207
|
};
|
|
336
208
|
}
|