generateSummary function
- List<
Message> messages, { - required SummarizeFn summarize,
- String? customInstructions,
- String? previousSummary,
- CancelToken? cancelToken,
- CompactionPrompts prompts = defaultCompactionPrompts,
- String? userRequestCandidates,
- int? maxPromptTokens,
- PinnedOperativePayload? pinnedOperative,
Generate (or update) a conversation summary for compaction.
Ported from pi's generateSummary: serializes messages into
<conversation> tags, optionally appends <previous-checkpoint> for
iterative updates, and ends with the fixed structured prompt (plus
Additional focus: when customInstructions is given). Throws
CompactionException on failure — callers must treat compaction as
failure-safe and leave history untouched.
maxPromptTokens (issue #729) bounds the outbound payload: when the
built prompt estimates over it, the region is summarized CHUNK-WISE —
each chunk under maxPromptTokens, every chunk's summary threaded into
the next as <previous-checkpoint> (the existing iterative-update
mechanism; the first chunk uses the plain summary prompt, later chunks
the update prompt) — and the last fold is returned. A single message
bigger than the budget is truncated with an explicit note. Under the
budget (and when null) the prompt is byte-identical to the unbounded
path.
Implementation
Future<String> generateSummary(
List<Message> messages, {
required SummarizeFn summarize,
String? customInstructions,
String? previousSummary,
CancelToken? cancelToken,
CompactionPrompts prompts = defaultCompactionPrompts,
String? userRequestCandidates,
int? maxPromptTokens,
PinnedOperativePayload? pinnedOperative,
}) async {
// Issue #1131: the previous checkpoint re-enters this prompt verbatim —
// heal it first so a poisoned old summary cannot be paraphrased forward
// into a fresh one (idempotent; clean records are unaffected). gh-1409:
// pinned lines ride the heal protected (AC4) — a pin inside an old
// checkpoint survives sanitization byte-identical.
previousSummary = previousSummary == null
? null
: sanitizeSummary(
previousSummary,
protectedLines: pinnedOperative?.lines ?? const {},
).text;
var basePrompt = previousSummary != null
? prompts.summaryUpdate
: prompts.summary;
// gh-1409 AC3: the verbatim-preserve duty rides the instruction tail of
// every call whose input carries the pinned block.
if (pinnedOperative != null) {
basePrompt = '$basePrompt\n\n${prompts.pinnedOperative}';
}
if (customInstructions != null) {
basePrompt = '$basePrompt\n\nAdditional focus: $customInstructions';
}
String build(
String conversation,
String? candidates,
String? prior,
String instructions,
) {
final prompt = StringBuffer()
..write('<conversation>\n')
..write(conversation)
..write('\n</conversation>\n\n');
if (candidates != null) {
prompt
..write(candidates)
..write('\n\n');
}
if (pinnedOperative != null) {
prompt
..write(pinnedOperative.block)
..write('\n\n');
}
if (prior != null) {
prompt.write('<previous-checkpoint>\n$prior\n</previous-checkpoint>\n\n');
}
prompt.write(instructions);
return prompt.toString();
}
final full = build(
serializeConversation(messages),
userRequestCandidates ?? userRequestCandidatesBlock(messages),
previousSummary,
basePrompt,
);
if (maxPromptTokens == null ||
estimateStringTokens(full) <= maxPromptTokens) {
return _runSummarization(
prompt: full,
summarize: summarize,
cancelToken: cancelToken,
failureLabel: 'Summarization failed',
);
}
// Over the payload budget (#729): chunk the region, fold chunk-wise.
// Each chunk carries only its own request candidates (the whole-range
// block would ride every chunk and re-inflate the payload); the record
// `<id>` pointers degrade to date-only on this path.
var prior = previousSummary;
var fold = '';
final chunks = chunkSummarizableMessages(
messages,
max(maxPromptTokens - _chunkEnvelopeReserveTokens, 256),
);
for (var i = 0; i < chunks.length; i++) {
final candidates = userRequestCandidatesBlock(chunks[i]);
final instructions = i == 0
? basePrompt
: (pinnedOperative == null
? prompts.summaryUpdate
// E7: the verbatim-preserve duty rides every chunk.
: '${prompts.summaryUpdate}\n\n${prompts.pinnedOperative}');
final conversation = truncateForSummaryBudget(
serializeConversation(chunks[i]),
budgetTokens: maxPromptTokens,
envelopeChars: _summaryEnvelopeChars(
candidates: candidates,
prior: prior,
instructions: instructions,
pinnedBlock: pinnedOperative?.block,
),
);
fold = await _runSummarization(
prompt: build(conversation, candidates, prior, instructions),
summarize: summarize,
cancelToken: cancelToken,
failureLabel: 'Summarization failed',
);
prior = fold;
}
return fold;
}