| 1 | package agent |
| 2 | |
| 3 | import "reasonix/internal/provider" |
| 4 | |
| 5 | // ContextReport is a point-in-time view of context pressure: the declared |
| 6 | // window, the thresholds derived from it, what the model currently sees, and how |
| 7 | // the last maintenance pass ended. A misconfigured window and a genuinely full |
| 8 | // one produce the same notices, so the numbers behind the decision have to be |
| 9 | // inspectable rather than inferred. |
| 10 | type ContextReport struct { |
| 11 | Window int |
| 12 | HardCeiling int |
| 13 | OutputBudget int |
| 14 | |
| 15 | LatestPrompt int |
| 16 | CanonicalTokens int |
| 17 | ProjectionTokens int |
| 18 | Projected bool |
| 19 | |
| 20 | SoftThreshold int |
| 21 | SnipThreshold int |
| 22 | FoldThreshold int |
| 23 | ForceThreshold int |
| 24 | |
| 25 | LastTrigger string |
| 26 | LastMode string |
| 27 | LastSource int |
| 28 | LastResult int |
| 29 | CacheState string |
| 30 | BlockedReason string |
| 31 | } |
| 32 | |
| 33 | // ContextReport samples the current context state. Compaction is disabled when |
| 34 | // Window is zero, in which case the thresholds carry no meaning. |
| 35 | func (a *Agent) ContextReport() ContextReport { |
| 36 | if a == nil { |
| 37 | return ContextReport{} |
| 38 | } |
| 39 | rep := ContextReport{ |
| 40 | Window: a.contextWindow, |
| 41 | HardCeiling: a.hardInputCeiling(), |
| 42 | OutputBudget: a.maxOutputTokens, |
| 43 | CacheState: a.CacheState(), |
| 44 | } |
| 45 | if u := a.sess.output.lastUsage.Load(); u != nil { |
| 46 | rep.LatestPrompt = u.LatestPromptTokens() |
| 47 | } |
| 48 | if a.sess.conversation != nil { |
| 49 | canonical, _ := a.sess.conversation.snapshotMessagesVersion() |
| 50 | rep.CanonicalTokens = a.estimatedPromptTokens(provider.ModelMessages(canonical)) |
| 51 | } |
| 52 | visible := a.modelVisibleMessages() |
| 53 | rep.ProjectionTokens = a.estimatedPromptTokens(provider.ModelMessages(visible)) |
| 54 | rep.Projected = rep.ProjectionTokens != rep.CanonicalTokens |
| 55 | |
| 56 | if a.contextWindow > 0 { |
| 57 | rep.FoldThreshold = a.compactTrigger() |
| 58 | if _, reason := a.contextMaintenanceBlocked(a.contextMaintenanceInputHash(visible), 0); reason != "" { |
| 59 | rep.BlockedReason = reason |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | a.sess.compactionMu.Lock() |
| 64 | st := a.sess.compactionState |
| 65 | a.sess.compactionMu.Unlock() |
| 66 | // Prefer LastReceipt; fall back to legacy top-level mirrors from older sidecars. |
| 67 | if r := st.LastReceipt; r != nil && r.Status == "applied" { |
| 68 | rep.LastTrigger = r.Trigger |
| 69 | if r.Action == "summary" { |
| 70 | rep.LastMode = CompactionModeSummarized |
| 71 | } else if r.Action != "" { |
| 72 | rep.LastMode = r.Action |
| 73 | } |
| 74 | rep.LastSource, rep.LastResult = r.InputTokens, r.ResultTokens |
| 75 | } else { |
| 76 | rep.LastTrigger, rep.LastMode = st.LastTrigger, st.LastMode |
| 77 | rep.LastSource, rep.LastResult = st.LastSourceTokens, st.LastResultTokens |
| 78 | } |
| 79 | if rep.BlockedReason == "" { |
| 80 | if r := st.LastReceipt; r != nil && (r.Status == "blocked" || r.Status == "failed") { |
| 81 | rep.BlockedReason = r.Reason |
| 82 | } else { |
| 83 | rep.BlockedReason = st.BlockedReason |
| 84 | } |
| 85 | } |
| 86 | return rep |
| 87 | } |
| 88 |