@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,414 @@
|
|
|
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 datetime
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
import requests
|
|
21
|
+
from cryptography import x509
|
|
22
|
+
from cryptography.hazmat.backends import default_backend
|
|
23
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
24
|
+
from cryptography.hazmat.primitives.asymmetric import ec
|
|
25
|
+
from cryptography.x509.oid import NameOID
|
|
26
|
+
|
|
27
|
+
from agentcp.base.env import Environ
|
|
28
|
+
from agentcp.base.log import log_debug, log_error, log_exception, log_info
|
|
29
|
+
|
|
30
|
+
from ..context import ErrorContext, exceptions
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CAClient:
|
|
34
|
+
def __init__(self, ca_server, aid_path, seed_password: str, timeout=30):
|
|
35
|
+
self.ca_server = ca_server or Environ.CA_SERVER.get() # 移除末尾的斜杠
|
|
36
|
+
if not self.ca_server or not self.ca_server.startswith(("http://", "https://")):
|
|
37
|
+
raise ValueError("无效的CA服务器地址")
|
|
38
|
+
|
|
39
|
+
self.ca_server = self.ca_server.rstrip("/") # 移除末尾的斜杠
|
|
40
|
+
self.ca_server = self.ca_server + "/api/accesspoint"
|
|
41
|
+
self.timeout = timeout
|
|
42
|
+
self.seed_password = seed_password
|
|
43
|
+
self.aid_path = aid_path
|
|
44
|
+
|
|
45
|
+
def get_aid_certs_path(self, aid_str):
|
|
46
|
+
return os.path.join(self.aid_path, aid_str, "private", "certs")
|
|
47
|
+
|
|
48
|
+
def __save_csr_to_file(self, csr, filename):
|
|
49
|
+
try:
|
|
50
|
+
# 确保目录存在
|
|
51
|
+
file_dir = os.path.dirname(filename)
|
|
52
|
+
os.makedirs(file_dir, exist_ok=True)
|
|
53
|
+
with open(filename, "wb") as f:
|
|
54
|
+
f.write(csr.public_bytes(serialization.Encoding.PEM))
|
|
55
|
+
log_debug(f"CSR save to {filename}") # 调试用
|
|
56
|
+
except Exception as e:
|
|
57
|
+
log_exception(f"save csr to file error: {e}") # 调试用
|
|
58
|
+
|
|
59
|
+
def save_cert_to_file(self, name, certificate: str):
|
|
60
|
+
try:
|
|
61
|
+
aid_path = os.path.join(self.aid_path, name, "private", "certs")
|
|
62
|
+
# 确保目录存在
|
|
63
|
+
os.makedirs(aid_path, exist_ok=True)
|
|
64
|
+
aid_path = os.path.join(aid_path, name + ".crt")
|
|
65
|
+
with open(aid_path, "wb") as f:
|
|
66
|
+
f.write(certificate.encode("utf-8"))
|
|
67
|
+
log_debug(f"private key saveto {name}.key") # 调试用
|
|
68
|
+
except Exception as e:
|
|
69
|
+
log_exception(f"save private key to file error{e}") # 调试用
|
|
70
|
+
ErrorContext.publish(exceptions.SDKError(f"保存证书失败: {e}"))
|
|
71
|
+
raise RuntimeError("保存证书失败")
|
|
72
|
+
|
|
73
|
+
def save_private_key_to_file(self, name, private_key):
|
|
74
|
+
try:
|
|
75
|
+
aid_path = os.path.join(self.aid_path, name, "private", "certs")
|
|
76
|
+
# 确保目录存在
|
|
77
|
+
os.makedirs(aid_path, exist_ok=True)
|
|
78
|
+
aid_path = os.path.join(aid_path, name + ".key")
|
|
79
|
+
with open(aid_path, "wb") as f:
|
|
80
|
+
f.write(
|
|
81
|
+
private_key.private_bytes(
|
|
82
|
+
encoding=serialization.Encoding.PEM,
|
|
83
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
84
|
+
encryption_algorithm=serialization.BestAvailableEncryption(self.seed_password.encode()),
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
log_debug(f"private key saveto {name}.key") # 调试用
|
|
88
|
+
except Exception as e:
|
|
89
|
+
log_exception(f"save private key to file error{e}") # 调试用
|
|
90
|
+
ErrorContext.publish(exceptions.SDKError(f"保存私钥失败: {e}"))
|
|
91
|
+
raise RuntimeError("保存私钥失败")
|
|
92
|
+
|
|
93
|
+
def modify_seed_password(self, aid_str, private_key, new_send_password):
|
|
94
|
+
temp_path = os.path.join(self.aid_path, aid_str)
|
|
95
|
+
os.path.exists(temp_path) or os.makedirs(temp_path)
|
|
96
|
+
aid_path = os.path.join(temp_path, aid_str + ".key")
|
|
97
|
+
with open(aid_path, "wb") as f:
|
|
98
|
+
f.write(
|
|
99
|
+
private_key.private_bytes(
|
|
100
|
+
encoding=serialization.Encoding.PEM,
|
|
101
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
102
|
+
encryption_algorithm=serialization.BestAvailableEncryption(new_send_password.encode("utf-8")),
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
old_aid_path = os.path.join(self.aid_path, aid_str, "private", aid_str + ".key")
|
|
106
|
+
old_key_path = os.path.join(self.aid_path, aid_str, "private", "old.key")
|
|
107
|
+
os.rename(old_aid_path, old_key_path)
|
|
108
|
+
# 复制新文件到旧路径
|
|
109
|
+
shutil.copy2(aid_path, old_aid_path)
|
|
110
|
+
# 删除old.key
|
|
111
|
+
os.remove(old_key_path)
|
|
112
|
+
os.remove(aid_path)
|
|
113
|
+
|
|
114
|
+
def __generate_private_key(self):
|
|
115
|
+
"""
|
|
116
|
+
生成NIST P-384椭圆曲线私钥
|
|
117
|
+
:return: 返回生成的私钥对象
|
|
118
|
+
"""
|
|
119
|
+
# 使用SECP384R1曲线生成私钥
|
|
120
|
+
private_key = ec.generate_private_key(ec.SECP384R1())
|
|
121
|
+
return private_key
|
|
122
|
+
|
|
123
|
+
def __generate_csr(self, private_key, common_name):
|
|
124
|
+
"""
|
|
125
|
+
使用NIST P-384私钥生成证书签名请求(CSR)
|
|
126
|
+
:param private_key: NIST P-384椭圆曲线私钥
|
|
127
|
+
:param common_name: 证书通用名称
|
|
128
|
+
:return: 返回生成的CSR对象
|
|
129
|
+
"""
|
|
130
|
+
# 创建 CSR 的主体信息
|
|
131
|
+
csr_builder = x509.CertificateSigningRequestBuilder().subject_name(
|
|
132
|
+
x509.Name(
|
|
133
|
+
[
|
|
134
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, "CN"),
|
|
135
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "SomeState"),
|
|
136
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, "SomeCity"),
|
|
137
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "SomeOrganization"),
|
|
138
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
139
|
+
]
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# 添加扩展(可选)
|
|
144
|
+
csr_builder = csr_builder.add_extension(
|
|
145
|
+
x509.BasicConstraints(ca=False, path_length=None),
|
|
146
|
+
critical=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# 使用私钥对 CSR 进行签名
|
|
150
|
+
csr = csr_builder.sign(private_key, hashes.SHA256(), default_backend())
|
|
151
|
+
return csr
|
|
152
|
+
|
|
153
|
+
def __load_csr(self, agent_id):
|
|
154
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".csr")
|
|
155
|
+
if os.path.exists(aid_path):
|
|
156
|
+
with open(aid_path, "rb") as f:
|
|
157
|
+
csr = x509.load_pem_x509_csr(f.read())
|
|
158
|
+
return csr
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
def load_private_key_str(self, agent_id, seed_password: str):
|
|
162
|
+
try:
|
|
163
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".key")
|
|
164
|
+
with open(aid_path, "rb") as f:
|
|
165
|
+
private_key = serialization.load_pem_private_key(
|
|
166
|
+
f.read(),
|
|
167
|
+
password=seed_password.encode("utf-8"),
|
|
168
|
+
)
|
|
169
|
+
return private_key.private_bytes(
|
|
170
|
+
encoding=serialization.Encoding.PEM,
|
|
171
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
172
|
+
encryption_algorithm=serialization.BestAvailableEncryption(seed_password.encode("utf-8")),
|
|
173
|
+
).decode("utf-8")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
ErrorContext.publish(exceptions.SDKError(f"load_private_key_str: {e}"))
|
|
176
|
+
return ""
|
|
177
|
+
|
|
178
|
+
# def load_cert_str(self,agent_id):
|
|
179
|
+
# try:
|
|
180
|
+
# aid_path = os.path.join(self.aid_path,agent_id,'private','certs',agent_id+".crt")
|
|
181
|
+
# with open(aid_path, "rb") as f:
|
|
182
|
+
# certificate_pem = f.read().decode('utf-8')
|
|
183
|
+
# return certificate_pem
|
|
184
|
+
# except Exception as e:
|
|
185
|
+
# return ""
|
|
186
|
+
|
|
187
|
+
def load_private_key(self, agent_id):
|
|
188
|
+
try:
|
|
189
|
+
# 加载私钥
|
|
190
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".key")
|
|
191
|
+
with open(aid_path, "rb") as f:
|
|
192
|
+
private_key = serialization.load_pem_private_key(
|
|
193
|
+
f.read(),
|
|
194
|
+
password=self.seed_password.encode("utf-8"),
|
|
195
|
+
)
|
|
196
|
+
return private_key
|
|
197
|
+
except Exception as e:
|
|
198
|
+
# 兼容性代码,按照不加密获取private_key
|
|
199
|
+
ErrorContext.publish(exceptions.SDKError(f"load_private_key: {e}"))
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def __load_public_key_pem(self, public_key):
|
|
203
|
+
public_key_pem = public_key.public_bytes(
|
|
204
|
+
encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo
|
|
205
|
+
).decode("utf-8")
|
|
206
|
+
return public_key_pem
|
|
207
|
+
|
|
208
|
+
def load_certificate_pem(self, agent_id):
|
|
209
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".crt")
|
|
210
|
+
with open(aid_path, "rb") as f:
|
|
211
|
+
certificate_pem = f.read().decode("utf-8")
|
|
212
|
+
return certificate_pem
|
|
213
|
+
|
|
214
|
+
def __get_guest_aid(self):
|
|
215
|
+
os.path.exists(self.aid_path) or os.makedirs(self.aid_path)
|
|
216
|
+
for entry in os.scandir(self.aid_path):
|
|
217
|
+
array = entry.name.split(".")
|
|
218
|
+
if entry.is_dir() and entry.name.startswith("guest"):
|
|
219
|
+
return entry.name
|
|
220
|
+
return ""
|
|
221
|
+
|
|
222
|
+
def get_guest_aid(self):
|
|
223
|
+
try:
|
|
224
|
+
local_guest_aid = self.__get_guest_aid()
|
|
225
|
+
path = os.path.join(self.aid_path, local_guest_aid, "private", "certs", local_guest_aid + ".crt")
|
|
226
|
+
if local_guest_aid and self.__pen_is_valid(path):
|
|
227
|
+
return local_guest_aid
|
|
228
|
+
elif local_guest_aid:
|
|
229
|
+
# 删除这个目录下的所有文件和子目录
|
|
230
|
+
shutil.rmtree(os.path.join(self.aid_path, local_guest_aid))
|
|
231
|
+
url = self.ca_server + "/sign_guest_cert"
|
|
232
|
+
response = requests.get(url, verify=False, proxies={})
|
|
233
|
+
if response.status_code == 200:
|
|
234
|
+
rjs = response.json()
|
|
235
|
+
if "guest_aid" in rjs and "key" in rjs and "cert" in rjs and "entrypoint" in rjs:
|
|
236
|
+
log_info(f"sign_guest_cert ok:{rjs}")
|
|
237
|
+
guest_aid = rjs["guest_aid"]
|
|
238
|
+
guest_key = rjs["key"]
|
|
239
|
+
guest_cert = rjs["cert"]
|
|
240
|
+
# 打印 guest_cert 内容,检查是否包含正确的 CSR 标记
|
|
241
|
+
|
|
242
|
+
try:
|
|
243
|
+
# 尝试加载证书
|
|
244
|
+
certificate = x509.load_pem_x509_certificate(guest_cert.encode("utf-8"), default_backend())
|
|
245
|
+
except Exception as e:
|
|
246
|
+
log_error(f"加载证书失败: {e}")
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
guest_key = serialization.load_pem_private_key(guest_key.encode("utf-8"), password=None)
|
|
250
|
+
|
|
251
|
+
if not os.path.exists(os.path.join(self.aid_path, guest_aid)):
|
|
252
|
+
os.makedirs(os.path.join(self.aid_path, guest_aid), exist_ok=True)
|
|
253
|
+
|
|
254
|
+
self.save_private_key_to_file(guest_aid, guest_key)
|
|
255
|
+
|
|
256
|
+
# 保存证书文件
|
|
257
|
+
aid_path = os.path.join(self.aid_path, guest_aid, "private", "certs")
|
|
258
|
+
cert_name = os.path.join(aid_path, guest_aid + ".crt")
|
|
259
|
+
with open(cert_name, "wb") as f:
|
|
260
|
+
f.write(guest_cert.encode("utf-8"))
|
|
261
|
+
time.sleep(0.5)
|
|
262
|
+
return guest_aid
|
|
263
|
+
else:
|
|
264
|
+
log_error(f"sign_guest_cert failed:{rjs}")
|
|
265
|
+
return None
|
|
266
|
+
else:
|
|
267
|
+
log_error(f"sign_guest_cert failed:{response.status_code} - {response.json().get('error', '')}")
|
|
268
|
+
return None
|
|
269
|
+
except Exception as e:
|
|
270
|
+
ErrorContext.publish(exceptions.SDKError(f"获取访问身份失败: {e}"))
|
|
271
|
+
log_error(f"获取访问身份失败: {e}")
|
|
272
|
+
# log_error("详细堆栈信息:")
|
|
273
|
+
# traceback.print_exc()
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
def __pen_is_valid(self, agent_id_key_path: str):
|
|
277
|
+
cert_valid = False
|
|
278
|
+
try:
|
|
279
|
+
aid_path = agent_id_key_path
|
|
280
|
+
certificate_pem = ""
|
|
281
|
+
with open(aid_path, "rb") as f:
|
|
282
|
+
certificate_pem = f.read().decode("utf-8")
|
|
283
|
+
certificate = x509.load_pem_x509_certificate(certificate_pem.encode("utf-8"), default_backend())
|
|
284
|
+
# 获取证书的有效期
|
|
285
|
+
not_valid_before = certificate.not_valid_before_utc
|
|
286
|
+
not_valid_after = certificate.not_valid_after_utc
|
|
287
|
+
# 获取当前时间
|
|
288
|
+
current_time = datetime.datetime.now(datetime.timezone.utc)
|
|
289
|
+
# 检查证书是否过期
|
|
290
|
+
if current_time < not_valid_before:
|
|
291
|
+
log_error("证书尚未生效")
|
|
292
|
+
elif current_time > not_valid_after:
|
|
293
|
+
log_error("证书已过期")
|
|
294
|
+
elif current_time + (not_valid_after - not_valid_before) / 10 > not_valid_after:
|
|
295
|
+
log_error("证书剩余有效期不足一半,需要续签")
|
|
296
|
+
else:
|
|
297
|
+
log_info("证书在有效期内")
|
|
298
|
+
cert_valid = True
|
|
299
|
+
except Exception as e:
|
|
300
|
+
log_error(f"解析证书时出错: {e}")
|
|
301
|
+
ErrorContext.publish(exceptions.SDKError(f"解析证书时出错: {e}"))
|
|
302
|
+
cert_valid = False
|
|
303
|
+
return cert_valid
|
|
304
|
+
|
|
305
|
+
def resign_csr(self, agent_id) -> bool:
|
|
306
|
+
path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".crt")
|
|
307
|
+
if self.__pen_is_valid(path):
|
|
308
|
+
return True
|
|
309
|
+
# 从CSR中提取公钥
|
|
310
|
+
csr = self.__generate_csr(self.load_private_key(agent_id), agent_id)
|
|
311
|
+
if csr == None:
|
|
312
|
+
raise Exception("读取csr证书文件失败")
|
|
313
|
+
|
|
314
|
+
public_key = csr.public_key()
|
|
315
|
+
private_key = self.load_private_key(agent_id)
|
|
316
|
+
public_key_pem = self.__load_public_key_pem(public_key)
|
|
317
|
+
|
|
318
|
+
# 加载原有的证书文件
|
|
319
|
+
certificate_pem = self.load_certificate_pem(agent_id)
|
|
320
|
+
|
|
321
|
+
# 获取当前Unix时间戳(毫秒)
|
|
322
|
+
current_time_ms = int(datetime.datetime.now().timestamp() * 1000)
|
|
323
|
+
|
|
324
|
+
# 准备发送给服务器的数据
|
|
325
|
+
data = {"id": agent_id, "request_id": f"{current_time_ms}", "public_key": public_key_pem}
|
|
326
|
+
|
|
327
|
+
# 发送到服务器
|
|
328
|
+
response = requests.post(f"{self.ca_server}/resign_cert", json=data, verify=False, proxies={})
|
|
329
|
+
log_info(f"resign cert response: {response.content}")
|
|
330
|
+
if response.status_code == 200:
|
|
331
|
+
if "nonce" in response.json():
|
|
332
|
+
nonce = response.json()["nonce"]
|
|
333
|
+
if nonce:
|
|
334
|
+
# 使用私钥对[公钥+盐]签名,以使服务器信任私钥仍然有效
|
|
335
|
+
# 使用NIST P-384私钥对nonce进行签名
|
|
336
|
+
signature = private_key.sign((public_key_pem + nonce).encode("utf-8"), ec.ECDSA(hashes.SHA256()))
|
|
337
|
+
csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
|
338
|
+
data = {
|
|
339
|
+
"id": agent_id,
|
|
340
|
+
"request_id": f"{current_time_ms}",
|
|
341
|
+
"public_key": public_key_pem,
|
|
342
|
+
"nonce": nonce,
|
|
343
|
+
"csr": csr_pem,
|
|
344
|
+
"cert": certificate_pem,
|
|
345
|
+
"signature": signature.hex(), # 将签名转为十六进制字符串
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
# 发送到服务器
|
|
349
|
+
|
|
350
|
+
response = requests.post(self.ca_server + "/resign_cert", json=data, verify=False, proxies={})
|
|
351
|
+
log_debug(f"resign cert response: {response.content}")
|
|
352
|
+
if response.status_code == 200:
|
|
353
|
+
entrypoint = ";"
|
|
354
|
+
if "entrypoint" in response.json():
|
|
355
|
+
entrypoint = response.json()["entrypoint"]
|
|
356
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs", agent_id + ".crt")
|
|
357
|
+
with open(aid_path, "wb") as f:
|
|
358
|
+
f.write(response.json()["certificate"].encode("utf-8")) # 从JSON响应中获取证书内容
|
|
359
|
+
return True
|
|
360
|
+
else:
|
|
361
|
+
log_error(f"resign csr failed: {response.status_code} - {response.json()['error']}") # 调试用
|
|
362
|
+
return False
|
|
363
|
+
else:
|
|
364
|
+
log_info(
|
|
365
|
+
f"verify public key failed: {response.status_code} - {response.json().get('error', '')}"
|
|
366
|
+
) # 调试用
|
|
367
|
+
ErrorContext.publish(
|
|
368
|
+
exceptions.SDKError(
|
|
369
|
+
f"verify public key failed: {response.status_code} - {response.json().get('error', '')}"
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
def send_csr_to_server(self, agent_id: str) -> bool:
|
|
375
|
+
# 确保证书文件路径存在
|
|
376
|
+
try:
|
|
377
|
+
# 确保目录存在
|
|
378
|
+
aid_path = os.path.join(self.aid_path, agent_id, "private", "certs")
|
|
379
|
+
os.makedirs(aid_path, exist_ok=True) # 确保aid/name目录存在
|
|
380
|
+
|
|
381
|
+
private_key = self.__generate_private_key()
|
|
382
|
+
csr = self.__generate_csr(private_key, agent_id)
|
|
383
|
+
|
|
384
|
+
csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
|
385
|
+
data = {"id": agent_id, "csr": csr_pem}
|
|
386
|
+
response = requests.post(f"{self.ca_server}/sign_cert", json=data, verify=False, proxies={})
|
|
387
|
+
if response.status_code == 200:
|
|
388
|
+
# 确保目录存在后再保存文件
|
|
389
|
+
if not os.path.exists(aid_path):
|
|
390
|
+
os.makedirs(aid_path, exist_ok=True)
|
|
391
|
+
crt_name = os.path.join(aid_path, agent_id + ".crt")
|
|
392
|
+
with open(crt_name, "wb") as f:
|
|
393
|
+
f.write(response.json()["certificate"].encode("utf-8"))
|
|
394
|
+
csr_name = os.path.join(aid_path, agent_id + ".csr")
|
|
395
|
+
self.__save_csr_to_file(csr, csr_name)
|
|
396
|
+
self.save_private_key_to_file(agent_id, private_key)
|
|
397
|
+
log_info(f"signed certificate successfully: {agent_id} {csr_name}") # 调试用
|
|
398
|
+
return True
|
|
399
|
+
else:
|
|
400
|
+
delete_path = os.path.join(self.aid_path, agent_id)
|
|
401
|
+
shutil.rmtree(delete_path)
|
|
402
|
+
log_error(f"sign failed: {self.ca_server}, {response.status_code} - {response.text}") # 调试用
|
|
403
|
+
return response.json()["error"]
|
|
404
|
+
except requests.RequestException as e:
|
|
405
|
+
ErrorContext.publish(exceptions.SDKError(f"send csr to server error: {e}"))
|
|
406
|
+
log_exception("send csr to server error") # 调试用
|
|
407
|
+
raise RuntimeError("send csr to server error")
|
|
408
|
+
|
|
409
|
+
def aid_is_not_exist(self, agent_id):
|
|
410
|
+
path = self.aid_path
|
|
411
|
+
for entry in os.scandir(path):
|
|
412
|
+
if entry.is_dir() and entry.name == agent_id:
|
|
413
|
+
return False
|
|
414
|
+
return True
|
|
@@ -0,0 +1,74 @@
|
|
|
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 os
|
|
16
|
+
|
|
17
|
+
from agentcp.base.log import log_error, log_info
|
|
18
|
+
class CARoot:
|
|
19
|
+
_instance = None
|
|
20
|
+
|
|
21
|
+
def __new__(cls):
|
|
22
|
+
if not cls._instance:
|
|
23
|
+
cls._instance = super().__new__(cls)
|
|
24
|
+
cls._instance.__initialized = False
|
|
25
|
+
return cls._instance
|
|
26
|
+
|
|
27
|
+
def set_ca_root_crt(self,ca_root_path):
|
|
28
|
+
self.__ca_root_path = ca_root_path
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.__ca_crt = []
|
|
32
|
+
# 内置根证书(PEM格式)
|
|
33
|
+
self.__ca_crt.append("""\
|
|
34
|
+
-----BEGIN CERTIFICATE-----
|
|
35
|
+
MIICJjCCAYigAwIBAgIQf2zjuigigLrW8Su0I+2AiTAKBggqhkjOPQQDBDAnMRMw
|
|
36
|
+
EQYDVQQKEwpBZ2VudFVuaW9uMRAwDgYDVQQDEwdSb290IENBMB4XDTI1MDUwODA3
|
|
37
|
+
MDE1OFoXDTQ1MDUwODA3MDE1OFowJzETMBEGA1UEChMKQWdlbnRVbmlvbjEQMA4G
|
|
38
|
+
A1UEAxMHUm9vdCBDQTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAUuDc4dMcjXI
|
|
39
|
+
GVxem8HStonZlyfyqtujfpTz8WP4ZcMUCTlrvnxZRjzNarmzSc2Yx2COcK1VEuzP
|
|
40
|
+
TcyQGE/Pw9i4AP9qGtX0j3dwLw+i2+TzEOmgoulm+t+fyjxhLsmqyWrIUdTv6T5C
|
|
41
|
+
IYVkSnX1mM0UPVQYxZi/2Uuyw8FOcPzIq7eWo1MwUTAOBgNVHQ8BAf8EBAMCAf4w
|
|
42
|
+
DwYDVR0lBAgwBgYEVR0lADAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQhkljB
|
|
43
|
+
FddnHb7Y0j6YEZ7wtYReNzAKBggqhkjOPQQDBAOBiwAwgYcCQWKjy52NZwqJZ1FN
|
|
44
|
+
1n1BRPAIy6nFDTke+HbM/JFWGyFrNSx4ceVSurpa+Uy9TWmwNUuog82MHRDXnlYp
|
|
45
|
+
e1KpOe6qAkIBGGII4Yfzoc5x+ZC0le7kyAYTJJl1XVLCdwECPRfzk/uZHonFA4tV
|
|
46
|
+
nHMwnEqFoMdsj2GgOqoRqAw/miMQwo2T0hA=
|
|
47
|
+
-----END CERTIFICATE-----
|
|
48
|
+
""")
|
|
49
|
+
self.__ca_root_path = None
|
|
50
|
+
self.__initialized = True
|
|
51
|
+
|
|
52
|
+
def get_ca_root_crt_number(self):
|
|
53
|
+
return len(self.__ca_crt)
|
|
54
|
+
|
|
55
|
+
def get_ca_root_crt(self,index=0):
|
|
56
|
+
if self.__ca_root_path:
|
|
57
|
+
try:
|
|
58
|
+
crt_files = sorted([f for f in os.listdir(self.__ca_root_path) if f.endswith('.crt')])
|
|
59
|
+
if not crt_files:
|
|
60
|
+
log_error(f"目录 {self.__ca_root_path} 中没有证书文件")
|
|
61
|
+
return self.__ca_crt[index]
|
|
62
|
+
|
|
63
|
+
if index >= len(crt_files):
|
|
64
|
+
log_error(f"索引 {index} 超出文件数量 {len(crt_files)}")
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
cert_path = os.path.join(self.__ca_root_path, crt_files[index])
|
|
68
|
+
with open(cert_path, 'r', encoding='utf-8') as f:
|
|
69
|
+
return f.read()
|
|
70
|
+
except FileNotFoundError:
|
|
71
|
+
log_error(f"根证书目录 {self.__ca_root_path} 不存在")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
log_error(f"根读取证书文件失败: {str(e)}")
|
|
74
|
+
return self.__ca_crt[index]
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
from .context import AtomicErrorContext
|
|
16
|
+
|
|
17
|
+
ErrorContext = AtomicErrorContext()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["ErrorContext"]
|
|
@@ -0,0 +1,73 @@
|
|
|
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 atexit
|
|
16
|
+
import queue
|
|
17
|
+
import threading
|
|
18
|
+
from typing import Callable
|
|
19
|
+
|
|
20
|
+
from .exceptions import SDKError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AtomicErrorContext:
|
|
24
|
+
"""
|
|
25
|
+
错误收集,只有在订阅后才运行,否则不会有任何错误信息加入到队列
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, max_queue_size=1024):
|
|
29
|
+
self.queue = queue.Queue[SDKError](maxsize=max_queue_size)
|
|
30
|
+
self.stop_flag = threading.Event()
|
|
31
|
+
self.start_flag = threading.Event()
|
|
32
|
+
self.worker_thread = threading.Thread(target=self._worker, daemon=True)
|
|
33
|
+
|
|
34
|
+
def publish(self, e: SDKError):
|
|
35
|
+
if not self.start_flag.is_set():
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
if self.queue.full():
|
|
40
|
+
self.queue.get_nowait()
|
|
41
|
+
e.agent_id = self.aid
|
|
42
|
+
|
|
43
|
+
self.queue.put(e, block=False)
|
|
44
|
+
except queue.Full:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
def subscribe(self, aid: str, func: Callable[[SDKError], None]):
|
|
48
|
+
"""
|
|
49
|
+
上层直接退出登录线程未完全退出,防止重复订阅(多账号来回切换会出现异常)
|
|
50
|
+
"""
|
|
51
|
+
if self.start_flag.is_set():
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
self.aid = aid
|
|
55
|
+
self.func = func
|
|
56
|
+
self.start_flag.set()
|
|
57
|
+
self.worker_thread.start()
|
|
58
|
+
atexit.register(self.close)
|
|
59
|
+
|
|
60
|
+
def _worker(self):
|
|
61
|
+
"""后台线程,定时或批量处理队列数据"""
|
|
62
|
+
while not self.stop_flag.is_set():
|
|
63
|
+
try:
|
|
64
|
+
# 等待队列有数据
|
|
65
|
+
data = self.queue.get(timeout=1)
|
|
66
|
+
self.func(data)
|
|
67
|
+
except queue.Empty:
|
|
68
|
+
pass # 到达interval时间也可能队列为空
|
|
69
|
+
|
|
70
|
+
def close(self):
|
|
71
|
+
"""关闭采集线程,确保队列数据全部发送"""
|
|
72
|
+
self.stop_flag.set()
|
|
73
|
+
self.worker_thread.join()
|