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/bin/cli.mjs
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { ensureAuth, openCodex } from "../src/launcher.mjs";
|
|
4
4
|
import { ensureCodexConfig } from "../src/config.mjs";
|
|
5
|
-
import { ensureClaudeConfig } from "../src/claude-config.mjs";
|
|
6
|
-
import { applyClaudeDesktopConfig, formatClaudeDesktopApplyResult, generatedClaudeDesktopApiKey, loadManagedClaudeDesktopApiKey, syncManagedClaudeDesktopModels } from "../src/claude-desktop-config.mjs";
|
|
7
5
|
import { startAdapter } from "../src/adapter.mjs";
|
|
8
6
|
import { defaultCopilotClient, refreshVSCodeVersion } from "../src/copilot.mjs";
|
|
9
|
-
import { claudeDesktopModelIds } from "../src/models.mjs";
|
|
10
7
|
import { status } from "../src/status.mjs";
|
|
11
8
|
import { configureLogging } from "../src/log.mjs";
|
|
12
9
|
import { flushUsageWrites, printUsageSummary } from "../src/usage.mjs";
|
|
@@ -17,7 +14,6 @@ import { assertSafeAdapterHost, isLoopbackHost } from "../src/security.mjs";
|
|
|
17
14
|
import { runInBackground } from "../src/startup.mjs";
|
|
18
15
|
import { cliCommandName, cliHelp, parseAdapterProbeOptions, parseCliArgs, parseRuntimeOptions } from "../src/cli-options.mjs";
|
|
19
16
|
import { formatAdapterStatus, readAdapterStatus } from "../src/cli-status.mjs";
|
|
20
|
-
import { formatCliError } from "../src/cli-error.mjs";
|
|
21
17
|
import { closeHttpServer } from "../src/shutdown.mjs";
|
|
22
18
|
import { runAutoReviewModelCommand } from "../src/auto-review-model.mjs";
|
|
23
19
|
import { autoReviewModelPreference } from "../src/user-settings.mjs";
|
|
@@ -54,6 +50,15 @@ if (CLI.command === "usage") {
|
|
|
54
50
|
});
|
|
55
51
|
process.exit(0);
|
|
56
52
|
}
|
|
53
|
+
if (CLI.command === "retired") {
|
|
54
|
+
console.error(status("err", `${CLI.integration} integration was retired in ccdx 0.7.0.`));
|
|
55
|
+
if (CLI.integration === "PM Studio") {
|
|
56
|
+
console.error(status("info", "Update or reinstall PM Studio to remove an older CCDX app patch."));
|
|
57
|
+
} else {
|
|
58
|
+
console.error(status("info", "Remove any old CCDX endpoint from the client before using a different provider."));
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
57
62
|
if (CLI.command === "auth") {
|
|
58
63
|
try {
|
|
59
64
|
const result = await runAuthCommand({
|
|
@@ -67,12 +72,6 @@ if (CLI.command === "auth") {
|
|
|
67
72
|
output: process.stdout,
|
|
68
73
|
});
|
|
69
74
|
if (result.output) console.log(result.output);
|
|
70
|
-
if (result.action === "login") {
|
|
71
|
-
if (!result.changed) {
|
|
72
|
-
console.log(status("ok", `Claude profile is already configured for ${result.identity?.login || result.identity?.id || "the saved account"}`));
|
|
73
|
-
}
|
|
74
|
-
console.log(status("warn", `Restart the running ${CLI_NAME} adapter to activate the Claude profile`));
|
|
75
|
-
}
|
|
76
75
|
process.exit(0);
|
|
77
76
|
} catch (e) {
|
|
78
77
|
console.error(status("err", e.message));
|
|
@@ -133,31 +132,6 @@ if (CLI.command === "update") {
|
|
|
133
132
|
process.exit(1);
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
|
-
if (CLI.command === "pms") {
|
|
137
|
-
try {
|
|
138
|
-
if (CLI.action === "status") {
|
|
139
|
-
const { runPmStudioStatus } = await import("../src/pm-studio-status.mjs");
|
|
140
|
-
const result = await runPmStudioStatus({
|
|
141
|
-
commandName: CLI_NAME,
|
|
142
|
-
format: CLI.outputFormat || "auto",
|
|
143
|
-
output: process.stdout,
|
|
144
|
-
});
|
|
145
|
-
process.exit(result.ok ? 0 : 1);
|
|
146
|
-
}
|
|
147
|
-
if (CLI.action === "restore") {
|
|
148
|
-
const { runPmStudioRestore } = await import("../src/pm-studio-setup.mjs");
|
|
149
|
-
await runPmStudioRestore({ commandName: CLI_NAME });
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
const { runPmStudioSetup } = await import("../src/pm-studio-setup.mjs");
|
|
153
|
-
await runPmStudioSetup({ commandName: CLI_NAME });
|
|
154
|
-
process.exit(0);
|
|
155
|
-
} catch (e) {
|
|
156
|
-
console.error(formatCliError(e));
|
|
157
|
-
process.exit(1);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
135
|
let RUNTIME;
|
|
162
136
|
try {
|
|
163
137
|
RUNTIME = parseRuntimeOptions(process.env);
|
|
@@ -170,7 +144,6 @@ const ADAPTER_PORT = RUNTIME.adapterPort;
|
|
|
170
144
|
const ADAPTER_HOST = RUNTIME.adapterHost;
|
|
171
145
|
const MODEL_REFRESH_TIMEOUT_MS = RUNTIME.modelRefreshTimeoutMs;
|
|
172
146
|
const EXISTING_ADAPTER_TIMEOUT_MS = RUNTIME.existingAdapterTimeoutMs;
|
|
173
|
-
const CONFIGURE_CLAUDE_DESKTOP = CLI.configureClaudeDesktop || process.env.CCDX_CONFIGURE_CLAUDE_DESKTOP === "1";
|
|
174
147
|
const LOGGING = configureLogging();
|
|
175
148
|
let activeServer = null;
|
|
176
149
|
let modelRefreshTimer = null;
|
|
@@ -181,24 +154,6 @@ if (LOGGING.filePath) {
|
|
|
181
154
|
if (LOGGING.level === "debug") console.log(status("debug", "Debug logging enabled"));
|
|
182
155
|
}
|
|
183
156
|
|
|
184
|
-
function currentClaudeDesktopApiKey() {
|
|
185
|
-
return String(process.env.CCDX_CLAUDE_DESKTOP_API_KEY || process.env.CCDX_PROXY_API_KEY || "").trim();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function syncClaudeDesktopProfileModels(modelDefs) {
|
|
189
|
-
const result = syncManagedClaudeDesktopModels({
|
|
190
|
-
port: ADAPTER_PORT,
|
|
191
|
-
host: ADAPTER_HOST,
|
|
192
|
-
modelIds: claudeDesktopModelIds(process.env, { modelDefs }),
|
|
193
|
-
});
|
|
194
|
-
if (result.updated) {
|
|
195
|
-
console.log(status("ok", `Updated Claude App profile models: ${result.modelIds.join(", ")}`));
|
|
196
|
-
} else if (result.error) {
|
|
197
|
-
console.log(status("warn", `Could not update Claude App profile models (${result.error.message})`));
|
|
198
|
-
}
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
157
|
async function reuseRunningAdapterIfAvailable() {
|
|
203
158
|
const running = await checkRunningAdapter({
|
|
204
159
|
port: ADAPTER_PORT,
|
|
@@ -213,25 +168,6 @@ async function reuseRunningAdapterIfAvailable() {
|
|
|
213
168
|
|
|
214
169
|
console.log(status("ok", `Using existing adapter at ${running.baseUrl}`));
|
|
215
170
|
ensureCodexConfig(ADAPTER_PORT, { host: ADAPTER_HOST });
|
|
216
|
-
ensureClaudeConfig(ADAPTER_PORT, { host: ADAPTER_HOST });
|
|
217
|
-
|
|
218
|
-
if (CONFIGURE_CLAUDE_DESKTOP) {
|
|
219
|
-
const claudeDesktopApiKey = currentClaudeDesktopApiKey();
|
|
220
|
-
if (claudeDesktopApiKey) {
|
|
221
|
-
const result = applyClaudeDesktopConfig({
|
|
222
|
-
port: ADAPTER_PORT,
|
|
223
|
-
host: ADAPTER_HOST,
|
|
224
|
-
gatewayApiKey: claudeDesktopApiKey,
|
|
225
|
-
modelIds: claudeDesktopModelIds(process.env),
|
|
226
|
-
});
|
|
227
|
-
console.log(status("ok", `Configured Claude App gateway profile at ${result.baseUrl}`));
|
|
228
|
-
console.log(formatClaudeDesktopApplyResult(result));
|
|
229
|
-
} else {
|
|
230
|
-
console.log(status("warn", "Existing adapter is running; skip Claude App profile update unless CCDX_CLAUDE_DESKTOP_API_KEY or CCDX_PROXY_API_KEY is set"));
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
console.log(status("ok", "Claude App support available with --configure-claude-app"));
|
|
234
|
-
}
|
|
235
171
|
|
|
236
172
|
await openCodex();
|
|
237
173
|
console.log(`
|
|
@@ -249,14 +185,13 @@ if (CLI.command === "doctor") {
|
|
|
249
185
|
host: ADAPTER_HOST,
|
|
250
186
|
online: CLI.online,
|
|
251
187
|
compat: CLI.compat,
|
|
252
|
-
profile: CLI.profile,
|
|
253
188
|
});
|
|
254
189
|
process.exit(checks.some((check) => check.kind === "err") ? 1 : 0);
|
|
255
190
|
}
|
|
256
191
|
|
|
257
192
|
console.log(`
|
|
258
193
|
${CLI_BANNER}
|
|
259
|
-
Use Codex Desktop
|
|
194
|
+
Use Codex Desktop and ChatGPT App with GitHub Copilot
|
|
260
195
|
`);
|
|
261
196
|
|
|
262
197
|
async function printUpdateNotice() {
|
|
@@ -279,84 +214,35 @@ try {
|
|
|
279
214
|
await ensureAuth();
|
|
280
215
|
|
|
281
216
|
const profileRuntime = createProfileRuntime({ codexClient: defaultCopilotClient });
|
|
282
|
-
if (profileRuntime.claudeMode === "isolated") {
|
|
283
|
-
if (profileRuntime.claudeProfile.valid) {
|
|
284
|
-
const account = profileRuntime.claudeProfile.identity?.login
|
|
285
|
-
|| profileRuntime.claudeProfile.identity?.id
|
|
286
|
-
|| "the saved account";
|
|
287
|
-
console.log(status("ok", `Claude requests will use the isolated GitHub account ${account}`));
|
|
288
|
-
} else {
|
|
289
|
-
console.log(status("warn", `Claude profile is configured but invalid (${profileRuntime.claudeProfile.reason}); Codex remains available, but Claude requests will fail until you run ${CLI_NAME} auth login claude --reauth and restart`));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
217
|
const profileModels = createProfileModelRuntime({
|
|
294
218
|
codexClient: profileRuntime.codexClient,
|
|
295
|
-
claudeClient: profileRuntime.claudeClient,
|
|
296
|
-
claudeMode: profileRuntime.claudeMode,
|
|
297
219
|
codexCredentialFingerprint: profileRuntime.codexCredentialFingerprint,
|
|
298
|
-
claudeCredentialFingerprint: profileRuntime.claudeCredentialFingerprint,
|
|
299
220
|
timeoutMs: MODEL_REFRESH_TIMEOUT_MS,
|
|
300
221
|
commandName: CLI_NAME,
|
|
301
222
|
autoReviewModelResolver: () => autoReviewModelPreference().model,
|
|
302
|
-
onClaudeModelsChanged: syncClaudeDesktopProfileModels,
|
|
303
223
|
});
|
|
304
224
|
|
|
305
225
|
// Refresh the VS Code version in the background; fallback is non-blocking.
|
|
306
226
|
void refreshVSCodeVersion();
|
|
307
|
-
|
|
308
|
-
const claudeDesktopModelDefs = modelInitialization.claude.modelDefs;
|
|
309
|
-
syncClaudeDesktopProfileModels(claudeDesktopModelDefs);
|
|
227
|
+
await profileModels.initialize();
|
|
310
228
|
|
|
311
229
|
if (!isLoopbackHost(ADAPTER_HOST)) {
|
|
312
230
|
console.log(status("warn", `ADAPTER_HOST=${ADAPTER_HOST} exposes the adapter beyond loopback because CCDX_ALLOW_LAN=1 is set. Use only on trusted networks.`));
|
|
313
231
|
}
|
|
314
232
|
|
|
315
|
-
const configuredClaudeDesktopApiKey = currentClaudeDesktopApiKey();
|
|
316
|
-
const restoredClaudeDesktopApiKey = configuredClaudeDesktopApiKey ? "" : loadManagedClaudeDesktopApiKey({
|
|
317
|
-
port: ADAPTER_PORT,
|
|
318
|
-
host: ADAPTER_HOST,
|
|
319
|
-
});
|
|
320
|
-
if (restoredClaudeDesktopApiKey) {
|
|
321
|
-
console.log(status("ok", "Restored Claude App gateway key from the managed profile"));
|
|
322
|
-
}
|
|
323
|
-
const claudeDesktopApiKey = configuredClaudeDesktopApiKey
|
|
324
|
-
|| restoredClaudeDesktopApiKey
|
|
325
|
-
|| (CONFIGURE_CLAUDE_DESKTOP ? generatedClaudeDesktopApiKey() : "");
|
|
326
|
-
|
|
327
233
|
// Start the in-process adapter.
|
|
328
234
|
activeServer = await startAdapter(ADAPTER_PORT, ADAPTER_HOST, {
|
|
329
|
-
claudeDesktopApiKey,
|
|
330
|
-
claudeDesktopModelDefs,
|
|
331
235
|
autoReviewModelResolver: () => autoReviewModelPreference().model,
|
|
332
236
|
codexClient: profileRuntime.codexClient,
|
|
333
|
-
claudeClient: profileRuntime.claudeClient,
|
|
334
|
-
claudeProfile: profileRuntime.claudeProfile,
|
|
335
237
|
codexModelRegistry: profileModels.codexRegistry,
|
|
336
|
-
claudeModelRegistry: profileModels.claudeRegistry,
|
|
337
|
-
claudeMode: profileRuntime.claudeMode,
|
|
338
|
-
isClaudeProfileCurrent: profileRuntime.isClaudeProfileCurrent,
|
|
339
238
|
showRequestId: CLI.showRequestId,
|
|
340
239
|
upstreamTimeoutMs: RUNTIME.upstreamTimeoutMs,
|
|
341
240
|
streamHandshakeTimeoutMs: RUNTIME.streamHandshakeTimeoutMs,
|
|
342
241
|
streamIdleTimeoutMs: RUNTIME.streamIdleTimeoutMs,
|
|
343
242
|
});
|
|
344
243
|
|
|
345
|
-
// Point Codex and
|
|
244
|
+
// Point Codex and compatible ChatGPT App builds at the adapter.
|
|
346
245
|
ensureCodexConfig(ADAPTER_PORT, { host: ADAPTER_HOST });
|
|
347
|
-
ensureClaudeConfig(ADAPTER_PORT, { host: ADAPTER_HOST });
|
|
348
|
-
if (CONFIGURE_CLAUDE_DESKTOP) {
|
|
349
|
-
const result = applyClaudeDesktopConfig({
|
|
350
|
-
port: ADAPTER_PORT,
|
|
351
|
-
host: ADAPTER_HOST,
|
|
352
|
-
gatewayApiKey: claudeDesktopApiKey,
|
|
353
|
-
modelIds: claudeDesktopModelIds(process.env, { modelDefs: claudeDesktopModelDefs }),
|
|
354
|
-
});
|
|
355
|
-
console.log(status("ok", `Configured Claude App gateway profile at ${result.baseUrl}`));
|
|
356
|
-
console.log(formatClaudeDesktopApplyResult(result));
|
|
357
|
-
} else {
|
|
358
|
-
console.log(status("ok", "Claude App support available with --configure-claude-app"));
|
|
359
|
-
}
|
|
360
246
|
|
|
361
247
|
// Launch Codex when available.
|
|
362
248
|
await openCodex();
|
|
@@ -371,10 +257,8 @@ try {
|
|
|
371
257
|
modelRefreshTimer.unref?.();
|
|
372
258
|
}
|
|
373
259
|
|
|
374
|
-
const readyClients = ["Codex App", "Claude Code"];
|
|
375
|
-
if (claudeDesktopApiKey) readyClients.splice(1, 0, "Claude App");
|
|
376
260
|
console.log(`
|
|
377
|
-
${status("ok",
|
|
261
|
+
${status("ok", "Ready, Codex App and ChatGPT App are ready to use")}
|
|
378
262
|
|
|
379
263
|
Adapter: ${adapterBaseUrl(ADAPTER_HOST, ADAPTER_PORT)}
|
|
380
264
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-copilot-dx",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Use Codex Desktop
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Use Codex Desktop and ChatGPT App with GitHub Copilot",
|
|
5
5
|
"author": "Dale Xiao",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -18,11 +18,6 @@
|
|
|
18
18
|
"codex",
|
|
19
19
|
"copilot",
|
|
20
20
|
"github",
|
|
21
|
-
"claude",
|
|
22
|
-
"claude-app",
|
|
23
|
-
"claude-code",
|
|
24
|
-
"claude-desktop",
|
|
25
|
-
"anthropic",
|
|
26
21
|
"openai",
|
|
27
22
|
"proxy",
|
|
28
23
|
"responses-api"
|
|
@@ -51,7 +46,6 @@
|
|
|
51
46
|
"prepublishOnly": "npm run verify"
|
|
52
47
|
},
|
|
53
48
|
"dependencies": {
|
|
54
|
-
"gpt-tokenizer": "^3.4.0",
|
|
55
49
|
"sharp": "^0.35.3"
|
|
56
50
|
}
|
|
57
51
|
}
|
package/scripts/benchmark.mjs
CHANGED
|
@@ -8,7 +8,6 @@ import { setTimeout as delay } from "node:timers/promises";
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import sharp from "sharp";
|
|
10
10
|
import { optimizeImagesInBody, prepareResponsesPayload } from "../src/image-optimization.mjs";
|
|
11
|
-
import { createPmStudioModelRouter } from "../src/profile-routing.mjs";
|
|
12
11
|
|
|
13
12
|
const packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
14
13
|
|
|
@@ -23,14 +22,6 @@ function runProbe(source) {
|
|
|
23
22
|
return JSON.parse(result.stdout.trim());
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
function median(values) {
|
|
27
|
-
const ordered = [...values].sort((left, right) => left - right);
|
|
28
|
-
const middle = Math.floor(ordered.length / 2);
|
|
29
|
-
return ordered.length % 2 === 0
|
|
30
|
-
? (ordered[middle - 1] + ordered[middle]) / 2
|
|
31
|
-
: ordered[middle];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
25
|
function sseLinearityProbe() {
|
|
35
26
|
return runProbe(`
|
|
36
27
|
import { webStreamLines } from "./src/stream.mjs";
|
|
@@ -220,38 +211,6 @@ function largePayloadProbe(targetMiB, concurrency) {
|
|
|
220
211
|
}
|
|
221
212
|
}
|
|
222
213
|
|
|
223
|
-
function largeTokenCountProbe(mode) {
|
|
224
|
-
const samples = Array.from({ length: 3 }, () => runProbe(`
|
|
225
|
-
const text = "a ".repeat(2 * 1024 * 1024);
|
|
226
|
-
const started = performance.now();
|
|
227
|
-
let tokens;
|
|
228
|
-
if (${JSON.stringify(mode)} === "proxy") {
|
|
229
|
-
const { countTokens } = await import("./src/anthropic.mjs");
|
|
230
|
-
tokens = (await countTokens({ messages: [{ role: "user", content: text }] })).input_tokens;
|
|
231
|
-
} else {
|
|
232
|
-
const { encode } = await import("gpt-tokenizer");
|
|
233
|
-
const encoded = encode(text);
|
|
234
|
-
tokens = encoded.length;
|
|
235
|
-
}
|
|
236
|
-
const memory = process.memoryUsage();
|
|
237
|
-
process.stdout.write(JSON.stringify({
|
|
238
|
-
input_mib: +(Buffer.byteLength(text) / 1048576).toFixed(1),
|
|
239
|
-
tokens,
|
|
240
|
-
elapsed_ms: +(performance.now() - started).toFixed(1),
|
|
241
|
-
rss_mib: +(memory.rss / 1048576).toFixed(1),
|
|
242
|
-
heap_used_mib: +(memory.heapUsed / 1048576).toFixed(1),
|
|
243
|
-
}));
|
|
244
|
-
`));
|
|
245
|
-
return {
|
|
246
|
-
input_mib: samples[0].input_mib,
|
|
247
|
-
tokens: samples[0].tokens,
|
|
248
|
-
elapsed_ms: +median(samples.map((sample) => sample.elapsed_ms)).toFixed(1),
|
|
249
|
-
rss_mib: +median(samples.map((sample) => sample.rss_mib)).toFixed(1),
|
|
250
|
-
heap_used_mib: +median(samples.map((sample) => sample.heap_used_mib)).toFixed(1),
|
|
251
|
-
samples: samples.length,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
214
|
function toolOutputParseCacheProbe() {
|
|
256
215
|
return runProbe(`
|
|
257
216
|
import { enforceResponsesImageLimit } from "./src/responses-image-limit.mjs";
|
|
@@ -355,29 +314,6 @@ const adapterImport = runProbe(`
|
|
|
355
314
|
}));
|
|
356
315
|
`);
|
|
357
316
|
|
|
358
|
-
const tokenCount = runProbe(`
|
|
359
|
-
const importStarted = performance.now();
|
|
360
|
-
const { countTokens } = await import("./src/anthropic.mjs");
|
|
361
|
-
const importMs = performance.now() - importStarted;
|
|
362
|
-
const body = { messages: [{ role: "user", content: "a ".repeat(50000) }] };
|
|
363
|
-
const firstStarted = performance.now();
|
|
364
|
-
const first = await countTokens(body);
|
|
365
|
-
const firstMs = performance.now() - firstStarted;
|
|
366
|
-
const warmStarted = performance.now();
|
|
367
|
-
const warm = await countTokens(body);
|
|
368
|
-
const warmMs = performance.now() - warmStarted;
|
|
369
|
-
globalThis.gc?.();
|
|
370
|
-
const memory = process.memoryUsage();
|
|
371
|
-
console.log(JSON.stringify({
|
|
372
|
-
module_import_ms: +importMs.toFixed(1),
|
|
373
|
-
first_call_ms: +firstMs.toFixed(1),
|
|
374
|
-
warm_call_ms: +warmMs.toFixed(1),
|
|
375
|
-
input_tokens: first.input_tokens,
|
|
376
|
-
deterministic: first.input_tokens === warm.input_tokens,
|
|
377
|
-
rss_mib: +(memory.rss / 1048576).toFixed(1),
|
|
378
|
-
}));
|
|
379
|
-
`);
|
|
380
|
-
|
|
381
317
|
const repeatedImage = "data:image/png;base64,QUJDRA==";
|
|
382
318
|
const duplicateBody = {
|
|
383
319
|
input: [{
|
|
@@ -402,34 +338,6 @@ const duplicateImages = {
|
|
|
402
338
|
optimizer_calls: duplicateCalls,
|
|
403
339
|
};
|
|
404
340
|
|
|
405
|
-
const routingCatalog = {
|
|
406
|
-
data: Array.from({ length: 1000 }, (_, index) => index % 5 === 0
|
|
407
|
-
? {
|
|
408
|
-
id: `claude-benchmark-${index}`,
|
|
409
|
-
vendor: "Anthropic",
|
|
410
|
-
model_picker_enabled: true,
|
|
411
|
-
supported_endpoints: ["/chat/completions"],
|
|
412
|
-
}
|
|
413
|
-
: { id: `gpt-benchmark-${index}`, vendor: "OpenAI", supported_endpoints: ["/responses"] }),
|
|
414
|
-
};
|
|
415
|
-
const modelRouter = createPmStudioModelRouter({
|
|
416
|
-
getCatalog: () => routingCatalog,
|
|
417
|
-
isClaudeEnabled: () => true,
|
|
418
|
-
});
|
|
419
|
-
const routingIterations = 100000;
|
|
420
|
-
const routingStarted = performance.now();
|
|
421
|
-
let claudeClassifications = 0;
|
|
422
|
-
for (let index = 0; index < routingIterations; index += 1) {
|
|
423
|
-
if (modelRouter.classify("claude-benchmark-0") === "claude") claudeClassifications += 1;
|
|
424
|
-
}
|
|
425
|
-
const pmModelRouting = {
|
|
426
|
-
catalog_models: routingCatalog.data.length,
|
|
427
|
-
iterations: routingIterations,
|
|
428
|
-
elapsed_ms: +(performance.now() - routingStarted).toFixed(1),
|
|
429
|
-
claude_classifications: claudeClassifications,
|
|
430
|
-
availability_rebuilds: modelRouter.diagnostics().rebuilds,
|
|
431
|
-
};
|
|
432
|
-
|
|
433
341
|
const pixels = deterministicPixels(1024 * 1024 * 3, 0x87654321);
|
|
434
342
|
const webp = await sharp(pixels, { raw: { width: 1024, height: 1024, channels: 3 } })
|
|
435
343
|
.webp({ quality: 100 })
|
|
@@ -474,18 +382,12 @@ const report = {
|
|
|
474
382
|
? "Relative performance and resource checks; absolute timings vary by machine."
|
|
475
383
|
: "Report-only benchmark; timings vary by machine and are not pass/fail thresholds.",
|
|
476
384
|
adapter_import: adapterImport,
|
|
477
|
-
token_count_100kb: tokenCount,
|
|
478
385
|
duplicate_images: duplicateImages,
|
|
479
|
-
pm_model_routing: pmModelRouting,
|
|
480
386
|
oversized_payload: oversizedPayload,
|
|
481
387
|
};
|
|
482
388
|
|
|
483
389
|
if (checkMode) {
|
|
484
390
|
report.sse_linearity = sseLinearityProbe();
|
|
485
|
-
report.large_token_count = {
|
|
486
|
-
proxy: largeTokenCountProbe("proxy"),
|
|
487
|
-
materialized_array: largeTokenCountProbe("array"),
|
|
488
|
-
};
|
|
489
391
|
report.tool_output_parse_cache = toolOutputParseCacheProbe();
|
|
490
392
|
report.large_payload_peak = [
|
|
491
393
|
largePayloadProbe(5, 4),
|
|
@@ -507,22 +409,6 @@ console.log(JSON.stringify(report, null, 2));
|
|
|
507
409
|
|
|
508
410
|
if (checkMode) {
|
|
509
411
|
const failures = [];
|
|
510
|
-
if (report.pm_model_routing.availability_rebuilds !== 1
|
|
511
|
-
|| report.pm_model_routing.claude_classifications !== report.pm_model_routing.iterations) {
|
|
512
|
-
failures.push("PM Studio model routing did not reuse the memoized catalog classification");
|
|
513
|
-
}
|
|
514
|
-
const proxy = report.large_token_count.proxy;
|
|
515
|
-
const array = report.large_token_count.materialized_array;
|
|
516
|
-
if (proxy.tokens !== array.tokens) failures.push(`token counts differ: proxy=${proxy.tokens} array=${array.tokens}`);
|
|
517
|
-
if (proxy.elapsed_ms > array.elapsed_ms * 1.25) {
|
|
518
|
-
failures.push(`token counter is materially slower: proxy=${proxy.elapsed_ms}ms array=${array.elapsed_ms}ms`);
|
|
519
|
-
}
|
|
520
|
-
if (proxy.rss_mib > array.rss_mib * 0.9) {
|
|
521
|
-
failures.push(`token counter RSS regression: proxy=${proxy.rss_mib}MiB array=${array.rss_mib}MiB`);
|
|
522
|
-
}
|
|
523
|
-
if (proxy.heap_used_mib > array.heap_used_mib * 0.75) {
|
|
524
|
-
failures.push(`token counter heap regression: proxy=${proxy.heap_used_mib}MiB array=${array.heap_used_mib}MiB`);
|
|
525
|
-
}
|
|
526
412
|
if (report.tool_output_parse_cache.parse_calls !== 1) {
|
|
527
413
|
failures.push(`stringified tool output parsed ${report.tool_output_parse_cache.parse_calls} times instead of once`);
|
|
528
414
|
}
|
package/src/adapter.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
-
import { createAnthropicCountTokensHandler, createAnthropicMessagesHandler } from "./anthropic-handler.mjs";
|
|
3
2
|
import { defaultCopilotClient } from "./copilot.mjs";
|
|
4
3
|
import { isValidModelList } from "./model-cache.mjs";
|
|
5
|
-
import { claudeDesktopModelsResponse } from "./models.mjs";
|
|
6
4
|
import {
|
|
7
5
|
ADAPTER_STATUS_PATH,
|
|
8
6
|
classifyAdapterRoute,
|
|
@@ -11,11 +9,9 @@ import {
|
|
|
11
9
|
runtimeStatusPayload,
|
|
12
10
|
} from "./observability.mjs";
|
|
13
11
|
import { createRequestId, runWithRequestContext } from "./request-context.mjs";
|
|
14
|
-
import { createPmStudioRelayHandler, PM_STUDIO_RELAY_PREFIX } from "./pm-studio-relay.mjs";
|
|
15
12
|
import { createStreamPerformanceMetrics } from "./stream-performance.mjs";
|
|
16
13
|
import { status } from "./status.mjs";
|
|
17
14
|
import { createTerminalActivityIndicator } from "./terminal-activity.mjs";
|
|
18
|
-
import { createTokenCounterService } from "./token-counter-service.mjs";
|
|
19
15
|
import { ADAPTER_HEALTH_PATH, adapterHealthPayload } from "./running-adapter.mjs";
|
|
20
16
|
import { createResponsesCompactHandler, createResponsesHandler } from "./responses-handler.mjs";
|
|
21
17
|
import { createResponsesImagePressureController } from "./responses-image-pressure.mjs";
|
|
@@ -63,17 +59,19 @@ export function requestPath(reqUrl) {
|
|
|
63
59
|
return new URL(reqUrl || "/", "http://localhost").pathname;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
function sendRetiredClaudeSurface(res) {
|
|
63
|
+
res.writeHead(410, {
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
"Cache-Control": "no-store",
|
|
66
|
+
});
|
|
67
|
+
res.end(JSON.stringify({
|
|
68
|
+
type: "error",
|
|
69
|
+
error: {
|
|
70
|
+
type: "not_found_error",
|
|
71
|
+
code: "ccdx_claude_retired",
|
|
72
|
+
message: "Claude App and Claude Code support was retired in ccdx 0.7.0.",
|
|
73
|
+
},
|
|
74
|
+
}));
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
function modelListCanUseLastKnownGood(statusCode) {
|
|
@@ -93,50 +91,18 @@ function sendLastKnownGoodModels(res, modelRegistry) {
|
|
|
93
91
|
// Shared request handler. Keeping this separate from the listener makes the
|
|
94
92
|
// complete HTTP routing layer testable without opening a local port.
|
|
95
93
|
export function createAdapterHandler(options = {}) {
|
|
96
|
-
const claudeDesktopApiKey = options.claudeDesktopApiKey
|
|
97
|
-
|| process.env.CCDX_CLAUDE_DESKTOP_API_KEY
|
|
98
|
-
|| process.env.CCDX_PROXY_API_KEY
|
|
99
|
-
|| "";
|
|
100
94
|
const codexClient = options.codexClient || defaultCopilotClient;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
if (options.claudeMode === "isolated"
|
|
106
|
-
&& (!options.claudeClient || options.claudeClient === codexClient)) {
|
|
107
|
-
throw new Error("Isolated Claude mode requires a distinct Claude client");
|
|
108
|
-
}
|
|
109
|
-
if (options.claudeMode === "inherited"
|
|
110
|
-
&& options.claudeClient && options.claudeClient !== codexClient) {
|
|
111
|
-
throw new Error("Inherited Claude mode requires the Codex client");
|
|
112
|
-
}
|
|
113
|
-
const claudeClient = options.claudeClient || codexClient;
|
|
114
|
-
// chatCompletionsFn is retained as a compatibility injection and continues to
|
|
115
|
-
// override both surfaces in existing tests and embedders. New dual-profile
|
|
116
|
-
// callers should pass codexClient/claudeClient instead.
|
|
117
|
-
const codexChatCompletionsFn = options.codexChatCompletionsFn
|
|
95
|
+
// Keep the established injection names because Responses may still use the
|
|
96
|
+
// internal Chat Completions compatibility path for upstream models that need it.
|
|
97
|
+
const chatCompletionsFn = options.codexChatCompletionsFn
|
|
118
98
|
|| options.chatCompletionsFn
|
|
119
99
|
|| codexClient.chatCompletions;
|
|
120
|
-
const claudeChatCompletionsFn = options.claudeChatCompletionsFn
|
|
121
|
-
|| options.chatCompletionsFn
|
|
122
|
-
|| claudeClient.chatCompletions;
|
|
123
100
|
const responsesFn = options.responsesFn || codexClient.responses;
|
|
124
101
|
const responsesCompactFn = options.responsesCompactFn || codexClient.responsesCompact;
|
|
125
102
|
const listModelsFn = options.listModelsFn || codexClient.listModels;
|
|
126
103
|
const getCachedModelEndpointsFn = options.getCachedModelEndpointsFn
|
|
127
104
|
|| codexClient.getCachedModelEndpoints;
|
|
128
|
-
const claudeMode = hasExplicitClaudeMode
|
|
129
|
-
? options.claudeMode
|
|
130
|
-
: claudeClient === codexClient ? "inherited" : "isolated";
|
|
131
|
-
if (claudeMode === "isolated" && options.chatCompletionsFn) {
|
|
132
|
-
throw new Error("Isolated Claude mode cannot use the shared chatCompletionsFn override; pass codexChatCompletionsFn or claudeChatCompletionsFn explicitly");
|
|
133
|
-
}
|
|
134
105
|
const codexModelRegistry = options.codexModelRegistry || options.modelRegistry;
|
|
135
|
-
const claudeModelRegistry = options.claudeModelRegistry || options.modelRegistry || codexModelRegistry;
|
|
136
|
-
if (claudeMode === "isolated"
|
|
137
|
-
&& (!options.claudeModelRegistry || claudeModelRegistry === codexModelRegistry)) {
|
|
138
|
-
throw new Error("Isolated Claude mode requires a distinct Claude model registry");
|
|
139
|
-
}
|
|
140
106
|
const openAIModelEnv = options.openAIModelEnv || process.env;
|
|
141
107
|
const upstreamTimeoutMs = parsePositiveInteger(options.upstreamTimeoutMs, ADAPTER_RUNTIME_CONFIG.upstreamTimeoutMs);
|
|
142
108
|
const streamHandshakeTimeoutMs = parsePositiveInteger(options.streamHandshakeTimeoutMs, ADAPTER_RUNTIME_CONFIG.streamHandshakeTimeoutMs);
|
|
@@ -146,19 +112,10 @@ export function createAdapterHandler(options = {}) {
|
|
|
146
112
|
const requestMetrics = options.requestMetrics || createRequestMetrics();
|
|
147
113
|
const streamPerformanceMetrics = options.streamPerformanceMetrics || createStreamPerformanceMetrics();
|
|
148
114
|
const imagePressure = options.imagePressure || createResponsesImagePressureController();
|
|
149
|
-
const ownsTokenCounterService = !options.tokenCounterService;
|
|
150
|
-
const tokenCounterService = options.tokenCounterService || createTokenCounterService({
|
|
151
|
-
timeoutMs: options.countTokensTimeoutMs,
|
|
152
|
-
maxQueued: options.countTokensMaxQueued,
|
|
153
|
-
});
|
|
154
|
-
const claudeDesktopModelOptions = () => {
|
|
155
|
-
const modelDefs = claudeModelRegistry?.modelDefs || options.claudeDesktopModelDefs;
|
|
156
|
-
return Array.isArray(modelDefs) ? { modelDefs } : {};
|
|
157
|
-
};
|
|
158
115
|
const responsesHandler = createResponsesHandler({
|
|
159
116
|
acquireRequest,
|
|
160
117
|
autoReviewModelResolver: options.autoReviewModelResolver,
|
|
161
|
-
chatCompletionsFn
|
|
118
|
+
chatCompletionsFn,
|
|
162
119
|
getCachedModelEndpointsFn,
|
|
163
120
|
imagePressure,
|
|
164
121
|
modelRegistry: codexModelRegistry,
|
|
@@ -184,34 +141,6 @@ export function createAdapterHandler(options = {}) {
|
|
|
184
141
|
streamIdleTimeoutMs,
|
|
185
142
|
upstreamTimeoutMs,
|
|
186
143
|
});
|
|
187
|
-
const anthropicCountTokensHandler = createAnthropicCountTokensHandler({
|
|
188
|
-
acquireRequest,
|
|
189
|
-
requestBodyTimeoutMs,
|
|
190
|
-
countTokensFn: (body, countOptions) => tokenCounterService.count(body, countOptions),
|
|
191
|
-
});
|
|
192
|
-
const anthropicMessagesHandler = createAnthropicMessagesHandler({
|
|
193
|
-
acquireRequest,
|
|
194
|
-
chatCompletionsFn: claudeChatCompletionsFn,
|
|
195
|
-
environment: process.env,
|
|
196
|
-
modelOptions: claudeDesktopModelOptions,
|
|
197
|
-
requestBodyTimeoutMs,
|
|
198
|
-
streamHandshakeTimeoutMs,
|
|
199
|
-
streamIdleTimeoutMs,
|
|
200
|
-
upstreamTimeoutMs,
|
|
201
|
-
});
|
|
202
|
-
const pmStudioRelayHandler = createPmStudioRelayHandler({
|
|
203
|
-
acquireRequest,
|
|
204
|
-
claudeClient,
|
|
205
|
-
claudeMode,
|
|
206
|
-
claudeProfile: options.claudeProfile,
|
|
207
|
-
claudeModelRegistry,
|
|
208
|
-
fetchImpl: options.pmStudioFetchImpl,
|
|
209
|
-
requestBodyTimeoutMs,
|
|
210
|
-
streamHandshakeTimeoutMs,
|
|
211
|
-
streamIdleTimeoutMs,
|
|
212
|
-
upstreamTimeoutMs,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
144
|
const dispatch = (req, res, pathname) => {
|
|
216
145
|
if (req.method === "GET" && pathname === ADAPTER_STATUS_PATH) {
|
|
217
146
|
if (!isLoopbackAddress(req.socket?.remoteAddress)) {
|
|
@@ -227,11 +156,7 @@ export function createAdapterHandler(options = {}) {
|
|
|
227
156
|
imagePressure,
|
|
228
157
|
modelRegistry: codexModelRegistry,
|
|
229
158
|
codexClient,
|
|
230
|
-
claudeClient,
|
|
231
159
|
codexModelRegistry,
|
|
232
|
-
claudeModelRegistry,
|
|
233
|
-
claudeMode,
|
|
234
|
-
isClaudeProfileCurrent: options.isClaudeProfileCurrent,
|
|
235
160
|
})));
|
|
236
161
|
return;
|
|
237
162
|
}
|
|
@@ -242,10 +167,6 @@ export function createAdapterHandler(options = {}) {
|
|
|
242
167
|
return;
|
|
243
168
|
}
|
|
244
169
|
|
|
245
|
-
if (pathname.startsWith(`${PM_STUDIO_RELAY_PREFIX}/`)) {
|
|
246
|
-
return pmStudioRelayHandler(req, res, pathname);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
170
|
if (req.method === "POST" && pathname === "/v1/responses") {
|
|
250
171
|
return responsesHandler(req, res);
|
|
251
172
|
}
|
|
@@ -255,11 +176,6 @@ export function createAdapterHandler(options = {}) {
|
|
|
255
176
|
}
|
|
256
177
|
|
|
257
178
|
if (req.method === "GET" && pathname === "/v1/models") {
|
|
258
|
-
if (shouldServeClaudeDesktopModels(req, claudeDesktopApiKey)) {
|
|
259
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
260
|
-
res.end(JSON.stringify(claudeDesktopModelsResponse(process.env, claudeDesktopModelOptions())));
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
179
|
const abort = createRequestAbort(req, res);
|
|
264
180
|
abort.setTimeout(upstreamTimeoutMs);
|
|
265
181
|
listModelsFn({ signal: abort.signal })
|
|
@@ -296,11 +212,11 @@ export function createAdapterHandler(options = {}) {
|
|
|
296
212
|
}
|
|
297
213
|
|
|
298
214
|
if (req.method === "POST" && pathname === "/v1/messages/count_tokens") {
|
|
299
|
-
return
|
|
215
|
+
return sendRetiredClaudeSurface(res);
|
|
300
216
|
}
|
|
301
217
|
|
|
302
218
|
if (req.method === "POST" && pathname === "/v1/messages") {
|
|
303
|
-
return
|
|
219
|
+
return sendRetiredClaudeSurface(res);
|
|
304
220
|
}
|
|
305
221
|
|
|
306
222
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
@@ -388,9 +304,6 @@ export function createAdapterHandler(options = {}) {
|
|
|
388
304
|
containUnexpectedError(error);
|
|
389
305
|
}
|
|
390
306
|
};
|
|
391
|
-
handler.cleanup = () => {
|
|
392
|
-
if (ownsTokenCounterService) tokenCounterService.close();
|
|
393
|
-
};
|
|
394
307
|
return handler;
|
|
395
308
|
}
|
|
396
309
|
|