claude-code-infinite 1.0.24 → 1.0.25
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 +1 -1
- package/dist/hooks.d.ts +6 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +12 -0
- package/dist/hooks.js.map +1 -1
- package/dist/memtree.d.ts +9 -0
- package/dist/memtree.d.ts.map +1 -1
- package/dist/memtree.js +18 -0
- package/dist/memtree.js.map +1 -1
- package/dist/payment-notice.d.ts +19 -1
- package/dist/payment-notice.d.ts.map +1 -1
- package/dist/payment-notice.js +25 -2
- package/dist/payment-notice.js.map +1 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +109 -8
- package/dist/proxy.js.map +1 -1
- package/dist/reqlog.d.ts +2 -0
- package/dist/reqlog.d.ts.map +1 -1
- package/dist/reqlog.js.map +1 -1
- package/dist/route-fallback.d.ts +6 -0
- package/dist/route-fallback.d.ts.map +1 -1
- package/dist/route-fallback.js +9 -0
- package/dist/route-fallback.js.map +1 -1
- package/package.json +1 -1
package/dist/proxy.js
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { capCacheBreakpoints } from "./route-cache.js";
|
|
42
42
|
import { PromptAccounting } from "./prompt-accounting.js";
|
|
43
|
-
import { INDEX_WAIT_RETRY_AFTER_SECONDS, RouteFallbackFailures } from "./route-fallback.js";
|
|
43
|
+
import { INDEX_WAIT_RETRY_AFTER_SECONDS, RouteFallbackFailures, pausedRefusal } from "./route-fallback.js";
|
|
44
44
|
import { createCountTokensSession, CountTokensCooldowns, plausibleSample, } from "./count-tokens.js";
|
|
45
45
|
import { RequestSizing, estimateRequestTokens, fitsMeasured, fitsNative, outputReservation } from "./request-sizing.js";
|
|
46
46
|
import { routeMessageHash, stablePrefixMessageHash } from "./route-identity.js";
|
|
@@ -56,7 +56,7 @@ import { COMPRESSED_NOTICE, compressedTotalsText, DEGRADED_NOTICE, NOT_COMPRESSE
|
|
|
56
56
|
import { NoticeDeliveryQueue, MEMTREE_COMPACT_COMMAND, MEMTREE_HELP_COMMAND, TRAILER_LABEL, LINK_LABEL, linkLines, urlLine, terminalSupportsHyperlinks, isMemtreeViewCommand, sessionCommandArgs, parseNoticeHookInput, } from "./hooks.js";
|
|
57
57
|
import { USAGE_LABEL, USAGE_SIGN_IN_NOTE } from "./usage-link.js";
|
|
58
58
|
import { hyperlink } from "./payment-gate.js";
|
|
59
|
-
import { PaymentNoticeThrottle, paymentNoticeText } from "./payment-notice.js";
|
|
59
|
+
import { PaymentNoticeThrottle, pausedCompactionText, pausedRefusalMessage, paymentNoticeText, subscribeUrlFromDetail } 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
62
|
import { isResumableSessionId } from "./resume-hint.js";
|
|
@@ -303,7 +303,9 @@ export function startProxy(opts) {
|
|
|
303
303
|
const shutdownAbort = new AbortController();
|
|
304
304
|
const state = {
|
|
305
305
|
paymentNotice: new PaymentNoticeThrottle(opts.paymentNoticeIntervalMs),
|
|
306
|
+
pausedNearWindowWarned: new Set(),
|
|
306
307
|
unpaidDetail: () => opts.memtree.paymentRequiredDetail,
|
|
308
|
+
pausedDetail: () => opts.memtree.pausedDetail,
|
|
307
309
|
usageUrl: opts.memtree.usageDashboardUrl,
|
|
308
310
|
sessionStartNotice: opts.sessionStartNotice,
|
|
309
311
|
diagnostics: opts.diagnostics,
|
|
@@ -1717,11 +1719,20 @@ async function handleMessages(req, res, opts, upstream, state) {
|
|
|
1717
1719
|
capture(opts, "anthropic-request", forwardBody);
|
|
1718
1720
|
const paymentDetail = opts.memtree.paymentRequiredDetail;
|
|
1719
1721
|
const mayQueueNotice = displayForThisTurn && state.mainPromptGeneration === noticePromptGeneration;
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
+
const overBudget = reachedBudget(rec.compaction);
|
|
1723
|
+
if (mayQueueNotice && paymentDetail !== null &&
|
|
1724
|
+
(state.paymentNotice.due() || (overBudget && !nearWindowWarned(state, sessionId)))) {
|
|
1725
|
+
// A paused refusal of this very request withdraws it (refuseWholeRequest),
|
|
1726
|
+
// and any later one withdraws its near-window line.
|
|
1727
|
+
const handle = state.notices.queueSuffix(paymentNoticeText(paymentDetail, recapLink(state, sessionId)?.link, { overBudget }), () => {
|
|
1722
1728
|
state.paymentNotice.markShown();
|
|
1729
|
+
if (overBudget && sessionId !== undefined)
|
|
1730
|
+
addNearWindowWarned(state, sessionId);
|
|
1723
1731
|
state.diagnostics?.record("paused_notice", { trigger: "turn" });
|
|
1724
1732
|
}, noticePromptId);
|
|
1733
|
+
pausedNotices.set(rec, handle);
|
|
1734
|
+
if (overBudget)
|
|
1735
|
+
state.nearWindowNotice = handle;
|
|
1725
1736
|
}
|
|
1726
1737
|
else if (mayQueueNotice && paymentDetail === null) {
|
|
1727
1738
|
state.notices.queueSuffix(DEGRADED_NOTICE, undefined, noticePromptId);
|
|
@@ -1976,10 +1987,15 @@ function joinNoticeLines(...parts) {
|
|
|
1976
1987
|
export const MEMTREE_COMPACT_MIN_TOKENS = 20_000;
|
|
1977
1988
|
/** The reply to a ccc slash command, or undefined for an ordinary prompt. */
|
|
1978
1989
|
function sessionCommandReply(state, sessionId, prompt) {
|
|
1990
|
+
const unpaid = state.unpaidDetail();
|
|
1991
|
+
// Paused, compaction needs the MemTree it cannot reach: say how to resume instead.
|
|
1992
|
+
const paused = unpaid === null ? undefined
|
|
1993
|
+
: pausedCompactionText(viewUrl(subscribeUrlFromDetail(unpaid)));
|
|
1979
1994
|
if (sessionCommandArgs(prompt, MEMTREE_HELP_COMMAND) !== undefined) {
|
|
1980
1995
|
return [
|
|
1981
1996
|
"• /memtree-view · show this session's MemTree page link",
|
|
1982
|
-
|
|
1997
|
+
paused !== undefined ? `• ${paused}`
|
|
1998
|
+
: `• /memtree-compact [tokens | off] · compact this session on your next message (default half the budget, at least ${MEMTREE_COMPACT_MIN_TOKENS / 1000}k)`,
|
|
1983
1999
|
usageLines(state),
|
|
1984
2000
|
].filter((line) => line !== undefined).join("\n");
|
|
1985
2001
|
}
|
|
@@ -2000,6 +2016,8 @@ function sessionCommandReply(state, sessionId, prompt) {
|
|
|
2000
2016
|
state.storedPrefixLookups.set(sessionId, null);
|
|
2001
2017
|
return `${TRAILER_LABEL} compaction off: the conversation is sent whole, and MemTree compresses only when it outgrows the model's budget.`;
|
|
2002
2018
|
}
|
|
2019
|
+
if (paused !== undefined)
|
|
2020
|
+
return `${TRAILER_LABEL} ${paused}`;
|
|
2003
2021
|
if (args === "") {
|
|
2004
2022
|
// Back to the automatic target (half the budget, or CCC_COMPACT_TARGET).
|
|
2005
2023
|
// Under CCC_COMPACT_TARGET=off the default is off, so pin the automatic
|
|
@@ -2082,7 +2100,9 @@ function viewUrl(url) {
|
|
|
2082
2100
|
*/
|
|
2083
2101
|
function sessionLink(state, sessionId, source) {
|
|
2084
2102
|
const newest = state.newestTrees?.peek(sessionId);
|
|
2085
|
-
|
|
2103
|
+
// The note suggests /memtree-compact, which cannot run while MemTree is paused.
|
|
2104
|
+
const note = source && !source.compressed && state.unpaidDetail() === null
|
|
2105
|
+
? { note: NOT_COMPRESSED_NOTE } : {};
|
|
2086
2106
|
if (newest)
|
|
2087
2107
|
return { key: newest.key, link: newest.url, ...note };
|
|
2088
2108
|
return source ? { key: source.index, link: source.url, ...note } : undefined;
|
|
@@ -2193,6 +2213,9 @@ function noteMemtreeHealth(state, compression) {
|
|
|
2193
2213
|
}
|
|
2194
2214
|
if (compression.liveHealth === "success") {
|
|
2195
2215
|
state.toolRecoveryCooldownUntil = 0;
|
|
2216
|
+
// Paid again: a later pause (next month's limit) warns near the window afresh.
|
|
2217
|
+
if (state.unpaidDetail() === null)
|
|
2218
|
+
state.pausedNearWindowWarned.clear();
|
|
2196
2219
|
}
|
|
2197
2220
|
}
|
|
2198
2221
|
/**
|
|
@@ -3915,6 +3938,7 @@ async function selectForwardCandidate(args) {
|
|
|
3915
3938
|
args.rec.overBudgetForward = { reason: measurement.source === "exact"
|
|
3916
3939
|
? "exact-count-fits-window" : measurement.source === "bytes"
|
|
3917
3940
|
? "bytes-fallback-fits-window" : "estimate-fits-window" };
|
|
3941
|
+
warnPausedNearWindow(args.state, args.rec, requestSessionId(args.req));
|
|
3918
3942
|
}
|
|
3919
3943
|
return changed ? candidate.body : intended;
|
|
3920
3944
|
}
|
|
@@ -3934,7 +3958,9 @@ async function wholeRequestRefused(sizing, candidate, limits) {
|
|
|
3934
3958
|
}
|
|
3935
3959
|
/** Write the bounded fallback refusal (route-fallback.ts); always false. */
|
|
3936
3960
|
function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
3937
|
-
const
|
|
3961
|
+
const unpaid = args.state.pausedDetail();
|
|
3962
|
+
const failure = unpaid !== null ? pausedRefusal(pausedRefusalMessage(unpaid))
|
|
3963
|
+
: args.state.fallbackFailures.fail(args.routeKey, { awaitingIndex: args.rec.indexBehind === true });
|
|
3938
3964
|
args.rec.forwardedBytes = 0;
|
|
3939
3965
|
args.rec.approxInputTokens = 0;
|
|
3940
3966
|
args.state.diagnostics?.record("refusal", {
|
|
@@ -3946,14 +3972,89 @@ function refuseWholeRequest(args, inputTokens, budgetTokens) {
|
|
|
3946
3972
|
lane: args.routeKey, attempt: failure.attempt, status: failure.status,
|
|
3947
3973
|
inputTokens, outputTokens: args.body.max_tokens ?? 0,
|
|
3948
3974
|
outputReservation: bodyOutputReservation(args.body), indexBehind: args.rec.indexBehind === true,
|
|
3949
|
-
budgetTokens, modelContextLimit: args.modelContextLimit,
|
|
3975
|
+
budgetTokens, modelContextLimit: args.modelContextLimit, ...(unpaid !== null ? { paused: true } : {}),
|
|
3950
3976
|
};
|
|
3977
|
+
if (unpaid !== null)
|
|
3978
|
+
cancelPausedNotice(args.state, args.rec);
|
|
3951
3979
|
if (args.res.headersSent || args.res.destroyed)
|
|
3952
3980
|
return false;
|
|
3953
3981
|
args.res.writeHead(failure.status, { "content-type": "application/json", ...failure.headers });
|
|
3954
3982
|
args.res.end(JSON.stringify(failure.body));
|
|
3983
|
+
if (unpaid !== null) {
|
|
3984
|
+
notePausedRefusal(args.state, args.rec, args.routeKey, requestSessionId(args.req));
|
|
3985
|
+
}
|
|
3955
3986
|
return false;
|
|
3956
3987
|
}
|
|
3988
|
+
/**
|
|
3989
|
+
* The refusal text already says MemTree is paused and how to resume, so it
|
|
3990
|
+
* counts as the paused notice: the throttle restarts, and this request's
|
|
3991
|
+
* queued notice is withdrawn rather than shown a second time after it.
|
|
3992
|
+
*/
|
|
3993
|
+
function notePausedRefusal(state, rec, lane, sessionId) {
|
|
3994
|
+
// A subagent's or hidden away-summary's refusal is not on the user's screen.
|
|
3995
|
+
if (rec.routeLane === "main") {
|
|
3996
|
+
state.paymentNotice.markShown();
|
|
3997
|
+
// Past the window /compact no longer fits: withdraw an undelivered
|
|
3998
|
+
// near-window line and show none later.
|
|
3999
|
+
state.notices.cancel(state.nearWindowNotice);
|
|
4000
|
+
if (sessionId !== undefined)
|
|
4001
|
+
addNearWindowWarned(state, sessionId);
|
|
4002
|
+
}
|
|
4003
|
+
// A tool turn in the outage cooldown or its lane's growth backoff refuses on
|
|
4004
|
+
// the last 402 without asking. End both so the next request asks MemTree
|
|
4005
|
+
// again (a 402 answers fast) and paying takes effect with no restart. A
|
|
4006
|
+
// concurrent attempt still compressing (no backoff yet) keeps its slot.
|
|
4007
|
+
state.toolRecoveryCooldownUntil = 0;
|
|
4008
|
+
const attempt = state.toolRecoveryAttemptedLanes.get(lane);
|
|
4009
|
+
if (attempt && (!attempt.inFlight || attempt.retryAtTokens !== undefined)) {
|
|
4010
|
+
state.toolRecoveryAttemptedLanes.delete(lane);
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
/** Withdraw the paused notice this request queued: its refusal says the same. */
|
|
4014
|
+
function cancelPausedNotice(state, rec) {
|
|
4015
|
+
state.notices.cancel(pausedNotices.get(rec));
|
|
4016
|
+
pausedNotices.delete(rec);
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* A paused main-lane request over the budget, still fitting the window (often a
|
|
4020
|
+
* tool round): once per session, say /compact still works now. A turn that
|
|
4021
|
+
* already queued the paused notice carries the line itself.
|
|
4022
|
+
*/
|
|
4023
|
+
function warnPausedNearWindow(state, rec, sessionId) {
|
|
4024
|
+
const detail = state.unpaidDetail();
|
|
4025
|
+
// Paid again: a later pause (next month's limit) warns afresh.
|
|
4026
|
+
if (detail === null)
|
|
4027
|
+
state.pausedNearWindowWarned.clear();
|
|
4028
|
+
// Never replace another queued notice (or this one again): a later request tries.
|
|
4029
|
+
if (detail === null || rec.routeLane !== "main" || sessionId === undefined ||
|
|
4030
|
+
pausedNotices.has(rec) || nearWindowWarned(state, sessionId) ||
|
|
4031
|
+
state.notices.hasPending(state.mainPromptId))
|
|
4032
|
+
return;
|
|
4033
|
+
state.nearWindowNotice = state.notices.queueSuffix(paymentNoticeText(detail, undefined, { overBudget: true }), () => {
|
|
4034
|
+
state.paymentNotice.markShown();
|
|
4035
|
+
addNearWindowWarned(state, sessionId);
|
|
4036
|
+
state.diagnostics?.record("paused_notice", { trigger: "turn" });
|
|
4037
|
+
}, state.mainPromptId);
|
|
4038
|
+
}
|
|
4039
|
+
/** Remember a warned session, keeping the set bounded (oldest first out). */
|
|
4040
|
+
function addNearWindowWarned(state, sessionId) {
|
|
4041
|
+
state.pausedNearWindowWarned.delete(sessionId);
|
|
4042
|
+
state.pausedNearWindowWarned.add(sessionId);
|
|
4043
|
+
if (state.pausedNearWindowWarned.size > MEMTREE_LINKS_MAX_SESSIONS) {
|
|
4044
|
+
const oldest = state.pausedNearWindowWarned.values().next().value;
|
|
4045
|
+
if (oldest !== undefined)
|
|
4046
|
+
state.pausedNearWindowWarned.delete(oldest);
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
function nearWindowWarned(state, sessionId) {
|
|
4050
|
+
return sessionId !== undefined && state.pausedNearWindowWarned.has(sessionId);
|
|
4051
|
+
}
|
|
4052
|
+
/** Whether this turn's own size estimate reached its whole-request budget. */
|
|
4053
|
+
function reachedBudget(compaction) {
|
|
4054
|
+
return compaction !== undefined && (compaction.estimatedTokens ?? 0) >= compaction.budgetTokens;
|
|
4055
|
+
}
|
|
4056
|
+
/** The paused notice a degraded request queued, until it is refused or settles. */
|
|
4057
|
+
const pausedNotices = new WeakMap();
|
|
3957
4058
|
const changedDeliveryRecords = new WeakSet();
|
|
3958
4059
|
const selectedCandidates = new WeakMap();
|
|
3959
4060
|
const candidateSizeOwners = new WeakMap();
|