claude-code-infinite 1.0.20 → 1.0.21
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 +91 -6
- package/dist/auto-update-runner.d.ts +52 -0
- package/dist/auto-update-runner.d.ts.map +1 -0
- package/dist/auto-update-runner.js +307 -0
- package/dist/auto-update-runner.js.map +1 -0
- package/dist/auto-update-setup.d.ts +38 -0
- package/dist/auto-update-setup.d.ts.map +1 -0
- package/dist/auto-update-setup.js +72 -0
- package/dist/auto-update-setup.js.map +1 -0
- package/dist/auto-update.d.ts +170 -0
- package/dist/auto-update.d.ts.map +1 -0
- package/dist/auto-update.js +401 -0
- package/dist/auto-update.js.map +1 -0
- package/dist/cli-main.js +22 -2
- package/dist/cli-main.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/count-tokens.d.ts +38 -3
- package/dist/count-tokens.d.ts.map +1 -1
- package/dist/count-tokens.js +88 -19
- package/dist/count-tokens.js.map +1 -1
- package/dist/memtree-finder.d.ts +11 -0
- package/dist/memtree-finder.d.ts.map +1 -1
- package/dist/memtree-finder.js +18 -0
- package/dist/memtree-finder.js.map +1 -1
- package/dist/memtree-mcp-config.d.ts.map +1 -1
- package/dist/memtree-mcp-config.js +4 -2
- package/dist/memtree-mcp-config.js.map +1 -1
- package/dist/memtree-mcp.d.ts +24 -4
- package/dist/memtree-mcp.d.ts.map +1 -1
- package/dist/memtree-mcp.js +47 -14
- package/dist/memtree-mcp.js.map +1 -1
- package/dist/memtree-tools.d.ts +17 -1
- package/dist/memtree-tools.d.ts.map +1 -1
- package/dist/memtree-tools.js +50 -2
- package/dist/memtree-tools.js.map +1 -1
- package/dist/memtree.d.ts +10 -1
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +54 -19
- package/dist/memtree.js.map +1 -1
- package/dist/notices.d.ts +6 -1
- package/dist/notices.d.ts.map +1 -1
- package/dist/notices.js +13 -1
- package/dist/notices.js.map +1 -1
- package/dist/proxy.d.ts +17 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +130 -47
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +10 -0
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js.map +1 -1
- package/dist/request-sizing.d.ts +21 -2
- package/dist/request-sizing.d.ts.map +1 -1
- package/dist/request-sizing.js +60 -7
- package/dist/request-sizing.js.map +1 -1
- package/dist/token-estimate.d.ts +35 -0
- package/dist/token-estimate.d.ts.map +1 -0
- package/dist/token-estimate.js +97 -0
- package/dist/token-estimate.js.map +1 -0
- package/dist/update-check.d.ts +4 -3
- package/dist/update-check.d.ts.map +1 -1
- package/dist/update-check.js +4 -3
- package/dist/update-check.js.map +1 -1
- package/dist/update-lock.d.ts +68 -0
- package/dist/update-lock.d.ts.map +1 -0
- package/dist/update-lock.js +176 -0
- package/dist/update-lock.js.map +1 -0
- package/package.json +1 -1
package/dist/proxy.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ import type { StablePrefixStore } from "./prefix-store.js";
|
|
|
47
47
|
import type { NewestTreeLookup } from "./memtree-newest.js";
|
|
48
48
|
import type { TranscriptUsageSource } from "./transcript-usage.js";
|
|
49
49
|
import type { ProjectMeta } from "./project-meta.js";
|
|
50
|
-
import { type RequestLogSink } from "./reqlog.js";
|
|
50
|
+
import { type MessagesRecord, type RequestLogSink } from "./reqlog.js";
|
|
51
51
|
export interface ProxyOptions {
|
|
52
52
|
memtree: MemtreeClient;
|
|
53
53
|
/**
|
|
@@ -171,6 +171,12 @@ export interface ProxyOptions {
|
|
|
171
171
|
* user can find sessions by project. Omitted means none is sent.
|
|
172
172
|
*/
|
|
173
173
|
projectMeta?: ProjectMeta;
|
|
174
|
+
/**
|
|
175
|
+
* An extra line for a main session's SessionStart (not compaction), e.g.
|
|
176
|
+
* the auto-update result (auto-update.ts). Called on every such hook; the
|
|
177
|
+
* provider decides how often it has something to say.
|
|
178
|
+
*/
|
|
179
|
+
sessionStartNotice?: () => string | undefined;
|
|
174
180
|
/** Test-only: forward to this origin instead of api.anthropic.com. */
|
|
175
181
|
upstreamOrigin?: string;
|
|
176
182
|
/** Test-only: dump each forwarded /v1/messages body to this directory. */
|
|
@@ -212,7 +218,17 @@ export declare const SUBAGENT_VIEW_NOTE = "(main session's page; subagent pages
|
|
|
212
218
|
export declare const SUBAGENT_COMPACT_REPLY = "MemTree compact applies to the main session only.";
|
|
213
219
|
/** "50k", "50000", "1.5m" → tokens; undefined when not a positive count. */
|
|
214
220
|
export declare function parseTokenCount(text: string): number | undefined;
|
|
221
|
+
/**
|
|
222
|
+
* Whether a forwarded compressed body still overflows the window: its exact
|
|
223
|
+
* count (`exactTokens`, made for delivery), else its reported input tokens,
|
|
224
|
+
* both Anthropic's own count, so the output floor applies; else its
|
|
225
|
+
* conservative image-aware bound (token-estimate.ts) with full max_tokens,
|
|
226
|
+
* never bytes/4, which reads base64 images as text.
|
|
227
|
+
*/
|
|
228
|
+
declare function compressedResultExceedsWindow(body: Record<string, any>, rec: MessagesRecord, compressedRaw: Buffer, modelContextLimit: number, exactTokens?: number): boolean;
|
|
215
229
|
/** Test seam. */
|
|
216
230
|
export declare const __testCapCacheBreakpoints: typeof capCacheBreakpoints;
|
|
217
231
|
export declare const __testEstimateRequestTokens: typeof estimateRequestTokens;
|
|
232
|
+
export declare const __testCompressedResultExceedsWindow: typeof compressedResultExceedsWindow;
|
|
233
|
+
export {};
|
|
218
234
|
//# sourceMappingURL=proxy.d.ts.map
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AASvD,OAAO,EAAiB,qBAAqB,EACD,MAAM,qBAAqB,CAAC;AAiBxE,OAAO,EAKL,aAAa,EAMd,MAAM,cAAc,CAAC;AA0CtB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACvF,OAAO,KAAK,EAAkB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAW5D,OAAO,KAAK,EAAgB,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AASvD,OAAO,EAAiB,qBAAqB,EACD,MAAM,qBAAqB,CAAC;AAiBxE,OAAO,EAKL,aAAa,EAMd,MAAM,cAAc,CAAC;AA0CtB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACvF,OAAO,KAAK,EAAkB,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAW5D,OAAO,KAAK,EAAgB,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAKL,KAAK,cAAc,EAEnB,KAAK,cAAc,EAGpB,MAAM,aAAa,CAAC;AAkCrB,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,aAAa,CAAC;IACvB;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,oFAAoF;IACpF,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B;;;;OAIG;IACH,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC9C,sEAAsE;IACtE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB;;;;OAIG;IACH,KAAK,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACjD;AAsOD;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAC;AA0OhD,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA0IpE;AAgRD,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOjE;AAu9CD,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAiDjD,eAAO,MAAM,kBAAkB,4DAA4D,CAAC;AAC5F,eAAO,MAAM,sBAAsB,sDAAsD,CAAC;AAsB1F,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMhE;AA4qCD;;;;;;GAMG;AACH,iBAAS,6BAA6B,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,GAAG,EAAE,cAAc,EACnB,aAAa,EAAE,MAAM,EACrB,iBAAiB,EAAE,MAAM,EACzB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAKT;AA09DD,iBAAiB;AACjB,eAAO,MAAM,yBAAyB,4BAAsB,CAAC;AAC7D,eAAO,MAAM,2BAA2B,8BAAwB,CAAC;AACjE,eAAO,MAAM,mCAAmC,sCAAgC,CAAC"}
|
package/dist/proxy.js
CHANGED
|
@@ -59,6 +59,7 @@ import { hyperlink } from "./payment-gate.js";
|
|
|
59
59
|
import { PaymentNoticeThrottle, paymentNoticeText } from "./payment-notice.js";
|
|
60
60
|
import { MEMTREE_LINKS_MAX_SESSIONS } from "./memtree-links.js";
|
|
61
61
|
import { describeClaudeCodeRequest, inspectMonitorTranscript, knownSideRequestKind, transcriptWarning, isClaudeCodeSideRequest, memtreeClientMeta, sessionTag, } from "./cc-request.js";
|
|
62
|
+
import { conservativeBodyTokens } from "./token-estimate.js";
|
|
62
63
|
import { approxTokensFromBytes, mergeUsageFromJsonBody, mergeUsageFromSseEvent, } from "./reqlog.js";
|
|
63
64
|
const DEFAULT_UPSTREAM = "https://api.anthropic.com";
|
|
64
65
|
const HOOK_BODY_LIMIT = 64 * 1024;
|
|
@@ -267,6 +268,7 @@ export function startProxy(opts) {
|
|
|
267
268
|
paymentNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
|
|
268
269
|
unpaidDetail: () => opts.memtree.paymentRequiredDetail,
|
|
269
270
|
usageUrl: opts.memtree.usageDashboardUrl,
|
|
271
|
+
sessionStartNotice: opts.sessionStartNotice,
|
|
270
272
|
notices: new NoticeDeliveryQueue(),
|
|
271
273
|
mainNoticePending: false,
|
|
272
274
|
mainPromptGeneration: 0,
|
|
@@ -657,7 +659,7 @@ onMainStop) {
|
|
|
657
659
|
if (parsed.agent_id === undefined && parsed.source === "resume") {
|
|
658
660
|
await state.newestTrees?.settle(parsed.session_id);
|
|
659
661
|
}
|
|
660
|
-
const line = joinNoticeLines(resumeLinkLine(state, parsed), resumePaymentNotice(state, parsed));
|
|
662
|
+
const line = joinNoticeLines(resumeLinkLine(state, parsed), resumePaymentNotice(state, parsed), extraSessionStartLine(state.sessionStartNotice, parsed));
|
|
661
663
|
if (!line) {
|
|
662
664
|
res.writeHead(204);
|
|
663
665
|
res.end();
|
|
@@ -950,10 +952,11 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
950
952
|
upstream, headers: forwardableRequestHeaders(req), requestUrl: req.url,
|
|
951
953
|
signal: state.shutdownSignal,
|
|
952
954
|
onAttempt: (result) => {
|
|
953
|
-
(rec.countTokens ??= []).push({ statusClass: result.outcome, ms: result.ms
|
|
955
|
+
(rec.countTokens ??= []).push({ statusClass: result.outcome, ms: result.ms,
|
|
956
|
+
bytes: result.bytes ?? 0, timeoutMs: result.timeoutMs ?? 0 });
|
|
954
957
|
},
|
|
955
958
|
}, state.countTokensCooldowns);
|
|
956
|
-
const requestSizing = new RequestSizing(countSession, opts.countTokens === true);
|
|
959
|
+
const requestSizing = new RequestSizing(countSession, opts.countTokens === true, typeof body.model === "string" ? body.model : undefined);
|
|
957
960
|
if (carriesServerTools(body))
|
|
958
961
|
serverToolRecords.add(rec);
|
|
959
962
|
const sizeSlot = wholeRequestSizeSlot(state, isMainRequest, requestSessionKey, requestSizeKey, body.model);
|
|
@@ -1170,7 +1173,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1170
1173
|
// prefix, else whole) with no compress call; at it, compress once to
|
|
1171
1174
|
// the target (planToolCompaction). A "replay" is a client retry of the
|
|
1172
1175
|
// request that installed the route, forwarded verbatim.
|
|
1173
|
-
const plan = planToolCompaction({
|
|
1176
|
+
const plan = await planToolCompaction({
|
|
1174
1177
|
opts,
|
|
1175
1178
|
state,
|
|
1176
1179
|
body,
|
|
@@ -1207,11 +1210,15 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1207
1210
|
// A concurrent request of this lane is already compressing.
|
|
1208
1211
|
rec.routeRecovery = { outcome: "in-flight" };
|
|
1209
1212
|
}
|
|
1210
|
-
else if (prior?.awaitingIndex && !plan.overWindow) {
|
|
1213
|
+
else if (prior?.awaitingIndex && (!plan.overWindow || plan.uncountedOverWindowOnly)) {
|
|
1211
1214
|
// The last attempt found no tree for this conversation, so a
|
|
1212
1215
|
// compress call could only pass everything through again. Forward
|
|
1213
1216
|
// now; the page check lets the next tool turn try once it exists.
|
|
1217
|
+
// Over the window only by its uncounted bound, delivery may still
|
|
1218
|
+
// count it; if it refuses, that is an index wait, not a strike.
|
|
1214
1219
|
rec.routeRecovery = { outcome: "awaiting-index" };
|
|
1220
|
+
if (plan.uncountedOverWindowOnly)
|
|
1221
|
+
rec.indexBehind = true;
|
|
1215
1222
|
checkAwaitedIndex(opts, prior, state.shutdownSignal);
|
|
1216
1223
|
}
|
|
1217
1224
|
else if (prior?.retryAtTokens !== undefined &&
|
|
@@ -1350,7 +1357,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1350
1357
|
// Stable-prefix (edge) compaction: the main thread's human turns only. The
|
|
1351
1358
|
// recap rides the main route above; subagents keep per-turn compression.
|
|
1352
1359
|
const edge = isMainRequest && sessionId !== undefined
|
|
1353
|
-
? planEdgeCompaction({
|
|
1360
|
+
? await planEdgeCompaction({
|
|
1354
1361
|
sizing: requestSizing,
|
|
1355
1362
|
opts,
|
|
1356
1363
|
state,
|
|
@@ -1877,6 +1884,17 @@ function resumePaymentNotice(state, input) {
|
|
|
1877
1884
|
state.paymentNotice.markShown();
|
|
1878
1885
|
return state.notices.warningText(paymentNoticeText(detail));
|
|
1879
1886
|
}
|
|
1887
|
+
/** The caller's extra SessionStart line; a throwing provider shows nothing. */
|
|
1888
|
+
function extraSessionStartLine(provider, input) {
|
|
1889
|
+
if (!provider || input.agent_id !== undefined || input.source === "compact")
|
|
1890
|
+
return undefined;
|
|
1891
|
+
try {
|
|
1892
|
+
return provider();
|
|
1893
|
+
}
|
|
1894
|
+
catch {
|
|
1895
|
+
return undefined;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1880
1898
|
function joinNoticeLines(...parts) {
|
|
1881
1899
|
const present = parts.filter((part) => !!part);
|
|
1882
1900
|
return present.length ? present.join("\n") : undefined;
|
|
@@ -2442,10 +2460,11 @@ function laneCompaction(opts, state, sessionId, model, modelContextLimit) {
|
|
|
2442
2460
|
}
|
|
2443
2461
|
/** Calibrated input must leave room for the requested output as well. */
|
|
2444
2462
|
function calibratedSizeExceedsWindow(record, body, modelContextLimit) {
|
|
2445
|
-
//
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2463
|
+
// An exact count reserves the model's output floor, as delivery does; an
|
|
2464
|
+
// estimate reserves the full max_tokens.
|
|
2465
|
+
const outputTokens = bodyOutputReservation(body, exactSizedRecords.has(record));
|
|
2466
|
+
return record.uncountedOverWindow === true || (record.sizeSource === "reported" &&
|
|
2467
|
+
(record.estimatedTokens ?? 0) + outputTokens > modelContextLimit);
|
|
2449
2468
|
}
|
|
2450
2469
|
function calibratedSizeExceedsLimits(record, body, modelContextLimit) {
|
|
2451
2470
|
return (record.sizeSource === "reported" &&
|
|
@@ -2515,16 +2534,24 @@ function usedServerTools(rec) {
|
|
|
2515
2534
|
* is dropped and the previous sample kept.
|
|
2516
2535
|
*/
|
|
2517
2536
|
function reportedSample(rec, bytes) {
|
|
2518
|
-
|
|
2519
|
-
return undefined;
|
|
2520
|
-
if (serverToolRecords.has(rec) || usedServerTools(rec))
|
|
2521
|
-
return undefined;
|
|
2522
|
-
const tokens = reportedInputTokens(rec);
|
|
2537
|
+
const tokens = reportedBodyTokens(rec);
|
|
2523
2538
|
if (tokens === undefined)
|
|
2524
2539
|
return undefined;
|
|
2525
2540
|
const sample = { tokens, forwardedBytes: bytes };
|
|
2526
2541
|
return plausibleSample(sample) ? sample : undefined;
|
|
2527
2542
|
}
|
|
2543
|
+
/**
|
|
2544
|
+
* Anthropic's input tokens for the body this request forwarded, when its usage
|
|
2545
|
+
* describes that body (not a changed delivery, no server tools). Not filtered
|
|
2546
|
+
* by plausibility: a body that is mostly images is truly cheap per byte.
|
|
2547
|
+
*/
|
|
2548
|
+
function reportedBodyTokens(rec) {
|
|
2549
|
+
if (changedDeliveryRecords.has(rec))
|
|
2550
|
+
return undefined;
|
|
2551
|
+
if (serverToolRecords.has(rec) || usedServerTools(rec))
|
|
2552
|
+
return undefined;
|
|
2553
|
+
return reportedInputTokens(rec);
|
|
2554
|
+
}
|
|
2528
2555
|
/**
|
|
2529
2556
|
* Key of a non-main request's size sample (ProxyState.laneSizes): its lane,
|
|
2530
2557
|
* its own agent id (helpers that carry only a parent id stay apart from the
|
|
@@ -2592,7 +2619,7 @@ function boundedSet(map, key, value, limit = STABLE_PREFIX_MAX_SESSIONS) {
|
|
|
2592
2619
|
*
|
|
2593
2620
|
* Fills rec.compaction with the budget, estimate and reason.
|
|
2594
2621
|
*/
|
|
2595
|
-
function planEdgeCompaction(args) {
|
|
2622
|
+
async function planEdgeCompaction(args) {
|
|
2596
2623
|
const { opts, state, body, messages, sessionId, modelContextLimit, forwardBody, rec } = args;
|
|
2597
2624
|
const mode = compactionMode(opts, state, sessionId);
|
|
2598
2625
|
const budget = resolveBudget(opts, state, body.model, modelContextLimit);
|
|
@@ -2644,12 +2671,16 @@ function planEdgeCompaction(args) {
|
|
|
2644
2671
|
}
|
|
2645
2672
|
const prefixCandidate = args.sizing.register(routed.raw, "ride", prefix.lastSize);
|
|
2646
2673
|
candidateSizeOwners.set(prefixCandidate, prefix);
|
|
2674
|
+
await countIfOverBound(args.sizing, prefixCandidate, body, modelContextLimit);
|
|
2647
2675
|
const size = args.sizing.plan(prefixCandidate);
|
|
2648
2676
|
compaction.estimatedTokens = size.tokens;
|
|
2649
2677
|
compaction.estimatedBytes = routed.raw.length;
|
|
2650
2678
|
compaction.sizeSource = size.source;
|
|
2651
|
-
|
|
2652
|
-
|
|
2679
|
+
noteUncountedBound(compaction, args.sizing, prefixCandidate, body, modelContextLimit);
|
|
2680
|
+
// Request-local sizing (an exact count, a calibrated sample, or the
|
|
2681
|
+
// image-aware bound), not bytes: a flatten that kept images is mostly
|
|
2682
|
+
// base64 that costs a fixed amount per image.
|
|
2683
|
+
const overWindow = calibratedSizeExceedsWindow(compaction, body, modelContextLimit);
|
|
2653
2684
|
const fallback = overWindow ? undefined : { prefix, routed };
|
|
2654
2685
|
if (state.compactNow.has(sessionId))
|
|
2655
2686
|
return forced("manual", fallback);
|
|
@@ -2663,10 +2694,13 @@ function planEdgeCompaction(args) {
|
|
|
2663
2694
|
}
|
|
2664
2695
|
if (state.compactNow.has(sessionId))
|
|
2665
2696
|
return forced("manual");
|
|
2666
|
-
const
|
|
2697
|
+
const originalCandidate = args.sizing.register(forwardBody, "original");
|
|
2698
|
+
await countIfOverBound(args.sizing, originalCandidate, body, modelContextLimit);
|
|
2699
|
+
const size = args.sizing.plan(originalCandidate);
|
|
2667
2700
|
compaction.estimatedTokens = size.tokens;
|
|
2668
2701
|
compaction.estimatedBytes = forwardBody.length;
|
|
2669
2702
|
compaction.sizeSource = size.source;
|
|
2703
|
+
noteUncountedBound(compaction, args.sizing, originalCandidate, body, modelContextLimit);
|
|
2670
2704
|
if (calibratedSizeExceedsLimits(compaction, body, modelContextLimit)) {
|
|
2671
2705
|
return forced(compaction.prefixMiss ? "prefix-mismatch" : "budget");
|
|
2672
2706
|
}
|
|
@@ -2697,7 +2731,7 @@ function planEdgeCompaction(args) {
|
|
|
2697
2731
|
*
|
|
2698
2732
|
* Fills rec.compaction.
|
|
2699
2733
|
*/
|
|
2700
|
-
function planToolCompaction(args) {
|
|
2734
|
+
async function planToolCompaction(args) {
|
|
2701
2735
|
const { opts, state, body, messages, sessionId, modelContextLimit, forwardBody, rec } = args;
|
|
2702
2736
|
const mode = compactionMode(opts, state, sessionId);
|
|
2703
2737
|
const budget = resolveBudget(opts, state, body.model, modelContextLimit);
|
|
@@ -2714,23 +2748,16 @@ function planToolCompaction(args) {
|
|
|
2714
2748
|
const stableLane = args.isMainRequest && sessionId !== undefined;
|
|
2715
2749
|
const current = stableLane ? state.stablePrefixes.get(sessionId) : undefined;
|
|
2716
2750
|
let ride = args.ride;
|
|
2717
|
-
let overWindow = false;
|
|
2718
2751
|
if (!ride && current && !prefixMismatch(body, messages, current, sessionId, stablePrefixMessageHash)) {
|
|
2719
2752
|
const routed = prefixRoutedBody(body, messages, current);
|
|
2720
2753
|
if (routed) {
|
|
2721
2754
|
const prefixCandidate = args.sizing.register(routed.raw, "ride", current.lastSize);
|
|
2722
2755
|
candidateSizeOwners.set(prefixCandidate, current);
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
ride = { raw: routed.raw, turnType: "tool-prefix", sizeHolder: current };
|
|
2756
|
+
// Sized below with the request-local sizing; an over-window ride is not
|
|
2757
|
+
// kept as a fallback.
|
|
2758
|
+
ride = { raw: routed.raw, turnType: "tool-prefix", sizeHolder: current };
|
|
2727
2759
|
}
|
|
2728
2760
|
}
|
|
2729
|
-
if (!ride) {
|
|
2730
|
-
const original = args.sizing.register(forwardBody, "original");
|
|
2731
|
-
overWindow ||= args.sizing.plan(original).tokens +
|
|
2732
|
-
bodyOutputReservation(body, args.sizing.isExact(original)) > modelContextLimit;
|
|
2733
|
-
}
|
|
2734
2761
|
const sample = ride
|
|
2735
2762
|
? ride.sizeHolder.lastSize
|
|
2736
2763
|
: stableLane
|
|
@@ -2739,12 +2766,18 @@ function planToolCompaction(args) {
|
|
|
2739
2766
|
const candidate = args.sizing.register(ride?.raw ?? forwardBody, ride ? "ride" : "original", sample);
|
|
2740
2767
|
if (ride)
|
|
2741
2768
|
candidateSizeOwners.set(candidate, ride.sizeHolder);
|
|
2769
|
+
await countIfOverBound(args.sizing, candidate, body, modelContextLimit);
|
|
2742
2770
|
const size = args.sizing.plan(candidate);
|
|
2743
2771
|
compaction.estimatedTokens = size.tokens;
|
|
2744
2772
|
compaction.estimatedBytes = (ride?.raw ?? forwardBody).length;
|
|
2745
2773
|
compaction.sizeSource = size.source;
|
|
2746
|
-
|
|
2774
|
+
noteUncountedBound(compaction, args.sizing, candidate, body, modelContextLimit);
|
|
2775
|
+
// Judged after any planning count, so a fitting exact count is not
|
|
2776
|
+
// overridden by the estimate it replaced.
|
|
2777
|
+
let overWindow = !fitsNative(size.tokens, { budget: budget.tokens, context: modelContextLimit,
|
|
2747
2778
|
output: bodyOutputReservation(body, args.sizing.isExact(candidate)) });
|
|
2779
|
+
const uncountedOnly = !overWindow && compaction.uncountedOverWindow === true;
|
|
2780
|
+
overWindow ||= uncountedOnly;
|
|
2748
2781
|
if (!args.canCompress) {
|
|
2749
2782
|
if (overWindow)
|
|
2750
2783
|
return { kind: "pass", original: true };
|
|
@@ -2762,6 +2795,7 @@ function planToolCompaction(args) {
|
|
|
2762
2795
|
estimateTokens: size.tokens,
|
|
2763
2796
|
budgetTokens: budget.tokens,
|
|
2764
2797
|
overWindow,
|
|
2798
|
+
...(uncountedOnly ? { uncountedOverWindowOnly: true } : {}),
|
|
2765
2799
|
...(ride && !overWindow ? { fallback: ride } : {}),
|
|
2766
2800
|
...(stableLane ? { replaces: current ?? null } : {}),
|
|
2767
2801
|
};
|
|
@@ -2806,12 +2840,46 @@ function memoryRoutedToolBody(body, messages, route, routeEpoch, sessionId) {
|
|
|
2806
2840
|
return null;
|
|
2807
2841
|
return Buffer.from(JSON.stringify(routed), "utf-8");
|
|
2808
2842
|
}
|
|
2809
|
-
/**
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2843
|
+
/**
|
|
2844
|
+
* Count the candidate a planner is about to judge (ride or original, sampled
|
|
2845
|
+
* or not) when only a count could admit it: no exact count, and its per-model
|
|
2846
|
+
* conservative bound plus max_tokens exceeds the window. Delivery reuses the
|
|
2847
|
+
* result.
|
|
2848
|
+
*/
|
|
2849
|
+
async function countIfOverBound(sizing, candidate, body, modelContextLimit) {
|
|
2850
|
+
await sizing.countIfOverBound(candidate, { budget: modelContextLimit, context: modelContextLimit, output: bodyOutputReservation(body, false) });
|
|
2851
|
+
}
|
|
2852
|
+
/** Compaction records whose estimatedTokens is an exact count (noteUncountedBound). */
|
|
2853
|
+
const exactSizedRecords = new WeakSet();
|
|
2854
|
+
/**
|
|
2855
|
+
* Note an exactly counted candidate (exactSizedRecords), or mark a planned
|
|
2856
|
+
* candidate still without an exact count (counting off, or its
|
|
2857
|
+
* planning count failed: countIfOverBound ran first) whose conservative bound
|
|
2858
|
+
* plus max_tokens exceeds the window: delivery would refuse it uncounted, so
|
|
2859
|
+
* it is over the window for planning and takes the normal gated compression.
|
|
2860
|
+
*/
|
|
2861
|
+
function noteUncountedBound(record, sizing, candidate, body, modelContextLimit) {
|
|
2862
|
+
if (sizing.isExact(candidate)) {
|
|
2863
|
+
exactSizedRecords.add(record);
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
if (sizing.exceedsRequestLimit(candidate) ||
|
|
2867
|
+
candidate.conservativeTokens + bodyOutputReservation(body, false) > modelContextLimit) {
|
|
2868
|
+
record.uncountedOverWindow = true;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2872
|
+
* Whether a forwarded compressed body still overflows the window: its exact
|
|
2873
|
+
* count (`exactTokens`, made for delivery), else its reported input tokens,
|
|
2874
|
+
* both Anthropic's own count, so the output floor applies; else its
|
|
2875
|
+
* conservative image-aware bound (token-estimate.ts) with full max_tokens,
|
|
2876
|
+
* never bytes/4, which reads base64 images as text.
|
|
2877
|
+
*/
|
|
2878
|
+
function compressedResultExceedsWindow(body, rec, compressedRaw, modelContextLimit, exactTokens) {
|
|
2879
|
+
const counted = exactTokens ?? reportedBodyTokens(rec);
|
|
2880
|
+
const tokens = counted ??
|
|
2881
|
+
conservativeBodyTokens(compressedRaw, typeof body.model === "string" ? body.model : undefined);
|
|
2882
|
+
return tokens + bodyOutputReservation(body, counted !== undefined) > modelContextLimit;
|
|
2815
2883
|
}
|
|
2816
2884
|
/** Output room a native-fit check reserves: the model's floor for an exact
|
|
2817
2885
|
* count, else (an estimate) the full max_tokens. */
|
|
@@ -2998,12 +3066,18 @@ function withFlattenCacheBreakpoint(body, ttl) {
|
|
|
2998
3066
|
if (messages.length !== 1)
|
|
2999
3067
|
return messages;
|
|
3000
3068
|
const only = messages[0];
|
|
3001
|
-
const text = typeof only.content === "string" ? only.content : undefined;
|
|
3002
|
-
if (text === undefined)
|
|
3003
|
-
return messages;
|
|
3004
3069
|
const cacheControl = { type: "ephemeral" };
|
|
3005
3070
|
if (ttl !== undefined)
|
|
3006
3071
|
cacheControl.ttl = ttl;
|
|
3072
|
+
if (Array.isArray(only.content) && only.content.length > 0) {
|
|
3073
|
+
// A flatten that kept images: the breakpoint goes on its last block.
|
|
3074
|
+
const blocks = only.content.map((block) => ({ ...block }));
|
|
3075
|
+
blocks[blocks.length - 1].cache_control = cacheControl;
|
|
3076
|
+
return [{ ...only, content: blocks }];
|
|
3077
|
+
}
|
|
3078
|
+
const text = typeof only.content === "string" ? only.content : undefined;
|
|
3079
|
+
if (text === undefined)
|
|
3080
|
+
return messages;
|
|
3007
3081
|
return [{ ...only, content: [{ type: "text", text, cache_control: cacheControl }] }];
|
|
3008
3082
|
}
|
|
3009
3083
|
/**
|
|
@@ -3147,7 +3221,9 @@ async function runBlockingCompression(args) {
|
|
|
3147
3221
|
// whatever the fallback gate would refuse to send uncompressed; a bytes/4
|
|
3148
3222
|
// guess is not sent, as it would force needless compression.
|
|
3149
3223
|
...(args.inputCandidate && args.sizing.clientInputTokens(args.inputCandidate) !== undefined
|
|
3150
|
-
? { clientInputTokens: args.sizing.clientInputTokens(args.inputCandidate)
|
|
3224
|
+
? { clientInputTokens: args.sizing.clientInputTokens(args.inputCandidate),
|
|
3225
|
+
// Only an exact count of these bytes may loosen the server's sizing.
|
|
3226
|
+
clientInputTokensExact: args.sizing.isExact(args.inputCandidate) }
|
|
3151
3227
|
: {}),
|
|
3152
3228
|
...(messageUsage && Object.keys(messageUsage).length ? { messageUsage } : {}),
|
|
3153
3229
|
...(messageTimes && Object.keys(messageTimes).length ? { messageTimes } : {}),
|
|
@@ -3592,8 +3668,11 @@ async function recoverToolRouteMiss(args) {
|
|
|
3592
3668
|
// Nothing to ride next turn, or a result that still does not fit the
|
|
3593
3669
|
// window (the next turn would reject it and compress again): wait for
|
|
3594
3670
|
// growth before the lane tries again.
|
|
3595
|
-
|
|
3671
|
+
const exactTokens = args.sizing.exactTokensFor(compressedRaw);
|
|
3672
|
+
if (!sizeHolder ||
|
|
3673
|
+
compressedResultExceedsWindow(body, rec, compressedRaw, modelContextLimit, exactTokens)) {
|
|
3596
3674
|
backOff();
|
|
3675
|
+
}
|
|
3597
3676
|
// A reservation that lost its race (stale) or never reached
|
|
3598
3677
|
// protocol-complete (upstream 5xx, truncated stream) installed nothing, so
|
|
3599
3678
|
// it must stop suppressing whoever is still trying to install.
|
|
@@ -3766,13 +3845,16 @@ async function selectForwardCandidate(args) {
|
|
|
3766
3845
|
return changed ? candidate.body : intended;
|
|
3767
3846
|
}
|
|
3768
3847
|
/**
|
|
3769
|
-
* Whether delivery would refuse `candidate` sent whole
|
|
3770
|
-
*
|
|
3771
|
-
*
|
|
3772
|
-
*
|
|
3848
|
+
* Whether delivery would refuse `candidate` sent whole, by delivery's own
|
|
3849
|
+
* admission: it is sendable only when its plan fits and either an exact count
|
|
3850
|
+
* or its conservative per-model bound (with the full max_tokens) fits too;
|
|
3851
|
+
* otherwise the measurement delivery would use (a count, else that bound)
|
|
3852
|
+
* decides. Only when it is refused is there no fallback, and only then may a
|
|
3853
|
+
* compress take the long budget.
|
|
3773
3854
|
*/
|
|
3774
3855
|
async function wholeRequestRefused(sizing, candidate, limits) {
|
|
3775
|
-
if (fitsNative(sizing.plan(candidate).tokens, limits)
|
|
3856
|
+
if (fitsNative(sizing.plan(candidate).tokens, limits) &&
|
|
3857
|
+
(sizing.isExact(candidate) || sizing.boundFits(candidate, limits)))
|
|
3776
3858
|
return false;
|
|
3777
3859
|
return !fitsMeasured(await sizing.measure(candidate, limits), limits);
|
|
3778
3860
|
}
|
|
@@ -4529,6 +4611,7 @@ function readAll(stream) {
|
|
|
4529
4611
|
/** Test seam. */
|
|
4530
4612
|
export const __testCapCacheBreakpoints = capCacheBreakpoints;
|
|
4531
4613
|
export const __testEstimateRequestTokens = estimateRequestTokens;
|
|
4614
|
+
export const __testCompressedResultExceedsWindow = compressedResultExceedsWindow;
|
|
4532
4615
|
/** Reads message times from the session's transcript, when there is one. */
|
|
4533
4616
|
function transcriptTimesFor(opts, sessionId, agentId) {
|
|
4534
4617
|
const source = opts.transcriptUsage;
|