@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/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
- } from "./chunk-ZM2KYLPH.js";
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.13.1",
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 = 12;
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 missing = ALIASES.filter((a) => !new RegExp("case" + reEsc(q(a)) + ":return").test(js));
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
- /(case"best":\{[^{}]*\})/,
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 createHash9 } from "crypto";
3856
+ import { createHash as createHash8 } from "crypto";
3886
3857
  import {
3887
3858
  chmodSync as chmodSync4,
3888
3859
  copyFileSync,
3889
- existsSync as existsSync5,
3860
+ existsSync as existsSync4,
3890
3861
  mkdtempSync,
3891
3862
  mkdirSync as mkdirSync5,
3892
- readFileSync as readFileSync10,
3893
- renameSync as renameSync3,
3894
- rmSync as rmSync2,
3895
- statSync as statSync8,
3863
+ readFileSync as readFileSync9,
3864
+ renameSync as renameSync2,
3865
+ rmSync,
3866
+ statSync as statSync7,
3896
3867
  unlinkSync as unlinkSync3,
3897
- writeFileSync as writeFileSync6,
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 homedir3 } from "os";
3903
- import { basename, dirname as dirname5, join as join8 } from "path";
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 packageRoot = dirname4(dirname4(binaryPath));
7056
- const packageJsonPath = join6(packageRoot, "package.json");
7057
- const installScriptPath = join6(packageRoot, "install.cjs");
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 as writeSync2 } from "fs";
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 = writeSync2(fd, bytes, 0, bytes.length, offset);
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 writeSync3 } from "fs";
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
- writeSync3(fd, needle, 0, 8, standInOffset);
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
- writeSync3(fd, value, 0, 8, pointerOffset);
7579
- writeSync3(fd, shim.displaced, 0, 8, standInOffset);
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 writeSync4 } from "fs";
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 = writeSync4(fd, bytes, written, bytes.length - written, position + written);
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 createHash6 } from "crypto";
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 ? createHash6("sha256").update(authorization).digest("hex") : "";
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 createHash6("sha256").update(canonicalJson(stable)).digest("hex");
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] = createHash6("sha256").update(canonicalJson(payload[key])).digest("hex").slice(0, 12);
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 createHash6("sha256").update(material).digest("hex");
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 && typeof schema === "object" && "default" in schema) {
9058
- perTool.set(prop, canonicalJson(schema.default));
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 createHash6("sha256").update(JSON.stringify(tuples)).digest("hex").slice(0, 16);
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 expected = perTool.get(key);
9082
- if (expected !== void 0 && canonicalJson(value) === expected) continue;
9083
- out[key] = value;
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 createHash6("sha256").update(canonicalJson(normalizeToolCallJson(value, defaults))).digest("hex").slice(0, 16);
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`]: createHash6("sha256").update(value).digest("hex").slice(0, 16)
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 ? createHash6("sha256").update(value).digest("hex").slice(0, 16) : void 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) return;
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 createConnection(WebSocket, wsUrl, headers, persistent, key, options, debug, agent) {
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 ? createHash6("sha256").update(options.accountId).digest("hex").slice(0, 16) : "",
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 createHash7, randomUUID as randomUUID3 } from "crypto";
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 = createHash7("sha256").update(input).digest("hex");
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 createHash7("sha256").update(`cliUserID:${seed}`).digest("hex");
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 writeSync5, closeSync as closeSync7 } from "fs";
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 anthropicSseModelRewrite(override) {
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 === "message_start" && parsed.message && typeof parsed.message.model === "string") {
11953
- parsed.message.model = override;
11954
- return "data: " + JSON.stringify(parsed);
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(anthropicSseModelRewrite(options.responseModelOverride)).on("error", () => res.destroy()).pipe(res);
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 (options.responseModelOverride && parsed && typeof parsed === "object" && !Array.isArray(parsed) && parsed.type === "message" && typeof parsed.model === "string") {
12043
- parsed.model = options.responseModelOverride;
12044
- text5 = JSON.stringify(parsed);
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 randomUUID4 } from "crypto";
11898
+ import { randomUUID as randomUUID6 } from "crypto";
12055
11899
 
12056
11900
  // src/sdk-adapter.ts
12057
- import { createHash as createHash8 } from "crypto";
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-" + createHash8("sha256").update(sessionId).digest("hex").slice(0, 32);
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-" + createHash8("sha256").update(material).digest("hex").slice(0, 32);
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 = "msg_" + Date.now();
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: "msg_" + Date.now(),
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
- writeSync5(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
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 = randomUUID4();
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 ?? randomUUID4() : void 0
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 ?? randomUUID4() } : {}
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 join8(getAppHome(), "patch.lock");
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
- writeFileSync6(path, `${JSON.stringify(manifest, null, 2)}
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 createHash9("sha256").update(JSON.stringify(payload)).digest("hex");
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(join8(lockPath, ".."), { recursive: true, mode: 448 });
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
- writeFileSync6(fd, JSON.stringify(content));
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(readFileSync10(lockPath, "utf8"));
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 = join8(homedir3(), ".local", "bin", "claude");
14340
- if (envOverride && !existsSync5(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 || (existsSync5(nativeSymlink) ? nativeSymlink : null) || findClaudeBinary();
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 (!statSync8(resolved).isFile()) return { ok: false, reason: "binary-not-found" };
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
- renameSync3(temp, to);
14280
+ renameSync2(temp, to);
14437
14281
  } catch (err) {
14438
14282
  try {
14439
- rmSync2(temp, { force: true });
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(join8(dirname5(binaryPath), ".clodex-patch-"));
14508
- const candidatePath = join8(candidateDir, basename(binaryPath));
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 = statSync8(candidatePath).size;
14499
+ patchedSize = statSync7(candidatePath).size;
14656
14500
  patchedSha256 = sha256File(candidatePath);
14657
- renameSync3(candidatePath, binaryPath);
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
- rmSync2(candidateDir, { recursive: true, force: true });
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 (existsSync5(manifest.backupPath)) {
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 = statSync8(binaryPath).mode;
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: statSync8(binaryPath).size
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 (existsSync5(manifest.backupPath)) {
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: statSync8(resolved.binaryPath).size
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 randomUUID5 } from "crypto";
15081
+ import { randomUUID as randomUUID7 } from "crypto";
15238
15082
  import {
15239
15083
  closeSync as closeSync9,
15240
- existsSync as existsSync6,
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 readFileSync11,
15247
- renameSync as renameSync4,
15090
+ readFileSync as readFileSync10,
15091
+ renameSync as renameSync3,
15248
15092
  unlinkSync as unlinkSync4,
15249
- writeFileSync as writeFileSync7
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 (!existsSync6(path)) return emptyJournal();
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(readFileSync11(fd, "utf8")));
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}.${randomUUID5()}.tmp`;
15221
+ const tmp = `${path}.${process.pid}.${randomUUID7()}.tmp`;
15378
15222
  let fd;
15379
15223
  try {
15380
15224
  fd = openSync9(tmp, "wx", FILE_MODE4);
15381
- writeFileSync7(fd, `${JSON.stringify(journal, null, 2)}
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
- renameSync4(tmp, path);
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 randomUUID6 } from "crypto";
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 = randomUUID6();
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 randomUUID7 } from "crypto";
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 existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
19367
- import { dirname as dirname7, join as join9, resolve as resolve3 } from "path";
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 = join9(getAppHome(), CERT_DIR);
19226
+ const dir = join8(getAppHome(), CERT_DIR);
19383
19227
  return {
19384
19228
  dir,
19385
- caCert: join9(dir, CA_CERT_FILE),
19386
- caKey: join9(dir, CA_KEY_FILE),
19387
- serverCert: join9(dir, SERVER_CERT_FILE),
19388
- serverKey: join9(dir, SERVER_KEY_FILE),
19389
- version: join9(dir, CERT_VERSION_FILE)
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
- writeFileSync8(path, value, { encoding: "utf8", mode: 384 });
19237
+ writeFileSync7(path, value, { encoding: "utf8", mode: 384 });
19394
19238
  chmodSync5(path, 384);
19395
19239
  }
19396
19240
  function writePublic(path, value) {
19397
- writeFileSync8(path, value, { encoding: "utf8", mode: 420 });
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(readFileSync12(paths.caCert, "utf8"));
19443
- const server = forge.pki.certificateFromPem(readFileSync12(paths.serverCert, "utf8"));
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(existsSync7) && readFileSync12(paths.version, "utf8") === CERT_VERSION && storedCertificatesAreCurrent(paths);
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: readFileSync12(paths.caCert, "utf8"),
19459
- serverCert: readFileSync12(paths.serverCert, "utf8"),
19460
- serverKey: readFileSync12(paths.serverKey, "utf8")
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 = readFileSync12(additionalCaCertPath, "utf8").trim();
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 = readFileSync12(relayCaCertPath, "utf8").trimEnd();
19485
- const combinedPath = join9(dirname7(relayCaCertPath), "combined-ca.pem");
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 = randomUUID7();
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 and WebSocket
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(fileURLToPath(import.meta.url)) === realpathSync3(process.argv[1]);
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,