Summarize the conversation so far and replace history with the summary.
()
| 485 | |
| 486 | /** Summarize the conversation so far and replace history with the summary. */ |
| 487 | async compact(): Promise<void> { |
| 488 | const { onEvent } = this.options.callbacks |
| 489 | if (this.messages.length === 0) { |
| 490 | onEvent({ type: "error", message: "Nothing to compact yet." }) |
| 491 | onEvent({ type: "turn-end" }) |
| 492 | return |
| 493 | } |
| 494 | // Covers the resume-then-immediately-/compact path, so SessionStart |
| 495 | // always fires before any SessionEnd. |
| 496 | await this.maybeFireSessionStart() |
| 497 | // If SessionStart produced context, fold it into the compaction request |
| 498 | // rather than letting it linger for the next turn. |
| 499 | let startContext = "" |
| 500 | if (this.pendingStartContext) { |
| 501 | startContext = wrapHookContext("SessionStart", this.pendingStartContext) + "\n\n" |
| 502 | this.pendingStartContext = "" |
| 503 | } |
| 504 | // PreCompact runs before summarizing (it cannot cancel compaction). |
| 505 | if (this.hooks.hasHooks("PreCompact")) { |
| 506 | await this.hooks.run("PreCompact", { trigger: "manual", custom_instructions: "" }) |
| 507 | } |
| 508 | this.abortController = new AbortController() |
| 509 | const signal = this.abortController.signal |
| 510 | try { |
| 511 | const request: OpenAI.Chat.ChatCompletionMessageParam[] = [ |
| 512 | ...this.outgoingMessages(), |
| 513 | { |
| 514 | role: "user", |
| 515 | content: |
| 516 | startContext + |
| 517 | "Summarize this conversation so it can replace the full history. Capture the user's goals, decisions made, files created or modified (with paths), important code details, and any remaining next steps. Be thorough but concise. Respond with only the summary.", |
| 518 | }, |
| 519 | ] |
| 520 | let summary = "" |
| 521 | for await (const chunk of this.streamWithRetry( |
| 522 | () => this.client.createMessage(this.systemPrompt, request, [], signal), |
| 523 | signal, |
| 524 | () => { |
| 525 | // Compaction only streams text (committed once at the end), so a |
| 526 | // mid-stream retry just discards the partial summary. |
| 527 | summary = "" |
| 528 | onEvent({ type: "stream-reset" }) |
| 529 | return true |
| 530 | }, |
| 531 | )) { |
| 532 | if (signal.aborted) throw new DOMException("aborted", "AbortError") |
| 533 | if (chunk.type === "text") { |
| 534 | summary += chunk.text |
| 535 | onEvent({ type: "text-delta", text: chunk.text }) |
| 536 | } else if (chunk.type === "usage") { |
| 537 | this.totalCost += chunk.totalCost ?? 0 |
| 538 | this.contextTokens = (chunk.inputTokens ?? 0) + (chunk.outputTokens ?? 0) |
| 539 | onEvent({ |
| 540 | type: "usage", |
| 541 | inputTokens: chunk.inputTokens, |
| 542 | outputTokens: chunk.outputTokens, |
| 543 | cost: chunk.totalCost ?? 0, |
| 544 | totalCost: this.totalCost, |
no test coverage detected