kollabor 0.4.9__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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""Conversation management for LLM core service.
|
|
2
|
+
|
|
3
|
+
Manages conversation state, history, context windows,
|
|
4
|
+
and message threading for LLM interactions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
|
+
from uuid import uuid4
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConversationManager:
|
|
18
|
+
"""Manage conversation state and history.
|
|
19
|
+
|
|
20
|
+
Handles message storage, context windows, conversation threading,
|
|
21
|
+
and session management for LLM interactions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config, conversation_logger=None):
|
|
25
|
+
"""Initialize conversation manager.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
config: Configuration manager
|
|
29
|
+
conversation_logger: Optional conversation logger instance
|
|
30
|
+
"""
|
|
31
|
+
self.config = config
|
|
32
|
+
self.conversation_logger = conversation_logger
|
|
33
|
+
|
|
34
|
+
# Conversation state
|
|
35
|
+
self.current_session_id = str(uuid4())
|
|
36
|
+
self.messages = []
|
|
37
|
+
self.message_index = {} # uuid -> message lookup
|
|
38
|
+
self.context_window = []
|
|
39
|
+
|
|
40
|
+
# Configuration
|
|
41
|
+
self.max_history = config.get("core.llm.max_history", 50)
|
|
42
|
+
self.max_context_tokens = config.get("core.llm.max_context_tokens", 4000)
|
|
43
|
+
self.save_conversations = config.get("core.llm.save_conversations", True)
|
|
44
|
+
|
|
45
|
+
# Conversation storage
|
|
46
|
+
from ..utils.config_utils import get_config_directory
|
|
47
|
+
config_dir = get_config_directory()
|
|
48
|
+
self.conversations_dir = config_dir / "conversations"
|
|
49
|
+
self.conversations_dir.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
# Current conversation metadata
|
|
52
|
+
self.current_parent_uuid = None # Track parent UUID for message threading
|
|
53
|
+
|
|
54
|
+
self.conversation_metadata = {
|
|
55
|
+
"started_at": datetime.now().isoformat(),
|
|
56
|
+
"message_count": 0,
|
|
57
|
+
"turn_count": 0,
|
|
58
|
+
"topics": [],
|
|
59
|
+
"model_used": None
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
logger.info(f"Conversation manager initialized with session: {self.current_session_id}")
|
|
63
|
+
|
|
64
|
+
def add_message(self, role: str, content: str,
|
|
65
|
+
parent_uuid: Optional[str] = None,
|
|
66
|
+
metadata: Optional[Dict[str, Any]] = None) -> str:
|
|
67
|
+
"""Add a message to the conversation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
role: Message role (user, assistant, system)
|
|
71
|
+
content: Message content
|
|
72
|
+
parent_uuid: UUID of parent message for threading
|
|
73
|
+
metadata: Optional message metadata
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
UUID of the added message
|
|
77
|
+
"""
|
|
78
|
+
message_uuid = str(uuid4())
|
|
79
|
+
timestamp = datetime.now().isoformat()
|
|
80
|
+
|
|
81
|
+
# Update current_parent_uuid for next message
|
|
82
|
+
if parent_uuid:
|
|
83
|
+
self.current_parent_uuid = parent_uuid
|
|
84
|
+
|
|
85
|
+
message = {
|
|
86
|
+
"uuid": message_uuid,
|
|
87
|
+
"role": role,
|
|
88
|
+
"content": content,
|
|
89
|
+
"timestamp": timestamp,
|
|
90
|
+
"parent_uuid": parent_uuid or self.current_parent_uuid,
|
|
91
|
+
"metadata": metadata or {},
|
|
92
|
+
"session_id": self.current_session_id
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Add to messages list
|
|
96
|
+
self.messages.append(message)
|
|
97
|
+
self.message_index[message_uuid] = message
|
|
98
|
+
|
|
99
|
+
# Update context window
|
|
100
|
+
self._update_context_window()
|
|
101
|
+
|
|
102
|
+
# Update metadata
|
|
103
|
+
self.conversation_metadata["message_count"] += 1
|
|
104
|
+
if role == "user":
|
|
105
|
+
self.conversation_metadata["turn_count"] += 1
|
|
106
|
+
|
|
107
|
+
# Log to conversation logger if available
|
|
108
|
+
if self.conversation_logger:
|
|
109
|
+
self.conversation_logger.log_message(
|
|
110
|
+
role=role,
|
|
111
|
+
content=content,
|
|
112
|
+
parent_uuid=parent_uuid,
|
|
113
|
+
metadata=metadata
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Auto-save if configured
|
|
117
|
+
if self.save_conversations and len(self.messages) % 10 == 0:
|
|
118
|
+
self.save_conversation()
|
|
119
|
+
|
|
120
|
+
logger.debug(f"Added {role} message: {message_uuid}")
|
|
121
|
+
return message_uuid
|
|
122
|
+
|
|
123
|
+
def get_context_messages(self, max_messages: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
124
|
+
"""Get messages for LLM context.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
max_messages: Maximum number of messages to return
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
List of messages for context
|
|
131
|
+
"""
|
|
132
|
+
if max_messages:
|
|
133
|
+
return self.messages[-max_messages:]
|
|
134
|
+
return self.context_window
|
|
135
|
+
|
|
136
|
+
def _update_context_window(self):
|
|
137
|
+
"""Update the context window with recent messages."""
|
|
138
|
+
# Simple sliding window for now
|
|
139
|
+
# TODO: Implement token counting for precise context management
|
|
140
|
+
self.context_window = self.messages[-self.max_history:]
|
|
141
|
+
|
|
142
|
+
# Ensure we have system message if it exists
|
|
143
|
+
system_messages = [m for m in self.messages if m["role"] == "system"]
|
|
144
|
+
if system_messages and system_messages[0] not in self.context_window:
|
|
145
|
+
# Prepend system message
|
|
146
|
+
self.context_window = [system_messages[0]] + self.context_window
|
|
147
|
+
|
|
148
|
+
def _get_last_message_uuid(self) -> Optional[str]:
|
|
149
|
+
"""Get UUID of the last message."""
|
|
150
|
+
if self.messages:
|
|
151
|
+
return self.messages[-1]["uuid"]
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
def get_message_thread(self, message_uuid: str) -> List[Dict[str, Any]]:
|
|
155
|
+
"""Get the thread of messages leading to a specific message.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
message_uuid: UUID of the target message
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
List of messages in the thread
|
|
162
|
+
"""
|
|
163
|
+
thread = []
|
|
164
|
+
current_uuid = message_uuid
|
|
165
|
+
|
|
166
|
+
while current_uuid:
|
|
167
|
+
if current_uuid in self.message_index:
|
|
168
|
+
message = self.message_index[current_uuid]
|
|
169
|
+
thread.insert(0, message)
|
|
170
|
+
current_uuid = message.get("parent_uuid")
|
|
171
|
+
else:
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
return thread
|
|
175
|
+
|
|
176
|
+
def search_messages(self, query: str, role: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
177
|
+
"""Search messages by content.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
query: Search query
|
|
181
|
+
role: Optional role filter
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
List of matching messages
|
|
185
|
+
"""
|
|
186
|
+
results = []
|
|
187
|
+
query_lower = query.lower()
|
|
188
|
+
|
|
189
|
+
for message in self.messages:
|
|
190
|
+
if role and message["role"] != role:
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
if query_lower in message["content"].lower():
|
|
194
|
+
results.append(message)
|
|
195
|
+
|
|
196
|
+
return results
|
|
197
|
+
|
|
198
|
+
def get_conversation_summary(self) -> Dict[str, Any]:
|
|
199
|
+
"""Get summary of current conversation.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Conversation summary statistics
|
|
203
|
+
"""
|
|
204
|
+
user_messages = [m for m in self.messages if m["role"] == "user"]
|
|
205
|
+
assistant_messages = [m for m in self.messages if m["role"] == "assistant"]
|
|
206
|
+
|
|
207
|
+
# Extract topics from messages
|
|
208
|
+
topics = self._extract_topics()
|
|
209
|
+
|
|
210
|
+
summary = {
|
|
211
|
+
"session_id": self.current_session_id,
|
|
212
|
+
"total_messages": len(self.messages),
|
|
213
|
+
"user_messages": len(user_messages),
|
|
214
|
+
"assistant_messages": len(assistant_messages),
|
|
215
|
+
"turn_count": self.conversation_metadata["turn_count"],
|
|
216
|
+
"started_at": self.conversation_metadata["started_at"],
|
|
217
|
+
"duration": self._calculate_duration(),
|
|
218
|
+
"topics": topics,
|
|
219
|
+
"average_message_length": self._calculate_avg_message_length(),
|
|
220
|
+
"context_usage": f"{len(self.context_window)}/{self.max_history}"
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return summary
|
|
224
|
+
|
|
225
|
+
def _extract_topics(self) -> List[str]:
|
|
226
|
+
"""Extract main topics from conversation."""
|
|
227
|
+
# Simple keyword extraction for now
|
|
228
|
+
# TODO: Implement more sophisticated topic extraction
|
|
229
|
+
topics = []
|
|
230
|
+
|
|
231
|
+
# Common technical keywords to look for
|
|
232
|
+
keywords = ["error", "bug", "feature", "implement", "fix", "create",
|
|
233
|
+
"update", "delete", "configure", "install", "debug"]
|
|
234
|
+
|
|
235
|
+
all_content = " ".join([m["content"] for m in self.messages])
|
|
236
|
+
all_content_lower = all_content.lower()
|
|
237
|
+
|
|
238
|
+
for keyword in keywords:
|
|
239
|
+
if keyword in all_content_lower:
|
|
240
|
+
topics.append(keyword)
|
|
241
|
+
|
|
242
|
+
return topics[:5] # Return top 5 topics
|
|
243
|
+
|
|
244
|
+
def _calculate_duration(self) -> str:
|
|
245
|
+
"""Calculate conversation duration."""
|
|
246
|
+
if not self.messages:
|
|
247
|
+
return "0m"
|
|
248
|
+
|
|
249
|
+
start = datetime.fromisoformat(self.messages[0]["timestamp"])
|
|
250
|
+
end = datetime.fromisoformat(self.messages[-1]["timestamp"])
|
|
251
|
+
duration = end - start
|
|
252
|
+
|
|
253
|
+
minutes = duration.total_seconds() / 60
|
|
254
|
+
if minutes < 60:
|
|
255
|
+
return f"{int(minutes)}m"
|
|
256
|
+
else:
|
|
257
|
+
hours = minutes / 60
|
|
258
|
+
return f"{hours:.1f}h"
|
|
259
|
+
|
|
260
|
+
def _calculate_avg_message_length(self) -> int:
|
|
261
|
+
"""Calculate average message length."""
|
|
262
|
+
if not self.messages:
|
|
263
|
+
return 0
|
|
264
|
+
|
|
265
|
+
total_length = sum(len(m["content"]) for m in self.messages)
|
|
266
|
+
return total_length // len(self.messages)
|
|
267
|
+
|
|
268
|
+
def save_conversation(self, filename: Optional[str] = None) -> Path:
|
|
269
|
+
"""Save current conversation to file.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
filename: Optional custom filename
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Path to saved conversation file
|
|
276
|
+
"""
|
|
277
|
+
if not filename:
|
|
278
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
279
|
+
filename = f"conversation_{self.current_session_id[:8]}_{timestamp}.json"
|
|
280
|
+
|
|
281
|
+
filepath = self.conversations_dir / filename
|
|
282
|
+
|
|
283
|
+
conversation_data = {
|
|
284
|
+
"metadata": self.conversation_metadata,
|
|
285
|
+
"summary": self.get_conversation_summary(),
|
|
286
|
+
"messages": self.messages
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
with open(filepath, 'w') as f:
|
|
290
|
+
json.dump(conversation_data, f, indent=2)
|
|
291
|
+
|
|
292
|
+
logger.info(f"Saved conversation to: {filepath}")
|
|
293
|
+
return filepath
|
|
294
|
+
|
|
295
|
+
def load_conversation(self, filepath: Path) -> bool:
|
|
296
|
+
"""Load a conversation from file.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
filepath: Path to conversation file
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
True if loaded successfully
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
with open(filepath, 'r') as f:
|
|
306
|
+
data = json.load(f)
|
|
307
|
+
|
|
308
|
+
self.messages = data.get("messages", [])
|
|
309
|
+
self.conversation_metadata = data.get("metadata", {})
|
|
310
|
+
|
|
311
|
+
# Rebuild message index
|
|
312
|
+
self.message_index = {m["uuid"]: m for m in self.messages}
|
|
313
|
+
|
|
314
|
+
# Update context window
|
|
315
|
+
self._update_context_window()
|
|
316
|
+
|
|
317
|
+
logger.info(f"Loaded conversation from: {filepath}")
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
except Exception as e:
|
|
321
|
+
logger.error(f"Failed to load conversation: {e}")
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
def clear_conversation(self):
|
|
325
|
+
"""Clear current conversation and start fresh."""
|
|
326
|
+
# Save current conversation if it has messages
|
|
327
|
+
if self.messages and self.save_conversations:
|
|
328
|
+
self.save_conversation()
|
|
329
|
+
|
|
330
|
+
# Reset state
|
|
331
|
+
self.current_session_id = str(uuid4())
|
|
332
|
+
self.messages = []
|
|
333
|
+
self.message_index = {}
|
|
334
|
+
self.context_window = []
|
|
335
|
+
|
|
336
|
+
# Reset metadata
|
|
337
|
+
self.current_parent_uuid = None # Track parent UUID for message threading
|
|
338
|
+
|
|
339
|
+
self.conversation_metadata = {
|
|
340
|
+
"started_at": datetime.now().isoformat(),
|
|
341
|
+
"message_count": 0,
|
|
342
|
+
"turn_count": 0,
|
|
343
|
+
"topics": [],
|
|
344
|
+
"model_used": None
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
logger.info(f"Cleared conversation, new session: {self.current_session_id}")
|
|
348
|
+
|
|
349
|
+
def export_for_training(self) -> List[Dict[str, str]]:
|
|
350
|
+
"""Export conversation in format suitable for model training.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
List of message pairs for training
|
|
354
|
+
"""
|
|
355
|
+
training_data = []
|
|
356
|
+
|
|
357
|
+
for i in range(0, len(self.messages) - 1, 2):
|
|
358
|
+
if (self.messages[i]["role"] == "user" and
|
|
359
|
+
i + 1 < len(self.messages) and
|
|
360
|
+
self.messages[i + 1]["role"] == "assistant"):
|
|
361
|
+
|
|
362
|
+
training_data.append({
|
|
363
|
+
"instruction": self.messages[i]["content"],
|
|
364
|
+
"response": self.messages[i + 1]["content"]
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return training_data
|
|
368
|
+
|
|
369
|
+
def get_conversation_stats(self) -> Dict[str, Any]:
|
|
370
|
+
"""Get detailed conversation statistics.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Detailed statistics about the conversation
|
|
374
|
+
"""
|
|
375
|
+
stats = {
|
|
376
|
+
"session": {
|
|
377
|
+
"id": self.current_session_id,
|
|
378
|
+
"started": self.conversation_metadata["started_at"],
|
|
379
|
+
"duration": self._calculate_duration()
|
|
380
|
+
},
|
|
381
|
+
"messages": {
|
|
382
|
+
"total": len(self.messages),
|
|
383
|
+
"by_role": {},
|
|
384
|
+
"average_length": self._calculate_avg_message_length(),
|
|
385
|
+
"shortest": min((len(m["content"]) for m in self.messages), default=0),
|
|
386
|
+
"longest": max((len(m["content"]) for m in self.messages), default=0)
|
|
387
|
+
},
|
|
388
|
+
"context": {
|
|
389
|
+
"window_size": len(self.context_window),
|
|
390
|
+
"max_size": self.max_history,
|
|
391
|
+
"utilization": f"{(len(self.context_window) / self.max_history * 100):.1f}%"
|
|
392
|
+
},
|
|
393
|
+
"threading": {
|
|
394
|
+
"unique_threads": len(set(m.get("parent_uuid") for m in self.messages)),
|
|
395
|
+
"max_thread_depth": self._calculate_max_thread_depth()
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
# Count messages by role
|
|
400
|
+
for message in self.messages:
|
|
401
|
+
role = message["role"]
|
|
402
|
+
stats["messages"]["by_role"][role] = stats["messages"]["by_role"].get(role, 0) + 1
|
|
403
|
+
|
|
404
|
+
return stats
|
|
405
|
+
|
|
406
|
+
def _calculate_max_thread_depth(self) -> int:
|
|
407
|
+
"""Calculate maximum thread depth in conversation."""
|
|
408
|
+
max_depth = 0
|
|
409
|
+
|
|
410
|
+
for message in self.messages:
|
|
411
|
+
depth = len(self.get_message_thread(message["uuid"]))
|
|
412
|
+
max_depth = max(max_depth, depth)
|
|
413
|
+
|
|
414
|
+
return max_depth
|