bohr-agent-sdk 0.1.101__py3-none-any.whl → 0.1.102__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.
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/METADATA +6 -2
- bohr_agent_sdk-0.1.102.dist-info/RECORD +80 -0
- dp/agent/cli/cli.py +126 -25
- dp/agent/cli/templates/__init__.py +1 -0
- dp/agent/cli/templates/calculation/simple.py.template +15 -0
- dp/agent/cli/templates/device/tescan_device.py.template +158 -0
- dp/agent/cli/templates/main.py.template +67 -0
- dp/agent/cli/templates/ui/__init__.py +1 -0
- dp/agent/cli/templates/ui/api/__init__.py +1 -0
- dp/agent/cli/templates/ui/api/config.py +32 -0
- dp/agent/cli/templates/ui/api/constants.py +61 -0
- dp/agent/cli/templates/ui/api/debug.py +257 -0
- dp/agent/cli/templates/ui/api/files.py +469 -0
- dp/agent/cli/templates/ui/api/files_upload.py +115 -0
- dp/agent/cli/templates/ui/api/files_user.py +50 -0
- dp/agent/cli/templates/ui/api/messages.py +161 -0
- dp/agent/cli/templates/ui/api/projects.py +146 -0
- dp/agent/cli/templates/ui/api/sessions.py +93 -0
- dp/agent/cli/templates/ui/api/utils.py +161 -0
- dp/agent/cli/templates/ui/api/websocket.py +184 -0
- dp/agent/cli/templates/ui/config/__init__.py +1 -0
- dp/agent/cli/templates/ui/config/agent_config.py +257 -0
- dp/agent/cli/templates/ui/frontend/index.html +13 -0
- dp/agent/cli/templates/ui/frontend/package.json +46 -0
- dp/agent/cli/templates/ui/frontend/tsconfig.json +26 -0
- dp/agent/cli/templates/ui/frontend/tsconfig.node.json +10 -0
- dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DdAmKhul.js +105 -0
- dp/agent/cli/templates/ui/frontend/ui-static/assets/index-DfN2raU9.css +1 -0
- dp/agent/cli/templates/ui/frontend/ui-static/index.html +14 -0
- dp/agent/cli/templates/ui/frontend/vite.config.ts +37 -0
- dp/agent/cli/templates/ui/scripts/build_ui.py +56 -0
- dp/agent/cli/templates/ui/server/__init__.py +0 -0
- dp/agent/cli/templates/ui/server/app.py +98 -0
- dp/agent/cli/templates/ui/server/connection.py +210 -0
- dp/agent/cli/templates/ui/server/file_watcher.py +85 -0
- dp/agent/cli/templates/ui/server/middleware.py +43 -0
- dp/agent/cli/templates/ui/server/models.py +53 -0
- dp/agent/cli/templates/ui/server/session_manager.py +1158 -0
- dp/agent/cli/templates/ui/server/user_files.py +85 -0
- dp/agent/cli/templates/ui/server/utils.py +50 -0
- dp/agent/cli/templates/ui/test_download.py +98 -0
- dp/agent/cli/templates/ui/ui_utils.py +260 -0
- dp/agent/cli/templates/ui/websocket-server.py +87 -0
- dp/agent/server/storage/http_storage.py +1 -1
- bohr_agent_sdk-0.1.101.dist-info/RECORD +0 -40
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/WHEEL +0 -0
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/entry_points.txt +0 -0
- {bohr_agent_sdk-0.1.101.dist-info → bohr_agent_sdk-0.1.102.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# WebSocket endpoint handler
|
|
2
|
+
import os
|
|
3
|
+
from fastapi import WebSocket
|
|
4
|
+
from fastapi.websockets import WebSocketDisconnect
|
|
5
|
+
|
|
6
|
+
from server.utils import get_ak_info_from_request
|
|
7
|
+
from server.session_manager import SessionManager
|
|
8
|
+
from api.projects import verify_user_project
|
|
9
|
+
from bohrium_open_sdk import OpenSDK
|
|
10
|
+
|
|
11
|
+
# Global session manager
|
|
12
|
+
manager = SessionManager()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
16
|
+
# Extract access_key and app_key from headers
|
|
17
|
+
access_key, app_key = get_ak_info_from_request(websocket.headers)
|
|
18
|
+
|
|
19
|
+
await manager.connect_client(websocket, access_key, app_key)
|
|
20
|
+
|
|
21
|
+
# Get connection context
|
|
22
|
+
context = manager.active_connections.get(websocket)
|
|
23
|
+
if not context:
|
|
24
|
+
await websocket.close()
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
# Try to get project_id from environment variable (for development)
|
|
28
|
+
env_project_id = os.environ.get('BOHR_PROJECT_ID')
|
|
29
|
+
if env_project_id and not context.project_id:
|
|
30
|
+
try:
|
|
31
|
+
project_id_int = int(env_project_id)
|
|
32
|
+
|
|
33
|
+
# Verify project_id (but allow usage in development mode)
|
|
34
|
+
is_valid = await verify_user_project(access_key, project_id_int)
|
|
35
|
+
|
|
36
|
+
context.project_id = project_id_int
|
|
37
|
+
# Notify frontend that project_id has been set
|
|
38
|
+
await websocket.send_json({
|
|
39
|
+
"type": "project_id_set",
|
|
40
|
+
"project_id": context.project_id,
|
|
41
|
+
"content": f"Project ID 已从环境变量设置为: {context.project_id} (开发模式)"
|
|
42
|
+
})
|
|
43
|
+
except ValueError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
while True:
|
|
48
|
+
data = await websocket.receive_json()
|
|
49
|
+
message_type = data.get("type")
|
|
50
|
+
|
|
51
|
+
if message_type == "message":
|
|
52
|
+
content = data.get("content", "").strip()
|
|
53
|
+
attachments = data.get("attachments", [])
|
|
54
|
+
if content or attachments:
|
|
55
|
+
await manager.process_message(context, content, attachments)
|
|
56
|
+
|
|
57
|
+
elif message_type == "create_session":
|
|
58
|
+
# Create new session
|
|
59
|
+
session = await manager.create_session(context)
|
|
60
|
+
await manager.switch_session(context, session.id)
|
|
61
|
+
await manager.send_sessions_list(context)
|
|
62
|
+
await manager.send_session_messages(context, session.id)
|
|
63
|
+
|
|
64
|
+
elif message_type == "switch_session":
|
|
65
|
+
# Switch session
|
|
66
|
+
session_id = data.get("session_id")
|
|
67
|
+
if session_id and await manager.switch_session(context, session_id):
|
|
68
|
+
# Send updated session list (with new current_session_id)
|
|
69
|
+
await manager.send_sessions_list(context)
|
|
70
|
+
# Send message history for new session
|
|
71
|
+
await manager.send_session_messages(context, session_id)
|
|
72
|
+
else:
|
|
73
|
+
await websocket.send_json({
|
|
74
|
+
"type": "error",
|
|
75
|
+
"content": "会话不存在"
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
elif message_type == "get_sessions":
|
|
79
|
+
# Get session list
|
|
80
|
+
await manager.send_sessions_list(context)
|
|
81
|
+
|
|
82
|
+
elif message_type == "delete_session":
|
|
83
|
+
# Delete session
|
|
84
|
+
session_id = data.get("session_id")
|
|
85
|
+
if session_id:
|
|
86
|
+
# Track if deleting current session
|
|
87
|
+
is_current_session = (session_id == context.current_session_id)
|
|
88
|
+
|
|
89
|
+
success = await manager.delete_session(context, session_id)
|
|
90
|
+
if success:
|
|
91
|
+
# If deleted current session, switch to another session
|
|
92
|
+
if is_current_session:
|
|
93
|
+
# Get remaining sessions list
|
|
94
|
+
user_identifier = context.get_user_identifier()
|
|
95
|
+
session_service = manager.session_services.get(user_identifier)
|
|
96
|
+
|
|
97
|
+
if session_service:
|
|
98
|
+
response = await session_service.list_sessions(
|
|
99
|
+
app_name=manager.app_name,
|
|
100
|
+
user_id=user_identifier
|
|
101
|
+
)
|
|
102
|
+
remaining_sessions = response.sessions if hasattr(response, 'sessions') else []
|
|
103
|
+
|
|
104
|
+
if remaining_sessions:
|
|
105
|
+
# Switch to first (most recent) session
|
|
106
|
+
# Sort by last update time
|
|
107
|
+
remaining_sessions.sort(
|
|
108
|
+
key=lambda s: manager._get_session_last_update_time(s),
|
|
109
|
+
reverse=True
|
|
110
|
+
)
|
|
111
|
+
new_session_id = remaining_sessions[0].id
|
|
112
|
+
await manager.switch_session(context, new_session_id)
|
|
113
|
+
# Send message history for new session
|
|
114
|
+
await manager.send_session_messages(context, new_session_id)
|
|
115
|
+
else:
|
|
116
|
+
# If no sessions left, create new one
|
|
117
|
+
session = await manager.create_session(context)
|
|
118
|
+
await manager.switch_session(context, session.id)
|
|
119
|
+
# New session has no message history, send empty list
|
|
120
|
+
await websocket.send_json({
|
|
121
|
+
"type": "session_messages",
|
|
122
|
+
"session_id": session.id,
|
|
123
|
+
"messages": []
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Send updated session list (with current session ID)
|
|
127
|
+
await manager.send_sessions_list(context)
|
|
128
|
+
else:
|
|
129
|
+
await websocket.send_json({
|
|
130
|
+
"type": "error",
|
|
131
|
+
"content": "删除会话失败"
|
|
132
|
+
})
|
|
133
|
+
else:
|
|
134
|
+
await websocket.send_json({
|
|
135
|
+
"type": "error",
|
|
136
|
+
"content": "删除会话失败"
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
elif message_type == "set_project_id":
|
|
140
|
+
# Set project_id
|
|
141
|
+
project_id = data.get("project_id")
|
|
142
|
+
if project_id is not None:
|
|
143
|
+
try:
|
|
144
|
+
# Ensure project_id is integer
|
|
145
|
+
project_id_int = int(project_id)
|
|
146
|
+
|
|
147
|
+
# Commented out project_id validation, allow users to set any project_id
|
|
148
|
+
# is_valid = await verify_user_project(access_key, project_id_int)
|
|
149
|
+
#
|
|
150
|
+
# if not is_valid:
|
|
151
|
+
# await websocket.send_json({
|
|
152
|
+
# "type": "error",
|
|
153
|
+
# "content": f"您没有权限使用项目 ID: {project_id_int}。请从项目列表中选择您有权限的项目。"
|
|
154
|
+
# })
|
|
155
|
+
# return
|
|
156
|
+
|
|
157
|
+
# Validation passed, set project_id
|
|
158
|
+
context.project_id = project_id_int
|
|
159
|
+
|
|
160
|
+
# Only reinitialize runner for current session
|
|
161
|
+
if context.current_session_id:
|
|
162
|
+
# Reinitialize runner through SessionManager
|
|
163
|
+
user_identifier = context.get_user_identifier()
|
|
164
|
+
runner_key = f"{user_identifier}_{context.current_session_id}"
|
|
165
|
+
if runner_key in manager.runners:
|
|
166
|
+
del manager.runners[runner_key]
|
|
167
|
+
# Reinitialize
|
|
168
|
+
await manager._init_runner(context, context.current_session_id)
|
|
169
|
+
|
|
170
|
+
await websocket.send_json({
|
|
171
|
+
"type": "project_id_set",
|
|
172
|
+
"project_id": context.project_id,
|
|
173
|
+
"content": f"Project ID 已设置为: {context.project_id}"
|
|
174
|
+
})
|
|
175
|
+
except ValueError:
|
|
176
|
+
await websocket.send_json({
|
|
177
|
+
"type": "error",
|
|
178
|
+
"content": f"无效的 Project ID: {project_id},必须是整数"
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
except WebSocketDisconnect:
|
|
182
|
+
await manager.disconnect_client(websocket)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
await manager.disconnect_client(websocket)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# 配置模块
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent Configuration Loader
|
|
3
|
+
|
|
4
|
+
This module provides a centralized configuration system for different agent implementations.
|
|
5
|
+
To switch between different agents, modify the agent-config.json file.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, Any
|
|
14
|
+
import importlib
|
|
15
|
+
import importlib.util
|
|
16
|
+
|
|
17
|
+
# 配置日志输出到文件
|
|
18
|
+
# 使用相对于项目根目录的路径或环境变量配置
|
|
19
|
+
log_file_path = os.environ.get('WEBSOCKET_LOG_PATH', './websocket.log')
|
|
20
|
+
# 只在没有配置过的情况下配置
|
|
21
|
+
if not logging.getLogger().handlers:
|
|
22
|
+
logging.basicConfig(
|
|
23
|
+
level=logging.DEBUG,
|
|
24
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
25
|
+
handlers=[
|
|
26
|
+
logging.FileHandler(log_file_path, encoding='utf-8', mode='a'),
|
|
27
|
+
logging.StreamHandler() # 同时输出到控制台
|
|
28
|
+
]
|
|
29
|
+
)
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
class AgentConfig:
|
|
33
|
+
def __init__(self, config_path: str = None):
|
|
34
|
+
# 优先使用环境变量中的配置路径
|
|
35
|
+
if config_path is None:
|
|
36
|
+
env_path = os.environ.get('AGENT_CONFIG_PATH', 'config/agent-config.json')
|
|
37
|
+
self.config_path = Path(env_path)
|
|
38
|
+
else:
|
|
39
|
+
self.config_path = Path(config_path)
|
|
40
|
+
|
|
41
|
+
self.config = self._load_config()
|
|
42
|
+
|
|
43
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
44
|
+
"""Load configuration from JSON file"""
|
|
45
|
+
logger.info(f"📦 加载配置文件: {self.config_path}")
|
|
46
|
+
|
|
47
|
+
if not self.config_path.exists():
|
|
48
|
+
logger.warning(f"⚠️ 配置文件不存在: {self.config_path}")
|
|
49
|
+
logger.info("🔄 使用默认配置")
|
|
50
|
+
# Fallback to default config if file doesn't exist
|
|
51
|
+
return self._get_default_config()
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
with open(self.config_path, 'r', encoding='utf-8') as f:
|
|
55
|
+
config = json.load(f)
|
|
56
|
+
logger.info("✅ 配置文件加载成功")
|
|
57
|
+
logger.debug(f" Agent名称: {config.get('agent', {}).get('name')}")
|
|
58
|
+
logger.debug(f" Agent模块: {config.get('agent', {}).get('module')}")
|
|
59
|
+
logger.debug(f" Root Agent: {config.get('agent', {}).get('rootAgent')}")
|
|
60
|
+
return config
|
|
61
|
+
except json.JSONDecodeError as e:
|
|
62
|
+
logger.error(f"❌ JSON解析错误: {e}")
|
|
63
|
+
logger.info("🔄 使用默认配置")
|
|
64
|
+
return self._get_default_config()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
logger.error(f"❌ 读取配置文件失败: {e}")
|
|
67
|
+
logger.info("🔄 使用默认配置")
|
|
68
|
+
return self._get_default_config()
|
|
69
|
+
|
|
70
|
+
def _get_default_config(self) -> Dict[str, Any]:
|
|
71
|
+
"""Provide default configuration for Agent"""
|
|
72
|
+
# 使用环境变量提供的端口,如果没有则使用默认值
|
|
73
|
+
default_port = int(os.environ.get('AGENT_SERVER_PORT', '50002'))
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
"agent": {
|
|
77
|
+
"name": "My Agent",
|
|
78
|
+
"description": "Agent",
|
|
79
|
+
"welcomeMessage": "welcome to chat with me",
|
|
80
|
+
"module": "agent.subagent",
|
|
81
|
+
"rootAgent": "rootagent"
|
|
82
|
+
},
|
|
83
|
+
"ui": {
|
|
84
|
+
"title": "Agent",
|
|
85
|
+
"features": {
|
|
86
|
+
"showFileExplorer": True,
|
|
87
|
+
"showSessionList": True
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"files": {
|
|
91
|
+
"watchDirectories": ["./output"]
|
|
92
|
+
},
|
|
93
|
+
"server": {
|
|
94
|
+
"host": ["localhost", "127.0.0.1"],
|
|
95
|
+
"port": default_port
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
def get_agent(self, ak: str = None, app_key: str = None, project_id: int = None):
|
|
100
|
+
"""Dynamically import and return the configured agent
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
ak: Optional access key to pass to the agent
|
|
104
|
+
app_key: Optional app key to pass to the agent
|
|
105
|
+
project_id: Optional project ID to pass to the agent
|
|
106
|
+
"""
|
|
107
|
+
agentconfig = self.config.get("agent", {})
|
|
108
|
+
module_path = agentconfig.get("module", "agent.subagent")
|
|
109
|
+
agentname = agentconfig.get("rootAgent", "rootagent")
|
|
110
|
+
|
|
111
|
+
logger.info(f"🤖 加载 Agent")
|
|
112
|
+
logger.debug(f" 模块路径: {module_path}")
|
|
113
|
+
logger.debug(f" Agent名称: {agentname}")
|
|
114
|
+
logger.debug(f" AK: {'有' if ak else '无'}")
|
|
115
|
+
logger.debug(f" App Key: {'有' if app_key else '无'}")
|
|
116
|
+
logger.debug(f" Project ID: {project_id}")
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
# 检查是否是文件路径(包含 / 或 \ 或以 .py 结尾)
|
|
120
|
+
if '/' in module_path or '\\' in module_path or module_path.endswith('.py'):
|
|
121
|
+
# 作为文件路径处理
|
|
122
|
+
file_path = Path(module_path)
|
|
123
|
+
|
|
124
|
+
# 如果是相对路径,基于用户工作目录解析
|
|
125
|
+
if not file_path.is_absolute():
|
|
126
|
+
user_working_dir = os.environ.get('USER_WORKING_DIR', os.getcwd())
|
|
127
|
+
file_path = Path(user_working_dir) / file_path
|
|
128
|
+
|
|
129
|
+
logger.debug(f" 解析文件路径: {file_path}")
|
|
130
|
+
|
|
131
|
+
# 确保文件存在
|
|
132
|
+
if not file_path.exists():
|
|
133
|
+
logger.error(f"❌ Agent模块文件不存在: {file_path}")
|
|
134
|
+
raise ImportError(f"Agent module file not found: {file_path}")
|
|
135
|
+
|
|
136
|
+
logger.info(f"📄 从文件加载: {file_path}")
|
|
137
|
+
|
|
138
|
+
# 从文件路径创建唯一的模块名,包含路径信息避免冲突
|
|
139
|
+
# 例如: /path/to/agent.py -> path_to_agent
|
|
140
|
+
module_name = str(file_path).replace('/', '_').replace('\\', '_').replace('.py', '').replace('.', '_')
|
|
141
|
+
# 确保模块名是有效的 Python 标识符
|
|
142
|
+
module_name = 'agent_' + module_name.strip('_')
|
|
143
|
+
|
|
144
|
+
# 使用 importlib.util 从文件加载模块
|
|
145
|
+
spec = importlib.util.spec_from_file_location(module_name, str(file_path))
|
|
146
|
+
if spec is None or spec.loader is None:
|
|
147
|
+
raise ImportError(f"Cannot load module from file: {file_path}")
|
|
148
|
+
|
|
149
|
+
module = importlib.util.module_from_spec(spec)
|
|
150
|
+
sys.modules[module_name] = module # 可选:将模块添加到 sys.modules
|
|
151
|
+
spec.loader.exec_module(module)
|
|
152
|
+
logger.info(f"✅ 模块加载成功: {module_name}")
|
|
153
|
+
else:
|
|
154
|
+
# 作为模块路径处理(原有逻辑)
|
|
155
|
+
logger.debug(f" 作为 Python模块导入: {module_path}")
|
|
156
|
+
module = importlib.import_module(module_path)
|
|
157
|
+
logger.info(f"✅ 模块导入成功: {module_path}")
|
|
158
|
+
|
|
159
|
+
# 检查是否有 create_agent 函数(推荐的方式)
|
|
160
|
+
if hasattr(module, 'create_agent'):
|
|
161
|
+
logger.info("🔧 使用 create_agent 函数创建 Agent")
|
|
162
|
+
# 使用工厂函数创建新的 agent 实例
|
|
163
|
+
# 检查函数接受哪些参数
|
|
164
|
+
import inspect
|
|
165
|
+
sig = inspect.signature(module.create_agent)
|
|
166
|
+
params = {}
|
|
167
|
+
|
|
168
|
+
# 只传递函数签名中存在的参数
|
|
169
|
+
if 'ak' in sig.parameters:
|
|
170
|
+
params['ak'] = ak
|
|
171
|
+
if 'app_key' in sig.parameters:
|
|
172
|
+
params['app_key'] = app_key
|
|
173
|
+
if 'project_id' in sig.parameters:
|
|
174
|
+
params['project_id'] = project_id
|
|
175
|
+
|
|
176
|
+
agent = module.create_agent(**params)
|
|
177
|
+
logger.info(f"✅ Agent 创建成功: {type(agent).__name__}")
|
|
178
|
+
return agent
|
|
179
|
+
else:
|
|
180
|
+
# 后向兼容:直接返回模块级别的 agent
|
|
181
|
+
logger.info(f"🔍 查找模块属性: {agentname}")
|
|
182
|
+
if hasattr(module, agentname):
|
|
183
|
+
agent = getattr(module, agentname)
|
|
184
|
+
logger.info(f"✅ 找到 Agent: {type(agent).__name__}")
|
|
185
|
+
return agent
|
|
186
|
+
else:
|
|
187
|
+
logger.error(f"❌ 模块 {module_path} 中没有找到 {agentname}")
|
|
188
|
+
logger.debug(f" 可用属性: {dir(module)}")
|
|
189
|
+
raise AttributeError(f"模块 {module_path} 中没有 {agentname}")
|
|
190
|
+
except ImportError as e:
|
|
191
|
+
logger.error(f"❌ 导入错误: {e}")
|
|
192
|
+
raise ImportError(f"Failed to load agent {agentname} from {module_path}: {e}")
|
|
193
|
+
except AttributeError as e:
|
|
194
|
+
logger.error(f"❌ 属性错误: {e}")
|
|
195
|
+
raise AttributeError(f"Failed to load agent {agentname} from {module_path}: {e}")
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error(f"❌ 未知错误: {e}")
|
|
198
|
+
raise Exception(f"Failed to load agent {agentname} from {module_path}: {e}")
|
|
199
|
+
|
|
200
|
+
def get_ui_config(self) -> Dict[str, Any]:
|
|
201
|
+
"""Get UI-specific configuration"""
|
|
202
|
+
return self.config.get("ui", {})
|
|
203
|
+
|
|
204
|
+
def get_files_config(self) -> Dict[str, Any]:
|
|
205
|
+
"""Get file handling configuration"""
|
|
206
|
+
return self.config.get("files", {})
|
|
207
|
+
|
|
208
|
+
def get_websocket_config(self) -> Dict[str, Any]:
|
|
209
|
+
"""Get WebSocket configuration"""
|
|
210
|
+
return self.config.get("websocket", {})
|
|
211
|
+
|
|
212
|
+
def get_tool_display_name(self, tool_name: str) -> str:
|
|
213
|
+
"""Get display name for a tool"""
|
|
214
|
+
tools_config = self.config.get("tools", {})
|
|
215
|
+
display_names = tools_config.get("displayNames", {})
|
|
216
|
+
return display_names.get(tool_name, tool_name)
|
|
217
|
+
|
|
218
|
+
def is_long_running_tool(self, tool_name: str) -> bool:
|
|
219
|
+
"""Check if a tool is marked as long-running"""
|
|
220
|
+
tools_config = self.config.get("tools", {})
|
|
221
|
+
long_running = tools_config.get("longRunningTools", [])
|
|
222
|
+
return tool_name in long_running
|
|
223
|
+
|
|
224
|
+
def get_server_config(self) -> Dict[str, Any]:
|
|
225
|
+
"""Get server configuration including port and allowed hosts"""
|
|
226
|
+
# 默认主机始终被允许
|
|
227
|
+
default_hosts = ["localhost", "127.0.0.1", "0.0.0.0"]
|
|
228
|
+
|
|
229
|
+
server_config = self.config.get("server", {})
|
|
230
|
+
|
|
231
|
+
# 支持 "host" 字段(用户配置)和 "allowedHosts"(向后兼容)
|
|
232
|
+
user_hosts = server_config.get("host", server_config.get("allowedHosts", []))
|
|
233
|
+
|
|
234
|
+
# 确保 user_hosts 是列表
|
|
235
|
+
if isinstance(user_hosts, str):
|
|
236
|
+
user_hosts = [user_hosts]
|
|
237
|
+
elif not isinstance(user_hosts, list):
|
|
238
|
+
user_hosts = []
|
|
239
|
+
|
|
240
|
+
# 如果用户配置了 "*",则允许所有主机
|
|
241
|
+
if "*" in user_hosts:
|
|
242
|
+
all_hosts = ["*"]
|
|
243
|
+
else:
|
|
244
|
+
# 合并默认主机和用户定义的额外主机
|
|
245
|
+
all_hosts = list(set(default_hosts + user_hosts)) # 使用 set 去重
|
|
246
|
+
|
|
247
|
+
# 使用环境变量或配置的端口,最后使用默认端口
|
|
248
|
+
default_port = int(os.environ.get('AGENT_SERVER_PORT', '50002'))
|
|
249
|
+
port = server_config.get("port", default_port)
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
"port": port,
|
|
253
|
+
"allowedHosts": all_hosts
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
# Singleton instance
|
|
257
|
+
agentconfig = AgentConfig()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/nexus-icon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Agent UI</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bohr-agent-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Beautiful UI for Bohr Agent SDK",
|
|
5
|
+
"private": true,
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@tailwindcss/typography": "^0.5.10",
|
|
8
|
+
"axios": "^1.6.2",
|
|
9
|
+
"classnames": "^2.3.2",
|
|
10
|
+
"clsx": "^2.1.1",
|
|
11
|
+
"date-fns": "^2.30.0",
|
|
12
|
+
"framer-motion": "^10.16.0",
|
|
13
|
+
"html-react-parser": "^4.2.10",
|
|
14
|
+
"lucide-react": "^0.292.0",
|
|
15
|
+
"react": "^18.2.0",
|
|
16
|
+
"react-dom": "^18.2.0",
|
|
17
|
+
"react-markdown": "^9.0.1",
|
|
18
|
+
"react-router-dom": "^6.20.0",
|
|
19
|
+
"react-syntax-highlighter": "^15.5.0",
|
|
20
|
+
"remark-gfm": "^4.0.0",
|
|
21
|
+
"socket.io-client": "^4.5.4",
|
|
22
|
+
"zustand": "^5.0.6"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/dompurify": "^3.0.5",
|
|
26
|
+
"@types/node": "^24.1.0",
|
|
27
|
+
"@types/react": "^18.2.0",
|
|
28
|
+
"@types/react-dom": "^18.2.0",
|
|
29
|
+
"@types/react-syntax-highlighter": "^15.5.11",
|
|
30
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
31
|
+
"autoprefixer": "^10.4.16",
|
|
32
|
+
"less": "^4.4.0",
|
|
33
|
+
"postcss": "^8.4.32",
|
|
34
|
+
"tailwindcss": "^3.3.6",
|
|
35
|
+
"typescript": "^5.3.0",
|
|
36
|
+
"vite": "^5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"dev": "vite",
|
|
40
|
+
"build": "tsc && vite build",
|
|
41
|
+
"preview": "vite preview",
|
|
42
|
+
"type-check": "tsc --noEmit"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"moduleResolution": "bundler",
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"noEmit": true,
|
|
12
|
+
"jsx": "react-jsx",
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": true,
|
|
16
|
+
"noFallthroughCasesInSwitch": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"baseUrl": ".",
|
|
20
|
+
"paths": {
|
|
21
|
+
"@/*": ["src/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["src"],
|
|
25
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
26
|
+
}
|