@bman654/clodex 2.18.10 → 2.19.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
@@ -7,12 +7,14 @@ import {
7
7
  REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT,
8
8
  assertRegistryWriteOwnership,
9
9
  backupDir,
10
+ cancelOnExit,
10
11
  clearActiveOAuthAccount,
11
12
  collectPristineFacts,
12
13
  contentAddressedBackupPath,
13
14
  customProviderId,
14
15
  emitParentNotice,
15
16
  ensureSecureAppHome,
17
+ exitAfterDrain,
16
18
  findClaudeBinary,
17
19
  getAppHome,
18
20
  getClaudeVersionForBinary,
@@ -74,7 +76,7 @@ import {
74
76
  withRegistryWriteLockSync,
75
77
  writeParentNoticeLines,
76
78
  writeParentNoticeLinesSync
77
- } from "./chunk-VFYRDLDD.js";
79
+ } from "./chunk-Q2YFYQQT.js";
78
80
 
79
81
  // src/cli.ts
80
82
  import pc13 from "picocolors";
@@ -323,19 +325,25 @@ function launchClaude(env, model, extraArgs) {
323
325
  env,
324
326
  shell: isWindows
325
327
  });
326
- const forward = (signal) => {
327
- child.kill(signal);
328
+ const forwardSigint = () => {
329
+ child.kill("SIGINT");
328
330
  };
329
- process.once("SIGINT", () => forward("SIGINT"));
330
- process.once("SIGTERM", () => forward("SIGTERM"));
331
- child.on("exit", (code) => {
332
- restore();
333
- resolve4(code ?? 0);
334
- });
335
- child.on("error", (err) => {
331
+ const forwardSigterm = () => {
332
+ child.kill("SIGTERM");
333
+ };
334
+ process.once("SIGINT", forwardSigint);
335
+ process.once("SIGTERM", forwardSigterm);
336
+ const stopForwarding = () => {
337
+ process.off("SIGINT", forwardSigint);
338
+ process.off("SIGTERM", forwardSigterm);
339
+ };
340
+ const finish = (code) => {
341
+ stopForwarding();
336
342
  restore();
337
- resolve4(1);
338
- });
343
+ resolve4(code);
344
+ };
345
+ child.on("exit", (code) => finish(code ?? 0));
346
+ child.on("error", () => finish(1));
339
347
  });
340
348
  }
341
349
 
@@ -346,7 +354,7 @@ import { join } from "path";
346
354
  // package.json
347
355
  var package_default = {
348
356
  name: "@bman654/clodex",
349
- version: "2.18.10",
357
+ version: "2.19.0",
350
358
  publishConfig: {
351
359
  access: "public"
352
360
  },
@@ -5682,9 +5690,12 @@ function getUserPricingCachePath() {
5682
5690
  function loadPricingCache() {
5683
5691
  return readPricingFile(getUserPricingCachePath()) ?? loadBundledPricingCache();
5684
5692
  }
5685
- async function fetchPricingCache() {
5693
+ async function fetchPricingCache(signal) {
5686
5694
  const controller = new AbortController();
5687
5695
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
5696
+ const onCallerAbort = () => controller.abort();
5697
+ if (signal?.aborted) controller.abort();
5698
+ else signal?.addEventListener("abort", onCallerAbort, { once: true });
5688
5699
  try {
5689
5700
  const response = await fetch(PRICING_API_URL, {
5690
5701
  signal: controller.signal,
@@ -5699,6 +5710,7 @@ async function fetchPricingCache() {
5699
5710
  return null;
5700
5711
  } finally {
5701
5712
  clearTimeout(timer);
5713
+ signal?.removeEventListener("abort", onCallerAbort);
5702
5714
  }
5703
5715
  }
5704
5716
  function pickPricingRow(rows, platform) {
@@ -5802,17 +5814,24 @@ function applyPricingToRegistryProviders(registry, cache) {
5802
5814
  return changed;
5803
5815
  }
5804
5816
  function enrichPricingAsync(onComplete) {
5817
+ const controller = new AbortController();
5818
+ const { signal } = controller;
5819
+ const unregister = cancelOnExit(() => controller.abort());
5805
5820
  void (async () => {
5806
- const fetched = await fetchPricingCache();
5821
+ const fetched = await fetchPricingCache(signal);
5822
+ if (signal.aborted) return;
5807
5823
  const cache = fetched ?? loadPricingCache();
5808
5824
  const changed = await withRegistryWriteLock(() => {
5825
+ if (signal.aborted) return void 0;
5809
5826
  const registry = loadRegistryStrict();
5810
5827
  const updated = applyPricingToRegistryProviders(registry, cache);
5811
5828
  if (updated) saveRegistry(registry);
5812
5829
  return updated;
5813
- });
5814
- onComplete?.(changed);
5815
- })().catch(() => onComplete?.(false));
5830
+ }, { signal });
5831
+ if (changed !== void 0) onComplete?.(changed);
5832
+ })().catch(() => {
5833
+ if (!signal.aborted) onComplete?.(false);
5834
+ }).finally(unregister);
5816
5835
  }
5817
5836
  function pricingPlatformForProvider(templateId, providerId) {
5818
5837
  return TEMPLATE_TO_PRICING_PLATFORM[templateId] ?? TEMPLATE_TO_PRICING_PLATFORM[providerId];
@@ -5821,6 +5840,7 @@ function pricingPlatformForProvider(templateId, providerId) {
5821
5840
  // src/registry/models-dev.ts
5822
5841
  var MODELS_DEV_API_URL = "https://models.dev/api.json";
5823
5842
  var FETCH_TIMEOUT_MS2 = 15e3;
5843
+ var MODELS_DEV_REFRESH_DELAY_MS = 500;
5824
5844
  var FILE_MODE2 = 384;
5825
5845
  var META_KEY = "_relay_meta";
5826
5846
  var memoryCache = null;
@@ -5921,9 +5941,12 @@ function loadModelsDevCache() {
5921
5941
  if (memoryCache && memoryCachePath === "bundled") return memoryCache;
5922
5942
  return rememberModelsDevCache("bundled", loadBundledModelsDevCache());
5923
5943
  }
5924
- async function fetchModelsDevCache() {
5944
+ async function fetchModelsDevCache(signal) {
5925
5945
  const controller = new AbortController();
5926
5946
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
5947
+ const onCallerAbort = () => controller.abort();
5948
+ if (signal?.aborted) controller.abort();
5949
+ else signal?.addEventListener("abort", onCallerAbort, { once: true });
5927
5950
  try {
5928
5951
  const response = await fetch(MODELS_DEV_API_URL, {
5929
5952
  signal: controller.signal,
@@ -5939,16 +5962,28 @@ async function fetchModelsDevCache() {
5939
5962
  return null;
5940
5963
  } finally {
5941
5964
  clearTimeout(timer);
5965
+ signal?.removeEventListener("abort", onCallerAbort);
5942
5966
  }
5943
5967
  }
5944
5968
  function resolveModelsDevSlug(providerId) {
5945
5969
  return REGISTRY_TO_MODELS_DEV[providerId] ?? providerId;
5946
5970
  }
5947
5971
  function refreshModelsDevCacheAsync(onComplete) {
5948
- void (async () => {
5949
- const updated = await fetchModelsDevCache() !== null;
5950
- onComplete?.(updated);
5951
- })();
5972
+ const controller = new AbortController();
5973
+ const start = setTimeout(() => {
5974
+ void (async () => {
5975
+ try {
5976
+ const updated = await fetchModelsDevCache(controller.signal) !== null;
5977
+ onComplete?.(updated);
5978
+ } finally {
5979
+ unregister();
5980
+ }
5981
+ })();
5982
+ }, MODELS_DEV_REFRESH_DELAY_MS);
5983
+ const unregister = cancelOnExit(() => {
5984
+ clearTimeout(start);
5985
+ controller.abort();
5986
+ });
5952
5987
  }
5953
5988
  function findModelsDevModel(providerId, modelId, cache = loadModelsDevCache()) {
5954
5989
  const slug = resolveModelsDevSlug(providerId);
@@ -6512,7 +6547,10 @@ function writeInferenceResponseLifecycleLog(path, entry) {
6512
6547
  ...entry.failureSource ? { failureSource: entry.failureSource } : {},
6513
6548
  ...entry.terminationSource ? { terminationSource: entry.terminationSource } : {},
6514
6549
  ...attempt !== void 0 ? { attempt } : {},
6515
- ...entry.reusedSocket !== void 0 ? { reusedSocket: entry.reusedSocket } : {}
6550
+ ...entry.reusedSocket !== void 0 ? { reusedSocket: entry.reusedSocket } : {},
6551
+ ...entry.outageHold !== void 0 ? { outageHold: entry.outageHold } : {},
6552
+ ...entry.holdElapsedMs !== void 0 ? { holdElapsedMs: entry.holdElapsedMs } : {},
6553
+ ...entry.retryDelayMs !== void 0 ? { retryDelayMs: entry.retryDelayMs } : {}
6516
6554
  }));
6517
6555
  }
6518
6556
  function writeProxyLifecycleLog(path, entry) {
@@ -8104,7 +8142,10 @@ import { AsyncLocalStorage } from "async_hooks";
8104
8142
 
8105
8143
  // src/outbound-proxy.ts
8106
8144
  import { randomUUID as randomUUID3 } from "crypto";
8145
+ import diagnosticsChannel from "diagnostics_channel";
8146
+ import { Socket } from "net";
8107
8147
  import { networkInterfaces } from "os";
8148
+ import { TLSSocket } from "tls";
8108
8149
  import { HttpsProxyAgent } from "https-proxy-agent";
8109
8150
  var OUTBOUND_PROXY_HOP_HEADER = "x-clodex-proxy-hop";
8110
8151
  var outboundProxyHopNonce = randomUUID3();
@@ -8177,12 +8218,34 @@ function proxyUrlTargetsListener(proxyUrl, listenerHost, listenerPort, localAddr
8177
8218
  return isWildcard(boundHost) && (isWildcard(proxyHost) || localAddresses.has(proxyHost));
8178
8219
  }
8179
8220
  var dispatcherInstalled = false;
8221
+ function abandonPendingConnectionsOnExit() {
8222
+ const pending = /* @__PURE__ */ new Set();
8223
+ const track = (message) => {
8224
+ const socket = message.socket;
8225
+ if (!(socket instanceof Socket)) return;
8226
+ pending.add(socket);
8227
+ const settled = () => pending.delete(socket);
8228
+ socket.once(socket instanceof TLSSocket ? "secureConnect" : "connect", settled);
8229
+ socket.once("close", settled);
8230
+ };
8231
+ diagnosticsChannel.subscribe("net.client.socket", track);
8232
+ cancelOnExit(() => {
8233
+ diagnosticsChannel.unsubscribe("net.client.socket", track);
8234
+ for (const socket of pending) {
8235
+ socket.on("error", () => {
8236
+ });
8237
+ socket.destroy(Object.assign(new Error("clodex is exiting"), { code: "ECONNABORTED" }));
8238
+ }
8239
+ pending.clear();
8240
+ });
8241
+ }
8180
8242
  async function installOutboundDispatcher() {
8181
8243
  if (dispatcherInstalled) return true;
8182
8244
  try {
8183
8245
  const { Agent: Agent3, EnvHttpProxyAgent, setGlobalDispatcher } = await import("undici");
8184
8246
  const dispatcher = hasOutboundProxyEnv() ? new EnvHttpProxyAgent({ allowH2: false }) : new Agent3({ allowH2: false });
8185
8247
  setGlobalDispatcher(dispatcher);
8248
+ abandonPendingConnectionsOnExit();
8186
8249
  dispatcherInstalled = true;
8187
8250
  return true;
8188
8251
  } catch (err) {
@@ -8699,6 +8762,34 @@ function passthroughUpstreamRetries(env = process.env, warn = defaultWarn) {
8699
8762
  }
8700
8763
  return DEFAULT_PASSTHROUGH_RETRIES;
8701
8764
  }
8765
+ var PASSTHROUGH_OUTAGE_HOLD_ENV = "CLODEX_PASSTHROUGH_OUTAGE_HOLD_MS";
8766
+ var DEFAULT_PASSTHROUGH_OUTAGE_HOLD_MS = 12e4;
8767
+ var MAX_PASSTHROUGH_OUTAGE_HOLD_MS = 57e4;
8768
+ function passthroughOutageHoldMs(env = process.env, warn = defaultWarn) {
8769
+ const raw = env[PASSTHROUGH_OUTAGE_HOLD_ENV]?.trim();
8770
+ if (raw === void 0 || raw === "") return DEFAULT_PASSTHROUGH_OUTAGE_HOLD_MS;
8771
+ const value = Number(raw);
8772
+ if (!Number.isInteger(value) || value < 0) {
8773
+ reportOnce(
8774
+ `${PASSTHROUGH_OUTAGE_HOLD_ENV}=${raw}`,
8775
+ `ignoring ${PASSTHROUGH_OUTAGE_HOLD_ENV}=${raw} (expected a non-negative integer number of milliseconds)`,
8776
+ warn
8777
+ );
8778
+ return DEFAULT_PASSTHROUGH_OUTAGE_HOLD_MS;
8779
+ }
8780
+ if (value > MAX_PASSTHROUGH_OUTAGE_HOLD_MS) {
8781
+ reportOnce(
8782
+ `${PASSTHROUGH_OUTAGE_HOLD_ENV}=${raw}`,
8783
+ `clamping ${PASSTHROUGH_OUTAGE_HOLD_ENV}=${raw} to ${MAX_PASSTHROUGH_OUTAGE_HOLD_MS}ms (the ceiling keeps the hold 29 s inside the 599 s Claude Code waits for response headers after a request has gone unanswered, at its default API_TIMEOUT_MS)`,
8784
+ warn
8785
+ );
8786
+ return MAX_PASSTHROUGH_OUTAGE_HOLD_MS;
8787
+ }
8788
+ return value;
8789
+ }
8790
+ function outageRetryDelayMs(retry) {
8791
+ return Math.min(15e3, 1e3 * 2 ** Math.max(0, retry - 1));
8792
+ }
8702
8793
 
8703
8794
  // src/oauth/ws-upgrade-pacer.ts
8704
8795
  var WS_NEW_CONNECTIONS_PER_MIN_ENV = "CLODEX_WS_MAX_NEW_CONNECTIONS_PER_MIN";
@@ -9782,12 +9873,17 @@ function deleteEntry(entry, closeSocket = true) {
9782
9873
  entry.inFlight = false;
9783
9874
  entry.current = void 0;
9784
9875
  unregisterEntry(entry);
9785
- if (closeSocket) {
9786
- try {
9787
- entry.socket.close();
9788
- } catch {
9789
- }
9876
+ if (closeSocket) closeGracefully(entry.socket);
9877
+ }
9878
+ var WS_CLOSING = 2;
9879
+ function closeGracefully(socket) {
9880
+ try {
9881
+ socket.close();
9882
+ } catch {
9883
+ return;
9790
9884
  }
9885
+ if (socket.readyState !== WS_CLOSING) return;
9886
+ socket.once("close", cancelOnExit(() => socket.terminate()));
9791
9887
  }
9792
9888
  function failContext(entry, ctx, message, diagnosticDetails, statusCode, retryAfter) {
9793
9889
  if (ctx.closed || entry.current !== ctx) return;
@@ -20069,6 +20165,19 @@ var MAX_BODY_BYTES = 50 * 1024 * 1024;
20069
20165
  var MAX_ERROR_BODY_BYTES = 64 * 1024;
20070
20166
  var MAX_USAGE_SSE_BLOCK_BYTES = 64 * 1024;
20071
20167
  var RETRYABLE_PASSTHROUGH_CODE = "ECONNRESET";
20168
+ var OUTAGE_HOLD_CODES = /* @__PURE__ */ new Set([
20169
+ "ENOTFOUND",
20170
+ "EAI_AGAIN",
20171
+ "EAI_FAIL",
20172
+ "ECONNREFUSED",
20173
+ "ENETUNREACH",
20174
+ "EHOSTUNREACH",
20175
+ "ENETDOWN",
20176
+ "EHOSTDOWN",
20177
+ "EADDRNOTAVAIL",
20178
+ "ECONNRESET",
20179
+ "ETIMEDOUT"
20180
+ ]);
20072
20181
  function upstreamUnreachableDetail(err) {
20073
20182
  return err.message || err.code || err.name || "connection failed";
20074
20183
  }
@@ -20223,10 +20332,17 @@ function requestHeadersWithoutProxyHeaders(req) {
20223
20332
  }
20224
20333
  return headers;
20225
20334
  }
20226
- function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, agent, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false) {
20335
+ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, agent, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false, passthrough = {}) {
20227
20336
  return new Promise((resolve4) => {
20228
20337
  const startedAt = Date.now();
20229
20338
  const retryBudget = passthroughUpstreamRetries();
20339
+ const outageHoldMs = passthrough.holdOutages ? passthroughOutageHoldMs() : 0;
20340
+ const holdDeadline = startedAt + outageHoldMs;
20341
+ let lastHoldError;
20342
+ let outageRetries = 0;
20343
+ let reuseRetries = 0;
20344
+ let holdTimer;
20345
+ let attemptDeadlineTimer;
20230
20346
  let lastActivityAt = startedAt;
20231
20347
  let headersReceived = false;
20232
20348
  let firstByteAt;
@@ -20278,8 +20394,49 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
20278
20394
  const errorType = (err) => err.code ?? err.name;
20279
20395
  let upstream;
20280
20396
  let attempt = 0;
20281
- const isRetryableUpstreamFailure = (err, request3) => attempt <= retryBudget && !headersReceived && !failed && !clientDisconnected2 && !isLocalShutdown() && request3.reusedSocket === true && err.code === RETRYABLE_PASSTHROUGH_CODE;
20282
- const sendAttempt = () => {
20397
+ const isRetryableUpstreamFailure = (err, request3) => reuseRetries < retryBudget && !headersReceived && !failed && !clientDisconnected2 && !isLocalShutdown() && request3.reusedSocket === true && err.code === RETRYABLE_PASSTHROUGH_CODE;
20398
+ const isOutageHoldFailure = (err, request3, tlsEstablished) => outageHoldMs > 0 && !headersReceived && !failed && !clientDisconnected2 && !isLocalShutdown() && request3.reusedSocket !== true && !tlsEstablished && OUTAGE_HOLD_CODES.has(err.code ?? "");
20399
+ const clearAttemptDeadline = () => {
20400
+ if (attemptDeadlineTimer) clearTimeout(attemptDeadlineTimer);
20401
+ attemptDeadlineTimer = void 0;
20402
+ };
20403
+ const answer502 = (detail, failure) => {
20404
+ failed = true;
20405
+ stopProgress();
20406
+ clearAttemptDeadline();
20407
+ const now = Date.now();
20408
+ writeLifecycle("response_failed", {
20409
+ statusCode: 502,
20410
+ phase: responsePhase(),
20411
+ durationMs: now - startedAt,
20412
+ idleMs: now - lastActivityAt,
20413
+ bytes,
20414
+ chunks,
20415
+ errorType: failure.errorType,
20416
+ terminationSource: isLocalShutdown() ? "local_shutdown" : "upstream_failure",
20417
+ attempt,
20418
+ reusedSocket: failure.reusedSocket,
20419
+ ...failure.outageHold ? { outageHold: true } : {}
20420
+ });
20421
+ onErrorResponse?.(502, `Anthropic upstream unreachable: ${detail}`);
20422
+ if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
20423
+ res.end(`Anthropic upstream unreachable: ${detail}`);
20424
+ done();
20425
+ };
20426
+ const holdExpiredDetail = (pendingHandshake) => {
20427
+ const parts = [`outage hold of ${outageHoldMs} ms expired`];
20428
+ if (pendingHandshake) parts[0] += " before the TLS handshake completed";
20429
+ if (lastHoldError) parts.push(`last upstream error: ${upstreamUnreachableDetail(lastHoldError)}`);
20430
+ return parts.join("; ");
20431
+ };
20432
+ const lateReplayDetail = () => {
20433
+ const parts = [
20434
+ `reused connection was reset and the outage hold of ${outageHoldMs} ms had expired before the request could be resent, so it was not resent on a new connection`
20435
+ ];
20436
+ if (lastHoldError) parts.push(`last upstream error: ${upstreamUnreachableDetail(lastHoldError)}`);
20437
+ return parts.join("; ");
20438
+ };
20439
+ const sendAttempt = (replayOfReset = false) => {
20283
20440
  attempt += 1;
20284
20441
  const request3 = https.request({
20285
20442
  protocol: "https:",
@@ -20290,8 +20447,10 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
20290
20447
  headers: requestHeadersWithoutProxyHeaders(req),
20291
20448
  servername: net.isIP(origin.hostname) ? void 0 : origin.hostname,
20292
20449
  rejectUnauthorized,
20293
- agent
20450
+ agent,
20451
+ ...passthrough.lookup ? { lookup: passthrough.lookup } : {}
20294
20452
  }, (upstreamRes) => {
20453
+ clearAttemptDeadline();
20295
20454
  headersReceived = true;
20296
20455
  statusCode = upstreamRes.statusCode ?? 502;
20297
20456
  lastActivityAt = Date.now();
@@ -20340,12 +20499,95 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
20340
20499
  });
20341
20500
  });
20342
20501
  upstream = request3;
20502
+ let tlsEstablished = false;
20503
+ const cutPendingAttempt = (lateReplay) => {
20504
+ if (tlsEstablished || headersReceived || failed || clientDisconnected2) return;
20505
+ answer502(lateReplay ? lateReplayDetail() : holdExpiredDetail(true), {
20506
+ errorType: "outage_hold_expired",
20507
+ reusedSocket: false,
20508
+ outageHold: true
20509
+ });
20510
+ request3.destroy();
20511
+ };
20512
+ const attemptRemaining = holdDeadline - Date.now();
20513
+ const startedAfterDeadline = outageHoldMs > 0 && attemptRemaining <= 0;
20514
+ if (outageHoldMs > 0 && attemptRemaining > 0) {
20515
+ attemptDeadlineTimer = setTimeout(() => {
20516
+ attemptDeadlineTimer = void 0;
20517
+ cutPendingAttempt(false);
20518
+ }, attemptRemaining);
20519
+ }
20520
+ request3.once("socket", (socket) => {
20521
+ if (request3.reusedSocket) {
20522
+ clearAttemptDeadline();
20523
+ return;
20524
+ }
20525
+ if (startedAfterDeadline) {
20526
+ cutPendingAttempt(replayOfReset);
20527
+ return;
20528
+ }
20529
+ socket.once("secureConnect", () => {
20530
+ tlsEstablished = true;
20531
+ clearAttemptDeadline();
20532
+ });
20533
+ });
20343
20534
  request3.once("error", (err) => {
20535
+ clearAttemptDeadline();
20344
20536
  if (clientDisconnected2) {
20345
20537
  done();
20346
20538
  return;
20347
20539
  }
20540
+ if (isOutageHoldFailure(err, request3, tlsEstablished)) {
20541
+ lastHoldError = err;
20542
+ const now = Date.now();
20543
+ const remaining = holdDeadline - now;
20544
+ if (remaining <= 0) {
20545
+ answer502(holdExpiredDetail(false), {
20546
+ errorType: errorType(err),
20547
+ reusedSocket: false,
20548
+ outageHold: true
20549
+ });
20550
+ return;
20551
+ }
20552
+ outageRetries += 1;
20553
+ const backoff = outageRetryDelayMs(outageRetries);
20554
+ const holdDelay = Math.min(backoff, remaining);
20555
+ const waitReachesDeadline = backoff >= remaining;
20556
+ writeLifecycle("response_retried", {
20557
+ phase: responsePhase(),
20558
+ durationMs: now - startedAt,
20559
+ idleMs: now - lastActivityAt,
20560
+ errorType: errorType(err),
20561
+ terminationSource: "upstream_failure",
20562
+ attempt,
20563
+ reusedSocket: false,
20564
+ outageHold: true,
20565
+ holdElapsedMs: now - startedAt,
20566
+ retryDelayMs: holdDelay
20567
+ });
20568
+ lastActivityAt = now;
20569
+ holdTimer = setTimeout(() => {
20570
+ holdTimer = void 0;
20571
+ if (clientDisconnected2 || failed || isLocalShutdown()) {
20572
+ if (isLocalShutdown()) res.destroy();
20573
+ done();
20574
+ return;
20575
+ }
20576
+ if (waitReachesDeadline || Date.now() >= holdDeadline) {
20577
+ answer502(holdExpiredDetail(false), {
20578
+ errorType: errorType(err),
20579
+ reusedSocket: false,
20580
+ outageHold: true
20581
+ });
20582
+ return;
20583
+ }
20584
+ lastActivityAt = Date.now();
20585
+ sendAttempt();
20586
+ }, holdDelay);
20587
+ return;
20588
+ }
20348
20589
  if (isRetryableUpstreamFailure(err, request3)) {
20590
+ reuseRetries += 1;
20349
20591
  const retriedAt = Date.now();
20350
20592
  writeLifecycle("response_retried", {
20351
20593
  phase: responsePhase(),
@@ -20357,33 +20599,17 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
20357
20599
  reusedSocket: true
20358
20600
  });
20359
20601
  lastActivityAt = retriedAt;
20360
- sendAttempt();
20602
+ sendAttempt(true);
20361
20603
  return;
20362
20604
  }
20363
20605
  if (failed) {
20364
20606
  done();
20365
20607
  return;
20366
20608
  }
20367
- failed = true;
20368
- stopProgress();
20369
- const now = Date.now();
20370
- writeLifecycle("response_failed", {
20371
- statusCode: 502,
20372
- phase: responsePhase(),
20373
- durationMs: now - startedAt,
20374
- idleMs: now - lastActivityAt,
20375
- bytes,
20376
- chunks,
20609
+ answer502(upstreamUnreachableDetail(err), {
20377
20610
  errorType: errorType(err),
20378
- terminationSource: isLocalShutdown() ? "local_shutdown" : "upstream_failure",
20379
- attempt,
20380
20611
  reusedSocket: request3.reusedSocket === true
20381
20612
  });
20382
- const detail = upstreamUnreachableDetail(err);
20383
- onErrorResponse?.(502, `Anthropic upstream unreachable: ${detail}`);
20384
- if (!res.headersSent) res.writeHead(502, { "Content-Type": "text/plain" });
20385
- res.end(`Anthropic upstream unreachable: ${detail}`);
20386
- done();
20387
20613
  });
20388
20614
  request3.end(rawBody);
20389
20615
  };
@@ -20404,6 +20630,11 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
20404
20630
  stopProgress();
20405
20631
  if (res.writableFinished || failed) return;
20406
20632
  clientDisconnected2 = true;
20633
+ if (holdTimer) {
20634
+ clearTimeout(holdTimer);
20635
+ holdTimer = void 0;
20636
+ }
20637
+ clearAttemptDeadline();
20407
20638
  const now = Date.now();
20408
20639
  writeLifecycle("response_client_disconnected", {
20409
20640
  statusCode,
@@ -20926,7 +21157,14 @@ async function startHttpProxy(options) {
20926
21157
  provider: "anthropic",
20927
21158
  progressIntervalMs: options.responseProgressIntervalMs ?? INFERENCE_PROGRESS_INTERVAL_MS
20928
21159
  } : void 0,
20929
- () => shuttingDown
21160
+ () => shuttingDown,
21161
+ // Only messages generation is held. count_tokens, OAuth, bootstrap and
21162
+ // telemetry calls carry no lifecycle log and short client timeouts of
21163
+ // their own, so holding them would only slow startup without a trace.
21164
+ {
21165
+ holdOutages: messagesEndpoint === "messages",
21166
+ lookup: options.anthropicLookup
21167
+ }
20930
21168
  );
20931
21169
  return;
20932
21170
  }
@@ -23742,14 +23980,11 @@ function isCliEntryPoint() {
23742
23980
  }
23743
23981
  }
23744
23982
  if (isCliEntryPoint()) {
23745
- main().then((exitCode) => {
23746
- process.exit(exitCode);
23747
- }).catch((err) => {
23748
- if (err === /* @__PURE__ */ Symbol.for("clack:cancel")) {
23749
- process.exit(0);
23750
- }
23751
- console.error(pc13.red("\nUnexpected error:"), err);
23752
- process.exit(1);
23983
+ const exitOptions = { trace: Boolean(parseArgs(process.argv.slice(2)).trace) };
23984
+ main().then((exitCode) => exitAfterDrain(exitCode, exitOptions), (err) => {
23985
+ const cancelled = err === /* @__PURE__ */ Symbol.for("clack:cancel");
23986
+ if (!cancelled) console.error(pc13.red("\nUnexpected error:"), err);
23987
+ exitAfterDrain(cancelled ? 0 : 1, exitOptions);
23753
23988
  });
23754
23989
  }
23755
23990
  export {