aury-agent 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aury/__init__.py +2 -0
- aury/agents/__init__.py +55 -0
- aury/agents/a2a/__init__.py +168 -0
- aury/agents/backends/__init__.py +196 -0
- aury/agents/backends/artifact/__init__.py +9 -0
- aury/agents/backends/artifact/memory.py +130 -0
- aury/agents/backends/artifact/types.py +133 -0
- aury/agents/backends/code/__init__.py +65 -0
- aury/agents/backends/file/__init__.py +11 -0
- aury/agents/backends/file/local.py +66 -0
- aury/agents/backends/file/types.py +40 -0
- aury/agents/backends/invocation/__init__.py +8 -0
- aury/agents/backends/invocation/memory.py +81 -0
- aury/agents/backends/invocation/types.py +110 -0
- aury/agents/backends/memory/__init__.py +8 -0
- aury/agents/backends/memory/memory.py +179 -0
- aury/agents/backends/memory/types.py +136 -0
- aury/agents/backends/message/__init__.py +9 -0
- aury/agents/backends/message/memory.py +122 -0
- aury/agents/backends/message/types.py +124 -0
- aury/agents/backends/sandbox.py +275 -0
- aury/agents/backends/session/__init__.py +8 -0
- aury/agents/backends/session/memory.py +93 -0
- aury/agents/backends/session/types.py +124 -0
- aury/agents/backends/shell/__init__.py +11 -0
- aury/agents/backends/shell/local.py +110 -0
- aury/agents/backends/shell/types.py +55 -0
- aury/agents/backends/shell.py +209 -0
- aury/agents/backends/snapshot/__init__.py +19 -0
- aury/agents/backends/snapshot/git.py +95 -0
- aury/agents/backends/snapshot/hybrid.py +125 -0
- aury/agents/backends/snapshot/memory.py +86 -0
- aury/agents/backends/snapshot/types.py +59 -0
- aury/agents/backends/state/__init__.py +29 -0
- aury/agents/backends/state/composite.py +49 -0
- aury/agents/backends/state/file.py +57 -0
- aury/agents/backends/state/memory.py +52 -0
- aury/agents/backends/state/sqlite.py +262 -0
- aury/agents/backends/state/types.py +178 -0
- aury/agents/backends/subagent/__init__.py +165 -0
- aury/agents/cli/__init__.py +41 -0
- aury/agents/cli/chat.py +239 -0
- aury/agents/cli/config.py +236 -0
- aury/agents/cli/extensions.py +460 -0
- aury/agents/cli/main.py +189 -0
- aury/agents/cli/session.py +337 -0
- aury/agents/cli/workflow.py +276 -0
- aury/agents/context_providers/__init__.py +66 -0
- aury/agents/context_providers/artifact.py +299 -0
- aury/agents/context_providers/base.py +177 -0
- aury/agents/context_providers/memory.py +70 -0
- aury/agents/context_providers/message.py +130 -0
- aury/agents/context_providers/skill.py +50 -0
- aury/agents/context_providers/subagent.py +46 -0
- aury/agents/context_providers/tool.py +68 -0
- aury/agents/core/__init__.py +83 -0
- aury/agents/core/base.py +573 -0
- aury/agents/core/context.py +797 -0
- aury/agents/core/context_builder.py +303 -0
- aury/agents/core/event_bus/__init__.py +15 -0
- aury/agents/core/event_bus/bus.py +203 -0
- aury/agents/core/factory.py +169 -0
- aury/agents/core/isolator.py +97 -0
- aury/agents/core/logging.py +95 -0
- aury/agents/core/parallel.py +194 -0
- aury/agents/core/runner.py +139 -0
- aury/agents/core/services/__init__.py +5 -0
- aury/agents/core/services/file_session.py +144 -0
- aury/agents/core/services/message.py +53 -0
- aury/agents/core/services/session.py +53 -0
- aury/agents/core/signals.py +109 -0
- aury/agents/core/state.py +363 -0
- aury/agents/core/types/__init__.py +107 -0
- aury/agents/core/types/action.py +176 -0
- aury/agents/core/types/artifact.py +135 -0
- aury/agents/core/types/block.py +736 -0
- aury/agents/core/types/message.py +350 -0
- aury/agents/core/types/recall.py +144 -0
- aury/agents/core/types/session.py +257 -0
- aury/agents/core/types/subagent.py +154 -0
- aury/agents/core/types/tool.py +205 -0
- aury/agents/eval/__init__.py +331 -0
- aury/agents/hitl/__init__.py +57 -0
- aury/agents/hitl/ask_user.py +242 -0
- aury/agents/hitl/compaction.py +230 -0
- aury/agents/hitl/exceptions.py +87 -0
- aury/agents/hitl/permission.py +617 -0
- aury/agents/hitl/revert.py +216 -0
- aury/agents/llm/__init__.py +31 -0
- aury/agents/llm/adapter.py +367 -0
- aury/agents/llm/openai.py +294 -0
- aury/agents/llm/provider.py +476 -0
- aury/agents/mcp/__init__.py +153 -0
- aury/agents/memory/__init__.py +46 -0
- aury/agents/memory/compaction.py +394 -0
- aury/agents/memory/manager.py +465 -0
- aury/agents/memory/processor.py +177 -0
- aury/agents/memory/store.py +187 -0
- aury/agents/memory/types.py +137 -0
- aury/agents/messages/__init__.py +40 -0
- aury/agents/messages/config.py +47 -0
- aury/agents/messages/raw_store.py +224 -0
- aury/agents/messages/store.py +118 -0
- aury/agents/messages/types.py +88 -0
- aury/agents/middleware/__init__.py +31 -0
- aury/agents/middleware/base.py +341 -0
- aury/agents/middleware/chain.py +342 -0
- aury/agents/middleware/message.py +129 -0
- aury/agents/middleware/message_container.py +126 -0
- aury/agents/middleware/raw_message.py +153 -0
- aury/agents/middleware/truncation.py +139 -0
- aury/agents/middleware/types.py +81 -0
- aury/agents/plugin.py +162 -0
- aury/agents/react/__init__.py +4 -0
- aury/agents/react/agent.py +1923 -0
- aury/agents/sandbox/__init__.py +23 -0
- aury/agents/sandbox/local.py +239 -0
- aury/agents/sandbox/remote.py +200 -0
- aury/agents/sandbox/types.py +115 -0
- aury/agents/skill/__init__.py +16 -0
- aury/agents/skill/loader.py +180 -0
- aury/agents/skill/types.py +83 -0
- aury/agents/tool/__init__.py +39 -0
- aury/agents/tool/builtin/__init__.py +23 -0
- aury/agents/tool/builtin/ask_user.py +155 -0
- aury/agents/tool/builtin/bash.py +107 -0
- aury/agents/tool/builtin/delegate.py +726 -0
- aury/agents/tool/builtin/edit.py +121 -0
- aury/agents/tool/builtin/plan.py +277 -0
- aury/agents/tool/builtin/read.py +91 -0
- aury/agents/tool/builtin/thinking.py +111 -0
- aury/agents/tool/builtin/yield_result.py +130 -0
- aury/agents/tool/decorator.py +252 -0
- aury/agents/tool/set.py +204 -0
- aury/agents/usage/__init__.py +12 -0
- aury/agents/usage/tracker.py +236 -0
- aury/agents/workflow/__init__.py +85 -0
- aury/agents/workflow/adapter.py +268 -0
- aury/agents/workflow/dag.py +116 -0
- aury/agents/workflow/dsl.py +575 -0
- aury/agents/workflow/executor.py +659 -0
- aury/agents/workflow/expression.py +136 -0
- aury/agents/workflow/parser.py +182 -0
- aury/agents/workflow/state.py +145 -0
- aury/agents/workflow/types.py +86 -0
- aury_agent-0.0.4.dist-info/METADATA +90 -0
- aury_agent-0.0.4.dist-info/RECORD +149 -0
- aury_agent-0.0.4.dist-info/WHEEL +4 -0
- aury_agent-0.0.4.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""Memory compaction manager.
|
|
2
|
+
|
|
3
|
+
Handles automatic compaction of conversation history when token count
|
|
4
|
+
exceeds threshold. Ejected messages are summarized and stored in memory.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Protocol, Callable
|
|
10
|
+
from abc import abstractmethod
|
|
11
|
+
|
|
12
|
+
from ..core.logging import memory_logger as logger
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .manager import MemoryManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TokenCounter(Protocol):
|
|
19
|
+
"""Protocol for counting tokens in content."""
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def count(self, content: str) -> int:
|
|
23
|
+
"""Count tokens in content."""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def count_messages(self, messages: list[dict[str, Any]]) -> int:
|
|
28
|
+
"""Count tokens in messages."""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SimpleTokenCounter:
|
|
33
|
+
"""Simple word-based token estimator.
|
|
34
|
+
|
|
35
|
+
Approximates 1 token ≈ 4 characters for English text.
|
|
36
|
+
Good enough for threshold checking, not for billing.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, chars_per_token: float = 4.0):
|
|
40
|
+
self.chars_per_token = chars_per_token
|
|
41
|
+
|
|
42
|
+
def count(self, content: str) -> int:
|
|
43
|
+
return int(len(content) / self.chars_per_token)
|
|
44
|
+
|
|
45
|
+
def count_messages(self, messages: list[dict[str, Any]]) -> int:
|
|
46
|
+
total = 0
|
|
47
|
+
for msg in messages:
|
|
48
|
+
# Base overhead for role, etc
|
|
49
|
+
total += 4
|
|
50
|
+
|
|
51
|
+
content = msg.get("content", "")
|
|
52
|
+
if isinstance(content, str):
|
|
53
|
+
total += self.count(content)
|
|
54
|
+
elif isinstance(content, list):
|
|
55
|
+
# Multi-part content
|
|
56
|
+
for part in content:
|
|
57
|
+
if isinstance(part, dict):
|
|
58
|
+
if part.get("type") == "text":
|
|
59
|
+
total += self.count(part.get("text", ""))
|
|
60
|
+
elif part.get("type") == "image_url":
|
|
61
|
+
total += 85 # Rough estimate for image token
|
|
62
|
+
return total
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class Summarizer(Protocol):
|
|
66
|
+
"""Protocol for summarizing content."""
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
async def summarize(
|
|
70
|
+
self,
|
|
71
|
+
messages: list[dict[str, Any]],
|
|
72
|
+
existing_summary: str | None = None,
|
|
73
|
+
) -> str:
|
|
74
|
+
"""Summarize messages, optionally incorporating existing summary."""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SimpleSummarizer:
|
|
79
|
+
"""Simple extractive summarizer.
|
|
80
|
+
|
|
81
|
+
Extracts key information without LLM call.
|
|
82
|
+
For production, replace with LLM-based summarizer.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, max_length: int = 500):
|
|
86
|
+
self.max_length = max_length
|
|
87
|
+
|
|
88
|
+
async def summarize(
|
|
89
|
+
self,
|
|
90
|
+
messages: list[dict[str, Any]],
|
|
91
|
+
existing_summary: str | None = None,
|
|
92
|
+
) -> str:
|
|
93
|
+
parts = []
|
|
94
|
+
|
|
95
|
+
# Include existing summary
|
|
96
|
+
if existing_summary:
|
|
97
|
+
parts.append(f"Previous: {existing_summary}")
|
|
98
|
+
|
|
99
|
+
# Extract key points from messages
|
|
100
|
+
for msg in messages:
|
|
101
|
+
role = msg.get("role", "unknown")
|
|
102
|
+
content = msg.get("content", "")
|
|
103
|
+
|
|
104
|
+
if isinstance(content, list):
|
|
105
|
+
# Extract text parts
|
|
106
|
+
text_parts = [
|
|
107
|
+
p.get("text", "")
|
|
108
|
+
for p in content
|
|
109
|
+
if isinstance(p, dict) and p.get("type") == "text"
|
|
110
|
+
]
|
|
111
|
+
content = " ".join(text_parts)
|
|
112
|
+
|
|
113
|
+
# Skip empty content
|
|
114
|
+
if not content.strip():
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
# Truncate long content
|
|
118
|
+
if len(content) > 200:
|
|
119
|
+
content = content[:200] + "..."
|
|
120
|
+
|
|
121
|
+
parts.append(f"{role}: {content}")
|
|
122
|
+
|
|
123
|
+
result = "\n".join(parts)
|
|
124
|
+
|
|
125
|
+
# Final truncation
|
|
126
|
+
if len(result) > self.max_length:
|
|
127
|
+
result = result[:self.max_length] + "..."
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class CompactionConfig:
|
|
134
|
+
"""Configuration for compaction behavior."""
|
|
135
|
+
|
|
136
|
+
# Token thresholds
|
|
137
|
+
token_threshold: int = 8000 # Start compaction when exceeded
|
|
138
|
+
target_tokens: int = 4000 # Target after compaction
|
|
139
|
+
|
|
140
|
+
# Message handling
|
|
141
|
+
keep_system: bool = True # Always keep system messages
|
|
142
|
+
keep_recent: int = 4 # Always keep N most recent messages
|
|
143
|
+
|
|
144
|
+
# Summary behavior
|
|
145
|
+
update_summary: bool = True # Update session summary
|
|
146
|
+
store_ejected: bool = True # Store ejected messages in memory
|
|
147
|
+
|
|
148
|
+
# Extra configuration
|
|
149
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass
|
|
153
|
+
class CompactionResult:
|
|
154
|
+
"""Result of a compaction operation."""
|
|
155
|
+
|
|
156
|
+
compacted: bool # Whether compaction occurred
|
|
157
|
+
messages_before: int
|
|
158
|
+
messages_after: int
|
|
159
|
+
tokens_before: int
|
|
160
|
+
tokens_after: int
|
|
161
|
+
ejected_count: int
|
|
162
|
+
summary: str | None = None
|
|
163
|
+
error: str | None = None
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def no_action(cls, messages: int, tokens: int) -> "CompactionResult":
|
|
167
|
+
"""Create result for no compaction needed."""
|
|
168
|
+
return cls(
|
|
169
|
+
compacted=False,
|
|
170
|
+
messages_before=messages,
|
|
171
|
+
messages_after=messages,
|
|
172
|
+
tokens_before=tokens,
|
|
173
|
+
tokens_after=tokens,
|
|
174
|
+
ejected_count=0,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def with_error(cls, error: str, messages: int, tokens: int) -> "CompactionResult":
|
|
179
|
+
"""Create result for error case."""
|
|
180
|
+
return cls(
|
|
181
|
+
compacted=False,
|
|
182
|
+
messages_before=messages,
|
|
183
|
+
messages_after=messages,
|
|
184
|
+
tokens_before=tokens,
|
|
185
|
+
tokens_after=tokens,
|
|
186
|
+
ejected_count=0,
|
|
187
|
+
error=error,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class CompactionManager:
|
|
192
|
+
"""Manages compaction of conversation history.
|
|
193
|
+
|
|
194
|
+
When token count exceeds threshold:
|
|
195
|
+
1. Identifies messages to eject (keeping system + recent)
|
|
196
|
+
2. Summarizes ejected messages
|
|
197
|
+
3. Stores ejected content in memory
|
|
198
|
+
4. Returns compacted message list with summary injected
|
|
199
|
+
|
|
200
|
+
Usage:
|
|
201
|
+
compaction = CompactionManager(
|
|
202
|
+
config=CompactionConfig(token_threshold=8000),
|
|
203
|
+
memory_manager=memory,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Check and compact if needed
|
|
207
|
+
result, messages = await compaction.check_and_compact(
|
|
208
|
+
messages=messages,
|
|
209
|
+
session_id=session_id,
|
|
210
|
+
invocation_id=invocation_id,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if result.compacted:
|
|
214
|
+
print(f"Compacted {result.ejected_count} messages")
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
def __init__(
|
|
218
|
+
self,
|
|
219
|
+
config: CompactionConfig | None = None,
|
|
220
|
+
memory_manager: MemoryManager | None = None,
|
|
221
|
+
token_counter: TokenCounter | None = None,
|
|
222
|
+
summarizer: Summarizer | None = None,
|
|
223
|
+
):
|
|
224
|
+
self.config = config or CompactionConfig()
|
|
225
|
+
self.memory = memory_manager
|
|
226
|
+
self.token_counter = token_counter or SimpleTokenCounter()
|
|
227
|
+
self.summarizer = summarizer or SimpleSummarizer()
|
|
228
|
+
|
|
229
|
+
async def check_and_compact(
|
|
230
|
+
self,
|
|
231
|
+
messages: list[dict[str, Any]],
|
|
232
|
+
session_id: str | None = None,
|
|
233
|
+
invocation_id: str | None = None,
|
|
234
|
+
) -> tuple[CompactionResult, list[dict[str, Any]]]:
|
|
235
|
+
"""Check if compaction needed and perform if so.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
messages: Current message list
|
|
239
|
+
session_id: Session ID for memory storage
|
|
240
|
+
invocation_id: Invocation ID for tracking
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
Tuple of (result, possibly_compacted_messages)
|
|
244
|
+
"""
|
|
245
|
+
tokens_before = self.token_counter.count_messages(messages)
|
|
246
|
+
messages_before = len(messages)
|
|
247
|
+
|
|
248
|
+
# Check threshold
|
|
249
|
+
if tokens_before <= self.config.token_threshold:
|
|
250
|
+
return CompactionResult.no_action(messages_before, tokens_before), messages
|
|
251
|
+
|
|
252
|
+
logger.info(
|
|
253
|
+
f"Compaction triggered: {tokens_before} tokens > {self.config.token_threshold}"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Perform compaction
|
|
258
|
+
compacted, ejected, summary = await self._compact(
|
|
259
|
+
messages,
|
|
260
|
+
session_id,
|
|
261
|
+
invocation_id,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
tokens_after = self.token_counter.count_messages(compacted)
|
|
265
|
+
|
|
266
|
+
result = CompactionResult(
|
|
267
|
+
compacted=True,
|
|
268
|
+
messages_before=messages_before,
|
|
269
|
+
messages_after=len(compacted),
|
|
270
|
+
tokens_before=tokens_before,
|
|
271
|
+
tokens_after=tokens_after,
|
|
272
|
+
ejected_count=len(ejected),
|
|
273
|
+
summary=summary,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return result, compacted
|
|
277
|
+
|
|
278
|
+
except Exception as e:
|
|
279
|
+
logger.error(f"Compaction failed: {e}")
|
|
280
|
+
return CompactionResult.with_error(str(e), messages_before, tokens_before), messages
|
|
281
|
+
|
|
282
|
+
async def _compact(
|
|
283
|
+
self,
|
|
284
|
+
messages: list[dict[str, Any]],
|
|
285
|
+
session_id: str | None,
|
|
286
|
+
invocation_id: str | None,
|
|
287
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], str | None]:
|
|
288
|
+
"""Perform compaction.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Tuple of (compacted_messages, ejected_messages, summary)
|
|
292
|
+
"""
|
|
293
|
+
# Separate system messages
|
|
294
|
+
system_msgs = []
|
|
295
|
+
non_system_msgs = []
|
|
296
|
+
|
|
297
|
+
for msg in messages:
|
|
298
|
+
if msg.get("role") == "system" and self.config.keep_system:
|
|
299
|
+
system_msgs.append(msg)
|
|
300
|
+
else:
|
|
301
|
+
non_system_msgs.append(msg)
|
|
302
|
+
|
|
303
|
+
# Determine what to keep vs eject
|
|
304
|
+
keep_recent = self.config.keep_recent
|
|
305
|
+
|
|
306
|
+
if len(non_system_msgs) <= keep_recent:
|
|
307
|
+
# Nothing to eject
|
|
308
|
+
return messages, [], None
|
|
309
|
+
|
|
310
|
+
# Calculate how many to eject to reach target
|
|
311
|
+
target_eject = 0
|
|
312
|
+
cumulative_tokens = self.token_counter.count_messages(system_msgs)
|
|
313
|
+
|
|
314
|
+
# Start from most recent and work backwards to find cut point
|
|
315
|
+
kept_msgs = []
|
|
316
|
+
to_eject = []
|
|
317
|
+
|
|
318
|
+
# We need to keep at least keep_recent messages
|
|
319
|
+
for i, msg in enumerate(reversed(non_system_msgs)):
|
|
320
|
+
msg_tokens = self.token_counter.count_messages([msg])
|
|
321
|
+
|
|
322
|
+
if i < keep_recent:
|
|
323
|
+
# Always keep recent messages
|
|
324
|
+
kept_msgs.insert(0, msg)
|
|
325
|
+
cumulative_tokens += msg_tokens
|
|
326
|
+
elif cumulative_tokens + msg_tokens > self.config.target_tokens:
|
|
327
|
+
# Eject this message
|
|
328
|
+
to_eject.insert(0, msg)
|
|
329
|
+
else:
|
|
330
|
+
# Can still fit, keep it
|
|
331
|
+
kept_msgs.insert(0, msg)
|
|
332
|
+
cumulative_tokens += msg_tokens
|
|
333
|
+
|
|
334
|
+
if not to_eject:
|
|
335
|
+
return messages, [], None
|
|
336
|
+
|
|
337
|
+
# Get existing summary for incremental update
|
|
338
|
+
existing_summary = None
|
|
339
|
+
if self.memory and self.config.update_summary:
|
|
340
|
+
summary_obj = await self.memory.get_summary(session_id) if session_id else None
|
|
341
|
+
existing_summary = summary_obj.content if summary_obj else None
|
|
342
|
+
|
|
343
|
+
# Summarize ejected messages
|
|
344
|
+
summary = await self.summarizer.summarize(to_eject, existing_summary)
|
|
345
|
+
|
|
346
|
+
# Store ejected messages in memory
|
|
347
|
+
if self.memory and self.config.store_ejected and session_id:
|
|
348
|
+
await self.memory.on_compress(
|
|
349
|
+
session_id=session_id,
|
|
350
|
+
invocation_id=invocation_id or "",
|
|
351
|
+
ejected_messages=to_eject,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Update session summary
|
|
355
|
+
if self.config.update_summary:
|
|
356
|
+
await self.memory.update_summary(
|
|
357
|
+
session_id=session_id,
|
|
358
|
+
content=summary,
|
|
359
|
+
last_invocation_id=invocation_id or "",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Build compacted messages
|
|
363
|
+
# Inject summary as a system message
|
|
364
|
+
compacted = list(system_msgs)
|
|
365
|
+
|
|
366
|
+
if summary:
|
|
367
|
+
compacted.append({
|
|
368
|
+
"role": "system",
|
|
369
|
+
"content": f"[Conversation history summary]\n{summary}",
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
compacted.extend(kept_msgs)
|
|
373
|
+
|
|
374
|
+
return compacted, to_eject, summary
|
|
375
|
+
|
|
376
|
+
def should_compact(self, messages: list[dict[str, Any]]) -> bool:
|
|
377
|
+
"""Quick check if compaction might be needed."""
|
|
378
|
+
tokens = self.token_counter.count_messages(messages)
|
|
379
|
+
return tokens > self.config.token_threshold
|
|
380
|
+
|
|
381
|
+
def estimate_tokens(self, messages: list[dict[str, Any]]) -> int:
|
|
382
|
+
"""Estimate token count for messages."""
|
|
383
|
+
return self.token_counter.count_messages(messages)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
__all__ = [
|
|
387
|
+
"TokenCounter",
|
|
388
|
+
"SimpleTokenCounter",
|
|
389
|
+
"Summarizer",
|
|
390
|
+
"SimpleSummarizer",
|
|
391
|
+
"CompactionConfig",
|
|
392
|
+
"CompactionResult",
|
|
393
|
+
"CompactionManager",
|
|
394
|
+
]
|