@agentunion/kite 1.4.0 → 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.
- package/CHANGELOG.md +102 -0
- package/cli.js +44 -5
- package/core/dependency_checker.py +250 -0
- package/core/env_checker.py +490 -0
- package/dependencies_lock.json +128 -0
- package/extensions/agents/assistant/server.py +33 -17
- package/extensions/channels/acp_channel/server.py +33 -17
- package/extensions/services/backup/entry.py +23 -16
- package/extensions/services/evol/auth_manager.py +443 -0
- package/extensions/services/evol/config.yaml +149 -0
- package/extensions/services/evol/config_loader.py +117 -0
- package/extensions/services/evol/entry.py +406 -0
- package/extensions/services/evol/evol_api.py +173 -0
- package/extensions/services/evol/evol_config.json5 +29 -0
- package/extensions/services/evol/migrate_tokens.py +122 -0
- package/extensions/services/evol/module.md +32 -0
- package/extensions/services/evol/pairing.py +250 -0
- package/extensions/services/evol/pairing_codes.jsonl +1 -0
- package/extensions/services/evol/relay.py +682 -0
- package/extensions/services/evol/relay_config.json5 +67 -0
- package/extensions/services/evol/routes/__init__.py +1 -0
- package/extensions/services/evol/routes/routes_management_ws.py +127 -0
- package/extensions/services/evol/routes/routes_rpc.py +89 -0
- package/extensions/services/evol/routes/routes_test.py +61 -0
- package/extensions/services/evol/server.py +875 -0
- package/extensions/services/evol/static/css/style.css +1200 -0
- package/extensions/services/evol/static/index.html +781 -0
- package/extensions/services/evol/static/index_evol.html +14 -0
- package/extensions/services/evol/static/js/app.js +6304 -0
- package/extensions/services/evol/static/js/auth.js +326 -0
- package/extensions/services/evol/static/js/dialog.js +285 -0
- package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
- package/extensions/services/evol/static/js/evol-app.js +1949 -0
- package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
- package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
- package/extensions/services/evol/static/js/kernel-client.js +396 -0
- package/extensions/services/evol/static/js/main.js +141 -0
- package/extensions/services/evol/static/js/registry-tests.js +585 -0
- package/extensions/services/evol/static/js/stats.js +217 -0
- package/extensions/services/evol/static/js/token-manager.js +175 -0
- package/extensions/services/evol/static/pairing.html +248 -0
- package/extensions/services/evol/static/test_registry.html +262 -0
- package/extensions/services/evol/static/test_relay.html +462 -0
- package/extensions/services/evol/stats_manager.py +240 -0
- package/extensions/services/model_service/entry.py +23 -1
- package/extensions/services/proxy/.claude/settings.local.json +13 -0
- package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
- package/extensions/services/proxy/_fix_prints.py +133 -0
- package/extensions/services/proxy/_fix_prints2.py +87 -0
- package/extensions/services/proxy/agentcp/LICENCE +178 -0
- package/extensions/services/proxy/agentcp/README copy.md +85 -0
- package/extensions/services/proxy/agentcp/README.md +260 -0
- package/extensions/services/proxy/agentcp/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/agent.py +4 -0
- package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
- package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
- package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
- package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
- package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
- package/extensions/services/proxy/agentcp/base/client.py +112 -0
- package/extensions/services/proxy/agentcp/base/env.py +34 -0
- package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
- package/extensions/services/proxy/agentcp/base/log.py +98 -0
- package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
- package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
- package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
- package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/context/context.py +73 -0
- package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
- package/extensions/services/proxy/agentcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
- package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
- package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
- package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
- package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
- package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/hcp.py +299 -0
- package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
- package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
- package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
- package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
- package/extensions/services/proxy/agentcp/llm_server.py +172 -0
- package/extensions/services/proxy/agentcp/mermaid.py +210 -0
- package/extensions/services/proxy/agentcp/message.py +149 -0
- package/extensions/services/proxy/agentcp/metrics.py +256 -0
- package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
- package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
- package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
- package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
- package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
- package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
- package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
- package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
- package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
- package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
- package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
- package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
- package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
- package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
- package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
- package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
- package/extensions/services/proxy/agentcp/requirements.txt +7 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
- package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
- package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
- package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
- package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
- package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
- package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
- package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
- package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
- package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
- package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
- package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
- package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
- package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
- package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
- package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
- package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
- package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
- package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
- package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
- package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
- package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
- package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
- package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
- package/extensions/services/proxy/agentcp/workflow.py +203 -0
- package/extensions/services/proxy/console_auth.py +109 -0
- package/extensions/services/proxy/evol/__init__.py +1 -0
- package/extensions/services/proxy/evol/config.py +37 -0
- package/extensions/services/proxy/evol/http/__init__.py +1 -0
- package/extensions/services/proxy/evol/http/async_http.py +551 -0
- package/extensions/services/proxy/evol/log.py +28 -0
- package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
- package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
- package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
- package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
- package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
- package/extensions/services/proxy/evol/server/__init__.py +1 -0
- package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
- package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
- package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
- package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
- package/extensions/services/proxy/evol/version.py +24 -0
- package/extensions/services/proxy/logs/websocket.log +260 -0
- package/extensions/services/proxy/main.py +240 -0
- package/extensions/services/proxy/requirements.txt +13 -0
- package/extensions/services/proxy/server.py +271 -0
- package/extensions/services/watchdog/entry.py +42 -16
- package/extensions/services/watchdog/module.md +1 -0
- package/extensions/services/watchdog/monitor.py +34 -4
- package/extensions/services/web/module.md +1 -1
- package/extensions/services/web/server.py +30 -18
- package/extensions/services/web/static/js/token-manager.js +10 -10
- package/kernel/entry.py +1 -1
- package/kernel/module.md +25 -1
- package/kernel/registry_store.py +2 -26
- package/kernel/rpc_router.py +36 -10
- package/kernel/server.py +106 -17
- package/kite_cli/commands/deps_install.py +67 -0
- package/kite_cli/commands/env_check.py +45 -0
- package/kite_cli/commands/prepare.py +49 -0
- package/kite_cli/commands/venv_setup.py +56 -0
- package/kite_cli/main.py +29 -1
- package/launcher/entry.py +306 -21
- package/launcher/module.md +9 -0
- package/launcher/module_scanner.py +11 -1
- package/main.py +4 -1
- package/package.json +8 -1
- package/python_version.json +4 -0
- package/requirements.txt +38 -0
- package/scripts/env-manager.js +328 -0
- package/scripts/python-env.js +79 -0
- package/scripts/scan_dependencies.py +461 -0
- package/scripts/setup-python-env.js +191 -0
|
@@ -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
|