@bman654/clodex 2.13.1 → 2.14.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 +6 -2
- package/dist/{chunk-ZM2KYLPH.js → chunk-3NCDMS7N.js} +468 -2
- package/dist/chunk-3NCDMS7N.js.map +1 -0
- package/dist/claude-wrapper.js +276 -7
- package/dist/claude-wrapper.js.map +1 -1
- package/dist/cli.js +634 -505
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +35 -0
- package/docs/windows-setup.md +203 -168
- package/launcher/clodex-claude-launcher.cs +311 -0
- package/package.json +2 -1
- package/dist/chunk-ZM2KYLPH.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,11 @@ import {
|
|
|
6
6
|
OAUTH_ACCOUNT_NAME_RE,
|
|
7
7
|
REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT,
|
|
8
8
|
assertRegistryWriteOwnership,
|
|
9
|
+
backupDir,
|
|
9
10
|
clearActiveOAuthAccount,
|
|
11
|
+
collectPristineFacts,
|
|
12
|
+
contentAddressedBackupPath,
|
|
13
|
+
emitParentNotice,
|
|
10
14
|
ensureSecureAppHome,
|
|
11
15
|
findClaudeBinary,
|
|
12
16
|
getAppHome,
|
|
@@ -18,20 +22,32 @@ import {
|
|
|
18
22
|
getLocalPatchesPath,
|
|
19
23
|
getLogsPath,
|
|
20
24
|
getOAuthAccountSlot,
|
|
25
|
+
getPatchManifestPath,
|
|
21
26
|
getSavedServerPassword,
|
|
22
27
|
getServerExposedProviders,
|
|
23
28
|
getServerFavoritesOnly,
|
|
24
29
|
getServerListenMode,
|
|
25
30
|
getServerMaskGatewayIds,
|
|
31
|
+
installParentNoticeSink,
|
|
32
|
+
installProvenancePath,
|
|
26
33
|
isDiscoveryDisabled,
|
|
34
|
+
isPatchedClaudeSource,
|
|
27
35
|
isValidProviderId,
|
|
36
|
+
legacyBackupPath,
|
|
28
37
|
listenTcpServer,
|
|
29
38
|
loadPreferences,
|
|
30
39
|
loadRegistry,
|
|
31
40
|
loadRegistryStrict,
|
|
41
|
+
looksLikeLegacyClodexPatch,
|
|
32
42
|
networkEnvBaseline,
|
|
43
|
+
planInspectedPristineSource,
|
|
44
|
+
planPristineSource,
|
|
45
|
+
planRestoreOnly,
|
|
33
46
|
providerDefaultAuthRef,
|
|
47
|
+
readInstallProvenance,
|
|
34
48
|
readLiveServerRuntimeStates,
|
|
49
|
+
readPatchManifest,
|
|
50
|
+
recordBackupProvenance,
|
|
35
51
|
recordLaunchSelection,
|
|
36
52
|
recordNetworkEnvMutation,
|
|
37
53
|
registerServerRuntimeState,
|
|
@@ -44,15 +60,19 @@ import {
|
|
|
44
60
|
setServerFavoritesOnly,
|
|
45
61
|
setServerListenMode,
|
|
46
62
|
setServerMaskGatewayIds,
|
|
63
|
+
sha256File,
|
|
47
64
|
storeActiveOAuthAccount,
|
|
48
65
|
tcpListenerUrlHost,
|
|
66
|
+
tweakccMirrorBackupPath,
|
|
49
67
|
unregisterServerRuntimeState,
|
|
50
68
|
waitForTcpListener,
|
|
51
69
|
withCredentialMutationLock,
|
|
52
70
|
withProviderMutationLock,
|
|
53
71
|
withRegistryWriteLock,
|
|
54
|
-
withRegistryWriteLockSync
|
|
55
|
-
|
|
72
|
+
withRegistryWriteLockSync,
|
|
73
|
+
writeParentNoticeLines,
|
|
74
|
+
writeParentNoticeLinesSync
|
|
75
|
+
} from "./chunk-3NCDMS7N.js";
|
|
56
76
|
|
|
57
77
|
// src/cli.ts
|
|
58
78
|
import pc13 from "picocolors";
|
|
@@ -228,69 +248,11 @@ function printFavoritesOnlyPanel() {
|
|
|
228
248
|
// src/cli.ts
|
|
229
249
|
import * as p12 from "@clack/prompts";
|
|
230
250
|
import { realpathSync as realpathSync3 } from "fs";
|
|
231
|
-
import { fileURLToPath } from "url";
|
|
251
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
232
252
|
|
|
233
253
|
// src/launch.ts
|
|
234
254
|
import { spawn } from "child_process";
|
|
235
255
|
import { appendFileSync } from "fs";
|
|
236
|
-
|
|
237
|
-
// src/parent-notice.ts
|
|
238
|
-
import { writeSync } from "fs";
|
|
239
|
-
var MAX_NOTICE_CHARS = 2e3;
|
|
240
|
-
var CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
241
|
-
var activeSink = null;
|
|
242
|
-
var stderrErrorGuardInstalled = false;
|
|
243
|
-
function toNoticeLine(message) {
|
|
244
|
-
const flattened = message.replace(CONTROL_CHARS, " ").trimEnd();
|
|
245
|
-
const bounded = flattened.length > MAX_NOTICE_CHARS ? `${flattened.slice(0, MAX_NOTICE_CHARS)}...` : flattened;
|
|
246
|
-
return `${bounded}
|
|
247
|
-
`;
|
|
248
|
-
}
|
|
249
|
-
function guardStderrErrors() {
|
|
250
|
-
if (stderrErrorGuardInstalled) return;
|
|
251
|
-
stderrErrorGuardInstalled = true;
|
|
252
|
-
try {
|
|
253
|
-
process.stderr.on("error", () => {
|
|
254
|
-
});
|
|
255
|
-
} catch {
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
function writeParentNoticeLines(lines) {
|
|
259
|
-
if (lines.length === 0) return;
|
|
260
|
-
guardStderrErrors();
|
|
261
|
-
try {
|
|
262
|
-
process.stderr.write(lines.join(""), () => {
|
|
263
|
-
});
|
|
264
|
-
} catch {
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
function writeParentNoticeLinesSync(lines) {
|
|
268
|
-
if (lines.length === 0) return;
|
|
269
|
-
try {
|
|
270
|
-
writeSync(2, lines.join(""));
|
|
271
|
-
} catch {
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function emitParentNotice(message) {
|
|
275
|
-
const line = toNoticeLine(message);
|
|
276
|
-
try {
|
|
277
|
-
if (activeSink) activeSink(line);
|
|
278
|
-
else writeParentNoticeLines([line]);
|
|
279
|
-
} catch {
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
function installParentNoticeSink(sink) {
|
|
283
|
-
const previous = activeSink;
|
|
284
|
-
activeSink = sink;
|
|
285
|
-
let released = false;
|
|
286
|
-
return () => {
|
|
287
|
-
if (released) return;
|
|
288
|
-
released = true;
|
|
289
|
-
if (activeSink === sink) activeSink = previous;
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// src/launch.ts
|
|
294
256
|
var isWindows = process.platform === "win32";
|
|
295
257
|
var MAX_QUEUED_NOTICES = 50;
|
|
296
258
|
function buildClaudeArgs(model, extraArgs) {
|
|
@@ -382,7 +344,7 @@ import { join } from "path";
|
|
|
382
344
|
// package.json
|
|
383
345
|
var package_default = {
|
|
384
346
|
name: "@bman654/clodex",
|
|
385
|
-
version: "2.
|
|
347
|
+
version: "2.14.1",
|
|
386
348
|
publishConfig: {
|
|
387
349
|
access: "public"
|
|
388
350
|
},
|
|
@@ -411,6 +373,7 @@ var package_default = {
|
|
|
411
373
|
files: [
|
|
412
374
|
"dist",
|
|
413
375
|
"docs",
|
|
376
|
+
"launcher",
|
|
414
377
|
"README.md"
|
|
415
378
|
],
|
|
416
379
|
engines: {
|
|
@@ -3469,7 +3432,7 @@ function savedStopsAfter(current, assignments) {
|
|
|
3469
3432
|
}
|
|
3470
3433
|
|
|
3471
3434
|
// src/patch-transforms.ts
|
|
3472
|
-
var PATCH_TRANSFORMS_VERSION =
|
|
3435
|
+
var PATCH_TRANSFORMS_VERSION = 13;
|
|
3473
3436
|
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
3474
3437
|
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3475
3438
|
function projectNativeEffort(effort) {
|
|
@@ -3626,14 +3589,22 @@ function applyClodexPatches(source, config) {
|
|
|
3626
3589
|
{ required: true, noopIsSkip: true }
|
|
3627
3590
|
);
|
|
3628
3591
|
{
|
|
3629
|
-
const
|
|
3592
|
+
const RESOLVER_ANCHOR = /(case"best":\{[^{}]*\})/;
|
|
3593
|
+
const RESOLVER_BUDGET = 2e3 + ALIASES.reduce((n, a) => n + 2 * a.length + 17, 0);
|
|
3594
|
+
const RESOLVER_SWITCH = new RegExp(
|
|
3595
|
+
'case"best":\\{[^{}]*\\}[\\s\\S]{0,' + RESOLVER_BUDGET + "}?default:return"
|
|
3596
|
+
);
|
|
3597
|
+
const resolver = js.match(RESOLVER_SWITCH)?.[0] ?? "";
|
|
3598
|
+
const missing = ALIASES.filter(
|
|
3599
|
+
(a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(resolver)
|
|
3600
|
+
);
|
|
3630
3601
|
const cases = missing.map((a) => "case" + q(a) + ":return " + q(a) + ";").join("");
|
|
3631
3602
|
if (ALIASES.length === 0) {
|
|
3632
3603
|
log12("SKIP", "PATCH 6: alias resolver switch", "no aliases configured");
|
|
3633
3604
|
} else {
|
|
3634
3605
|
applyOnce(
|
|
3635
3606
|
"PATCH 6: alias resolver switch",
|
|
3636
|
-
|
|
3607
|
+
RESOLVER_ANCHOR,
|
|
3637
3608
|
(m) => m + cases,
|
|
3638
3609
|
{ required: true, noopIsSkip: true }
|
|
3639
3610
|
);
|
|
@@ -3882,25 +3853,25 @@ function applyClodexPatches(source, config) {
|
|
|
3882
3853
|
}
|
|
3883
3854
|
|
|
3884
3855
|
// src/patcher.ts
|
|
3885
|
-
import { createHash as
|
|
3856
|
+
import { createHash as createHash8 } from "crypto";
|
|
3886
3857
|
import {
|
|
3887
3858
|
chmodSync as chmodSync4,
|
|
3888
3859
|
copyFileSync,
|
|
3889
|
-
existsSync as
|
|
3860
|
+
existsSync as existsSync4,
|
|
3890
3861
|
mkdtempSync,
|
|
3891
3862
|
mkdirSync as mkdirSync5,
|
|
3892
|
-
readFileSync as
|
|
3893
|
-
renameSync as
|
|
3894
|
-
rmSync
|
|
3895
|
-
statSync as
|
|
3863
|
+
readFileSync as readFileSync9,
|
|
3864
|
+
renameSync as renameSync2,
|
|
3865
|
+
rmSync,
|
|
3866
|
+
statSync as statSync7,
|
|
3896
3867
|
unlinkSync as unlinkSync3,
|
|
3897
|
-
writeFileSync as
|
|
3868
|
+
writeFileSync as writeFileSync5,
|
|
3898
3869
|
openSync as openSync8,
|
|
3899
3870
|
closeSync as closeSync8,
|
|
3900
3871
|
realpathSync as realpathSync2
|
|
3901
3872
|
} from "fs";
|
|
3902
|
-
import { homedir as
|
|
3903
|
-
import { basename, dirname as dirname5, join as
|
|
3873
|
+
import { homedir as homedir2 } from "os";
|
|
3874
|
+
import { basename, dirname as dirname5, join as join7 } from "path";
|
|
3904
3875
|
import pc3 from "picocolors";
|
|
3905
3876
|
import * as p2 from "@clack/prompts";
|
|
3906
3877
|
|
|
@@ -7052,9 +7023,9 @@ function getPlatformKey() {
|
|
|
7052
7023
|
return `${platform}-${cpu}`;
|
|
7053
7024
|
}
|
|
7054
7025
|
function inspectNativePackage(binaryPath) {
|
|
7055
|
-
const
|
|
7056
|
-
const packageJsonPath = join6(
|
|
7057
|
-
const installScriptPath = join6(
|
|
7026
|
+
const packageRoot2 = dirname4(dirname4(binaryPath));
|
|
7027
|
+
const packageJsonPath = join6(packageRoot2, "package.json");
|
|
7028
|
+
const installScriptPath = join6(packageRoot2, "install.cjs");
|
|
7058
7029
|
let manifest;
|
|
7059
7030
|
try {
|
|
7060
7031
|
manifest = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
|
|
@@ -7110,7 +7081,7 @@ function inspectClaudeNativeBinaryPlaceholder(path) {
|
|
|
7110
7081
|
}
|
|
7111
7082
|
|
|
7112
7083
|
// src/bun-entry-module.ts
|
|
7113
|
-
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync6, writeSync
|
|
7084
|
+
import { closeSync as closeSync3, openSync as openSync3, readSync as readSync2, statSync as statSync6, writeSync } from "fs";
|
|
7114
7085
|
import { execFileSync } from "child_process";
|
|
7115
7086
|
var BUN_TRAILER = Buffer.from("\n---- Bun! ----\n");
|
|
7116
7087
|
var BUN_OFFSETS_BYTES = 32;
|
|
@@ -7275,7 +7246,7 @@ function isModuleNameAt(fd, offset, marker) {
|
|
|
7275
7246
|
}
|
|
7276
7247
|
function writeNameBytes(fd, name, offset) {
|
|
7277
7248
|
const bytes = Buffer.from(name);
|
|
7278
|
-
const written =
|
|
7249
|
+
const written = writeSync(fd, bytes, 0, bytes.length, offset);
|
|
7279
7250
|
if (written !== bytes.length) {
|
|
7280
7251
|
throw new Error(`wrote ${written} of ${bytes.length} entry-module name bytes at ${offset}`);
|
|
7281
7252
|
}
|
|
@@ -7372,7 +7343,7 @@ function resignMachOBinary(path) {
|
|
|
7372
7343
|
}
|
|
7373
7344
|
|
|
7374
7345
|
// src/bun-compiled-pointer.ts
|
|
7375
|
-
import { closeSync as closeSync4, fstatSync as fstatSync2, openSync as openSync4, readSync as readSync3, writeSync as
|
|
7346
|
+
import { closeSync as closeSync4, fstatSync as fstatSync2, openSync as openSync4, readSync as readSync3, writeSync as writeSync2 } from "fs";
|
|
7376
7347
|
var TWEAKCC_SCAN_STRIDE = 16384n;
|
|
7377
7348
|
var SCAN_CHUNK = 1 << 20;
|
|
7378
7349
|
var ELF_MAGIC = 1179403647;
|
|
@@ -7540,7 +7511,7 @@ function shimBunCompiledPointer(path) {
|
|
|
7540
7511
|
const standInOffset = Number(segment.offset + (standInVaddr - segment.vaddr));
|
|
7541
7512
|
const displaced = readAt2(fd, 8, standInOffset);
|
|
7542
7513
|
if (displaced.length !== 8) throw new Error("could not read the bytes Bun's blob pointer displaces");
|
|
7543
|
-
|
|
7514
|
+
writeSync2(fd, needle, 0, 8, standInOffset);
|
|
7544
7515
|
return { pointerVaddr, standInVaddr, displaced, bunVaddr: bun.addr };
|
|
7545
7516
|
} finally {
|
|
7546
7517
|
closeSync4(fd);
|
|
@@ -7575,8 +7546,8 @@ function restoreBunCompiledPointer(path, shim) {
|
|
|
7575
7546
|
}
|
|
7576
7547
|
const value = Buffer.alloc(8);
|
|
7577
7548
|
value.writeBigUInt64LE(written);
|
|
7578
|
-
|
|
7579
|
-
|
|
7549
|
+
writeSync2(fd, value, 0, 8, pointerOffset);
|
|
7550
|
+
writeSync2(fd, shim.displaced, 0, 8, standInOffset);
|
|
7580
7551
|
if (!readAt2(fd, 8, pointerOffset).equals(value)) {
|
|
7581
7552
|
throw new Error("Bun's blob pointer did not take the repacked address");
|
|
7582
7553
|
}
|
|
@@ -7589,7 +7560,7 @@ function restoreBunCompiledPointer(path, shim) {
|
|
|
7589
7560
|
}
|
|
7590
7561
|
|
|
7591
7562
|
// src/bun-bundle.ts
|
|
7592
|
-
import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, writeSync as
|
|
7563
|
+
import { closeSync as closeSync5, openSync as openSync5, readSync as readSync4, writeSync as writeSync3 } from "fs";
|
|
7593
7564
|
function writableModuleIndex(path) {
|
|
7594
7565
|
const table = readBunModuleTable(path);
|
|
7595
7566
|
if (!table) return null;
|
|
@@ -7804,322 +7775,17 @@ function applyBundleWritePlan(path, plan) {
|
|
|
7804
7775
|
function writeAll(fd, bytes, position, path) {
|
|
7805
7776
|
let written = 0;
|
|
7806
7777
|
while (written < bytes.length) {
|
|
7807
|
-
const wrote =
|
|
7778
|
+
const wrote = writeSync3(fd, bytes, written, bytes.length - written, position + written);
|
|
7808
7779
|
if (wrote <= 0) throw new Error(`wrote ${written} of ${bytes.length} blob bytes to ${path}`);
|
|
7809
7780
|
written += wrote;
|
|
7810
7781
|
}
|
|
7811
7782
|
}
|
|
7812
7783
|
|
|
7813
|
-
// src/patch-backup.ts
|
|
7814
|
-
import { createHash as createHash5 } from "crypto";
|
|
7815
|
-
import { existsSync as existsSync4, readFileSync as readFileSync9, readdirSync, renameSync as renameSync2, rmSync, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
7816
|
-
import { homedir as homedir2 } from "os";
|
|
7817
|
-
import { join as join7 } from "path";
|
|
7818
|
-
var BACKUP_SHA_PREFIX_LENGTH = 16;
|
|
7819
|
-
function backupDir() {
|
|
7820
|
-
return process.env["TWEAKCC_CONFIG_DIR"]?.trim() || join7(homedir2(), ".tweakcc");
|
|
7821
|
-
}
|
|
7822
|
-
function sha256File(path) {
|
|
7823
|
-
return createHash5("sha256").update(readFileSync9(path)).digest("hex");
|
|
7824
|
-
}
|
|
7825
|
-
function backupVersionTag(version) {
|
|
7826
|
-
const tag = version.trim().replace(/[^\w.-]+/g, "_");
|
|
7827
|
-
if (!tag) throw new Error("clodex patch: refusing to name a pristine backup for an empty claude version");
|
|
7828
|
-
return tag;
|
|
7829
|
-
}
|
|
7830
|
-
function contentAddressedBackupPath(version, sha256, dir = backupDir()) {
|
|
7831
|
-
return join7(dir, `claude-${backupVersionTag(version)}-${sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH)}.orig`);
|
|
7832
|
-
}
|
|
7833
|
-
function legacyBackupPath(version, dir = backupDir()) {
|
|
7834
|
-
return join7(dir, `claude-${backupVersionTag(version)}.orig`);
|
|
7835
|
-
}
|
|
7836
|
-
function tweakccMirrorBackupPath(dir = backupDir()) {
|
|
7837
|
-
return join7(dir, "native-binary.backup");
|
|
7838
|
-
}
|
|
7839
|
-
function installProvenancePath(backupPath, binaryPath) {
|
|
7840
|
-
const tag = createHash5("sha256").update(binaryPath).digest("hex").slice(0, BACKUP_SHA_PREFIX_LENGTH);
|
|
7841
|
-
return `${backupPath}.for-${tag}.json`;
|
|
7842
|
-
}
|
|
7843
|
-
function installProvenancePattern(name) {
|
|
7844
|
-
return new RegExp(`^${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.for-[0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}\\.json$`);
|
|
7845
|
-
}
|
|
7846
|
-
function readInstallProvenance(path) {
|
|
7847
|
-
let raw;
|
|
7848
|
-
try {
|
|
7849
|
-
raw = readFileSync9(path, "utf8");
|
|
7850
|
-
} catch {
|
|
7851
|
-
return null;
|
|
7852
|
-
}
|
|
7853
|
-
let parsed;
|
|
7854
|
-
try {
|
|
7855
|
-
parsed = JSON.parse(raw);
|
|
7856
|
-
} catch {
|
|
7857
|
-
return "damaged";
|
|
7858
|
-
}
|
|
7859
|
-
if (typeof parsed !== "object" || parsed === null) return "damaged";
|
|
7860
|
-
const { install, assumed } = parsed;
|
|
7861
|
-
if (typeof install !== "string" || install === "") return "damaged";
|
|
7862
|
-
if (typeof assumed !== "boolean") return "damaged";
|
|
7863
|
-
return { install, assumed };
|
|
7864
|
-
}
|
|
7865
|
-
function recordBackupProvenance(backupPath, binaryPath, opts) {
|
|
7866
|
-
const target = installProvenancePath(backupPath, binaryPath);
|
|
7867
|
-
const existing = readInstallProvenance(target);
|
|
7868
|
-
if (existing && existing !== "damaged" && existing.install === binaryPath) {
|
|
7869
|
-
if (existing.assumed === opts.assumed) return existing.assumed ? "assumed" : "established";
|
|
7870
|
-
if (!existing.assumed) return "established";
|
|
7871
|
-
}
|
|
7872
|
-
const record = { install: binaryPath, assumed: opts.assumed };
|
|
7873
|
-
const temp = `${target}.tmp-${process.pid}-${Date.now().toString(36)}`;
|
|
7874
|
-
try {
|
|
7875
|
-
writeFileSync5(temp, `${JSON.stringify(record, null, 2)}
|
|
7876
|
-
`);
|
|
7877
|
-
renameSync2(temp, target);
|
|
7878
|
-
return opts.assumed ? "assumed" : "established";
|
|
7879
|
-
} catch (err) {
|
|
7880
|
-
try {
|
|
7881
|
-
rmSync(temp, { force: true });
|
|
7882
|
-
} catch {
|
|
7883
|
-
}
|
|
7884
|
-
throw err;
|
|
7885
|
-
}
|
|
7886
|
-
}
|
|
7887
|
-
function scanPristineBackups(version, dir = backupDir()) {
|
|
7888
|
-
const tag = backupVersionTag(version);
|
|
7889
|
-
const pattern = new RegExp(`^claude-${tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:-([0-9a-f]{${BACKUP_SHA_PREFIX_LENGTH}}))?\\.orig$`);
|
|
7890
|
-
let entries;
|
|
7891
|
-
try {
|
|
7892
|
-
entries = readdirSync(dir);
|
|
7893
|
-
} catch {
|
|
7894
|
-
return { valid: [], corrupt: [] };
|
|
7895
|
-
}
|
|
7896
|
-
const provenanceOf = (name) => {
|
|
7897
|
-
const recordPattern = installProvenancePattern(name);
|
|
7898
|
-
const installs = [];
|
|
7899
|
-
const assumedInstalls = [];
|
|
7900
|
-
const damagedProvenance = [];
|
|
7901
|
-
for (const entry of entries) {
|
|
7902
|
-
if (!recordPattern.test(entry)) continue;
|
|
7903
|
-
const path = join7(dir, entry);
|
|
7904
|
-
const record = readInstallProvenance(path);
|
|
7905
|
-
if (record === null || record === "damaged" || installProvenancePath(join7(dir, name), record.install) !== path) {
|
|
7906
|
-
damagedProvenance.push(path);
|
|
7907
|
-
continue;
|
|
7908
|
-
}
|
|
7909
|
-
(record.assumed ? assumedInstalls : installs).push(record.install);
|
|
7910
|
-
}
|
|
7911
|
-
return {
|
|
7912
|
-
installs: installs.sort(),
|
|
7913
|
-
assumedInstalls: assumedInstalls.sort(),
|
|
7914
|
-
damagedProvenance: damagedProvenance.sort()
|
|
7915
|
-
};
|
|
7916
|
-
};
|
|
7917
|
-
const valid = [];
|
|
7918
|
-
const corrupt = [];
|
|
7919
|
-
for (const entry of entries.sort()) {
|
|
7920
|
-
const match = pattern.exec(entry);
|
|
7921
|
-
if (!match) continue;
|
|
7922
|
-
const path = join7(dir, entry);
|
|
7923
|
-
let sha256;
|
|
7924
|
-
try {
|
|
7925
|
-
if (!statSync7(path).isFile()) continue;
|
|
7926
|
-
sha256 = sha256File(path);
|
|
7927
|
-
} catch {
|
|
7928
|
-
corrupt.push(path);
|
|
7929
|
-
continue;
|
|
7930
|
-
}
|
|
7931
|
-
const embedded = match[1];
|
|
7932
|
-
if (embedded) {
|
|
7933
|
-
if (sha256.slice(0, BACKUP_SHA_PREFIX_LENGTH) !== embedded) {
|
|
7934
|
-
corrupt.push(path);
|
|
7935
|
-
continue;
|
|
7936
|
-
}
|
|
7937
|
-
valid.push({ path, kind: "content-addressed", sha256, ...provenanceOf(entry) });
|
|
7938
|
-
} else {
|
|
7939
|
-
valid.push({ path, kind: "legacy", sha256, ...provenanceOf(entry) });
|
|
7940
|
-
}
|
|
7941
|
-
}
|
|
7942
|
-
return { valid, corrupt };
|
|
7943
|
-
}
|
|
7944
|
-
var CLODEX_PATCH_COMMENT_PREFIX = "/*ccpatch:";
|
|
7945
|
-
var LEGACY_CLODEX_PATCH_MARKERS = [
|
|
7946
|
-
"Additional custom models: ",
|
|
7947
|
-
// PATCH 4 — Agent tool model description
|
|
7948
|
-
"function(_i){return _i.value===_o.value}",
|
|
7949
|
-
// PATCH 5 — picker dedupe guard
|
|
7950
|
-
'"clodex:'
|
|
7951
|
-
// PATCH 1/3 — canonical ids as model identities
|
|
7952
|
-
];
|
|
7953
|
-
function isPatchedClaudeSource(source) {
|
|
7954
|
-
return source.includes(CLODEX_PATCH_COMMENT_PREFIX);
|
|
7955
|
-
}
|
|
7956
|
-
function looksLikeLegacyClodexPatch(source) {
|
|
7957
|
-
return !isPatchedClaudeSource(source) && LEGACY_CLODEX_PATCH_MARKERS.some((marker) => source.includes(marker));
|
|
7958
|
-
}
|
|
7959
|
-
function noBackupMessage(facts) {
|
|
7960
|
-
const corrupt = facts.corruptBackups?.length ? ` (${facts.corruptBackups.length} backup file(s) for this version failed integrity checks and were ignored)` : "";
|
|
7961
|
-
return `clodex holds no pristine backup of claude ${facts.version} it can attribute to ${facts.binaryPath}: none was found in ${backupDir()}${corrupt}. If that binary is patched, reinstall Claude Code to get a pristine one and run \`clodex patch\` again. If it was never patched, there is nothing to restore.`;
|
|
7962
|
-
}
|
|
7963
|
-
function identifiesABackup(manifest) {
|
|
7964
|
-
return manifest.pristineSha256 !== void 0 || manifest.backupPath !== void 0;
|
|
7965
|
-
}
|
|
7966
|
-
function recordedBackupGoneMessage(facts, manifest) {
|
|
7967
|
-
const named = manifest.backupPath ?? `the backup holding sha256 ${manifest.pristineSha256}`;
|
|
7968
|
-
const corrupt = facts.corruptBackups?.length ? ` (${facts.corruptBackups.length} backup file(s) for this version failed integrity checks and were ignored)` : "";
|
|
7969
|
-
const others = facts.backups.length ? `The ${facts.backups.length} other pristine backup(s) tagged claude ${facts.version} there were made for some other install \u2014 two installs of one Claude Code version are different files, so restoring one of them would overwrite this install with bytes that were never its own. ` : "";
|
|
7970
|
-
return `The patch manifest records ${named} as the pristine content of ${facts.binaryPath}, and clodex cannot use it: it is missing from ${backupDir()}, failed its integrity check, or holds a different claude version${corrupt}. ${others}Reinstall Claude Code to get a pristine binary, then run \`clodex patch\`.`;
|
|
7971
|
-
}
|
|
7972
|
-
function otherInstallMessage(facts, otherBinaryPath) {
|
|
7973
|
-
return `The patch manifest records a different Claude Code install (${otherBinaryPath}), so nothing establishes that a pristine backup tagged claude ${facts.version} in ${backupDir()} belongs to ${facts.binaryPath}. Two installs of one Claude Code version are different files, so restoring by version tag alone would overwrite this install with another one's bytes. Set TWEAKCC_CC_INSTALLATION_PATH=${otherBinaryPath} to restore that install instead, or reinstall Claude Code to make this one pristine.`;
|
|
7974
|
-
}
|
|
7975
|
-
function otherInstallProvenanceMessage(facts, recorded) {
|
|
7976
|
-
const owners = [...new Set(recorded.flatMap((backup) => backup.installs))];
|
|
7977
|
-
return `The pristine backup(s) of claude ${facts.version} in ${backupDir()} are recorded as the pristine content of ${owners.join(", ")}, not of ${facts.binaryPath}. Two installs of one Claude Code version are different files, so restoring one of those would overwrite this install with bytes that were never its own. Set TWEAKCC_CC_INSTALLATION_PATH=${owners[0]} to restore that install instead, or reinstall Claude Code to make this one pristine.`;
|
|
7978
|
-
}
|
|
7979
|
-
function damagedProvenanceMessage(facts, damaged) {
|
|
7980
|
-
return `A pristine backup of claude ${facts.version} in ${backupDir()} carries a provenance record that clodex cannot read (${damaged.join(", ")}). Something recorded which install those bytes belong to and that record is damaged, so clodex will not fall back to matching on the claude version in the file name \u2014 on a machine with more than one install that would hand ${facts.binaryPath} bytes that were never its own. Delete the unreadable record to accept that fallback, or reinstall Claude Code to make this install pristine.`;
|
|
7981
|
-
}
|
|
7982
|
-
function contradictoryProvenanceMessage(facts, mine) {
|
|
7983
|
-
return `More than one pristine backup of claude ${facts.version} is recorded as the pristine content of ${facts.binaryPath} (${mine.map((backup) => backup.path).join(", ")}), and they do not hold the same bytes, so clodex cannot tell which one that install holds now \u2014 being snapshotted twice, at two different sets of unpatched bytes, reaches this state without either record being wrong. Delete whichever is stale to decide it: ${mine.map((backup) => installProvenancePath(backup.path, facts.binaryPath)).join(", ")}. Or reinstall the Claude Code at ${facts.binaryPath} and delete both.`;
|
|
7984
|
-
}
|
|
7985
|
-
function manifestContradictsRecordMessage(facts, manifestChoice, recorded) {
|
|
7986
|
-
return `The patch manifest names ${manifestChoice} as the pristine content of ${facts.binaryPath}, but ${recorded.map((backup) => backup.path).join(", ")} is recorded as that install's pristine content and holds different bytes. Nothing establishes which of them the install at that path is now \u2014 an executable replaced in place with a different build of the same claude version reaches exactly this state \u2014 so clodex will not pick one. Delete whichever of these records is stale to decide it: ${recorded.map((backup) => installProvenancePath(backup.path, facts.binaryPath)).join(", ")}. Or reinstall the Claude Code at ${facts.binaryPath} and delete both: a pristine install needs no backup, and \`clodex patch\` will record its own.`;
|
|
7987
|
-
}
|
|
7988
|
-
function assumedElsewhereMessage(facts, backups) {
|
|
7989
|
-
const others = [...new Set(backups.flatMap((backup) => backup.assumedInstalls))];
|
|
7990
|
-
return `The pristine backup(s) of claude ${facts.version} in ${backupDir()} have already been restored onto ${others.join(", ")} \u2014 not onto ${facts.binaryPath}. That was itself matched on the claude version in a file name, so it is not proof of ownership, but it is a reason not to hand the same bytes to a second install: two installs of one version are different files. Set TWEAKCC_CC_INSTALLATION_PATH=${others[0]} to restore that install, or reinstall Claude Code to make this one pristine.`;
|
|
7991
|
-
}
|
|
7992
|
-
function selectRestoreSource(facts) {
|
|
7993
|
-
const notes = [];
|
|
7994
|
-
let assumedForThisInstall = false;
|
|
7995
|
-
const recorded = facts.manifest;
|
|
7996
|
-
const speaksForThisVersion = !recorded?.claudeVersion || recorded.claudeVersion === facts.version;
|
|
7997
|
-
const manifest = recorded && recorded.binaryPath === facts.binaryPath && speaksForThisVersion ? recorded : null;
|
|
7998
|
-
const other = recorded && recorded.binaryPath !== facts.binaryPath && speaksForThisVersion ? recorded : null;
|
|
7999
|
-
let chosen = manifest?.pristineSha256 ? facts.backups.find((backup) => backup.sha256 === manifest.pristineSha256) : void 0;
|
|
8000
|
-
if (!chosen && manifest?.backupPath) {
|
|
8001
|
-
chosen = facts.backups.find((backup) => backup.path === manifest.backupPath);
|
|
8002
|
-
}
|
|
8003
|
-
if (!chosen && manifest && identifiesABackup(manifest)) {
|
|
8004
|
-
return { action: "error", message: recordedBackupGoneMessage(facts, manifest) };
|
|
8005
|
-
}
|
|
8006
|
-
const manifestDescribesLiveBytes = !!manifest?.patchedSha256 && manifest.patchedSha256 === facts.liveSha256;
|
|
8007
|
-
if (chosen && !manifestDescribesLiveBytes) {
|
|
8008
|
-
const manifestChoice = chosen;
|
|
8009
|
-
const contradicting = facts.backups.filter(
|
|
8010
|
-
(backup) => backup.installs.includes(facts.binaryPath) && backup.sha256 !== manifestChoice.sha256
|
|
8011
|
-
);
|
|
8012
|
-
if (contradicting.length) {
|
|
8013
|
-
return {
|
|
8014
|
-
action: "error",
|
|
8015
|
-
message: manifestContradictsRecordMessage(facts, manifestChoice.path, contradicting)
|
|
8016
|
-
};
|
|
8017
|
-
}
|
|
8018
|
-
}
|
|
8019
|
-
if (!chosen) {
|
|
8020
|
-
const mine = facts.backups.filter((backup) => backup.installs.includes(facts.binaryPath));
|
|
8021
|
-
if (mine.length) {
|
|
8022
|
-
if (new Set(mine.map((backup) => backup.sha256)).size > 1) {
|
|
8023
|
-
return { action: "error", message: contradictoryProvenanceMessage(facts, mine) };
|
|
8024
|
-
}
|
|
8025
|
-
chosen = mine.find((backup) => backup.kind === "content-addressed") ?? mine[0];
|
|
8026
|
-
}
|
|
8027
|
-
}
|
|
8028
|
-
if (!chosen) {
|
|
8029
|
-
if (other) {
|
|
8030
|
-
return facts.backups.length ? { action: "error", message: otherInstallMessage(facts, other.binaryPath) } : { action: "error", message: noBackupMessage(facts) };
|
|
8031
|
-
}
|
|
8032
|
-
const recordedElsewhere = facts.backups.filter((backup) => backup.installs.length > 0);
|
|
8033
|
-
if (recordedElsewhere.length) {
|
|
8034
|
-
return { action: "error", message: otherInstallProvenanceMessage(facts, recordedElsewhere) };
|
|
8035
|
-
}
|
|
8036
|
-
const damaged = facts.backups.flatMap((backup) => backup.damagedProvenance);
|
|
8037
|
-
if (damaged.length) {
|
|
8038
|
-
return { action: "error", message: damagedProvenanceMessage(facts, damaged) };
|
|
8039
|
-
}
|
|
8040
|
-
const assumedElsewhere = facts.backups.filter(
|
|
8041
|
-
(backup) => backup.assumedInstalls.some((install) => install !== facts.binaryPath)
|
|
8042
|
-
);
|
|
8043
|
-
if (assumedElsewhere.length) {
|
|
8044
|
-
return { action: "error", message: assumedElsewhereMessage(facts, assumedElsewhere) };
|
|
8045
|
-
}
|
|
8046
|
-
const distinct = [...new Set(facts.backups.map((backup) => backup.sha256))];
|
|
8047
|
-
if (distinct.length > 1) {
|
|
8048
|
-
return {
|
|
8049
|
-
action: "error",
|
|
8050
|
-
message: `Found conflicting pristine backups for claude ${facts.version}: ${facts.backups.map((backup) => backup.path).join(", ")}. They do not hold the same bytes, so clodex cannot tell which one is pristine. If this machine has more than one Claude Code install, both are probably genuine \u2014 one per install \u2014 and deleting either one is a guess. Reinstall the Claude Code at ${facts.binaryPath} instead: a pristine install needs no backup, and \`clodex patch\` will record its own.`
|
|
8051
|
-
};
|
|
8052
|
-
}
|
|
8053
|
-
chosen = facts.backups.find((backup) => backup.kind === "content-addressed") ?? facts.backups[0];
|
|
8054
|
-
if (chosen) {
|
|
8055
|
-
assumedForThisInstall = true;
|
|
8056
|
-
notes.push(
|
|
8057
|
-
`Neither a patch manifest nor a provenance record ties ${chosen.path} to ${facts.binaryPath}, so it is being used as that install's pristine content on the strength of its claude ${facts.version} version tag alone \u2014 it predates the records clodex now writes. On a machine with more than one Claude Code install those bytes may belong to the other one, and clodex will not record this guess as provenance.`
|
|
8058
|
-
);
|
|
8059
|
-
}
|
|
8060
|
-
}
|
|
8061
|
-
if (!chosen) return { action: "error", message: noBackupMessage(facts) };
|
|
8062
|
-
if (manifest?.pristineProvenance === "assumed") assumedForThisInstall = true;
|
|
8063
|
-
return {
|
|
8064
|
-
action: "restore",
|
|
8065
|
-
backupPath: chosen.path,
|
|
8066
|
-
pristineSha256: chosen.sha256,
|
|
8067
|
-
// A legacy name carries no hash, so its bytes could be anything — including
|
|
8068
|
-
// another version's binary, stored under a mislabeled name by an older
|
|
8069
|
-
// clodex. Executing it is the only evidence available; require it.
|
|
8070
|
-
probeVersion: chosen.kind === "legacy",
|
|
8071
|
-
assumedForThisInstall,
|
|
8072
|
-
notes
|
|
8073
|
-
};
|
|
8074
|
-
}
|
|
8075
|
-
function planPristineSource(facts) {
|
|
8076
|
-
const identical = facts.backups.find((backup) => backup.sha256 === facts.liveSha256 && backup.kind === "content-addressed") ?? facts.backups.find((backup) => backup.sha256 === facts.liveSha256);
|
|
8077
|
-
if (identical) {
|
|
8078
|
-
return { action: "reuse", backupPath: identical.path, pristineSha256: identical.sha256, notes: [] };
|
|
8079
|
-
}
|
|
8080
|
-
const manifest = facts.manifest;
|
|
8081
|
-
if (manifest && manifest.binaryPath === facts.binaryPath && manifest.patchedSha256 === facts.liveSha256) {
|
|
8082
|
-
return selectRestoreSource(facts);
|
|
8083
|
-
}
|
|
8084
|
-
return { action: "inspect" };
|
|
8085
|
-
}
|
|
8086
|
-
function planInspectedPristineSource(facts, inspection) {
|
|
8087
|
-
if (inspection.patched) return selectRestoreSource(facts);
|
|
8088
|
-
const notes = [];
|
|
8089
|
-
const conflicting = facts.backups.filter((backup) => backup.sha256 !== facts.liveSha256);
|
|
8090
|
-
if (conflicting.length) {
|
|
8091
|
-
notes.push(
|
|
8092
|
-
`Existing backup(s) for claude ${facts.version} hold different bytes (${conflicting.map((b) => b.path).join(", ")}); the binary being patched carries no clodex patch marker, so it is being stored under its own content address. Both files are kept.`
|
|
8093
|
-
);
|
|
8094
|
-
}
|
|
8095
|
-
return {
|
|
8096
|
-
action: "snapshot",
|
|
8097
|
-
backupPath: contentAddressedBackupPath(facts.version, facts.liveSha256),
|
|
8098
|
-
pristineSha256: facts.liveSha256,
|
|
8099
|
-
notes
|
|
8100
|
-
};
|
|
8101
|
-
}
|
|
8102
|
-
function planRestoreOnly(facts) {
|
|
8103
|
-
return selectRestoreSource(facts);
|
|
8104
|
-
}
|
|
8105
|
-
function collectPristineFacts(args) {
|
|
8106
|
-
const dir = args.dir ?? backupDir();
|
|
8107
|
-
const scan = scanPristineBackups(args.version, dir);
|
|
8108
|
-
return {
|
|
8109
|
-
version: args.version,
|
|
8110
|
-
binaryPath: args.binaryPath,
|
|
8111
|
-
liveSha256: existsSync4(args.binaryPath) ? sha256File(args.binaryPath) : "",
|
|
8112
|
-
manifest: args.manifest,
|
|
8113
|
-
backups: scan.valid,
|
|
8114
|
-
corruptBackups: scan.corrupt
|
|
8115
|
-
};
|
|
8116
|
-
}
|
|
8117
|
-
|
|
8118
7784
|
// src/provider-factory.ts
|
|
8119
7785
|
import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
|
|
8120
7786
|
|
|
8121
7787
|
// src/oauth/responses-websocket.ts
|
|
8122
|
-
import { createHash as
|
|
7788
|
+
import { createHash as createHash5 } from "crypto";
|
|
8123
7789
|
import { closeSync as closeSync6, openSync as openSync6 } from "fs";
|
|
8124
7790
|
import { devNull } from "os";
|
|
8125
7791
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -8487,6 +8153,69 @@ function sanitizeToolInput(input, requiredProps) {
|
|
|
8487
8153
|
return out;
|
|
8488
8154
|
}
|
|
8489
8155
|
|
|
8156
|
+
// src/tool-input-coerce.ts
|
|
8157
|
+
var SCALAR_KINDS = /* @__PURE__ */ new Set(["number", "integer", "boolean"]);
|
|
8158
|
+
var LOCAL_REF = /^#\/(?:\$defs|definitions)\/([^/]+)$/;
|
|
8159
|
+
var MAX_SCHEMA_VISITS = 64;
|
|
8160
|
+
function preferredName(kinds) {
|
|
8161
|
+
let compound;
|
|
8162
|
+
let scalar;
|
|
8163
|
+
let hasString = false;
|
|
8164
|
+
for (const kind of kinds) {
|
|
8165
|
+
if (kind === "array" || kind === "object") compound ??= kind;
|
|
8166
|
+
else if (kind === "string") hasString = true;
|
|
8167
|
+
else if (kind !== void 0 && kind !== "null") scalar ??= kind;
|
|
8168
|
+
}
|
|
8169
|
+
return compound ?? (hasString ? "string" : scalar);
|
|
8170
|
+
}
|
|
8171
|
+
function schemaScalarKind(schema, parameters) {
|
|
8172
|
+
const defs = parameters && typeof parameters === "object" ? parameters.$defs ?? parameters.definitions : void 0;
|
|
8173
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8174
|
+
const resolve4 = (node) => {
|
|
8175
|
+
if (visited.size > MAX_SCHEMA_VISITS || visited.has(node) || !node || typeof node !== "object") return void 0;
|
|
8176
|
+
visited.add(node);
|
|
8177
|
+
const record = node;
|
|
8178
|
+
if (typeof record.type === "string") return record.type;
|
|
8179
|
+
if (Array.isArray(record.type)) {
|
|
8180
|
+
const named = preferredName(record.type.filter((entry) => typeof entry === "string"));
|
|
8181
|
+
if (named !== void 0) return named;
|
|
8182
|
+
}
|
|
8183
|
+
if (typeof record.$ref === "string" && defs && typeof defs === "object") {
|
|
8184
|
+
const match = LOCAL_REF.exec(record.$ref);
|
|
8185
|
+
if (match) return resolve4(defs[match[1]]);
|
|
8186
|
+
}
|
|
8187
|
+
for (const branches of [record.anyOf, record.oneOf]) {
|
|
8188
|
+
if (!Array.isArray(branches)) continue;
|
|
8189
|
+
const named = preferredName(branches.map(resolve4));
|
|
8190
|
+
if (named !== void 0) return named;
|
|
8191
|
+
}
|
|
8192
|
+
return void 0;
|
|
8193
|
+
};
|
|
8194
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return void 0;
|
|
8195
|
+
const kind = resolve4(schema);
|
|
8196
|
+
return kind !== void 0 && SCALAR_KINDS.has(kind) ? kind : void 0;
|
|
8197
|
+
}
|
|
8198
|
+
var DECIMAL_LITERAL = /^[-+]?\d+(\.\d+)?$/;
|
|
8199
|
+
var BOOLEAN_LITERAL = /^\uFEFF?[ \t\n\r]*(true|false)[ \t\n\r]*$/;
|
|
8200
|
+
var MAX_SIGNIFICANT_DIGITS = 15;
|
|
8201
|
+
function significantDigits(decimal) {
|
|
8202
|
+
const [integer = "", fraction = ""] = decimal.replace(/^[-+]/, "").split(".");
|
|
8203
|
+
return integer.replace(/^0+/, "").length + fraction.length;
|
|
8204
|
+
}
|
|
8205
|
+
function coerceEchoedScalar(value, kind) {
|
|
8206
|
+
if (typeof value !== "string") return value;
|
|
8207
|
+
if (kind === "boolean") {
|
|
8208
|
+
const match = BOOLEAN_LITERAL.exec(value);
|
|
8209
|
+
return match ? match[1] === "true" : value;
|
|
8210
|
+
}
|
|
8211
|
+
const trimmed = value.trim();
|
|
8212
|
+
if (DECIMAL_LITERAL.test(trimmed)) {
|
|
8213
|
+
return significantDigits(trimmed) <= MAX_SIGNIFICANT_DIGITS ? Number(trimmed) : value;
|
|
8214
|
+
}
|
|
8215
|
+
const numeric = Number(value);
|
|
8216
|
+
return Number.isFinite(numeric) && String(numeric) === value ? numeric : value;
|
|
8217
|
+
}
|
|
8218
|
+
|
|
8490
8219
|
// src/upstream-retry.ts
|
|
8491
8220
|
var UPSTREAM_MAX_RETRIES_ENV = "CLODEX_UPSTREAM_MAX_RETRIES";
|
|
8492
8221
|
var UPSTREAM_IDLE_TIMEOUT_ENV = "CLODEX_UPSTREAM_IDLE_TIMEOUT_MS";
|
|
@@ -8958,7 +8687,7 @@ function hasResponsesLiteHeader(headers) {
|
|
|
8958
8687
|
}
|
|
8959
8688
|
function authorizationHeaderFingerprint(headers) {
|
|
8960
8689
|
const authorization = Object.entries(headers).find(([key]) => key.toLowerCase() === "authorization")?.[1];
|
|
8961
|
-
return authorization ?
|
|
8690
|
+
return authorization ? createHash5("sha256").update(authorization).digest("hex") : "";
|
|
8962
8691
|
}
|
|
8963
8692
|
function bodyToString(body) {
|
|
8964
8693
|
if (body == null) return "";
|
|
@@ -8991,13 +8720,13 @@ function responsesWebSocketPromptFingerprint(payload) {
|
|
|
8991
8720
|
delete stable.previous_response_id;
|
|
8992
8721
|
delete stable.stream;
|
|
8993
8722
|
delete stable.background;
|
|
8994
|
-
return
|
|
8723
|
+
return createHash5("sha256").update(canonicalJson(stable)).digest("hex");
|
|
8995
8724
|
}
|
|
8996
8725
|
function responsesWebSocketPromptFieldHashes(payload) {
|
|
8997
8726
|
const hashes = {};
|
|
8998
8727
|
for (const key of Object.keys(payload).sort()) {
|
|
8999
8728
|
if (key === "input" || key === "previous_response_id" || key === "stream" || key === "background") continue;
|
|
9000
|
-
hashes[key] =
|
|
8729
|
+
hashes[key] = createHash5("sha256").update(canonicalJson(payload[key])).digest("hex").slice(0, 12);
|
|
9001
8730
|
}
|
|
9002
8731
|
return hashes;
|
|
9003
8732
|
}
|
|
@@ -9034,7 +8763,7 @@ function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizat
|
|
|
9034
8763
|
authorizationFingerprint,
|
|
9035
8764
|
claudeAgentId
|
|
9036
8765
|
].join("");
|
|
9037
|
-
return
|
|
8766
|
+
return createHash5("sha256").update(material).digest("hex");
|
|
9038
8767
|
}
|
|
9039
8768
|
function inputArray(payload) {
|
|
9040
8769
|
return Array.isArray(payload.input) ? payload.input : [];
|
|
@@ -9054,9 +8783,12 @@ function toolSchemaDefaults(payload) {
|
|
|
9054
8783
|
if (!properties || typeof properties !== "object") return;
|
|
9055
8784
|
const perTool = /* @__PURE__ */ new Map();
|
|
9056
8785
|
for (const [prop, schema] of Object.entries(properties)) {
|
|
9057
|
-
if (schema
|
|
9058
|
-
|
|
9059
|
-
|
|
8786
|
+
if (!schema || typeof schema !== "object") continue;
|
|
8787
|
+
const rule = {};
|
|
8788
|
+
if ("default" in schema) rule.default = canonicalJson(schema.default);
|
|
8789
|
+
const scalar = schemaScalarKind(schema, parameters);
|
|
8790
|
+
if (scalar) rule.scalar = scalar;
|
|
8791
|
+
if (rule.default !== void 0 || rule.scalar) perTool.set(prop, rule);
|
|
9060
8792
|
}
|
|
9061
8793
|
if (perTool.size) defaults.set(record.name, perTool);
|
|
9062
8794
|
};
|
|
@@ -9069,7 +8801,7 @@ function toolSchemaDefaultsFingerprint(defaults) {
|
|
|
9069
8801
|
name,
|
|
9070
8802
|
[...perTool.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
9071
8803
|
]);
|
|
9072
|
-
return
|
|
8804
|
+
return createHash5("sha256").update(JSON.stringify(tuples)).digest("hex").slice(0, 16);
|
|
9073
8805
|
}
|
|
9074
8806
|
function stripSchemaDefaults(name, args, defaults) {
|
|
9075
8807
|
if (!defaults) return args;
|
|
@@ -9078,9 +8810,10 @@ function stripSchemaDefaults(name, args, defaults) {
|
|
|
9078
8810
|
if (!perTool) return args;
|
|
9079
8811
|
const out = {};
|
|
9080
8812
|
for (const [key, value] of Object.entries(args)) {
|
|
9081
|
-
const
|
|
9082
|
-
|
|
9083
|
-
|
|
8813
|
+
const rule = perTool.get(key);
|
|
8814
|
+
const typed = rule?.scalar ? coerceEchoedScalar(value, rule.scalar) : value;
|
|
8815
|
+
if (rule?.default !== void 0 && canonicalJson(typed) === rule.default) continue;
|
|
8816
|
+
out[key] = typed;
|
|
9084
8817
|
}
|
|
9085
8818
|
return out;
|
|
9086
8819
|
}
|
|
@@ -9118,7 +8851,7 @@ function conversationItemKind(value) {
|
|
|
9118
8851
|
return "object";
|
|
9119
8852
|
}
|
|
9120
8853
|
function conversationItemHash(value, defaults) {
|
|
9121
|
-
return
|
|
8854
|
+
return createHash5("sha256").update(canonicalJson(normalizeToolCallJson(value, defaults))).digest("hex").slice(0, 16);
|
|
9122
8855
|
}
|
|
9123
8856
|
function reasoningNormalizationGap(expected, actual, defaults) {
|
|
9124
8857
|
if (conversationItemKind(expected) !== "reasoning" || conversationItemKind(actual) !== "reasoning") return void 0;
|
|
@@ -9418,7 +9151,7 @@ function diagnosticTextFingerprint(field, value) {
|
|
|
9418
9151
|
if (typeof value !== "string" || value.length === 0) return {};
|
|
9419
9152
|
return {
|
|
9420
9153
|
[`${field}Bytes`]: Buffer.byteLength(value),
|
|
9421
|
-
[`${field}Hash`]:
|
|
9154
|
+
[`${field}Hash`]: createHash5("sha256").update(value).digest("hex").slice(0, 16)
|
|
9422
9155
|
};
|
|
9423
9156
|
}
|
|
9424
9157
|
function responseFailureDetails(event) {
|
|
@@ -9454,7 +9187,7 @@ function emitResponseErrorDiagnostic(entry, ctx, details) {
|
|
|
9454
9187
|
emitContextDiagnostic(entry, ctx, { event: "ws_response_error", ...details });
|
|
9455
9188
|
}
|
|
9456
9189
|
function diagnosticItemIdHash(value) {
|
|
9457
|
-
return typeof value === "string" && value.length > 0 ?
|
|
9190
|
+
return typeof value === "string" && value.length > 0 ? createHash5("sha256").update(value).digest("hex").slice(0, 16) : void 0;
|
|
9458
9191
|
}
|
|
9459
9192
|
function reasoningPartIndex(value) {
|
|
9460
9193
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : void 0;
|
|
@@ -9969,7 +9702,10 @@ function transportReplaySafe(ctx) {
|
|
|
9969
9702
|
}
|
|
9970
9703
|
function handleSocketMessage(entry, data) {
|
|
9971
9704
|
const ctx = entry.current;
|
|
9972
|
-
if (!ctx || ctx.closed)
|
|
9705
|
+
if (!ctx || ctx.closed) {
|
|
9706
|
+
if (entry.connectionDiagnostic) observeIdleFrame(entry, data);
|
|
9707
|
+
return;
|
|
9708
|
+
}
|
|
9973
9709
|
const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
|
|
9974
9710
|
ctx.frameCount += 1;
|
|
9975
9711
|
if (ctx.transportRetryPending) {
|
|
@@ -9989,6 +9725,7 @@ function handleSocketMessage(entry, data) {
|
|
|
9989
9725
|
return;
|
|
9990
9726
|
}
|
|
9991
9727
|
const type = eventType(event);
|
|
9728
|
+
if (isQuotaEvent(type)) observeQuotaEvent(entry, event, "during_response", ctx.emitDiagnostic);
|
|
9992
9729
|
trackReasoningProtocol(entry, ctx, event, type);
|
|
9993
9730
|
captureOutput(ctx, event);
|
|
9994
9731
|
if (type === "response.completed") {
|
|
@@ -10180,7 +9917,62 @@ function numericRetryAfterHeader(value) {
|
|
|
10180
9917
|
const single = Array.isArray(value) ? value[0] : value;
|
|
10181
9918
|
return typeof single === "string" && /^\d+$/.test(single.trim()) ? Number(single.trim()) : void 0;
|
|
10182
9919
|
}
|
|
10183
|
-
function
|
|
9920
|
+
function isQuotaEvent(type) {
|
|
9921
|
+
return type === "codex.rate_limits";
|
|
9922
|
+
}
|
|
9923
|
+
var QUOTA_LEDGER_MAX_BYTES = 8e3;
|
|
9924
|
+
var QUOTA_FIELDS_MAX_COUNT = 24;
|
|
9925
|
+
function boundedLedger(value) {
|
|
9926
|
+
let serialized;
|
|
9927
|
+
try {
|
|
9928
|
+
serialized = JSON.stringify(value);
|
|
9929
|
+
} catch {
|
|
9930
|
+
return {};
|
|
9931
|
+
}
|
|
9932
|
+
if (serialized === void 0) return {};
|
|
9933
|
+
const bytes = Buffer.byteLength(serialized);
|
|
9934
|
+
return bytes <= QUOTA_LEDGER_MAX_BYTES ? { value, bytes } : { bytes };
|
|
9935
|
+
}
|
|
9936
|
+
function observeQuotaEvent(entry, event, phase, emit) {
|
|
9937
|
+
if (!emit) return;
|
|
9938
|
+
const record = event;
|
|
9939
|
+
const rate = boundedLedger(record.rate_limits);
|
|
9940
|
+
const additional = boundedLedger(record.additional_rate_limits);
|
|
9941
|
+
const codeReview = boundedLedger(record.code_review_rate_limits);
|
|
9942
|
+
const credits = boundedLedger(record.credits);
|
|
9943
|
+
const promo = boundedLedger(record.promo);
|
|
9944
|
+
emit({
|
|
9945
|
+
event: "ws_rate_limits",
|
|
9946
|
+
connectionId: entry.debugId,
|
|
9947
|
+
generation: entry.generation,
|
|
9948
|
+
phase,
|
|
9949
|
+
upstreamEventType: "codex.rate_limits",
|
|
9950
|
+
fieldCount: Object.keys(record).length,
|
|
9951
|
+
fieldsPresent: Object.keys(record).slice(0, QUOTA_FIELDS_MAX_COUNT).map(boundedDiagnosticIdentifier).filter((name) => name !== void 0),
|
|
9952
|
+
rateLimits: rate.value,
|
|
9953
|
+
rateLimitsBytes: rate.bytes,
|
|
9954
|
+
additionalRateLimits: additional.value,
|
|
9955
|
+
additionalRateLimitsBytes: additional.bytes,
|
|
9956
|
+
codeReviewRateLimits: codeReview.value,
|
|
9957
|
+
codeReviewRateLimitsBytes: codeReview.bytes,
|
|
9958
|
+
credits: credits.value,
|
|
9959
|
+
creditsBytes: credits.bytes,
|
|
9960
|
+
promo: promo.value,
|
|
9961
|
+
promoBytes: promo.bytes,
|
|
9962
|
+
planType: boundedDiagnosticIdentifier(record.plan_type)
|
|
9963
|
+
});
|
|
9964
|
+
}
|
|
9965
|
+
function observeIdleFrame(entry, data) {
|
|
9966
|
+
let event;
|
|
9967
|
+
try {
|
|
9968
|
+
event = JSON.parse(Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8"));
|
|
9969
|
+
} catch {
|
|
9970
|
+
return;
|
|
9971
|
+
}
|
|
9972
|
+
if (!isQuotaEvent(eventType(event))) return;
|
|
9973
|
+
observeQuotaEvent(entry, event, "idle", entry.connectionDiagnostic);
|
|
9974
|
+
}
|
|
9975
|
+
function createConnection(WebSocket, wsUrl, headers, persistent, key, options, debug, agent, connectionDiagnostic) {
|
|
10184
9976
|
const now = options.now();
|
|
10185
9977
|
const socket = new WebSocket(wsUrl, agent ? { headers, agent } : { headers });
|
|
10186
9978
|
const entry = {
|
|
@@ -10195,7 +9987,8 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
|
|
|
10195
9987
|
lastUsedAt: now,
|
|
10196
9988
|
inFlight: false,
|
|
10197
9989
|
options,
|
|
10198
|
-
debug
|
|
9990
|
+
debug,
|
|
9991
|
+
connectionDiagnostic
|
|
10199
9992
|
};
|
|
10200
9993
|
if (persistent && key) registerEntry(entry);
|
|
10201
9994
|
debug(
|
|
@@ -10560,7 +10353,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10560
10353
|
keyTuple: {
|
|
10561
10354
|
wsUrl,
|
|
10562
10355
|
providerId: options.providerId ?? "openai",
|
|
10563
|
-
accountIdHash: options.accountId ?
|
|
10356
|
+
accountIdHash: options.accountId ? createHash5("sha256").update(options.accountId).digest("hex").slice(0, 16) : "",
|
|
10564
10357
|
model: typeof payload.model === "string" ? payload.model : void 0,
|
|
10565
10358
|
effort: typeof payload.reasoning?.effort === "string" ? String(payload.reasoning.effort).trim().toLowerCase() : "",
|
|
10566
10359
|
promptCacheKey: typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : void 0,
|
|
@@ -10612,6 +10405,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10612
10405
|
})),
|
|
10613
10406
|
evictions
|
|
10614
10407
|
}, diagnosticCorrelation);
|
|
10408
|
+
const connectionDiagnostic = options.onDiagnostic ? (event) => emitDiagnostic(options, event, {}) : void 0;
|
|
10615
10409
|
let activeContext;
|
|
10616
10410
|
const stream = new ReadableStream({
|
|
10617
10411
|
start(controller) {
|
|
@@ -10644,7 +10438,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10644
10438
|
partitionKey,
|
|
10645
10439
|
resolvedOptions,
|
|
10646
10440
|
debug,
|
|
10647
|
-
proxyAgent
|
|
10441
|
+
proxyAgent,
|
|
10442
|
+
connectionDiagnostic
|
|
10648
10443
|
)
|
|
10649
10444
|
};
|
|
10650
10445
|
activeContext = ctx;
|
|
@@ -10656,7 +10451,8 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10656
10451
|
partitionKey,
|
|
10657
10452
|
resolvedOptions,
|
|
10658
10453
|
debug,
|
|
10659
|
-
proxyAgent
|
|
10454
|
+
proxyAgent,
|
|
10455
|
+
connectionDiagnostic
|
|
10660
10456
|
);
|
|
10661
10457
|
dispatchContext(entry, ctx);
|
|
10662
10458
|
const signal = init?.signal;
|
|
@@ -10688,7 +10484,7 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10688
10484
|
}
|
|
10689
10485
|
|
|
10690
10486
|
// src/oauth/claude-identity.ts
|
|
10691
|
-
import { createHash as
|
|
10487
|
+
import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
|
|
10692
10488
|
var CLAUDE_CODE_CLI_VERSION = "2.1.195";
|
|
10693
10489
|
var CLAUDE_CODE_USER_AGENT = `claude-cli/${CLAUDE_CODE_CLI_VERSION} (external, cli)`;
|
|
10694
10490
|
var CLAUDE_CODE_ENTRYPOINT = process.env.CLAUDE_CODE_ENTRYPOINT ?? "cli";
|
|
@@ -10703,7 +10499,7 @@ function getOrCreateSessionId(seed) {
|
|
|
10703
10499
|
return id;
|
|
10704
10500
|
}
|
|
10705
10501
|
function uuidFromHash(input) {
|
|
10706
|
-
const h =
|
|
10502
|
+
const h = createHash6("sha256").update(input).digest("hex");
|
|
10707
10503
|
return [
|
|
10708
10504
|
h.slice(0, 8),
|
|
10709
10505
|
h.slice(8, 12),
|
|
@@ -10717,7 +10513,7 @@ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
10717
10513
|
function resolveCliUserID(providerData, seed) {
|
|
10718
10514
|
const v = providerData?.cliUserID;
|
|
10719
10515
|
if (typeof v === "string" && HEX64_RE.test(v)) return v;
|
|
10720
|
-
return
|
|
10516
|
+
return createHash6("sha256").update(`cliUserID:${seed}`).digest("hex");
|
|
10721
10517
|
}
|
|
10722
10518
|
function resolveAccountUUID(providerData, seed) {
|
|
10723
10519
|
const v = providerData?.accountUUID;
|
|
@@ -11621,7 +11417,7 @@ function thinkingProviderOptions(npm) {
|
|
|
11621
11417
|
|
|
11622
11418
|
// src/proxy.ts
|
|
11623
11419
|
import { createServer } from "http";
|
|
11624
|
-
import { appendFileSync as appendFileSync2, openSync as openSync7, writeSync as
|
|
11420
|
+
import { appendFileSync as appendFileSync2, openSync as openSync7, writeSync as writeSync4, closeSync as closeSync7 } from "fs";
|
|
11625
11421
|
|
|
11626
11422
|
// src/http-utils.ts
|
|
11627
11423
|
import * as zlib from "zlib";
|
|
@@ -11853,6 +11649,7 @@ function routeUnavailableMessage(modelId, reason) {
|
|
|
11853
11649
|
}
|
|
11854
11650
|
|
|
11855
11651
|
// src/upstream-forward.ts
|
|
11652
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
11856
11653
|
import { Readable, Transform } from "stream";
|
|
11857
11654
|
import { StringDecoder } from "string_decoder";
|
|
11858
11655
|
|
|
@@ -11941,18 +11738,50 @@ async function fetchWithOAuthRetry(apiKey, request3, refreshToken) {
|
|
|
11941
11738
|
response = await request3(refreshed);
|
|
11942
11739
|
return { response, apiKey: refreshed, refreshed: true };
|
|
11943
11740
|
}
|
|
11741
|
+
function upstreamHoldsThreads(url) {
|
|
11742
|
+
try {
|
|
11743
|
+
return new URL(url).hostname.toLowerCase() === "api.anthropic.com";
|
|
11744
|
+
} catch {
|
|
11745
|
+
return false;
|
|
11746
|
+
}
|
|
11747
|
+
}
|
|
11748
|
+
function anchorSafeMessageId(id) {
|
|
11749
|
+
return typeof id === "string" && id.startsWith("msg_") ? "clodex_" + randomUUID4().replace(/-/g, "") : id;
|
|
11750
|
+
}
|
|
11751
|
+
function isThreadContinuation(body) {
|
|
11752
|
+
const thread = body.thread;
|
|
11753
|
+
return typeof thread === "object" && thread !== null && thread.type === "continue";
|
|
11754
|
+
}
|
|
11755
|
+
var THREAD_UNSUPPORTED_BODY = JSON.stringify({
|
|
11756
|
+
type: "error",
|
|
11757
|
+
error: {
|
|
11758
|
+
type: "invalid_request_error",
|
|
11759
|
+
message: "thread continuation is not supported on this route; resend the full conversation",
|
|
11760
|
+
details: { error_code: "thread_unsupported_request" }
|
|
11761
|
+
}
|
|
11762
|
+
});
|
|
11944
11763
|
var SSE_LINE_SPLIT = /(\r\n|\r|\n)/;
|
|
11945
|
-
function
|
|
11764
|
+
function anthropicSseMessageStartRewrite(rewrite) {
|
|
11946
11765
|
const decoder = new StringDecoder("utf8");
|
|
11947
11766
|
let tail = "";
|
|
11948
11767
|
const rewriteLine = (line) => {
|
|
11949
11768
|
if (!line.startsWith("data:") || !line.includes('"message_start"')) return line;
|
|
11950
11769
|
try {
|
|
11951
11770
|
const parsed = JSON.parse(line.slice(5));
|
|
11952
|
-
if (parsed.type
|
|
11953
|
-
|
|
11954
|
-
|
|
11771
|
+
if (parsed.type !== "message_start" || !parsed.message) return line;
|
|
11772
|
+
let changed = false;
|
|
11773
|
+
if (rewrite.model !== void 0 && typeof parsed.message.model === "string") {
|
|
11774
|
+
parsed.message.model = rewrite.model;
|
|
11775
|
+
changed = true;
|
|
11955
11776
|
}
|
|
11777
|
+
if (rewrite.anchorSafeId) {
|
|
11778
|
+
const id = anchorSafeMessageId(parsed.message.id);
|
|
11779
|
+
if (id !== parsed.message.id) {
|
|
11780
|
+
parsed.message.id = id;
|
|
11781
|
+
changed = true;
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
if (changed) return "data: " + JSON.stringify(parsed);
|
|
11956
11785
|
} catch {
|
|
11957
11786
|
}
|
|
11958
11787
|
return line;
|
|
@@ -12018,8 +11847,11 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
12018
11847
|
"Connection": "keep-alive"
|
|
12019
11848
|
});
|
|
12020
11849
|
const upstream = Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy());
|
|
12021
|
-
if (options.responseModelOverride) {
|
|
12022
|
-
upstream.pipe(
|
|
11850
|
+
if (options.responseModelOverride || options.anchorSafeMessageIds) {
|
|
11851
|
+
upstream.pipe(anthropicSseMessageStartRewrite({
|
|
11852
|
+
model: options.responseModelOverride,
|
|
11853
|
+
anchorSafeId: options.anchorSafeMessageIds
|
|
11854
|
+
})).on("error", () => res.destroy()).pipe(res);
|
|
12023
11855
|
} else {
|
|
12024
11856
|
upstream.pipe(res);
|
|
12025
11857
|
}
|
|
@@ -12039,9 +11871,21 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
12039
11871
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
|
|
12040
11872
|
return;
|
|
12041
11873
|
}
|
|
12042
|
-
if (
|
|
12043
|
-
|
|
12044
|
-
|
|
11874
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.type === "message") {
|
|
11875
|
+
const message = parsed;
|
|
11876
|
+
let changed = false;
|
|
11877
|
+
if (options.responseModelOverride && typeof message.model === "string") {
|
|
11878
|
+
message.model = options.responseModelOverride;
|
|
11879
|
+
changed = true;
|
|
11880
|
+
}
|
|
11881
|
+
if (options.anchorSafeMessageIds) {
|
|
11882
|
+
const id = anchorSafeMessageId(message.id);
|
|
11883
|
+
if (id !== message.id) {
|
|
11884
|
+
message.id = id;
|
|
11885
|
+
changed = true;
|
|
11886
|
+
}
|
|
11887
|
+
}
|
|
11888
|
+
if (changed) text5 = JSON.stringify(message);
|
|
12045
11889
|
}
|
|
12046
11890
|
res.writeHead(200, {
|
|
12047
11891
|
"Content-Type": "application/json",
|
|
@@ -12051,10 +11895,10 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
12051
11895
|
}
|
|
12052
11896
|
|
|
12053
11897
|
// src/proxy.ts
|
|
12054
|
-
import { randomUUID as
|
|
11898
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
12055
11899
|
|
|
12056
11900
|
// src/sdk-adapter.ts
|
|
12057
|
-
import { createHash as
|
|
11901
|
+
import { createHash as createHash7, randomUUID as randomUUID5 } from "crypto";
|
|
12058
11902
|
import { streamText, generateText, tool, jsonSchema } from "ai";
|
|
12059
11903
|
|
|
12060
11904
|
// src/proxy-shared.ts
|
|
@@ -12484,7 +12328,7 @@ function extractClaudeAgentIds(headers) {
|
|
|
12484
12328
|
};
|
|
12485
12329
|
}
|
|
12486
12330
|
function claudeSessionPromptCacheKey(sessionId) {
|
|
12487
|
-
return "relay-session-" +
|
|
12331
|
+
return "relay-session-" + createHash7("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
12488
12332
|
}
|
|
12489
12333
|
function anthropicEffortFromRequest(body) {
|
|
12490
12334
|
const effort = body.output_config?.effort;
|
|
@@ -12494,7 +12338,7 @@ function anthropicEffortFromRequest(body) {
|
|
|
12494
12338
|
function openAiPromptCacheKey(system, tools) {
|
|
12495
12339
|
const toolSig = (tools ?? []).map((t) => `${t.name}${t.description ?? ""}${JSON.stringify(t.input_schema ?? {})}`).join("");
|
|
12496
12340
|
const material = `${system ?? ""}\0${toolSig}`;
|
|
12497
|
-
return "relay-" +
|
|
12341
|
+
return "relay-" + createHash7("sha256").update(material).digest("hex").slice(0, 32);
|
|
12498
12342
|
}
|
|
12499
12343
|
function supportsOpenAiPromptCacheBreakpoints(modelId) {
|
|
12500
12344
|
const match = modelId.toLowerCase().match(/^gpt-(\d+)(?:\.(\d+))?(?:-|$)/);
|
|
@@ -12902,8 +12746,11 @@ function forwardAbortSignal(source, target) {
|
|
|
12902
12746
|
source.addEventListener("abort", forward, { once: true });
|
|
12903
12747
|
return () => source.removeEventListener("abort", forward);
|
|
12904
12748
|
}
|
|
12749
|
+
function translatedMessageId() {
|
|
12750
|
+
return "clodex_" + randomUUID5().replace(/-/g, "");
|
|
12751
|
+
}
|
|
12905
12752
|
async function writeAnthropicStream(stream, modelId, write, log12, observer, tools) {
|
|
12906
|
-
const messageId =
|
|
12753
|
+
const messageId = translatedMessageId();
|
|
12907
12754
|
const requiredProps = toolRequiredProps(tools);
|
|
12908
12755
|
let blockIndex = -1;
|
|
12909
12756
|
let started = false;
|
|
@@ -13261,7 +13108,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
13261
13108
|
reportPromptTokens({ onPromptTokens: options?.onPromptTokens }, usage);
|
|
13262
13109
|
const requiredProps = toolRequiredProps(params.tools);
|
|
13263
13110
|
return {
|
|
13264
|
-
id:
|
|
13111
|
+
id: translatedMessageId(),
|
|
13265
13112
|
type: "message",
|
|
13266
13113
|
role: "assistant",
|
|
13267
13114
|
model: modelId,
|
|
@@ -13427,7 +13274,7 @@ function appendSecureLog(logPath, line) {
|
|
|
13427
13274
|
try {
|
|
13428
13275
|
const fd = openSync7(logPath, "a", 384);
|
|
13429
13276
|
try {
|
|
13430
|
-
|
|
13277
|
+
writeSync4(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
|
|
13431
13278
|
`);
|
|
13432
13279
|
} finally {
|
|
13433
13280
|
closeSync7(fd);
|
|
@@ -13477,7 +13324,7 @@ function configuredAliasLookupNames(alias) {
|
|
|
13477
13324
|
}))].map(normalizeRouteLookupId);
|
|
13478
13325
|
}
|
|
13479
13326
|
async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPath, debugLogPath, webSocketDiagnosticsLogPath, modelAliases) {
|
|
13480
|
-
const proxyToken =
|
|
13327
|
+
const proxyToken = randomUUID6();
|
|
13481
13328
|
silenceSdkWarnings();
|
|
13482
13329
|
if (routes.length === 0) {
|
|
13483
13330
|
throw new Error("Proxy catalog requires at least one route");
|
|
@@ -13629,6 +13476,13 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13629
13476
|
}
|
|
13630
13477
|
return;
|
|
13631
13478
|
}
|
|
13479
|
+
const holdsThreads = route.modelFormat === "anthropic" && upstreamHoldsThreads(upstreamUrl);
|
|
13480
|
+
if (!holdsThreads && isThreadContinuation(anthropicBody)) {
|
|
13481
|
+
plog(() => `thread continuation refused: route=${route.realModelId} upstream does not hold threads`);
|
|
13482
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
13483
|
+
res.end(THREAD_UNSUPPORTED_BODY);
|
|
13484
|
+
return;
|
|
13485
|
+
}
|
|
13632
13486
|
if (!apiKey && routeAuthType !== "none" && !usesSdkAdapter) {
|
|
13633
13487
|
anthropicError(res, 401, "Missing API key");
|
|
13634
13488
|
return;
|
|
@@ -13682,6 +13536,9 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13682
13536
|
// the client would believe it reached a model it never reached, and
|
|
13683
13537
|
// would key its context window off that phantom id.
|
|
13684
13538
|
responseModelOverride: resolvedRoute && typeof originalModel === "string" && originalModel !== route.realModelId ? originalModel : void 0,
|
|
13539
|
+
// An upstream that holds no threads must not hand Claude Code an id
|
|
13540
|
+
// it would anchor the next request on.
|
|
13541
|
+
anchorSafeMessageIds: !holdsThreads,
|
|
13685
13542
|
onUpstreamError: inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(inferenceLogPath, {
|
|
13686
13543
|
modelId: originalModel,
|
|
13687
13544
|
provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
|
|
@@ -13890,7 +13747,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13890
13747
|
res,
|
|
13891
13748
|
upstreamStatus === 500 ? 502 : upstreamStatus,
|
|
13892
13749
|
clientMessage,
|
|
13893
|
-
contextLengthExceeded ? relayRequestId ??
|
|
13750
|
+
contextLengthExceeded ? relayRequestId ?? randomUUID6() : void 0
|
|
13894
13751
|
);
|
|
13895
13752
|
} else {
|
|
13896
13753
|
const errorType = anthropicErrorType(upstreamStatus, details?.transportCode);
|
|
@@ -13898,7 +13755,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13898
13755
|
data: ${JSON.stringify({
|
|
13899
13756
|
type: "error",
|
|
13900
13757
|
error: { type: errorType, message: clientMessage },
|
|
13901
|
-
...contextLengthExceeded ? { request_id: relayRequestId ??
|
|
13758
|
+
...contextLengthExceeded ? { request_id: relayRequestId ?? randomUUID6() } : {}
|
|
13902
13759
|
})}
|
|
13903
13760
|
|
|
13904
13761
|
`);
|
|
@@ -14127,25 +13984,12 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
|
|
|
14127
13984
|
}
|
|
14128
13985
|
|
|
14129
13986
|
// src/patcher.ts
|
|
14130
|
-
function getPatchManifestPath() {
|
|
14131
|
-
return join8(getAppHome(), "patch-state.json");
|
|
14132
|
-
}
|
|
14133
13987
|
function getPatchLockPath() {
|
|
14134
|
-
return
|
|
14135
|
-
}
|
|
14136
|
-
function readPatchManifest(path = getPatchManifestPath()) {
|
|
14137
|
-
try {
|
|
14138
|
-
const parsed = JSON.parse(readFileSync10(path, "utf8"));
|
|
14139
|
-
if (parsed && typeof parsed.binaryPath === "string" && typeof parsed.configHash === "string") {
|
|
14140
|
-
return parsed;
|
|
14141
|
-
}
|
|
14142
|
-
} catch {
|
|
14143
|
-
}
|
|
14144
|
-
return null;
|
|
13988
|
+
return join7(getAppHome(), "patch.lock");
|
|
14145
13989
|
}
|
|
14146
13990
|
function writePatchManifest(manifest, path = getPatchManifestPath()) {
|
|
14147
13991
|
mkdirSync5(getAppHome(), { recursive: true, mode: 448 });
|
|
14148
|
-
|
|
13992
|
+
writeFileSync5(path, `${JSON.stringify(manifest, null, 2)}
|
|
14149
13993
|
`, { encoding: "utf8", mode: 384 });
|
|
14150
13994
|
}
|
|
14151
13995
|
function buildPatchModelConfig(favorites, aliases, modelMetaFor) {
|
|
@@ -14220,7 +14064,7 @@ function computePatchConfigHash(config, transformsVersion = PATCH_TRANSFORMS_VER
|
|
|
14220
14064
|
if (localPatchIdentity !== void 0) {
|
|
14221
14065
|
payload.push(["local-patches", localPatchIdentity]);
|
|
14222
14066
|
}
|
|
14223
|
-
return
|
|
14067
|
+
return createHash8("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
14224
14068
|
}
|
|
14225
14069
|
function buildDesiredPatchConfig(options = {}) {
|
|
14226
14070
|
const prefs = loadPreferences();
|
|
@@ -14298,12 +14142,12 @@ function pidIsAlive(pid) {
|
|
|
14298
14142
|
function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
14299
14143
|
const now = opts.now ?? Date.now();
|
|
14300
14144
|
const isAlive = opts.isAlive ?? pidIsAlive;
|
|
14301
|
-
mkdirSync5(
|
|
14145
|
+
mkdirSync5(join7(lockPath, ".."), { recursive: true, mode: 448 });
|
|
14302
14146
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
14303
14147
|
try {
|
|
14304
14148
|
const fd = openSync8(lockPath, "wx");
|
|
14305
14149
|
const content = { pid: process.pid, startedAt: now };
|
|
14306
|
-
|
|
14150
|
+
writeFileSync5(fd, JSON.stringify(content));
|
|
14307
14151
|
closeSync8(fd);
|
|
14308
14152
|
return () => {
|
|
14309
14153
|
try {
|
|
@@ -14314,7 +14158,7 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
|
|
|
14314
14158
|
} catch {
|
|
14315
14159
|
let stale = false;
|
|
14316
14160
|
try {
|
|
14317
|
-
const existing = JSON.parse(
|
|
14161
|
+
const existing = JSON.parse(readFileSync9(lockPath, "utf8"));
|
|
14318
14162
|
stale = !existing.pid || !isAlive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > PATCH_LOCK_STALE_MS;
|
|
14319
14163
|
} catch {
|
|
14320
14164
|
stale = true;
|
|
@@ -14336,11 +14180,11 @@ function takeIgnoredLaunchOverride() {
|
|
|
14336
14180
|
}
|
|
14337
14181
|
function resolveClaudeBinaryForPatch() {
|
|
14338
14182
|
const envOverride = process.env["TWEAKCC_CC_INSTALLATION_PATH"]?.trim() || null;
|
|
14339
|
-
const nativeSymlink =
|
|
14340
|
-
if (envOverride && !
|
|
14183
|
+
const nativeSymlink = join7(homedir2(), ".local", "bin", "claude");
|
|
14184
|
+
if (envOverride && !existsSync4(envOverride)) {
|
|
14341
14185
|
return { ok: false, reason: "patch-target-missing", declaredPath: envOverride };
|
|
14342
14186
|
}
|
|
14343
|
-
const source = envOverride || (
|
|
14187
|
+
const source = envOverride || (existsSync4(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
|
|
14344
14188
|
if (!source) return { ok: false, reason: "binary-not-found" };
|
|
14345
14189
|
let resolved;
|
|
14346
14190
|
try {
|
|
@@ -14372,7 +14216,7 @@ function resolveClaudeBinaryForPatch() {
|
|
|
14372
14216
|
}
|
|
14373
14217
|
ignoredLaunchOverride = launchOverrideResolved && launchOverrideResolved !== resolved ? { used: resolved, ignored: launchOverride } : null;
|
|
14374
14218
|
try {
|
|
14375
|
-
if (!
|
|
14219
|
+
if (!statSync7(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
|
|
14376
14220
|
} catch {
|
|
14377
14221
|
return { ok: false, reason: "binary-not-found" };
|
|
14378
14222
|
}
|
|
@@ -14433,10 +14277,10 @@ function publishFileByRename(from, to, mode) {
|
|
|
14433
14277
|
try {
|
|
14434
14278
|
copyFileSync(from, temp);
|
|
14435
14279
|
if (mode !== void 0) chmodSync4(temp, mode);
|
|
14436
|
-
|
|
14280
|
+
renameSync2(temp, to);
|
|
14437
14281
|
} catch (err) {
|
|
14438
14282
|
try {
|
|
14439
|
-
|
|
14283
|
+
rmSync(temp, { force: true });
|
|
14440
14284
|
} catch {
|
|
14441
14285
|
}
|
|
14442
14286
|
throw err;
|
|
@@ -14504,8 +14348,8 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
14504
14348
|
try {
|
|
14505
14349
|
mkdirSync5(backupDir(), { recursive: true });
|
|
14506
14350
|
const { tryDetectInstallation, readContent, writeContent } = await import("tweakcc");
|
|
14507
|
-
candidateDir = mkdtempSync(
|
|
14508
|
-
const candidatePath =
|
|
14351
|
+
candidateDir = mkdtempSync(join7(dirname5(binaryPath), ".clodex-patch-"));
|
|
14352
|
+
const candidatePath = join7(candidateDir, basename(binaryPath));
|
|
14509
14353
|
const seedCandidate = async (from) => {
|
|
14510
14354
|
copyFileSync(from, candidatePath);
|
|
14511
14355
|
const shim = shimEntryModuleName(candidatePath);
|
|
@@ -14652,9 +14496,9 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
14652
14496
|
}
|
|
14653
14497
|
if (writeShim) restoreEntryModuleName(candidatePath, writeShim, { resign: true });
|
|
14654
14498
|
else if (publishedBlob) resignMachOBinary(candidatePath);
|
|
14655
|
-
patchedSize =
|
|
14499
|
+
patchedSize = statSync7(candidatePath).size;
|
|
14656
14500
|
patchedSha256 = sha256File(candidatePath);
|
|
14657
|
-
|
|
14501
|
+
renameSync2(candidatePath, binaryPath);
|
|
14658
14502
|
} catch (err) {
|
|
14659
14503
|
const detailLines = err instanceof PatchApplyError ? summarizePatchResults(err.results) : [];
|
|
14660
14504
|
if (opts.trace && detailLines.length) {
|
|
@@ -14669,7 +14513,7 @@ async function applyPatch(binaryPath, version, desired, configHash, opts) {
|
|
|
14669
14513
|
} finally {
|
|
14670
14514
|
if (candidateDir !== void 0) {
|
|
14671
14515
|
try {
|
|
14672
|
-
|
|
14516
|
+
rmSync(candidateDir, { recursive: true, force: true });
|
|
14673
14517
|
} catch (err) {
|
|
14674
14518
|
if (opts.trace) {
|
|
14675
14519
|
process.stderr.write(
|
|
@@ -14743,7 +14587,7 @@ function runRestoreCommand(target) {
|
|
|
14743
14587
|
}
|
|
14744
14588
|
if (manifest && manifest.backupPath && manifest.binaryPath === binaryPath && manifest.claudeVersion !== version && manifest.backupPath !== plan.backupPath) {
|
|
14745
14589
|
try {
|
|
14746
|
-
if (
|
|
14590
|
+
if (existsSync4(manifest.backupPath)) {
|
|
14747
14591
|
recordBackupProvenance(manifest.backupPath, manifest.binaryPath, {
|
|
14748
14592
|
assumed: manifest.pristineProvenance === "assumed"
|
|
14749
14593
|
});
|
|
@@ -14772,7 +14616,7 @@ function runRestoreCommand(target) {
|
|
|
14772
14616
|
}
|
|
14773
14617
|
let publishedMode;
|
|
14774
14618
|
try {
|
|
14775
|
-
publishedMode =
|
|
14619
|
+
publishedMode = statSync7(binaryPath).mode;
|
|
14776
14620
|
} catch {
|
|
14777
14621
|
}
|
|
14778
14622
|
try {
|
|
@@ -14831,7 +14675,7 @@ async function runPatchCommand(opts = {}) {
|
|
|
14831
14675
|
binaryPath,
|
|
14832
14676
|
claudeVersion: version,
|
|
14833
14677
|
configHash,
|
|
14834
|
-
binarySize:
|
|
14678
|
+
binarySize: statSync7(binaryPath).size
|
|
14835
14679
|
});
|
|
14836
14680
|
if (state === "current") {
|
|
14837
14681
|
p2.log.success(`claude ${version} is already patched with the current model config \u2014 nothing to do.`);
|
|
@@ -14844,7 +14688,7 @@ async function runPatchCommand(opts = {}) {
|
|
|
14844
14688
|
}
|
|
14845
14689
|
if (manifest && manifest.backupPath && (manifest.binaryPath !== binaryPath || manifest.claudeVersion !== version)) {
|
|
14846
14690
|
try {
|
|
14847
|
-
if (
|
|
14691
|
+
if (existsSync4(manifest.backupPath)) {
|
|
14848
14692
|
recordBackupProvenance(manifest.backupPath, manifest.binaryPath, {
|
|
14849
14693
|
assumed: manifest.pristineProvenance === "assumed"
|
|
14850
14694
|
});
|
|
@@ -14900,7 +14744,7 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
14900
14744
|
binaryPath: resolved.binaryPath,
|
|
14901
14745
|
claudeVersion: resolved.version,
|
|
14902
14746
|
configHash,
|
|
14903
|
-
binarySize:
|
|
14747
|
+
binarySize: statSync7(resolved.binaryPath).size
|
|
14904
14748
|
});
|
|
14905
14749
|
if (state === "current") return;
|
|
14906
14750
|
const interactive = !opts.dryRun && !opts.agentStdout && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -15234,19 +15078,19 @@ function localProvidersToServerModels(localProviders) {
|
|
|
15234
15078
|
}
|
|
15235
15079
|
|
|
15236
15080
|
// src/registry/credential-cleanup-journal.ts
|
|
15237
|
-
import { randomUUID as
|
|
15081
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
15238
15082
|
import {
|
|
15239
15083
|
closeSync as closeSync9,
|
|
15240
|
-
existsSync as
|
|
15084
|
+
existsSync as existsSync5,
|
|
15241
15085
|
fstatSync as fstatSync3,
|
|
15242
15086
|
fsyncSync as fsyncSync2,
|
|
15243
15087
|
lstatSync,
|
|
15244
15088
|
mkdirSync as mkdirSync6,
|
|
15245
15089
|
openSync as openSync9,
|
|
15246
|
-
readFileSync as
|
|
15247
|
-
renameSync as
|
|
15090
|
+
readFileSync as readFileSync10,
|
|
15091
|
+
renameSync as renameSync3,
|
|
15248
15092
|
unlinkSync as unlinkSync4,
|
|
15249
|
-
writeFileSync as
|
|
15093
|
+
writeFileSync as writeFileSync6
|
|
15250
15094
|
} from "fs";
|
|
15251
15095
|
import { dirname as dirname6 } from "path";
|
|
15252
15096
|
var JOURNAL_SCHEMA_VERSION = 1;
|
|
@@ -15327,7 +15171,7 @@ function parseJournal(raw) {
|
|
|
15327
15171
|
};
|
|
15328
15172
|
}
|
|
15329
15173
|
function readJournalUnlocked(path) {
|
|
15330
|
-
if (!
|
|
15174
|
+
if (!existsSync5(path)) return emptyJournal();
|
|
15331
15175
|
let fd;
|
|
15332
15176
|
try {
|
|
15333
15177
|
const before = lstatSync(path);
|
|
@@ -15350,7 +15194,7 @@ function readJournalUnlocked(path) {
|
|
|
15350
15194
|
if (opened.size > MAX_JOURNAL_BYTES) {
|
|
15351
15195
|
throw new Error("Credential cleanup journal is too large.");
|
|
15352
15196
|
}
|
|
15353
|
-
return parseJournal(JSON.parse(
|
|
15197
|
+
return parseJournal(JSON.parse(readFileSync10(fd, "utf8")));
|
|
15354
15198
|
} catch (error) {
|
|
15355
15199
|
const message = error instanceof Error ? error.message : String(error);
|
|
15356
15200
|
throw new Error(`Could not read credential cleanup journal: ${message}`);
|
|
@@ -15374,17 +15218,17 @@ function writeJournalUnlocked(journal, path) {
|
|
|
15374
15218
|
assertRegistryWriteOwnership(path);
|
|
15375
15219
|
ensureSecureAppHome();
|
|
15376
15220
|
mkdirSync6(dirname6(path), { recursive: true, mode: DIR_MODE2 });
|
|
15377
|
-
const tmp = `${path}.${process.pid}.${
|
|
15221
|
+
const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
|
|
15378
15222
|
let fd;
|
|
15379
15223
|
try {
|
|
15380
15224
|
fd = openSync9(tmp, "wx", FILE_MODE4);
|
|
15381
|
-
|
|
15225
|
+
writeFileSync6(fd, `${JSON.stringify(journal, null, 2)}
|
|
15382
15226
|
`);
|
|
15383
15227
|
fsyncSync2(fd);
|
|
15384
15228
|
closeSync9(fd);
|
|
15385
15229
|
fd = void 0;
|
|
15386
15230
|
assertRegistryWriteOwnership(path);
|
|
15387
|
-
|
|
15231
|
+
renameSync3(tmp, path);
|
|
15388
15232
|
syncParentDirectory(path);
|
|
15389
15233
|
} finally {
|
|
15390
15234
|
if (fd !== void 0) closeSync9(fd);
|
|
@@ -18267,7 +18111,7 @@ async function askSaveServerPassword() {
|
|
|
18267
18111
|
|
|
18268
18112
|
// src/server/router.ts
|
|
18269
18113
|
import { createServer as createServer2 } from "http";
|
|
18270
|
-
import { randomUUID as
|
|
18114
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
18271
18115
|
|
|
18272
18116
|
// src/openai-adapter.ts
|
|
18273
18117
|
import { tool as tool2, jsonSchema as jsonSchema2, streamText as streamText2, generateText as generateText2 } from "ai";
|
|
@@ -18683,7 +18527,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
18683
18527
|
plog(`model not found: ${body.model}`);
|
|
18684
18528
|
return;
|
|
18685
18529
|
}
|
|
18686
|
-
const requestId =
|
|
18530
|
+
const requestId = randomUUID8();
|
|
18687
18531
|
const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
|
|
18688
18532
|
const claudeSessionId = extractClaudeSessionId(body, claudeSessionIdHeader);
|
|
18689
18533
|
const claudeAgentIds = extractClaudeAgentIds(req.headers);
|
|
@@ -19357,14 +19201,14 @@ import * as http2 from "http";
|
|
|
19357
19201
|
import * as https from "https";
|
|
19358
19202
|
import * as net from "net";
|
|
19359
19203
|
import { PassThrough } from "stream";
|
|
19360
|
-
import { randomUUID as
|
|
19204
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
19361
19205
|
import { URL as URL2 } from "url";
|
|
19362
19206
|
import { createBrotliDecompress, createGunzip, createInflate } from "zlib";
|
|
19363
19207
|
|
|
19364
19208
|
// src/http-proxy/ca.ts
|
|
19365
19209
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
19366
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
19367
|
-
import { dirname as dirname7, join as
|
|
19210
|
+
import { chmodSync as chmodSync5, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
19211
|
+
import { dirname as dirname7, join as join8, resolve as resolve3 } from "path";
|
|
19368
19212
|
import forge from "node-forge";
|
|
19369
19213
|
var CERT_DIR = "http-proxy";
|
|
19370
19214
|
var CA_CERT_FILE = "clodex-ca.pem";
|
|
@@ -19379,22 +19223,22 @@ function serialNumber() {
|
|
|
19379
19223
|
return bytes.toString("hex");
|
|
19380
19224
|
}
|
|
19381
19225
|
function certPaths() {
|
|
19382
|
-
const dir =
|
|
19226
|
+
const dir = join8(getAppHome(), CERT_DIR);
|
|
19383
19227
|
return {
|
|
19384
19228
|
dir,
|
|
19385
|
-
caCert:
|
|
19386
|
-
caKey:
|
|
19387
|
-
serverCert:
|
|
19388
|
-
serverKey:
|
|
19389
|
-
version:
|
|
19229
|
+
caCert: join8(dir, CA_CERT_FILE),
|
|
19230
|
+
caKey: join8(dir, CA_KEY_FILE),
|
|
19231
|
+
serverCert: join8(dir, SERVER_CERT_FILE),
|
|
19232
|
+
serverKey: join8(dir, SERVER_KEY_FILE),
|
|
19233
|
+
version: join8(dir, CERT_VERSION_FILE)
|
|
19390
19234
|
};
|
|
19391
19235
|
}
|
|
19392
19236
|
function writePrivate(path, value) {
|
|
19393
|
-
|
|
19237
|
+
writeFileSync7(path, value, { encoding: "utf8", mode: 384 });
|
|
19394
19238
|
chmodSync5(path, 384);
|
|
19395
19239
|
}
|
|
19396
19240
|
function writePublic(path, value) {
|
|
19397
|
-
|
|
19241
|
+
writeFileSync7(path, value, { encoding: "utf8", mode: 420 });
|
|
19398
19242
|
chmodSync5(path, 420);
|
|
19399
19243
|
}
|
|
19400
19244
|
function generateCertificates(paths) {
|
|
@@ -19439,8 +19283,8 @@ function generateCertificates(paths) {
|
|
|
19439
19283
|
}
|
|
19440
19284
|
function storedCertificatesAreCurrent(paths) {
|
|
19441
19285
|
try {
|
|
19442
|
-
const ca = forge.pki.certificateFromPem(
|
|
19443
|
-
const server = forge.pki.certificateFromPem(
|
|
19286
|
+
const ca = forge.pki.certificateFromPem(readFileSync11(paths.caCert, "utf8"));
|
|
19287
|
+
const server = forge.pki.certificateFromPem(readFileSync11(paths.serverCert, "utf8"));
|
|
19444
19288
|
const now = Date.now();
|
|
19445
19289
|
const renewalBuffer = 7 * 24 * 60 * 60 * 1e3;
|
|
19446
19290
|
return ca.validity.notBefore.getTime() <= now && ca.validity.notAfter.getTime() > now + renewalBuffer && server.validity.notBefore.getTime() <= now && server.validity.notAfter.getTime() > now + renewalBuffer && ca.verify(ca) && ca.verify(server);
|
|
@@ -19451,13 +19295,13 @@ function storedCertificatesAreCurrent(paths) {
|
|
|
19451
19295
|
function ensureHttpProxyCertificates() {
|
|
19452
19296
|
const paths = certPaths();
|
|
19453
19297
|
const required = [paths.caCert, paths.caKey, paths.serverCert, paths.serverKey, paths.version];
|
|
19454
|
-
const current = required.every(
|
|
19298
|
+
const current = required.every(existsSync6) && readFileSync11(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
|
|
19455
19299
|
if (!current) generateCertificates(paths);
|
|
19456
19300
|
return {
|
|
19457
19301
|
caCertPath: paths.caCert,
|
|
19458
|
-
caCert:
|
|
19459
|
-
serverCert:
|
|
19460
|
-
serverKey:
|
|
19302
|
+
caCert: readFileSync11(paths.caCert, "utf8"),
|
|
19303
|
+
serverCert: readFileSync11(paths.serverCert, "utf8"),
|
|
19304
|
+
serverKey: readFileSync11(paths.serverKey, "utf8")
|
|
19461
19305
|
};
|
|
19462
19306
|
}
|
|
19463
19307
|
function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarning) {
|
|
@@ -19471,7 +19315,7 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
|
|
|
19471
19315
|
let additionalCa;
|
|
19472
19316
|
try {
|
|
19473
19317
|
if (resolve3(additionalCaCertPath) === resolve3(relayCaCertPath)) return relayCaCertPath;
|
|
19474
|
-
additionalCa =
|
|
19318
|
+
additionalCa = readFileSync11(additionalCaCertPath, "utf8").trim();
|
|
19475
19319
|
} catch (err) {
|
|
19476
19320
|
return warn(
|
|
19477
19321
|
`NODE_EXTRA_CA_CERTS=${additionalCaCertPath} cannot be read (${err instanceof Error ? err.message : String(err)}), so it is not part of the proxy CA bundle. Where node reports this itself it says only "Ignoring extra certs ... load failed", without naming the variable. Clear or correct it.`
|
|
@@ -19481,8 +19325,8 @@ function ensureHttpProxyCaBundle(relayCaCertPath, additionalCaCertPath, onWarnin
|
|
|
19481
19325
|
return warn(`NODE_EXTRA_CA_CERTS=${additionalCaCertPath} is empty, so it adds nothing.`);
|
|
19482
19326
|
}
|
|
19483
19327
|
try {
|
|
19484
|
-
const relayCa =
|
|
19485
|
-
const combinedPath =
|
|
19328
|
+
const relayCa = readFileSync11(relayCaCertPath, "utf8").trimEnd();
|
|
19329
|
+
const combinedPath = join8(dirname7(relayCaCertPath), "combined-ca.pem");
|
|
19486
19330
|
writePublic(combinedPath, `${relayCa}
|
|
19487
19331
|
${additionalCa}
|
|
19488
19332
|
`);
|
|
@@ -20215,7 +20059,7 @@ async function startHttpProxy(options) {
|
|
|
20215
20059
|
}
|
|
20216
20060
|
const messagesEndpoint = anthropicMessagesEndpoint(req.url);
|
|
20217
20061
|
if (req.method === "POST" && messagesEndpoint) {
|
|
20218
|
-
const requestId =
|
|
20062
|
+
const requestId = randomUUID9();
|
|
20219
20063
|
let parsed = null;
|
|
20220
20064
|
let route;
|
|
20221
20065
|
let adapterBody = rawBody;
|
|
@@ -20622,6 +20466,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
|
|
|
20622
20466
|
if (handle.webSocketDiagnosticsLogPath) {
|
|
20623
20467
|
console.log(` WebSocket diagnostics: ${handle.webSocketDiagnosticsLogPath}`);
|
|
20624
20468
|
console.log(pc10.yellow(" Diagnostic mode records request headers and metadata; credential headers are redacted."));
|
|
20469
|
+
console.log(pc10.yellow(" It also records OpenAI usage-limit reports verbatim, including account credits and promotions."));
|
|
20625
20470
|
}
|
|
20626
20471
|
console.log("");
|
|
20627
20472
|
printHttpProxyModels(loaded.routes, loaded.aliases);
|
|
@@ -21001,6 +20846,7 @@ async function runServerCommand(options = {}) {
|
|
|
21001
20846
|
if (webSocketDiagnosticsLogPath) {
|
|
21002
20847
|
console.log(` WebSocket diagnostics: ${webSocketDiagnosticsLogPath}`);
|
|
21003
20848
|
console.log(pc11.yellow(" Diagnostic mode records request headers and metadata; credential headers are redacted."));
|
|
20849
|
+
console.log(pc11.yellow(" It also records OpenAI usage-limit reports verbatim, including account credits and promotions."));
|
|
21004
20850
|
}
|
|
21005
20851
|
if (mode === "network") {
|
|
21006
20852
|
for (const { name, address } of getLocalIps()) {
|
|
@@ -21290,6 +21136,227 @@ function planLaunchWizard(opts) {
|
|
|
21290
21136
|
return { skip: false, target: null };
|
|
21291
21137
|
}
|
|
21292
21138
|
|
|
21139
|
+
// src/vscode-launcher.ts
|
|
21140
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
21141
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readdirSync, readFileSync as readFileSync12, rmSync as rmSync2, statSync as statSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
21142
|
+
import { tmpdir } from "os";
|
|
21143
|
+
import { dirname as dirname8, join as join9 } from "path";
|
|
21144
|
+
import { fileURLToPath } from "url";
|
|
21145
|
+
var LAUNCHER_EXE_NAME = "clodex-claude.exe";
|
|
21146
|
+
var VSCODE_WRAPPER_SETTING = "claudeCode.claudeProcessWrapper";
|
|
21147
|
+
var NODE_PATH_PLACEHOLDER = '"__CLODEX_NODE_PATH__"';
|
|
21148
|
+
var WRAPPER_PATH_PLACEHOLDER = '"__CLODEX_WRAPPER_SCRIPT_PATH__"';
|
|
21149
|
+
var LAUNCHER_SOURCE_FILE = "clodex-claude-launcher.cs";
|
|
21150
|
+
var UTF8_BOM = "\uFEFF";
|
|
21151
|
+
var CSC_ARGS = ["/nologo", "/target:exe", "/platform:anycpu", "/optimize+", `/out:${LAUNCHER_EXE_NAME}`, LAUNCHER_SOURCE_FILE];
|
|
21152
|
+
var packageRoot = join9(dirname8(fileURLToPath(import.meta.url)), "..");
|
|
21153
|
+
function defaultLauncherInputs() {
|
|
21154
|
+
return {
|
|
21155
|
+
launcherSourcePath: join9(packageRoot, "launcher", LAUNCHER_SOURCE_FILE),
|
|
21156
|
+
wrapperScriptPath: join9(packageRoot, "dist", "claude-wrapper.js")
|
|
21157
|
+
};
|
|
21158
|
+
}
|
|
21159
|
+
function launcherInstallPath(env = process.env) {
|
|
21160
|
+
return join9(getAppHome(env), "bin", LAUNCHER_EXE_NAME);
|
|
21161
|
+
}
|
|
21162
|
+
function csharpStringLiteral(value) {
|
|
21163
|
+
let literal = '"';
|
|
21164
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
21165
|
+
const unit = value.charCodeAt(i);
|
|
21166
|
+
if (unit === 92) literal += "\\\\";
|
|
21167
|
+
else if (unit === 34) literal += '\\"';
|
|
21168
|
+
else if (unit >= 32 && unit <= 126) literal += value[i];
|
|
21169
|
+
else literal += `\\u${unit.toString(16).padStart(4, "0")}`;
|
|
21170
|
+
}
|
|
21171
|
+
return `${literal}"`;
|
|
21172
|
+
}
|
|
21173
|
+
function renderLauncherSource(template, paths) {
|
|
21174
|
+
const replaceOnce = (source, placeholder, value) => {
|
|
21175
|
+
const first = source.indexOf(placeholder);
|
|
21176
|
+
if (first === -1 || source.indexOf(placeholder, first + 1) !== -1) {
|
|
21177
|
+
throw new Error(`launcher source must contain ${placeholder} exactly once`);
|
|
21178
|
+
}
|
|
21179
|
+
return source.slice(0, first) + csharpStringLiteral(value) + source.slice(first + placeholder.length);
|
|
21180
|
+
};
|
|
21181
|
+
const withNode = replaceOnce(template, NODE_PATH_PLACEHOLDER, paths.nodePath);
|
|
21182
|
+
return replaceOnce(withNode, WRAPPER_PATH_PLACEHOLDER, paths.wrapperScriptPath);
|
|
21183
|
+
}
|
|
21184
|
+
function cscSearchRoots(env = process.env) {
|
|
21185
|
+
const windir = env.WINDIR?.trim() || env.SystemRoot?.trim() || "C:\\Windows";
|
|
21186
|
+
const dotnet = join9(windir, "Microsoft.NET");
|
|
21187
|
+
return [join9(dotnet, "Framework64"), join9(dotnet, "Framework")];
|
|
21188
|
+
}
|
|
21189
|
+
function frameworkVersionKey(name) {
|
|
21190
|
+
return name.slice(1).split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
21191
|
+
}
|
|
21192
|
+
function compareVersionKeys(a, b) {
|
|
21193
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
21194
|
+
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
|
21195
|
+
if (diff !== 0) return diff;
|
|
21196
|
+
}
|
|
21197
|
+
return 0;
|
|
21198
|
+
}
|
|
21199
|
+
function findCsc(env = process.env) {
|
|
21200
|
+
for (const root of cscSearchRoots(env)) {
|
|
21201
|
+
let entries;
|
|
21202
|
+
try {
|
|
21203
|
+
entries = readdirSync(root);
|
|
21204
|
+
} catch {
|
|
21205
|
+
continue;
|
|
21206
|
+
}
|
|
21207
|
+
const candidates = entries.filter((name) => /^v4\.\d+(\.\d+)*$/i.test(name)).sort((a, b) => compareVersionKeys(frameworkVersionKey(b), frameworkVersionKey(a)));
|
|
21208
|
+
for (const version of candidates) {
|
|
21209
|
+
const csc = join9(root, version, "csc.exe");
|
|
21210
|
+
try {
|
|
21211
|
+
if (statSync8(csc).isFile()) return csc;
|
|
21212
|
+
} catch {
|
|
21213
|
+
}
|
|
21214
|
+
}
|
|
21215
|
+
}
|
|
21216
|
+
return null;
|
|
21217
|
+
}
|
|
21218
|
+
function formatWrapperSetting(exePath) {
|
|
21219
|
+
return `"${VSCODE_WRAPPER_SETTING}": ${JSON.stringify(exePath)}`;
|
|
21220
|
+
}
|
|
21221
|
+
var runCsc = (cscPath, args, cwd) => {
|
|
21222
|
+
const result = spawnSync2(cscPath, [...args], {
|
|
21223
|
+
cwd,
|
|
21224
|
+
encoding: "utf8",
|
|
21225
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
21226
|
+
windowsHide: true
|
|
21227
|
+
});
|
|
21228
|
+
return {
|
|
21229
|
+
status: result.status,
|
|
21230
|
+
stdout: result.stdout ?? "",
|
|
21231
|
+
stderr: result.stderr ?? "",
|
|
21232
|
+
...result.error ? { error: result.error } : {}
|
|
21233
|
+
};
|
|
21234
|
+
};
|
|
21235
|
+
function describeError(err) {
|
|
21236
|
+
return err instanceof Error ? err.message : String(err);
|
|
21237
|
+
}
|
|
21238
|
+
function installVscodeLauncher(opts = {}) {
|
|
21239
|
+
const platform = opts.platform ?? process.platform;
|
|
21240
|
+
const env = opts.env ?? process.env;
|
|
21241
|
+
const defaults = defaultLauncherInputs();
|
|
21242
|
+
const nodePath = opts.nodePath ?? process.execPath;
|
|
21243
|
+
const wrapperScriptPath = opts.wrapperScriptPath ?? defaults.wrapperScriptPath;
|
|
21244
|
+
const launcherSourcePath = opts.launcherSourcePath ?? defaults.launcherSourcePath;
|
|
21245
|
+
const compile = opts.compile ?? runCsc;
|
|
21246
|
+
if (platform !== "win32") {
|
|
21247
|
+
return {
|
|
21248
|
+
ok: false,
|
|
21249
|
+
reason: "not-windows",
|
|
21250
|
+
message: `clodex install-vscode-launcher is only needed on Windows. On macOS and Linux npm installs clodex-claude as a program the VS Code extension can spawn directly: set ${VSCODE_WRAPPER_SETTING} to the path printed by \`which clodex-claude\`.`
|
|
21251
|
+
};
|
|
21252
|
+
}
|
|
21253
|
+
const cscPath = findCsc(env);
|
|
21254
|
+
if (!cscPath) {
|
|
21255
|
+
return {
|
|
21256
|
+
ok: false,
|
|
21257
|
+
reason: "no-csc",
|
|
21258
|
+
message: `could not find the .NET Framework C# compiler (csc.exe). It ships with Windows 10/11 under ${cscSearchRoots(env).join(" or ")}\\v4.*; if it is missing, install .NET Framework 4.8 from Microsoft and re-run.`
|
|
21259
|
+
};
|
|
21260
|
+
}
|
|
21261
|
+
for (const [label, path] of [["claude-wrapper.js", wrapperScriptPath], ["node.exe", nodePath], ["launcher source", launcherSourcePath]]) {
|
|
21262
|
+
if (!existsSync7(path)) {
|
|
21263
|
+
return { ok: false, reason: "missing-input", message: `${label} is not at ${path}; reinstall clodex and re-run.` };
|
|
21264
|
+
}
|
|
21265
|
+
}
|
|
21266
|
+
let source;
|
|
21267
|
+
try {
|
|
21268
|
+
source = renderLauncherSource(readFileSync12(launcherSourcePath, "utf8"), { nodePath, wrapperScriptPath });
|
|
21269
|
+
} catch (err) {
|
|
21270
|
+
return { ok: false, reason: "missing-input", message: `could not prepare the launcher source: ${describeError(err)}` };
|
|
21271
|
+
}
|
|
21272
|
+
const exePath = launcherInstallPath(env);
|
|
21273
|
+
const tempRoot = opts.tempRoot ?? tmpdir();
|
|
21274
|
+
let scratch;
|
|
21275
|
+
try {
|
|
21276
|
+
scratch = mkdtempSync2(join9(tempRoot, "clodex-vscode-launcher-"));
|
|
21277
|
+
} catch (err) {
|
|
21278
|
+
return { ok: false, reason: "install-failed", message: `could not create a scratch directory under ${tempRoot}: ${describeError(err)}` };
|
|
21279
|
+
}
|
|
21280
|
+
let result;
|
|
21281
|
+
try {
|
|
21282
|
+
result = buildAndPublish({ scratch, source, cscPath, compile, exePath, nodePath, wrapperScriptPath });
|
|
21283
|
+
} catch (err) {
|
|
21284
|
+
result = { ok: false, reason: "install-failed", message: `could not build the launcher: ${describeError(err)}` };
|
|
21285
|
+
}
|
|
21286
|
+
const cleanupWarning = removeScratch(scratch);
|
|
21287
|
+
return cleanupWarning ? { ...result, warning: cleanupWarning } : result;
|
|
21288
|
+
}
|
|
21289
|
+
function buildAndPublish(step) {
|
|
21290
|
+
const { scratch, cscPath, exePath } = step;
|
|
21291
|
+
writeFileSync8(join9(scratch, LAUNCHER_SOURCE_FILE), UTF8_BOM + step.source, "utf8");
|
|
21292
|
+
const compiled = step.compile(cscPath, CSC_ARGS, scratch);
|
|
21293
|
+
const builtExe = join9(scratch, LAUNCHER_EXE_NAME);
|
|
21294
|
+
if (compiled.error || compiled.status !== 0 || !existsSync7(builtExe)) {
|
|
21295
|
+
const output = [compiled.stdout, compiled.stderr].map((text5) => text5.trim()).filter(Boolean).join("\n");
|
|
21296
|
+
return {
|
|
21297
|
+
ok: false,
|
|
21298
|
+
reason: "compile-failed",
|
|
21299
|
+
message: compiled.error ? `could not run ${cscPath}: ${compiled.error.message}` : `${cscPath} failed (exit ${compiled.status ?? "signal"}) while building the launcher.`,
|
|
21300
|
+
...output ? { detail: output } : {}
|
|
21301
|
+
};
|
|
21302
|
+
}
|
|
21303
|
+
try {
|
|
21304
|
+
mkdirSync8(dirname8(exePath), { recursive: true });
|
|
21305
|
+
publishFileByRename(builtExe, exePath);
|
|
21306
|
+
} catch (err) {
|
|
21307
|
+
return {
|
|
21308
|
+
ok: false,
|
|
21309
|
+
reason: "install-failed",
|
|
21310
|
+
message: `could not write ${exePath}: ${describeError(err)}. If VS Code is running a chat through the launcher, close it and re-run.`
|
|
21311
|
+
};
|
|
21312
|
+
}
|
|
21313
|
+
return { ok: true, exePath, cscPath, nodePath: step.nodePath, wrapperScriptPath: step.wrapperScriptPath };
|
|
21314
|
+
}
|
|
21315
|
+
function removeScratch(scratch) {
|
|
21316
|
+
try {
|
|
21317
|
+
rmSync2(scratch, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
21318
|
+
return void 0;
|
|
21319
|
+
} catch (err) {
|
|
21320
|
+
return `could not remove the scratch directory ${scratch}: ${describeError(err)}`;
|
|
21321
|
+
}
|
|
21322
|
+
}
|
|
21323
|
+
function successReport(result) {
|
|
21324
|
+
return [
|
|
21325
|
+
`Built ${result.exePath}`,
|
|
21326
|
+
` compiler: ${result.cscPath}`,
|
|
21327
|
+
` runs: ${result.nodePath} ${result.wrapperScriptPath}`,
|
|
21328
|
+
"",
|
|
21329
|
+
"Add this to your VS Code settings (Ctrl+Shift+P \u2192 Preferences: Open User Settings (JSON)):",
|
|
21330
|
+
"",
|
|
21331
|
+
` ${formatWrapperSetting(result.exePath)}`,
|
|
21332
|
+
"",
|
|
21333
|
+
"Then reload the window (Developer: Reload Window). Keep `clodex server --proxy` running:",
|
|
21334
|
+
"the launcher starts clodex-claude, which finds that server and bridges each session to it,",
|
|
21335
|
+
"so HTTPS_PROXY/HTTP_PROXY/NODE_EXTRA_CA_CERTS entries in claudeCode.environmentVariables",
|
|
21336
|
+
"are no longer needed (remove them so Claude still starts when the server is down).",
|
|
21337
|
+
"",
|
|
21338
|
+
"With `clodex patch` applied to your installed Claude Code, clodex-claude runs that patched",
|
|
21339
|
+
"install in place of the extension's bundled claude.exe when the two builds match, which is",
|
|
21340
|
+
"what puts clodex models in the extension's model picker. Re-run `clodex patch` after either",
|
|
21341
|
+
"the extension or the CLI updates; a mismatched chat runs the bundled binary and logs one line",
|
|
21342
|
+
`to the extension's "Claude VSCode" output channel.`,
|
|
21343
|
+
"",
|
|
21344
|
+
"Re-run `clodex install-vscode-launcher` after switching Node versions or moving the clodex",
|
|
21345
|
+
"install: the paths above are compiled into the executable."
|
|
21346
|
+
];
|
|
21347
|
+
}
|
|
21348
|
+
function runInstallVscodeLauncherCommand(opts = {}) {
|
|
21349
|
+
const result = installVscodeLauncher(opts);
|
|
21350
|
+
if (result.warning) console.error(`clodex: warning: ${result.warning}`);
|
|
21351
|
+
if (!result.ok) {
|
|
21352
|
+
console.error(`clodex: ${result.message}`);
|
|
21353
|
+
if (result.detail) console.error(result.detail);
|
|
21354
|
+
return 1;
|
|
21355
|
+
}
|
|
21356
|
+
console.log(successReport(result).join("\n"));
|
|
21357
|
+
return 0;
|
|
21358
|
+
}
|
|
21359
|
+
|
|
21293
21360
|
// src/cli.ts
|
|
21294
21361
|
var STARTER_CLAUDE_FLAGS = /* @__PURE__ */ new Set(["--dry-run", "--trace", "--fast", "--endpoint", "--proxy", "--save-mode", "--help", "-h", "--version", "-v"]);
|
|
21295
21362
|
var CLODEX_LAUNCH_FLAGS = /* @__PURE__ */ new Set(["--provider", "--model", "--context"]);
|
|
@@ -21511,6 +21578,15 @@ function parseArgs(args) {
|
|
|
21511
21578
|
}
|
|
21512
21579
|
return parsed2;
|
|
21513
21580
|
}
|
|
21581
|
+
if (first === "install-vscode-launcher") {
|
|
21582
|
+
const parsed2 = emptyParsed("install-vscode-launcher");
|
|
21583
|
+
for (const arg of rest) {
|
|
21584
|
+
if (arg === "--help" || arg === "-h") parsed2.showHelp = true;
|
|
21585
|
+
else if (arg === "--version" || arg === "-v") parsed2.showVersion = true;
|
|
21586
|
+
else if (!parsed2.error) parsed2.error = `Unknown install-vscode-launcher option: ${arg}`;
|
|
21587
|
+
}
|
|
21588
|
+
return parsed2;
|
|
21589
|
+
}
|
|
21514
21590
|
if (first !== "claude") {
|
|
21515
21591
|
return {
|
|
21516
21592
|
...emptyParsed("root"),
|
|
@@ -21556,6 +21632,7 @@ ${pc13.bold("Usage:")}
|
|
|
21556
21632
|
clodex models
|
|
21557
21633
|
clodex favorites
|
|
21558
21634
|
clodex providers
|
|
21635
|
+
clodex install-vscode-launcher
|
|
21559
21636
|
clodex --help
|
|
21560
21637
|
clodex --version
|
|
21561
21638
|
|
|
@@ -21570,6 +21647,9 @@ ${pc13.bold("Commands:")}
|
|
|
21570
21647
|
models Manage favorite models and aliases (max ${MAX_MODEL_CATALOG})
|
|
21571
21648
|
favorites Alias for models
|
|
21572
21649
|
providers Add or sign in to model providers
|
|
21650
|
+
install-vscode-launcher
|
|
21651
|
+
Windows only: build the .exe the Claude Code VS Code extension
|
|
21652
|
+
needs to launch Claude through clodex
|
|
21573
21653
|
|
|
21574
21654
|
${pc13.bold("Bridge modes (claude and server):")}
|
|
21575
21655
|
--endpoint Local Anthropic-format gateway; Claude Code launches with
|
|
@@ -21676,8 +21756,9 @@ ${pc13.bold("Common options (both modes):")}
|
|
|
21676
21756
|
~/.clodex/server-runtime.json, so the
|
|
21677
21757
|
clodex-claude wrapper never bridges to it
|
|
21678
21758
|
(CLODEX_NO_DISCOVERY=1 works too)
|
|
21679
|
-
--ws-diagnostics Log sanitized request envelopes
|
|
21680
|
-
head decisions
|
|
21759
|
+
--ws-diagnostics Log sanitized request envelopes, WebSocket
|
|
21760
|
+
head decisions, and the server's usage-limit
|
|
21761
|
+
reports verbatim (includes account credits)
|
|
21681
21762
|
--help, --version Help / version
|
|
21682
21763
|
|
|
21683
21764
|
${pc13.bold("Endpoint mode only")} ${pc13.dim("(error if combined with --proxy)")}:
|
|
@@ -21810,6 +21891,42 @@ ${pc13.bold("Behavior:")}
|
|
|
21810
21891
|
permissions. Local failures are reported but never block the built-ins.
|
|
21811
21892
|
Run clodex patch again after every claude update.`;
|
|
21812
21893
|
}
|
|
21894
|
+
function installVscodeLauncherHelpText() {
|
|
21895
|
+
return `${pc13.bold("clodex install-vscode-launcher")} v${VERSION}
|
|
21896
|
+
Windows only. Build a small native clodex-claude.exe that the Claude Code VS Code
|
|
21897
|
+
extension can use as its claudeCode.claudeProcessWrapper, so chats in the editor
|
|
21898
|
+
launch Claude Code through clodex.
|
|
21899
|
+
|
|
21900
|
+
${pc13.bold("Usage:")}
|
|
21901
|
+
clodex install-vscode-launcher
|
|
21902
|
+
clodex install-vscode-launcher --help
|
|
21903
|
+
|
|
21904
|
+
${pc13.bold("Behavior:")}
|
|
21905
|
+
The extension spawns its process wrapper without a shell, and on Windows npm
|
|
21906
|
+
installs clodex-claude as script shims (clodex-claude, .cmd, .ps1) with no .exe,
|
|
21907
|
+
so the setting fails with spawn EINVAL.
|
|
21908
|
+
This command compiles launcher/clodex-claude-launcher.cs from the clodex package
|
|
21909
|
+
with the C# compiler that ships inside the .NET Framework on Windows 10/11
|
|
21910
|
+
(csc.exe under %WINDIR%\\Microsoft.NET), writes the result to
|
|
21911
|
+
~/.clodex/bin/clodex-claude.exe, and prints the VS Code setting to paste.
|
|
21912
|
+
Nothing is downloaded and no settings file is edited.
|
|
21913
|
+
|
|
21914
|
+
The launcher runs node.exe with clodex's claude-wrapper.js and hands the
|
|
21915
|
+
extension's arguments through unchanged, so the wrapper bridges the session to
|
|
21916
|
+
a running clodex server --proxy. Node and Claude Code are tied to the launcher
|
|
21917
|
+
with a Windows job object so that cancelling a chat leaves no stray processes;
|
|
21918
|
+
if Windows refuses the job object the launcher says so on stderr and runs anyway.
|
|
21919
|
+
As on macOS and Linux, clodex-claude then runs your clodex-patched install in
|
|
21920
|
+
place of the bundled claude.exe when the two builds match, so clodex models
|
|
21921
|
+
appear in the extension's model picker; re-run clodex patch after updates.
|
|
21922
|
+
|
|
21923
|
+
The paths to node.exe and to the clodex install are compiled in: re-run this
|
|
21924
|
+
command after switching Node versions or moving the clodex install.
|
|
21925
|
+
On macOS and Linux it is not needed \u2014 point the setting at clodex-claude itself.
|
|
21926
|
+
|
|
21927
|
+
${pc13.bold("Setup guide:")}
|
|
21928
|
+
docs/windows-setup.md in the clodex package (also on GitHub).`;
|
|
21929
|
+
}
|
|
21813
21930
|
function printHelp(text5) {
|
|
21814
21931
|
console.log(`
|
|
21815
21932
|
${text5}
|
|
@@ -22719,6 +22836,17 @@ Error: ${resolved.error}
|
|
|
22719
22836
|
localPatches: parsed.patchLocalPatches
|
|
22720
22837
|
});
|
|
22721
22838
|
}
|
|
22839
|
+
if (parsed.command === "install-vscode-launcher") {
|
|
22840
|
+
if (parsed.showVersion) {
|
|
22841
|
+
console.log(VERSION);
|
|
22842
|
+
return 0;
|
|
22843
|
+
}
|
|
22844
|
+
if (parsed.showHelp) {
|
|
22845
|
+
printHelp(installVscodeLauncherHelpText());
|
|
22846
|
+
return 0;
|
|
22847
|
+
}
|
|
22848
|
+
return runInstallVscodeLauncherCommand();
|
|
22849
|
+
}
|
|
22722
22850
|
if (parsed.showVersion) {
|
|
22723
22851
|
console.log(VERSION);
|
|
22724
22852
|
return 0;
|
|
@@ -22741,7 +22869,7 @@ Error: ${resolved.error}
|
|
|
22741
22869
|
function isCliEntryPoint() {
|
|
22742
22870
|
if (!process.argv[1]) return false;
|
|
22743
22871
|
try {
|
|
22744
|
-
return realpathSync3(
|
|
22872
|
+
return realpathSync3(fileURLToPath2(import.meta.url)) === realpathSync3(process.argv[1]);
|
|
22745
22873
|
} catch {
|
|
22746
22874
|
return false;
|
|
22747
22875
|
}
|
|
@@ -22759,6 +22887,7 @@ if (isCliEntryPoint()) {
|
|
|
22759
22887
|
}
|
|
22760
22888
|
export {
|
|
22761
22889
|
claudeHelpText,
|
|
22890
|
+
installVscodeLauncherHelpText,
|
|
22762
22891
|
main,
|
|
22763
22892
|
modelsHelpText,
|
|
22764
22893
|
parseArgs,
|