TestDoCompactAfterHookFires verifies that after_compaction fires when a summary was applied (LLM-path or hook-path), and that the hook receives the produced summary text together with the *pre-compaction* token counts (so observability handlers can express "compacted from X to Y").
(t *testing.T)
| 230 | // *pre-compaction* token counts (so observability handlers can |
| 231 | // express "compacted from X to Y"). |
| 232 | func TestDoCompactAfterHookFires(t *testing.T) { |
| 233 | t.Parallel() |
| 234 | |
| 235 | dir := t.TempDir() |
| 236 | logFile := dir + "/after.log" |
| 237 | |
| 238 | const customSummary = "summary from the before hook" |
| 239 | beforeJSON := `{"hook_specific_output":{"hook_event_name":"before_compaction","summary":"` + customSummary + `"}}` |
| 240 | |
| 241 | hookCfg := &latest.HooksConfig{ |
| 242 | BeforeCompaction: []latest.HookDefinition{ |
| 243 | {Type: "command", Command: "echo '" + beforeJSON + "'", Timeout: 5}, |
| 244 | }, |
| 245 | AfterCompaction: []latest.HookDefinition{ |
| 246 | // Capture summary plus pre-compaction tokens; if the runtime |
| 247 | // regresses to passing post-compaction values we'll see |
| 248 | // input_tokens == EstimateMessageTokens(summary) instead of |
| 249 | // the pre-compaction 1234. |
| 250 | {Type: "command", Command: "cat | jq -r '\"\\(.summary)|\\(.input_tokens)|\\(.output_tokens)\"' > " + logFile, Timeout: 5}, |
| 251 | }, |
| 252 | } |
| 253 | |
| 254 | prov := &mockProvider{id: "test/mock-model", stream: &mockStream{}} |
| 255 | root := agent.New("root", "test", |
| 256 | agent.WithModel(prov), |
| 257 | agent.WithHooks(hookCfg), |
| 258 | ) |
| 259 | tm := team.New(team.WithAgents(root)) |
| 260 | |
| 261 | rt, err := NewLocalRuntime(t.Context(), tm, |
| 262 | WithSessionCompaction(false), |
| 263 | WithModelStore(mockModelStoreWithLimit{limit: 100_000}), |
| 264 | ) |
| 265 | require.NoError(t, err) |
| 266 | |
| 267 | sess := session.New(session.WithMessages([]session.Item{ |
| 268 | session.NewMessageItem(&session.Message{Message: chat.Message{Role: chat.MessageRoleUser, Content: "hi"}}), |
| 269 | })) |
| 270 | // Seed pre-compaction token counts so we can verify the hook |
| 271 | // receives them rather than the post-compaction values (which |
| 272 | // would be approximately EstimateMessageTokens(summary) and 0). |
| 273 | sess.InputTokens = 1234 |
| 274 | sess.OutputTokens = 567 |
| 275 | |
| 276 | events := make(chan Event, 32) |
| 277 | rt.compactWithReason(t.Context(), sess, "", compactionReasonThreshold, NewChannelSink(events)) |
| 278 | close(events) |
| 279 | for range events { |
| 280 | } |
| 281 | |
| 282 | logged, readErr := os.ReadFile(logFile) |
| 283 | require.NoError(t, readErr, "after_compaction hook must have run and produced the log file") |
| 284 | assert.Equal(t, customSummary+"|1234|567\n", string(logged), |
| 285 | "after_compaction must receive the produced summary and the *pre-compaction* token counts") |
| 286 | } |
| 287 | |
| 288 | // TestDoCompactNoHooksMatchesPriorBehavior is a regression guard: with |
| 289 | // no compaction-related hooks configured, compactWithReason must still |
nothing calls this directly
no test coverage detected