MCPcopy Index your code
hub / github.com/codeaashu/claude-code / enforceToolResultBudget

Function enforceToolResultBudget

src/utils/toolResultStorage.ts:769–909  ·  view source on GitHub ↗
(
  messages: Message[],
  state: ContentReplacementState,
  skipToolNames: ReadonlySet<string> = new Set(),
)

Source from the content-addressed store, hash-verified

767 * Caller persists these to the transcript for resume reconstruction.
768 */
769export async function enforceToolResultBudget(
770 messages: Message[],
771 state: ContentReplacementState,
772 skipToolNames: ReadonlySet<string> = new Set(),
773): Promise<{
774 messages: Message[]
775 newlyReplaced: ToolResultReplacementRecord[]
776}> {
777 const candidatesByMessage = collectCandidatesByMessage(messages)
778 const nameByToolUseId =
779 skipToolNames.size > 0 ? buildToolNameMap(messages) : undefined
780 const shouldSkip = (id: string): boolean =>
781 nameByToolUseId !== undefined &&
782 skipToolNames.has(nameByToolUseId.get(id) ?? '')
783 // Resolve once per call. A mid-session flag change only affects FRESH
784 // messages (prior decisions are frozen via seenIds/replacements), so
785 // prompt cache for already-seen content is preserved regardless.
786 const limit = getPerMessageBudgetLimit()
787
788 // Walk each API-level message group independently. For previously-processed messages
789 // (all IDs in seenIds) this just re-applies cached replacements. For the
790 // single new message this turn added, it runs the budget check.
791 const replacementMap = new Map<string, string>()
792 const toPersist: ToolResultCandidate[] = []
793 let reappliedCount = 0
794 let messagesOverBudget = 0
795
796 for (const candidates of candidatesByMessage) {
797 const { mustReapply, frozen, fresh } = partitionByPriorDecision(
798 candidates,
799 state,
800 )
801
802 // Re-apply: pure Map lookups. No file I/O, byte-identical, cannot fail.
803 mustReapply.forEach(c => replacementMap.set(c.toolUseId, c.replacement))
804 reappliedCount += mustReapply.length
805
806 // Fresh means this is a new message. Check its per-message budget.
807 // (A previously-processed message has fresh.length === 0 because all
808 // its IDs were added to seenIds when first seen.)
809 if (fresh.length === 0) {
810 // mustReapply/frozen are already in seenIds from their first pass —
811 // re-adding is a no-op but keeps the invariant explicit.
812 candidates.forEach(c => state.seenIds.add(c.toolUseId))
813 continue
814 }
815
816 // Tools with maxResultSizeChars: Infinity (Read) — never persist.
817 // Mark as seen (frozen) so the decision sticks across turns. They don't
818 // count toward freshSize; if that lets the group slip under budget and
819 // the wire message is still large, that's the contract — Read's own
820 // maxTokens is the bound, not this wrapper.
821 const skipped = fresh.filter(c => shouldSkip(c.toolUseId))
822 skipped.forEach(c => state.seenIds.add(c.toolUseId))
823 const eligible = fresh.filter(c => !shouldSkip(c.toolUseId))
824
825 const frozenSize = frozen.reduce((sum, c) => sum + c.size, 0)
826 const freshSize = eligible.reduce((sum, c) => sum + c.size, 0)

Callers 1

applyToolResultBudgetFunction · 0.85

Calls 15

buildToolNameMapFunction · 0.85
getPerMessageBudgetLimitFunction · 0.85
partitionByPriorDecisionFunction · 0.85
shouldSkipFunction · 0.85
selectFreshToReplaceFunction · 0.85
buildReplacementFunction · 0.85
logEventFunction · 0.85
logForDebuggingFunction · 0.85
formatFileSizeFunction · 0.85
forEachMethod · 0.80

Tested by

no test coverage detected