@agentunion/kite 1.3.2 → 1.5.0

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 (293) hide show
  1. package/CHANGELOG.md +302 -0
  2. package/cli.js +119 -4
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/entry.py +111 -1
  7. package/extensions/agents/assistant/server.py +279 -215
  8. package/extensions/channels/acp_channel/entry.py +111 -1
  9. package/extensions/channels/acp_channel/module.md +23 -22
  10. package/extensions/channels/acp_channel/server.py +279 -215
  11. package/extensions/event_hub_bench/entry.py +107 -1
  12. package/extensions/services/backup/entry.py +306 -21
  13. package/extensions/services/backup/module.md +24 -22
  14. package/extensions/services/evol/auth_manager.py +443 -0
  15. package/extensions/services/evol/config.yaml +149 -0
  16. package/extensions/services/evol/config_loader.py +117 -0
  17. package/extensions/services/evol/entry.py +406 -0
  18. package/extensions/services/evol/evol_api.py +173 -0
  19. package/extensions/services/evol/evol_config.json5 +29 -0
  20. package/extensions/services/evol/migrate_tokens.py +122 -0
  21. package/extensions/services/evol/module.md +32 -0
  22. package/extensions/services/evol/pairing.py +250 -0
  23. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  24. package/extensions/services/evol/relay.py +682 -0
  25. package/extensions/services/evol/relay_config.json5 +67 -0
  26. package/extensions/services/evol/routes/__init__.py +1 -0
  27. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  28. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  29. package/extensions/services/evol/routes/routes_test.py +61 -0
  30. package/extensions/services/evol/server.py +875 -0
  31. package/extensions/services/evol/static/css/style.css +1200 -0
  32. package/extensions/services/evol/static/index.html +781 -0
  33. package/extensions/services/evol/static/index_evol.html +14 -0
  34. package/extensions/services/evol/static/js/app.js +6304 -0
  35. package/extensions/services/evol/static/js/auth.js +326 -0
  36. package/extensions/services/evol/static/js/dialog.js +285 -0
  37. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  38. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  39. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  40. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  41. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  42. package/extensions/services/evol/static/js/main.js +141 -0
  43. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  44. package/extensions/services/evol/static/js/stats.js +217 -0
  45. package/extensions/services/evol/static/js/token-manager.js +175 -0
  46. package/extensions/services/evol/static/pairing.html +248 -0
  47. package/extensions/services/evol/static/test_registry.html +262 -0
  48. package/extensions/services/evol/static/test_relay.html +462 -0
  49. package/extensions/services/evol/stats_manager.py +240 -0
  50. package/extensions/services/model_service/entry.py +167 -19
  51. package/extensions/services/model_service/module.md +21 -22
  52. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  53. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  54. package/extensions/services/proxy/_fix_prints.py +133 -0
  55. package/extensions/services/proxy/_fix_prints2.py +87 -0
  56. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  57. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  58. package/extensions/services/proxy/agentcp/README.md +260 -0
  59. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  60. package/extensions/services/proxy/agentcp/agent.py +4 -0
  61. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  62. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  63. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  64. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  65. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  66. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  67. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  68. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  69. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  70. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  71. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  72. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  73. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  74. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  75. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  76. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  77. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  78. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  79. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  80. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  81. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  82. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  83. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  84. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  85. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  86. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  87. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  88. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  89. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  90. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  91. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  92. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  93. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  94. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  95. package/extensions/services/proxy/agentcp/message.py +149 -0
  96. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  97. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  98. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  99. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  100. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  101. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  102. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  103. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  104. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  105. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  106. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  107. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  108. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  109. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  110. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  111. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  112. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  113. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  122. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  123. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  124. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  125. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  126. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  127. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  128. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  129. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  130. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  131. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  132. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  133. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  134. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  135. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  136. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  138. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  139. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  140. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  141. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  142. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  143. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  144. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  145. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  146. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  147. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  148. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  149. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  150. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  151. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  152. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  153. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  154. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  155. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  156. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  157. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  158. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  159. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  160. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  161. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  162. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  163. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  165. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  166. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  168. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  169. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  171. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  172. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  173. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  174. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  175. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  176. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  177. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  178. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  179. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  180. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  181. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  182. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  183. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  184. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  185. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  186. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  187. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  188. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  189. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  190. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  191. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  192. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  193. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  194. package/extensions/services/proxy/console_auth.py +109 -0
  195. package/extensions/services/proxy/evol/__init__.py +1 -0
  196. package/extensions/services/proxy/evol/config.py +37 -0
  197. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  198. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  199. package/extensions/services/proxy/evol/log.py +28 -0
  200. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  201. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  202. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  203. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  204. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  205. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  206. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  207. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  208. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  209. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  210. package/extensions/services/proxy/evol/version.py +24 -0
  211. package/extensions/services/proxy/logs/websocket.log +260 -0
  212. package/extensions/services/proxy/main.py +240 -0
  213. package/extensions/services/proxy/requirements.txt +13 -0
  214. package/extensions/services/proxy/server.py +271 -0
  215. package/extensions/services/watchdog/entry.py +215 -26
  216. package/extensions/services/watchdog/module.md +1 -0
  217. package/extensions/services/watchdog/monitor.py +178 -38
  218. package/extensions/services/web/WEBSOCKET_STATUS.md +143 -0
  219. package/extensions/services/web/config_example.py +35 -0
  220. package/extensions/services/web/config_loader.py +110 -0
  221. package/extensions/services/web/entry.py +114 -26
  222. package/extensions/services/web/module.md +35 -24
  223. package/extensions/services/web/pairing.py +250 -0
  224. package/extensions/services/web/pairing_codes.jsonl +16 -0
  225. package/extensions/services/web/relay.py +643 -0
  226. package/extensions/services/web/relay_config.json5 +67 -0
  227. package/extensions/services/web/routes/routes_management_ws.py +127 -0
  228. package/extensions/services/web/routes/routes_rpc.py +89 -0
  229. package/extensions/services/web/routes/routes_test.py +61 -0
  230. package/extensions/services/web/routes/schemas.py +0 -22
  231. package/extensions/services/web/server.py +434 -99
  232. package/extensions/services/web/static/css/style.css +67 -28
  233. package/extensions/services/web/static/index.html +234 -44
  234. package/extensions/services/web/static/js/app.js +1335 -48
  235. package/extensions/services/web/static/js/kernel-client-example.js +161 -0
  236. package/extensions/services/web/static/js/kernel-client.js +383 -0
  237. package/extensions/services/web/static/js/registry-tests.js +558 -0
  238. package/extensions/services/web/static/js/token-manager.js +175 -0
  239. package/extensions/services/web/static/pairing.html +248 -0
  240. package/extensions/services/web/static/test_registry.html +262 -0
  241. package/extensions/services/web/web_config.json5 +29 -0
  242. package/kernel/entry.py +120 -32
  243. package/kernel/event_hub.py +141 -16
  244. package/kernel/module.md +60 -33
  245. package/kernel/registry_store.py +45 -36
  246. package/kernel/rpc_router.py +152 -59
  247. package/kernel/server.py +322 -26
  248. package/kite_cli/__init__.py +3 -0
  249. package/kite_cli/__main__.py +5 -0
  250. package/kite_cli/commands/__init__.py +1 -0
  251. package/kite_cli/commands/clean.py +101 -0
  252. package/kite_cli/commands/deps_install.py +67 -0
  253. package/kite_cli/commands/doctor.py +35 -0
  254. package/kite_cli/commands/env_check.py +45 -0
  255. package/kite_cli/commands/history.py +111 -0
  256. package/kite_cli/commands/info.py +96 -0
  257. package/kite_cli/commands/install.py +313 -0
  258. package/kite_cli/commands/list.py +143 -0
  259. package/kite_cli/commands/log.py +81 -0
  260. package/kite_cli/commands/prepare.py +49 -0
  261. package/kite_cli/commands/rollback.py +88 -0
  262. package/kite_cli/commands/search.py +73 -0
  263. package/kite_cli/commands/uninstall.py +85 -0
  264. package/kite_cli/commands/update.py +118 -0
  265. package/kite_cli/commands/venv_setup.py +56 -0
  266. package/kite_cli/core/__init__.py +1 -0
  267. package/kite_cli/core/checker.py +142 -0
  268. package/kite_cli/core/dependency.py +229 -0
  269. package/kite_cli/core/downloader.py +209 -0
  270. package/kite_cli/core/install_info.py +40 -0
  271. package/kite_cli/core/tool_installer.py +397 -0
  272. package/kite_cli/core/validator.py +78 -0
  273. package/kite_cli/main.py +317 -0
  274. package/kite_cli/utils/__init__.py +1 -0
  275. package/kite_cli/utils/i18n.py +252 -0
  276. package/kite_cli/utils/interactive.py +63 -0
  277. package/kite_cli/utils/operation_log.py +77 -0
  278. package/kite_cli/utils/paths.py +34 -0
  279. package/kite_cli/utils/version.py +308 -0
  280. package/launcher/entry.py +1124 -178
  281. package/launcher/logging_setup.py +104 -0
  282. package/launcher/module.md +46 -37
  283. package/launcher/module_scanner.py +11 -1
  284. package/main.py +4 -1
  285. package/package.json +9 -1
  286. package/python_version.json +4 -0
  287. package/requirements.txt +38 -0
  288. package/scripts/env-manager.js +328 -0
  289. package/scripts/plan_manager.py +315 -0
  290. package/scripts/python-env.js +79 -0
  291. package/scripts/scan_dependencies.py +461 -0
  292. package/scripts/setup-python-env.js +191 -0
  293. package/extensions/services/web/routes/routes_modules.py +0 -249
@@ -0,0 +1,1062 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2025 AgentUnion Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import asyncio
16
+ import json
17
+ import queue
18
+ import threading
19
+ import time
20
+ import uuid
21
+ from threading import Lock
22
+ from typing import Optional
23
+
24
+ from agentcp.base.log import log_debug, log_error, log_exception, log_info, log_warning
25
+ from agentcp.db.db_mananger import DBManager
26
+ from agentcp.message import AgentInstructionBlock
27
+ from agentcp.msg.message_client import MessageClient
28
+ from agentcp.msg.message_serialize import InviteMessageReq
29
+ from agentcp.msg.stream_client import StreamClient
30
+ from agentcp.msg.wss_binary_message import *
31
+
32
+ from ..context import ErrorContext, exceptions
33
+
34
+
35
+ class Session:
36
+ def __init__(self, agent_id: str, message_client: MessageClient):
37
+ """心跳客户端类
38
+ Args:
39
+ agent_id: 代理ID
40
+ server_url: 服务器URL
41
+ """
42
+ self.agent_id = agent_id
43
+ self.identifying_code = ""
44
+ self.on_message_receive = None
45
+ self.on_invite_ack = None
46
+ self.on_session_message_ack = None
47
+ self.on_system_message = None
48
+ self.on_member_list_receive = None
49
+ self.message_client: MessageClient = message_client
50
+ self.stream_client_map = {}
51
+ # self.StreamClient = None
52
+ self.queue = queue.Queue()
53
+ self.invite_message = None
54
+ self.text_stream_pulling = False
55
+ self.text_stream_pull_url = ""
56
+ self.session_id = None
57
+ self.text_stream_recv_thread: Optional[threading.Thread] = None
58
+ # ✅ 移除锁:create_stream 使用 UUID 保证请求唯一性,无需串行化
59
+
60
+ def can_invite_member(self):
61
+ return not not self.identifying_code
62
+
63
+ def set_session_id(self, session_id: str):
64
+ self.session_id = session_id
65
+
66
+ def close_session(self):
67
+ try:
68
+ if self.identifying_code is not None:
69
+ self.__send_leave_session()
70
+ return
71
+ self.__send_close_session()
72
+ except Exception as e:
73
+ log_exception(f"send close chat session message exception: {e}") # 记录异常
74
+ ErrorContext.publish(exceptions.SDKError(f"close_session: {e}"))
75
+ # try:
76
+ # self.message_client.stop_websocket_client()
77
+ # except Exception as e:
78
+ # log_exception(f'stop websocket client exception: {e}') # 记录异常
79
+ self.message_client = None
80
+
81
+ def __send_leave_session(self):
82
+ try:
83
+ data = {
84
+ "cmd": "leave_session_req",
85
+ "data": {"session_id": f"{self.session_id}", "request_id": f"{int(time.time() * 1000)}"},
86
+ }
87
+ msg = json.dumps(data)
88
+ self.message_client.send_msg(msg)
89
+ log_debug(f"send close chat session message: {msg}") # 调试日志
90
+ except Exception as e:
91
+ log_exception(f"send close chat session message exception: {e}") # 记录异常
92
+
93
+ def __send_close_session(self):
94
+ try:
95
+ data = {
96
+ "cmd": "close_session_req",
97
+ "data": {
98
+ "session_id": f"{self.session_id}",
99
+ "request_id": f"{int(time.time() * 1000)}",
100
+ "identifying_code": self.identifying_code,
101
+ },
102
+ }
103
+ msg = json.dumps(data)
104
+ self.message_client.send_msg(msg)
105
+ log_debug(f"send close chat session message: {msg}") # 调试日志
106
+ except Exception as e:
107
+ log_exception(f"send close chat session message exception: {e}") # 记录异常
108
+
109
+ # accept invite request
110
+ def accept_invite(self, invite_req: InviteMessageReq):
111
+ try:
112
+ data = {
113
+ "cmd": "join_session_req",
114
+ "data": {
115
+ "session_id": invite_req.SessionId,
116
+ "request_id": f"{int(time.time() * 1000)}",
117
+ "inviter_agent_id": invite_req.InviterAgentId,
118
+ "invite_code": invite_req.InviteCode,
119
+ "last_msg_id": "0",
120
+ },
121
+ }
122
+ msg = json.dumps(data)
123
+ self.message_client.send_msg(msg)
124
+ log_debug(f"send join chat session message: {msg}") # 调试日志
125
+ except Exception as e:
126
+ log_exception(f"send join chat session message exception: {e}") # 记录异常
127
+ ErrorContext.publish(exceptions.JoinSessionError(f"accept_invite: {e}"))
128
+
129
+ def reject_invite(self, invite_req: InviteMessageReq):
130
+ pass
131
+
132
+ def leave_session(self, session_id: str):
133
+ pass
134
+
135
+ def invite_member(self, acceptor_aid: str):
136
+ try:
137
+ data = {
138
+ "cmd": "invite_agent_req",
139
+ "data": {
140
+ "session_id": self.session_id,
141
+ "request_id": f"{uuid.uuid4().hex}",
142
+ "inviter_id": self.agent_id,
143
+ "acceptor_id": acceptor_aid,
144
+ "invite_code": self.identifying_code,
145
+ },
146
+ }
147
+ msg = json.dumps(data)
148
+ ret = self.message_client.send_msg(msg)
149
+ log_debug(f"send invite message: {msg} , ret:{ret}") # 调试日志
150
+ return ret
151
+ except Exception as e:
152
+ ErrorContext.publish(exceptions.SDKError(f"invite_member: {e}"))
153
+ log_exception(f"send invite message exception: {e}") # 记录异常
154
+ return False
155
+
156
+ def eject_member(self, eject_aid: str):
157
+ try:
158
+ data = {
159
+ "cmd": "eject_agent_req",
160
+ "data": {
161
+ "session_id": f"{self.session_id}",
162
+ "request_id": f"{int(time.time() * 1000)}",
163
+ "eject_agent_id": self.agent_id,
164
+ "identifying_code": self.identifying_code,
165
+ },
166
+ }
167
+ msg = json.dumps(data)
168
+ self.message_client.send_msg(msg)
169
+ log_debug(f"send eject message: {msg}") # 调试日志
170
+ return True
171
+ except Exception as e:
172
+ ErrorContext.publish(exceptions.SDKError(f"eject_member: {e}"))
173
+ log_exception(f"send eject message exception: {e}")
174
+ return False
175
+
176
+ def get_member_list(self):
177
+ try:
178
+ data = {
179
+ "cmd": "get_member_list",
180
+ "data": {
181
+ "session_id": f"{self.session_id}",
182
+ "request_id": f"{int(time.time() * 1000)}",
183
+ },
184
+ }
185
+ msg = json.dumps(data)
186
+ self.message_client.send_msg(msg)
187
+ log_debug(f"send get member list message: {msg}") # 调试日志
188
+ return True
189
+ except Exception as e:
190
+ log_exception(f"send get member list message exception: {e}")
191
+ return False
192
+
193
+ def send_msg(
194
+ self,
195
+ msg: list,
196
+ receiver: str,
197
+ ref_msg_id: str = "",
198
+ message_id: str = "",
199
+ agent_cmd_block: AgentInstructionBlock = None,
200
+ ):
201
+ if len(msg) == 0:
202
+ log_error("msg is empty")
203
+ return
204
+ import urllib.parse
205
+
206
+ # ✅ 修复: 序列化 AgentInstructionBlock 对象
207
+ instruction_data = None
208
+ if agent_cmd_block is not None:
209
+ from dataclasses import asdict
210
+ instruction_data = asdict(agent_cmd_block)
211
+
212
+ send_msg = urllib.parse.quote(json.dumps(msg))
213
+ data = {
214
+ "cmd": "session_message",
215
+ "data": {
216
+ "message_id": message_id,
217
+ "session_id": self.session_id,
218
+ "ref_msg_id": ref_msg_id,
219
+ "sender": f"{self.agent_id}",
220
+ "instruction": instruction_data, # ✅ 使用序列化后的字典
221
+ "receiver": receiver,
222
+ "message": send_msg,
223
+ "timestamp": f"{int(time.time() * 1000)}",
224
+ },
225
+ }
226
+ msg = json.dumps(data)
227
+ log_debug(f"send message: {msg}")
228
+ return self.message_client.send_msg(msg)
229
+
230
+ def on_open(self):
231
+ """WebSocket连接建立时的处理函数"""
232
+ try:
233
+ #log_info("WebSocket connection opened.")
234
+ # 成员断线加入
235
+ if self.invite_message is not None:
236
+ self.accept_invite(self.invite_message)
237
+ # owner重新加入
238
+ if self.identifying_code:
239
+ self.owner_rejoin()
240
+ except Exception as e:
241
+ import traceback
242
+ log_error(f"WebSocket连接建立时的处理函数: {e}\n{traceback.format_exc()}")
243
+
244
+ def owner_rejoin(self):
245
+ try:
246
+ data = {
247
+ "cmd": "join_session_req",
248
+ "data": {
249
+ "session_id": self.session_id,
250
+ "request_id": f"{int(time.time() * 1000)}",
251
+ "inviter_agent_id": "",
252
+ "invite_code": self.identifying_code,
253
+ "last_msg_id": "0",
254
+ },
255
+ }
256
+ msg = json.dumps(data)
257
+ self.message_client.send_msg(msg)
258
+ log_debug(f"send owner rejoin message: {msg}") # 调试日志
259
+ except Exception as e:
260
+ ErrorContext.publish(exceptions.JoinSessionError(f"加入会话失败: {self.session_id}"))
261
+ log_exception(f"send owner rejoin message exception: {e}")
262
+
263
+ async def create_stream(self, to_aid_list: [], content_type: str = "text/event-stream", ref_msg_id: str = ""):
264
+ """创建流式通道 - 带连接恢复自动重试
265
+
266
+ 当检测到连接断开时,会等待连接恢复后自动重试,对调用方透明。
267
+
268
+ 重试策略:
269
+ - 最大重试次数: 2次(总共尝试3次)
270
+ - 等待连接恢复超时: 10秒
271
+ - 单次请求超时: 10秒
272
+ - 最坏情况总超时: 约60秒
273
+ """
274
+ max_retries = 2 # 最多重试2次
275
+ retry_wait_timeout = 10.0 # 等待连接恢复的超时时间
276
+
277
+ for retry_count in range(max_retries + 1):
278
+ try:
279
+ result = await self._create_stream_once(to_aid_list, content_type, ref_msg_id)
280
+ push_url, error_or_pull = result
281
+
282
+ # 成功
283
+ if push_url is not None:
284
+ return result
285
+
286
+ # 检查是否是连接断开导致的失败
287
+ if not self._is_connection_lost_error(error_or_pull):
288
+ # 非连接问题(如服务器拒绝、参数错误等),直接返回失败
289
+ return result
290
+
291
+ # 连接断开,尝试等待恢复后重试
292
+ if retry_count < max_retries:
293
+ log_warning(f"🔄 连接断开,等待恢复后重试 ({retry_count + 1}/{max_retries})...")
294
+
295
+ # 等待连接恢复
296
+ reconnected = await self._wait_for_reconnection(retry_wait_timeout)
297
+ if reconnected:
298
+ log_info(f"✅ 连接已恢复,重新发送 create_stream 请求...")
299
+ continue # 重试
300
+ else:
301
+ log_error(f"❌ 等待连接恢复超时 ({retry_wait_timeout}s)")
302
+ # 继续尝试,可能在重试过程中恢复
303
+ continue
304
+ else:
305
+ # 达到最大重试次数
306
+ return result
307
+
308
+ except Exception as e:
309
+ import traceback
310
+ log_error(f"❌ create_stream 重试循环异常: {e}\n{traceback.format_exc()}")
311
+ if retry_count >= max_retries:
312
+ return None, f"创建流异常: {str(e)}"
313
+
314
+ return None, "重试次数已用完"
315
+
316
+ def _is_connection_lost_error(self, error_msg: str) -> bool:
317
+ """判断是否是连接断开导致的错误"""
318
+ if error_msg is None:
319
+ return False
320
+ error_lower = str(error_msg).lower()
321
+ connection_keywords = [
322
+ "connection_lost",
323
+ "连接断开",
324
+ "websocket 连接不可用",
325
+ "连接不可用",
326
+ "发送创建流请求失败",
327
+ "发送请求失败"
328
+ ]
329
+ return any(keyword in error_lower for keyword in connection_keywords)
330
+
331
+ async def _wait_for_reconnection(self, timeout: float) -> bool:
332
+ """等待 WebSocket 连接恢复
333
+
334
+ Args:
335
+ timeout: 最大等待时间(秒)
336
+
337
+ Returns:
338
+ True: 连接已恢复并验证通过
339
+ False: 等待超时或连接不可用
340
+ """
341
+ if self.message_client is None:
342
+ return False
343
+
344
+ start_time = time.time()
345
+ check_interval = 0.3 # 每 0.3 秒检查一次(更频繁)
346
+
347
+ log_info(f"⏳ 等待连接恢复,超时时间: {timeout}s...")
348
+
349
+ while time.time() - start_time < timeout:
350
+ # 检查连接是否已恢复(多重条件)
351
+ ws_open = self.message_client._is_ws_open()
352
+ event_set = self.message_client.connected_event.is_set()
353
+
354
+ # 需要两个条件都满足才认为连接真正恢复
355
+ if ws_open and event_set:
356
+ # 额外等待 0.2 秒让连接稳定
357
+ await asyncio.sleep(0.2)
358
+ # 再次验证
359
+ if self.message_client._is_ws_open():
360
+ elapsed = time.time() - start_time
361
+ log_info(f"✅ 连接已恢复,耗时: {elapsed:.1f}s")
362
+ return True
363
+
364
+ await asyncio.sleep(check_interval)
365
+
366
+ # 超时,最后检查一次
367
+ elapsed = time.time() - start_time
368
+ ws_open = self.message_client._is_ws_open()
369
+ log_warning(f"⏱️ 等待连接恢复超时: {elapsed:.1f}s, ws_open={ws_open}")
370
+ return ws_open
371
+
372
+ async def _create_stream_once(self, to_aid_list: [], content_type: str, ref_msg_id: str):
373
+ """单次创建流(不含重试逻辑)
374
+
375
+ Returns:
376
+ (push_url, pull_url): 成功时返回两个 URL
377
+ (None, error_msg): 失败时返回 None 和错误信息
378
+ """
379
+ try:
380
+ start_time = time.time()
381
+ receiver = ",".join(to_aid_list)
382
+ request_id = f"{uuid.uuid4().hex}"
383
+
384
+ # 检查 message_client
385
+ if self.message_client is None:
386
+ error_msg = "message_client 未初始化"
387
+ log_error(f"❌ 创建流失败: {error_msg}")
388
+ ErrorContext.publish(exceptions.CreateStreamError(error_msg))
389
+ return None, error_msg
390
+
391
+ # ✅ 增强:检查连接状态,同时检查 connected_event
392
+ ws_open = self.message_client._is_ws_open()
393
+ event_set = self.message_client.connected_event.is_set()
394
+
395
+ if not ws_open or not event_set:
396
+ error_msg = f"WebSocket 连接不可用 (ws_open={ws_open}, event_set={event_set})"
397
+ log_warning(f"⚠️ 创建流: {error_msg}")
398
+ return None, error_msg
399
+
400
+ # 构建请求消息
401
+ data = {
402
+ "cmd": "session_create_stream_req",
403
+ "data": {
404
+ "session_id": self.session_id,
405
+ "request_id": f"{request_id}",
406
+ "ref_msg_id": ref_msg_id,
407
+ "sender": f"{self.agent_id}",
408
+ "receiver": receiver,
409
+ "content_type": content_type,
410
+ "timestamp": f"{int(time.time() * 1000)}",
411
+ },
412
+ }
413
+ msg = json.dumps(data)
414
+
415
+ # 注册响应队列(使用线程安全方法)
416
+ temp_queue = asyncio.Queue()
417
+ try:
418
+ loop = asyncio.get_running_loop() # Python 3.10+ 推荐用法
419
+ except RuntimeError:
420
+ loop = asyncio.get_event_loop() # 兼容旧版本
421
+ self.message_client.register_stream_request(request_id, {
422
+ "queue": temp_queue,
423
+ "loop": loop,
424
+ "timestamp": start_time,
425
+ "receiver": receiver
426
+ })
427
+
428
+ # 发送请求
429
+ send_success = self.message_client.send_msg(msg)
430
+ if not send_success:
431
+ self.message_client.unregister_stream_request(request_id)
432
+ error_msg = "发送创建流请求失败"
433
+ log_warning(f"⚠️ {error_msg}")
434
+ return None, error_msg
435
+
436
+ log_info(f"📤 发送创建流请求: request_id={request_id[:8]}... receiver={receiver}")
437
+
438
+ # 等待服务器响应(单次超时10秒)
439
+ try:
440
+ ack = await asyncio.wait_for(temp_queue.get(), timeout=10.0)
441
+ elapsed = time.time() - start_time
442
+ log_info(f"✅ 收到流创建响应: request_id={request_id[:8]}... 耗时={elapsed:.2f}s")
443
+ except asyncio.TimeoutError:
444
+ elapsed = time.time() - start_time
445
+ pending_count = self.message_client.get_pending_stream_count()
446
+ log_error(f"⏱️ 创建流超时: request_id={request_id[:8]}... receiver={receiver} 耗时={elapsed:.2f}s")
447
+ log_error(f"📊 当前等待响应的请求数: {pending_count}")
448
+ ErrorContext.publish(exceptions.CreateStreamError(f"创建流超时(10秒): receiver={receiver}"))
449
+ return None, f"创建流超时: 10秒内未收到服务器响应"
450
+ finally:
451
+ self.message_client.unregister_stream_request(request_id)
452
+
453
+ # 检查错误标记(连接断开通知或清理线程放入的)
454
+ if "error" in ack:
455
+ error_type = ack.get("error", "unknown")
456
+ error_msg = ack.get("message", "流创建失败")
457
+ log_warning(f"⚠️ 收到错误标记 ({error_type}): {error_msg}")
458
+ # 不发布 ErrorContext,让外层决定是否重试
459
+ return None, error_msg
460
+
461
+ # 验证响应完整性
462
+ if "session_id" in ack and "push_url" in ack and "pull_url" in ack and "message_id" in ack:
463
+ push_url = ack["push_url"]
464
+ pull_url = ack["pull_url"]
465
+
466
+ # 创建流客户端连接
467
+ try:
468
+ success = await self.__create_stream_client(self.session_id, push_url)
469
+ if not success:
470
+ await asyncio.sleep(1)
471
+ success = await self.__create_stream_client(self.session_id, push_url)
472
+ if not success:
473
+ ErrorContext.publish(exceptions.CreateStreamError(f"创建流失败: {push_url}"))
474
+ log_error(f"❌ 创建流客户端失败: {push_url}")
475
+ return None, f"创建流客户端连接失败"
476
+ except Exception as e:
477
+ log_error(f"❌ 创建流客户端异常: {str(e)}")
478
+ ErrorContext.publish(exceptions.CreateStreamError(f"创建流失败: {push_url}"))
479
+ return None, f"创建流客户端异常: {str(e)}"
480
+
481
+ return push_url, pull_url
482
+ else:
483
+ log_error(f"❌ 服务器响应不完整: {ack}")
484
+ ErrorContext.publish(exceptions.CreateStreamError("未获取到流连接"))
485
+ return None, "服务器响应不完整"
486
+
487
+ except Exception as e:
488
+ import traceback
489
+ error_msg = traceback.format_exc()
490
+ log_error(f"❌ 单次创建流异常: {error_msg}")
491
+ ErrorContext.publish(exceptions.CreateStreamError(f"创建流异常: {str(e)}"))
492
+ return None, f"创建流异常: {str(e)}"
493
+
494
+ async def __create_stream_client(self, session_id, push_url):
495
+ stream_client = StreamClient(self.agent_id, session_id, push_url, self.message_client.auth_client.signature)
496
+ ws_url = push_url
497
+ ws_url = ws_url + f"&agent_id={self.agent_id}&signature={self.message_client.auth_client.signature}"
498
+ log_info(f"ws_ts_url = {ws_url}")
499
+ stream_client.ws_url = ws_url
500
+ stream_client.ws_is_running = True
501
+ success = await stream_client.start_websocket_client()
502
+ if not success:
503
+ log_error(f"创建流失败, 启动websocket失败: {stream_client.ws_url}")
504
+ ErrorContext.publish(exceptions.CreateStreamError(f"创建流失败: {stream_client.ws_url}"))
505
+ return None
506
+ self.stream_client_map[push_url] = stream_client
507
+ return stream_client
508
+
509
+ def send_chunk_to_stream(self, stream_url: str, chunk,type="text/event-stream"):
510
+ stream_client: StreamClient = self.stream_client_map.get(stream_url)
511
+ if not stream_client:
512
+ error_msg = f"send_chunk_to_stream, stream_client is none for url: {stream_url}"
513
+ ErrorContext.publish(
514
+ exceptions.SendChunkToStreamError(error_msg)
515
+ )
516
+ return False, error_msg
517
+ return stream_client.send_chunk_to_stream(chunk)
518
+
519
+ def send_file_chunk_to_stream(self, stream_url: str, offset: int, chunk: bytes):
520
+ stream_client: StreamClient = self.stream_client_map.get(stream_url)
521
+ if not stream_client:
522
+ error_msg = f"send_file_chunk_to_stream, stream_client is none for url: {stream_url}"
523
+ ErrorContext.publish(
524
+ exceptions.SendChunkToStreamError(error_msg)
525
+ )
526
+ return False, error_msg
527
+ return stream_client.send_chunk_to_file_stream(offset,chunk)
528
+
529
+ def close_stream(self, stream_url: str):
530
+ stream_client: StreamClient = self.stream_client_map.get(stream_url)
531
+ if stream_client is not None:
532
+ stream_client.close_stream(stream_url)
533
+ stream_client = None
534
+ self.stream_client_map.pop(stream_url)
535
+ log_info(f"关闭流: {stream_url}")
536
+
537
+
538
+ class SessionManager:
539
+ def __init__(self, agent_id: str, server_url: str, aid_path: str, seed_password: str, db_mananger: DBManager, agent_id_ref=None):
540
+ # ✅ 优化: 使用细粒度锁,避免全局阻塞
541
+ self.sessions_lock = threading.RLock() # 保护 sessions 字典的读写
542
+ self.sessions = {}
543
+ self.agent_id = agent_id
544
+ self.server_url = server_url
545
+ self.aid_path = aid_path
546
+ self.seed_password = seed_password
547
+ self._agent_id_ref = agent_id_ref
548
+ # 连接多个消息服务器
549
+ self.message_client_map = {}
550
+ # 多条流式消息
551
+ self.message_server_map = {}
552
+ self.db_mananger = db_mananger
553
+ self.queue = queue.Queue()
554
+ self.create_session_queue_map = {}
555
+ self.create_session_event = threading.Event()
556
+ self._create_session_lock = Lock()
557
+
558
+ def _get_session_safely(self, session_id: str) -> Optional[Session]:
559
+ """✅ 线程安全地获取session(不持锁返回)
560
+
561
+ Args:
562
+ session_id: 会话ID
563
+
564
+ Returns:
565
+ Session对象或None
566
+ """
567
+ with self.sessions_lock:
568
+ return self.sessions.get(session_id)
569
+
570
+ def _add_session_safely(self, session_id: str, session: Session) -> None:
571
+ """✅ 线程安全地添加session"""
572
+ with self.sessions_lock:
573
+ self.sessions[session_id] = session
574
+
575
+ def _remove_session_safely(self, session_id: str) -> Optional[Session]:
576
+ """✅ 线程安全地移除session"""
577
+ with self.sessions_lock:
578
+ return self.sessions.pop(session_id, None)
579
+
580
+ def create_session_id(
581
+ self, name: str, message_client: MessageClient, subject: str, *, session_type: str = "public"
582
+ ) -> str:
583
+ with self._create_session_lock:
584
+ log_info(f"sign in success: {self.agent_id}")
585
+ message_client.set_message_handler(self)
586
+ if not message_client.start_websocket_client():
587
+ log_error("Failed to start WebSocket client.")
588
+ ErrorContext.publish(exceptions.CreateSessionError("message_client start_websocket_client is none"))
589
+ return None, None
590
+
591
+ request_id, temp_queue = self.__create(message_client, name, subject, session_type)
592
+ if not request_id or temp_queue is None:
593
+ ErrorContext.publish(exceptions.CreateSessionError("create_session_req send failed"))
594
+ return None, None
595
+ try:
596
+ session_result = temp_queue.get(timeout=10)
597
+ temp_queue.task_done()
598
+ temp_queue = None
599
+ except Exception as e:
600
+ self.create_session_queue_map.pop(request_id, None)
601
+ import traceback
602
+ ErrorContext.publish(exceptions.CreateSessionError(f"创建会话等待结果超时: {traceback.format_exc()}"))
603
+ log_error("队列获取超时,当前队列内容:{list(self.queue.queue)}")
604
+ return None, None
605
+ return session_result["session_id"], session_result["identifying_code"]
606
+
607
+ def on_open(self, ws):
608
+ """✅ 优化: WebSocket连接建立时的处理函数,修复遍历sessions的竞态条件"""
609
+ #log_info("WebSocket connection opened.")
610
+ try:
611
+ # ✅ 修复: 在锁内快速复制sessions列表,避免遍历时被修改
612
+ with self.sessions_lock:
613
+ sessions_to_reopen = list(self.sessions.values())
614
+
615
+ # ✅ 释放锁后再调用每个session的on_open(避免持锁时间过长)
616
+ for session in sessions_to_reopen:
617
+ try:
618
+ session.on_open()
619
+ except Exception as e:
620
+ log_error(f"session.on_open() failed: {e}")
621
+ except Exception as e:
622
+ import traceback
623
+ log_error(f"WebSocket连接建立时的处理函数: {e}\n{traceback.format_exc()}")
624
+
625
+ def get_content_array_from_message(self, message):
626
+ # 消息数组
627
+ message_content = message.get("message", "")
628
+ message_array = []
629
+ if isinstance(message_content, str):
630
+ try:
631
+ if message_content.strip(): # 检查内容是否非空
632
+ llm_content_json_array = json.loads(message_content)
633
+ if isinstance(llm_content_json_array, list) and len(llm_content_json_array) > 0:
634
+ return llm_content_json_array # 返回整个数组而不是第一个元素的 conten
635
+ else:
636
+ message_array.append(llm_content_json_array)
637
+ return message_array
638
+ else:
639
+ log_info("收到空消息内容")
640
+ return []
641
+ except json.JSONDecodeError:
642
+ log_error(f"无法解析的消息内容: {message_content}")
643
+ return []
644
+ elif isinstance(message_content, list) and len(message_content) > 0:
645
+ return message_content
646
+ else:
647
+ log_error("无效的消息格式")
648
+ return []
649
+
650
+ def on_message(self, ws, message:str):
651
+ """✅ P0-1修复: 移除线程创建,改为直接同步调用
652
+
653
+ 接收到服务器消息时的处理函数
654
+
655
+ 修改要点:
656
+ 1. 移除所有 threading.Thread 创建
657
+ 2. 改为直接同步调用回调函数
658
+ 3. 回调函数内部会将任务提交到 Scheduler,因此这里同步调用是安全的
659
+ 4. 异常处理确保单个消息失败不影响后续消息接收
660
+ """
661
+ try:
662
+ #log_info(f"received a message session mananger: {len(message)}")
663
+
664
+ js = json.loads(message)
665
+ if "cmd" not in js or "data" not in js:
666
+ log_error("收到的消息中不包括cmd字段,不符合预期格式")
667
+ return
668
+
669
+ cmd = js["cmd"]
670
+ message_data = js["data"]
671
+ #log_info(f"received a message session mananger: {cmd}")
672
+
673
+ # ✅ P0-1修复: 所有消息处理改为直接同步调用
674
+ if cmd == "create_session_ack":
675
+ # 创建session的ack(同步处理)
676
+ self.__on_create_session_ack(js["data"])
677
+
678
+ elif cmd == "session_message":
679
+ # ✅ 修复: 移除线程创建,直接同步调用
680
+ import urllib.parse
681
+ message_content = js["data"]["message"]
682
+ js["data"]["message"] = urllib.parse.unquote(message_content)
683
+
684
+ if self.on_message_receive is not None:
685
+ try:
686
+ # ✅ 直接同步调用(内部会提交到 Scheduler)
687
+ self.on_message_receive(js["data"])
688
+ except Exception as e:
689
+ log_error(f"消息处理回调异常: {e}")
690
+ import traceback
691
+ log_error(traceback.format_exc())
692
+ else:
693
+ log_error("on_message_receive is None")
694
+
695
+ elif cmd == "invite_agent_ack":
696
+ log_info(f"收到邀请消息: {js}")
697
+ if self.on_invite_ack is not None:
698
+ try:
699
+ # ✅ 修复: 移除线程创建,直接同步调用
700
+ self.on_invite_ack(js["data"])
701
+ except Exception as e:
702
+ log_error(f"邀请回调异常: {e}")
703
+ else:
704
+ log_error("on_invite_ack is None")
705
+
706
+ elif cmd == "session_message_ack":
707
+ session_id = message_data.get("session_id", "")
708
+ session = self._get_session_safely(session_id)
709
+ if session is not None and self.on_session_message_ack is not None:
710
+ try:
711
+ # ✅ 修复: 移除线程创建,直接同步调用
712
+ self.on_session_message_ack(js["data"])
713
+ except Exception as e:
714
+ log_error(f"消息确认回调异常: {e}")
715
+
716
+ elif cmd == "session_create_stream_ack":
717
+ session_id = message_data.get("session_id", "")
718
+ session = self._get_session_safely(session_id)
719
+ if session is not None and session.message_client is not None:
720
+ request_id = js["data"]["request_id"]
721
+ # ✅ 使用线程安全方法获取队列条目
722
+ queue_entry = session.message_client.get_stream_request(request_id)
723
+ if queue_entry:
724
+ # ✅ 从字典中获取队列对象和事件循环
725
+ temp_queue = queue_entry["queue"]
726
+ loop = queue_entry["loop"]
727
+
728
+ # ✅ 使用 call_soon_threadsafe 确保线程安全
729
+ # 从 WebSocket 线程安全地向 asyncio.Queue 放入数据
730
+ loop.call_soon_threadsafe(temp_queue.put_nowait, js["data"])
731
+
732
+ elif cmd == "system_message":
733
+ session_id = message_data.get("session_id", "")
734
+ session = self._get_session_safely(session_id)
735
+ if session is not None and self.on_system_message is not None:
736
+ try:
737
+ # ✅ 修复: 移除线程创建,直接同步调用
738
+ self.on_system_message(js["data"])
739
+ except Exception as e:
740
+ log_error(f"系统消息回调异常: {e}")
741
+
742
+ except Exception as e:
743
+ import traceback
744
+ log_error(f"处理消息时发生异常: {e}\n{traceback.format_exc()}")
745
+
746
+ def __create(self, message_client: MessageClient, session_name: str, subject: str, session_type: str = "public"):
747
+ log_info(f"create_session: {session_name}, {subject}, {session_type}")
748
+ try:
749
+ log_debug("check WebSocket connection status") # 调试日志
750
+ request_id = f"{uuid.uuid4().hex}"
751
+ data = {
752
+ "cmd": "create_session_req",
753
+ "data": {
754
+ "request_id": f"{request_id}",
755
+ "type": f"{session_type}",
756
+ "group_name": f"{session_name}",
757
+ "subject": f"{subject}",
758
+ "timestamp": f"{int(time.time() * 1000)}",
759
+ },
760
+ }
761
+ temp_queue = queue.Queue()
762
+ self.create_session_queue_map[request_id] = temp_queue
763
+ msg = json.dumps(data)
764
+ message_client.send_msg(msg)
765
+ log_debug(f"send message: {msg}") # 调试日志
766
+ return request_id, temp_queue
767
+ except Exception as e:
768
+ import traceback
769
+ ErrorContext.publish(exceptions.CreateSessionError(f"创建会话等待结果超时: {traceback.format_exc()}"))
770
+ log_exception(f"send create chat session message exception: {e}") # 记录异常
771
+ return None, None
772
+
773
+ def get(self, session_id: str):
774
+ """✅ 优化: 使用细粒度锁"""
775
+ return self._get_session_safely(session_id)
776
+
777
+ def check_stream_url_exists(self, stream_url: str):
778
+ """✅ 优化: 简化锁使用"""
779
+ with self.sessions_lock:
780
+ return stream_url in self.message_server_map
781
+ return False
782
+
783
+ def create_session(self, name: str, subject: str, session_type: str = "public"):
784
+ """✅ 优化: 只在必要时持锁,修复竞态条件"""
785
+ # ✅ 第一次加锁:获取或创建 message_client
786
+ with self.sessions_lock:
787
+ cache_auth_client = self.message_server_map.get(self.server_url)
788
+
789
+ if self.server_url in self.message_client_map:
790
+ log_info("复用message_client")
791
+ message_client = self.message_client_map[self.server_url]
792
+ else:
793
+ message_client = MessageClient(
794
+ self.agent_id, self.server_url, self.aid_path, self.seed_password, cache_auth_client, agent_id_ref=self._agent_id_ref
795
+ )
796
+ message_client.initialize()
797
+ self.message_client_map[self.server_url] = message_client
798
+
799
+ # ✅ 释放锁后再执行耗时操作
800
+ session = Session(self.agent_id, message_client)
801
+ session_id, identifying_code = self.create_session_id(
802
+ name, message_client, subject, session_type=session_type
803
+ )
804
+
805
+ if session_id is None or identifying_code is None:
806
+ log_error(f"Failed to create Session {name}.")
807
+ return None
808
+
809
+ session.session_id = session_id
810
+ session.identifying_code = identifying_code
811
+
812
+ if not session_id:
813
+ log_error(f"Failed to create Session {name}.")
814
+ return None
815
+
816
+ # ✅ 第二次加锁:添加session,并检查是否已存在(避免重复创建)
817
+ with self.sessions_lock:
818
+ if session_id in self.sessions:
819
+ # ✅ 修复: 如果已存在,返回已有的session
820
+ #log_info(f"session {session_id} already exists, returning existing session.")
821
+ return self.sessions[session_id]
822
+
823
+ self.sessions[session_id] = session
824
+ self.message_server_map[self.server_url] = message_client.auth_client
825
+
826
+ log_info(f"session {name} created: {session_id}.")
827
+ return session
828
+
829
+ def __on_create_session_ack(self, js):
830
+ if "session_id" in js and "status_code" in js and "message" in js and "identifying_code" in js:
831
+ # session_id = js["session_id"]
832
+ # self.identifying_code = js["identifying_code"]
833
+ temp_queue = self.create_session_queue_map.get(js["request_id"])
834
+ if temp_queue:
835
+ temp_queue.put(js)
836
+ self.create_session_queue_map.pop(js["request_id"],None)
837
+ if js["status_code"] == 200 or js["status_code"] == "200":
838
+ log_info(f"create_session_ack: {js}")
839
+ else:
840
+ log_error(f"create_session_ack failed: {js}")
841
+ else:
842
+ log_error("收到的消息中不包括session_id字段,不符合预期格式")
843
+
844
+ def close_all_session(self):
845
+ """✅ 优化: 先获取所有session,释放锁后再关闭
846
+
847
+ 修复:同时关闭所有 MessageClient 的 WebSocket 连接,
848
+ 避免旧连接变成"孤儿"继续运行。
849
+ """
850
+ with self.sessions_lock:
851
+ sessions_to_close = list(self.sessions.items())
852
+ self.sessions.clear()
853
+ # ✅ 获取所有 MessageClient(在锁内复制引用)
854
+ message_clients_to_close = list(self.message_client_map.values())
855
+ self.message_client_map.clear()
856
+ self.message_server_map.clear()
857
+
858
+ # ✅ 释放锁后再执行耗时的关闭操作
859
+ for session_id, session in sessions_to_close:
860
+ try:
861
+ session.close_session()
862
+ except Exception as e:
863
+ log_error(f"close session {session_id} exception: {e}")
864
+
865
+ # ✅ 关闭所有 MessageClient 的 WebSocket 连接
866
+ for mc in message_clients_to_close:
867
+ try:
868
+ if mc:
869
+ log_info(f"[SessionManager] 关闭 MessageClient: {mc.server_url}")
870
+ mc.stop_websocket_client()
871
+ except Exception as e:
872
+ log_error(f"[SessionManager] 关闭 MessageClient 异常: {e}")
873
+
874
+ def close_session(self, session_id: str):
875
+ """✅ 优化: 快速获取session后释放锁再关闭"""
876
+ session = self._remove_session_safely(session_id)
877
+ if session is None:
878
+ log_error(f"Session {session_id} does not exist.")
879
+ return False
880
+
881
+ # ✅ 释放锁后再执行耗时的关闭操作
882
+ try:
883
+ session.close_session()
884
+ except Exception as e:
885
+ log_error(f"close session {session_id} exception: {e}")
886
+ return True
887
+
888
+ def join_session(self, req: InviteMessageReq):
889
+ """✅ 优化: 只在必要时持锁,修复竞态条件"""
890
+ # ✅ 第一次加锁:获取或创建 message_client
891
+ with self.sessions_lock:
892
+ # ✅ 双重检查:可能已经加入过了
893
+ if req.SessionId in self.sessions:
894
+ #log_info(f"session {req.SessionId} already exists, returning existing session.")
895
+ return self.sessions[req.SessionId]
896
+
897
+ cache_auth_client = self.message_server_map.get(req.MessageServer)
898
+
899
+ if req.MessageServer in self.message_client_map:
900
+ message_client = self.message_client_map[req.MessageServer]
901
+ else:
902
+ message_client = MessageClient(
903
+ self.agent_id, req.MessageServer, self.aid_path, self.seed_password, cache_auth_client, agent_id_ref=self._agent_id_ref
904
+ )
905
+ message_client.initialize()
906
+ message_client.set_message_handler(self)
907
+ self.message_client_map[req.MessageServer] = message_client
908
+
909
+ # ✅ 释放锁后创建session
910
+ session: Session = Session(self.agent_id, message_client)
911
+ session.session_id = req.SessionId
912
+ session.accept_invite(req)
913
+ session.invite_message = req
914
+
915
+ # ✅ 第二次加锁:添加时再次检查,防止重复
916
+ with self.sessions_lock:
917
+ if req.SessionId in self.sessions:
918
+ log_info(f"session {req.SessionId} was created by another thread, returning existing.")
919
+ return self.sessions[req.SessionId]
920
+
921
+ self.sessions[req.SessionId] = session
922
+ self.message_server_map[req.MessageServer] = message_client.auth_client
923
+
924
+ return session
925
+
926
+ def leave_session(self, session_id: str):
927
+ self.close_session(session_id)
928
+ return
929
+
930
+ def invite_member(self, session_id: str, acceptor_aid: str):
931
+ """✅ 优化: 快速获取session后释放锁"""
932
+ session = self._get_session_safely(session_id)
933
+ if session is None:
934
+ log_error(f"Session {session_id} does not exist.")
935
+ return False
936
+
937
+ # ✅ 释放锁后再执行操作
938
+ return session.invite_member(acceptor_aid)
939
+
940
+ async def create_stream(
941
+ self, session_id: str, to_aid_list: [], content_type: str = "text/event-stream", ref_msg_id: str = ""
942
+ ):
943
+ """✅ 优化: 不持锁等待异步响应 - 关键修复!
944
+
945
+ 这是阻塞问题的根源:之前在持锁状态下等待服务器响应(最多15秒)
946
+ 现在改为快速获取session后立即释放锁,再进行异步等待
947
+ """
948
+ session = self._get_session_safely(session_id)
949
+ if session is None:
950
+ log_error(f"Session {session_id} does not exist.")
951
+ return None, f"Session {session_id} does not exist."
952
+
953
+ # ✅ 关键: 不持有任何锁的情况下等待异步响应
954
+ return await session.create_stream(to_aid_list, content_type, ref_msg_id)
955
+
956
+ def close_stream(self, session_id: str, stream_url: str):
957
+ """✅ 优化: 快速获取session后释放锁"""
958
+ session = self._get_session_safely(session_id)
959
+ if session is None:
960
+ log_error(f"Session {session_id} does not exist.")
961
+ return False
962
+
963
+ # ✅ 释放锁后再执行操作
964
+ session.close_stream(stream_url)
965
+ return True
966
+
967
+ def send_chunk_to_stream(self, session_id: str, stream_url: str, chunk,type="text/event-stream"):
968
+ """✅ 优化: 快速获取session后释放锁"""
969
+ session = self._get_session_safely(session_id)
970
+ if session is None:
971
+ log_error(f"session {session_id} does not exist.")
972
+ return False
973
+
974
+ # ✅ 释放锁后再执行操作
975
+ return session.send_chunk_to_stream(stream_url, chunk, type = type)
976
+
977
+ def send_chunk_to_file_stream(self,session_id: str, stream_url: str, offset: int, chunk: bytes):
978
+ """✅ 优化: 快速获取session后释放锁"""
979
+ session = self._get_session_safely(session_id)
980
+ if session is None:
981
+ log_error(f"session {session_id} does not exist.")
982
+ return False
983
+
984
+ # ✅ 释放锁后再执行操作
985
+ return session.send_file_chunk_to_stream(stream_url, offset, chunk)
986
+
987
+ def send_msg(
988
+ self,
989
+ session_id: str,
990
+ msg: list,
991
+ receiver: str,
992
+ ref_msg_id: str = "",
993
+ message_id: str = "",
994
+ agent_cmd_block: AgentInstructionBlock = None,
995
+ ):
996
+ """✅ 优化: 快速获取或创建session后释放锁,修复竞态条件"""
997
+ session = self._get_session_safely(session_id)
998
+
999
+ # ✅ 如果session不存在,需要创建
1000
+ if session is None:
1001
+ log_error(f"session {session_id} does not exist.")
1002
+
1003
+ # 第一次加锁:获取或创建 message_client 和 session
1004
+ with self.sessions_lock:
1005
+ # ✅ 双重检查:可能其他线程已经创建了
1006
+ if session_id in self.sessions:
1007
+ session = self.sessions[session_id]
1008
+ else:
1009
+ # 确实不存在,获取 message_client
1010
+ if self.server_url in self.message_client_map:
1011
+ log_info("复用message_client")
1012
+ message_client = self.message_client_map[self.server_url]
1013
+ else:
1014
+ cache_auth_client = self.message_server_map.get(self.server_url)
1015
+ message_client = MessageClient(
1016
+ self.agent_id, self.server_url, self.aid_path, self.seed_password, cache_auth_client, agent_id_ref=self._agent_id_ref
1017
+ )
1018
+ message_client.initialize()
1019
+ self.message_client_map[self.server_url] = message_client
1020
+
1021
+ # ✅ 在锁内创建并添加session(避免释放锁后的竞态)
1022
+ session = Session(self.agent_id, message_client)
1023
+ message_client.set_message_handler(self)
1024
+ session.session_id = session_id
1025
+
1026
+ # 尝试加载历史(如果失败也继续)
1027
+ try:
1028
+ result = self.db_mananger.load_session_history(session_id)
1029
+ if result:
1030
+ session.identifying_code = result[0]["identifying_code"]
1031
+ except Exception as e:
1032
+ log_error(f"load session history failed: {e}")
1033
+
1034
+ # ✅ 在锁内添加,确保原子性
1035
+ self.sessions[session_id] = session
1036
+
1037
+ # ✅ 释放锁后再发送消息
1038
+ session.send_msg(msg, receiver, ref_msg_id, message_id, agent_cmd_block)
1039
+ return True
1040
+
1041
+ def init_his_session(self, session_id: str, session: Session):
1042
+ session.session_id = session_id
1043
+ result = self.db_mananger.load_session_history(session_id)
1044
+ if not result:
1045
+ log_error(f"load session history failed: {session_id}")
1046
+ return False
1047
+ session.identifying_code = result[0]["identifying_code"]
1048
+
1049
+ def set_on_message_receive(self, on_message_recive):
1050
+ self.on_message_receive = on_message_recive
1051
+
1052
+ def set_on_invite_ack(self, on_invite_ack):
1053
+ self.on_invite_ack = on_invite_ack
1054
+
1055
+ def set_on_session_message_ack(self, on_session_message_ack):
1056
+ self.on_session_message_ack = on_session_message_ack
1057
+
1058
+ def set_on_system_message(self, on_system_message):
1059
+ self.on_system_message = on_system_message
1060
+
1061
+ def set_on_member_list_receive(self, on_member_list_receive):
1062
+ self.on_member_list_receive = on_member_list_receive