@bman654/clodex 2.11.6 → 2.12.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 +3 -0
- package/dist/cli.js +74 -23
- package/dist/cli.js.map +1 -1
- package/docs/windows-setup.md +300 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -106,6 +106,9 @@ flowchart LR
|
|
|
106
106
|
> [!TIP]
|
|
107
107
|
> Using Claude Code's agents view or background agents? Ask your Claude Code agent to read [docs/background-agents.md](docs/background-agents.md) and set it up for you — one global `clodex server --proxy` plus the `clodex-claude` wrapper bin bridges every claude process automatically.
|
|
108
108
|
|
|
109
|
+
> [!TIP]
|
|
110
|
+
> On Windows with the Claude Code VS Code extension? See [docs/windows-setup.md](docs/windows-setup.md) — proxy-mode env vars route the extension through clodex, and an optional process wrapper makes clodex models appear in its model picker.
|
|
111
|
+
|
|
109
112
|
## CLI reference
|
|
110
113
|
|
|
111
114
|
### `clodex claude [options] [claude-flags]`
|
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.
|
|
385
|
+
version: "2.12.0",
|
|
386
386
|
publishConfig: {
|
|
387
387
|
access: "public"
|
|
388
388
|
},
|
|
@@ -8717,8 +8717,8 @@ var FAILURE_EVENT_TYPES = /* @__PURE__ */ new Set(["error", "response.failed", "
|
|
|
8717
8717
|
var RESPONSES_WS_HARD_TTL_MS = 55 * 6e4;
|
|
8718
8718
|
var RESPONSES_WS_IDLE_TTL_MS = 30 * 6e4;
|
|
8719
8719
|
var RESPONSES_WS_NURSERY_IDLE_TTL_MS = 5 * 6e4;
|
|
8720
|
-
var RESPONSES_WS_MAX_CONNECTIONS =
|
|
8721
|
-
var RESPONSES_WS_MAX_NURSERY_CONNECTIONS =
|
|
8720
|
+
var RESPONSES_WS_MAX_CONNECTIONS = 64;
|
|
8721
|
+
var RESPONSES_WS_MAX_NURSERY_CONNECTIONS = 48;
|
|
8722
8722
|
var diagnosticContext = new AsyncLocalStorage();
|
|
8723
8723
|
function withResponsesWebSocketDiagnosticContext(context, fn) {
|
|
8724
8724
|
return diagnosticContext.run(context, fn);
|
|
@@ -8847,7 +8847,7 @@ function instructionChangeSummary(previous, current) {
|
|
|
8847
8847
|
const firstDiffLine = previous.slice(0, prefix).split("\n").length;
|
|
8848
8848
|
return `instructions changed: previous_chars=${previous.length} current_chars=${current.length} common_prefix_chars=${prefix} common_suffix_chars=${suffix} first_diff_line=${firstDiffLine}`;
|
|
8849
8849
|
}
|
|
8850
|
-
function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizationFingerprint = "") {
|
|
8850
|
+
function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizationFingerprint = "", claudeAgentId = "") {
|
|
8851
8851
|
const promptCacheKey = payload.prompt_cache_key;
|
|
8852
8852
|
const model = payload.model;
|
|
8853
8853
|
if (typeof promptCacheKey !== "string" || !promptCacheKey || typeof model !== "string" || !model) return void 0;
|
|
@@ -8860,7 +8860,8 @@ function responsesWebSocketPartitionKey(wsUrl, payload, options = {}, authorizat
|
|
|
8860
8860
|
model,
|
|
8861
8861
|
effort,
|
|
8862
8862
|
promptCacheKey,
|
|
8863
|
-
authorizationFingerprint
|
|
8863
|
+
authorizationFingerprint,
|
|
8864
|
+
claudeAgentId
|
|
8864
8865
|
].join("");
|
|
8865
8866
|
return createHash6("sha256").update(material).digest("hex");
|
|
8866
8867
|
}
|
|
@@ -9100,13 +9101,16 @@ function mismatchDumpLine(items, index) {
|
|
|
9100
9101
|
function canonicalItemStrings(items) {
|
|
9101
9102
|
return items.map((item) => canonicalJson(normalizeToolCallJson([item])));
|
|
9102
9103
|
}
|
|
9103
|
-
function
|
|
9104
|
-
if (client.length
|
|
9104
|
+
function isPrefixOrEqual(head, client) {
|
|
9105
|
+
if (client.length < head.length) return false;
|
|
9105
9106
|
for (let index = 0; index < head.length; index += 1) {
|
|
9106
9107
|
if (head[index] !== client[index]) return false;
|
|
9107
9108
|
}
|
|
9108
9109
|
return true;
|
|
9109
9110
|
}
|
|
9111
|
+
function isStrictPrefix(head, client) {
|
|
9112
|
+
return client.length > head.length && isPrefixOrEqual(head, client);
|
|
9113
|
+
}
|
|
9110
9114
|
function continuationMatch(entry, payload, clientItems) {
|
|
9111
9115
|
if (!entry.responseId || !entry.requestInput || !entry.expectedAssistant) return void 0;
|
|
9112
9116
|
const full = inputArray(payload);
|
|
@@ -9599,11 +9603,16 @@ function evictOldestIdleGeneration(generation, maxConnections, reason) {
|
|
|
9599
9603
|
while (connectionCountByGeneration(generation) >= maxConnections && idle.length) {
|
|
9600
9604
|
const oldest = idle.shift();
|
|
9601
9605
|
if (oldest) {
|
|
9606
|
+
const idleMs = Math.max(0, oldest.options.now() - oldest.lastUsedAt);
|
|
9607
|
+
oldest.debug(
|
|
9608
|
+
`evicting the oldest idle ${generation} connection to stay within its cap: connection=${oldest.debugId} idle_ms=${idleMs} cap=${maxConnections} reason=${reason}`
|
|
9609
|
+
);
|
|
9602
9610
|
evictions.push({
|
|
9603
9611
|
connectionId: oldest.debugId,
|
|
9604
9612
|
partitionKey: oldest.key,
|
|
9605
9613
|
generation: oldest.generation,
|
|
9606
|
-
reason
|
|
9614
|
+
reason,
|
|
9615
|
+
idleMs
|
|
9607
9616
|
});
|
|
9608
9617
|
deleteEntry(oldest);
|
|
9609
9618
|
}
|
|
@@ -10011,28 +10020,42 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10011
10020
|
}
|
|
10012
10021
|
if (hasResponsesLiteHeader(headers)) payload = applyResponsesLiteShape(payload);
|
|
10013
10022
|
const authorizationFingerprint = authorizationHeaderFingerprint(headers);
|
|
10023
|
+
const diagnosticCorrelation = diagnosticContext.getStore();
|
|
10024
|
+
const claudeAgentId = diagnosticCorrelation?.claudeAgentId ?? "";
|
|
10014
10025
|
const partitionKey = responsesWebSocketPartitionKey(
|
|
10015
10026
|
wsUrl,
|
|
10016
10027
|
payload,
|
|
10017
10028
|
options,
|
|
10018
|
-
authorizationFingerprint
|
|
10029
|
+
authorizationFingerprint,
|
|
10030
|
+
claudeAgentId
|
|
10019
10031
|
);
|
|
10020
10032
|
const promptFingerprint = responsesWebSocketPromptFingerprint(payload);
|
|
10021
10033
|
const promptFieldHashes = responsesWebSocketPromptFieldHashes(payload);
|
|
10022
10034
|
const instructionsSnapshot = instructionsFromPayload(payload);
|
|
10023
|
-
const diagnosticCorrelation = diagnosticContext.getStore();
|
|
10024
10035
|
let now = resolvedOptions.now();
|
|
10025
10036
|
const evictions = cleanupExpiredConnections(now);
|
|
10037
|
+
let canonicalClientItems;
|
|
10038
|
+
const clientItems = () => canonicalClientItems ??= canonicalItemStrings(inputArray(payload));
|
|
10026
10039
|
const scanForHeads = () => {
|
|
10027
10040
|
const scanned = partitionKey ? connectionEntries(partitionKey) : [];
|
|
10028
10041
|
const idle = scanned.filter((entry) => !entry.inFlight);
|
|
10029
|
-
const
|
|
10042
|
+
const canonical = idle.length ? clientItems() : [];
|
|
10030
10043
|
return {
|
|
10031
10044
|
candidates: scanned,
|
|
10032
10045
|
idleCandidates: idle,
|
|
10033
|
-
matches: idle.map((entry) => ({ entry, match: continuationMatch(entry, payload,
|
|
10046
|
+
matches: idle.map((entry) => ({ entry, match: continuationMatch(entry, payload, canonical) })).filter((candidate) => candidate.match !== void 0).sort((left, right) => left.match.delta.length - right.match.delta.length || (left.match.mode === right.match.mode ? 0 : left.match.mode === "exact" ? -1 : 1))
|
|
10034
10047
|
};
|
|
10035
10048
|
};
|
|
10049
|
+
const couldPrecedeThisRequest = (entry) => {
|
|
10050
|
+
if (entry.responseId && entry.requestInput && entry.expectedAssistant) {
|
|
10051
|
+
return continuationMatch(entry, payload, clientItems()) !== void 0;
|
|
10052
|
+
}
|
|
10053
|
+
const streaming = entry.current;
|
|
10054
|
+
if (!streaming) return true;
|
|
10055
|
+
streaming.canonicalInput ??= canonicalItemStrings(inputArray(streaming.originalPayload));
|
|
10056
|
+
return isPrefixOrEqual(streaming.canonicalInput, clientItems());
|
|
10057
|
+
};
|
|
10058
|
+
const blockingHead = (entries) => entries.find((entry) => entry.inFlight && couldPrecedeThisRequest(entry));
|
|
10036
10059
|
let { candidates, idleCandidates, matches } = scanForHeads();
|
|
10037
10060
|
let selected = matches[0]?.entry;
|
|
10038
10061
|
let selectedMatch = matches[0]?.match;
|
|
@@ -10075,9 +10098,10 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10075
10098
|
);
|
|
10076
10099
|
return "continuation";
|
|
10077
10100
|
};
|
|
10101
|
+
let arrivalBlockingHead = selected ? void 0 : blockingHead(candidates);
|
|
10078
10102
|
if (selected && selectedDelta) {
|
|
10079
10103
|
decision = continueOnHead(selected, selectedMatch);
|
|
10080
|
-
} else if (
|
|
10104
|
+
} else if (arrivalBlockingHead) {
|
|
10081
10105
|
selected = void 0;
|
|
10082
10106
|
persistent = false;
|
|
10083
10107
|
decision = "parallel_isolated";
|
|
@@ -10176,11 +10200,15 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10176
10200
|
pacingRescanOutcome = "no_change";
|
|
10177
10201
|
}
|
|
10178
10202
|
}
|
|
10179
|
-
if (!selected && persistent && partitionKey
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10203
|
+
if (!selected && persistent && partitionKey) {
|
|
10204
|
+
const blocked = blockingHead(connectionEntries(partitionKey));
|
|
10205
|
+
if (blocked) {
|
|
10206
|
+
arrivalBlockingHead = blocked;
|
|
10207
|
+
persistent = false;
|
|
10208
|
+
decision = "parallel_isolated";
|
|
10209
|
+
debug("parallel request using an isolated socket after pacing");
|
|
10210
|
+
if (pacingRescanOutcome === "no_change") pacingRescanOutcome = "parallel_isolated";
|
|
10211
|
+
}
|
|
10184
10212
|
}
|
|
10185
10213
|
}
|
|
10186
10214
|
let suppressedMismatchWarnings;
|
|
@@ -10211,7 +10239,9 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10211
10239
|
accountIdHash: options.accountId ? createHash6("sha256").update(options.accountId).digest("hex").slice(0, 16) : "",
|
|
10212
10240
|
model: typeof payload.model === "string" ? payload.model : void 0,
|
|
10213
10241
|
effort: typeof payload.reasoning?.effort === "string" ? String(payload.reasoning.effort).trim().toLowerCase() : "",
|
|
10214
|
-
promptCacheKey: typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : void 0
|
|
10242
|
+
promptCacheKey: typeof payload.prompt_cache_key === "string" ? payload.prompt_cache_key : void 0,
|
|
10243
|
+
claudeAgentId: claudeAgentId || void 0,
|
|
10244
|
+
claudeParentAgentId: diagnosticCorrelation?.claudeParentAgentId
|
|
10215
10245
|
},
|
|
10216
10246
|
promptFingerprint,
|
|
10217
10247
|
promptFieldHashes,
|
|
@@ -10236,6 +10266,10 @@ function createResponsesWebSocketFetch(wsUrl, log12, options = {}) {
|
|
|
10236
10266
|
createdConnectionId: selected ? void 0 : nextConnectionDebugId,
|
|
10237
10267
|
...pacingWaitedMs !== void 0 ? { pacingWaitedMs } : {},
|
|
10238
10268
|
...pacingRescanOutcome !== void 0 ? { pacingRescanOutcome } : {},
|
|
10269
|
+
// Which busy head this request could not be told apart from. Without it an
|
|
10270
|
+
// isolated turn reads as unexplained, and isolation is the single largest
|
|
10271
|
+
// source of uncached prompt tokens on this transport.
|
|
10272
|
+
...decision === "parallel_isolated" && arrivalBlockingHead ? { isolatedByConnectionId: arrivalBlockingHead.debugId } : {},
|
|
10239
10273
|
...suppressedMismatchWarnings !== void 0 ? { suppressedMismatchWarnings } : {},
|
|
10240
10274
|
createdGeneration: selected ? void 0 : persistent ? "nursery" : "isolated",
|
|
10241
10275
|
incrementalInputItems: selectedDelta?.length,
|
|
@@ -12105,6 +12139,18 @@ function extractClaudeSessionId(body, headerFallback) {
|
|
|
12105
12139
|
}
|
|
12106
12140
|
return validClaudeSessionId(headerFallback);
|
|
12107
12141
|
}
|
|
12142
|
+
var CLAUDE_AGENT_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
|
|
12143
|
+
function extractClaudeAgentIds(headers) {
|
|
12144
|
+
const read = (name) => {
|
|
12145
|
+
const raw = headers[name];
|
|
12146
|
+
const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
|
|
12147
|
+
return value && CLAUDE_AGENT_ID_RE.test(value) ? value : void 0;
|
|
12148
|
+
};
|
|
12149
|
+
return {
|
|
12150
|
+
claudeAgentId: read("x-claude-code-agent-id"),
|
|
12151
|
+
claudeParentAgentId: read("x-claude-code-parent-agent-id")
|
|
12152
|
+
};
|
|
12153
|
+
}
|
|
12108
12154
|
function claudeSessionPromptCacheKey(sessionId) {
|
|
12109
12155
|
return "relay-session-" + createHash8("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
12110
12156
|
}
|
|
@@ -13316,6 +13362,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13316
13362
|
const openAiOAuth = isOpenAiOAuthRoute(route);
|
|
13317
13363
|
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"];
|
|
13318
13364
|
const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
|
|
13365
|
+
const claudeAgentIds = extractClaudeAgentIds(req.headers);
|
|
13319
13366
|
const translationLifecycle = createTranslationLifecycle(
|
|
13320
13367
|
inferenceLogPath,
|
|
13321
13368
|
relayRequestId,
|
|
@@ -13389,7 +13436,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13389
13436
|
keepAlive.unref();
|
|
13390
13437
|
try {
|
|
13391
13438
|
await withResponsesWebSocketDiagnosticContext(
|
|
13392
|
-
{ requestId: relayRequestId, claudeSessionId },
|
|
13439
|
+
{ requestId: relayRequestId, claudeSessionId, ...claudeAgentIds },
|
|
13393
13440
|
() => streamAnthropicResponse(
|
|
13394
13441
|
model,
|
|
13395
13442
|
params,
|
|
@@ -13420,7 +13467,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
13420
13467
|
res.end();
|
|
13421
13468
|
} else {
|
|
13422
13469
|
const anthropicResponse = await withResponsesWebSocketDiagnosticContext(
|
|
13423
|
-
{ requestId: relayRequestId, claudeSessionId },
|
|
13470
|
+
{ requestId: relayRequestId, claudeSessionId, ...claudeAgentIds },
|
|
13424
13471
|
() => generateAnthropicResponse(
|
|
13425
13472
|
model,
|
|
13426
13473
|
params,
|
|
@@ -18189,6 +18236,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
18189
18236
|
const requestId = randomUUID5();
|
|
18190
18237
|
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"];
|
|
18191
18238
|
const claudeSessionId = extractClaudeSessionId(body, claudeSessionIdHeader);
|
|
18239
|
+
const claudeAgentIds = extractClaudeAgentIds(req.headers);
|
|
18192
18240
|
if (options.webSocketDiagnosticsLogPath) {
|
|
18193
18241
|
writeWebSocketDiagnosticRequestLog(options.webSocketDiagnosticsLogPath, {
|
|
18194
18242
|
requestId,
|
|
@@ -18353,7 +18401,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
18353
18401
|
res.write(chunk);
|
|
18354
18402
|
};
|
|
18355
18403
|
await withResponsesWebSocketDiagnosticContext(
|
|
18356
|
-
{ requestId, claudeSessionId },
|
|
18404
|
+
{ requestId, claudeSessionId, ...claudeAgentIds },
|
|
18357
18405
|
() => streamAnthropicResponse(languageModel, params, responseModelId, writeStreamChunk, void 0, {
|
|
18358
18406
|
abortSignal: clientAbort.signal,
|
|
18359
18407
|
initialInputTokens: estimateAnthropicInputTokens(body),
|
|
@@ -18369,7 +18417,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
18369
18417
|
res.end();
|
|
18370
18418
|
} else {
|
|
18371
18419
|
const anthropicResponse = await withResponsesWebSocketDiagnosticContext(
|
|
18372
|
-
{ requestId, claudeSessionId },
|
|
18420
|
+
{ requestId, claudeSessionId, ...claudeAgentIds },
|
|
18373
18421
|
() => generateAnthropicResponse(languageModel, params, responseModelId, {
|
|
18374
18422
|
forceStream: openAiOAuth,
|
|
18375
18423
|
abortSignal: clientAbort.signal,
|
|
@@ -19462,6 +19510,9 @@ function forwardToAdapter(req, res, rawBody, adapter, adapterRequest = http2.req
|
|
|
19462
19510
|
"Content-Length": String(rawBody.length),
|
|
19463
19511
|
"x-api-key": adapter.token,
|
|
19464
19512
|
...typeof req.headers["x-claude-code-session-id"] === "string" ? { "x-claude-code-session-id": req.headers["x-claude-code-session-id"] } : {},
|
|
19513
|
+
// Subagent identity: the relay partitions ChatGPT WebSocket heads by it.
|
|
19514
|
+
...typeof req.headers["x-claude-code-agent-id"] === "string" ? { "x-claude-code-agent-id": req.headers["x-claude-code-agent-id"] } : {},
|
|
19515
|
+
...typeof req.headers["x-claude-code-parent-agent-id"] === "string" ? { "x-claude-code-parent-agent-id": req.headers["x-claude-code-parent-agent-id"] } : {},
|
|
19465
19516
|
...lifecycle ? { "x-relay-request-id": lifecycle.requestId } : {}
|
|
19466
19517
|
}
|
|
19467
19518
|
}, (upstreamRes) => {
|