claude-code-infinite 1.0.35 → 1.0.37
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 +114 -10
- package/dist/body-shape.d.ts +49 -0
- package/dist/body-shape.d.ts.map +1 -0
- package/dist/body-shape.js +232 -0
- package/dist/body-shape.js.map +1 -0
- package/dist/cc-request.d.ts +15 -0
- package/dist/cc-request.d.ts.map +1 -1
- package/dist/cc-request.js +30 -0
- package/dist/cc-request.js.map +1 -1
- package/dist/claude-env.d.ts +24 -1
- package/dist/claude-env.d.ts.map +1 -1
- package/dist/claude-env.js +48 -7
- package/dist/claude-env.js.map +1 -1
- package/dist/cli-args.d.ts +1 -1
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +1 -1
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-main.js +113 -11
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/count-tokens.d.ts +15 -2
- package/dist/count-tokens.d.ts.map +1 -1
- package/dist/count-tokens.js +13 -4
- package/dist/count-tokens.js.map +1 -1
- package/dist/desktop-command.d.ts +38 -0
- package/dist/desktop-command.d.ts.map +1 -0
- package/dist/desktop-command.js +324 -0
- package/dist/desktop-command.js.map +1 -0
- package/dist/desktop-service.d.ts +41 -0
- package/dist/desktop-service.d.ts.map +1 -0
- package/dist/desktop-service.js +449 -0
- package/dist/desktop-service.js.map +1 -0
- package/dist/desktop-setup.d.ts +94 -0
- package/dist/desktop-setup.d.ts.map +1 -0
- package/dist/desktop-setup.js +288 -0
- package/dist/desktop-setup.js.map +1 -0
- package/dist/financial-redaction.d.ts +12 -0
- package/dist/financial-redaction.d.ts.map +1 -0
- package/dist/financial-redaction.js +509 -0
- package/dist/financial-redaction.js.map +1 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +4 -0
- package/dist/hooks.js.map +1 -1
- package/dist/https-proxy-eligibility.d.ts +31 -0
- package/dist/https-proxy-eligibility.d.ts.map +1 -0
- package/dist/https-proxy-eligibility.js +204 -0
- package/dist/https-proxy-eligibility.js.map +1 -0
- package/dist/https-proxy-mode.d.ts +59 -0
- package/dist/https-proxy-mode.d.ts.map +1 -0
- package/dist/https-proxy-mode.js +255 -0
- package/dist/https-proxy-mode.js.map +1 -0
- package/dist/https-proxy-session.d.ts +64 -0
- package/dist/https-proxy-session.d.ts.map +1 -0
- package/dist/https-proxy-session.js +165 -0
- package/dist/https-proxy-session.js.map +1 -0
- package/dist/image-tokens.d.ts +6 -1
- package/dist/image-tokens.d.ts.map +1 -1
- package/dist/image-tokens.js +75 -14
- package/dist/image-tokens.js.map +1 -1
- package/dist/lane-activity.d.ts +42 -0
- package/dist/lane-activity.d.ts.map +1 -0
- package/dist/lane-activity.js +79 -0
- package/dist/lane-activity.js.map +1 -0
- package/dist/memtree-mcp.d.ts +32 -1
- package/dist/memtree-mcp.d.ts.map +1 -1
- package/dist/memtree-mcp.js +73 -7
- package/dist/memtree-mcp.js.map +1 -1
- package/dist/memtree.d.ts +10 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +59 -12
- package/dist/memtree.js.map +1 -1
- package/dist/message-refs.d.ts +81 -0
- package/dist/message-refs.d.ts.map +1 -0
- package/dist/message-refs.js +378 -0
- package/dist/message-refs.js.map +1 -0
- package/dist/mitm-ca.d.ts +31 -0
- package/dist/mitm-ca.d.ts.map +1 -0
- package/dist/mitm-ca.js +236 -0
- package/dist/mitm-ca.js.map +1 -0
- package/dist/paused-session.d.ts.map +1 -1
- package/dist/paused-session.js +9 -2
- package/dist/paused-session.js.map +1 -1
- package/dist/proxy.d.ts +25 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +168 -22
- package/dist/proxy.js.map +1 -1
- package/dist/refusal-reason.d.ts +3 -2
- package/dist/refusal-reason.d.ts.map +1 -1
- package/dist/refusal-reason.js +4 -3
- package/dist/refusal-reason.js.map +1 -1
- package/dist/reqlog.d.ts +13 -3
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js +1 -1
- package/dist/reqlog.js.map +1 -1
- package/dist/request-sizing.d.ts +38 -2
- package/dist/request-sizing.d.ts.map +1 -1
- package/dist/request-sizing.js +72 -6
- package/dist/request-sizing.js.map +1 -1
- package/dist/secret-redaction.d.ts +25 -0
- package/dist/secret-redaction.d.ts.map +1 -0
- package/dist/secret-redaction.js +492 -0
- package/dist/secret-redaction.js.map +1 -0
- package/dist/statusline-auto.d.ts +15 -0
- package/dist/statusline-auto.d.ts.map +1 -0
- package/dist/statusline-auto.js +125 -0
- package/dist/statusline-auto.js.map +1 -0
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +3 -2
- package/dist/statusline.js.map +1 -1
- package/dist/utf8-bytes.d.ts +10 -0
- package/dist/utf8-bytes.d.ts.map +1 -0
- package/dist/utf8-bytes.js +107 -0
- package/dist/utf8-bytes.js.map +1 -0
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { UNKNOWN_INDEX_WAIT_SECONDS } from "./index-wait-413.js";
|
|
42
42
|
import { capCacheBreakpoints } from "./route-cache.js";
|
|
43
|
+
import { attachHttpsProxyMode, } from "./https-proxy-mode.js";
|
|
43
44
|
import { PromptAccounting } from "./prompt-accounting.js";
|
|
44
45
|
import { INDEX_WAIT_RETRY_AFTER_SECONDS, RefusalStreaks, RouteFallbackFailures, pausedRefusal, unreachableRefusal } from "./route-fallback.js";
|
|
45
46
|
import { refusalCause, refusalMessage } from "./refusal-reason.js";
|
|
47
|
+
import { AGENT_RESULT_WAITING_LINE, LaneActivity, heavyRequestNoticeText } from "./lane-activity.js";
|
|
46
48
|
import { SlowUploads } from "./slow-upload.js";
|
|
47
49
|
import { freshFailure, networkFailureOf, unreachableMessage, } from "./network-failure.js";
|
|
48
50
|
import { createCountTokensSession, CountTokensCooldowns, plausibleSample, } from "./count-tokens.js";
|
|
@@ -67,12 +69,13 @@ import { keyInvalidNoticeText, keyInvalidRefusalMessage } from "./key-status.js"
|
|
|
67
69
|
import { KEY_INVALID_KIND, ModelContextNotices, PAUSED_KIND, keyInvalidModelContext, lastToldKindInFile, pausedModelContext } from "./model-context.js";
|
|
68
70
|
import { PaymentNoticeThrottle, pausedCompactionText, pausedRefusalMessage, paymentNoticeText, subscribeUrlFromDetail } from "./payment-notice.js";
|
|
69
71
|
import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
|
|
70
|
-
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
|
|
72
|
+
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, notSessionConversation, sessionTag, } from "./cc-request.js";
|
|
71
73
|
import { isResumableSessionId } from "./resume-hint.js";
|
|
72
74
|
import { isSubagentCall, postToolUseOutput, SUBAGENT_RESULT_HINT, subagentMemoryHintText, subagentStartOutput, } from "./subagent-hint.js";
|
|
73
75
|
import { PROJECT_TREES_HOOK_SUFFIX, RESUME_GUIDANCE_HOOK_SUFFIX, SUBAGENT_RESULT_HOOK_SUFFIX, } from "./hooks.js";
|
|
74
76
|
import { conservativeBodyTokens } from "./token-estimate.js";
|
|
75
77
|
import { bodyImages } from "./image-tokens.js";
|
|
78
|
+
import { bodyShape, prefixDigest } from "./body-shape.js";
|
|
76
79
|
import { claudeAuthFromHeaders, upstreamErrorCode } from "./claude-auth.js";
|
|
77
80
|
import { rateLimitFromResponse } from "./rate-limit.js";
|
|
78
81
|
import { approxTokensFromBytes, mergeUsageFromJsonBody, mergeUsageFromSseEvent, } from "./reqlog.js";
|
|
@@ -382,6 +385,7 @@ export function startProxy(opts) {
|
|
|
382
385
|
fallbackFailures: new RouteFallbackFailures(undefined, opts.indexWaitDeadlineMs, opts.indexWaitMaxMs),
|
|
383
386
|
refusalStreaks: new RefusalStreaks(),
|
|
384
387
|
activeSubagents: new Set(),
|
|
388
|
+
laneActivity: new LaneActivity(),
|
|
385
389
|
deferredAgentIndexes: new Map(),
|
|
386
390
|
adoptingPrepared: new Map(),
|
|
387
391
|
memoryRoutes: new Map(),
|
|
@@ -424,10 +428,12 @@ export function startProxy(opts) {
|
|
|
424
428
|
// A newer tree from the background lookup reaches the status line without a turn ending.
|
|
425
429
|
opts.newestTrees?.onTree((sessionId) => publishStatusLink(state, sessionId));
|
|
426
430
|
state.notices.setFirstTreeIntro(opts.firstTreeIntro ?? null);
|
|
427
|
-
|
|
431
|
+
// One handler for the loopback server and, in HTTPS-proxy mode, for the decrypted
|
|
432
|
+
// api.anthropic.com streams (`intercepted`): both are tracked, drained and cancelled alike.
|
|
433
|
+
const onRequest = (intercepted) => (req, res) => {
|
|
428
434
|
const accepted = { req, res };
|
|
429
435
|
acceptedRequests.add(accepted);
|
|
430
|
-
const task = handleRequest(req, res, opts, upstream, state, hookPath).catch((err) => {
|
|
436
|
+
const task = handleRequest(req, res, opts, upstream, state, hookPath, intercepted).catch((err) => {
|
|
431
437
|
try {
|
|
432
438
|
sendAnthropicError(res, `local proxy error: ${err?.message ?? err}`);
|
|
433
439
|
}
|
|
@@ -445,7 +451,20 @@ export function startProxy(opts) {
|
|
|
445
451
|
});
|
|
446
452
|
if (shutdownAbort.signal.aborted)
|
|
447
453
|
cancelAcceptedRequest(accepted);
|
|
448
|
-
}
|
|
454
|
+
};
|
|
455
|
+
const server = http.createServer(opts.innerToken === undefined ? onRequest(false)
|
|
456
|
+
: (req, res) => {
|
|
457
|
+
const inner = acceptInnerRequest(req, opts.innerToken);
|
|
458
|
+
if (inner === undefined) {
|
|
459
|
+
res.writeHead(403, { "content-type": "application/json" });
|
|
460
|
+
res.end(JSON.stringify({ detail: "ccc inner proxy: not for direct use" }));
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
onRequest(inner.intercepted)(req, res);
|
|
464
|
+
});
|
|
465
|
+
const closeTunnels = opts.httpsProxyMode
|
|
466
|
+
? attachHttpsProxyMode(server, onRequest(true), interceptUpstream(upstream), opts.httpsProxyMode)
|
|
467
|
+
: { blind: () => { }, all: () => { } };
|
|
449
468
|
// Long-running SSE responses must not be cut by idle timeouts.
|
|
450
469
|
server.requestTimeout = 0;
|
|
451
470
|
server.headersTimeout = 60_000;
|
|
@@ -467,6 +486,9 @@ export function startProxy(opts) {
|
|
|
467
486
|
? Math.floor(timeoutMs)
|
|
468
487
|
: 5_000;
|
|
469
488
|
backgroundAbort.abort();
|
|
489
|
+
// CONNECT tunnels are raw sockets that keep server.close() waiting: the blind ones
|
|
490
|
+
// (and idle decrypted streams) carry no ccc work, so they go now.
|
|
491
|
+
closeTunnels.blind();
|
|
470
492
|
const quiesced = (async () => {
|
|
471
493
|
// Once close completes, every accepted request has entered the tracked
|
|
472
494
|
// set.
|
|
@@ -495,6 +517,7 @@ export function startProxy(opts) {
|
|
|
495
517
|
shutdownAbort.abort();
|
|
496
518
|
for (const accepted of acceptedRequests)
|
|
497
519
|
cancelAcceptedRequest(accepted);
|
|
520
|
+
closeTunnels.all();
|
|
498
521
|
await quiesced;
|
|
499
522
|
sendDeferredAgentIndexes(state);
|
|
500
523
|
return false;
|
|
@@ -529,8 +552,10 @@ function cancelAcceptedRequest(accepted) {
|
|
|
529
552
|
if (!res.destroyed && !res.writableEnded)
|
|
530
553
|
res.destroy();
|
|
531
554
|
}
|
|
532
|
-
async function handleRequest(req, res, opts, upstream, state, hookPath) {
|
|
555
|
+
async function handleRequest(req, res, opts, upstream, state, hookPath, intercepted) {
|
|
533
556
|
const url = new URL(req.url ?? "/", `http://127.0.0.1`);
|
|
557
|
+
if (intercepted)
|
|
558
|
+
return handleInterceptedRequest(req, res, url, opts, upstream, state);
|
|
534
559
|
if (!isLocalCaller(req)) {
|
|
535
560
|
res.writeHead(403, { "content-type": "application/json" });
|
|
536
561
|
res.end(JSON.stringify({ detail: "The ccc proxy only serves programs on this machine" }));
|
|
@@ -575,6 +600,32 @@ async function handleRequest(req, res, opts, upstream, state, hookPath) {
|
|
|
575
600
|
}
|
|
576
601
|
return passThroughStreaming(req, res, upstream, state.shutdownSignal);
|
|
577
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* Claude Code's own api.anthropic.com traffic, decrypted (HTTPS-proxy mode): model calls
|
|
605
|
+
* get MemTree exactly as through the base URL; everything else (Remote Control's bridge,
|
|
606
|
+
* sessions, files, OAuth, telemetry) goes verbatim to api.anthropic.com. ccc's
|
|
607
|
+
* loopback-only routes (hooks, /memtree/*) are never answered here: such a path is
|
|
608
|
+
* Anthropic's.
|
|
609
|
+
*/
|
|
610
|
+
function handleInterceptedRequest(req, res, url, opts, upstream, state) {
|
|
611
|
+
if (req.method === "POST" && url.pathname === "/v1/messages") {
|
|
612
|
+
try {
|
|
613
|
+
opts.httpsProxyMode?.onModelCall?.();
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// A notice hook must never fail a model call.
|
|
617
|
+
}
|
|
618
|
+
return handleMessages(req, res, opts, upstream, state);
|
|
619
|
+
}
|
|
620
|
+
if (req.method === "POST" && url.pathname === "/v1/messages/count_tokens") {
|
|
621
|
+
return handleCountTokens(req, res, opts, upstream, state);
|
|
622
|
+
}
|
|
623
|
+
return passThroughStreaming(req, res, upstream, state.shutdownSignal);
|
|
624
|
+
}
|
|
625
|
+
/** Where a WebSocket upgrade on a decrypted stream is replayed: the same upstream. */
|
|
626
|
+
function interceptUpstream(upstream) {
|
|
627
|
+
return { host: upstream.host, port: upstream.port, secure: upstream.module === https };
|
|
628
|
+
}
|
|
578
629
|
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
579
630
|
const BROWSER_SAME_SITE = new Set(["same-origin", "none"]);
|
|
580
631
|
/**
|
|
@@ -1256,6 +1307,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1256
1307
|
finally {
|
|
1257
1308
|
if (rec.totalMs === undefined)
|
|
1258
1309
|
rec.totalMs = Date.now() - received;
|
|
1310
|
+
noteForwardedSize(state, rec, requestSessionId(req));
|
|
1259
1311
|
opts.reqlog?.log(rec);
|
|
1260
1312
|
}
|
|
1261
1313
|
};
|
|
@@ -1379,6 +1431,12 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1379
1431
|
const { lane: requestRouteLane, key: requestRouteKey } = routeIdentity(req, isAwaySummary);
|
|
1380
1432
|
const isSubagentRequest = requestRouteLane === "agent";
|
|
1381
1433
|
const isMainRequest = requestRouteLane === "main";
|
|
1434
|
+
// A side request or a thread continuation carries a fragment, not the conversation:
|
|
1435
|
+
// it is forwarded as sent, with no compress, route, recovery or MemTree upload.
|
|
1436
|
+
// The away summary keeps its own lane and handling.
|
|
1437
|
+
const notConversation = isAwaySummary ? undefined : notSessionConversation(body, clientInfo);
|
|
1438
|
+
if (notConversation)
|
|
1439
|
+
rec.notConversation = notConversation;
|
|
1382
1440
|
const requestSizeKey = laneSizeKey(req, requestRouteKey, body.model);
|
|
1383
1441
|
const hold = indexHolds.get(rec);
|
|
1384
1442
|
if (hold) {
|
|
@@ -1431,6 +1489,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1431
1489
|
}
|
|
1432
1490
|
}
|
|
1433
1491
|
rec.routeLane = requestRouteLane;
|
|
1492
|
+
noteLaneActivity(state, rec, requestSessionId(req));
|
|
1434
1493
|
// Stored by the server on the usage row (client_meta): which Claude Code,
|
|
1435
1494
|
// lane, agent and model produced this request.
|
|
1436
1495
|
const clientMeta = memtreeClientMeta({
|
|
@@ -1444,7 +1503,9 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1444
1503
|
// API identity and validated history alone govern route eligibility.
|
|
1445
1504
|
const rideableCandidate = getMemoryRoute(state, requestRouteKey);
|
|
1446
1505
|
const existingRide = rideableCandidate && memoryRoutedToolBody(body, messages, rideableCandidate, state.mainRouteEpoch, requestSessionId(req));
|
|
1447
|
-
|
|
1506
|
+
// A fragment is never a follow-up: it must not reserve a route decision (it would never
|
|
1507
|
+
// release it, and an in-flight main compression could not install its route).
|
|
1508
|
+
const isFollowupUserTurn = !notConversation && isUserTurn && hasEarlierNonToolUserMessage(messages);
|
|
1448
1509
|
const routeEpoch = state.mainRouteEpoch;
|
|
1449
1510
|
let routeDecisionGeneration = state.mainRouteDecisionGeneration;
|
|
1450
1511
|
let routeDecisionSettled = false;
|
|
@@ -1490,7 +1551,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1490
1551
|
// ORIGINAL conversation (the one MemTree indexed), never the ride itself.
|
|
1491
1552
|
recoverLate: async (_ride, rideTokens, target) => {
|
|
1492
1553
|
// A compression already made may be redone only to a smaller target.
|
|
1493
|
-
if ((rec.compress && target === undefined) ||
|
|
1554
|
+
if (notConversation || (rec.compress && target === undefined) ||
|
|
1494
1555
|
compactionMode(opts, state, requestSessionKey).mode === "off" ||
|
|
1495
1556
|
!hasEarlierNonToolUserMessage(messages) ||
|
|
1496
1557
|
(rec.turnType.startsWith("tool") && opts.toolRouteRecovery === false))
|
|
@@ -1513,9 +1574,14 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1513
1574
|
// The main lane's latest conversation: what its final index sends, plus the
|
|
1514
1575
|
// reply the session ends with (scheduleFinalIndex, on Stop).
|
|
1515
1576
|
const mainSessionId = isMainRequest ? requestSessionId(req) : undefined;
|
|
1516
|
-
if (mainSessionId !== undefined) {
|
|
1517
|
-
|
|
1518
|
-
|
|
1577
|
+
if (mainSessionId !== undefined && isResumableSessionId(mainSessionId)) {
|
|
1578
|
+
state.resumableMainSession = mainSessionId;
|
|
1579
|
+
}
|
|
1580
|
+
// A continuation is the session at work (a side request, e.g. a status summary, is not).
|
|
1581
|
+
if (mainSessionId !== undefined && notConversation === "thread-continue") {
|
|
1582
|
+
opts.memtree.noteMainActivity(mainSessionId);
|
|
1583
|
+
}
|
|
1584
|
+
if (mainSessionId !== undefined && !notConversation) {
|
|
1519
1585
|
opts.memtree.noteMainConversation(mainSessionId, {
|
|
1520
1586
|
messages: msgsForMemtree,
|
|
1521
1587
|
modelContextLimit,
|
|
@@ -1535,7 +1601,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1535
1601
|
// Observation only: which requests carry Claude Code's turn origin.
|
|
1536
1602
|
if (clientInfo)
|
|
1537
1603
|
rec.client = clientInfo;
|
|
1538
|
-
if (!isFollowupUserTurn) {
|
|
1604
|
+
if (!isFollowupUserTurn || notConversation) {
|
|
1539
1605
|
// Tool turn or FIRST user turn: keep the index fed off the response path
|
|
1540
1606
|
// and forward as-is (on the lane's memory route or the session's stable
|
|
1541
1607
|
// prefix when one applies) — except a tool turn whose estimated size
|
|
@@ -1551,7 +1617,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1551
1617
|
let routedTool = false;
|
|
1552
1618
|
let routedCandidate;
|
|
1553
1619
|
let routeMiss;
|
|
1554
|
-
if (isToolResultTurn || isContinuationTurn || isUserTurn) {
|
|
1620
|
+
if (!notConversation && (isToolResultTurn || isContinuationTurn || isUserTurn)) {
|
|
1555
1621
|
// Reuse only this API session/lane's independently validated prefix.
|
|
1556
1622
|
const activeRoute = rideableCandidate;
|
|
1557
1623
|
if (activeRoute) {
|
|
@@ -1612,7 +1678,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1612
1678
|
rec.continuation = true;
|
|
1613
1679
|
// Cheap shape check: without an earlier real user message there is
|
|
1614
1680
|
// nothing MemTree could compress, so no attempt is worth making.
|
|
1615
|
-
const canCompress = hasEarlierNonToolUserMessage(messages);
|
|
1681
|
+
const canCompress = !notConversation && hasEarlierNonToolUserMessage(messages);
|
|
1616
1682
|
let toolRide = routedTool
|
|
1617
1683
|
? {
|
|
1618
1684
|
raw: routedBody,
|
|
@@ -1621,7 +1687,10 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1621
1687
|
}
|
|
1622
1688
|
: undefined;
|
|
1623
1689
|
let toolNeedsOriginal = false;
|
|
1624
|
-
if (
|
|
1690
|
+
if (notConversation) {
|
|
1691
|
+
// A fragment: forwarded as sent (no route, no stable prefix, no compaction).
|
|
1692
|
+
}
|
|
1693
|
+
else if (opts.toolRouteRecovery === false) {
|
|
1625
1694
|
// Kill switch: no size check and no compress call on any tool turn.
|
|
1626
1695
|
// A route still rides; a miss forwards whole and records "disabled".
|
|
1627
1696
|
if ((routeMiss === "missing" || routeMiss === "rejected") && canCompress) {
|
|
@@ -1798,8 +1867,10 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1798
1867
|
requestSendPolicies.get(req)?.allowed.add(toolRide.raw);
|
|
1799
1868
|
const sendBody = toolRide?.raw ?? (toolNeedsOriginal ? rawBody : forwardBody);
|
|
1800
1869
|
recordTurn(rec, toolRide ? toolRide.turnType : isUserTurn ? "first-user" : "tool", sendBody);
|
|
1801
|
-
|
|
1802
|
-
|
|
1870
|
+
if (!notConversation) {
|
|
1871
|
+
indexTurn({ opts, state, req, rec, hash, msgsForMemtree, modelContextLimit,
|
|
1872
|
+
sessionId: toolSessionId, clientMeta });
|
|
1873
|
+
}
|
|
1803
1874
|
capture(opts, toolRide ? "anthropic-request-memory-tool" : "anthropic-request", sendBody);
|
|
1804
1875
|
// The size Anthropic reports is the next budget check's anchor: a ride
|
|
1805
1876
|
// reports to its prefix (or route), a whole request to the session's (or
|
|
@@ -3326,8 +3397,16 @@ function reportedSample(rec, sent) {
|
|
|
3326
3397
|
}
|
|
3327
3398
|
/** A size sample of `sent` carrying its inline images (estimateRequestTokens). */
|
|
3328
3399
|
function sizeSampleOf(sent, tokens, model) {
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3400
|
+
const id = typeof model === "string" ? model : undefined;
|
|
3401
|
+
const { imageBytes, imageTokens } = bodyImages(sent, id);
|
|
3402
|
+
return { tokens, forwardedBytes: sent.length, imageBytes, imageTokens, ...sampleLineage(sent, id) };
|
|
3403
|
+
}
|
|
3404
|
+
/** The sampled body's lineage (body-shape.ts) for anchoredBound; none for a small body. */
|
|
3405
|
+
function sampleLineage(sent, model) {
|
|
3406
|
+
const shape = bodyShape(sent);
|
|
3407
|
+
const digest = shape && prefixDigest(shape, shape.messages.length);
|
|
3408
|
+
return shape && digest && model
|
|
3409
|
+
? { model, messageCount: shape.messages.length, prefixDigest: digest } : {};
|
|
3331
3410
|
}
|
|
3332
3411
|
/**
|
|
3333
3412
|
* Anthropic's input tokens for the body this request forwarded, when its usage
|
|
@@ -4112,6 +4191,8 @@ const exactSizedRecords = new WeakSet();
|
|
|
4112
4191
|
* it is over the window for planning and takes the normal gated compression.
|
|
4113
4192
|
*/
|
|
4114
4193
|
function noteUncountedBound(record, sizing, candidate, body, modelContextLimit) {
|
|
4194
|
+
if (candidate.anchored)
|
|
4195
|
+
record.anchoredBound = true;
|
|
4115
4196
|
if (sizing.isExact(candidate)) {
|
|
4116
4197
|
exactSizedRecords.add(record);
|
|
4117
4198
|
return;
|
|
@@ -5227,7 +5308,23 @@ async function recoverToolRouteMiss(args) {
|
|
|
5227
5308
|
function recordTurn(rec, turnType, forwardBody) {
|
|
5228
5309
|
rec.turnType = turnType;
|
|
5229
5310
|
rec.forwardedBytes = forwardBody.length;
|
|
5230
|
-
rec.approxInputTokens =
|
|
5311
|
+
rec.approxInputTokens = approxBodyTokens(forwardBody, rec.model);
|
|
5312
|
+
forwardedImageBytes.set(rec, bodyImages(forwardBody, typeof rec.model === "string" ? rec.model : undefined).imageBytes);
|
|
5313
|
+
}
|
|
5314
|
+
/** The forwarded body's inline image bytes (recordTurn), for the heavy-request notice. */
|
|
5315
|
+
const forwardedImageBytes = new WeakMap();
|
|
5316
|
+
/** What this request uploaded to Anthropic, for the session's heavy-request notice. */
|
|
5317
|
+
function noteForwardedSize(state, rec, sessionId) {
|
|
5318
|
+
const bytes = rec.forwardedBytes ?? 0;
|
|
5319
|
+
if (bytes > 0) {
|
|
5320
|
+
state.laneActivity.sent(sessionId, { bytes, imageBytes: forwardedImageBytes.get(rec) ?? 0 });
|
|
5321
|
+
}
|
|
5322
|
+
}
|
|
5323
|
+
/** A body's text at bytes/4 plus its inline images at their visual tokens (log only):
|
|
5324
|
+
* bytes/4 alone logged a 22 MB contact-sheet lane at 5.5M tokens (Anthropic: 491k). */
|
|
5325
|
+
function approxBodyTokens(body, model) {
|
|
5326
|
+
const images = bodyImages(body, typeof model === "string" ? model : undefined);
|
|
5327
|
+
return approxTokensFromBytes(body.length - images.imageBytes) + images.imageTokens;
|
|
5231
5328
|
}
|
|
5232
5329
|
function headerText(headers, name) {
|
|
5233
5330
|
const value = headers[name];
|
|
@@ -5707,7 +5804,7 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
5707
5804
|
: offline ? unreachableRefusal(offline.message)
|
|
5708
5805
|
: args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true,
|
|
5709
5806
|
indexSecondsLeft: activeIndexWaits.get(args.rec),
|
|
5710
|
-
refusalMessage: refusalMessage(cause, currentSessionLink(args.state, requestSessionId(args.req))) });
|
|
5807
|
+
refusalMessage: refusalMessage(cause, currentSessionLink(args.state, requestSessionId(args.req)), agentResultNote(args)) });
|
|
5711
5808
|
args.rec.forwardedBytes = 0;
|
|
5712
5809
|
args.rec.approxInputTokens = 0;
|
|
5713
5810
|
// Nothing went out uncompressed: Claude Code retries it (2026-10-28).
|
|
@@ -5750,6 +5847,17 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
5750
5847
|
args.state.keyNotice.markShown();
|
|
5751
5848
|
return false;
|
|
5752
5849
|
}
|
|
5850
|
+
/**
|
|
5851
|
+
* A main-lane refusal while an agent lane of this session was recently active: a
|
|
5852
|
+
* background agent's finished result reaches the main session only as part of its
|
|
5853
|
+
* next turn, so say it may be waiting (2026-11-13: a refused main lane held one back).
|
|
5854
|
+
*/
|
|
5855
|
+
function agentResultNote(args) {
|
|
5856
|
+
// This session's own agent traffic only: activeSubagents is process-wide (review r1 P3).
|
|
5857
|
+
return args.rec.routeLane === "main" &&
|
|
5858
|
+
args.state.laneActivity.agentRecent(requestSessionId(args.req))
|
|
5859
|
+
? AGENT_RESULT_WAITING_LINE : undefined;
|
|
5860
|
+
}
|
|
5753
5861
|
/** Requests held back by a lane backoff whose attempt MemTree answered (unreachableNow). */
|
|
5754
5862
|
const answeredBackoffs = new WeakSet();
|
|
5755
5863
|
/**
|
|
@@ -5781,6 +5889,25 @@ function unreachableNow(state, rec) {
|
|
|
5781
5889
|
return { message: unreachableMessage(memtree, anthropic),
|
|
5782
5890
|
what: anthropic ? "internet" : "memtree" };
|
|
5783
5891
|
}
|
|
5892
|
+
/**
|
|
5893
|
+
* Note this request's lane (lane-activity.ts) and, on a main-lane request of the current
|
|
5894
|
+
* prompt, queue the session's one heavy-request warning when an earlier upload passed the
|
|
5895
|
+
* size and no other notice waits. It is the lowest priority (otherNoticePending): any
|
|
5896
|
+
* other notice may replace it, which leaves it due for a later turn.
|
|
5897
|
+
*/
|
|
5898
|
+
function noteLaneActivity(state, rec, sessionId) {
|
|
5899
|
+
state.laneActivity.seen(sessionId, rec.routeLane);
|
|
5900
|
+
const heavy = state.laneActivity.heavyDue(sessionId);
|
|
5901
|
+
const prompt = noticeContexts.get(rec);
|
|
5902
|
+
if (!heavy || sessionId === undefined || rec.routeLane !== "main" || prompt === undefined ||
|
|
5903
|
+
prompt.generation !== state.mainPromptGeneration || state.notices.hasPending())
|
|
5904
|
+
return;
|
|
5905
|
+
state.heavyNotice = state.notices.queueSuffix(heavyRequestNoticeText(heavy), () => state.laneActivity.markWarned(sessionId), prompt.promptId);
|
|
5906
|
+
}
|
|
5907
|
+
/** Whether a notice other than the heavy-request one (the lowest priority) waits. */
|
|
5908
|
+
function otherNoticePending(state, promptId) {
|
|
5909
|
+
return state.notices.hasPending(promptId) && !state.notices.pendingIs(state.heavyNotice);
|
|
5910
|
+
}
|
|
5784
5911
|
/**
|
|
5785
5912
|
* A repeated retryable refusal on the main thread: say once per streak that
|
|
5786
5913
|
* MemTree is slow and Claude Code is retrying, unless another notice waits.
|
|
@@ -5793,7 +5920,7 @@ function noteSlowRetry(args, failure) {
|
|
|
5793
5920
|
const canShow = failure.status === 503 && rec.routeLane === "main" &&
|
|
5794
5921
|
rec.indexBehind !== true && prompt !== undefined &&
|
|
5795
5922
|
prompt.generation === state.mainPromptGeneration && !args.res.destroyed &&
|
|
5796
|
-
!state
|
|
5923
|
+
!otherNoticePending(state);
|
|
5797
5924
|
if (!state.refusalStreaks.refused(args.routeKey, Date.now(), canShow))
|
|
5798
5925
|
return;
|
|
5799
5926
|
state.notices.queueSuffix(SLOW_RETRY_NOTICE, undefined, prompt.promptId);
|
|
@@ -5844,7 +5971,7 @@ function warnPausedNearWindow(state, rec, sessionId) {
|
|
|
5844
5971
|
// Never replace another queued notice (or this one again): a later request tries.
|
|
5845
5972
|
if (detail === null || rec.routeLane !== "main" || sessionId === undefined ||
|
|
5846
5973
|
pausedNotices.has(rec) || nearWindowWarned(state, sessionId) ||
|
|
5847
|
-
|
|
5974
|
+
otherNoticePending(state, state.mainPromptId))
|
|
5848
5975
|
return;
|
|
5849
5976
|
state.nearWindowNotice = state.notices.queueSuffix(paymentNoticeText(detail, undefined, { overBudget: true }), () => {
|
|
5850
5977
|
state.paymentNotice.markShown();
|
|
@@ -6700,4 +6827,23 @@ function transcriptTimesFor(opts, sessionId, agentId) {
|
|
|
6700
6827
|
return (messages) => source.timesFor(sessionId, messages, agentId);
|
|
6701
6828
|
}
|
|
6702
6829
|
export const __testHoldingLane = holdingLane;
|
|
6830
|
+
/**
|
|
6831
|
+
* The inner-proxy headers (ProxyOptions.innerToken): checked, then stripped so nothing
|
|
6832
|
+
* forwards them. undefined: not the service.
|
|
6833
|
+
*/
|
|
6834
|
+
function acceptInnerRequest(req, token) {
|
|
6835
|
+
if (!sameSecret(req.headers[INNER_TOKEN_HEADER], token))
|
|
6836
|
+
return undefined;
|
|
6837
|
+
const intercepted = req.headers[INNER_INTERCEPTED_HEADER] === "1";
|
|
6838
|
+
delete req.headers[INNER_TOKEN_HEADER];
|
|
6839
|
+
delete req.headers[INNER_INTERCEPTED_HEADER];
|
|
6840
|
+
for (let i = req.rawHeaders.length - 2; i >= 0; i -= 2) {
|
|
6841
|
+
const name = req.rawHeaders[i].toLowerCase();
|
|
6842
|
+
if (name === INNER_TOKEN_HEADER || name === INNER_INTERCEPTED_HEADER)
|
|
6843
|
+
req.rawHeaders.splice(i, 2);
|
|
6844
|
+
}
|
|
6845
|
+
return { intercepted };
|
|
6846
|
+
}
|
|
6847
|
+
export const INNER_TOKEN_HEADER = "x-ccc-inner";
|
|
6848
|
+
export const INNER_INTERCEPTED_HEADER = "x-ccc-intercepted";
|
|
6703
6849
|
//# sourceMappingURL=proxy.js.map
|