claude-code-infinite 1.0.17 → 1.0.18
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 +9 -1
- package/dist/cli-main.js +4 -0
- package/dist/cli-main.js.map +1 -1
- package/dist/memtree.d.ts +45 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +96 -11
- package/dist/memtree.js.map +1 -1
- package/dist/prefix-store.d.ts +59 -0
- package/dist/prefix-store.d.ts.map +1 -0
- package/dist/prefix-store.js +330 -0
- package/dist/prefix-store.js.map +1 -0
- package/dist/proxy.d.ts +7 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +155 -15
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +8 -0
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js +11 -0
- package/dist/reqlog.js.map +1 -1
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -70,6 +70,8 @@ const HOOK_BODY_LIMIT = 64 * 1024;
|
|
|
70
70
|
// Long enough that a real outage costs one stall, short enough that a
|
|
71
71
|
// transient blip does not disable recovery for a working session.
|
|
72
72
|
const TOOL_RECOVERY_FAILURE_COOLDOWN_MS = 60_000;
|
|
73
|
+
/** Safety cap on a long-budget compress, should a transport ignore its abort. */
|
|
74
|
+
const MAX_LONG_COMPRESS_WAIT_MS = 10 * 60_000;
|
|
73
75
|
const SKIP_REQUEST_HEADERS = new Set([
|
|
74
76
|
"host",
|
|
75
77
|
"connection",
|
|
@@ -282,6 +284,10 @@ export function startProxy(opts) {
|
|
|
282
284
|
memorySourcePages: new Map(),
|
|
283
285
|
memtreeCallSeq: 0,
|
|
284
286
|
memtreeLinkStore: opts.memtreeLinkStore,
|
|
287
|
+
stablePrefixStore: opts.stablePrefixStore,
|
|
288
|
+
storedPrefixLookups: new Map(),
|
|
289
|
+
storageOwner: typeof opts.memtree.storageIdentity === "string"
|
|
290
|
+
? opts.memtree.storageIdentity : undefined,
|
|
285
291
|
newestTrees: opts.newestTrees,
|
|
286
292
|
memtreeLinkPlacement: opts.memtreeLinkPlacement ?? "turn",
|
|
287
293
|
compactTargets: new Map(),
|
|
@@ -945,7 +951,9 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
945
951
|
},
|
|
946
952
|
}, state.countTokensCooldowns);
|
|
947
953
|
const requestSizing = new RequestSizing(countSession, opts.countTokens === true);
|
|
948
|
-
|
|
954
|
+
if (carriesServerTools(body))
|
|
955
|
+
serverToolRecords.add(rec);
|
|
956
|
+
const sizeSlot = wholeRequestSizeSlot(state, isMainRequest, requestSessionKey, requestSizeKey, body.model);
|
|
949
957
|
const originalCandidate = requestSizing.register(rawBody, "original", sizeSlot.get());
|
|
950
958
|
const forwardCandidate = requestSizing.register(forwardBody, "original", sizeSlot.get());
|
|
951
959
|
candidateSizeOwners.set(originalCandidate, sizeSlot);
|
|
@@ -956,6 +964,9 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
956
964
|
output: bodyOutputReservation(body),
|
|
957
965
|
recountOutput: outputReservation(undefined, body.max_tokens), // full max_tokens
|
|
958
966
|
});
|
|
967
|
+
if (isMainRequest && requestSessionKey !== undefined) {
|
|
968
|
+
adoptStoredPrefix(opts, state, rec, requestSessionKey, body, messages, modelContextLimit);
|
|
969
|
+
}
|
|
959
970
|
const rides = state.memoryRoutes.has(requestRouteKey) ||
|
|
960
971
|
(isMainRequest && requestSessionKey !== undefined && state.stablePrefixes.has(requestSessionKey));
|
|
961
972
|
if (!rides) {
|
|
@@ -1263,6 +1274,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1263
1274
|
...(plan.fallback ? { fallback: plan.fallback } : {}),
|
|
1264
1275
|
retryAtTokens: plan.estimateTokens +
|
|
1265
1276
|
Math.floor(plan.budgetTokens * TOOL_COMPACTION_RETRY_BUDGET_RATIO),
|
|
1277
|
+
...(plan.fallback ? {} : { wholeRefused: () => wholeRequestRefused(requestSizing, originalCandidate, sizingLimits()) }),
|
|
1266
1278
|
}).finally(() => {
|
|
1267
1279
|
recoveryAttempt.inFlight = false;
|
|
1268
1280
|
}));
|
|
@@ -1289,7 +1301,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1289
1301
|
if (toolRide)
|
|
1290
1302
|
noteRideSize(toolRide.sizeHolder, rec, sendBody.length);
|
|
1291
1303
|
else {
|
|
1292
|
-
noteWholeRequestSize(state, isMainRequest, toolSessionId, requestSizeKey, rec, sendBody.length);
|
|
1304
|
+
noteWholeRequestSize(state, isMainRequest, toolSessionId, requestSizeKey, body.model, rec, sendBody.length);
|
|
1293
1305
|
}
|
|
1294
1306
|
}));
|
|
1295
1307
|
}
|
|
@@ -1528,7 +1540,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1528
1540
|
const sendingWhole = () => { requestSendPolicies.get(req).failedRebuild = true; };
|
|
1529
1541
|
const noteWholeSize = () => {
|
|
1530
1542
|
if (isMainRequest)
|
|
1531
|
-
notePassthroughSize(state, sessionId, rec, forwardBody.length);
|
|
1543
|
+
notePassthroughSize(state, sessionId, body.model, rec, forwardBody.length);
|
|
1532
1544
|
};
|
|
1533
1545
|
// The complete request upload can outlive its downstream subscriber while
|
|
1534
1546
|
// MemTree compression is in flight. Track that subscriber locally without
|
|
@@ -1563,6 +1575,10 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1563
1575
|
: edge?.kind === "off"
|
|
1564
1576
|
? { compaction: {} }
|
|
1565
1577
|
: {}),
|
|
1578
|
+
// Riding the old prefix, or sending a raw request that fits, keeps the
|
|
1579
|
+
// short budget; with neither, a timeout could only end in a refusal.
|
|
1580
|
+
longBudget: !(edge?.kind === "compress" && edge.fallback) &&
|
|
1581
|
+
await wholeRequestRefused(requestSizing, forwardCandidate, sizingLimits()),
|
|
1566
1582
|
});
|
|
1567
1583
|
}
|
|
1568
1584
|
catch {
|
|
@@ -1575,7 +1591,7 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1575
1591
|
return logged(forwardRaw(req, res, rawBody, opts, upstream, state.shutdownSignal, rec)
|
|
1576
1592
|
.then((delivered) => {
|
|
1577
1593
|
if (isMainRequest)
|
|
1578
|
-
notePassthroughSize(state, sessionId, rec, rawBody.length);
|
|
1594
|
+
notePassthroughSize(state, sessionId, body.model, rec, rawBody.length);
|
|
1579
1595
|
if (delivered)
|
|
1580
1596
|
markMainPromptDelivered();
|
|
1581
1597
|
}));
|
|
@@ -1880,6 +1896,9 @@ function sessionCommandReply(state, sessionId, prompt) {
|
|
|
1880
1896
|
state.memoryRoutes.delete(JSON.stringify([sessionId, "main"]));
|
|
1881
1897
|
state.compactNow.delete(sessionId);
|
|
1882
1898
|
state.stablePrefixes.delete(sessionId);
|
|
1899
|
+
if (state.storageOwner)
|
|
1900
|
+
state.stablePrefixStore?.delete(sessionId, state.storageOwner);
|
|
1901
|
+
state.storedPrefixLookups.set(sessionId, null);
|
|
1883
1902
|
return `${TRAILER_LABEL} compaction off: the conversation is sent whole, and MemTree compresses only when it outgrows the model's budget.`;
|
|
1884
1903
|
}
|
|
1885
1904
|
if (args === "") {
|
|
@@ -2151,6 +2170,9 @@ stable) {
|
|
|
2151
2170
|
// request carried, and the compressed bytes sent for them. Later requests
|
|
2152
2171
|
// reuse these bytes unchanged, so the prefix stays a cache read.
|
|
2153
2172
|
route.stablePrefix = storeStablePrefix(state, sessionId, originalMessages, route, stable);
|
|
2173
|
+
if (route.stablePrefix && key === JSON.stringify([sessionId, "main"])) {
|
|
2174
|
+
persistStablePrefix(state, sessionId, route.stablePrefix);
|
|
2175
|
+
}
|
|
2154
2176
|
}
|
|
2155
2177
|
setMemoryRoute(state, key, route);
|
|
2156
2178
|
state.routeDecisionsLive.set(key, new Set([decisionGeneration]));
|
|
@@ -2186,6 +2208,83 @@ function storeStablePrefix(state, sessionId, originalMessages, compressed, stabl
|
|
|
2186
2208
|
state.compactNow.delete(sessionId);
|
|
2187
2209
|
return prefix;
|
|
2188
2210
|
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Persist the main lane's newly adopted prefix with its memory-source page.
|
|
2213
|
+
* Deferred one tick so the page noted right after the install is included,
|
|
2214
|
+
* and skipped when a newer prefix replaced this one meanwhile.
|
|
2215
|
+
*/
|
|
2216
|
+
function persistStablePrefix(state, sessionId, prefix) {
|
|
2217
|
+
const store = state.stablePrefixStore;
|
|
2218
|
+
const owner = state.storageOwner;
|
|
2219
|
+
if (!store || !owner)
|
|
2220
|
+
return;
|
|
2221
|
+
state.storedPrefixLookups.set(sessionId, null);
|
|
2222
|
+
setImmediate(() => {
|
|
2223
|
+
if (state.stablePrefixes.get(sessionId) !== prefix)
|
|
2224
|
+
return;
|
|
2225
|
+
const page = state.memorySourcePages.get(sessionId);
|
|
2226
|
+
store.save(sessionId, owner, {
|
|
2227
|
+
originalSystemHash: prefix.originalSystemHash,
|
|
2228
|
+
originalPrefixHashes: prefix.originalPrefixHashes,
|
|
2229
|
+
compressedMessages: prefix.compressedMessages,
|
|
2230
|
+
compressedSystem: prefix.compressedSystem,
|
|
2231
|
+
hasCompressedSystem: prefix.hasCompressedSystem,
|
|
2232
|
+
targetTokens: prefix.targetTokens,
|
|
2233
|
+
explicitTarget: prefix.explicitTarget,
|
|
2234
|
+
modelContextLimit: prefix.modelContextLimit,
|
|
2235
|
+
}, page && { url: page.url, index: page.index, compressed: page.compressed });
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
/**
|
|
2239
|
+
* A fresh process's main-lane requests for a session: adopt the session's
|
|
2240
|
+
* persisted prefix when this history passes the checks the in-memory prefix
|
|
2241
|
+
* would (same session, system and covered messages; same window; a routable
|
|
2242
|
+
* body). Only an authoritative main history may discard it: one with the same
|
|
2243
|
+
* system and window, no server tools, and at least the covered length, whose
|
|
2244
|
+
* covered messages differ. Anything else (a side or helper request such as
|
|
2245
|
+
* the Haiku WebSearch call) leaves it for a later request. The disk is read
|
|
2246
|
+
* once per session; the store checks the backend/account owner.
|
|
2247
|
+
*/
|
|
2248
|
+
function adoptStoredPrefix(opts, state, rec, sessionId, body, messages, modelContextLimit) {
|
|
2249
|
+
const { stablePrefixStore: store, storageOwner: owner } = state;
|
|
2250
|
+
if (!store || !owner || state.stablePrefixes.has(sessionId))
|
|
2251
|
+
return;
|
|
2252
|
+
if (compactionMode(opts, state, sessionId).mode === "off")
|
|
2253
|
+
return;
|
|
2254
|
+
let entry = state.storedPrefixLookups.get(sessionId);
|
|
2255
|
+
if (entry === undefined) {
|
|
2256
|
+
entry = store.load(sessionId, owner) ?? null;
|
|
2257
|
+
boundedSet(state.storedPrefixLookups, sessionId, entry);
|
|
2258
|
+
}
|
|
2259
|
+
if (entry === null)
|
|
2260
|
+
return;
|
|
2261
|
+
const prefix = { sessionId, ...entry.prefix };
|
|
2262
|
+
const verdict = storedPrefixVerdict(body, messages, prefix, modelContextLimit);
|
|
2263
|
+
if (verdict === "defer")
|
|
2264
|
+
return;
|
|
2265
|
+
state.storedPrefixLookups.set(sessionId, null);
|
|
2266
|
+
if (verdict === "discard") {
|
|
2267
|
+
store.delete(sessionId, owner);
|
|
2268
|
+
rec.storedPrefix = "discarded";
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
boundedSet(state.stablePrefixes, sessionId, prefix);
|
|
2272
|
+
if (entry.source && !state.memorySourcePages.has(sessionId)) {
|
|
2273
|
+
rememberPage(state.memorySourcePages, sessionId, { sessionId, ...entry.source, seq: 0 });
|
|
2274
|
+
}
|
|
2275
|
+
rec.storedPrefix = "adopted";
|
|
2276
|
+
}
|
|
2277
|
+
/** What this request says about a persisted prefix (adoptStoredPrefix). */
|
|
2278
|
+
function storedPrefixVerdict(body, messages, prefix, modelContextLimit) {
|
|
2279
|
+
if (carriesServerTools(body) || prefix.modelContextLimit !== modelContextLimit)
|
|
2280
|
+
return "defer";
|
|
2281
|
+
const miss = prefixMismatch(body, messages, prefix, prefix.sessionId, stablePrefixMessageHash);
|
|
2282
|
+
if (miss === null)
|
|
2283
|
+
return prefixRoutedBody(body, messages, prefix) ? "adopt" : "defer";
|
|
2284
|
+
const authoritative = miss === "prefix" &&
|
|
2285
|
+
messages.length >= prefix.originalPrefixHashes.length;
|
|
2286
|
+
return authoritative ? "discard" : "defer";
|
|
2287
|
+
}
|
|
2189
2288
|
/**
|
|
2190
2289
|
* A fork of the main conversation (the away recap) sent on the main thread's
|
|
2191
2290
|
* last compressed prefix: that prefix, then the fork's own messages after it.
|
|
@@ -2367,11 +2466,31 @@ function notePrefixSize(prefix, rec, bytes) {
|
|
|
2367
2466
|
prefix.lastSize = sample;
|
|
2368
2467
|
}
|
|
2369
2468
|
/** Record the reported size of a main-thread request forwarded whole. */
|
|
2370
|
-
function notePassthroughSize(state, sessionId, rec, bytes) {
|
|
2469
|
+
function notePassthroughSize(state, sessionId, model, rec, bytes) {
|
|
2371
2470
|
const sample = reportedSample(rec, bytes);
|
|
2372
2471
|
if (sessionId === undefined || sample === undefined)
|
|
2373
2472
|
return;
|
|
2374
|
-
boundedSet(state.passthroughSizes, sessionId, sample);
|
|
2473
|
+
boundedSet(state.passthroughSizes, sessionSizeKey(sessionId, model), sample);
|
|
2474
|
+
}
|
|
2475
|
+
/** Key of a main-thread size sample (ProxyState.passthroughSizes). */
|
|
2476
|
+
function sessionSizeKey(sessionId, model) {
|
|
2477
|
+
return JSON.stringify([sessionId, typeof model === "string" ? model : null]);
|
|
2478
|
+
}
|
|
2479
|
+
/** Requests whose reported input includes server-injected tool results. */
|
|
2480
|
+
const serverToolRecords = new WeakSet();
|
|
2481
|
+
/**
|
|
2482
|
+
* Whether the request offers server tools (web search, web fetch, code
|
|
2483
|
+
* execution, tool search): their results are added to the input server-side,
|
|
2484
|
+
* so the reported size says nothing about the body's bytes.
|
|
2485
|
+
*/
|
|
2486
|
+
function carriesServerTools(body) {
|
|
2487
|
+
return Array.isArray(body.tools) && body.tools.some((tool) => typeof tool?.type === "string" && SERVER_TOOL_TYPE.test(tool.type));
|
|
2488
|
+
}
|
|
2489
|
+
const SERVER_TOOL_TYPE = /^(web_search|web_fetch|code_execution|tool_search_tool)_/;
|
|
2490
|
+
/** Whether the response reports any server tool use (server_tool_use counts). */
|
|
2491
|
+
function usedServerTools(rec) {
|
|
2492
|
+
const used = rec.usage?.server_tool_use;
|
|
2493
|
+
return !!used && Object.values(used).some((count) => count > 0);
|
|
2375
2494
|
}
|
|
2376
2495
|
/**
|
|
2377
2496
|
* The size Anthropic reported for a body of `bytes`, if it can describe one:
|
|
@@ -2381,6 +2500,8 @@ function notePassthroughSize(state, sessionId, rec, bytes) {
|
|
|
2381
2500
|
function reportedSample(rec, bytes) {
|
|
2382
2501
|
if (changedDeliveryRecords.has(rec))
|
|
2383
2502
|
return undefined;
|
|
2503
|
+
if (serverToolRecords.has(rec) || usedServerTools(rec))
|
|
2504
|
+
return undefined;
|
|
2384
2505
|
const tokens = reportedInputTokens(rec);
|
|
2385
2506
|
if (tokens === undefined)
|
|
2386
2507
|
return undefined;
|
|
@@ -2400,15 +2521,16 @@ function laneSizeKey(req, routeKey, model) {
|
|
|
2400
2521
|
]);
|
|
2401
2522
|
}
|
|
2402
2523
|
/** The session's passthrough size on the main thread, else the lane's. */
|
|
2403
|
-
function wholeRequestSizeSlot(state, isMainRequest, sessionId, sizeKey) {
|
|
2524
|
+
function wholeRequestSizeSlot(state, isMainRequest, sessionId, sizeKey, model) {
|
|
2404
2525
|
const main = isMainRequest && sessionId !== undefined;
|
|
2526
|
+
const mainKey = main ? sessionSizeKey(sessionId, model) : "";
|
|
2405
2527
|
return {
|
|
2406
|
-
get: () => (main ? state.passthroughSizes.get(
|
|
2528
|
+
get: () => (main ? state.passthroughSizes.get(mainKey) : state.laneSizes.get(sizeKey)),
|
|
2407
2529
|
set: (sample) => {
|
|
2408
2530
|
if (!plausibleSample(sample))
|
|
2409
2531
|
return;
|
|
2410
2532
|
if (main)
|
|
2411
|
-
boundedSet(state.passthroughSizes,
|
|
2533
|
+
boundedSet(state.passthroughSizes, mainKey, sample);
|
|
2412
2534
|
else
|
|
2413
2535
|
boundedSet(state.laneSizes, sizeKey, sample, MEMORY_ROUTE_MAX_LANES);
|
|
2414
2536
|
},
|
|
@@ -2420,9 +2542,9 @@ const noteRideSize = notePrefixSize;
|
|
|
2420
2542
|
* Record the reported size of a request forwarded whole: the session's
|
|
2421
2543
|
* passthrough size on the main thread, else its lane's.
|
|
2422
2544
|
*/
|
|
2423
|
-
function noteWholeRequestSize(state, isMainRequest, sessionId, sizeKey, rec, bytes) {
|
|
2545
|
+
function noteWholeRequestSize(state, isMainRequest, sessionId, sizeKey, model, rec, bytes) {
|
|
2424
2546
|
if (isMainRequest && sessionId !== undefined) {
|
|
2425
|
-
notePassthroughSize(state, sessionId, rec, bytes);
|
|
2547
|
+
notePassthroughSize(state, sessionId, model, rec, bytes);
|
|
2426
2548
|
return;
|
|
2427
2549
|
}
|
|
2428
2550
|
const sample = reportedSample(rec, bytes);
|
|
@@ -2595,7 +2717,7 @@ function planToolCompaction(args) {
|
|
|
2595
2717
|
const sample = ride
|
|
2596
2718
|
? ride.sizeHolder.lastSize
|
|
2597
2719
|
: stableLane
|
|
2598
|
-
? state.passthroughSizes.get(sessionId)
|
|
2720
|
+
? state.passthroughSizes.get(sessionSizeKey(sessionId, body.model))
|
|
2599
2721
|
: state.laneSizes.get(args.sizeKey);
|
|
2600
2722
|
const candidate = args.sizing.register(ride?.raw ?? forwardBody, ride ? "ride" : "original", sample);
|
|
2601
2723
|
if (ride)
|
|
@@ -2987,6 +3109,8 @@ async function runBlockingCompression(args) {
|
|
|
2987
3109
|
: undefined;
|
|
2988
3110
|
const messageTimes = transcriptTimesFor(opts, args.sessionId, args.agentId)?.(msgsForMemtree);
|
|
2989
3111
|
const compressStarted = Date.now();
|
|
3112
|
+
const budgetMs = args.longBudget
|
|
3113
|
+
? opts.memtree.longCompressBudgetMs : opts.memtree.compressBudgetMs;
|
|
2990
3114
|
const compressMeta = {
|
|
2991
3115
|
// Model + tools drive the server's model-based memory budget
|
|
2992
3116
|
// (e.g. 800k whole-request target for Fable / Opus 5). Omitting
|
|
@@ -3012,6 +3136,7 @@ async function runBlockingCompression(args) {
|
|
|
3012
3136
|
...(messageTimes && Object.keys(messageTimes).length ? { messageTimes } : {}),
|
|
3013
3137
|
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
|
|
3014
3138
|
...(args.clientMeta ? { clientMeta: args.clientMeta } : {}),
|
|
3139
|
+
...(args.longBudget ? { timeoutMs: budgetMs } : {}),
|
|
3015
3140
|
};
|
|
3016
3141
|
// Sampled BEFORE the call, while it still describes this call: after the
|
|
3017
3142
|
// await the hash is in the cache regardless of who put it there.
|
|
@@ -3019,7 +3144,7 @@ async function runBlockingCompression(args) {
|
|
|
3019
3144
|
// compress() maps every failure to a resolved null (it never rejects).
|
|
3020
3145
|
// Keep foreground work finite even if a transport ignores its abort signal.
|
|
3021
3146
|
let deadline;
|
|
3022
|
-
const maximumWait = Math.min(30_000, Math.max(1, Number.isFinite(
|
|
3147
|
+
const maximumWait = Math.min(args.longBudget ? MAX_LONG_COMPRESS_WAIT_MS : 30_000, Math.max(1, Number.isFinite(budgetMs) ? budgetMs : 15_000));
|
|
3023
3148
|
const result = await Promise.race([
|
|
3024
3149
|
opts.memtree.compress(hash, msgsForMemtree, modelContextLimit, state.shutdownSignal, compressMeta),
|
|
3025
3150
|
new Promise((resolve) => { deadline = setTimeout(() => resolve(null), maximumWait); }),
|
|
@@ -3043,7 +3168,7 @@ async function runBlockingCompression(args) {
|
|
|
3043
3168
|
// Budget-consumed heuristic: the client maps every failure to null, so a
|
|
3044
3169
|
// null that took (roughly) the whole abort budget was almost certainly
|
|
3045
3170
|
// the AbortSignal timeout, not a fast server error.
|
|
3046
|
-
timedOut: result === null && compressMs >=
|
|
3171
|
+
timedOut: result === null && compressMs >= budgetMs,
|
|
3047
3172
|
};
|
|
3048
3173
|
return {
|
|
3049
3174
|
result,
|
|
@@ -3161,7 +3286,7 @@ async function recoverToolRouteMiss(args) {
|
|
|
3161
3286
|
capture(opts, "anthropic-request", args.originalBody);
|
|
3162
3287
|
return forwardRaw(req, res, args.originalBody, opts, upstream, state.shutdownSignal, rec).then((delivered) => {
|
|
3163
3288
|
toolForwarding.settle(delivered);
|
|
3164
|
-
noteWholeRequestSize(state, isMainRequest, sessionId, args.sizeKey, rec, args.originalBody.length);
|
|
3289
|
+
noteWholeRequestSize(state, isMainRequest, sessionId, args.sizeKey, body.model, rec, args.originalBody.length);
|
|
3165
3290
|
});
|
|
3166
3291
|
};
|
|
3167
3292
|
// Same downstream-close tracking as the followup path: compression
|
|
@@ -3190,6 +3315,7 @@ async function recoverToolRouteMiss(args) {
|
|
|
3190
3315
|
sizing: args.sizing,
|
|
3191
3316
|
inputCandidate: args.inputCandidate,
|
|
3192
3317
|
...(args.compaction ? { compaction: args.compaction } : {}),
|
|
3318
|
+
longBudget: args.wholeRefused ? await args.wholeRefused() : false,
|
|
3193
3319
|
});
|
|
3194
3320
|
}
|
|
3195
3321
|
catch {
|
|
@@ -3609,6 +3735,17 @@ async function selectForwardCandidate(args) {
|
|
|
3609
3735
|
}
|
|
3610
3736
|
return changed ? candidate.body : intended;
|
|
3611
3737
|
}
|
|
3738
|
+
/**
|
|
3739
|
+
* Whether delivery would refuse `candidate` sent whole: it is planned over the
|
|
3740
|
+
* window, and the measurement selection would use (an exact count, else the
|
|
3741
|
+
* same estimate or bytes/4 fallback) does not fit either. Only then is there
|
|
3742
|
+
* no fallback, and only then may a compress take the long budget.
|
|
3743
|
+
*/
|
|
3744
|
+
async function wholeRequestRefused(sizing, candidate, limits) {
|
|
3745
|
+
if (fitsNative(sizing.plan(candidate).tokens, limits))
|
|
3746
|
+
return false;
|
|
3747
|
+
return !fitsMeasured(await sizing.measure(candidate, limits), limits);
|
|
3748
|
+
}
|
|
3612
3749
|
/** Write the bounded fallback refusal (route-fallback.ts); always false. */
|
|
3613
3750
|
function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
3614
3751
|
const failure = args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
|
|
@@ -3647,6 +3784,9 @@ function noteSelectedCandidateSize(rec) {
|
|
|
3647
3784
|
const tokens = reportedInputTokens(rec);
|
|
3648
3785
|
if (!candidate || !owner || tokens === undefined)
|
|
3649
3786
|
return;
|
|
3787
|
+
// Server tools' injected results inflate the reported input (reportedSample).
|
|
3788
|
+
if (serverToolRecords.has(rec) || usedServerTools(rec))
|
|
3789
|
+
return;
|
|
3650
3790
|
const sample = { tokens, forwardedBytes: candidate.body.length };
|
|
3651
3791
|
if (!plausibleSample(sample))
|
|
3652
3792
|
return;
|