dacp 0.3.0__py3-none-any.whl → 0.3.2__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.
- dacp/__init__.py +23 -4
- dacp/intelligence.py +241 -210
- dacp/llm.py +13 -6
- dacp/logging_config.py +129 -0
- dacp/main.py +7 -13
- dacp/orchestrator.py +225 -201
- dacp/tools.py +72 -22
- dacp-0.3.2.dist-info/METADATA +805 -0
- dacp-0.3.2.dist-info/RECORD +15 -0
- dacp-0.3.0.dist-info/METADATA +0 -369
- dacp-0.3.0.dist-info/RECORD +0 -14
- {dacp-0.3.0.dist-info → dacp-0.3.2.dist-info}/WHEEL +0 -0
- {dacp-0.3.0.dist-info → dacp-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {dacp-0.3.0.dist-info → dacp-0.3.2.dist-info}/top_level.txt +0 -0
dacp/logging_config.py
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
"""
|
2
|
+
DACP Logging Configuration
|
3
|
+
|
4
|
+
Utilities for configuring logging for DACP components.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
import sys
|
9
|
+
from typing import Optional
|
10
|
+
|
11
|
+
|
12
|
+
def setup_dacp_logging(
|
13
|
+
level: str = "INFO",
|
14
|
+
format_style: str = "detailed",
|
15
|
+
include_timestamp: bool = True,
|
16
|
+
log_file: Optional[str] = None,
|
17
|
+
) -> None:
|
18
|
+
"""
|
19
|
+
Set up logging for DACP components.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
23
|
+
format_style: Log format style ('simple', 'detailed', 'emoji')
|
24
|
+
include_timestamp: Whether to include timestamps in logs
|
25
|
+
log_file: Optional file path to also log to a file
|
26
|
+
"""
|
27
|
+
# Define format styles
|
28
|
+
if format_style == "simple":
|
29
|
+
if include_timestamp:
|
30
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
31
|
+
else:
|
32
|
+
log_format = "%(name)s - %(levelname)s - %(message)s"
|
33
|
+
elif format_style == "detailed":
|
34
|
+
if include_timestamp:
|
35
|
+
log_format = (
|
36
|
+
"%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s"
|
37
|
+
)
|
38
|
+
else:
|
39
|
+
log_format = "%(name)s:%(lineno)d - %(levelname)s - %(message)s"
|
40
|
+
elif format_style == "emoji":
|
41
|
+
# Emoji format doesn't include logger name since emojis provide context
|
42
|
+
if include_timestamp:
|
43
|
+
log_format = "%(asctime)s - %(message)s"
|
44
|
+
else:
|
45
|
+
log_format = "%(message)s"
|
46
|
+
else:
|
47
|
+
raise ValueError(f"Unknown format_style: {format_style}")
|
48
|
+
|
49
|
+
# Configure root logger for DACP components
|
50
|
+
logger = logging.getLogger("dacp")
|
51
|
+
logger.setLevel(getattr(logging, level.upper()))
|
52
|
+
|
53
|
+
# Remove existing handlers to avoid duplicates
|
54
|
+
for handler in logger.handlers[:]:
|
55
|
+
logger.removeHandler(handler)
|
56
|
+
|
57
|
+
# Create formatter
|
58
|
+
formatter = logging.Formatter(
|
59
|
+
log_format, datefmt="%Y-%m-%d %H:%M:%S" if include_timestamp else None
|
60
|
+
)
|
61
|
+
|
62
|
+
# Console handler
|
63
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
64
|
+
console_handler.setFormatter(formatter)
|
65
|
+
logger.addHandler(console_handler)
|
66
|
+
|
67
|
+
# Optional file handler
|
68
|
+
if log_file:
|
69
|
+
file_handler = logging.FileHandler(log_file)
|
70
|
+
file_handler.setFormatter(formatter)
|
71
|
+
logger.addHandler(file_handler)
|
72
|
+
|
73
|
+
# Prevent propagation to root logger to avoid duplicate messages
|
74
|
+
logger.propagate = False
|
75
|
+
|
76
|
+
logger.info(f"🚀 DACP logging configured: level={level}, style={format_style}")
|
77
|
+
|
78
|
+
|
79
|
+
def set_dacp_log_level(level: str) -> None:
|
80
|
+
"""
|
81
|
+
Set the log level for all DACP components.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
85
|
+
"""
|
86
|
+
logger = logging.getLogger("dacp")
|
87
|
+
logger.setLevel(getattr(logging, level.upper()))
|
88
|
+
logger.info(f"📊 DACP log level changed to {level}")
|
89
|
+
|
90
|
+
|
91
|
+
def disable_dacp_logging() -> None:
|
92
|
+
"""Disable all DACP logging."""
|
93
|
+
logger = logging.getLogger("dacp")
|
94
|
+
logger.disabled = True
|
95
|
+
|
96
|
+
|
97
|
+
def enable_dacp_logging() -> None:
|
98
|
+
"""Re-enable DACP logging."""
|
99
|
+
logger = logging.getLogger("dacp")
|
100
|
+
logger.disabled = False
|
101
|
+
|
102
|
+
|
103
|
+
def get_dacp_logger(name: str) -> logging.Logger:
|
104
|
+
"""
|
105
|
+
Get a logger for a DACP component.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
name: Logger name (usually __name__)
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Configured logger
|
112
|
+
"""
|
113
|
+
return logging.getLogger(f"dacp.{name}")
|
114
|
+
|
115
|
+
|
116
|
+
# Convenience functions for quick setup
|
117
|
+
def enable_debug_logging(log_file: Optional[str] = None) -> None:
|
118
|
+
"""Enable debug logging with detailed format."""
|
119
|
+
setup_dacp_logging(level="DEBUG", format_style="detailed", log_file=log_file)
|
120
|
+
|
121
|
+
|
122
|
+
def enable_info_logging(log_file: Optional[str] = None) -> None:
|
123
|
+
"""Enable info logging with emoji format."""
|
124
|
+
setup_dacp_logging(level="INFO", format_style="emoji", log_file=log_file)
|
125
|
+
|
126
|
+
|
127
|
+
def enable_quiet_logging() -> None:
|
128
|
+
"""Enable only error and critical logging."""
|
129
|
+
setup_dacp_logging(level="ERROR", format_style="simple", include_timestamp=False)
|
dacp/main.py
CHANGED
@@ -1,15 +1,9 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
DACP Main Entry Point
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
This module provides examples and testing functionality for DACP.
|
6
|
+
"""
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Orchestrator sends a message to the agent and prints the response
|
10
|
-
input_message = {"name": "Alice"}
|
11
|
-
response = orchestrator.call_agent("hello_agent", input_message)
|
12
|
-
print("Orchestrator received:", response)
|
13
|
-
|
14
|
-
if __name__ == "__main__":
|
15
|
-
main()
|
8
|
+
print("DACP - Declarative Agent Communication Protocol")
|
9
|
+
print("For examples, see the examples/ directory")
|
dacp/orchestrator.py
CHANGED
@@ -1,260 +1,284 @@
|
|
1
1
|
"""
|
2
|
-
DACP Orchestrator -
|
2
|
+
DACP Orchestrator - Agent management and message routing.
|
3
|
+
|
4
|
+
This module provides the core orchestrator functionality for managing agents
|
5
|
+
and routing messages between them.
|
3
6
|
"""
|
4
7
|
|
5
8
|
import logging
|
6
|
-
|
7
|
-
import
|
8
|
-
|
9
|
-
from .
|
10
|
-
|
11
|
-
|
12
|
-
get_tool_request,
|
13
|
-
wrap_tool_result,
|
14
|
-
is_final_response,
|
15
|
-
get_final_response,
|
16
|
-
)
|
17
|
-
from .tools import run_tool
|
18
|
-
|
19
|
-
log = logging.getLogger(__name__)
|
9
|
+
import time
|
10
|
+
from typing import Dict, Any, List, Optional
|
11
|
+
|
12
|
+
from .tools import execute_tool
|
13
|
+
|
14
|
+
logger = logging.getLogger("dacp.orchestrator")
|
20
15
|
|
21
16
|
|
22
17
|
class Agent:
|
23
|
-
"""
|
24
|
-
|
18
|
+
"""
|
19
|
+
Base agent class that all DACP agents should inherit from.
|
20
|
+
|
21
|
+
This provides the standard interface for agent communication.
|
22
|
+
"""
|
23
|
+
|
25
24
|
def handle_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
26
|
-
"""
|
25
|
+
"""
|
26
|
+
Handle incoming messages.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
message: Message dictionary containing task and parameters
|
30
|
+
|
31
|
+
Returns:
|
32
|
+
Response dictionary with either 'response', 'tool_request', or 'error'
|
33
|
+
"""
|
27
34
|
raise NotImplementedError("Agents must implement handle_message method")
|
28
35
|
|
29
36
|
|
30
37
|
class Orchestrator:
|
31
38
|
"""
|
32
39
|
Central orchestrator for managing agents and routing messages.
|
33
|
-
|
40
|
+
|
41
|
+
The orchestrator handles agent registration, message routing, tool execution,
|
42
|
+
and conversation history management.
|
34
43
|
"""
|
35
|
-
|
36
|
-
def __init__(self):
|
37
|
-
|
44
|
+
|
45
|
+
def __init__(self, session_id: Optional[str] = None):
|
46
|
+
"""Initialize orchestrator with optional session ID."""
|
47
|
+
self.agents: Dict[str, Agent] = {}
|
48
|
+
self.session_id = session_id or f"session_{int(time.time())}"
|
38
49
|
self.conversation_history: List[Dict[str, Any]] = []
|
39
|
-
|
40
|
-
|
41
|
-
|
50
|
+
|
51
|
+
logger.info(f"🎭 Orchestrator initialized with session ID: {self.session_id}")
|
52
|
+
|
53
|
+
def register_agent(self, name: str, agent: Agent) -> None:
|
42
54
|
"""
|
43
55
|
Register an agent with the orchestrator.
|
44
|
-
|
56
|
+
|
45
57
|
Args:
|
46
|
-
|
47
|
-
agent: Agent instance
|
58
|
+
name: Unique name for the agent
|
59
|
+
agent: Agent instance implementing the Agent interface
|
48
60
|
"""
|
49
|
-
if not
|
50
|
-
raise ValueError(
|
51
|
-
|
52
|
-
self.agents[
|
53
|
-
|
54
|
-
|
55
|
-
|
61
|
+
if not isinstance(agent, Agent):
|
62
|
+
raise ValueError("Agent must inherit from dacp.Agent base class")
|
63
|
+
|
64
|
+
self.agents[name] = agent
|
65
|
+
logger.info(
|
66
|
+
f"✅ Agent '{name}' registered successfully "
|
67
|
+
f"(type: {type(agent).__name__})"
|
68
|
+
)
|
69
|
+
logger.debug(f"📊 Total registered agents: {len(self.agents)}")
|
70
|
+
|
71
|
+
def unregister_agent(self, name: str) -> bool:
|
56
72
|
"""
|
57
73
|
Unregister an agent from the orchestrator.
|
58
|
-
|
74
|
+
|
59
75
|
Args:
|
60
|
-
|
61
|
-
|
76
|
+
name: Name of the agent to unregister
|
77
|
+
|
62
78
|
Returns:
|
63
|
-
True if agent was
|
79
|
+
True if agent was unregistered, False if not found
|
64
80
|
"""
|
65
|
-
if
|
66
|
-
del self.agents[
|
67
|
-
|
81
|
+
if name in self.agents:
|
82
|
+
del self.agents[name]
|
83
|
+
logger.info(f"🗑️ Agent '{name}' unregistered successfully")
|
84
|
+
logger.debug(f"📊 Remaining agents: {len(self.agents)}")
|
68
85
|
return True
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
return self.agents.get(agent_id)
|
74
|
-
|
86
|
+
else:
|
87
|
+
logger.warning(f"⚠️ Agent '{name}' not found for unregistration")
|
88
|
+
return False
|
89
|
+
|
75
90
|
def list_agents(self) -> List[str]:
|
76
|
-
"""Get list of registered agent
|
91
|
+
"""Get list of registered agent names."""
|
77
92
|
return list(self.agents.keys())
|
78
|
-
|
79
|
-
def send_message(self,
|
93
|
+
|
94
|
+
def send_message(self, agent_name: str, message: Dict[str, Any]) -> Dict[str, Any]:
|
80
95
|
"""
|
81
96
|
Send a message to a specific agent.
|
82
|
-
|
97
|
+
|
83
98
|
Args:
|
84
|
-
|
85
|
-
message: Message to send
|
86
|
-
|
99
|
+
agent_name: Name of the target agent
|
100
|
+
message: Message dictionary to send
|
101
|
+
|
87
102
|
Returns:
|
88
|
-
Response from the agent
|
89
|
-
|
90
|
-
Raises:
|
91
|
-
ValueError: If agent_id is not found
|
103
|
+
Response from the agent after processing
|
92
104
|
"""
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"
|
101
|
-
"
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
+
start_time = time.time()
|
106
|
+
|
107
|
+
logger.info(f"📨 Sending message to agent '{agent_name}'")
|
108
|
+
logger.debug(f"📋 Message content: {message}")
|
109
|
+
|
110
|
+
if agent_name not in self.agents:
|
111
|
+
error_msg = f"Agent '{agent_name}' not found"
|
112
|
+
logger.error(f"❌ {error_msg}")
|
113
|
+
return {"error": error_msg}
|
114
|
+
|
115
|
+
agent = self.agents[agent_name]
|
116
|
+
|
105
117
|
try:
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
+
logger.debug(f"🔄 Calling handle_message on agent '{agent_name}'")
|
119
|
+
|
120
|
+
# Call the agent's message handler
|
121
|
+
response = agent.handle_message(message)
|
122
|
+
|
123
|
+
duration = time.time() - start_time
|
124
|
+
logger.info(f"✅ Agent '{agent_name}' responded in {duration:.3f}s")
|
125
|
+
logger.debug(f"📤 Agent response: {response}")
|
126
|
+
|
127
|
+
# Check if agent requested tool execution
|
128
|
+
if isinstance(response, dict) and "tool_request" in response:
|
129
|
+
logger.info(f"🔧 Agent '{agent_name}' requested tool execution")
|
130
|
+
response = self._handle_tool_request(
|
131
|
+
agent_name, response["tool_request"]
|
132
|
+
)
|
133
|
+
|
134
|
+
# Log the conversation
|
135
|
+
self._log_conversation(agent_name, message, response, duration)
|
136
|
+
|
118
137
|
return response
|
119
|
-
|
138
|
+
|
120
139
|
except Exception as e:
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
140
|
+
duration = time.time() - start_time
|
141
|
+
error_msg = f"Error in agent '{agent_name}': {type(e).__name__}: {e}"
|
142
|
+
logger.error(f"❌ {error_msg}")
|
143
|
+
logger.debug("💥 Exception details", exc_info=True)
|
144
|
+
|
145
|
+
error_response = {"error": error_msg}
|
146
|
+
self._log_conversation(agent_name, message, error_response, duration)
|
147
|
+
|
126
148
|
return error_response
|
127
|
-
|
128
|
-
def broadcast_message(self, message: Dict[str, Any]
|
149
|
+
|
150
|
+
def broadcast_message(self, message: Dict[str, Any]) -> Dict[str, Any]:
|
129
151
|
"""
|
130
|
-
|
131
|
-
|
152
|
+
Send a message to all registered agents.
|
153
|
+
|
132
154
|
Args:
|
133
|
-
message: Message to broadcast
|
134
|
-
|
135
|
-
|
155
|
+
message: Message dictionary to broadcast
|
156
|
+
|
136
157
|
Returns:
|
137
|
-
Dictionary mapping
|
158
|
+
Dictionary mapping agent names to their responses
|
138
159
|
"""
|
139
|
-
|
160
|
+
logger.info(f"📢 Broadcasting message to {len(self.agents)} agents")
|
161
|
+
logger.debug(f"📋 Broadcast message: {message}")
|
162
|
+
|
140
163
|
responses = {}
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
164
|
+
start_time = time.time()
|
165
|
+
|
166
|
+
for agent_name in self.agents:
|
167
|
+
logger.debug(f"📨 Broadcasting to agent '{agent_name}'")
|
168
|
+
responses[agent_name] = self.send_message(agent_name, message)
|
169
|
+
|
170
|
+
duration = time.time() - start_time
|
171
|
+
logger.info(
|
172
|
+
f"✅ Broadcast completed in {duration:.3f}s "
|
173
|
+
f"({len(responses)} responses)"
|
174
|
+
)
|
175
|
+
|
149
176
|
return responses
|
150
|
-
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
Args:
|
156
|
-
tool_name: Name of the tool to execute
|
157
|
-
args: Arguments for the tool
|
158
|
-
|
159
|
-
Returns:
|
160
|
-
Tool execution result wrapped in protocol format
|
161
|
-
"""
|
162
|
-
try:
|
163
|
-
result = run_tool(tool_name, args)
|
164
|
-
return wrap_tool_result(tool_name, result)
|
165
|
-
except Exception as e:
|
166
|
-
error_result = {
|
167
|
-
"success": False,
|
168
|
-
"error": str(e),
|
169
|
-
"tool_name": tool_name,
|
170
|
-
"args": args
|
171
|
-
}
|
172
|
-
return wrap_tool_result(tool_name, error_result)
|
173
|
-
|
174
|
-
def process_agent_response(self, agent_id: str, response: Any) -> Dict[str, Any]:
|
177
|
+
|
178
|
+
def _handle_tool_request(
|
179
|
+
self, agent_name: str, tool_request: Dict[str, Any]
|
180
|
+
) -> Dict[str, Any]:
|
175
181
|
"""
|
176
|
-
|
177
|
-
|
182
|
+
Handle tool execution request from an agent.
|
183
|
+
|
178
184
|
Args:
|
179
|
-
|
180
|
-
|
181
|
-
|
185
|
+
agent_name: Name of the requesting agent
|
186
|
+
tool_request: Tool request dictionary with 'name' and 'args'
|
187
|
+
|
182
188
|
Returns:
|
183
|
-
|
189
|
+
Tool execution result
|
184
190
|
"""
|
191
|
+
tool_name = tool_request.get("name")
|
192
|
+
tool_args = tool_request.get("args", {})
|
193
|
+
|
194
|
+
if not tool_name:
|
195
|
+
return {"error": "Tool name is required"}
|
196
|
+
|
197
|
+
logger.info(f"🛠️ Executing tool: '{tool_name}' with args: {tool_args}")
|
198
|
+
|
199
|
+
start_time = time.time()
|
200
|
+
|
185
201
|
try:
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
# Log tool execution
|
198
|
-
self.conversation_history.append({
|
199
|
-
"type": "tool_execution",
|
200
|
-
"agent_id": agent_id,
|
201
|
-
"tool_name": tool_name,
|
202
|
-
"args": args,
|
203
|
-
"result": tool_result,
|
204
|
-
"timestamp": self._get_timestamp()
|
205
|
-
})
|
206
|
-
|
207
|
-
return tool_result
|
208
|
-
|
209
|
-
# Check if it's a final response
|
210
|
-
elif is_final_response(parsed_response):
|
211
|
-
final_response = get_final_response(parsed_response)
|
212
|
-
log.info(f"Agent {agent_id} sent final response")
|
213
|
-
return final_response
|
214
|
-
|
215
|
-
else:
|
216
|
-
# Return the parsed response as-is
|
217
|
-
return parsed_response
|
218
|
-
|
202
|
+
result = execute_tool(tool_name, tool_args)
|
203
|
+
duration = time.time() - start_time
|
204
|
+
|
205
|
+
logger.info(
|
206
|
+
f"✅ Tool '{tool_name}' executed successfully in {duration:.3f}s"
|
207
|
+
)
|
208
|
+
logger.debug(f"🔧 Tool result: {result}")
|
209
|
+
|
210
|
+
return {"tool_result": {"name": tool_name, "result": result}}
|
211
|
+
|
219
212
|
except Exception as e:
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
225
|
-
|
226
|
-
def
|
213
|
+
duration = time.time() - start_time
|
214
|
+
error_msg = f"Tool '{tool_name}' failed: {type(e).__name__}: {e}"
|
215
|
+
logger.error(f"❌ {error_msg}")
|
216
|
+
|
217
|
+
return {"error": error_msg}
|
218
|
+
|
219
|
+
def _log_conversation(
|
220
|
+
self,
|
221
|
+
agent_name: str,
|
222
|
+
message: Dict[str, Any],
|
223
|
+
response: Dict[str, Any],
|
224
|
+
duration: float,
|
225
|
+
) -> None:
|
226
|
+
"""Log conversation entry to history."""
|
227
|
+
logger.debug("💾 Logging conversation entry")
|
228
|
+
|
229
|
+
entry = {
|
230
|
+
"timestamp": time.time(),
|
231
|
+
"session_id": self.session_id,
|
232
|
+
"agent_name": agent_name,
|
233
|
+
"message": message,
|
234
|
+
"response": response,
|
235
|
+
"duration": duration,
|
236
|
+
}
|
237
|
+
|
238
|
+
self.conversation_history.append(entry)
|
239
|
+
|
240
|
+
# Keep history manageable (last 1000 entries)
|
241
|
+
if len(self.conversation_history) > 1000:
|
242
|
+
self.conversation_history = self.conversation_history[-1000:]
|
243
|
+
logger.debug("🗂️ Conversation history trimmed to 1000 entries")
|
244
|
+
|
245
|
+
def get_conversation_history(
|
246
|
+
self, limit: Optional[int] = None
|
247
|
+
) -> List[Dict[str, Any]]:
|
227
248
|
"""
|
228
|
-
Get conversation history
|
229
|
-
|
249
|
+
Get conversation history.
|
250
|
+
|
230
251
|
Args:
|
231
|
-
|
232
|
-
|
252
|
+
limit: Maximum number of entries to return (None for all)
|
253
|
+
|
233
254
|
Returns:
|
234
255
|
List of conversation entries
|
235
256
|
"""
|
236
|
-
if
|
237
|
-
return
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
def clear_history(self) -> None:
|
257
|
+
if limit is None:
|
258
|
+
return self.conversation_history.copy()
|
259
|
+
else:
|
260
|
+
return self.conversation_history[-limit:].copy()
|
261
|
+
|
262
|
+
def clear_conversation_history(self) -> None:
|
244
263
|
"""Clear conversation history."""
|
245
264
|
self.conversation_history.clear()
|
246
|
-
|
247
|
-
|
248
|
-
def
|
249
|
-
"""Get
|
265
|
+
logger.info("🗑️ Conversation history cleared")
|
266
|
+
|
267
|
+
def get_session_metadata(self) -> Dict[str, Any]:
|
268
|
+
"""Get session metadata and statistics."""
|
250
269
|
return {
|
251
270
|
"session_id": self.session_id,
|
252
|
-
"registered_agents": self.
|
253
|
-
"
|
254
|
-
"
|
271
|
+
"registered_agents": len(self.agents),
|
272
|
+
"agent_names": list(self.agents.keys()),
|
273
|
+
"conversation_entries": len(self.conversation_history),
|
274
|
+
"start_time": (
|
275
|
+
self.conversation_history[0]["timestamp"]
|
276
|
+
if self.conversation_history
|
277
|
+
else None
|
278
|
+
),
|
279
|
+
"last_activity": (
|
280
|
+
self.conversation_history[-1]["timestamp"]
|
281
|
+
if self.conversation_history
|
282
|
+
else None
|
283
|
+
),
|
255
284
|
}
|
256
|
-
|
257
|
-
def _get_timestamp(self) -> str:
|
258
|
-
"""Get current timestamp in ISO format."""
|
259
|
-
from datetime import datetime
|
260
|
-
return datetime.utcnow().isoformat() + "Z"
|