Scenario catalog, generated steps, and precise overrides
The policy implementation follows.
ts
contextStrategyCatalog = {
recent: { operation: 'trim', build: recent },
continuity: { operation: 'compress', build: continuity },
} satisfies Record<GeneratedContextStrategyOptions['kind'], StrategyDefinition>
export function createContextStrategy(
options: ContextStrategyOptions | readonly ContextStrategyOptions[],
settings: ContextStrategySettings = {},
): ContextStrategy {
const definitions: readonly ContextStrategyOptions[] = Array.isArray(options)
? options
: [options as ContextStrategyOptions];
breakWhile(!definitions.length, 'Context strategy requires at least one history');
const single = definitions.length === 1 ? definitions[0] : undefined;
// Array items generate operations; global processing settings belong to the second argument.
if (
!single &&
definitions.some((item) =>
[
'selection',
'steps',
'selectors',
'compressors',
'triggerChars',
'targetInputChars',
'triggerTokens',
'targetInputTokens',
].some((key) => key in item),
)
) {
throw new TypeError('Configure shared context strategy settings in the second argument');
}
const config: Record<string, unknown> = {};
const keys = [
'selection',
'steps',
'selectors',
'compressors',
'triggerChars',
'targetInputChars',
'triggerTokens',
'targetInputTokens',
] as const;
for (const source of [single, settings])
if (source) for (const key of keys) if (key in source) config[key] = source[key];
const configured = config as ContextStrategySettings;
const generated =
configured.steps !== undefined
? []
: (definitions as readonly GeneratedContextStrategyOptions[]).map((options) => {
const { kind, fragment, field } = options;
const preset = options.preset;
breakWhile(
preset !== undefined && !Object.hasOwn(contextHistoryStrategies, preset),
'Unknown history strategy preset',
);
breakWhile(
!Number.isSafeInteger(fragment.maxChars) || fragment.maxChars < 1,
'History strategy requires a positive fragment allowance',
);
const defaults = preset === undefined ? undefined : contextHistoryStrategies[preset];
const id = fragment.id,
targetChars =
options.targetChars ??
(defaults
? Math.max(1, Math.floor(fragment.maxChars * defaults.targetRatio))
: fragment.maxChars);
const grouping = options.grouping ?? { kind: 'user-turn' };
const preserve = {
field,
keepGroups: options.keepGroups ?? defaults?.keepGroups ?? 1,
grouping:
grouping.kind === 'fixed'
? { kind: grouping.kind, size: grouping.size }
: { kind: grouping.kind },
};
breakWhile(!Object.hasOwn(contextStrategyCatalog, kind), 'Unknown context strategy');
return contextStrategyCatalog[kind].build({ options, id, targetChars, preserve });
});
const steps = configured.steps ?? generated.map((item) => item.step);
// A single step needs no selection decision; several steps keep declared order.
const selection =
configured.selection ??
(steps.length === 1
? { mode: 'fixed' as const, kind: steps[0].kind }
: { mode: 'sequence' as const });
const compressors = [
...generated.flatMap((item) => item.compressors),
...(configured.compressors ?? []),
];
const unique = new Map<string, ContextCompressor>();
for (const compressor of compressors) {
// The same handler may be referenced by several steps; a different object under one ID is
// ambiguous for the policy, so it is rejected instead of silently replaced.
breakWhile(
unique.has(compressor.id) && unique.get(compressor.id) !== compressor,
`Conflicting context compressor: ${compressor.id}`,
);
unique.set(compressor.id, compressor);
}
const selectors = [...(configured.selectors ?? [])];
const policy: ContextPolicy = {
mode: 'reduce',
steps: [...steps],
selection,
triggerChars: configured.triggerChars,
targetInputChars: configured.targetInputChars,
triggerTokens: configured.triggerTokens,
targetInputTokens: configured.targetInputTokens,
};
breakWhile(
selection.mode === 'fixed' && !steps.some((step) => step.kind === selection.kind),
'Context strategy selection has no matching operation',
);
validateContextPolicy(policy, [...unique.values()], selectors);
return Object.freeze({
policy,
compressors: Object.freeze([...unique.values()]),
selectors: Object.freeze(selectors),
});
}
function recent({ id, targetChars, preserve }: StrategyInput) {
return {
step: { id: `${id}.window`, kind: 'trim' as const, fragmentId: id, targetChars, ...preserve },
compressors: [],
};
}
function continuity({ options, id, targetChars, preserve }: StrategyInput) {
const compressor = options.compressor;
breakWhile(!compressor, 'Continuity strategy requires a compressor');
return {
step: {
id: `${id}.summary`,
kind: 'compress' as const,
fragmentId: id,
targetChars,
handlerId: compressor!.id,
preserve,
},
compressors: [compressor!],
};
}