@bman654/clodex 2.12.2 → 2.13.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
@@ -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.1",
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
@@ -8181,17 +8183,17 @@ function proxyUrlTargetsListener(proxyUrl, listenerHost, listenerPort, localAddr
8181
8183
  return isWildcard(boundHost) && (isWildcard(proxyHost) || localAddresses.has(proxyHost));
8182
8184
  }
8183
8185
  var dispatcherInstalled = false;
8184
- async function installOutboundProxyDispatcher() {
8186
+ async function installOutboundDispatcher() {
8185
8187
  if (dispatcherInstalled) return true;
8186
- if (!hasOutboundProxyEnv()) return false;
8187
8188
  try {
8188
- const { EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
8189
- setGlobalDispatcher(new EnvHttpProxyAgent());
8189
+ const { Agent: Agent3, EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
8190
+ const dispatcher = hasOutboundProxyEnv() ? new EnvHttpProxyAgent({ allowH2: false }) : new Agent3({ allowH2: false });
8191
+ setGlobalDispatcher(dispatcher);
8190
8192
  dispatcherInstalled = true;
8191
8193
  return true;
8192
8194
  } catch (err) {
8193
8195
  console.error(
8194
- `clodex: HTTP(S)_PROXY is set but installing the outbound proxy dispatcher failed; using direct connections (${err instanceof Error ? err.message : String(err)})`
8196
+ `clodex: installing the outbound fetch dispatcher failed; continuing with Node's existing dispatcher (${err instanceof Error ? err.message : String(err)})`
8195
8197
  );
8196
8198
  return false;
8197
8199
  }
@@ -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";
@@ -11831,7 +11904,13 @@ function anthropicUpstreamHeaders(apiKey, stream = false, inboundBeta, authType,
11831
11904
  }
11832
11905
  var UpstreamUnreachableError = class extends Error {
11833
11906
  constructor(cause) {
11834
- super(`Upstream unreachable: ${cause instanceof Error ? cause.message : String(cause)}`);
11907
+ const detail = cause instanceof Error ? cause.message : String(cause);
11908
+ const directCode = cause !== null && typeof cause === "object" ? cause.code : void 0;
11909
+ const nestedCause = cause instanceof Error ? cause.cause : void 0;
11910
+ const nestedCode = nestedCause !== null && typeof nestedCause === "object" ? nestedCause.code : void 0;
11911
+ const code = typeof directCode === "string" ? directCode : typeof nestedCode === "string" ? nestedCode : void 0;
11912
+ const detailWithCode = code && !detail.includes(code) ? detail ? `${detail} (${code})` : code : detail;
11913
+ super(`Upstream unreachable: ${detailWithCode}`, { cause });
11835
11914
  this.name = "UpstreamUnreachableError";
11836
11915
  }
11837
11916
  };
@@ -13346,12 +13425,12 @@ function createTranslationLifecycle(logPath, requestId, claudeSessionId, modelId
13346
13425
  function appendSecureLog(logPath, line) {
13347
13426
  const redacted = redactTraceLine(line);
13348
13427
  try {
13349
- const fd = openSync6(logPath, "a", 384);
13428
+ const fd = openSync7(logPath, "a", 384);
13350
13429
  try {
13351
13430
  writeSync5(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
13352
13431
  `);
13353
13432
  } finally {
13354
- closeSync6(fd);
13433
+ closeSync7(fd);
13355
13434
  }
13356
13435
  } catch {
13357
13436
  try {
@@ -14222,10 +14301,10 @@ function tryAcquirePatchLock(lockPath = getPatchLockPath(), opts = {}) {
14222
14301
  mkdirSync5(join8(lockPath, ".."), { recursive: true, mode: 448 });
14223
14302
  for (let attempt = 0; attempt < 2; attempt++) {
14224
14303
  try {
14225
- const fd = openSync7(lockPath, "wx");
14304
+ const fd = openSync8(lockPath, "wx");
14226
14305
  const content = { pid: process.pid, startedAt: now };
14227
14306
  writeFileSync6(fd, JSON.stringify(content));
14228
- closeSync7(fd);
14307
+ closeSync8(fd);
14229
14308
  return () => {
14230
14309
  try {
14231
14310
  unlinkSync3(lockPath);
@@ -15157,13 +15236,13 @@ function localProvidersToServerModels(localProviders) {
15157
15236
  // src/registry/credential-cleanup-journal.ts
15158
15237
  import { randomUUID as randomUUID5 } from "crypto";
15159
15238
  import {
15160
- closeSync as closeSync8,
15239
+ closeSync as closeSync9,
15161
15240
  existsSync as existsSync6,
15162
15241
  fstatSync as fstatSync3,
15163
15242
  fsyncSync as fsyncSync2,
15164
15243
  lstatSync,
15165
15244
  mkdirSync as mkdirSync6,
15166
- openSync as openSync8,
15245
+ openSync as openSync9,
15167
15246
  readFileSync as readFileSync11,
15168
15247
  renameSync as renameSync4,
15169
15248
  unlinkSync as unlinkSync4,
@@ -15255,7 +15334,7 @@ function readJournalUnlocked(path) {
15255
15334
  if (before.isSymbolicLink() || !before.isFile()) {
15256
15335
  throw new Error("Credential cleanup journal must be a regular file.");
15257
15336
  }
15258
- fd = openSync8(path, "r");
15337
+ fd = openSync9(path, "r");
15259
15338
  const opened = fstatSync3(fd);
15260
15339
  if (before.dev !== opened.dev || before.ino !== opened.ino) {
15261
15340
  throw new Error("Credential cleanup journal changed while opening.");
@@ -15276,19 +15355,19 @@ function readJournalUnlocked(path) {
15276
15355
  const message = error instanceof Error ? error.message : String(error);
15277
15356
  throw new Error(`Could not read credential cleanup journal: ${message}`);
15278
15357
  } finally {
15279
- if (fd !== void 0) closeSync8(fd);
15358
+ if (fd !== void 0) closeSync9(fd);
15280
15359
  }
15281
15360
  }
15282
15361
  function syncParentDirectory(path) {
15283
15362
  let fd;
15284
15363
  try {
15285
- fd = openSync8(dirname6(path), "r");
15364
+ fd = openSync9(dirname6(path), "r");
15286
15365
  fsyncSync2(fd);
15287
15366
  } catch (error) {
15288
15367
  const code = error.code;
15289
15368
  if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EPERM") throw error;
15290
15369
  } finally {
15291
- if (fd !== void 0) closeSync8(fd);
15370
+ if (fd !== void 0) closeSync9(fd);
15292
15371
  }
15293
15372
  }
15294
15373
  function writeJournalUnlocked(journal, path) {
@@ -15298,17 +15377,17 @@ function writeJournalUnlocked(journal, path) {
15298
15377
  const tmp = `${path}.${process.pid}.${randomUUID5()}.tmp`;
15299
15378
  let fd;
15300
15379
  try {
15301
- fd = openSync8(tmp, "wx", FILE_MODE4);
15380
+ fd = openSync9(tmp, "wx", FILE_MODE4);
15302
15381
  writeFileSync7(fd, `${JSON.stringify(journal, null, 2)}
15303
15382
  `);
15304
15383
  fsyncSync2(fd);
15305
- closeSync8(fd);
15384
+ closeSync9(fd);
15306
15385
  fd = void 0;
15307
15386
  assertRegistryWriteOwnership(path);
15308
15387
  renameSync4(tmp, path);
15309
15388
  syncParentDirectory(path);
15310
15389
  } finally {
15311
- if (fd !== void 0) closeSync8(fd);
15390
+ if (fd !== void 0) closeSync9(fd);
15312
15391
  try {
15313
15392
  unlinkSync4(tmp);
15314
15393
  } catch (error) {
@@ -20298,6 +20377,7 @@ async function startHttpProxy(options) {
20298
20377
  mitmServer.emit("connection", clientSocket);
20299
20378
  return;
20300
20379
  }
20380
+ clientSocket.once("error", () => clientSocket.destroy());
20301
20381
  const target = authorityParts(req.url ?? "");
20302
20382
  if (!target) {
20303
20383
  clientSocket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
@@ -22533,7 +22613,7 @@ Error: ${launchPlan.error}
22533
22613
  return exitCode;
22534
22614
  }
22535
22615
  async function main(args = process.argv.slice(2)) {
22536
- await installOutboundProxyDispatcher();
22616
+ await installOutboundDispatcher();
22537
22617
  const parsed = parseArgs(args);
22538
22618
  if (parsed.error) {
22539
22619
  console.error(pc13.red(`