code-puppy 0.0.135__py3-none-any.whl → 0.0.136__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 (60) hide show
  1. code_puppy/agent.py +15 -17
  2. code_puppy/agents/agent_manager.py +320 -9
  3. code_puppy/agents/base_agent.py +58 -2
  4. code_puppy/agents/runtime_manager.py +68 -42
  5. code_puppy/command_line/command_handler.py +82 -33
  6. code_puppy/command_line/mcp/__init__.py +10 -0
  7. code_puppy/command_line/mcp/add_command.py +183 -0
  8. code_puppy/command_line/mcp/base.py +35 -0
  9. code_puppy/command_line/mcp/handler.py +133 -0
  10. code_puppy/command_line/mcp/help_command.py +146 -0
  11. code_puppy/command_line/mcp/install_command.py +176 -0
  12. code_puppy/command_line/mcp/list_command.py +94 -0
  13. code_puppy/command_line/mcp/logs_command.py +126 -0
  14. code_puppy/command_line/mcp/remove_command.py +82 -0
  15. code_puppy/command_line/mcp/restart_command.py +92 -0
  16. code_puppy/command_line/mcp/search_command.py +117 -0
  17. code_puppy/command_line/mcp/start_all_command.py +126 -0
  18. code_puppy/command_line/mcp/start_command.py +98 -0
  19. code_puppy/command_line/mcp/status_command.py +185 -0
  20. code_puppy/command_line/mcp/stop_all_command.py +109 -0
  21. code_puppy/command_line/mcp/stop_command.py +79 -0
  22. code_puppy/command_line/mcp/test_command.py +107 -0
  23. code_puppy/command_line/mcp/utils.py +129 -0
  24. code_puppy/command_line/mcp/wizard_utils.py +259 -0
  25. code_puppy/command_line/model_picker_completion.py +21 -4
  26. code_puppy/command_line/prompt_toolkit_completion.py +9 -0
  27. code_puppy/main.py +23 -17
  28. code_puppy/mcp/__init__.py +42 -16
  29. code_puppy/mcp/async_lifecycle.py +51 -49
  30. code_puppy/mcp/blocking_startup.py +125 -113
  31. code_puppy/mcp/captured_stdio_server.py +63 -70
  32. code_puppy/mcp/circuit_breaker.py +63 -47
  33. code_puppy/mcp/config_wizard.py +169 -136
  34. code_puppy/mcp/dashboard.py +79 -71
  35. code_puppy/mcp/error_isolation.py +147 -100
  36. code_puppy/mcp/examples/retry_example.py +55 -42
  37. code_puppy/mcp/health_monitor.py +152 -141
  38. code_puppy/mcp/managed_server.py +100 -93
  39. code_puppy/mcp/manager.py +168 -156
  40. code_puppy/mcp/registry.py +148 -110
  41. code_puppy/mcp/retry_manager.py +63 -61
  42. code_puppy/mcp/server_registry_catalog.py +271 -225
  43. code_puppy/mcp/status_tracker.py +80 -80
  44. code_puppy/mcp/system_tools.py +47 -52
  45. code_puppy/messaging/message_queue.py +20 -13
  46. code_puppy/messaging/renderers.py +30 -15
  47. code_puppy/state_management.py +103 -0
  48. code_puppy/tui/app.py +64 -7
  49. code_puppy/tui/components/chat_view.py +3 -3
  50. code_puppy/tui/components/human_input_modal.py +12 -8
  51. code_puppy/tui/screens/__init__.py +2 -2
  52. code_puppy/tui/screens/mcp_install_wizard.py +208 -179
  53. code_puppy/tui/tests/test_agent_command.py +3 -3
  54. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
  55. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
  56. code_puppy/command_line/mcp_commands.py +0 -1789
  57. {code_puppy-0.0.135.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
  58. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
  59. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
  60. {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
code_puppy/agent.py CHANGED
@@ -3,15 +3,10 @@ from pathlib import Path
3
3
  from typing import Dict, Optional
4
4
 
5
5
  from pydantic_ai import Agent
6
- from pydantic_ai.mcp import MCPServerSSE, MCPServerStdio, MCPServerStreamableHTTP
7
6
  from pydantic_ai.settings import ModelSettings
8
7
  from pydantic_ai.usage import UsageLimits
9
8
 
10
9
  from code_puppy.agents import get_current_agent_config
11
- from code_puppy.http_utils import (
12
- create_reopenable_async_client,
13
- resolve_env_var_in_header,
14
- )
15
10
  from code_puppy.message_history_processor import (
16
11
  get_model_context_length,
17
12
  message_history_accumulator,
@@ -45,7 +40,7 @@ _code_generation_agent = None
45
40
  def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
46
41
  """Load MCP servers using the new manager while maintaining backward compatibility."""
47
42
  from code_puppy.config import get_value, load_mcp_server_configs
48
- from code_puppy.mcp import get_mcp_manager, ServerConfig
43
+ from code_puppy.mcp import ServerConfig, get_mcp_manager
49
44
 
50
45
  # Check if MCP servers are disabled
51
46
  mcp_disabled = get_value("disable_mcp_servers")
@@ -55,7 +50,7 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
55
50
 
56
51
  # Get the MCP manager singleton
57
52
  manager = get_mcp_manager()
58
-
53
+
59
54
  # Load configurations from legacy file for backward compatibility
60
55
  configs = load_mcp_server_configs()
61
56
  if not configs:
@@ -74,9 +69,9 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
74
69
  name=name,
75
70
  type=conf.get("type", "sse"),
76
71
  enabled=conf.get("enabled", True),
77
- config=conf
72
+ config=conf,
78
73
  )
79
-
74
+
80
75
  # Check if server already registered
81
76
  existing = manager.get_server_by_name(name)
82
77
  if not existing:
@@ -88,14 +83,14 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
88
83
  if existing.config != server_config.config:
89
84
  manager.update_server(existing.id, server_config)
90
85
  emit_system_message(f"[dim]Updated MCP server: {name}[/dim]")
91
-
86
+
92
87
  except Exception as e:
93
88
  emit_error(f"Failed to register MCP server '{name}': {str(e)}")
94
89
  continue
95
-
90
+
96
91
  # Get pydantic-ai compatible servers from manager
97
92
  servers = manager.get_servers_for_agent()
98
-
93
+
99
94
  if servers:
100
95
  emit_system_message(
101
96
  f"[green]Successfully loaded {len(servers)} MCP server(s)[/green]"
@@ -104,14 +99,14 @@ def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
104
99
  emit_system_message(
105
100
  "[yellow]No MCP servers available (check if servers are enabled)[/yellow]"
106
101
  )
107
-
102
+
108
103
  return servers
109
104
 
110
105
 
111
106
  def reload_mcp_servers():
112
107
  """Reload MCP servers without restarting the agent."""
113
108
  from code_puppy.mcp import get_mcp_manager
114
-
109
+
115
110
  manager = get_mcp_manager()
116
111
  # Reload configurations
117
112
  _load_mcp_servers()
@@ -124,15 +119,18 @@ def reload_code_generation_agent(message_group: str | None):
124
119
  if message_group is None:
125
120
  message_group = str(uuid.uuid4())
126
121
  global _code_generation_agent, _LAST_MODEL_NAME
127
- from code_puppy.config import clear_model_cache, get_model_name
128
122
  from code_puppy.agents import clear_agent_cache
123
+ from code_puppy.config import clear_model_cache, get_model_name
129
124
 
130
125
  # Clear both ModelFactory cache and config cache when force reloading
131
126
  clear_model_cache()
132
127
  clear_agent_cache()
133
128
 
134
129
  model_name = get_model_name()
135
- emit_info(f"[bold cyan]Loading Model: {model_name}[/bold cyan]", message_group=message_group)
130
+ emit_info(
131
+ f"[bold cyan]Loading Model: {model_name}[/bold cyan]",
132
+ message_group=message_group,
133
+ )
136
134
  models_config = ModelFactory.load_config()
137
135
  model = ModelFactory.get_model(model_name, models_config)
138
136
 
@@ -140,7 +138,7 @@ def reload_code_generation_agent(message_group: str | None):
140
138
  agent_config = get_current_agent_config()
141
139
  emit_info(
142
140
  f"[bold magenta]Loading Agent: {agent_config.display_name}[/bold magenta]",
143
- message_group=message_group
141
+ message_group=message_group,
144
142
  )
145
143
 
146
144
  instructions = agent_config.get_system_prompt()
@@ -1,20 +1,185 @@
1
1
  """Agent manager for handling different agent configurations."""
2
2
 
3
3
  import importlib
4
+ import json
5
+ import os
4
6
  import pkgutil
5
7
  import uuid
8
+ from pathlib import Path
6
9
  from typing import Dict, Optional, Type, Union
7
10
 
8
- from code_puppy.config import get_value, set_config_value
9
- from .base_agent import BaseAgent
10
- from .json_agent import JSONAgent, discover_json_agents
11
11
  from ..callbacks import on_agent_reload
12
12
  from ..messaging import emit_warning
13
+ from .base_agent import BaseAgent
14
+ from .json_agent import JSONAgent, discover_json_agents
13
15
 
14
16
  # Registry of available agents (Python classes and JSON file paths)
15
17
  _AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
16
18
  _CURRENT_AGENT_CONFIG: Optional[BaseAgent] = None
17
19
 
20
+ # Terminal session-based agent selection
21
+ _SESSION_AGENTS_CACHE: dict[str, str] = {}
22
+ _SESSION_FILE_LOADED: bool = False
23
+
24
+
25
+ # Session persistence file path
26
+ def _get_session_file_path() -> Path:
27
+ """Get the path to the terminal sessions file."""
28
+ from ..config import CONFIG_DIR
29
+
30
+ return Path(CONFIG_DIR) / "terminal_sessions.json"
31
+
32
+
33
+ def get_terminal_session_id() -> str:
34
+ """Get a unique identifier for the current terminal session.
35
+
36
+ Uses parent process ID (PPID) as the session identifier.
37
+ This works across all platforms and provides session isolation.
38
+
39
+ Returns:
40
+ str: Unique session identifier (e.g., "session_12345")
41
+ """
42
+ try:
43
+ ppid = os.getppid()
44
+ return f"session_{ppid}"
45
+ except (OSError, AttributeError):
46
+ # Fallback to current process ID if PPID unavailable
47
+ return f"fallback_{os.getpid()}"
48
+
49
+
50
+ def _is_process_alive(pid: int) -> bool:
51
+ """Check if a process with the given PID is still alive.
52
+
53
+ Args:
54
+ pid: Process ID to check
55
+
56
+ Returns:
57
+ bool: True if process exists, False otherwise
58
+ """
59
+ try:
60
+ # On Unix: os.kill(pid, 0) raises OSError if process doesn't exist
61
+ # On Windows: This also works with signal 0
62
+ os.kill(pid, 0)
63
+ return True
64
+ except (OSError, ProcessLookupError):
65
+ return False
66
+
67
+
68
+ def _cleanup_dead_sessions(sessions: dict[str, str]) -> dict[str, str]:
69
+ """Remove sessions for processes that no longer exist.
70
+
71
+ Args:
72
+ sessions: Dictionary of session_id -> agent_name
73
+
74
+ Returns:
75
+ dict: Cleaned sessions dictionary
76
+ """
77
+ cleaned = {}
78
+ for session_id, agent_name in sessions.items():
79
+ if session_id.startswith("session_"):
80
+ try:
81
+ pid_str = session_id.replace("session_", "")
82
+ pid = int(pid_str)
83
+ if _is_process_alive(pid):
84
+ cleaned[session_id] = agent_name
85
+ # else: skip dead session
86
+ except (ValueError, TypeError):
87
+ # Invalid session ID format, keep it anyway
88
+ cleaned[session_id] = agent_name
89
+ else:
90
+ # Non-standard session ID (like "fallback_"), keep it
91
+ cleaned[session_id] = agent_name
92
+ return cleaned
93
+
94
+
95
+ def _load_session_data() -> dict[str, str]:
96
+ """Load terminal session data from the JSON file.
97
+
98
+ Returns:
99
+ dict: Session ID to agent name mapping
100
+ """
101
+ session_file = _get_session_file_path()
102
+ try:
103
+ if session_file.exists():
104
+ with open(session_file, "r", encoding="utf-8") as f:
105
+ data = json.load(f)
106
+ # Clean up dead sessions while loading
107
+ return _cleanup_dead_sessions(data)
108
+ return {}
109
+ except (json.JSONDecodeError, IOError, OSError):
110
+ # File corrupted or permission issues, start fresh
111
+ return {}
112
+
113
+
114
+ def _save_session_data(sessions: dict[str, str]) -> None:
115
+ """Save terminal session data to the JSON file.
116
+
117
+ Args:
118
+ sessions: Session ID to agent name mapping
119
+ """
120
+ session_file = _get_session_file_path()
121
+ try:
122
+ # Ensure the config directory exists
123
+ session_file.parent.mkdir(parents=True, exist_ok=True)
124
+
125
+ # Clean up dead sessions before saving
126
+ cleaned_sessions = _cleanup_dead_sessions(sessions)
127
+
128
+ # Write to file atomically (write to temp file, then rename)
129
+ temp_file = session_file.with_suffix(".tmp")
130
+ with open(temp_file, "w", encoding="utf-8") as f:
131
+ json.dump(cleaned_sessions, f, indent=2)
132
+
133
+ # Atomic rename (works on all platforms)
134
+ temp_file.replace(session_file)
135
+
136
+ except (IOError, OSError):
137
+ # File permission issues, etc. - just continue without persistence
138
+ pass
139
+
140
+
141
+ def _ensure_session_cache_loaded() -> None:
142
+ """Ensure the session cache is loaded from disk."""
143
+ global _SESSION_AGENTS_CACHE, _SESSION_FILE_LOADED
144
+ if not _SESSION_FILE_LOADED:
145
+ _SESSION_AGENTS_CACHE.update(_load_session_data())
146
+ _SESSION_FILE_LOADED = True
147
+
148
+
149
+ # Persistent storage for agent message histories
150
+ _AGENT_HISTORIES: Dict[str, Dict[str, any]] = {}
151
+ # Structure: {agent_name: {"message_history": [...], "compacted_hashes": set(...)}}
152
+
153
+
154
+ def _save_agent_history(agent_name: str, agent: BaseAgent) -> None:
155
+ """Save an agent's message history to persistent storage.
156
+
157
+ Args:
158
+ agent_name: The name of the agent
159
+ agent: The agent instance to save history from
160
+ """
161
+ global _AGENT_HISTORIES
162
+ _AGENT_HISTORIES[agent_name] = {
163
+ "message_history": agent.get_message_history().copy(),
164
+ "compacted_hashes": agent.get_compacted_message_hashes().copy(),
165
+ }
166
+
167
+
168
+ def _restore_agent_history(agent_name: str, agent: BaseAgent) -> None:
169
+ """Restore an agent's message history from persistent storage.
170
+
171
+ Args:
172
+ agent_name: The name of the agent
173
+ agent: The agent instance to restore history to
174
+ """
175
+ global _AGENT_HISTORIES
176
+ if agent_name in _AGENT_HISTORIES:
177
+ stored_data = _AGENT_HISTORIES[agent_name]
178
+ agent.set_message_history(stored_data["message_history"])
179
+ # Restore compacted hashes
180
+ for hash_val in stored_data["compacted_hashes"]:
181
+ agent.add_compacted_message_hash(hash_val)
182
+
18
183
 
19
184
  def _discover_agents(message_group_id: Optional[str] = None):
20
185
  """Dynamically discover all agent classes and JSON agents."""
@@ -97,12 +262,14 @@ def get_available_agents() -> Dict[str, str]:
97
262
 
98
263
 
99
264
  def get_current_agent_name() -> str:
100
- """Get the name of the currently active agent.
265
+ """Get the name of the currently active agent for this terminal session.
101
266
 
102
267
  Returns:
103
- The name of the current agent, defaults to 'code-puppy'.
268
+ The name of the current agent for this session, defaults to 'code-puppy'.
104
269
  """
105
- return get_value("current_agent") or "code-puppy"
270
+ _ensure_session_cache_loaded()
271
+ session_id = get_terminal_session_id()
272
+ return _SESSION_AGENTS_CACHE.get(session_id, "code-puppy")
106
273
 
107
274
 
108
275
  def set_current_agent(agent_name: str) -> bool:
@@ -117,12 +284,26 @@ def set_current_agent(agent_name: str) -> bool:
117
284
  # Generate a message group ID for agent switching
118
285
  message_group_id = str(uuid.uuid4())
119
286
  _discover_agents(message_group_id=message_group_id)
287
+
288
+ # Save current agent's history before switching
289
+ global _CURRENT_AGENT_CONFIG, _CURRENT_AGENT_NAME
290
+ if _CURRENT_AGENT_CONFIG is not None:
291
+ _save_agent_history(_CURRENT_AGENT_CONFIG.name, _CURRENT_AGENT_CONFIG)
292
+
120
293
  # Clear the cached config when switching agents
121
- global _CURRENT_AGENT_CONFIG
122
294
  _CURRENT_AGENT_CONFIG = None
123
295
  agent_obj = load_agent_config(agent_name)
296
+
297
+ # Restore the agent's history if it exists
298
+ _restore_agent_history(agent_name, agent_obj)
299
+
300
+ # Update session-based agent selection and persist to disk
301
+ _ensure_session_cache_loaded()
302
+ session_id = get_terminal_session_id()
303
+ _SESSION_AGENTS_CACHE[session_id] = agent_name
304
+ _save_session_data(_SESSION_AGENTS_CACHE)
305
+
124
306
  on_agent_reload(agent_obj.id, agent_name)
125
- set_config_value("current_agent", agent_name)
126
307
  return True
127
308
 
128
309
 
@@ -134,7 +315,11 @@ def get_current_agent_config() -> BaseAgent:
134
315
  """
135
316
  global _CURRENT_AGENT_CONFIG
136
317
 
137
- _CURRENT_AGENT_CONFIG = load_agent_config(get_current_agent_name())
318
+ if _CURRENT_AGENT_CONFIG is None:
319
+ agent_name = get_current_agent_name()
320
+ _CURRENT_AGENT_CONFIG = load_agent_config(agent_name)
321
+ # Restore the agent's history if it exists
322
+ _restore_agent_history(agent_name, _CURRENT_AGENT_CONFIG)
138
323
 
139
324
  return _CURRENT_AGENT_CONFIG
140
325
 
@@ -201,6 +386,20 @@ def clear_agent_cache():
201
386
  _CURRENT_AGENT_CONFIG = None
202
387
 
203
388
 
389
+ def reset_to_default_agent():
390
+ """Reset the current agent to the default (code-puppy) for this terminal session.
391
+
392
+ This is useful for testing or when you want to start fresh.
393
+ """
394
+ global _CURRENT_AGENT_CONFIG
395
+ _ensure_session_cache_loaded()
396
+ session_id = get_terminal_session_id()
397
+ if session_id in _SESSION_AGENTS_CACHE:
398
+ del _SESSION_AGENTS_CACHE[session_id]
399
+ _save_session_data(_SESSION_AGENTS_CACHE)
400
+ _CURRENT_AGENT_CONFIG = None
401
+
402
+
204
403
  def refresh_agents():
205
404
  """Refresh the agent discovery to pick up newly created agents.
206
405
 
@@ -209,3 +408,115 @@ def refresh_agents():
209
408
  # Generate a message group ID for agent refreshing
210
409
  message_group_id = str(uuid.uuid4())
211
410
  _discover_agents(message_group_id=message_group_id)
411
+
412
+
413
+ def clear_all_agent_histories():
414
+ """Clear all agent message histories from persistent storage.
415
+
416
+ This is useful for debugging or when you want a fresh start.
417
+ """
418
+ global _AGENT_HISTORIES
419
+ _AGENT_HISTORIES.clear()
420
+ # Also clear the current agent's history
421
+ if _CURRENT_AGENT_CONFIG is not None:
422
+ _CURRENT_AGENT_CONFIG.messages = []
423
+
424
+
425
+ def cleanup_dead_terminal_sessions() -> int:
426
+ """Clean up terminal sessions for processes that no longer exist.
427
+
428
+ Returns:
429
+ int: Number of dead sessions removed
430
+ """
431
+ _ensure_session_cache_loaded()
432
+ original_count = len(_SESSION_AGENTS_CACHE)
433
+ cleaned_cache = _cleanup_dead_sessions(_SESSION_AGENTS_CACHE)
434
+
435
+ if len(cleaned_cache) != original_count:
436
+ _SESSION_AGENTS_CACHE.clear()
437
+ _SESSION_AGENTS_CACHE.update(cleaned_cache)
438
+ _save_session_data(_SESSION_AGENTS_CACHE)
439
+
440
+ return original_count - len(cleaned_cache)
441
+
442
+
443
+ # Agent-aware message history functions
444
+ def get_current_agent_message_history():
445
+ """Get the message history for the currently active agent.
446
+
447
+ Returns:
448
+ List of messages from the current agent's conversation history.
449
+ """
450
+ current_agent = get_current_agent_config()
451
+ return current_agent.get_message_history()
452
+
453
+
454
+ def set_current_agent_message_history(history):
455
+ """Set the message history for the currently active agent.
456
+
457
+ Args:
458
+ history: List of messages to set as the current agent's conversation history.
459
+ """
460
+ current_agent = get_current_agent_config()
461
+ current_agent.set_message_history(history)
462
+ # Also update persistent storage
463
+ _save_agent_history(current_agent.name, current_agent)
464
+
465
+
466
+ def clear_current_agent_message_history():
467
+ """Clear the message history for the currently active agent."""
468
+ current_agent = get_current_agent_config()
469
+ current_agent.clear_message_history()
470
+ # Also clear from persistent storage
471
+ global _AGENT_HISTORIES
472
+ if current_agent.name in _AGENT_HISTORIES:
473
+ _AGENT_HISTORIES[current_agent.name] = {
474
+ "message_history": [],
475
+ "compacted_hashes": set(),
476
+ }
477
+
478
+
479
+ def append_to_current_agent_message_history(message):
480
+ """Append a message to the currently active agent's history.
481
+
482
+ Args:
483
+ message: Message to append to the current agent's conversation history.
484
+ """
485
+ current_agent = get_current_agent_config()
486
+ current_agent.append_to_message_history(message)
487
+ # Also update persistent storage
488
+ _save_agent_history(current_agent.name, current_agent)
489
+
490
+
491
+ def extend_current_agent_message_history(history):
492
+ """Extend the currently active agent's message history with multiple messages.
493
+
494
+ Args:
495
+ history: List of messages to append to the current agent's conversation history.
496
+ """
497
+ current_agent = get_current_agent_config()
498
+ current_agent.extend_message_history(history)
499
+ # Also update persistent storage
500
+ _save_agent_history(current_agent.name, current_agent)
501
+
502
+
503
+ def get_current_agent_compacted_message_hashes():
504
+ """Get the set of compacted message hashes for the currently active agent.
505
+
506
+ Returns:
507
+ Set of hashes for messages that have been compacted/summarized.
508
+ """
509
+ current_agent = get_current_agent_config()
510
+ return current_agent.get_compacted_message_hashes()
511
+
512
+
513
+ def add_current_agent_compacted_message_hash(message_hash: str):
514
+ """Add a message hash to the current agent's set of compacted message hashes.
515
+
516
+ Args:
517
+ message_hash: Hash of a message that has been compacted/summarized.
518
+ """
519
+ current_agent = get_current_agent_config()
520
+ current_agent.add_compacted_message_hash(message_hash)
521
+ # Also update persistent storage
522
+ _save_agent_history(current_agent.name, current_agent)
@@ -1,8 +1,8 @@
1
1
  """Base agent configuration class for defining agent properties."""
2
2
 
3
- from abc import ABC, abstractmethod
4
- from typing import Any, Dict, List, Optional
5
3
  import uuid
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any, Dict, List, Optional, Set
6
6
 
7
7
 
8
8
  class BaseAgent(ABC):
@@ -10,6 +10,8 @@ class BaseAgent(ABC):
10
10
 
11
11
  def __init__(self):
12
12
  self.id = str(uuid.uuid4())
13
+ self._message_history: List[Any] = []
14
+ self._compacted_message_hashes: Set[str] = set()
13
15
 
14
16
  @property
15
17
  @abstractmethod
@@ -58,3 +60,57 @@ class BaseAgent(ABC):
58
60
  Custom prompt string, or None to use default.
59
61
  """
60
62
  return None
63
+
64
+ # Message history management methods
65
+ def get_message_history(self) -> List[Any]:
66
+ """Get the message history for this agent.
67
+
68
+ Returns:
69
+ List of messages in this agent's conversation history.
70
+ """
71
+ return self._message_history
72
+
73
+ def set_message_history(self, history: List[Any]) -> None:
74
+ """Set the message history for this agent.
75
+
76
+ Args:
77
+ history: List of messages to set as the conversation history.
78
+ """
79
+ self._message_history = history
80
+
81
+ def clear_message_history(self) -> None:
82
+ """Clear the message history for this agent."""
83
+ self._message_history = []
84
+ self._compacted_message_hashes.clear()
85
+
86
+ def append_to_message_history(self, message: Any) -> None:
87
+ """Append a message to this agent's history.
88
+
89
+ Args:
90
+ message: Message to append to the conversation history.
91
+ """
92
+ self._message_history.append(message)
93
+
94
+ def extend_message_history(self, history: List[Any]) -> None:
95
+ """Extend this agent's message history with multiple messages.
96
+
97
+ Args:
98
+ history: List of messages to append to the conversation history.
99
+ """
100
+ self._message_history.extend(history)
101
+
102
+ def get_compacted_message_hashes(self) -> Set[str]:
103
+ """Get the set of compacted message hashes for this agent.
104
+
105
+ Returns:
106
+ Set of hashes for messages that have been compacted/summarized.
107
+ """
108
+ return self._compacted_message_hashes
109
+
110
+ def add_compacted_message_hash(self, message_hash: str) -> None:
111
+ """Add a message hash to the set of compacted message hashes.
112
+
113
+ Args:
114
+ message_hash: Hash of a message that has been compacted/summarized.
115
+ """
116
+ self._compacted_message_hashes.add(message_hash)