@bman654/clodex 2.12.2 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -382,7 +382,7 @@ import { join } from "path";
382
382
  // package.json
383
383
  var package_default = {
384
384
  name: "@bman654/clodex",
385
- version: "2.12.2",
385
+ version: "2.13.0",
386
386
  publishConfig: {
387
387
  access: "public"
388
388
  },
@@ -3895,8 +3895,8 @@ import {
3895
3895
  statSync as statSync8,
3896
3896
  unlinkSync as unlinkSync3,
3897
3897
  writeFileSync as writeFileSync6,
3898
- openSync as openSync7,
3899
- closeSync as closeSync7,
3898
+ openSync as openSync8,
3899
+ closeSync as closeSync8,
3900
3900
  realpathSync as realpathSync2
3901
3901
  } from "fs";
3902
3902
  import { homedir as homedir3 } from "os";
@@ -8120,6 +8120,8 @@ import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
8120
8120
 
8121
8121
  // src/oauth/responses-websocket.ts
8122
8122
  import { createHash as createHash6 } from "crypto";
8123
+ import { closeSync as closeSync6, openSync as openSync6 } from "fs";
8124
+ import { devNull } from "os";
8123
8125
  import { AsyncLocalStorage } from "async_hooks";
8124
8126
 
8125
8127
  // src/outbound-proxy.ts
@@ -8886,8 +8888,8 @@ var FAILURE_EVENT_TYPES = /* @__PURE__ */ new Set(["error", "response.failed", "
8886
8888
  var RESPONSES_WS_HARD_TTL_MS = 55 * 6e4;
8887
8889
  var RESPONSES_WS_IDLE_TTL_MS = 30 * 6e4;
8888
8890
  var RESPONSES_WS_NURSERY_IDLE_TTL_MS = 5 * 6e4;
8889
- var RESPONSES_WS_MAX_CONNECTIONS = 64;
8890
- var RESPONSES_WS_MAX_NURSERY_CONNECTIONS = 48;
8891
+ var RESPONSES_WS_MAX_CONNECTIONS = Number.POSITIVE_INFINITY;
8892
+ var RESPONSES_WS_MAX_NURSERY_CONNECTIONS = Number.POSITIVE_INFINITY;
8891
8893
  var diagnosticContext = new AsyncLocalStorage();
8892
8894
  function withResponsesWebSocketDiagnosticContext(context, fn) {
8893
8895
  return diagnosticContext.run(context, fn);
@@ -9845,6 +9847,64 @@ function evictOldestIdleGeneration(generation, maxConnections, reason) {
9845
9847
  }
9846
9848
  return evictions;
9847
9849
  }
9850
+ var DESCRIPTOR_EXHAUSTION_CODES = /* @__PURE__ */ new Set(["EMFILE", "ENFILE"]);
9851
+ function isDescriptorExhaustion(code) {
9852
+ return typeof code === "string" && DESCRIPTOR_EXHAUSTION_CODES.has(code);
9853
+ }
9854
+ function descriptorExhaustionCode(error) {
9855
+ const code = error.code;
9856
+ if (isDescriptorExhaustion(code)) return code;
9857
+ try {
9858
+ closeSync6(openSync6(devNull, "r"));
9859
+ return void 0;
9860
+ } catch (probe) {
9861
+ const probeCode = probe.code;
9862
+ return isDescriptorExhaustion(probeCode) ? probeCode : void 0;
9863
+ }
9864
+ }
9865
+ var descriptorExhaustionNoticed = false;
9866
+ function shedIdleConnectionsForDescriptors(failing, ctx, code, socketErrorCode) {
9867
+ const registered = connectionEntries().filter((entry) => entry !== failing);
9868
+ const idle = registered.filter((entry) => !entry.inFlight && entry.generation !== "isolated").sort((left, right) => left.lastUsedAt - right.lastUsedAt);
9869
+ for (const victim of idle) {
9870
+ const idleMs = Math.max(0, victim.options.now() - victim.lastUsedAt);
9871
+ victim.debug(
9872
+ `shedding idle ${victim.generation} connection after ${code}: connection=${victim.debugId} idle_ms=${idleMs} reason=descriptor_exhaustion`
9873
+ );
9874
+ victim.inFlight = false;
9875
+ victim.current = void 0;
9876
+ unregisterEntry(victim);
9877
+ try {
9878
+ victim.socket.terminate();
9879
+ } catch {
9880
+ }
9881
+ }
9882
+ failing.debug(
9883
+ `${code} opening connection=${failing.debugId}: registered=${registered.length} shed=${idle.length}` + (socketErrorCode && socketErrorCode !== code ? ` reported_as=${socketErrorCode}` : "")
9884
+ );
9885
+ emitContextDiagnostic(failing, ctx, {
9886
+ event: "ws_descriptor_exhaustion",
9887
+ code,
9888
+ detectedBy: socketErrorCode === code ? "error_code" : "descriptor_probe",
9889
+ socketErrorCode: boundedDiagnosticIdentifier(socketErrorCode),
9890
+ heldConnections: registered.length,
9891
+ shedConnections: idle.length
9892
+ });
9893
+ if (descriptorExhaustionNoticed) return;
9894
+ descriptorExhaustionNoticed = true;
9895
+ emitParentNotice(
9896
+ `clodex: warning: ${descriptorLimitName(code)} was reached (${code}) while opening a ChatGPT connection. clodex had ${registered.length} pooled connection(s) registered and closed ${idle.length} idle one(s) to recover; each parallel conversation keeps one open. ${descriptorLimitRemedy(code)} Further open-file warnings suppressed.`
9897
+ );
9898
+ }
9899
+ function descriptorLimitName(code) {
9900
+ return code === "ENFILE" ? "the system-wide open-file limit" : "this process's open-file limit";
9901
+ }
9902
+ function descriptorLimitRemedy(code) {
9903
+ return code === "ENFILE" ? "Close other programs holding many files, or raise the system-wide file limit, if this recurs." : "Raise it with `ulimit -n` in the shell that starts clodex (or the service limit for a launchd/systemd-managed server) if this recurs.";
9904
+ }
9905
+ function descriptorExhaustionMessage(code, registered) {
9906
+ return `${descriptorLimitName(code)} was reached (${code}) while opening a ChatGPT connection with ${registered} pooled connection(s) registered; ${descriptorLimitRemedy(code)}`;
9907
+ }
9848
9908
  function isModelDataEvent(type) {
9849
9909
  return Boolean(type && (type.includes(".delta") || type === "response.output_item.added" || type === "response.output_item.done"));
9850
9910
  }
@@ -10177,13 +10237,21 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
10177
10237
  socket.on("error", (error) => {
10178
10238
  const ctx = entry.current;
10179
10239
  if (ctx) {
10240
+ const socketErrorCode = error.code;
10180
10241
  const details = {
10181
10242
  source: "socket_error",
10182
10243
  socketErrorName: boundedDiagnosticIdentifier(error.name),
10183
- socketErrorCode: boundedDiagnosticIdentifier(error.code),
10244
+ socketErrorCode: boundedDiagnosticIdentifier(socketErrorCode),
10184
10245
  ...diagnosticTextFingerprint("errorMessage", error.message)
10185
10246
  };
10186
- handleTransportFailure(entry, ctx, error.message, details);
10247
+ let message = error.message;
10248
+ const exhaustion = entry.open ? void 0 : descriptorExhaustionCode(error);
10249
+ if (exhaustion) {
10250
+ const registered = connectionEntries().filter((other) => other !== entry).length;
10251
+ message = descriptorExhaustionMessage(exhaustion, registered);
10252
+ shedIdleConnectionsForDescriptors(entry, ctx, exhaustion, socketErrorCode);
10253
+ }
10254
+ handleTransportFailure(entry, ctx, message, details);
10187
10255
  } else deleteEntry(entry);
10188
10256
  });
10189
10257
  socket.on("close", (code, reason) => {
@@ -10204,13 +10272,16 @@ function createConnection(WebSocket, wsUrl, headers, persistent, key, options, d
10204
10272
  });
10205
10273
  return entry;
10206
10274
  }
10275
+ function diagnosticCap(cap) {
10276
+ return Number.isFinite(cap) ? cap : null;
10277
+ }
10207
10278
  function envConnectionCap(name, log12) {
10208
10279
  const raw = process.env[name];
10209
10280
  if (raw === void 0 || raw.trim() === "") return void 0;
10210
10281
  const value = Number(raw.trim());
10211
- if (!Number.isInteger(value) || value < 1 || value > 1024) {
10282
+ if (!Number.isInteger(value) || value < 1) {
10212
10283
  try {
10213
- log12?.(`ws: ignoring ${name}=${raw} (expected an integer between 1 and 1024)`);
10284
+ log12?.(`ws: ignoring ${name}=${raw} (expected a positive integer)`);
10214
10285
  } catch {
10215
10286
  }
10216
10287
  return void 0;
@@ -10510,8 +10581,10 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
10510
10581
  activeConnectionCount: connectionCount(),
10511
10582
  nurseryConnectionCount: connectionCountByGeneration("nursery"),
10512
10583
  establishedConnectionCount: connectionCountByGeneration("established"),
10513
- maxConnections: resolvedOptions.maxConnections,
10514
- maxNurseryConnections: resolvedOptions.maxNurseryConnections,
10584
+ // `null` is unbounded, the shipped default. A number is a finite cap on
10585
+ // the idle pool from an env or programmatic override.
10586
+ maxConnections: diagnosticCap(resolvedOptions.maxConnections),
10587
+ maxNurseryConnections: diagnosticCap(resolvedOptions.maxNurseryConnections),
10515
10588
  selectedConnectionId: selected?.debugId,
10516
10589
  selectedGeneration: selected?.generation,
10517
10590
  continuationMatchMode: selectedMatch?.mode,
@@ -11548,7 +11621,7 @@ function thinkingProviderOptions(npm) {
11548
11621
 
11549
11622
  // src/proxy.ts
11550
11623
  import { createServer } from "http";
11551
- import { appendFileSync as appendFileSync2, openSync as openSync6, writeSync as writeSync5, closeSync as closeSync6 } from "fs";
11624
+ import { appendFileSync as appendFileSync2, openSync as openSync7, writeSync as writeSync5, closeSync as closeSync7 } from "fs";
11552
11625
 
11553
11626
  // src/http-utils.ts
11554
11627
  import * as zlib from "zlib";
@@ -13346,12 +13419,12 @@ function createTranslationLifecycle(logPath, requestId, claudeSessionId, modelId
13346
13419
  function appendSecureLog(logPath, line) {
13347
13420
  const redacted = redactTraceLine(line);
13348
13421
  try {
13349
- const fd = openSync6(logPath, "a", 384);
13422
+ const fd = openSync7(logPath, "a", 384);
13350
13423
  try {
13351
13424
  writeSync5(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
13352
13425
  `);
13353
13426
  } finally {
13354
- closeSync6(fd);
13427
+ closeSync7(fd);
13355
13428
  }
13356
13429
  } catch {
13357
13430
  try {
@@ -14222,10 +14295,10 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14222
14295
  mkdirSync5(join8(lockPath, ".."), { recursive: true, mode: 448 });
14223
14296
  for (let attempt = 0; attempt < 2; attempt++) {
14224
14297
  try {
14225
- const fd = openSync7(lockPath, "wx");
14298
+ const fd = openSync8(lockPath, "wx");
14226
14299
  const content = { pid: process.pid, startedAt: now };
14227
14300
  writeFileSync6(fd, JSON.stringify(content));
14228
- closeSync7(fd);
14301
+ closeSync8(fd);
14229
14302
  return () => {
14230
14303
  try {
14231
14304
  unlinkSync3(lockPath);
@@ -15157,13 +15230,13 @@ function localProvidersToServerModels(localProviders) {
15157
15230
  // src/registry/credential-cleanup-journal.ts
15158
15231
  import { randomUUID as randomUUID5 } from "crypto";
15159
15232
  import {
15160
- closeSync as closeSync8,
15233
+ closeSync as closeSync9,
15161
15234
  existsSync as existsSync6,
15162
15235
  fstatSync as fstatSync3,
15163
15236
  fsyncSync as fsyncSync2,
15164
15237
  lstatSync,
15165
15238
  mkdirSync as mkdirSync6,
15166
- openSync as openSync8,
15239
+ openSync as openSync9,
15167
15240
  readFileSync as readFileSync11,
15168
15241
  renameSync as renameSync4,
15169
15242
  unlinkSync as unlinkSync4,
@@ -15255,7 +15328,7 @@ function readJournalUnlocked(path) {
15255
15328
  if (before.isSymbolicLink() || !before.isFile()) {
15256
15329
  throw new Error("Credential cleanup journal must be a regular file.");
15257
15330
  }
15258
- fd = openSync8(path, "r");
15331
+ fd = openSync9(path, "r");
15259
15332
  const opened = fstatSync3(fd);
15260
15333
  if (before.dev !== opened.dev || before.ino !== opened.ino) {
15261
15334
  throw new Error("Credential cleanup journal changed while opening.");
@@ -15276,19 +15349,19 @@ function readJournalUnlocked(path) {
15276
15349
  const message = error instanceof Error ? error.message : String(error);
15277
15350
  throw new Error(`Could not read credential cleanup journal: ${message}`);
15278
15351
  } finally {
15279
- if (fd !== void 0) closeSync8(fd);
15352
+ if (fd !== void 0) closeSync9(fd);
15280
15353
  }
15281
15354
  }
15282
15355
  function syncParentDirectory(path) {
15283
15356
  let fd;
15284
15357
  try {
15285
- fd = openSync8(dirname6(path), "r");
15358
+ fd = openSync9(dirname6(path), "r");
15286
15359
  fsyncSync2(fd);
15287
15360
  } catch (error) {
15288
15361
  const code = error.code;
15289
15362
  if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EPERM") throw error;
15290
15363
  } finally {
15291
- if (fd !== void 0) closeSync8(fd);
15364
+ if (fd !== void 0) closeSync9(fd);
15292
15365
  }
15293
15366
  }
15294
15367
  function writeJournalUnlocked(journal, path) {
@@ -15298,17 +15371,17 @@ function writeJournalUnlocked(journal, path) {
15298
15371
  const tmp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
15299
15372
  let fd;
15300
15373
  try {
15301
- fd = openSync8(tmp, "wx", FILE_MODE4);
15374
+ fd = openSync9(tmp, "wx", FILE_MODE4);
15302
15375
  writeFileSync7(fd, `${JSON.stringify(journal, null, 2)}
15303
15376
  `);
15304
15377
  fsyncSync2(fd);
15305
- closeSync8(fd);
15378
+ closeSync9(fd);
15306
15379
  fd = void 0;
15307
15380
  assertRegistryWriteOwnership(path);
15308
15381
  renameSync4(tmp, path);
15309
15382
  syncParentDirectory(path);
15310
15383
  } finally {
15311
- if (fd !== void 0) closeSync8(fd);
15384
+ if (fd !== void 0) closeSync9(fd);
15312
15385
  try {
15313
15386
  unlinkSync4(tmp);
15314
15387
  } catch (error) {