first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
// This file (US-003) ties the compaction pieces together: given a message list
// and settings, it finds the cut point, extracts file operations from the
// summarized range, generates the structured summary, and returns a
// CompactionResult ready to be persisted as a session compaction entry and used
// to rebuild the agent context. Mirrors pi's prepareCompaction + compact.
package compaction
import (
"context"
"encoding/json"
"github.com/smallnest/pigo/internal/agentcore"
"github.com/smallnest/pigo/internal/provider"
)
// CompactionDetails records the files touched in the compacted history, stored
// alongside a compaction entry so a later iterative compaction can seed its
// file lists. Mirrors pi's CompactionDetails.
type CompactionDetails struct {
// ReadFiles are files read but not modified in the compacted range.
ReadFiles []string `json:"readFiles"`
// ModifiedFiles are files written or edited in the compacted range.
ModifiedFiles []string `json:"modifiedFiles"`
}
// CompactionResult is the outcome of a compaction: the summary text (with file
// metadata appended), the index where retained history begins, the estimated
// tokens before compaction, and the extracted file details. Mirrors pi's
// CompactionResult, adapted to pigo's flat message list (index vs entry id).
type CompactionResult struct {
// Summary is the structured summary that replaces the compacted history.
Summary string
// FirstKeptIndex is the index of the first retained message.
FirstKeptIndex int
// TokensBefore is the estimated context tokens before compaction.
TokensBefore int
// Details holds the file operations extracted from the compacted range.
Details CompactionDetails
}
// Compact prepares and generates a compaction over msgs. It cuts at
// FindCutPoint(msgs, settings.KeepRecentTokens), summarizes messages in
// [prevCompactionIndex+1, firstKeptIndex) (seeding file ops from a prior
// compaction's details when provided), and appends <read-files>/<modified-files>
// metadata to the summary. previousSummary, when non-empty, switches to the
// iterative update template.
//
// prevCompactionIndex is the index of the last already-applied compaction point
// (-1 when none); summarization starts just after it so each compaction only
// covers the newly accumulated history. prevDetails carries that compaction's
// file lists so long-lived reads/edits survive across successive compactions.
//
// It returns (nil, nil) when there is nothing to compact (no valid cut point or
// an empty summarization range).
func Compact(
ctx context.Context,
stream provider.StreamFn,
model provider.Model,
msgs []agentcore.Message,
settings CompactionSettings,
prevCompactionIndex int,
prevDetails *CompactionDetails,
previousSummary string,
cfg provider.StreamConfig,
) (*CompactionResult, error) {
cut := FindCutPoint(msgs, settings.KeepRecentTokens)
start := prevCompactionIndex + 1
if start < 0 {
start = 0
}
if start >= cut.FirstKeptIndex {
// Nothing new to summarize.
return nil, nil
}
toSummarize := msgs[start:cut.FirstKeptIndex]
// Seed file ops from the previous compaction, then fold in this range.
ops := NewFileOps()
if prevDetails != nil {
for _, f := range prevDetails.ReadFiles {
ops.Read[f] = struct{}{}
}
for _, f := range prevDetails.ModifiedFiles {
ops.Edited[f] = struct{}{}
}
}
for _, m := range toSummarize {
extractFileOpsFromMessage(m, ops)
}
readFiles, modifiedFiles := computeFileLists(ops)
summary, err := GenerateSummary(ctx, stream, model, toSummarize, settings.ReserveTokens, previousSummary, cfg)
if err != nil {
return nil, err
}
summary += formatFileOperations(readFiles, modifiedFiles)
return &CompactionResult{
Summary: summary,
FirstKeptIndex: cut.FirstKeptIndex,
TokensBefore: EstimateContextTokens(msgs).Tokens,
Details: CompactionDetails{ReadFiles: readFiles, ModifiedFiles: modifiedFiles},
}, nil
}
// Message builds the CompactionMessage to persist for this result: the summary
// text plus the estimated tokens-before and the file details (as raw JSON).
func (r *CompactionResult) Message(now int64) agentcore.CompactionMessage {
var details json.RawMessage
if b, err := json.Marshal(r.Details); err == nil {
details = b
}
return agentcore.CompactionMessage{
RoleField: agentcore.RoleCompaction,
Summary: r.Summary,
TokensBefore: r.TokensBefore,
Details: details,
Timestamp: now,
}
}
// RebuildContext returns the post-compaction message list: the compaction
// checkpoint followed by the retained recent messages (msgs[FirstKeptIndex:]).
// The summarized prefix is dropped and replaced by the single checkpoint,
// keeping context continuous while staying within the window. Mirrors pi's
// post-compact context reconstruction (summary entry + retained tail).
func (r *CompactionResult) RebuildContext(msgs []agentcore.Message, now int64) agentcore.MessageList {
out := make(agentcore.MessageList, 0, len(msgs)-r.FirstKeptIndex+1)
out = append(out, r.Message(now))
out = append(out, msgs[r.FirstKeptIndex:]...)
return out
}