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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {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
|
-
#
|
|
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 (
|
|
49
|
+
# Streaming (from config, not profile-specific)
|
|
79
50
|
self.enable_streaming = config.get("core.llm.enable_streaming", False)
|
|
80
51
|
|
|
81
|
-
#
|
|
82
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
419
|
-
headers =
|
|
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
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
|
522
|
-
headers =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
659
|
-
timestamp
|
|
660
|
-
|
|
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",
|