neuro-simulator 0.1.3__py3-none-any.whl → 0.2.0__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 (49) hide show
  1. neuro_simulator/__init__.py +1 -10
  2. neuro_simulator/agent/__init__.py +1 -8
  3. neuro_simulator/agent/base.py +43 -0
  4. neuro_simulator/agent/core.py +111 -397
  5. neuro_simulator/agent/factory.py +30 -0
  6. neuro_simulator/agent/llm.py +34 -31
  7. neuro_simulator/agent/memory/__init__.py +1 -4
  8. neuro_simulator/agent/memory/manager.py +61 -203
  9. neuro_simulator/agent/tools/__init__.py +1 -4
  10. neuro_simulator/agent/tools/core.py +8 -18
  11. neuro_simulator/api/__init__.py +1 -0
  12. neuro_simulator/api/agent.py +163 -0
  13. neuro_simulator/api/stream.py +55 -0
  14. neuro_simulator/api/system.py +90 -0
  15. neuro_simulator/cli.py +53 -142
  16. neuro_simulator/core/__init__.py +1 -0
  17. neuro_simulator/core/agent_factory.py +52 -0
  18. neuro_simulator/core/agent_interface.py +91 -0
  19. neuro_simulator/core/application.py +278 -0
  20. neuro_simulator/services/__init__.py +1 -0
  21. neuro_simulator/{chatbot.py → services/audience.py} +24 -24
  22. neuro_simulator/{audio_synthesis.py → services/audio.py} +18 -15
  23. neuro_simulator/services/builtin.py +87 -0
  24. neuro_simulator/services/letta.py +206 -0
  25. neuro_simulator/{stream_manager.py → services/stream.py} +39 -47
  26. neuro_simulator/utils/__init__.py +1 -0
  27. neuro_simulator/utils/logging.py +90 -0
  28. neuro_simulator/utils/process.py +67 -0
  29. neuro_simulator/{stream_chat.py → utils/queue.py} +17 -4
  30. neuro_simulator/utils/state.py +14 -0
  31. neuro_simulator/{websocket_manager.py → utils/websocket.py} +18 -14
  32. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/METADATA +176 -176
  33. neuro_simulator-0.2.0.dist-info/RECORD +37 -0
  34. neuro_simulator/agent/api.py +0 -737
  35. neuro_simulator/agent/memory.py +0 -137
  36. neuro_simulator/agent/tools.py +0 -69
  37. neuro_simulator/builtin_agent.py +0 -83
  38. neuro_simulator/config.yaml.example +0 -157
  39. neuro_simulator/letta.py +0 -164
  40. neuro_simulator/log_handler.py +0 -43
  41. neuro_simulator/main.py +0 -673
  42. neuro_simulator/media/neuro_start.mp4 +0 -0
  43. neuro_simulator/process_manager.py +0 -70
  44. neuro_simulator/shared_state.py +0 -11
  45. neuro_simulator-0.1.3.dist-info/RECORD +0 -31
  46. /neuro_simulator/{config.py → core/config.py} +0 -0
  47. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/WHEEL +0 -0
  48. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/entry_points.txt +0 -0
  49. {neuro_simulator-0.1.3.dist-info → neuro_simulator-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,163 @@
1
+ # neuro_simulator/api/agent.py
2
+ """Unified API endpoints for agent management, decoupled from implementation."""
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException, status, Request
5
+ from typing import Dict, Any, List, Optional
6
+ from pydantic import BaseModel
7
+
8
+ # Imports for the new structure
9
+ from ..core.config import config_manager
10
+ from ..core.agent_factory import create_agent
11
+ from ..core.agent_interface import BaseAgent
12
+
13
+ router = APIRouter(prefix="/api/agent", tags=["Agent Management"])
14
+
15
+ # Security dependency (remains the same)
16
+ async def get_api_token(request: Request):
17
+ password = config_manager.settings.server.panel_password
18
+ if not password:
19
+ return True
20
+ header_token = request.headers.get("X-API-Token")
21
+ if header_token and header_token == password:
22
+ return True
23
+ raise HTTPException(
24
+ status_code=status.HTTP_401_UNAUTHORIZED,
25
+ detail="Invalid API token",
26
+ headers={"WWW-Authenticate": "Bearer"},
27
+ )
28
+
29
+ # Pydantic models (remains the same)
30
+ class MessageItem(BaseModel):
31
+ username: str
32
+ text: str
33
+ role: str = "user"
34
+
35
+ class ToolExecutionRequest(BaseModel):
36
+ tool_name: str
37
+ params: Dict[str, Any]
38
+
39
+ class MemoryUpdateRequest(BaseModel):
40
+ title: Optional[str] = None
41
+ description: Optional[str] = None
42
+ content: Optional[List[str]] = None
43
+
44
+ class MemoryCreateRequest(BaseModel):
45
+ title: str
46
+ description: str
47
+ content: List[str]
48
+
49
+ class InitMemoryUpdateRequest(BaseModel):
50
+ memory: Dict[str, Any]
51
+
52
+ class TempMemoryItem(BaseModel):
53
+ content: str
54
+ role: str = "system"
55
+
56
+ # Dependency to get the agent instance, making endpoints cleaner
57
+ async def get_agent() -> BaseAgent:
58
+ return await create_agent()
59
+
60
+ # A single dependency for both auth and agent instance
61
+ class AgentDeps:
62
+ def __init__(self, token: bool = Depends(get_api_token), agent: BaseAgent = Depends(get_agent)):
63
+ self.agent = agent
64
+
65
+ # --- Refactored Agent Endpoints ---
66
+
67
+ @router.get("/messages", response_model=List[Dict[str, Any]])
68
+ async def get_agent_messages(deps: AgentDeps = Depends()):
69
+ """Get agent's detailed message processing history."""
70
+ return await deps.agent.get_message_history()
71
+
72
+ @router.get("/context", response_model=List[Dict[str, Any]])
73
+ async def get_agent_context(deps: AgentDeps = Depends()):
74
+ """Get agent's recent conversation context (last 20 entries)."""
75
+ return await deps.agent.get_message_history(limit=20)
76
+
77
+ @router.delete("/messages")
78
+ async def clear_agent_messages(deps: AgentDeps = Depends()):
79
+ """Clear agent's message history."""
80
+ await deps.agent.reset_memory()
81
+ return {"status": "success", "message": "Agent memory reset successfully"}
82
+
83
+ @router.post("/messages")
84
+ async def send_message_to_agent(message: MessageItem, deps: AgentDeps = Depends()):
85
+ """Send a message to the agent."""
86
+ response = await deps.agent.process_messages([message.dict()])
87
+ return {"response": response}
88
+
89
+ @router.get("/memory/init", response_model=Dict[str, Any])
90
+ async def get_init_memory(deps: AgentDeps = Depends()):
91
+ """Get initialization memory content."""
92
+ return await deps.agent.get_init_memory()
93
+
94
+ @router.put("/memory/init")
95
+ async def update_init_memory(request: InitMemoryUpdateRequest, deps: AgentDeps = Depends()):
96
+ """Update initialization memory content."""
97
+ await deps.agent.update_init_memory(request.memory)
98
+ return {"status": "success", "message": "Initialization memory updated"}
99
+
100
+ @router.get("/memory/temp", response_model=List[Dict[str, Any]])
101
+ async def get_temp_memory(deps: AgentDeps = Depends()):
102
+ """Get all temporary memory content."""
103
+ return await deps.agent.get_temp_memory()
104
+
105
+ @router.post("/memory/temp")
106
+ async def add_temp_memory_item(request: TempMemoryItem, deps: AgentDeps = Depends()):
107
+ """Add an item to temporary memory."""
108
+ await deps.agent.add_temp_memory(request.content, request.role)
109
+ return {"status": "success", "message": "Item added to temporary memory"}
110
+
111
+ @router.delete("/memory/temp")
112
+ async def clear_temp_memory(deps: AgentDeps = Depends()):
113
+ """Clear temporary memory."""
114
+ await deps.agent.clear_temp_memory()
115
+ return {"status": "success", "message": "Temporary memory cleared"}
116
+
117
+ @router.get("/memory/blocks", response_model=List[Dict[str, Any]])
118
+ async def get_memory_blocks(deps: AgentDeps = Depends()):
119
+ """Get all memory blocks."""
120
+ return await deps.agent.get_memory_blocks()
121
+
122
+ @router.get("/memory/blocks/{block_id}", response_model=Dict[str, Any])
123
+ async def get_memory_block(block_id: str, deps: AgentDeps = Depends()):
124
+ """Get a specific memory block."""
125
+ block = await deps.agent.get_memory_block(block_id)
126
+ if block is None:
127
+ raise HTTPException(status_code=404, detail="Memory block not found")
128
+ return block
129
+
130
+ @router.post("/memory/blocks", response_model=Dict[str, str])
131
+ async def create_memory_block(request: MemoryCreateRequest, deps: AgentDeps = Depends()):
132
+ """Create a new memory block."""
133
+ return await deps.agent.create_memory_block(request.title, request.description, request.content)
134
+
135
+ @router.put("/memory/blocks/{block_id}")
136
+ async def update_memory_block(block_id: str, request: MemoryUpdateRequest, deps: AgentDeps = Depends()):
137
+ """Update a memory block."""
138
+ await deps.agent.update_memory_block(block_id, request.title, request.description, request.content)
139
+ return {"status": "success"}
140
+
141
+ @router.delete("/memory/blocks/{block_id}")
142
+ async def delete_memory_block(block_id: str, deps: AgentDeps = Depends()):
143
+ """Delete a memory block."""
144
+ await deps.agent.delete_memory_block(block_id)
145
+ return {"status": "success"}
146
+
147
+ @router.post("/reset_memory")
148
+ async def reset_agent_memory(deps: AgentDeps = Depends()):
149
+ """Reset all agent memory types."""
150
+ await deps.agent.reset_memory()
151
+ return {"status": "success", "message": "Agent memory reset successfully"}
152
+
153
+ @router.get("/tools")
154
+ async def get_available_tools(deps: AgentDeps = Depends()):
155
+ """Get list of available tools."""
156
+ # Return in the format expected by the frontend
157
+ return {"tools": await deps.agent.get_available_tools()}
158
+
159
+ @router.post("/tools/execute")
160
+ async def execute_tool(request: ToolExecutionRequest, deps: AgentDeps = Depends()):
161
+ """Execute a tool with given parameters."""
162
+ result = await deps.agent.execute_tool(request.tool_name, request.params)
163
+ return {"result": result}
@@ -0,0 +1,55 @@
1
+ # neuro_simulator/api/stream.py
2
+ """API endpoints for controlling the live stream lifecycle."""
3
+
4
+ import asyncio
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ from fastapi import APIRouter, Depends
9
+
10
+ from ..core.agent_factory import create_agent
11
+ from ..utils.process import process_manager
12
+ from .agent import get_api_token # Re-using the auth dependency from agent API
13
+
14
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
15
+ router = APIRouter(prefix="/api/stream", tags=["Stream Control"])
16
+
17
+ @router.post("/start", dependencies=[Depends(get_api_token)])
18
+ async def start_stream():
19
+ """Starts the live stream processes."""
20
+ try:
21
+ agent = await create_agent()
22
+ await agent.reset_memory()
23
+ except Exception as e:
24
+ logger.error(f"Could not reset agent memory on stream start: {e}", exc_info=True)
25
+
26
+ if not process_manager.is_running:
27
+ process_manager.start_live_processes()
28
+ return {"status": "success", "message": "Stream started"}
29
+ else:
30
+ return {"status": "info", "message": "Stream is already running"}
31
+
32
+ @router.post("/stop", dependencies=[Depends(get_api_token)])
33
+ async def stop_stream():
34
+ """Stops the live stream processes."""
35
+ if process_manager.is_running:
36
+ process_manager.stop_live_processes()
37
+ return {"status": "success", "message": "Stream stopped"}
38
+ else:
39
+ return {"status": "info", "message": "Stream is not running"}
40
+
41
+ @router.post("/restart", dependencies=[Depends(get_api_token)])
42
+ async def restart_stream():
43
+ """Restarts the live stream processes."""
44
+ process_manager.stop_live_processes()
45
+ await asyncio.sleep(1) # Give time for tasks to cancel
46
+ process_manager.start_live_processes()
47
+ return {"status": "success", "message": "Stream restarted"}
48
+
49
+ @router.get("/status", dependencies=[Depends(get_api_token)])
50
+ async def get_stream_status():
51
+ """Gets the current status of the stream."""
52
+ return {
53
+ "is_running": process_manager.is_running,
54
+ "backend_status": "running" if process_manager.is_running else "stopped"
55
+ }
@@ -0,0 +1,90 @@
1
+ # neuro_simulator/api/system.py
2
+ """API endpoints for system, config, and utility functions."""
3
+
4
+ from fastapi import APIRouter, Depends, HTTPException
5
+ from pydantic import BaseModel
6
+ import time
7
+
8
+ from ..core.config import config_manager, AppSettings
9
+ from ..services.audio import synthesize_audio_segment
10
+ from .agent import get_api_token # Re-using the auth dependency
11
+
12
+ router = APIRouter(tags=["System & Utilities"])
13
+
14
+ # --- TTS Endpoint ---
15
+
16
+ class SpeechRequest(BaseModel):
17
+ text: str
18
+ voice_name: str | None = None
19
+ pitch: float | None = None
20
+
21
+ @router.post("/api/tts/synthesize", dependencies=[Depends(get_api_token)])
22
+ async def synthesize_speech_endpoint(request: SpeechRequest):
23
+ """Synthesizes text to speech using the configured TTS service."""
24
+ try:
25
+ audio_base64, _ = await synthesize_audio_segment(
26
+ text=request.text, voice_name=request.voice_name, pitch=request.pitch
27
+ )
28
+ return {"audio_base64": audio_base64}
29
+ except Exception as e:
30
+ raise HTTPException(status_code=500, detail=str(e))
31
+
32
+ # --- Config Management Endpoints ---
33
+
34
+ # This helper can be moved to a more central location if needed
35
+ def filter_config_for_frontend(settings: AppSettings):
36
+ """Filters the settings to return only the fields safe for the frontend."""
37
+ # Using .model_dump() with include is a more robust Pydantic approach
38
+ return settings.model_dump(include={
39
+ 'stream_metadata': {'stream_title', 'stream_category', 'stream_tags'},
40
+ 'agent': {'agent_provider', 'agent_model'},
41
+ 'neuro_behavior': {'input_chat_sample_size', 'post_speech_cooldown_sec', 'initial_greeting'},
42
+ 'audience_simulation': {'llm_provider', 'gemini_model', 'openai_model', 'llm_temperature', 'chat_generation_interval_sec', 'chats_per_batch', 'max_output_tokens', 'username_blocklist', 'username_pool'},
43
+ 'performance': {'neuro_input_queue_max_size', 'audience_chat_buffer_max_size', 'initial_chat_backlog_limit'}
44
+ })
45
+
46
+ @router.get("/api/configs", dependencies=[Depends(get_api_token)])
47
+ async def get_configs():
48
+ """Gets the current, frontend-safe configuration."""
49
+ return filter_config_for_frontend(config_manager.settings)
50
+
51
+ @router.patch("/api/configs", dependencies=[Depends(get_api_token)])
52
+ async def update_configs(new_settings: dict):
53
+ """Updates the configuration with new values from the frontend."""
54
+ try:
55
+ await config_manager.update_settings(new_settings)
56
+ return filter_config_for_frontend(config_manager.settings)
57
+ except Exception as e:
58
+ raise HTTPException(status_code=500, detail=f"Failed to update settings: {str(e)}")
59
+
60
+ @router.post("/api/configs/reload", dependencies=[Depends(get_api_token)])
61
+ async def reload_configs():
62
+ """Triggers a reload of the configuration from the config.yaml file."""
63
+ try:
64
+ # Passing an empty dict forces a reload and triggers callbacks
65
+ await config_manager.update_settings({})
66
+ return {"status": "success", "message": "Configuration reloaded"}
67
+ except Exception as e:
68
+ raise HTTPException(status_code=500, detail=f"Failed to reload settings: {str(e)}")
69
+
70
+ # --- System Endpoints ---
71
+
72
+ @router.get("/api/system/health")
73
+ async def health_check():
74
+ """Provides a simple health check of the server."""
75
+ from ..utils.process import process_manager
76
+ return {
77
+ "status": "healthy",
78
+ "backend_running": True,
79
+ "process_manager_running": process_manager.is_running,
80
+ "timestamp": time.time()
81
+ }
82
+
83
+ @router.get("/")
84
+ async def root():
85
+ """Returns basic information about the API."""
86
+ return {
87
+ "message": "Neuro-Sama Simulator Backend",
88
+ "version": "2.0",
89
+ "api_docs": "/docs",
90
+ }
neuro_simulator/cli.py CHANGED
@@ -1,177 +1,88 @@
1
1
  #!/usr/bin/env python3
2
+ """Command-line interface for the Neuro-Simulator Server."""
2
3
 
3
4
  import argparse
5
+ import logging
4
6
  import os
5
- import sys
6
7
  import shutil
8
+ import sys
7
9
  from pathlib import Path
8
10
 
11
+ def copy_resource(package_name: str, resource_path: str, destination_path: Path, is_dir: bool = False):
12
+ """A helper function to copy a resource from the package to the working directory."""
13
+ if destination_path.exists():
14
+ return
15
+
16
+ try:
17
+ import pkg_resources
18
+ source_path_str = pkg_resources.resource_filename(package_name, resource_path)
19
+ source_path = Path(source_path_str)
20
+ except (ModuleNotFoundError, KeyError):
21
+ source_path = Path(__file__).parent / resource_path
22
+
23
+ if source_path.exists():
24
+ try:
25
+ if is_dir:
26
+ shutil.copytree(source_path, destination_path)
27
+ else:
28
+ shutil.copy(source_path, destination_path)
29
+ logging.info(f"Created '{destination_path}' from package resource.")
30
+ except Exception as e:
31
+ logging.warning(f"Could not copy resource '{resource_path}'. Error: {e}")
32
+ else:
33
+ logging.warning(f"Resource '{resource_path}' not found in package or development folder.")
9
34
 
10
35
  def main():
36
+ """Main entry point for the CLI."""
11
37
  parser = argparse.ArgumentParser(description="Neuro-Simulator Server")
12
- parser.add_argument("-D", "--dir", help="Working directory containing config.yaml")
38
+ parser.add_argument("-D", "--dir", help="Working directory for config and data")
13
39
  parser.add_argument("-H", "--host", help="Host to bind the server to")
14
40
  parser.add_argument("-P", "--port", type=int, help="Port to bind the server to")
15
41
 
16
42
  args = parser.parse_args()
17
43
 
18
- # Set working directory
44
+ # 1. Set working directory
19
45
  if args.dir:
20
46
  work_dir = Path(args.dir).resolve()
21
- # If the directory doesn't exist (and it's not the default), raise an error
22
47
  if not work_dir.exists():
23
- print(f"Error: Working directory '{work_dir}' does not exist. Please create it manually.")
48
+ logging.error(f"Working directory '{work_dir}' does not exist. Please create it first.")
24
49
  sys.exit(1)
25
50
  else:
26
51
  work_dir = Path.home() / ".config" / "neuro-simulator"
27
52
  work_dir.mkdir(parents=True, exist_ok=True)
28
53
 
29
- # Change to working directory
30
54
  os.chdir(work_dir)
31
-
32
- # Handle config.yaml.example
33
- settings_example_path = work_dir / "config.yaml.example"
34
- settings_path = work_dir / "config.yaml"
35
-
36
- # Copy config.yaml.example from package if it doesn't exist
37
- if not settings_example_path.exists():
38
- try:
39
- # Try pkg_resources first (for installed packages)
40
- try:
41
- import pkg_resources
42
- example_path = pkg_resources.resource_filename('neuro_simulator', 'config.yaml.example')
43
- if os.path.exists(example_path):
44
- shutil.copy(example_path, settings_example_path)
45
- print(f"Created {settings_example_path} from package example")
46
- else:
47
- # Fallback to relative path (for development mode)
48
- dev_example_path = Path(__file__).parent / "config.yaml.example"
49
- if dev_example_path.exists():
50
- shutil.copy(dev_example_path, settings_example_path)
51
- print(f"Created {settings_example_path} from development example")
52
- else:
53
- print("Warning: config.yaml.example not found in package or development folder")
54
- except Exception:
55
- # Fallback to relative path (for development mode)
56
- dev_example_path = Path(__file__).parent / "config.yaml.example"
57
- if dev_example_path.exists():
58
- shutil.copy(dev_example_path, settings_example_path)
59
- print(f"Created {settings_example_path} from development example")
60
- else:
61
- print("Warning: config.yaml.example not found in package or development folder")
62
- except Exception as e:
63
- print(f"Warning: Could not copy config.yaml.example from package: {e}")
64
-
65
- # Handle media folder
66
- media_dir = work_dir / "media"
67
- video_path = media_dir / "neuro_start.mp4"
68
-
69
- # Copy media folder from package if it doesn't exist or is invalid
70
- if not media_dir.exists() or not video_path.exists():
71
- # If media dir exists but video doesn't, remove the incomplete media dir
72
- if media_dir.exists():
73
- shutil.rmtree(media_dir)
74
-
75
- try:
76
- # Try pkg_resources first (for installed packages)
77
- try:
78
- import pkg_resources
79
- package_media_path = pkg_resources.resource_filename('neuro_simulator', 'media')
80
- if os.path.exists(package_media_path):
81
- shutil.copytree(package_media_path, media_dir)
82
- print(f"Created {media_dir} from package media")
83
- else:
84
- # Fallback to relative path (for development mode)
85
- dev_media_path = Path(__file__).parent / "media"
86
- if dev_media_path.exists():
87
- shutil.copytree(dev_media_path, media_dir)
88
- print(f"Created {media_dir} from development media")
89
- else:
90
- print("Warning: media folder not found in package or development folder")
91
- except Exception:
92
- # Fallback to relative path (for development mode)
93
- dev_media_path = Path(__file__).parent / "media"
94
- if dev_media_path.exists():
95
- shutil.copytree(dev_media_path, media_dir)
96
- print(f"Created {media_dir} from development media")
97
- else:
98
- print("Warning: media folder not found in package or development folder")
99
- except Exception as e:
100
- print(f"Warning: Could not copy media folder from package: {e}")
101
-
102
- # Handle agent/memory directory and example JSON files
55
+ logging.info(f"Using working directory: {work_dir}")
56
+
57
+ # 2. Ensure required assets and configs exist
58
+ copy_resource('neuro_simulator', 'core/config.yaml.example', work_dir / 'config.yaml.example')
59
+ copy_resource('neuro_simulator', 'assets', work_dir / 'assets', is_dir=True)
103
60
  agent_memory_dir = work_dir / "agent" / "memory"
104
61
  agent_memory_dir.mkdir(parents=True, exist_ok=True)
105
-
106
- # List of example JSON files to copy
107
- example_memory_files = [
108
- "context.json",
109
- "core_memory.json",
110
- "dialog_history.json",
111
- "init_memory.json"
112
- ]
113
-
114
- # Copy each example memory file if it doesn't exist
115
- for filename in example_memory_files:
116
- target_path = agent_memory_dir / filename
117
- if not target_path.exists():
118
- try:
119
- # Try pkg_resources first (for installed packages)
120
- try:
121
- import pkg_resources
122
- package_example_path = pkg_resources.resource_filename('neuro_simulator', f'agent/memory/{filename}')
123
- if os.path.exists(package_example_path):
124
- shutil.copy(package_example_path, target_path)
125
- print(f"Created {target_path} from package example")
126
- else:
127
- # Fallback to relative path (for development mode)
128
- dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
129
- if dev_example_path.exists():
130
- shutil.copy(dev_example_path, target_path)
131
- print(f"Created {target_path} from development example")
132
- else:
133
- print(f"Warning: {filename} not found in package or development folder")
134
- except Exception:
135
- # Fallback to relative path (for development mode)
136
- dev_example_path = Path(__file__).parent / "agent" / "memory" / filename
137
- if dev_example_path.exists():
138
- shutil.copy(dev_example_path, target_path)
139
- print(f"Created {target_path} from development example")
140
- else:
141
- print(f"Warning: {filename} not found in package or development folder")
142
- except Exception as e:
143
- print(f"Warning: Could not copy {filename} from package: {e}")
144
-
145
- # Now check for required files and handle errors appropriately
62
+ for filename in ["context.json", "core_memory.json", "dialog_history.json", "init_memory.json"]:
63
+ copy_resource('neuro_simulator', f'agent/memory/{filename}', agent_memory_dir / filename)
64
+
65
+ # 3. Validate essential files
146
66
  errors = []
147
-
148
- # Check for config.yaml (required for running)
149
- if not settings_path.exists():
150
- if settings_example_path.exists():
151
- errors.append(f"Error: {settings_path} not found. Please copy {settings_example_path} to {settings_path} and configure it.")
152
- else:
153
- errors.append(f"Error: Neither {settings_path} nor {settings_example_path} found. Please ensure proper configuration.")
154
-
155
- # Check for required media files (required for running)
156
- if not media_dir.exists() or not video_path.exists():
157
- errors.append(f"Error: Required media files not found in {media_dir}.")
158
-
159
- # If there are any errors, print them and exit
67
+ if not (work_dir / "config.yaml").exists():
68
+ errors.append(f"'config.yaml' not found in '{work_dir}'. Please copy 'config.yaml.example' to 'config.yaml' and configure it.")
69
+ if not (work_dir / "assets" / "neuro_start.mp4").exists():
70
+ errors.append(f"Required file 'neuro_start.mp4' not found in '{work_dir / 'assets'}'.")
71
+
160
72
  if errors:
161
73
  for error in errors:
162
- print(error)
74
+ logging.error(error)
163
75
  sys.exit(1)
164
-
165
- # Import and run the main application
76
+
77
+ # 4. Import and run the server
166
78
  try:
167
- from neuro_simulator.main import run_server
79
+ from neuro_simulator.core.application import run_server
80
+ logging.info("Starting Neuro-Simulator server...")
81
+ # The full application logger will take over from here
168
82
  run_server(args.host, args.port)
169
- except ImportError:
170
- # Fallback for development mode
171
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
172
- from neuro_simulator.main import run_server
173
- run_server(args.host, args.port)
174
-
83
+ except ImportError as e:
84
+ logging.error(f"Could not import the application. Make sure the package is installed correctly. Details: {e}", exc_info=True)
85
+ sys.exit(1)
175
86
 
176
87
  if __name__ == "__main__":
177
88
  main()
@@ -0,0 +1 @@
1
+ # This file makes the 'core' directory a Python package.
@@ -0,0 +1,52 @@
1
+ # neuro_simulator/core/agent_factory.py
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ from .agent_interface import BaseAgent
6
+ from .config import config_manager, AppSettings
7
+
8
+ logger = logging.getLogger(__name__.replace("neuro_simulator", "server", 1))
9
+
10
+ # A cache for the agent instance to avoid re-initialization
11
+ _agent_instance: BaseAgent = None
12
+
13
+ def _reset_agent_on_config_update(new_settings: AppSettings):
14
+ global _agent_instance
15
+ logger.info("Configuration has been updated. Resetting cached agent instance.")
16
+ _agent_instance = None
17
+
18
+ # Register the callback to the config manager
19
+ config_manager.register_update_callback(_reset_agent_on_config_update)
20
+
21
+ async def create_agent() -> BaseAgent:
22
+ """
23
+ Factory function to create and initialize an agent instance based on the configuration.
24
+ Returns a cached instance unless the configuration has changed.
25
+ """
26
+ global _agent_instance
27
+ if _agent_instance is not None:
28
+ return _agent_instance
29
+
30
+ agent_type = config_manager.settings.agent_type
31
+ logger.info(f"Creating new agent instance of type: {agent_type}")
32
+
33
+ if agent_type == "builtin":
34
+ from ..services.builtin import BuiltinAgentWrapper, initialize_builtin_agent
35
+
36
+ agent_impl = await initialize_builtin_agent()
37
+
38
+ if agent_impl is None:
39
+ raise RuntimeError("Failed to initialize the Builtin agent implementation.")
40
+
41
+ _agent_instance = BuiltinAgentWrapper(agent_impl)
42
+
43
+ elif agent_type == "letta":
44
+ from ..services.letta import LettaAgent
45
+ _agent_instance = LettaAgent()
46
+
47
+ else:
48
+ raise ValueError(f"Unknown agent type: {agent_type}")
49
+
50
+ await _agent_instance.initialize()
51
+
52
+ return _agent_instance