bohr-agent-sdk 0.1.101__py3-none-any.whl → 0.1.103__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. bohr_agent_sdk-0.1.103.dist-info/METADATA +292 -0
  2. bohr_agent_sdk-0.1.103.dist-info/RECORD +80 -0
  3. dp/agent/cli/cli.py +126 -25
  4. dp/agent/cli/templates/__init__.py +1 -0
  5. dp/agent/cli/templates/calculation/simple.py.template +15 -0
  6. dp/agent/cli/templates/device/tescan_device.py.template +158 -0
  7. dp/agent/cli/templates/main.py.template +67 -0
  8. dp/agent/cli/templates/ui/__init__.py +1 -0
  9. dp/agent/cli/templates/ui/api/__init__.py +1 -0
  10. dp/agent/cli/templates/ui/api/config.py +32 -0
  11. dp/agent/cli/templates/ui/api/constants.py +61 -0
  12. dp/agent/cli/templates/ui/api/debug.py +257 -0
  13. dp/agent/cli/templates/ui/api/files.py +469 -0
  14. dp/agent/cli/templates/ui/api/files_upload.py +115 -0
  15. dp/agent/cli/templates/ui/api/files_user.py +50 -0
  16. dp/agent/cli/templates/ui/api/messages.py +161 -0
  17. dp/agent/cli/templates/ui/api/projects.py +146 -0
  18. dp/agent/cli/templates/ui/api/sessions.py +93 -0
  19. dp/agent/cli/templates/ui/api/utils.py +161 -0
  20. dp/agent/cli/templates/ui/api/websocket.py +184 -0
  21. dp/agent/cli/templates/ui/config/__init__.py +1 -0
  22. dp/agent/cli/templates/ui/config/agent_config.py +257 -0
  23. dp/agent/cli/templates/ui/frontend/index.html +13 -0
  24. dp/agent/cli/templates/ui/frontend/package.json +46 -0
  25. dp/agent/cli/templates/ui/frontend/tsconfig.json +26 -0
  26. dp/agent/cli/templates/ui/frontend/tsconfig.node.json +10 -0
  27. dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DdAmKhul.js +105 -0
  28. dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DfN2raU9.css +1 -0
  29. dp/agent/cli/templates/ui/frontend/ui-static/index.html +14 -0
  30. dp/agent/cli/templates/ui/frontend/vite.config.ts +37 -0
  31. dp/agent/cli/templates/ui/scripts/build_ui.py +56 -0
  32. dp/agent/cli/templates/ui/server/__init__.py +0 -0
  33. dp/agent/cli/templates/ui/server/app.py +98 -0
  34. dp/agent/cli/templates/ui/server/connection.py +210 -0
  35. dp/agent/cli/templates/ui/server/file_watcher.py +85 -0
  36. dp/agent/cli/templates/ui/server/middleware.py +43 -0
  37. dp/agent/cli/templates/ui/server/models.py +53 -0
  38. dp/agent/cli/templates/ui/server/session_manager.py +1158 -0
  39. dp/agent/cli/templates/ui/server/user_files.py +85 -0
  40. dp/agent/cli/templates/ui/server/utils.py +50 -0
  41. dp/agent/cli/templates/ui/test_download.py +98 -0
  42. dp/agent/cli/templates/ui/ui_utils.py +260 -0
  43. dp/agent/cli/templates/ui/websocket-server.py +87 -0
  44. dp/agent/server/storage/http_storage.py +1 -1
  45. bohr_agent_sdk-0.1.101.dist-info/METADATA +0 -224
  46. bohr_agent_sdk-0.1.101.dist-info/RECORD +0 -40
  47. {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.103.dist-info}/WHEEL +0 -0
  48. {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.103.dist-info}/entry_points.txt +0 -0
  49. {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.103.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,85 @@
1
+ """
2
+ User file manager
3
+ """
4
+ from pathlib import Path
5
+
6
+
7
+ class UserFileManager:
8
+ """Manage user-specific file directories"""
9
+
10
+ def __init__(self, base_dir: str, sessions_dir: str = ".agent_sessions"):
11
+ self.base_dir = Path(base_dir)
12
+ # Support custom sessions directory, can be absolute or relative path
13
+ sessions_path = Path(sessions_dir)
14
+ if sessions_path.is_absolute():
15
+ self.sessions_dir = sessions_path
16
+ else:
17
+ self.sessions_dir = self.base_dir / sessions_dir
18
+ self.user_sessions_dir = self.sessions_dir / "user_sessions"
19
+ self.temp_sessions_dir = self.sessions_dir / "temp_sessions"
20
+
21
+ # Ensure directories exist
22
+ self.user_sessions_dir.mkdir(parents=True, exist_ok=True)
23
+ self.temp_sessions_dir.mkdir(parents=True, exist_ok=True)
24
+
25
+ def _get_user_dir(self, user_id: str) -> str:
26
+ """Get user directory name"""
27
+ # Use user_id directly as directory name
28
+ return user_id
29
+
30
+ def get_user_files_dir(self, user_id: str) -> Path:
31
+ """Get user's file directory
32
+
33
+ Args:
34
+ user_id: User's unique identifier (Bohrium user_id or temporary user ID)
35
+
36
+ Returns:
37
+ User's file directory path
38
+ """
39
+ if not user_id:
40
+ # If no user_id, use default directory
41
+ user_files_dir = self.temp_sessions_dir / "default" / "files"
42
+ elif user_id.startswith("user_"):
43
+ # Temporary user (generated ID starts with user_)
44
+ user_files_dir = self.temp_sessions_dir / user_id / "files"
45
+ else:
46
+ # Registered user (Bohrium user_id)
47
+ user_dir_name = self._get_user_dir(user_id)
48
+ user_files_dir = self.user_sessions_dir / user_dir_name / "files"
49
+
50
+ # Ensure directory exists
51
+ user_files_dir.mkdir(parents=True, exist_ok=True)
52
+
53
+ # Create default output subdirectory
54
+ output_dir = user_files_dir / "output"
55
+ output_dir.mkdir(exist_ok=True)
56
+
57
+ return user_files_dir
58
+
59
+ def cleanup_temp_files(self, max_age_days: int = 7):
60
+ """Clean up expired temporary user files
61
+
62
+ Args:
63
+ max_age_days: Maximum days to keep files
64
+ """
65
+ import time
66
+ import shutil
67
+
68
+ current_time = time.time()
69
+ max_age_seconds = max_age_days * 24 * 60 * 60
70
+
71
+ # Only clean temporary sessions directory
72
+ if not self.temp_sessions_dir.exists():
73
+ return
74
+
75
+ for session_dir in self.temp_sessions_dir.iterdir():
76
+ if not session_dir.is_dir():
77
+ continue
78
+
79
+ # Check directory modification time
80
+ dir_mtime = session_dir.stat().st_mtime
81
+ if current_time - dir_mtime > max_age_seconds:
82
+ try:
83
+ shutil.rmtree(session_dir)
84
+ except Exception:
85
+ pass
@@ -0,0 +1,50 @@
1
+ """
2
+ Utility functions
3
+ """
4
+ import os
5
+ from typing import Tuple
6
+ from http.cookies import SimpleCookie
7
+ import socket
8
+
9
+ def get_ak_info_from_request(headers) -> Tuple[str, str]:
10
+ """Extract AK info from request headers
11
+
12
+ Priority:
13
+ 1. Get from Cookie (production environment)
14
+ 2. Get from environment variables (development debugging)
15
+ 3. Return empty string to allow user custom input (commented out restriction)
16
+ """
17
+ # First try to get from cookie
18
+ cookie_header = headers.get("cookie", "")
19
+ if cookie_header:
20
+ simple_cookie = SimpleCookie()
21
+ simple_cookie.load(cookie_header)
22
+
23
+ access_key = ""
24
+ app_key = ""
25
+
26
+ if "appAccessKey" in simple_cookie:
27
+ access_key = simple_cookie["appAccessKey"].value
28
+ if "clientName" in simple_cookie:
29
+ app_key = simple_cookie["clientName"].value
30
+
31
+ # If got valid values from cookie, return directly
32
+ if access_key or app_key:
33
+ return access_key, app_key
34
+
35
+ # If not in cookie, try to get from environment variables (for development debugging)
36
+ access_key = os.environ.get("BOHR_ACCESS_KEY", "")
37
+ app_key = os.environ.get("BOHR_APP_KEY", "")
38
+
39
+ return access_key, app_key
40
+
41
+
42
+ def check_port_available(port: int) -> bool:
43
+ """Check if port is available"""
44
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
45
+ try:
46
+ sock.bind(('', port))
47
+ sock.close()
48
+ return True
49
+ except OSError:
50
+ return False
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env python3
2
+ """测试文件下载功能"""
3
+
4
+ import os
5
+ import sys
6
+ import time
7
+ import tempfile
8
+ import requests
9
+ from pathlib import Path
10
+
11
+ def test_download_api():
12
+ """测试下载 API"""
13
+
14
+ # API 基础地址
15
+ base_url = "http://localhost:8001"
16
+
17
+ print("=" * 60)
18
+ print("文件下载功能测试")
19
+ print("=" * 60)
20
+
21
+ # 1. 创建测试文件
22
+ test_dir = Path(tempfile.gettempdir()) / "download_test"
23
+ test_dir.mkdir(exist_ok=True)
24
+
25
+ # 创建测试文件
26
+ test_file = test_dir / "test.txt"
27
+ test_file.write_text("This is a test file for download functionality.\n测试文件下载功能。")
28
+
29
+ test_json = test_dir / "data.json"
30
+ test_json.write_text('{"name": "test", "value": 123}')
31
+
32
+ # 创建子目录和文件
33
+ sub_dir = test_dir / "subdir"
34
+ sub_dir.mkdir(exist_ok=True)
35
+ (sub_dir / "file1.txt").write_text("File 1 content")
36
+ (sub_dir / "file2.txt").write_text("File 2 content")
37
+
38
+ print(f"✅ 测试文件已创建在: {test_dir}")
39
+ print()
40
+
41
+ # 2. 测试单文件下载
42
+ print("测试单文件下载...")
43
+ try:
44
+ # 模拟文件下载请求
45
+ file_path = str(test_file)
46
+ download_url = f"{base_url}/api/download/file{file_path}"
47
+
48
+ print(f" 下载 URL: {download_url}")
49
+ print(f" 预期结果: 文件应该可以正常下载")
50
+ print()
51
+
52
+ # 注意:实际测试需要运行服务器并通过浏览器或 curl 测试
53
+ print(" 💡 请在浏览器中打开以下链接测试下载:")
54
+ print(f" {download_url}")
55
+ print()
56
+
57
+ except Exception as e:
58
+ print(f" ❌ 错误: {e}")
59
+ print()
60
+
61
+ # 3. 测试文件夹下载
62
+ print("测试文件夹下载...")
63
+ try:
64
+ # 模拟文件夹下载请求
65
+ folder_path = str(sub_dir)
66
+ download_url = f"{base_url}/api/download/folder{folder_path}"
67
+
68
+ print(f" 下载 URL: {download_url}")
69
+ print(f" 预期结果: 文件夹应打包为 zip 下载")
70
+ print()
71
+
72
+ print(" 💡 请在浏览器中打开以下链接测试下载:")
73
+ print(f" {download_url}")
74
+ print()
75
+
76
+ except Exception as e:
77
+ print(f" ❌ 错误: {e}")
78
+ print()
79
+
80
+ # 4. 测试说明
81
+ print("=" * 60)
82
+ print("测试说明:")
83
+ print("1. 确保 Agent UI 服务正在运行 (端口 8001)")
84
+ print("2. 在浏览器中打开文件浏览器")
85
+ print("3. 测试以下功能:")
86
+ print(" - 点击文件旁的下载图标下载单个文件")
87
+ print(" - 点击文件夹旁的下载图标下载整个文件夹(zip格式)")
88
+ print(" - 在文件预览界面点击下载按钮")
89
+ print("4. 验证下载的文件内容是否正确")
90
+ print("=" * 60)
91
+
92
+ # 清理测试文件(可选)
93
+ # import shutil
94
+ # shutil.rmtree(test_dir)
95
+ # print(f"\n✅ 测试文件已清理")
96
+
97
+ if __name__ == "__main__":
98
+ test_download_api()
@@ -0,0 +1,260 @@
1
+ import os
2
+ import json
3
+ import subprocess
4
+ import signal
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+ from typing import Dict, Any, Optional, List
9
+ import click
10
+
11
+
12
+ class UIConfigManager:
13
+ """管理 UI 配置的工具类"""
14
+
15
+ DEFAULT_CONFIG = {
16
+ "agent": {
17
+ "module": "agent", # 用户必须提供具体的模块路径
18
+ "rootAgent": "root_agent",
19
+ "name": "DP Agent Assistant",
20
+ "description": "AI Assistant powered by DP Agent SDK",
21
+ "welcomeMessage": "欢迎使用 DP Agent Assistant!我可以帮助您进行科学计算、数据分析等任务。",
22
+ },
23
+ "ui": {
24
+ "title": "DP Agent Assistant"
25
+ },
26
+ "server": {
27
+ "port": int(os.environ.get('AGENT_SERVER_PORT', '50002')),
28
+ "host": ["*"] # 默认允许所有主机访问
29
+ }
30
+ }
31
+
32
+ def __init__(self, config_path: Optional[str] = None):
33
+ self.config_path = Path(config_path) if config_path else Path.cwd() / "agent-config.json"
34
+ self.config = self._load_config()
35
+
36
+ def _load_config(self) -> Dict[str, Any]:
37
+ """加载配置文件,如果不存在则使用默认配置"""
38
+ if self.config_path.exists():
39
+ with open(self.config_path, 'r') as f:
40
+ user_config = json.load(f)
41
+ # 深度合并用户配置和默认配置
42
+ return self._deep_merge(self.DEFAULT_CONFIG.copy(), user_config)
43
+ return self.DEFAULT_CONFIG.copy()
44
+
45
+ def _deep_merge(self, base: Dict, update: Dict) -> Dict:
46
+ """深度合并两个字典"""
47
+ for key, value in update.items():
48
+ if key in base and isinstance(base[key], dict) and isinstance(value, dict):
49
+ base[key] = self._deep_merge(base[key], value)
50
+ else:
51
+ base[key] = value
52
+ return base
53
+
54
+ def save_config(self, config_path: Optional[Path] = None):
55
+ """保存配置到文件"""
56
+ save_path = config_path or self.config_path
57
+ with open(save_path, 'w') as f:
58
+ json.dump(self.config, f, indent=2)
59
+
60
+ def update_from_cli(self, **kwargs):
61
+ """从命令行参数更新配置"""
62
+ if kwargs.get('agent'):
63
+ module, _, variable = kwargs['agent'].partition(':')
64
+ self.config['agent']['module'] = module
65
+ if variable:
66
+ self.config['agent']['rootAgent'] = variable
67
+
68
+ if kwargs.get('port'):
69
+ self.config['server']['port'] = kwargs['port']
70
+
71
+
72
+
73
+
74
+ class UIProcessManager:
75
+ """管理 UI 相关进程的工具类"""
76
+
77
+ def __init__(self, ui_dir: Path, config: Dict[str, Any]):
78
+ self.ui_dir = ui_dir
79
+ self.config = config
80
+ self.processes: List[subprocess.Popen] = []
81
+ self._setup_signal_handlers()
82
+
83
+ def _setup_signal_handlers(self):
84
+ """设置信号处理器以优雅关闭进程"""
85
+ signal.signal(signal.SIGINT, self._signal_handler)
86
+ signal.signal(signal.SIGTERM, self._signal_handler)
87
+
88
+ def _signal_handler(self, signum, frame):
89
+ """处理终止信号"""
90
+ # Prevent multiple signal handling
91
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
92
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
93
+ self.cleanup()
94
+ # Don't exit here, let the main process handle it
95
+
96
+ def start_websocket_server(self):
97
+ """启动 WebSocket 服务器"""
98
+ # 统一使用 server.port
99
+ server_port = self.config.get('server', {}).get('port', int(os.environ.get('AGENT_SERVER_PORT', '50002')))
100
+
101
+ websocket_script = self.ui_dir / "websocket-server.py"
102
+ if not websocket_script.exists():
103
+ raise FileNotFoundError(f"找不到 websocket-server.py: {websocket_script}")
104
+
105
+ # 设置环境变量
106
+ env = os.environ.copy()
107
+ env['AGENT_CONFIG_PATH'] = str(self.ui_dir / "config" / "agent-config.temp.json")
108
+ env['USER_WORKING_DIR'] = str(Path.cwd()) # 传递用户工作目录
109
+ env['UI_TEMPLATE_DIR'] = str(self.ui_dir) # 传递UI模板目录
110
+ # Ensure PYTHONPATH includes the user's working directory
111
+ if 'PYTHONPATH' in env:
112
+ env['PYTHONPATH'] = f"{str(Path.cwd())}:{env['PYTHONPATH']}"
113
+ else:
114
+ env['PYTHONPATH'] = str(Path.cwd())
115
+
116
+ # 静默启动,将输出重定向到日志文件
117
+ # 使用 'w' 模式清空旧日志
118
+ log_file = open(Path.cwd() / "websocket.log", "w")
119
+ process = subprocess.Popen(
120
+ [sys.executable, str(websocket_script)],
121
+ cwd=str(Path.cwd()), # 在用户工作目录运行
122
+ env=env,
123
+ stdout=log_file,
124
+ stderr=subprocess.STDOUT
125
+ )
126
+ self.processes.append(process)
127
+
128
+ # 等待服务器启动
129
+ time.sleep(2)
130
+
131
+ if process.poll() is not None:
132
+ raise RuntimeError("WebSocket 服务器启动失败")
133
+
134
+ click.echo(f"🚀 WebSocket 服务器已启动(端口 {server_port})")
135
+ click.echo("📝 查看日志: websocket.log")
136
+
137
+ return process
138
+
139
+ def start_frontend_server(self, dev_mode: bool = True):
140
+ """启动前端服务器"""
141
+ server_port = self.config.get('server', {}).get('port', int(os.environ.get('AGENT_SERVER_PORT', '50002')))
142
+
143
+ ui_path = self.ui_dir / "frontend"
144
+ if not ui_path.exists():
145
+ raise FileNotFoundError(f"找不到 UI 目录: {ui_path}")
146
+
147
+ # 检查是否有构建好的静态文件
148
+ dist_path = ui_path / "ui-static"
149
+ if dist_path.exists() and not dev_mode:
150
+ # 生产模式:静态文件由 WebSocket 服务器提供
151
+ click.echo(f"✨ Agent UI 已启动: http://{os.environ.get('AGENT_HOST', 'localhost')}:{server_port}")
152
+ return None
153
+
154
+ if not dev_mode and not dist_path.exists():
155
+ click.echo("警告: 未找到构建的静态文件,将使用开发模式")
156
+ dev_mode = True
157
+
158
+ # 检查是否已安装依赖
159
+ node_modules = ui_path / "node_modules"
160
+ if not node_modules.exists():
161
+ click.echo("检测到未安装前端依赖,正在安装...")
162
+ subprocess.run(["npm", "install"], cwd=str(ui_path), check=True)
163
+
164
+ # 设置环境变量
165
+ env = os.environ.copy()
166
+ # 开发模式下,前端开发服务器端口
167
+ frontend_dev_port = int(os.environ.get('FRONTEND_DEV_PORT', '3000'))
168
+ env['FRONTEND_PORT'] = str(frontend_dev_port)
169
+ # 告诉前端后端服务器在哪个端口
170
+ env['VITE_WS_PORT'] = str(server_port)
171
+
172
+ # 启动命令
173
+ if dev_mode:
174
+ cmd = ["npm", "run", "dev"]
175
+ click.echo(f"启动前端开发服务器...")
176
+ else:
177
+ cmd = ["npm", "run", "build"]
178
+ click.echo("构建前端生产版本...")
179
+
180
+ # 启动前端
181
+ log_file_path = Path.cwd() / "frontend.log"
182
+ with open(log_file_path, "a") as log_file:
183
+ process = subprocess.Popen(
184
+ cmd,
185
+ cwd=str(ui_path),
186
+ env=env,
187
+ stdout=log_file,
188
+ stderr=subprocess.STDOUT
189
+ )
190
+ self.processes.append(process)
191
+
192
+ # 等待服务器启动
193
+ time.sleep(3)
194
+
195
+ # 检查进程状态
196
+ if process.poll() is not None:
197
+ # 读取错误日志
198
+ with open(log_file_path, "r") as f:
199
+ error_log = f.read()
200
+ if "EADDRINUSE" in error_log:
201
+ raise RuntimeError(f"端口 {frontend_dev_port} 已被占用")
202
+ else:
203
+ raise RuntimeError(f"前端服务器启动失败,请查看 frontend.log 了解详情")
204
+
205
+ if dev_mode:
206
+ click.echo(f"\n✨ 前端开发服务器: http://localhost:{frontend_dev_port}")
207
+ click.echo(f"📡 后端服务器: http://localhost:{server_port}\n")
208
+
209
+ return process
210
+
211
+
212
+ def wait_for_processes(self):
213
+ """等待所有进程结束"""
214
+ try:
215
+ for process in self.processes:
216
+ if process: # 处理可能的 None
217
+ process.wait()
218
+ except KeyboardInterrupt:
219
+ # Don't handle here, let it bubble up to main
220
+ raise
221
+
222
+ def cleanup(self):
223
+ """清理所有进程"""
224
+ if not self.processes:
225
+ return
226
+
227
+ click.echo("\n🛑 正在停止所有进程...")
228
+
229
+ # First attempt to terminate all processes gracefully
230
+ for process in self.processes:
231
+ if process and process.poll() is None:
232
+ try:
233
+ process.terminate()
234
+ except:
235
+ pass
236
+
237
+ # Give processes time to terminate gracefully
238
+ time.sleep(1)
239
+
240
+ # Force kill any remaining processes
241
+ for process in self.processes:
242
+ if process and process.poll() is None:
243
+ try:
244
+ if sys.platform == "win32":
245
+ # Windows specific kill
246
+ subprocess.run(["taskkill", "/F", "/PID", str(process.pid)], capture_output=True)
247
+ else:
248
+ # Unix-like systems
249
+ process.kill()
250
+ process.wait(timeout=1)
251
+ except:
252
+ pass
253
+
254
+ self.processes.clear()
255
+
256
+ # 等待端口释放
257
+ time.sleep(0.5)
258
+ click.echo("✅ 所有进程已停止")
259
+
260
+
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent WebSocket 服务器 - 主入口文件
4
+ 使用 Session 运行 rootagent,并通过 WebSocket 与前端通信
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import warnings
10
+
11
+ # 忽略 paramiko 的加密算法弃用警告
12
+ warnings.filterwarnings("ignore", category=DeprecationWarning, module="paramiko")
13
+
14
+ # Add user working directory to Python path first
15
+ user_working_dir = os.environ.get('USER_WORKING_DIR')
16
+ if user_working_dir and user_working_dir not in sys.path:
17
+ sys.path.insert(0, user_working_dir)
18
+
19
+ # Add UI template directory to Python path for config imports
20
+ ui_template_dir = os.environ.get('UI_TEMPLATE_DIR')
21
+ if ui_template_dir and ui_template_dir not in sys.path:
22
+ sys.path.insert(0, ui_template_dir)
23
+
24
+ import uvicorn
25
+ from server.app import create_app
26
+ from server.utils import check_port_available
27
+ from config.agent_config import agentconfig
28
+
29
+
30
+ if __name__ == "__main__":
31
+ print("🚀 启动 Agent WebSocket 服务器...")
32
+
33
+ # 统一使用 server 配置
34
+ server_config = agentconfig.config.get('server', {})
35
+ port = server_config.get('port', 8000)
36
+ # host 数组中的第一个作为显示用
37
+ hosts = server_config.get('host', ['localhost'])
38
+ display_host = hosts[0] if isinstance(hosts, list) else hosts
39
+
40
+
41
+ # 创建应用
42
+ app = create_app()
43
+
44
+ print("📡 使用 Session 模式运行 rootagent")
45
+ print(f"🌐 服务器地址: http://{display_host}:{port}")
46
+ print(f"🔌 WebSocket 端点: ws://{display_host}:{port}/ws")
47
+ print("🛑 使用 Ctrl+C 优雅关闭服务器")
48
+
49
+ # uvicorn 始终监听 0.0.0.0 以支持所有配置的主机
50
+ uvicorn.run(
51
+ app,
52
+ host="0.0.0.0",
53
+ port=port,
54
+ log_level="info", # 使用 info 级别,过滤掉 warning
55
+ access_log=False, # 禁用访问日志,减少噪音
56
+ # 添加自定义的日志配置
57
+ log_config={
58
+ "version": 1,
59
+ "disable_existing_loggers": False,
60
+ "formatters": {
61
+ "default": {
62
+ "fmt": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
63
+ "datefmt": "%Y-%m-%d %H:%M:%S"
64
+ }
65
+ },
66
+ "handlers": {
67
+ "default": {
68
+ "formatter": "default",
69
+ "class": "logging.StreamHandler",
70
+ "stream": "ext://sys.stdout"
71
+ }
72
+ },
73
+ "root": {
74
+ "level": "INFO",
75
+ "handlers": ["default"]
76
+ },
77
+ "loggers": {
78
+ "uvicorn.error": {
79
+ "level": "ERROR"
80
+ },
81
+ "uvicorn.access": {
82
+ "handlers": [],
83
+ "propagate": False
84
+ }
85
+ }
86
+ }
87
+ )
@@ -51,4 +51,4 @@ class HTTPStorage(BaseStorage):
51
51
 
52
52
 
53
53
  class HTTPSStorage(HTTPStorage):
54
- scheme = "https"
54
+ scheme = "https"