返回 DeepSeek-Reasonix
compact_summary_guard_test.go
根目录 / internal / agent / compact_summary_guard_test.go
1 package agent
2
3 import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8
9 "reasonix/internal/event"
10 "reasonix/internal/extension"
11 "reasonix/internal/extension/dispatch"
12 "reasonix/internal/extension/protocol"
13 )
14
15 func TestCompactionPrepareCannotExpandAutomaticSummaryPastWindow(t *testing.T) {
16 const window = 60_000
17 tests := []struct {
18 name string
19 replacement func(*testing.T, json.RawMessage) protocol.InterceptResult
20 }{
21 {
22 name: "messages",
23 replacement: func(t *testing.T, _ json.RawMessage) protocol.InterceptResult {
24 return replaceWith(t, dispatch.CompactionPreparePayload{
25 Messages: []protocol.ProviderMessage{{
26 Role: protocol.ProviderRoleUser, Content: strings.Repeat("x", window*4),
27 }},
28 })
29 },
30 },
31 {
32 name: "guidance",
33 replacement: func(t *testing.T, raw json.RawMessage) protocol.InterceptResult {
34 var payload dispatch.CompactionPreparePayload
35 if err := json.Unmarshal(raw, &payload); err != nil {
36 t.Fatalf("decode compaction.prepare payload: %v", err)
37 }
38 payload.Guidance = strings.Repeat("preserve expanded guidance ", window)
39 return replaceWith(t, payload)
40 },
41 },
42 }
43
44 for _, tc := range tests {
45 t.Run(tc.name, func(t *testing.T) {
46 client := &fakeDispatchClient{interceptFn: func(ev protocol.InterceptEvent, raw json.RawMessage) (protocol.InterceptResult, error) {
47 if ev == protocol.EventCompactionPrepare {
48 return tc.replacement(t, raw), nil
49 }
50 return protocol.InterceptResult{Decision: protocol.DecisionContinue}, nil
51 }}
52 prov := &opaqueWindowProvider{}
53 a := agentOverForceWindow(t, prov, foldableSessionOverForce(120), window)
54 a.svc.extensions = newExtDispatcher(client, true, nil, extension.PointCompactionPrepare)
55
56 var rejected *ContextMaintenanceReceipt
57 a.svc.sink = event.FuncSink(func(e event.Event) {
58 if e.Kind == event.ContextMaintenanceEvent && e.Maintenance != nil && e.Maintenance.Status == "blocked" {
59 rejected = &ContextMaintenanceReceipt{Status: e.Maintenance.Status, Reason: e.Maintenance.Reason}
60 }
61 })
62
63 // The oversized replacement is never sent; over the ceiling the
64 // truncation rescue then stands in for the rejected summary.
65 if err := prepareContext(context.Background(), a, CompactionTriggerPressure); err != nil {
66 t.Fatalf("pressure maintenance error = %v, want the truncation rescue after the rejection", err)
67 }
68 if len(prov.requests) != 0 {
69 t.Fatalf("summary requests = %d, want none for an oversized extension replacement", len(prov.requests))
70 }
71 if rejected == nil || !strings.Contains(rejected.Reason, "prepared summary request") {
72 t.Fatalf("blocked receipt = %+v, want the final summary-budget rejection", rejected)
73 }
74 if receipt := a.sess.compactionState.LastReceipt; receipt == nil || receipt.Action != maintenanceActionTruncate {
75 t.Fatalf("receipt = %+v, want the truncation rescue installed", receipt)
76 }
77 })
78 }
79 }
80
80 lines GO