claude-code-infinite 1.0.34 → 1.0.36
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 +56 -0
- 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/cli-main.js +19 -2
- package/dist/cli-main.js.map +1 -1
- package/dist/cli.js +23 -10
- package/dist/cli.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/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/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-newest.d.ts +11 -3
- package/dist/memtree-newest.d.ts.map +1 -1
- package/dist/memtree-newest.js +24 -4
- package/dist/memtree-newest.js.map +1 -1
- package/dist/memtree.d.ts +10 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +14 -0
- package/dist/memtree.js.map +1 -1
- package/dist/proxy.d.ts +21 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +153 -12
- package/dist/proxy.js.map +1 -1
- package/dist/refusal-reason.d.ts +70 -0
- package/dist/refusal-reason.d.ts.map +1 -0
- package/dist/refusal-reason.js +132 -0
- package/dist/refusal-reason.js.map +1 -0
- package/dist/reqlog.d.ts +25 -2
- 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/route-fallback.d.ts +2 -0
- package/dist/route-fallback.d.ts.map +1 -1
- package/dist/route-fallback.js +8 -8
- package/dist/route-fallback.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/slow-upload.d.ts +40 -0
- package/dist/slow-upload.d.ts.map +1 -0
- package/dist/slow-upload.js +69 -0
- package/dist/slow-upload.js.map +1 -0
- package/dist/status-link.d.ts +18 -0
- package/dist/status-link.d.ts.map +1 -0
- package/dist/status-link.js +103 -0
- package/dist/status-link.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 +51 -0
- package/dist/statusline.d.ts.map +1 -0
- package/dist/statusline.js +380 -0
- package/dist/statusline.js.map +1 -0
- 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
|
@@ -42,6 +42,9 @@ import { UNKNOWN_INDEX_WAIT_SECONDS } from "./index-wait-413.js";
|
|
|
42
42
|
import { capCacheBreakpoints } from "./route-cache.js";
|
|
43
43
|
import { PromptAccounting } from "./prompt-accounting.js";
|
|
44
44
|
import { INDEX_WAIT_RETRY_AFTER_SECONDS, RefusalStreaks, RouteFallbackFailures, pausedRefusal, unreachableRefusal } from "./route-fallback.js";
|
|
45
|
+
import { refusalCause, refusalMessage } from "./refusal-reason.js";
|
|
46
|
+
import { AGENT_RESULT_WAITING_LINE, LaneActivity, heavyRequestNoticeText } from "./lane-activity.js";
|
|
47
|
+
import { SlowUploads } from "./slow-upload.js";
|
|
45
48
|
import { freshFailure, networkFailureOf, unreachableMessage, } from "./network-failure.js";
|
|
46
49
|
import { createCountTokensSession, CountTokensCooldowns, plausibleSample, } from "./count-tokens.js";
|
|
47
50
|
import { RequestSizing, estimateRequestTokens, fitsMeasured, fitsNative, outputReservation } from "./request-sizing.js";
|
|
@@ -55,7 +58,7 @@ import { CLAUDE_PID_HEADER, HOST_TOKEN_HEADER } from "./host-session.js";
|
|
|
55
58
|
import { brotliDecompressSync, createBrotliDecompress, createGunzip, createInflate, gunzipSync, inflateSync, } from "node:zlib";
|
|
56
59
|
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
57
60
|
import { join } from "node:path";
|
|
58
|
-
import { cachedPromptTokenCount, checkCompressedHistory, didMemtreeCompress, indexLagEvidence, losesMedia, MemtreeClient, modelBudgetTokens, normalizeMessagesForMemtree, serverFlattenedMessages, rawPromptTokenCount, } from "./memtree.js";
|
|
61
|
+
import { cachedPromptTokenCount, checkCompressedHistory, didMemtreeCompress, indexLagEvidence, losesMedia, MemtreeClient, modelBudgetTokens, normalizeMessagesForMemtree, serverFlattenedMessages, rawPromptTokenCount, memoryReadySeconds, unindexedTokenCount, } from "./memtree.js";
|
|
59
62
|
import { contextLimitForModel, hasEarlierNonToolUserMessage, isAwaySummaryUserMessage, isLocalBashCommandTurn, isNonToolUserMessage, isToolResultUserMessage, lastNonSystemMessage, messagesWithSystem, modelForMemtree, stripSystemReminderText, } from "./turns.js";
|
|
60
63
|
import { COMPRESSED_NOTICE, compressedTotalsText, DEGRADED_NOTICE, NOT_COMPRESSED_NOTE, SLOW_RETRY_NOTICE, recapLinkText, SseNoticeRewriter, stripNoticeBlocks, stripNoticeSystem, } from "./notices.js";
|
|
61
64
|
import { NoticeDeliveryQueue, MEMTREE_COMPACT_COMMAND, MEMTREE_HELP_COMMAND, TRAILER_LABEL, LINK_LABEL, linkLines, terminalSupportsHyperlinks, isMemtreeViewCommand, sessionCommandArgs, parseNoticeHookInput, PRECOMPACT_HOOK_PATH } from "./hooks.js";
|
|
@@ -71,6 +74,7 @@ import { isSubagentCall, postToolUseOutput, SUBAGENT_RESULT_HINT, subagentMemory
|
|
|
71
74
|
import { PROJECT_TREES_HOOK_SUFFIX, RESUME_GUIDANCE_HOOK_SUFFIX, SUBAGENT_RESULT_HOOK_SUFFIX, } from "./hooks.js";
|
|
72
75
|
import { conservativeBodyTokens } from "./token-estimate.js";
|
|
73
76
|
import { bodyImages } from "./image-tokens.js";
|
|
77
|
+
import { bodyShape, prefixDigest } from "./body-shape.js";
|
|
74
78
|
import { claudeAuthFromHeaders, upstreamErrorCode } from "./claude-auth.js";
|
|
75
79
|
import { rateLimitFromResponse } from "./rate-limit.js";
|
|
76
80
|
import { approxTokensFromBytes, mergeUsageFromJsonBody, mergeUsageFromSseEvent, } from "./reqlog.js";
|
|
@@ -229,6 +233,12 @@ function noteIndexLag(opts, rec, result) {
|
|
|
229
233
|
if (!indexLagEvidence(result))
|
|
230
234
|
return false;
|
|
231
235
|
rec.indexBehind = true;
|
|
236
|
+
const etaSeconds = memoryReadySeconds(result);
|
|
237
|
+
const unindexedTokens = unindexedTokenCount(result);
|
|
238
|
+
if (etaSeconds !== undefined || unindexedTokens !== undefined) {
|
|
239
|
+
rec.indexLag = { ...(etaSeconds !== undefined ? { etaSeconds } : {}),
|
|
240
|
+
...(unindexedTokens !== undefined ? { unindexedTokens } : {}) };
|
|
241
|
+
}
|
|
232
242
|
opts.memtree.expireCompressResult?.(result, opts.indexWaitRefreshMs ?? INDEX_WAIT_RETRY_AFTER_SECONDS * 1000);
|
|
233
243
|
return true;
|
|
234
244
|
}
|
|
@@ -370,9 +380,11 @@ export function startProxy(opts) {
|
|
|
370
380
|
mainNoticePending: false,
|
|
371
381
|
mainPromptGeneration: 0,
|
|
372
382
|
promptAccounting: new PromptAccounting(),
|
|
383
|
+
slowUploads: new SlowUploads(undefined, opts.slowUploadMinBytes),
|
|
373
384
|
fallbackFailures: new RouteFallbackFailures(undefined, opts.indexWaitDeadlineMs, opts.indexWaitMaxMs),
|
|
374
385
|
refusalStreaks: new RefusalStreaks(),
|
|
375
386
|
activeSubagents: new Set(),
|
|
387
|
+
laneActivity: new LaneActivity(),
|
|
376
388
|
deferredAgentIndexes: new Map(),
|
|
377
389
|
adoptingPrepared: new Map(),
|
|
378
390
|
memoryRoutes: new Map(),
|
|
@@ -395,6 +407,8 @@ export function startProxy(opts) {
|
|
|
395
407
|
storageOwner: typeof opts.memtree.storageIdentity === "string"
|
|
396
408
|
? opts.memtree.storageIdentity : undefined,
|
|
397
409
|
newestTrees: opts.newestTrees,
|
|
410
|
+
statusLinks: opts.statusLinks,
|
|
411
|
+
statusLinkRefresh: opts.statusLinkRefresh === true,
|
|
398
412
|
memtreeLinkPlacement: opts.memtreeLinkPlacement ?? "turn",
|
|
399
413
|
compactTargets: new Map(),
|
|
400
414
|
defaultCompactOff: opts.defaultCompactTarget === null,
|
|
@@ -410,6 +424,8 @@ export function startProxy(opts) {
|
|
|
410
424
|
laneSizes: new Map(),
|
|
411
425
|
};
|
|
412
426
|
installMemtreeLink(state, state.memtreeLinkPlacement);
|
|
427
|
+
// A newer tree from the background lookup reaches the status line without a turn ending.
|
|
428
|
+
opts.newestTrees?.onTree((sessionId) => publishStatusLink(state, sessionId));
|
|
413
429
|
state.notices.setFirstTreeIntro(opts.firstTreeIntro ?? null);
|
|
414
430
|
const server = http.createServer((req, res) => {
|
|
415
431
|
const accepted = { req, res };
|
|
@@ -1243,6 +1259,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1243
1259
|
finally {
|
|
1244
1260
|
if (rec.totalMs === undefined)
|
|
1245
1261
|
rec.totalMs = Date.now() - received;
|
|
1262
|
+
noteForwardedSize(state, rec, requestSessionId(req));
|
|
1246
1263
|
opts.reqlog?.log(rec);
|
|
1247
1264
|
}
|
|
1248
1265
|
};
|
|
@@ -1354,6 +1371,11 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1354
1371
|
return link ? recapLinkText(link.link, streamedTextChars, link.note) : undefined;
|
|
1355
1372
|
});
|
|
1356
1373
|
}
|
|
1374
|
+
else if (state.statusLinks && state.statusLinkRefresh) {
|
|
1375
|
+
// Keeps the status line's link fresh through long tool runs: answers from the cache,
|
|
1376
|
+
// refreshing in the background at most once a minute (NEWEST_TREE_TTL_MS).
|
|
1377
|
+
state.newestTrees?.peek(requestSessionId(req));
|
|
1378
|
+
}
|
|
1357
1379
|
const isLocalBashCommand = isLocalBashCommandTurn(messages);
|
|
1358
1380
|
// CC 2.1.207 identifies agent API calls explicitly. Use that wire-level
|
|
1359
1381
|
// attribution before lifecycle-hook state so an agent request cannot claim
|
|
@@ -1413,6 +1435,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1413
1435
|
}
|
|
1414
1436
|
}
|
|
1415
1437
|
rec.routeLane = requestRouteLane;
|
|
1438
|
+
noteLaneActivity(state, rec, requestSessionId(req));
|
|
1416
1439
|
// Stored by the server on the usage row (client_meta): which Claude Code,
|
|
1417
1440
|
// lane, agent and model produced this request.
|
|
1418
1441
|
const clientMeta = memtreeClientMeta({
|
|
@@ -2773,10 +2796,11 @@ function noteMemtreePage(state, seq, sessionId, result) {
|
|
|
2773
2796
|
if (latest && latest.seq >= seq)
|
|
2774
2797
|
return;
|
|
2775
2798
|
const compressed = didMemtreeCompress(result);
|
|
2799
|
+
// A cached newest tree may predate this page; ask again (first, so the status line's
|
|
2800
|
+
// link is this page until the lookup answers).
|
|
2801
|
+
state.newestTrees?.invalidate(sessionId);
|
|
2776
2802
|
rememberMemtreePage(state, sessionId, { sessionId, url, index, compressed, seq });
|
|
2777
2803
|
state.memtreeLinkStore?.put(sessionId, { url, index, compressed });
|
|
2778
|
-
// A cached newest tree may predate this page; ask again.
|
|
2779
|
-
state.newestTrees?.invalidate(sessionId);
|
|
2780
2804
|
}
|
|
2781
2805
|
/**
|
|
2782
2806
|
* Record a compress answer's page as the source of the memory now forwarded
|
|
@@ -2795,6 +2819,13 @@ function noteMemorySource(state, seq, sessionId, result) {
|
|
|
2795
2819
|
}
|
|
2796
2820
|
function rememberMemtreePage(state, sessionId, page) {
|
|
2797
2821
|
rememberPage(state.memtreePages, sessionId, page);
|
|
2822
|
+
publishStatusLink(state, sessionId);
|
|
2823
|
+
}
|
|
2824
|
+
/** Write the session's current link (as the trailers would show it) for the status line. */
|
|
2825
|
+
function publishStatusLink(state, sessionId) {
|
|
2826
|
+
if (!state.statusLinks)
|
|
2827
|
+
return;
|
|
2828
|
+
state.statusLinks.write(sessionId, currentSessionLink(state, sessionId));
|
|
2798
2829
|
}
|
|
2799
2830
|
/** Bound optional page state independently of how many sessions use the proxy. */
|
|
2800
2831
|
function rememberPage(pages, sessionId, page) {
|
|
@@ -3300,8 +3331,16 @@ function reportedSample(rec, sent) {
|
|
|
3300
3331
|
}
|
|
3301
3332
|
/** A size sample of `sent` carrying its inline images (estimateRequestTokens). */
|
|
3302
3333
|
function sizeSampleOf(sent, tokens, model) {
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3334
|
+
const id = typeof model === "string" ? model : undefined;
|
|
3335
|
+
const { imageBytes, imageTokens } = bodyImages(sent, id);
|
|
3336
|
+
return { tokens, forwardedBytes: sent.length, imageBytes, imageTokens, ...sampleLineage(sent, id) };
|
|
3337
|
+
}
|
|
3338
|
+
/** The sampled body's lineage (body-shape.ts) for anchoredBound; none for a small body. */
|
|
3339
|
+
function sampleLineage(sent, model) {
|
|
3340
|
+
const shape = bodyShape(sent);
|
|
3341
|
+
const digest = shape && prefixDigest(shape, shape.messages.length);
|
|
3342
|
+
return shape && digest && model
|
|
3343
|
+
? { model, messageCount: shape.messages.length, prefixDigest: digest } : {};
|
|
3305
3344
|
}
|
|
3306
3345
|
/**
|
|
3307
3346
|
* Anthropic's input tokens for the body this request forwarded, when its usage
|
|
@@ -3966,7 +4005,7 @@ function carryFor(args) {
|
|
|
3966
4005
|
* one beside it: 2026-10-30, eleven 26 MB compressions in ten minutes, each billed, none
|
|
3967
4006
|
* used. False when the session already has a preparation in flight (it serves instead).
|
|
3968
4007
|
*/
|
|
3969
|
-
function carryOverCompression(state, carry, pending) {
|
|
4008
|
+
function carryOverCompression(state, carry, pending, bytes = 0) {
|
|
3970
4009
|
const { sessionId, base } = carry;
|
|
3971
4010
|
// A prefix installed since the call started (another turn's) supersedes it already.
|
|
3972
4011
|
if (state.backgroundSignal.aborted || state.preparing.has(sessionId) ||
|
|
@@ -3979,6 +4018,9 @@ function carryOverCompression(state, carry, pending) {
|
|
|
3979
4018
|
// refusal or a slow server's timeout must not arm it, as runBlockingCompression).
|
|
3980
4019
|
if (result)
|
|
3981
4020
|
noteMemtreeHealth(state, { result, liveHealth: "success" });
|
|
4021
|
+
// Its upload got through: a slow-upload episode is over (Codex r4 P2).
|
|
4022
|
+
if (result)
|
|
4023
|
+
state.slowUploads.settled(sessionId, bytes, "ok");
|
|
3982
4024
|
return result ? preparedFromResult(carry, result) : undefined;
|
|
3983
4025
|
})
|
|
3984
4026
|
.catch(() => undefined)
|
|
@@ -3995,6 +4037,14 @@ function carryOverCompression(state, carry, pending) {
|
|
|
3995
4037
|
/** How long a carried compaction may run: the long budget, or its upload's if longer. */
|
|
3996
4038
|
/** How much longer than its turn's wait a carried call must be allowed to run. */
|
|
3997
4039
|
const CARRY_MIN_EXTRA_MS = 5_000;
|
|
4040
|
+
/**
|
|
4041
|
+
* The long compress budget for an upload of `bytes`: the long budget or the upload's
|
|
4042
|
+
* scaled one if longer, raised after the session's last large upload timed out.
|
|
4043
|
+
*/
|
|
4044
|
+
function longCompressBudgetFor(opts, state, sessionId, bytes) {
|
|
4045
|
+
const longMs = Math.max(opts.memtree.longCompressBudgetMs, opts.memtree.compressBudgetForBytes(bytes));
|
|
4046
|
+
return state.slowUploads.budgetMs(sessionId, bytes, longMs);
|
|
4047
|
+
}
|
|
3998
4048
|
function carriedBudgetMs(opts, scaledMs) {
|
|
3999
4049
|
return Math.min(MAX_LONG_COMPRESS_WAIT_MS, Math.max(opts.memtree.longCompressBudgetMs, scaledMs));
|
|
4000
4050
|
}
|
|
@@ -4075,6 +4125,8 @@ const exactSizedRecords = new WeakSet();
|
|
|
4075
4125
|
* it is over the window for planning and takes the normal gated compression.
|
|
4076
4126
|
*/
|
|
4077
4127
|
function noteUncountedBound(record, sizing, candidate, body, modelContextLimit) {
|
|
4128
|
+
if (candidate.anchored)
|
|
4129
|
+
record.anchoredBound = true;
|
|
4078
4130
|
if (sizing.isExact(candidate)) {
|
|
4079
4131
|
exactSizedRecords.add(record);
|
|
4080
4132
|
return;
|
|
@@ -4567,7 +4619,11 @@ async function runBlockingCompressionOnce(args) {
|
|
|
4567
4619
|
// The upload is about the request's size: the budget grows with it (the long one
|
|
4568
4620
|
// too, never below the short one's).
|
|
4569
4621
|
const scaledMs = opts.memtree.compressBudgetForBytes(rec.requestBytes);
|
|
4570
|
-
|
|
4622
|
+
// A session whose last large upload ran out of the long budget gets more (slow-upload.ts).
|
|
4623
|
+
const extendedBudget = args.longBudget === true &&
|
|
4624
|
+
state.slowUploads.extended(args.sessionId, rec.requestBytes);
|
|
4625
|
+
const budgetMs = Math.min(args.longBudget ? MAX_LONG_COMPRESS_WAIT_MS : MAX_COMPRESS_WAIT_MS, args.longBudget ? longCompressBudgetFor(opts, state, args.sessionId, rec.requestBytes)
|
|
4626
|
+
: scaledMs, args.capMs ?? Infinity);
|
|
4571
4627
|
// Carried only when running on gives it meaningfully more time than this wait.
|
|
4572
4628
|
const carry = args.carry && args.capMs === undefined &&
|
|
4573
4629
|
carriedBudgetMs(opts, scaledMs) >= budgetMs + CARRY_MIN_EXTRA_MS ? args.carry : undefined;
|
|
@@ -4623,7 +4679,7 @@ async function runBlockingCompressionOnce(args) {
|
|
|
4623
4679
|
const compressMs = Date.now() - compressStarted;
|
|
4624
4680
|
// Given up on but still running: the session's next turn waits for it, not a new call.
|
|
4625
4681
|
const carried = result === null && !settled && carry !== undefined &&
|
|
4626
|
-
carryOverCompression(state, carry, call);
|
|
4682
|
+
carryOverCompression(state, carry, call, rec.requestBytes);
|
|
4627
4683
|
noteServerBudget(state, body.model, modelContextLimit, result);
|
|
4628
4684
|
// Sampled at this call's settle, before a concurrent same-request failure of
|
|
4629
4685
|
// another class can overwrite the complete-key entry.
|
|
@@ -4650,10 +4706,21 @@ async function runBlockingCompressionOnce(args) {
|
|
|
4650
4706
|
const othersAnswered = timedOut && (opts.memtree.lastCompressSuccessAt ?? 0) >= compressStarted;
|
|
4651
4707
|
// A carried call has not failed: it is still running (its answer reports its health).
|
|
4652
4708
|
const hadLiveFailure = !cached && result === null && failureArming && !othersAnswered && !carried;
|
|
4709
|
+
// Only a whole long budget spent is the upload's slowness (a hold's cap is not).
|
|
4710
|
+
// A large upload's live answer (any budget, not a cache hit) ends a slow-upload episode.
|
|
4711
|
+
if (result !== null && !cached)
|
|
4712
|
+
state.slowUploads.settled(args.sessionId, rec.requestBytes, "ok");
|
|
4713
|
+
const uploadTimeout = result === null && args.longBudget === true &&
|
|
4714
|
+
args.capMs === undefined && !carried &&
|
|
4715
|
+
state.slowUploads.settled(args.sessionId, rec.requestBytes, timedOut ? "timeout" : "other");
|
|
4653
4716
|
rec.compress = {
|
|
4654
4717
|
ms: compressMs,
|
|
4655
4718
|
ok: result !== null,
|
|
4656
4719
|
timedOut,
|
|
4720
|
+
budgetMs,
|
|
4721
|
+
...(extendedBudget ? { extendedBudget: true } : {}),
|
|
4722
|
+
...(uploadTimeout ? { uploadTimeout: true } : {}),
|
|
4723
|
+
...(failureStatus !== undefined ? { status: failureStatus } : {}),
|
|
4657
4724
|
...(carried ? { carriedOver: true } : {}),
|
|
4658
4725
|
// A joined call's indexing 413 is MemTree's refusal of this request too.
|
|
4659
4726
|
...((!cached && failureStatus === OVER_CONTEXT_STATUS) || indexWaitSeconds !== undefined
|
|
@@ -5175,7 +5242,23 @@ async function recoverToolRouteMiss(args) {
|
|
|
5175
5242
|
function recordTurn(rec, turnType, forwardBody) {
|
|
5176
5243
|
rec.turnType = turnType;
|
|
5177
5244
|
rec.forwardedBytes = forwardBody.length;
|
|
5178
|
-
rec.approxInputTokens =
|
|
5245
|
+
rec.approxInputTokens = approxBodyTokens(forwardBody, rec.model);
|
|
5246
|
+
forwardedImageBytes.set(rec, bodyImages(forwardBody, typeof rec.model === "string" ? rec.model : undefined).imageBytes);
|
|
5247
|
+
}
|
|
5248
|
+
/** The forwarded body's inline image bytes (recordTurn), for the heavy-request notice. */
|
|
5249
|
+
const forwardedImageBytes = new WeakMap();
|
|
5250
|
+
/** What this request uploaded to Anthropic, for the session's heavy-request notice. */
|
|
5251
|
+
function noteForwardedSize(state, rec, sessionId) {
|
|
5252
|
+
const bytes = rec.forwardedBytes ?? 0;
|
|
5253
|
+
if (bytes > 0) {
|
|
5254
|
+
state.laneActivity.sent(sessionId, { bytes, imageBytes: forwardedImageBytes.get(rec) ?? 0 });
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
/** A body's text at bytes/4 plus its inline images at their visual tokens (log only):
|
|
5258
|
+
* bytes/4 alone logged a 22 MB contact-sheet lane at 5.5M tokens (Anthropic: 491k). */
|
|
5259
|
+
function approxBodyTokens(body, model) {
|
|
5260
|
+
const images = bodyImages(body, typeof model === "string" ? model : undefined);
|
|
5261
|
+
return approxTokensFromBytes(body.length - images.imageBytes) + images.imageTokens;
|
|
5179
5262
|
}
|
|
5180
5263
|
function headerText(headers, name) {
|
|
5181
5264
|
const value = headers[name];
|
|
@@ -5617,6 +5700,30 @@ async function wholeRequestRefused(sizing, candidate, limits) {
|
|
|
5617
5700
|
return false;
|
|
5618
5701
|
return !fitsMeasured(await sizing.measure(candidate, limits), limits);
|
|
5619
5702
|
}
|
|
5703
|
+
/** What explains refusing this request (refusal-reason.ts), from its record and the lane. */
|
|
5704
|
+
function refusalCauseFor(args, inputTokens) {
|
|
5705
|
+
const { state, rec } = args;
|
|
5706
|
+
const sessionId = requestSessionId(args.req);
|
|
5707
|
+
const bytes = rec.requestBytes;
|
|
5708
|
+
// After a slow upload: what the session's next long-budget try gets.
|
|
5709
|
+
const nextCompressBudgetMs = rec.compress?.uploadTimeout
|
|
5710
|
+
? Math.min(MAX_LONG_COMPRESS_WAIT_MS, longCompressBudgetFor(args.opts, state, sessionId, bytes))
|
|
5711
|
+
: undefined;
|
|
5712
|
+
// This request's own 413, then its own answer's ETA, then the lane's last stated time.
|
|
5713
|
+
const known = (seconds) => seconds === UNKNOWN_INDEX_WAIT_SECONDS ? undefined : seconds;
|
|
5714
|
+
return refusalCause(rec, {
|
|
5715
|
+
inputTokens, modelContextLimit: args.modelContextLimit,
|
|
5716
|
+
// The 60 s stand-in for an indexing 413 that stated no time is no estimate.
|
|
5717
|
+
indexSecondsLeft: known(activeIndexWaits.get(rec)) ?? rec.indexLag?.etaSeconds ??
|
|
5718
|
+
known(state.fallbackFailures.indexSecondsLeft(args.routeKey)),
|
|
5719
|
+
nextCompressBudgetMs,
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
/** The session's MemTree page (refusal line, status line), as the trailers would show it. */
|
|
5723
|
+
function currentSessionLink(state, sessionId) {
|
|
5724
|
+
return sessionId === undefined ? undefined
|
|
5725
|
+
: sessionLink(state, sessionId, currentMemtreePage(state, sessionId))?.link;
|
|
5726
|
+
}
|
|
5620
5727
|
/** Write the bounded fallback refusal (route-fallback.ts); always false. */
|
|
5621
5728
|
function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
5622
5729
|
// A refused key or a paused one: a retry cannot fix either, so it is final at once.
|
|
@@ -5624,11 +5731,14 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
5624
5731
|
const unpaid = keyInvalid ? null : args.state.pausedDetail();
|
|
5625
5732
|
// MemTree can't be reached: nothing says the request can't fit, so no strike (2026-11-09).
|
|
5626
5733
|
const offline = keyInvalid || unpaid !== null ? undefined : unreachableNow(args.state, args.rec);
|
|
5734
|
+
const cause = keyInvalid || unpaid !== null || offline ? undefined
|
|
5735
|
+
: refusalCauseFor(args, inputTokens);
|
|
5627
5736
|
const failure = keyInvalid ? pausedRefusal(keyInvalidRefusalMessage(args.state.keyPageUrl))
|
|
5628
5737
|
: unpaid !== null ? pausedRefusal(pausedRefusalMessage(unpaid))
|
|
5629
5738
|
: offline ? unreachableRefusal(offline.message)
|
|
5630
5739
|
: args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true,
|
|
5631
|
-
indexSecondsLeft: activeIndexWaits.get(args.rec)
|
|
5740
|
+
indexSecondsLeft: activeIndexWaits.get(args.rec),
|
|
5741
|
+
refusalMessage: refusalMessage(cause, currentSessionLink(args.state, requestSessionId(args.req)), agentResultNote(args)) });
|
|
5632
5742
|
args.rec.forwardedBytes = 0;
|
|
5633
5743
|
args.rec.approxInputTokens = 0;
|
|
5634
5744
|
// Nothing went out uncompressed: Claude Code retries it (2026-10-28).
|
|
@@ -5655,6 +5765,7 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
5655
5765
|
budgetTokens, modelContextLimit: args.modelContextLimit, ...(unpaid !== null ? { paused: true } : {}),
|
|
5656
5766
|
...(keyInvalid ? { keyInvalid: true } : {}),
|
|
5657
5767
|
...(offline ? { unreachable: offline.what } : {}),
|
|
5768
|
+
...(cause && failure.status === 400 ? { cause: cause.kind } : {}),
|
|
5658
5769
|
};
|
|
5659
5770
|
if (unpaid !== null || keyInvalid)
|
|
5660
5771
|
cancelPausedNotice(args.state, args.rec);
|
|
@@ -5670,6 +5781,17 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
5670
5781
|
args.state.keyNotice.markShown();
|
|
5671
5782
|
return false;
|
|
5672
5783
|
}
|
|
5784
|
+
/**
|
|
5785
|
+
* A main-lane refusal while an agent lane of this session was recently active: a
|
|
5786
|
+
* background agent's finished result reaches the main session only as part of its
|
|
5787
|
+
* next turn, so say it may be waiting (2026-11-13: a refused main lane held one back).
|
|
5788
|
+
*/
|
|
5789
|
+
function agentResultNote(args) {
|
|
5790
|
+
// This session's own agent traffic only: activeSubagents is process-wide (review r1 P3).
|
|
5791
|
+
return args.rec.routeLane === "main" &&
|
|
5792
|
+
args.state.laneActivity.agentRecent(requestSessionId(args.req))
|
|
5793
|
+
? AGENT_RESULT_WAITING_LINE : undefined;
|
|
5794
|
+
}
|
|
5673
5795
|
/** Requests held back by a lane backoff whose attempt MemTree answered (unreachableNow). */
|
|
5674
5796
|
const answeredBackoffs = new WeakSet();
|
|
5675
5797
|
/**
|
|
@@ -5701,6 +5823,25 @@ function unreachableNow(state, rec) {
|
|
|
5701
5823
|
return { message: unreachableMessage(memtree, anthropic),
|
|
5702
5824
|
what: anthropic ? "internet" : "memtree" };
|
|
5703
5825
|
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Note this request's lane (lane-activity.ts) and, on a main-lane request of the current
|
|
5828
|
+
* prompt, queue the session's one heavy-request warning when an earlier upload passed the
|
|
5829
|
+
* size and no other notice waits. It is the lowest priority (otherNoticePending): any
|
|
5830
|
+
* other notice may replace it, which leaves it due for a later turn.
|
|
5831
|
+
*/
|
|
5832
|
+
function noteLaneActivity(state, rec, sessionId) {
|
|
5833
|
+
state.laneActivity.seen(sessionId, rec.routeLane);
|
|
5834
|
+
const heavy = state.laneActivity.heavyDue(sessionId);
|
|
5835
|
+
const prompt = noticeContexts.get(rec);
|
|
5836
|
+
if (!heavy || sessionId === undefined || rec.routeLane !== "main" || prompt === undefined ||
|
|
5837
|
+
prompt.generation !== state.mainPromptGeneration || state.notices.hasPending())
|
|
5838
|
+
return;
|
|
5839
|
+
state.heavyNotice = state.notices.queueSuffix(heavyRequestNoticeText(heavy), () => state.laneActivity.markWarned(sessionId), prompt.promptId);
|
|
5840
|
+
}
|
|
5841
|
+
/** Whether a notice other than the heavy-request one (the lowest priority) waits. */
|
|
5842
|
+
function otherNoticePending(state, promptId) {
|
|
5843
|
+
return state.notices.hasPending(promptId) && !state.notices.pendingIs(state.heavyNotice);
|
|
5844
|
+
}
|
|
5704
5845
|
/**
|
|
5705
5846
|
* A repeated retryable refusal on the main thread: say once per streak that
|
|
5706
5847
|
* MemTree is slow and Claude Code is retrying, unless another notice waits.
|
|
@@ -5713,7 +5854,7 @@ function noteSlowRetry(args, failure) {
|
|
|
5713
5854
|
const canShow = failure.status === 503 && rec.routeLane === "main" &&
|
|
5714
5855
|
rec.indexBehind !== true && prompt !== undefined &&
|
|
5715
5856
|
prompt.generation === state.mainPromptGeneration && !args.res.destroyed &&
|
|
5716
|
-
!state
|
|
5857
|
+
!otherNoticePending(state);
|
|
5717
5858
|
if (!state.refusalStreaks.refused(args.routeKey, Date.now(), canShow))
|
|
5718
5859
|
return;
|
|
5719
5860
|
state.notices.queueSuffix(SLOW_RETRY_NOTICE, undefined, prompt.promptId);
|
|
@@ -5764,7 +5905,7 @@ function warnPausedNearWindow(state, rec, sessionId) {
|
|
|
5764
5905
|
// Never replace another queued notice (or this one again): a later request tries.
|
|
5765
5906
|
if (detail === null || rec.routeLane !== "main" || sessionId === undefined ||
|
|
5766
5907
|
pausedNotices.has(rec) || nearWindowWarned(state, sessionId) ||
|
|
5767
|
-
|
|
5908
|
+
otherNoticePending(state, state.mainPromptId))
|
|
5768
5909
|
return;
|
|
5769
5910
|
state.nearWindowNotice = state.notices.queueSuffix(paymentNoticeText(detail, undefined, { overBudget: true }), () => {
|
|
5770
5911
|
state.paymentNotice.markShown();
|