kollabor 0.4.9__py3-none-any.whl → 0.4.15__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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -16,6 +16,9 @@ from typing import Any, Dict, List, Optional
16
16
 
17
17
  import aiohttp
18
18
 
19
+ from .api_adapters import get_adapter, BaseAPIAdapter
20
+ from .profile_manager import LLMProfile
21
+
19
22
  logger = logging.getLogger(__name__)
20
23
 
21
24
 
@@ -29,77 +32,25 @@ class APICommunicationService:
29
32
  Eliminates API concerns from the main LLM service class.
30
33
  """
31
34
 
32
- def __init__(self, config, raw_conversations_dir):
35
+ def __init__(self, config, raw_conversations_dir, profile: LLMProfile):
33
36
  """Initialize API communication service.
34
37
 
35
38
  Args:
36
39
  config: Configuration manager for API settings
37
40
  raw_conversations_dir: Directory for raw interaction logs
41
+ profile: LLM profile with configuration (resolves env vars -> config -> defaults)
38
42
  """
39
43
  self.config = config
40
44
  self.raw_conversations_dir = raw_conversations_dir
41
45
 
42
- # Load API configuration (environment variables take precedence over config)
43
-
44
- # API endpoint/URL
45
- self.api_url = os.environ.get("KOLLABOR_API_ENDPOINT") or config.get("core.llm.api_url", "http://localhost:1234")
46
- if os.environ.get("KOLLABOR_API_ENDPOINT"):
47
- logger.debug("Using API endpoint from KOLLABOR_API_ENDPOINT environment variable")
48
-
49
- # Model name
50
- self.model = os.environ.get("KOLLABOR_API_MODEL") or config.get("core.llm.model", "qwen/qwen3-4b")
51
- if os.environ.get("KOLLABOR_API_MODEL"):
52
- logger.debug("Using model from KOLLABOR_API_MODEL environment variable")
53
-
54
- # Temperature (with type conversion)
55
- env_temperature = os.environ.get("KOLLABOR_API_TEMPERATURE")
56
- if env_temperature:
57
- try:
58
- self.temperature = float(env_temperature)
59
- logger.debug("Using temperature from KOLLABOR_API_TEMPERATURE environment variable")
60
- except ValueError:
61
- logger.warning(f"Invalid KOLLABOR_API_TEMPERATURE value: {env_temperature}, using config/default")
62
- self.temperature = config.get("core.llm.temperature", 0.7)
63
- else:
64
- self.temperature = config.get("core.llm.temperature", 0.7)
65
-
66
- # Timeout (with type conversion)
67
- env_timeout = os.environ.get("KOLLABOR_API_TIMEOUT")
68
- if env_timeout:
69
- try:
70
- self.timeout = int(env_timeout)
71
- logger.debug("Using timeout from KOLLABOR_API_TIMEOUT environment variable")
72
- except ValueError:
73
- logger.warning(f"Invalid KOLLABOR_API_TIMEOUT value: {env_timeout}, using config/default")
74
- self.timeout = config.get("core.llm.timeout", 30000)
75
- else:
76
- self.timeout = config.get("core.llm.timeout", 30000)
46
+ # Initialize from profile (resolves env vars through profile's getter methods)
47
+ self.update_from_profile(profile)
77
48
 
78
- # Streaming (not typically set via env, kept for completeness)
49
+ # Streaming (from config, not profile-specific)
79
50
  self.enable_streaming = config.get("core.llm.enable_streaming", False)
80
51
 
81
- # Max tokens (with type conversion)
82
- env_max_tokens = os.environ.get("KOLLABOR_API_MAX_TOKENS")
83
- if env_max_tokens:
84
- try:
85
- self.max_tokens = int(env_max_tokens)
86
- logger.debug("Using max tokens from KOLLABOR_API_MAX_TOKENS environment variable")
87
- except ValueError:
88
- logger.warning(f"Invalid KOLLABOR_API_MAX_TOKENS value: {env_max_tokens}, using config/default")
89
- self.max_tokens = config.get("core.llm.max_tokens", None)
90
- else:
91
- self.max_tokens = config.get("core.llm.max_tokens", None)
92
-
93
- # API token (supports both KOLLABOR_API_TOKEN and KOLLABOR_API_KEY)
94
- self.api_token = (
95
- os.environ.get("KOLLABOR_API_TOKEN")
96
- or os.environ.get("KOLLABOR_API_KEY")
97
- or config.get("core.llm.api_token")
98
- )
99
- if os.environ.get("KOLLABOR_API_TOKEN"):
100
- logger.debug("Using API token from KOLLABOR_API_TOKEN environment variable")
101
- elif os.environ.get("KOLLABOR_API_KEY"):
102
- logger.debug("Using API token from KOLLABOR_API_KEY environment variable")
52
+ # Session tracking for raw log linking
53
+ self.current_session_id: Optional[str] = None
103
54
 
104
55
  # HTTP session state with enhanced lifecycle management
105
56
  self.session = None
@@ -114,6 +65,10 @@ class APICommunicationService:
114
65
  # Token usage tracking
115
66
  self.last_token_usage = {}
116
67
 
68
+ # Native tool calling support
69
+ self.last_tool_calls = [] # Tool calls from last response
70
+ self.last_stop_reason = "" # Stop reason from last response
71
+
117
72
  # Resource monitoring and statistics
118
73
  self._connection_stats = {
119
74
  'total_requests': 0,
@@ -124,13 +79,56 @@ class APICommunicationService:
124
79
  'connection_errors': 0
125
80
  }
126
81
 
127
- logger.info(f"API service initialized for {self.api_url}")
128
-
129
- async def initialize(self):
82
+ logger.info(f"API service initialized for {self.api_url} (profile: {profile.name})")
83
+
84
+ def set_session_id(self, session_id: str) -> None:
85
+ """Set current session ID for raw log linking.
86
+
87
+ Args:
88
+ session_id: Session identifier from conversation logger
89
+ """
90
+ self.current_session_id = session_id
91
+ logger.debug(f"API service session ID set to: {session_id}")
92
+
93
+ def update_from_profile(self, profile: LLMProfile) -> None:
94
+ """Update API settings from a profile.
95
+
96
+ Uses profile getter methods that resolve env var -> config -> default.
97
+
98
+ Args:
99
+ profile: LLMProfile with configuration
100
+ """
101
+ old_url = getattr(self, 'api_url', None)
102
+ old_model = getattr(self, 'model', None)
103
+ old_format = getattr(self, 'tool_format', 'openai')
104
+
105
+ # Get all values from profile (getter methods resolve env vars)
106
+ self.api_url = profile.get_endpoint()
107
+ self.model = profile.get_model()
108
+ self.temperature = profile.get_temperature()
109
+ self.tool_format = profile.get_tool_format()
110
+ self.max_tokens = profile.get_max_tokens()
111
+ self.timeout = profile.get_timeout()
112
+ self.api_token = profile.get_token()
113
+
114
+ # Recreate adapter if tool format or URL changed
115
+ if self.tool_format != old_format or self.api_url != old_url:
116
+ self._adapter = get_adapter(self.tool_format, self.api_url)
117
+ elif not hasattr(self, '_adapter'):
118
+ # First initialization
119
+ self._adapter = get_adapter(self.tool_format, self.api_url)
120
+
121
+ if old_url and old_model:
122
+ logger.info(
123
+ f"API service updated: {old_model}@{old_url} ({old_format}) -> "
124
+ f"{self.model}@{self.api_url} ({self.tool_format})"
125
+ )
126
+
127
+ async def initialize(self) -> bool:
130
128
  """Initialize HTTP session with proper error handling and resource management."""
131
129
  async with self._session_lock:
132
130
  if self._initialized:
133
- return
131
+ return True
134
132
 
135
133
  try:
136
134
  # Create session with proper configuration and resource limits
@@ -291,7 +289,46 @@ class APICommunicationService:
291
289
  Dictionary containing token usage info
292
290
  """
293
291
  return self.last_token_usage.copy()
294
-
292
+
293
+ def get_last_tool_calls(self) -> List[Any]:
294
+ """Get tool calls from the last API call (native function calling).
295
+
296
+ Returns:
297
+ List of ToolCallResult objects from last response
298
+ """
299
+ return self.last_tool_calls.copy() if self.last_tool_calls else []
300
+
301
+ def has_pending_tool_calls(self) -> bool:
302
+ """Check if last response has tool calls that need execution.
303
+
304
+ Returns:
305
+ True if there are tool calls to execute
306
+ """
307
+ return bool(self.last_tool_calls)
308
+
309
+ def get_last_stop_reason(self) -> str:
310
+ """Get stop reason from last response.
311
+
312
+ Returns:
313
+ Stop reason string (e.g., 'end_turn', 'tool_use', 'max_tokens')
314
+ """
315
+ return self.last_stop_reason
316
+
317
+ def format_tool_result(self, tool_id: str, result: Any, is_error: bool = False) -> Dict[str, Any]:
318
+ """Format a tool execution result for sending back to the LLM.
319
+
320
+ Uses the adapter to format in the correct API format (OpenAI/Anthropic).
321
+
322
+ Args:
323
+ tool_id: The tool call ID from the original request
324
+ result: The tool execution result
325
+ is_error: Whether the result is an error
326
+
327
+ Returns:
328
+ Formatted tool result message for conversation
329
+ """
330
+ return self._adapter.format_tool_result(tool_id, result, is_error)
331
+
295
332
  def cancel_current_request(self):
296
333
  """Cancel any active API request."""
297
334
  self.cancel_requested = True
@@ -301,13 +338,15 @@ class APICommunicationService:
301
338
  self.current_request_task.cancel()
302
339
 
303
340
  async def call_llm(self, conversation_history: List[Dict[str, str]],
304
- max_history: int = None, streaming_callback=None) -> str:
341
+ max_history: int = None, streaming_callback=None,
342
+ tools: List[Dict[str, Any]] = None) -> str:
305
343
  """Make API call to LLM with conversation history and robust error handling.
306
344
 
307
345
  Args:
308
346
  conversation_history: List of conversation messages
309
347
  max_history: Maximum number of messages to send (optional)
310
348
  streaming_callback: Optional callback for streaming content chunks
349
+ tools: Optional list of tool definitions for native function calling
311
350
 
312
351
  Returns:
313
352
  LLM response content
@@ -337,17 +376,15 @@ class APICommunicationService:
337
376
  # Prepare messages for API
338
377
  messages = self._prepare_messages(conversation_history, max_history)
339
378
 
340
- # Build request payload
341
- payload = {
342
- "model": self.model,
343
- "messages": messages,
344
- "temperature": self.temperature,
345
- "stream": self.enable_streaming
346
- }
347
-
348
- # Add max_tokens if configured
349
- if self.max_tokens:
350
- payload["max_tokens"] = int(self.max_tokens)
379
+ # Build request payload using adapter
380
+ payload = self._adapter.format_request(
381
+ messages=messages,
382
+ model=self.model,
383
+ temperature=self.temperature,
384
+ stream=self.enable_streaming,
385
+ max_tokens=int(self.max_tokens) if self.max_tokens else None,
386
+ tools=tools # Native function calling (OpenAI/Anthropic format)
387
+ )
351
388
 
352
389
  # Execute request with cancellation support and comprehensive error handling
353
390
  self.current_request_task = asyncio.create_task(
@@ -415,16 +452,17 @@ class APICommunicationService:
415
452
  # Log raw request
416
453
  self._log_raw_interaction(payload)
417
454
 
418
- # Build headers for authentication
419
- headers = {"Content-Type": "application/json"}
420
- if self.api_token:
421
- headers["Authorization"] = f"Bearer {self.api_token}"
455
+ # Build headers using adapter (handles auth format differences)
456
+ headers = self._adapter.get_headers(self.api_token)
422
457
 
423
- # Determine the correct URL
424
- if "/chat/completions" in self.api_url:
458
+ # Determine URL - prefer user's configured URL if it's a full endpoint
459
+ # This handles custom APIs like z.ai that have non-standard paths
460
+ if "/chat/completions" in self.api_url or "/messages" in self.api_url:
461
+ # User provided full endpoint URL, use it directly
425
462
  url = self.api_url
426
463
  else:
427
- url = f"{self.api_url}/v1/chat/completions"
464
+ # User provided base URL, use adapter's endpoint pattern
465
+ url = self._adapter.api_endpoint
428
466
 
429
467
  # Execute request with proper timeout and error handling
430
468
  timeout_val = None if self.timeout == 0 else self.timeout
@@ -446,11 +484,17 @@ class APICommunicationService:
446
484
  if response.status == 200:
447
485
  if self.enable_streaming:
448
486
  content = await self._handle_streaming_response(response)
487
+ self.last_tool_calls = [] # Streaming doesn't support tool calls yet
449
488
  else:
450
489
  data = await response.json()
451
- content = data["choices"][0]["message"]["content"]
452
- # Extract token usage if available
453
- self.last_token_usage = data.get("usage", {})
490
+ # Use adapter to parse response (handles OpenAI vs Anthropic formats)
491
+ parsed = self._adapter.parse_response(data)
492
+ content = parsed.content
493
+ # Extract token usage from unified format
494
+ self.last_token_usage = parsed.usage
495
+ # Store tool calls for native function calling
496
+ self.last_tool_calls = parsed.tool_calls
497
+ self.last_stop_reason = parsed.stop_reason
454
498
 
455
499
  # Log successful response
456
500
  self._log_raw_interaction(
@@ -504,30 +548,31 @@ class APICommunicationService:
504
548
  raise
505
549
 
506
550
  async def _execute_request(self, payload: Dict[str, Any]) -> str:
507
- """Execute the actual HTTP request.
508
-
551
+ """Execute the actual HTTP request (legacy method - uses adapter for parsing).
552
+
553
+ Note: This is a legacy method. Prefer _execute_request_with_error_handling
554
+ which has better error handling and session recovery.
555
+
509
556
  Args:
510
557
  payload: Request payload
511
-
558
+
512
559
  Returns:
513
560
  Response content
514
561
  """
515
562
  start_time = time.time()
516
-
563
+
517
564
  try:
518
565
  # Log raw request
519
566
  self._log_raw_interaction(payload)
520
567
 
521
- # Build headers for authentication
522
- headers = {"Content-Type": "application/json"}
523
- if self.api_token:
524
- headers["Authorization"] = f"Bearer {self.api_token}"
568
+ # Build headers using adapter (handles auth format differences)
569
+ headers = self._adapter.get_headers(self.api_token)
525
570
 
526
- # Determine the correct URL - if it already contains the full path, use as-is
527
- if "/chat/completions" in self.api_url:
571
+ # Determine URL - prefer user's configured URL if it's a full endpoint
572
+ if "/chat/completions" in self.api_url or "/messages" in self.api_url:
528
573
  url = self.api_url
529
574
  else:
530
- url = f"{self.api_url}/v1/chat/completions"
575
+ url = self._adapter.api_endpoint
531
576
 
532
577
  async with self.session.post(
533
578
  url,
@@ -535,39 +580,40 @@ class APICommunicationService:
535
580
  headers=headers,
536
581
  timeout=aiohttp.ClientTimeout(total=None if self.timeout == 0 else self.timeout)
537
582
  ) as response:
538
-
583
+
539
584
  request_duration = time.time() - start_time
540
-
585
+
541
586
  if response.status == 200:
542
587
  if self.enable_streaming:
543
588
  content = await self._handle_streaming_response(response)
544
589
  else:
545
590
  data = await response.json()
546
- content = data["choices"][0]["message"]["content"]
547
-
548
- # Extract token usage if available
549
- self.last_token_usage = data.get("usage", {})
591
+ # Use adapter to parse response (handles OpenAI vs Anthropic formats)
592
+ parsed = self._adapter.parse_response(data)
593
+ content = parsed.content
594
+ # Extract token usage from unified format
595
+ self.last_token_usage = parsed.usage
550
596
 
551
597
  # Log successful response with full data
552
598
  self._log_raw_interaction(payload, response_data=data if not self.enable_streaming else {"choices": [{"message": {"content": content}}]})
553
-
599
+
554
600
  logger.debug(f"API call completed in {request_duration:.2f}s")
555
601
  return content
556
-
602
+
557
603
  else:
558
604
  error_text = await response.text()
559
605
  error_msg = f"LLM API error: {response.status} - {error_text}"
560
-
606
+
561
607
  # Log error response
562
608
  self._log_raw_interaction(payload, error=error_msg)
563
-
609
+
564
610
  raise Exception(error_msg)
565
-
611
+
566
612
  except asyncio.TimeoutError:
567
613
  error_msg = f"LLM API timeout after {self.timeout} seconds"
568
614
  self._log_raw_interaction(payload, error=error_msg)
569
615
  raise Exception(error_msg)
570
-
616
+
571
617
  except Exception as e:
572
618
  # Log any other exceptions
573
619
  if not str(e).startswith("LLM API error"):
@@ -655,14 +701,22 @@ class APICommunicationService:
655
701
  cancelled: Whether the request was cancelled (optional)
656
702
  """
657
703
  try:
658
- # Create filename with timestamp
659
- timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
660
- filename = f"raw_llm_interactions_{timestamp}.jsonl"
704
+ # Create filename with session ID for easy correlation
705
+ # Format: {session_id}_raw_{timestamp}.jsonl or fallback to raw_llm_interactions_{timestamp}.jsonl
706
+ timestamp = datetime.now().strftime("%H%M%S")
707
+ if self.current_session_id:
708
+ # e.g., session_2025-12-11_112522_raw_112635.jsonl
709
+ filename = f"{self.current_session_id}_raw_{timestamp}.jsonl"
710
+ else:
711
+ # Fallback if no session ID set
712
+ date_timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
713
+ filename = f"raw_llm_interactions_{date_timestamp}.jsonl"
661
714
  filepath = self.raw_conversations_dir / filename
662
715
 
663
- # Create log entry
716
+ # Create log entry with session linking
664
717
  log_entry = {
665
718
  "timestamp": datetime.now().isoformat(),
719
+ "session_id": self.current_session_id, # Links to conversations/session_*.jsonl
666
720
  "request": {
667
721
  "url": f"{self.api_url}/v1/chat/completions",
668
722
  "method": "POST",