@bman654/clodex 2.18.9 → 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/README.md +69 -1
- package/dist/{chunk-VFYRDLDD.js → chunk-Q2YFYQQT.js} +76 -5
- package/dist/chunk-Q2YFYQQT.js.map +1 -0
- package/dist/claude-wrapper.js +17 -7
- package/dist/claude-wrapper.js.map +1 -1
- package/dist/cli.js +298 -63
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +4 -3
- package/package.json +1 -1
- package/dist/chunk-VFYRDLDD.js.map +0 -1
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-
|
|
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
|
|
327
|
-
child.kill(
|
|
328
|
+
const forwardSigint = () => {
|
|
329
|
+
child.kill("SIGINT");
|
|
328
330
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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(
|
|
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.
|
|
357
|
+
version: "2.19.0",
|
|
350
358
|
publishConfig: {
|
|
351
359
|
access: "public"
|
|
352
360
|
},
|
|
@@ -3439,7 +3447,7 @@ function savedStopsAfter(current, assignments) {
|
|
|
3439
3447
|
}
|
|
3440
3448
|
|
|
3441
3449
|
// src/patch-transforms.ts
|
|
3442
|
-
var PATCH_TRANSFORMS_VERSION =
|
|
3450
|
+
var PATCH_TRANSFORMS_VERSION = 15;
|
|
3443
3451
|
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
3444
3452
|
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3445
3453
|
function projectNativeEffort(effort) {
|
|
@@ -3842,14 +3850,14 @@ function applyClodexPatches(source, config) {
|
|
|
3842
3850
|
}
|
|
3843
3851
|
return -1;
|
|
3844
3852
|
};
|
|
3845
|
-
const passthroughSites = source.includes(marker) ? 1 : (source.match(/\)return process\.env;let [\w$]+=\{/g) ?? []).length;
|
|
3853
|
+
const passthroughSites = source.includes(marker) ? 1 : (source.match(/\)return process\.env;let (?:(?:[^;{}]|\{[^;{}]*\})*?,)??[\w$]+=\{/g) ?? []).length;
|
|
3846
3854
|
if (passthroughSites !== 1) {
|
|
3847
3855
|
log13("FAIL", patchName, "child env passthrough appears " + passthroughSites + " times (expected 1)");
|
|
3848
3856
|
fail("clodex patch: required patch failed: " + patchName);
|
|
3849
3857
|
}
|
|
3850
3858
|
applyOnce(
|
|
3851
3859
|
patchName,
|
|
3852
|
-
/(function [\w$]+\(\)\{)(let[ {[](?:[^;{}]|\{[^;{}]*\})*?(?:\{[^;{}]*)?(?:getAgentProxyEnv|[\w$]+\(process\.env\.CLAUDE_CODE_REMOTE\)\?)(?:(?!\}\s*function )[\s\S])*?\)return process\.env;let ([\w$]+)=\{(?:(?!\}\s*function )[\s\S])*?return \3)(\})/,
|
|
3860
|
+
/(function [\w$]+\(\)\{)(let[ {[](?:[^;{}]|\{[^;{}]*\})*?(?:\{[^;{}]*)?(?:getAgentProxyEnv|[\w$]+\(process\.env\.CLAUDE_CODE_REMOTE\)\?)(?:(?!\}\s*function )[\s\S])*?\)return process\.env;let (?:(?:[^;{}]|\{[^;{}]*\})*?,)??([\w$]+)=\{(?:(?!\}\s*function )[\s\S])*?return \3)(\})/,
|
|
3853
3861
|
(match, head, body, _copyVar, tail) => {
|
|
3854
3862
|
const at = js.indexOf(match);
|
|
3855
3863
|
const closingBrace = at < 0 ? -1 : blockEndIndex(js, at + head.length - 1);
|
|
@@ -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(() =>
|
|
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
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
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
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
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) =>
|
|
20282
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
23746
|
-
|
|
23747
|
-
|
|
23748
|
-
if (
|
|
23749
|
-
|
|
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 {
|