structuredSummarize summarizes the "middle" block of messages using a structured fact-extraction prompt.
( ctx context.Context, history []models.Message, llmClient client.LLMClient, cfg CompactConfig, )
| 286 | // structuredSummarize summarizes the "middle" block of messages using |
| 287 | // a structured fact-extraction prompt. |
| 288 | func (hc *HistoryCompactor) structuredSummarize( |
| 289 | ctx context.Context, |
| 290 | history []models.Message, |
| 291 | llmClient client.LLMClient, |
| 292 | cfg CompactConfig, |
| 293 | ) ([]models.Message, error) { |
| 294 | // Find boundaries: [system messages | middle (to summarize) | recent (keep verbatim)] |
| 295 | systemEnd := 0 |
| 296 | for i, msg := range history { |
| 297 | if msg.Role == "system" && i == systemEnd { |
| 298 | systemEnd = i + 1 |
| 299 | } else { |
| 300 | break |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | recentStart := len(history) - cfg.MinKeepRecent |
| 305 | if recentStart <= systemEnd { |
| 306 | // Not enough messages to split — nothing to summarize |
| 307 | return history, nil |
| 308 | } |
| 309 | |
| 310 | middleMessages := history[systemEnd:recentStart] |
| 311 | if len(middleMessages) < 4 { |
| 312 | return history, nil |
| 313 | } |
| 314 | |
| 315 | // Build input for the summarizer |
| 316 | var sb strings.Builder |
| 317 | for _, msg := range middleMessages { |
| 318 | content := msg.Content |
| 319 | if len(content) > 2000 { |
| 320 | content = content[:1500] + "\n... [truncated for summarization] ...\n" + content[len(content)-300:] |
| 321 | } |
| 322 | sb.WriteString(fmt.Sprintf("[%s]: %s\n\n", msg.Role, content)) |
| 323 | } |
| 324 | |
| 325 | prompt := structuredSummaryPrompt + "\n\nCONVERSATION SEGMENT TO EXTRACT FROM:\n\n" + sb.String() |
| 326 | |
| 327 | summaryHistory := []models.Message{ |
| 328 | {Role: "user", Content: prompt}, |
| 329 | } |
| 330 | |
| 331 | // Derive from parent ctx so that a user-initiated cancel (Ctrl+C / ESC) |
| 332 | // propagates and aborts the long summarization. We add our OWN generous |
| 333 | // timeout (10 min) to protect against ambient turn-level deadlines that |
| 334 | // might be shorter than the summary LLM call. |
| 335 | summarizeCtx, cancel := context.WithTimeout(ctx, 10*time.Minute) |
| 336 | defer cancel() |
| 337 | |
| 338 | response, err := llmClient.SendPrompt(summarizeCtx, prompt, summaryHistory, 0) |
| 339 | if err != nil { |
| 340 | return nil, fmt.Errorf("structured summarization LLM call failed: %w", err) |
| 341 | } |
| 342 | |
| 343 | // Reconstruct: system + summary message + recent messages |
| 344 | result := make([]models.Message, 0, systemEnd+1+cfg.MinKeepRecent) |
| 345 | result = append(result, history[:systemEnd]...) |
no test coverage detected