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.
Files changed (128) hide show
  1. core/__init__.py +18 -0
  2. core/application.py +578 -0
  3. core/cli.py +193 -0
  4. core/commands/__init__.py +43 -0
  5. core/commands/executor.py +277 -0
  6. core/commands/menu_renderer.py +319 -0
  7. core/commands/parser.py +186 -0
  8. core/commands/registry.py +331 -0
  9. core/commands/system_commands.py +479 -0
  10. core/config/__init__.py +7 -0
  11. core/config/llm_task_config.py +110 -0
  12. core/config/loader.py +501 -0
  13. core/config/manager.py +112 -0
  14. core/config/plugin_config_manager.py +346 -0
  15. core/config/plugin_schema.py +424 -0
  16. core/config/service.py +399 -0
  17. core/effects/__init__.py +1 -0
  18. core/events/__init__.py +12 -0
  19. core/events/bus.py +129 -0
  20. core/events/executor.py +154 -0
  21. core/events/models.py +258 -0
  22. core/events/processor.py +176 -0
  23. core/events/registry.py +289 -0
  24. core/fullscreen/__init__.py +19 -0
  25. core/fullscreen/command_integration.py +290 -0
  26. core/fullscreen/components/__init__.py +12 -0
  27. core/fullscreen/components/animation.py +258 -0
  28. core/fullscreen/components/drawing.py +160 -0
  29. core/fullscreen/components/matrix_components.py +177 -0
  30. core/fullscreen/manager.py +302 -0
  31. core/fullscreen/plugin.py +204 -0
  32. core/fullscreen/renderer.py +282 -0
  33. core/fullscreen/session.py +324 -0
  34. core/io/__init__.py +52 -0
  35. core/io/buffer_manager.py +362 -0
  36. core/io/config_status_view.py +272 -0
  37. core/io/core_status_views.py +410 -0
  38. core/io/input_errors.py +313 -0
  39. core/io/input_handler.py +2655 -0
  40. core/io/input_mode_manager.py +402 -0
  41. core/io/key_parser.py +344 -0
  42. core/io/layout.py +587 -0
  43. core/io/message_coordinator.py +204 -0
  44. core/io/message_renderer.py +601 -0
  45. core/io/modal_interaction_handler.py +315 -0
  46. core/io/raw_input_processor.py +946 -0
  47. core/io/status_renderer.py +845 -0
  48. core/io/terminal_renderer.py +586 -0
  49. core/io/terminal_state.py +551 -0
  50. core/io/visual_effects.py +734 -0
  51. core/llm/__init__.py +26 -0
  52. core/llm/api_communication_service.py +863 -0
  53. core/llm/conversation_logger.py +473 -0
  54. core/llm/conversation_manager.py +414 -0
  55. core/llm/file_operations_executor.py +1401 -0
  56. core/llm/hook_system.py +402 -0
  57. core/llm/llm_service.py +1629 -0
  58. core/llm/mcp_integration.py +386 -0
  59. core/llm/message_display_service.py +450 -0
  60. core/llm/model_router.py +214 -0
  61. core/llm/plugin_sdk.py +396 -0
  62. core/llm/response_parser.py +848 -0
  63. core/llm/response_processor.py +364 -0
  64. core/llm/tool_executor.py +520 -0
  65. core/logging/__init__.py +19 -0
  66. core/logging/setup.py +208 -0
  67. core/models/__init__.py +5 -0
  68. core/models/base.py +23 -0
  69. core/plugins/__init__.py +13 -0
  70. core/plugins/collector.py +212 -0
  71. core/plugins/discovery.py +386 -0
  72. core/plugins/factory.py +263 -0
  73. core/plugins/registry.py +152 -0
  74. core/storage/__init__.py +5 -0
  75. core/storage/state_manager.py +84 -0
  76. core/ui/__init__.py +6 -0
  77. core/ui/config_merger.py +176 -0
  78. core/ui/config_widgets.py +369 -0
  79. core/ui/live_modal_renderer.py +276 -0
  80. core/ui/modal_actions.py +162 -0
  81. core/ui/modal_overlay_renderer.py +373 -0
  82. core/ui/modal_renderer.py +591 -0
  83. core/ui/modal_state_manager.py +443 -0
  84. core/ui/widget_integration.py +222 -0
  85. core/ui/widgets/__init__.py +27 -0
  86. core/ui/widgets/base_widget.py +136 -0
  87. core/ui/widgets/checkbox.py +85 -0
  88. core/ui/widgets/dropdown.py +140 -0
  89. core/ui/widgets/label.py +78 -0
  90. core/ui/widgets/slider.py +185 -0
  91. core/ui/widgets/text_input.py +224 -0
  92. core/utils/__init__.py +11 -0
  93. core/utils/config_utils.py +656 -0
  94. core/utils/dict_utils.py +212 -0
  95. core/utils/error_utils.py +275 -0
  96. core/utils/key_reader.py +171 -0
  97. core/utils/plugin_utils.py +267 -0
  98. core/utils/prompt_renderer.py +151 -0
  99. kollabor-0.4.9.dist-info/METADATA +298 -0
  100. kollabor-0.4.9.dist-info/RECORD +128 -0
  101. kollabor-0.4.9.dist-info/WHEEL +5 -0
  102. kollabor-0.4.9.dist-info/entry_points.txt +2 -0
  103. kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
  104. kollabor-0.4.9.dist-info/top_level.txt +4 -0
  105. kollabor_cli_main.py +20 -0
  106. plugins/__init__.py +1 -0
  107. plugins/enhanced_input/__init__.py +18 -0
  108. plugins/enhanced_input/box_renderer.py +103 -0
  109. plugins/enhanced_input/box_styles.py +142 -0
  110. plugins/enhanced_input/color_engine.py +165 -0
  111. plugins/enhanced_input/config.py +150 -0
  112. plugins/enhanced_input/cursor_manager.py +72 -0
  113. plugins/enhanced_input/geometry.py +81 -0
  114. plugins/enhanced_input/state.py +130 -0
  115. plugins/enhanced_input/text_processor.py +115 -0
  116. plugins/enhanced_input_plugin.py +385 -0
  117. plugins/fullscreen/__init__.py +9 -0
  118. plugins/fullscreen/example_plugin.py +327 -0
  119. plugins/fullscreen/matrix_plugin.py +132 -0
  120. plugins/hook_monitoring_plugin.py +1299 -0
  121. plugins/query_enhancer_plugin.py +350 -0
  122. plugins/save_conversation_plugin.py +502 -0
  123. plugins/system_commands_plugin.py +93 -0
  124. plugins/tmux_plugin.py +795 -0
  125. plugins/workflow_enforcement_plugin.py +629 -0
  126. system_prompt/default.md +1286 -0
  127. system_prompt/default_win.md +265 -0
  128. system_prompt/example_with_trender.md +47 -0
@@ -0,0 +1,364 @@
1
+ """Response processing and formatting for LLM outputs.
2
+
3
+ Handles response parsing, formatting, and special tag processing
4
+ for LLM responses including thinking tags and final responses.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import re
10
+ from typing import Any, Dict, List, Optional, Tuple
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class ResponseProcessor:
16
+ """Process and format LLM responses.
17
+
18
+ Handles parsing of special tags, response formatting,
19
+ and content extraction from raw LLM outputs.
20
+ """
21
+
22
+ def __init__(self):
23
+ """Initialize response processor."""
24
+ # Regex patterns for tag extraction
25
+ self.thinking_pattern = re.compile(
26
+ r'<think>(.*?)</think>',
27
+ re.DOTALL | re.IGNORECASE
28
+ )
29
+ self.terminal_pattern = re.compile(
30
+ r'<terminal>(.*?)</terminal>',
31
+ re.DOTALL | re.IGNORECASE
32
+ )
33
+
34
+ logger.info("Response processor initialized")
35
+
36
+ def process_response(self, raw_response: str) -> Dict[str, Any]:
37
+ """Process raw LLM response.
38
+
39
+ Args:
40
+ raw_response: Raw response from LLM
41
+
42
+ Returns:
43
+ Processed response with extracted components
44
+ """
45
+ # Extract components
46
+ thinking_content = self._extract_thinking(raw_response)
47
+ terminal_commands = self._extract_terminal_commands(raw_response)
48
+
49
+ # Clean main content
50
+ clean_content = self._clean_content(raw_response)
51
+
52
+ # Determine response type
53
+ response_type = self._determine_response_type(
54
+ raw_response,
55
+ thinking_content,
56
+ terminal_commands
57
+ )
58
+
59
+ processed = {
60
+ "type": response_type,
61
+ "content": clean_content,
62
+ "raw": raw_response,
63
+ "components": {
64
+ "thinking": thinking_content,
65
+ "terminal_commands": terminal_commands
66
+ },
67
+ "metadata": {
68
+ "has_thinking": bool(thinking_content),
69
+ "has_terminal_commands": bool(terminal_commands),
70
+ "word_count": len(clean_content.split()),
71
+ "char_count": len(clean_content)
72
+ }
73
+ }
74
+
75
+ logger.debug(f"Processed response: type={response_type}, components={len(processed['components'])}")
76
+ return processed
77
+
78
+ def _extract_thinking(self, content: str) -> List[str]:
79
+ """Extract thinking tags from content.
80
+
81
+ Args:
82
+ content: Raw content with potential thinking tags
83
+
84
+ Returns:
85
+ List of thinking content blocks
86
+ """
87
+ matches = self.thinking_pattern.findall(content)
88
+ return [match.strip() for match in matches if match.strip()]
89
+
90
+ def _extract_terminal_commands(self, content: str) -> List[str]:
91
+ """Extract terminal commands from content.
92
+
93
+ Args:
94
+ content: Raw content with potential terminal tags
95
+
96
+ Returns:
97
+ List of terminal commands
98
+ """
99
+ matches = self.terminal_pattern.findall(content)
100
+ return [match.strip() for match in matches if match.strip()]
101
+
102
+ def _extract_tool_calls(self, content: str) -> List[Dict[str, Any]]:
103
+ """Extract tool calls from content.
104
+
105
+ Args:
106
+ content: Raw content with potential tool calls
107
+
108
+ Returns:
109
+ List of parsed tool calls
110
+ """
111
+ tool_calls = []
112
+ matches = self.tool_call_pattern.findall(content)
113
+
114
+ for match in matches:
115
+ try:
116
+ # Try to parse as JSON
117
+ tool_data = json.loads(match)
118
+ tool_calls.append(tool_data)
119
+ except json.JSONDecodeError:
120
+ # Parse as plain text command
121
+ lines = match.strip().split('\n')
122
+ if lines:
123
+ tool_calls.append({
124
+ "command": lines[0],
125
+ "raw": match
126
+ })
127
+
128
+ return tool_calls
129
+
130
+ def _extract_code_blocks(self, content: str) -> List[Dict[str, str]]:
131
+ """Extract code blocks from content.
132
+
133
+ Args:
134
+ content: Raw content with potential code blocks
135
+
136
+ Returns:
137
+ List of code blocks with language and content
138
+ """
139
+ code_blocks = []
140
+ matches = self.code_block_pattern.findall(content)
141
+
142
+ for language, code in matches:
143
+ code_blocks.append({
144
+ "language": language or "plain",
145
+ "code": code.strip()
146
+ })
147
+
148
+ return code_blocks
149
+
150
+ def _clean_content(self, content: str) -> str:
151
+ """Remove special tags from content.
152
+
153
+ Args:
154
+ content: Raw content with tags
155
+
156
+ Returns:
157
+ Cleaned content without special tags
158
+ """
159
+ # Remove thinking tags
160
+ cleaned = self.thinking_pattern.sub('', content)
161
+
162
+ # Remove final response tags but keep content
163
+ final_match = self.final_response_pattern.search(cleaned)
164
+ if final_match:
165
+ cleaned = self.final_response_pattern.sub(final_match.group(1), cleaned)
166
+
167
+ # Remove tool call tags
168
+ cleaned = self.tool_call_pattern.sub('', cleaned)
169
+
170
+ # Clean up extra whitespace
171
+ cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
172
+ cleaned = cleaned.strip()
173
+
174
+ return cleaned
175
+
176
+ def _determine_response_type(self, raw: str, thinking: List[str],
177
+ final: Optional[str], tools: List[Dict]) -> str:
178
+ """Determine the type of response.
179
+
180
+ Args:
181
+ raw: Raw response content
182
+ thinking: Extracted thinking content
183
+ final: Extracted final response
184
+ tools: Extracted tool calls
185
+
186
+ Returns:
187
+ Response type identifier
188
+ """
189
+ if final:
190
+ return "final"
191
+ elif tools:
192
+ return "tool_execution"
193
+ elif thinking:
194
+ return "reasoning"
195
+ elif "?" in raw:
196
+ return "question"
197
+ elif len(raw) < 100:
198
+ return "brief"
199
+ else:
200
+ return "standard"
201
+
202
+ def format_for_display(self, processed_response: Dict[str, Any]) -> str:
203
+ """Format processed response for display.
204
+
205
+ Args:
206
+ processed_response: Processed response data
207
+
208
+ Returns:
209
+ Formatted string for display
210
+ """
211
+ components = processed_response.get("components", {})
212
+
213
+ # Build display string
214
+ display_parts = []
215
+
216
+ # Add thinking if present (dimmed)
217
+ thinking = components.get("thinking", [])
218
+ if thinking:
219
+ for thought in thinking:
220
+ display_parts.append(f"[dim]{thought}[/dim]")
221
+ display_parts.append("")
222
+
223
+ # Add main content or final response
224
+ if components.get("final_response"):
225
+ display_parts.append(components["final_response"])
226
+ else:
227
+ display_parts.append(processed_response.get("content", ""))
228
+
229
+ return "\n".join(display_parts)
230
+
231
+ def extract_metrics(self, processed_response: Dict[str, Any]) -> Dict[str, Any]:
232
+ """Extract metrics from processed response.
233
+
234
+ Args:
235
+ processed_response: Processed response data
236
+
237
+ Returns:
238
+ Response metrics
239
+ """
240
+ metadata = processed_response.get("metadata", {})
241
+ components = processed_response.get("components", {})
242
+
243
+ metrics = {
244
+ "response_type": processed_response.get("type", "unknown"),
245
+ "word_count": metadata.get("word_count", 0),
246
+ "char_count": metadata.get("char_count", 0),
247
+ "thinking_blocks": len(components.get("thinking", [])),
248
+ "code_blocks": len(components.get("code_blocks", [])),
249
+ "tool_calls": len(components.get("tool_calls", [])),
250
+ "has_final_response": metadata.get("has_final_response", False),
251
+ "complexity": self._assess_complexity(processed_response)
252
+ }
253
+
254
+ return metrics
255
+
256
+ def _assess_complexity(self, processed_response: Dict[str, Any]) -> str:
257
+ """Assess response complexity.
258
+
259
+ Args:
260
+ processed_response: Processed response data
261
+
262
+ Returns:
263
+ Complexity level (simple, moderate, complex)
264
+ """
265
+ metadata = processed_response.get("metadata", {})
266
+
267
+ # Calculate complexity score
268
+ score = 0
269
+
270
+ if metadata.get("word_count", 0) > 200:
271
+ score += 2
272
+ elif metadata.get("word_count", 0) > 100:
273
+ score += 1
274
+
275
+ if metadata.get("has_thinking"):
276
+ score += 2
277
+
278
+ if metadata.get("has_code"):
279
+ score += 1
280
+
281
+ if metadata.get("has_tool_calls"):
282
+ score += 1
283
+
284
+ # Map score to complexity level
285
+ if score >= 4:
286
+ return "complex"
287
+ elif score >= 2:
288
+ return "moderate"
289
+ else:
290
+ return "simple"
291
+
292
+ def merge_streaming_chunks(self, chunks: List[str]) -> str:
293
+ """Merge streaming response chunks.
294
+
295
+ Args:
296
+ chunks: List of response chunks
297
+
298
+ Returns:
299
+ Merged response content
300
+ """
301
+ # Join chunks
302
+ merged = "".join(chunks)
303
+
304
+ # Fix any broken tags from chunking
305
+ merged = self._fix_broken_tags(merged)
306
+
307
+ return merged
308
+
309
+ def _fix_broken_tags(self, content: str) -> str:
310
+ """Fix broken tags from streaming.
311
+
312
+ Args:
313
+ content: Content with potentially broken tags
314
+
315
+ Returns:
316
+ Content with fixed tags
317
+ """
318
+ # Fix broken thinking tags
319
+ content = re.sub(r'<thi\s*nk>', '<think>', content, flags=re.IGNORECASE)
320
+ content = re.sub(r'</thi\s*nk>', '</think>', content, flags=re.IGNORECASE)
321
+
322
+ # Fix broken final response tags
323
+ content = re.sub(r'<final_res\s*ponse>', '<final_response>', content, flags=re.IGNORECASE)
324
+ content = re.sub(r'</final_res\s*ponse>', '</final_response>', content, flags=re.IGNORECASE)
325
+
326
+ return content
327
+
328
+ def validate_response(self, response: str) -> Tuple[bool, List[str]]:
329
+ """Validate response format and content.
330
+
331
+ Args:
332
+ response: Response to validate
333
+
334
+ Returns:
335
+ Tuple of (is_valid, list_of_issues)
336
+ """
337
+ issues = []
338
+
339
+ # Check for unclosed tags
340
+ if '<think>' in response and '</think>' not in response:
341
+ issues.append("Unclosed thinking tag")
342
+
343
+ if '<final_response>' in response and '</final_response>' not in response:
344
+ issues.append("Unclosed final response tag")
345
+
346
+ # Check for empty response
347
+ if not response.strip():
348
+ issues.append("Empty response")
349
+
350
+ # Check for truncation indicators
351
+ if response.endswith('...') and len(response) > 1000:
352
+ issues.append("Response appears truncated")
353
+
354
+ # Check for malformed JSON in tool calls
355
+ tool_matches = self.tool_call_pattern.findall(response)
356
+ for match in tool_matches:
357
+ if match.strip().startswith('{'):
358
+ try:
359
+ json.loads(match)
360
+ except json.JSONDecodeError:
361
+ issues.append("Malformed JSON in tool call")
362
+
363
+ is_valid = len(issues) == 0
364
+ return is_valid, issues