Fragment validation and budget rejection
The policy implementation follows.
ts
export function validateContextFragment(
fragment: ContextFragment,
ids: Set<string> = new Set(),
checkBudget = true,
): string {
// Ids are stable keys in usage records and protected-context errors, so they start with a
// lowercase letter and admit only characters that are safe in both positions.
if (
!/^[a-z][a-zA-Z0-9:._-]*$/.test(fragment.id) ||
ids.has(fragment.id) ||
!fragment.sourceId ||
!['instruction', 'server', 'user', 'tool'].includes(fragment.trust) ||
!['configuration', 'turn', 'step'].includes(fragment.lifetime) ||
!['system', 'user'].includes(fragment.role) ||
!Number.isSafeInteger(fragment.maxChars) ||
fragment.maxChars < 1
)
throw new Error('Invalid context fragment');
// Consumed before the later checks: a failed fragment must not leave its id reusable by a
// retry through the same accumulator.
ids.add(fragment.id);
if (
fragment.role === 'system' &&
(fragment.trust === 'user' || fragment.trust === 'tool' || typeof fragment.value !== 'string')
)
throw new Error('Untrusted context cannot become a system instruction');
if (
typeof fragment.value !== 'string' &&
(!fragment.value || typeof fragment.value !== 'object' || Array.isArray(fragment.value))
)
throw new Error('Invalid context value');
// Structured values are measured as the JSON text that will be sent, so the declared budget
// describes the payload rather than the in-memory object.
const serialized =
typeof fragment.value === 'string' ? fragment.value : JSON.stringify(fragment.value);
if (checkBudget && serialized.length > fragment.maxChars)
throw new ValidationError([
{
code: 'CONTEXT_BUDGET',
message: `上下文片段 ${fragment.id} 超过预算;不截断原问题、约束与证据。`,
},
]);
return serialized;
}
export function validateContextBudget(
inputChars: number,
maxInputChars: number,
exceeded?: (limit: number) => Error,
): void {
breakWhile(
!Number.isSafeInteger(inputChars) || inputChars < 0,
() => new TypeError('Invalid context input size'),
);
breakWhile(
maxInputChars !== Infinity && (!Number.isSafeInteger(maxInputChars) || maxInputChars < 0),
() => new TypeError('Invalid context budget'),
);
if (inputChars > maxInputChars)
throw (
exceeded?.(maxInputChars) ??
new ValidationError([
{ code: 'CONTEXT_BUDGET', message: `Model input exceeds ${maxInputChars} characters` },
])
);
}