src / engine / budget / SearchPayloadLimiter.ts
src / engine / budget / SearchPayloadLimiter.ts
import { SearchResult } from "../../models/SearchResult";
import { TextNormalizer } from "../normalization/TextNormalizer";
/** Minimum character length floor for provider summary text (SearchResult.snippet). */
export const MINIMUM_SNIPPET_LENGTH = 50;
/** Safety margin buffer in character reduction per summary text field to account for JSON serialization formatting overhead. */
export const SAFETY_MARGIN = 10;
/**
* Minimum practical serialized payload budget required for a single SearchResult.
*
* Below this threshold, the configured payload budget is considered too small
* to produce a meaningful SearchResult, so payload reduction is not attempted.
*/
export const MINIMUM_PER_RESULT_PAYLOAD_BUDGET = 200;
export const MAX_PAYLOAD_TOO_SMALL_ERROR =
"The configured Max Payload Size is too small to return meaningful search results. Increase the Max Payload Size.";
export interface AdditionalResultTrace {
resultIndex: number;
originalPayloadChars: number;
perResultBudgetChars?: number;
perResultPass?: boolean;
perResultTrimmedSnippet?: {
originalSnippetChars: number;
trimmedSnippetChars: number;
};
remainingAvailableChars: number;
remainingPass: boolean;
secondTrimmedSnippet?: {
originalSnippetChars: number;
trimmedSnippetChars: number;
};
decision:
| "Accepted"
| "Trimmed and accepted"
| "Rejected because remaining payload budget was insufficient"
| "Rejected because snippet would fall below the 50-character minimum";
trimmedPayloadChars?: number;
originalSnippetChars?: number;
finalSnippetChars?: number;
stoppedProcessing?: boolean;
}
export interface LimiterDecisionInfo {
scenario: "Scenario 1" | "Scenario 2A1" | "Scenario 2A2" | "Scenario 2B";
guaranteedResults: number;
guaranteedTrimmed: number;
perResultPayloadBudget?: number;
additionalsEvaluated: number;
additionalsReturned: number;
additionalsTrimmed: number;
stoppedAfterFirstTrimmed: boolean;
initialCount: number;
finalCount: number;
additionalTraces?: AdditionalResultTrace[];
}
export type PayloadLimitResult =
| { success: true; results: SearchResult[]; decision: LimiterDecisionInfo }
| { success: false; reason: string; decision?: LimiterDecisionInfo };
export function formatSearchResult(r: SearchResult) {
return {
title: r.title,
url: r.url,
snippet: r.snippet,
source: r.source,
...(r.publishedDate ? { published_date: r.publishedDate } : {}),
...(r.score !== undefined ? { score: r.score } : {}),
};
}
/**
* Pure, stateless payload budget limiter function for search results.
* Guarantees that the serialized JSON payload of complete SearchResult objects returned to tool endpoints
* fits strictly within the configured character budget using a deterministic payload reduction strategy.
*
* Terminology:
* - Summary Snippet: The `SearchResult.snippet` string field. This is the ONLY field reduced during payload limiting.
* - SearchResult: The complete `SearchResult` object (title, url, snippet, source, etc.). Payload measurements are always performed on serialized arrays of complete SearchResult objects.
* - perResultPayloadBudget: The maximum serialized payload size allowed for a single SearchResult during payload recovery.
*/
export function limitSearchPayload(
results: SearchResult[],
budgetCharacters: number
): PayloadLimitResult {
if (!results || results.length === 0 || budgetCharacters <= 0) {
const emptyDecision: LimiterDecisionInfo = {
scenario: "Scenario 1",
guaranteedResults: 0,
guaranteedTrimmed: 0,
additionalsEvaluated: 0,
additionalsReturned: 0,
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: 0,
finalCount: 0,
additionalTraces: [],
};
return { success: true, results: [], decision: emptyDecision };
}
// Work on shallow copies of canonical SearchResult objects (preserving immutability)
const canonicalResults: SearchResult[] = results.map((r) => ({ ...r }));
// Helper to measure current serialized JSON payload length of an array of SearchResult objects (as formatted for tool output)
const measurePayload = (items: SearchResult[]): number => {
return JSON.stringify(items.map(formatSearchResult)).length;
};
const initialPayload = measurePayload(canonicalResults);
// Scenario 1: Total combined size of all Canonical results is already UNDER or EQUAL to user configured max payload limit
if (initialPayload <= budgetCharacters) {
const decision: LimiterDecisionInfo = {
scenario: "Scenario 1",
guaranteedResults: Math.min(5, canonicalResults.length),
guaranteedTrimmed: 0,
additionalsEvaluated: Math.max(0, canonicalResults.length - 5),
additionalsReturned: Math.max(0, canonicalResults.length - 5),
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: canonicalResults.length,
finalCount: canonicalResults.length,
additionalTraces: [],
};
return { success: true, results: canonicalResults, decision };
}
// Scenario 2: Total combined size of Canonical results EXCEEDS user configured max payload limit
const guaranteedCount = Math.min(5, canonicalResults.length);
// Step 2A1 Fast Path: Canonical results > 5 AND top 5 combined size fits as-is within budgetCharacters
if (
canonicalResults.length > 5 &&
measurePayload(canonicalResults.slice(0, 5)) <= budgetCharacters
) {
const acceptedResults: SearchResult[] = canonicalResults
.slice(0, 5)
.map((r) => ({ ...r }));
const additionalResults = canonicalResults.slice(5);
let additionalsEvaluated = 0;
let additionalsReturned = 0;
let additionalsTrimmed = 0;
let stoppedAfterFirstTrimmed = false;
const additionalTraces: AdditionalResultTrace[] = [];
for (let i = 0; i < additionalResults.length; i++) {
const candidate = additionalResults[i];
const resultIndex = 5 + i + 1;
additionalsEvaluated += 1;
const origPayload = JSON.stringify(formatSearchResult(candidate)).length;
const remainingBefore = budgetCharacters - measurePayload(acceptedResults);
if (measurePayload([...acceptedResults, candidate]) <= budgetCharacters) {
// Fits fully as-is
acceptedResults.push({ ...candidate });
additionalsReturned += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: true,
decision: "Accepted",
});
} else {
// Does not fit as-is under Scenario 2A1 rules:
// Calculate max summary snippet length allowing complete SearchResult to fit within remaining payload space
const overflow =
measurePayload([...acceptedResults, candidate]) - budgetCharacters;
const maxAllowedSummaryLength = candidate.snippet.length - overflow - SAFETY_MARGIN;
if (maxAllowedSummaryLength >= MINIMUM_SNIPPET_LENGTH) {
const trimmedCandidate: SearchResult = {
...candidate,
snippet: TextNormalizer.truncate(candidate.snippet, maxAllowedSummaryLength),
};
if (
measurePayload([...acceptedResults, trimmedCandidate]) <=
budgetCharacters
) {
acceptedResults.push(trimmedCandidate);
additionalsReturned += 1;
additionalsTrimmed += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: JSON.stringify(formatSearchResult(trimmedCandidate)).length,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: trimmedCandidate.snippet.length,
remainingAvailableChars: remainingBefore,
remainingPass: false,
secondTrimmedSnippet: {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: maxAllowedSummaryLength,
},
decision: "Trimmed and accepted",
stoppedProcessing: true,
});
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because remaining payload budget was insufficient",
stoppedProcessing: true,
});
}
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because snippet would fall below the 50-character minimum",
stoppedProcessing: true,
});
}
// Stop immediately after first non-fitting additional result
stoppedAfterFirstTrimmed = true;
break;
}
}
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2A1",
guaranteedResults: 5,
guaranteedTrimmed: 0,
additionalsEvaluated,
additionalsReturned,
additionalsTrimmed,
stoppedAfterFirstTrimmed,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces,
};
return { success: true, results: acceptedResults, decision };
}
// Scenario 2A2 / 2B Branch: Guaranteed Results initial payload exceeds budgetCharacters
// Reserve SAFETY_MARGIN upfront per result to account for JSON array framing (commas, brackets) and rounding
const perResultPayloadBudget =
Math.floor(budgetCharacters / guaranteedCount) - SAFETY_MARGIN;
if (perResultPayloadBudget < MINIMUM_PER_RESULT_PAYLOAD_BUDGET) {
return {
success: false,
reason: MAX_PAYLOAD_TOO_SMALL_ERROR,
};
}
const guaranteedResults = canonicalResults.slice(0, guaranteedCount);
const acceptedResults: SearchResult[] = [];
let guaranteedTrimmed = 0;
// Process Guaranteed Results sequentially against perResultPayloadBudget
for (const item of guaranteedResults) {
const result = { ...item };
const singlePayload = JSON.stringify(formatSearchResult(result)).length;
if (singlePayload > perResultPayloadBudget) {
const overflow = singlePayload - perResultPayloadBudget;
const targetSnippetLength = Math.max(
MINIMUM_SNIPPET_LENGTH,
result.snippet.length - overflow - SAFETY_MARGIN
);
result.snippet = TextNormalizer.truncate(result.snippet, targetSnippetLength);
if (result.snippet.length < item.snippet.length) {
guaranteedTrimmed += 1;
}
}
acceptedResults.push(result);
}
const combinedGuaranteedPayload = measurePayload(acceptedResults);
// Terminal failure check if Guaranteed Results still exceed payload budget
if (combinedGuaranteedPayload > budgetCharacters) {
return {
success: false,
reason: MAX_PAYLOAD_TOO_SMALL_ERROR,
};
}
// Branch on Additional Results for Scenario 2A2 / 2B
if (canonicalResults.length === guaranteedCount) {
// Scenario 2B: No additional results exist
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2B",
guaranteedResults: guaranteedCount,
guaranteedTrimmed,
perResultPayloadBudget,
additionalsEvaluated: 0,
additionalsReturned: 0,
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces: [],
};
return { success: true, results: acceptedResults, decision };
}
// Scenario 2A2 with additionals: Process additional results under Scenario 2A2 rules
const additionalResults = canonicalResults.slice(guaranteedCount);
let additionalsEvaluated = 0;
let additionalsReturned = 0;
let additionalsTrimmed = 0;
let stoppedAfterFirstTrimmed = false;
const additionalTraces: AdditionalResultTrace[] = [];
for (let i = 0; i < additionalResults.length; i++) {
const candidate = additionalResults[i];
const resultIndex = guaranteedCount + i + 1;
additionalsEvaluated += 1;
const candidateToTest = { ...candidate };
let wasTrimmed = false;
const origPayload = JSON.stringify(formatSearchResult(candidateToTest)).length;
const remainingBefore = budgetCharacters - measurePayload(acceptedResults);
// 1. Check against perResultPayloadBudget
if (origPayload > perResultPayloadBudget) {
const overflow = origPayload - perResultPayloadBudget;
const targetSnippetLength = Math.max(
MINIMUM_SNIPPET_LENGTH,
candidateToTest.snippet.length - overflow - SAFETY_MARGIN
);
candidateToTest.snippet = TextNormalizer.truncate(
candidateToTest.snippet,
targetSnippetLength
);
if (candidateToTest.snippet.length < candidate.snippet.length) {
wasTrimmed = true;
}
}
const candidateTestedPayload = JSON.stringify(formatSearchResult(candidateToTest)).length;
// 2. Check against remaining payload space
if (measurePayload([...acceptedResults, candidateToTest]) <= budgetCharacters) {
acceptedResults.push(candidateToTest);
additionalsReturned += 1;
if (wasTrimmed) additionalsTrimmed += 1;
const remainingAfter = budgetCharacters - measurePayload(acceptedResults);
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: wasTrimmed ? candidateTestedPayload : undefined,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: candidateToTest.snippet.length,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
perResultTrimmedSnippet: wasTrimmed
? {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: candidateToTest.snippet.length,
}
: undefined,
remainingAvailableChars: remainingBefore,
remainingPass: true,
decision: wasTrimmed ? "Trimmed and accepted" : "Accepted",
});
} else {
// Calculate max summary snippet length allowing complete SearchResult to fit within remaining payload space
const overflow =
measurePayload([...acceptedResults, candidateToTest]) - budgetCharacters;
const maxAllowedSummaryLength =
candidateToTest.snippet.length - overflow - SAFETY_MARGIN;
if (maxAllowedSummaryLength >= MINIMUM_SNIPPET_LENGTH) {
const finalTrimmedCandidate: SearchResult = {
...candidateToTest,
snippet: TextNormalizer.truncate(
candidateToTest.snippet,
maxAllowedSummaryLength
),
};
if (
measurePayload([...acceptedResults, finalTrimmedCandidate]) <=
budgetCharacters
) {
acceptedResults.push(finalTrimmedCandidate);
additionalsReturned += 1;
additionalsTrimmed += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: JSON.stringify(formatSearchResult(finalTrimmedCandidate)).length,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: finalTrimmedCandidate.snippet.length,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
perResultTrimmedSnippet: wasTrimmed
? {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: candidateToTest.snippet.length,
}
: undefined,
remainingAvailableChars: remainingBefore,
remainingPass: false,
secondTrimmedSnippet: {
originalSnippetChars: candidateToTest.snippet.length,
trimmedSnippetChars: maxAllowedSummaryLength,
},
decision: "Trimmed and accepted",
stoppedProcessing: true,
});
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because remaining payload budget was insufficient",
stoppedProcessing: true,
});
}
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because snippet would fall below the 50-character minimum",
stoppedProcessing: true,
});
}
// Stop immediately after first non-fitting additional result
stoppedAfterFirstTrimmed = true;
break;
}
}
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2A2",
guaranteedResults: guaranteedCount,
guaranteedTrimmed,
perResultPayloadBudget,
additionalsEvaluated,
additionalsReturned,
additionalsTrimmed,
stoppedAfterFirstTrimmed,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces,
};
return { success: true, results: acceptedResults, decision };
}
import { SearchResult } from "../../models/SearchResult";
import { TextNormalizer } from "../normalization/TextNormalizer";
/** Minimum character length floor for provider summary text (SearchResult.snippet). */
export const MINIMUM_SNIPPET_LENGTH = 50;
/** Safety margin buffer in character reduction per summary text field to account for JSON serialization formatting overhead. */
export const SAFETY_MARGIN = 10;
/**
* Minimum practical serialized payload budget required for a single SearchResult.
*
* Below this threshold, the configured payload budget is considered too small
* to produce a meaningful SearchResult, so payload reduction is not attempted.
*/
export const MINIMUM_PER_RESULT_PAYLOAD_BUDGET = 200;
export const MAX_PAYLOAD_TOO_SMALL_ERROR =
"The configured Max Payload Size is too small to return meaningful search results. Increase the Max Payload Size.";
export interface AdditionalResultTrace {
resultIndex: number;
originalPayloadChars: number;
perResultBudgetChars?: number;
perResultPass?: boolean;
perResultTrimmedSnippet?: {
originalSnippetChars: number;
trimmedSnippetChars: number;
};
remainingAvailableChars: number;
remainingPass: boolean;
secondTrimmedSnippet?: {
originalSnippetChars: number;
trimmedSnippetChars: number;
};
decision:
| "Accepted"
| "Trimmed and accepted"
| "Rejected because remaining payload budget was insufficient"
| "Rejected because snippet would fall below the 50-character minimum";
trimmedPayloadChars?: number;
originalSnippetChars?: number;
finalSnippetChars?: number;
stoppedProcessing?: boolean;
}
export interface LimiterDecisionInfo {
scenario: "Scenario 1" | "Scenario 2A1" | "Scenario 2A2" | "Scenario 2B";
guaranteedResults: number;
guaranteedTrimmed: number;
perResultPayloadBudget?: number;
additionalsEvaluated: number;
additionalsReturned: number;
additionalsTrimmed: number;
stoppedAfterFirstTrimmed: boolean;
initialCount: number;
finalCount: number;
additionalTraces?: AdditionalResultTrace[];
}
export type PayloadLimitResult =
| { success: true; results: SearchResult[]; decision: LimiterDecisionInfo }
| { success: false; reason: string; decision?: LimiterDecisionInfo };
export function formatSearchResult(r: SearchResult) {
return {
title: r.title,
url: r.url,
snippet: r.snippet,
source: r.source,
...(r.publishedDate ? { published_date: r.publishedDate } : {}),
...(r.score !== undefined ? { score: r.score } : {}),
};
}
/**
* Pure, stateless payload budget limiter function for search results.
* Guarantees that the serialized JSON payload of complete SearchResult objects returned to tool endpoints
* fits strictly within the configured character budget using a deterministic payload reduction strategy.
*
* Terminology:
* - Summary Snippet: The `SearchResult.snippet` string field. This is the ONLY field reduced during payload limiting.
* - SearchResult: The complete `SearchResult` object (title, url, snippet, source, etc.). Payload measurements are always performed on serialized arrays of complete SearchResult objects.
* - perResultPayloadBudget: The maximum serialized payload size allowed for a single SearchResult during payload recovery.
*/
export function limitSearchPayload(
results: SearchResult[],
budgetCharacters: number
): PayloadLimitResult {
if (!results || results.length === 0 || budgetCharacters <= 0) {
const emptyDecision: LimiterDecisionInfo = {
scenario: "Scenario 1",
guaranteedResults: 0,
guaranteedTrimmed: 0,
additionalsEvaluated: 0,
additionalsReturned: 0,
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: 0,
finalCount: 0,
additionalTraces: [],
};
return { success: true, results: [], decision: emptyDecision };
}
// Work on shallow copies of canonical SearchResult objects (preserving immutability)
const canonicalResults: SearchResult[] = results.map((r) => ({ ...r }));
// Helper to measure current serialized JSON payload length of an array of SearchResult objects (as formatted for tool output)
const measurePayload = (items: SearchResult[]): number => {
return JSON.stringify(items.map(formatSearchResult)).length;
};
const initialPayload = measurePayload(canonicalResults);
// Scenario 1: Total combined size of all Canonical results is already UNDER or EQUAL to user configured max payload limit
if (initialPayload <= budgetCharacters) {
const decision: LimiterDecisionInfo = {
scenario: "Scenario 1",
guaranteedResults: Math.min(5, canonicalResults.length),
guaranteedTrimmed: 0,
additionalsEvaluated: Math.max(0, canonicalResults.length - 5),
additionalsReturned: Math.max(0, canonicalResults.length - 5),
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: canonicalResults.length,
finalCount: canonicalResults.length,
additionalTraces: [],
};
return { success: true, results: canonicalResults, decision };
}
// Scenario 2: Total combined size of Canonical results EXCEEDS user configured max payload limit
const guaranteedCount = Math.min(5, canonicalResults.length);
// Step 2A1 Fast Path: Canonical results > 5 AND top 5 combined size fits as-is within budgetCharacters
if (
canonicalResults.length > 5 &&
measurePayload(canonicalResults.slice(0, 5)) <= budgetCharacters
) {
const acceptedResults: SearchResult[] = canonicalResults
.slice(0, 5)
.map((r) => ({ ...r }));
const additionalResults = canonicalResults.slice(5);
let additionalsEvaluated = 0;
let additionalsReturned = 0;
let additionalsTrimmed = 0;
let stoppedAfterFirstTrimmed = false;
const additionalTraces: AdditionalResultTrace[] = [];
for (let i = 0; i < additionalResults.length; i++) {
const candidate = additionalResults[i];
const resultIndex = 5 + i + 1;
additionalsEvaluated += 1;
const origPayload = JSON.stringify(formatSearchResult(candidate)).length;
const remainingBefore = budgetCharacters - measurePayload(acceptedResults);
if (measurePayload([...acceptedResults, candidate]) <= budgetCharacters) {
// Fits fully as-is
acceptedResults.push({ ...candidate });
additionalsReturned += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: true,
decision: "Accepted",
});
} else {
// Does not fit as-is under Scenario 2A1 rules:
// Calculate max summary snippet length allowing complete SearchResult to fit within remaining payload space
const overflow =
measurePayload([...acceptedResults, candidate]) - budgetCharacters;
const maxAllowedSummaryLength = candidate.snippet.length - overflow - SAFETY_MARGIN;
if (maxAllowedSummaryLength >= MINIMUM_SNIPPET_LENGTH) {
const trimmedCandidate: SearchResult = {
...candidate,
snippet: TextNormalizer.truncate(candidate.snippet, maxAllowedSummaryLength),
};
if (
measurePayload([...acceptedResults, trimmedCandidate]) <=
budgetCharacters
) {
acceptedResults.push(trimmedCandidate);
additionalsReturned += 1;
additionalsTrimmed += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: JSON.stringify(formatSearchResult(trimmedCandidate)).length,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: trimmedCandidate.snippet.length,
remainingAvailableChars: remainingBefore,
remainingPass: false,
secondTrimmedSnippet: {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: maxAllowedSummaryLength,
},
decision: "Trimmed and accepted",
stoppedProcessing: true,
});
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because remaining payload budget was insufficient",
stoppedProcessing: true,
});
}
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because snippet would fall below the 50-character minimum",
stoppedProcessing: true,
});
}
// Stop immediately after first non-fitting additional result
stoppedAfterFirstTrimmed = true;
break;
}
}
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2A1",
guaranteedResults: 5,
guaranteedTrimmed: 0,
additionalsEvaluated,
additionalsReturned,
additionalsTrimmed,
stoppedAfterFirstTrimmed,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces,
};
return { success: true, results: acceptedResults, decision };
}
// Scenario 2A2 / 2B Branch: Guaranteed Results initial payload exceeds budgetCharacters
// Reserve SAFETY_MARGIN upfront per result to account for JSON array framing (commas, brackets) and rounding
const perResultPayloadBudget =
Math.floor(budgetCharacters / guaranteedCount) - SAFETY_MARGIN;
if (perResultPayloadBudget < MINIMUM_PER_RESULT_PAYLOAD_BUDGET) {
return {
success: false,
reason: MAX_PAYLOAD_TOO_SMALL_ERROR,
};
}
const guaranteedResults = canonicalResults.slice(0, guaranteedCount);
const acceptedResults: SearchResult[] = [];
let guaranteedTrimmed = 0;
// Process Guaranteed Results sequentially against perResultPayloadBudget
for (const item of guaranteedResults) {
const result = { ...item };
const singlePayload = JSON.stringify(formatSearchResult(result)).length;
if (singlePayload > perResultPayloadBudget) {
const overflow = singlePayload - perResultPayloadBudget;
const targetSnippetLength = Math.max(
MINIMUM_SNIPPET_LENGTH,
result.snippet.length - overflow - SAFETY_MARGIN
);
result.snippet = TextNormalizer.truncate(result.snippet, targetSnippetLength);
if (result.snippet.length < item.snippet.length) {
guaranteedTrimmed += 1;
}
}
acceptedResults.push(result);
}
const combinedGuaranteedPayload = measurePayload(acceptedResults);
// Terminal failure check if Guaranteed Results still exceed payload budget
if (combinedGuaranteedPayload > budgetCharacters) {
return {
success: false,
reason: MAX_PAYLOAD_TOO_SMALL_ERROR,
};
}
// Branch on Additional Results for Scenario 2A2 / 2B
if (canonicalResults.length === guaranteedCount) {
// Scenario 2B: No additional results exist
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2B",
guaranteedResults: guaranteedCount,
guaranteedTrimmed,
perResultPayloadBudget,
additionalsEvaluated: 0,
additionalsReturned: 0,
additionalsTrimmed: 0,
stoppedAfterFirstTrimmed: false,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces: [],
};
return { success: true, results: acceptedResults, decision };
}
// Scenario 2A2 with additionals: Process additional results under Scenario 2A2 rules
const additionalResults = canonicalResults.slice(guaranteedCount);
let additionalsEvaluated = 0;
let additionalsReturned = 0;
let additionalsTrimmed = 0;
let stoppedAfterFirstTrimmed = false;
const additionalTraces: AdditionalResultTrace[] = [];
for (let i = 0; i < additionalResults.length; i++) {
const candidate = additionalResults[i];
const resultIndex = guaranteedCount + i + 1;
additionalsEvaluated += 1;
const candidateToTest = { ...candidate };
let wasTrimmed = false;
const origPayload = JSON.stringify(formatSearchResult(candidateToTest)).length;
const remainingBefore = budgetCharacters - measurePayload(acceptedResults);
// 1. Check against perResultPayloadBudget
if (origPayload > perResultPayloadBudget) {
const overflow = origPayload - perResultPayloadBudget;
const targetSnippetLength = Math.max(
MINIMUM_SNIPPET_LENGTH,
candidateToTest.snippet.length - overflow - SAFETY_MARGIN
);
candidateToTest.snippet = TextNormalizer.truncate(
candidateToTest.snippet,
targetSnippetLength
);
if (candidateToTest.snippet.length < candidate.snippet.length) {
wasTrimmed = true;
}
}
const candidateTestedPayload = JSON.stringify(formatSearchResult(candidateToTest)).length;
// 2. Check against remaining payload space
if (measurePayload([...acceptedResults, candidateToTest]) <= budgetCharacters) {
acceptedResults.push(candidateToTest);
additionalsReturned += 1;
if (wasTrimmed) additionalsTrimmed += 1;
const remainingAfter = budgetCharacters - measurePayload(acceptedResults);
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: wasTrimmed ? candidateTestedPayload : undefined,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: candidateToTest.snippet.length,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
perResultTrimmedSnippet: wasTrimmed
? {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: candidateToTest.snippet.length,
}
: undefined,
remainingAvailableChars: remainingBefore,
remainingPass: true,
decision: wasTrimmed ? "Trimmed and accepted" : "Accepted",
});
} else {
// Calculate max summary snippet length allowing complete SearchResult to fit within remaining payload space
const overflow =
measurePayload([...acceptedResults, candidateToTest]) - budgetCharacters;
const maxAllowedSummaryLength =
candidateToTest.snippet.length - overflow - SAFETY_MARGIN;
if (maxAllowedSummaryLength >= MINIMUM_SNIPPET_LENGTH) {
const finalTrimmedCandidate: SearchResult = {
...candidateToTest,
snippet: TextNormalizer.truncate(
candidateToTest.snippet,
maxAllowedSummaryLength
),
};
if (
measurePayload([...acceptedResults, finalTrimmedCandidate]) <=
budgetCharacters
) {
acceptedResults.push(finalTrimmedCandidate);
additionalsReturned += 1;
additionalsTrimmed += 1;
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
trimmedPayloadChars: JSON.stringify(formatSearchResult(finalTrimmedCandidate)).length,
originalSnippetChars: candidate.snippet.length,
finalSnippetChars: finalTrimmedCandidate.snippet.length,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
perResultTrimmedSnippet: wasTrimmed
? {
originalSnippetChars: candidate.snippet.length,
trimmedSnippetChars: candidateToTest.snippet.length,
}
: undefined,
remainingAvailableChars: remainingBefore,
remainingPass: false,
secondTrimmedSnippet: {
originalSnippetChars: candidateToTest.snippet.length,
trimmedSnippetChars: maxAllowedSummaryLength,
},
decision: "Trimmed and accepted",
stoppedProcessing: true,
});
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because remaining payload budget was insufficient",
stoppedProcessing: true,
});
}
} else {
additionalTraces.push({
resultIndex,
originalPayloadChars: origPayload,
perResultBudgetChars: perResultPayloadBudget,
perResultPass: !wasTrimmed,
remainingAvailableChars: remainingBefore,
remainingPass: false,
decision: "Rejected because snippet would fall below the 50-character minimum",
stoppedProcessing: true,
});
}
// Stop immediately after first non-fitting additional result
stoppedAfterFirstTrimmed = true;
break;
}
}
const decision: LimiterDecisionInfo = {
scenario: "Scenario 2A2",
guaranteedResults: guaranteedCount,
guaranteedTrimmed,
perResultPayloadBudget,
additionalsEvaluated,
additionalsReturned,
additionalsTrimmed,
stoppedAfterFirstTrimmed,
initialCount: canonicalResults.length,
finalCount: acceptedResults.length,
additionalTraces,
};
return { success: true, results: acceptedResults, decision };
}