| 1 | """API handler for chat compaction.""" |
| 2 | from helpers.api import ApiHandler, Input, Output, Request, Response |
| 3 | from agent import AgentContext |
| 4 | from plugins._chat_compaction.helpers.compactor import ( |
| 5 | MIN_COMPACTION_TOKENS, |
| 6 | get_compaction_stats, |
| 7 | run_compaction, |
| 8 | ) |
| 9 | |
| 10 | |
| 11 | class CompactChat(ApiHandler): |
| 12 | """Compact the current chat history into a summarized message.""" |
| 13 | |
| 14 | async def process(self, input: Input, request: Request) -> Output: |
| 15 | ctxid = input.get("context", "") |
| 16 | action = input.get("action", "compact") |
| 17 | |
| 18 | if not ctxid: |
| 19 | return Response("Missing context id", 400) |
| 20 | |
| 21 | context = AgentContext.get(ctxid) |
| 22 | if not context: |
| 23 | return Response("Context not found", 404) |
| 24 | |
| 25 | if context.is_running(): |
| 26 | return Response("Cannot compact while agent is running", 409) |
| 27 | |
| 28 | visible_count = len(context.log.logs) |
| 29 | if visible_count <= 1: |
| 30 | return Response("Not enough messages to compact", 400) |
| 31 | |
| 32 | # Gate both stats and compact — no point opening the modal for tiny chats |
| 33 | stats = await get_compaction_stats(context) |
| 34 | if stats["token_count"] < MIN_COMPACTION_TOKENS: |
| 35 | return { |
| 36 | "ok": False, |
| 37 | "message": f"Not enough content to compact (minimum {MIN_COMPACTION_TOKENS:,} tokens)", |
| 38 | } |
| 39 | |
| 40 | if action == "stats": |
| 41 | return {"ok": True, "stats": stats} |
| 42 | |
| 43 | elif action == "compact": |
| 44 | use_chat_model = input.get("use_chat_model", True) |
| 45 | preset_name = input.get("preset_name") or None |
| 46 | |
| 47 | context.run_task( |
| 48 | _run_compaction_task, context, use_chat_model, preset_name |
| 49 | ) |
| 50 | |
| 51 | return {"ok": True, "message": "Compaction started"} |
| 52 | |
| 53 | else: |
| 54 | return Response(f"Unknown action: {action}", 400) |
| 55 | |
| 56 | |
| 57 | async def _run_compaction_task(context, use_chat_model: bool, preset_name: str | None): |
| 58 | """Wrapper to run compaction and handle errors.""" |
| 59 | try: |
| 60 | await run_compaction(context, use_chat_model, preset_name) |
| 61 | except Exception as e: |
| 62 | context.log.log( |
| 63 | type="error", |
| 64 | heading="Compaction Failed", |
| 65 | content=str(e), |
| 66 | ) |
| 67 | from helpers.state_monitor_integration import mark_dirty_all |
| 68 | mark_dirty_all(reason="plugins._chat_compaction.compact_chat_error") |