@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.
Files changed (235) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/cli.js +44 -5
  3. package/core/dependency_checker.py +250 -0
  4. package/core/env_checker.py +490 -0
  5. package/dependencies_lock.json +128 -0
  6. package/extensions/agents/assistant/server.py +33 -17
  7. package/extensions/channels/acp_channel/server.py +33 -17
  8. package/extensions/services/backup/entry.py +23 -16
  9. package/extensions/services/evol/auth_manager.py +443 -0
  10. package/extensions/services/evol/config.yaml +149 -0
  11. package/extensions/services/evol/config_loader.py +117 -0
  12. package/extensions/services/evol/entry.py +406 -0
  13. package/extensions/services/evol/evol_api.py +173 -0
  14. package/extensions/services/evol/evol_config.json5 +29 -0
  15. package/extensions/services/evol/migrate_tokens.py +122 -0
  16. package/extensions/services/evol/module.md +32 -0
  17. package/extensions/services/evol/pairing.py +250 -0
  18. package/extensions/services/evol/pairing_codes.jsonl +1 -0
  19. package/extensions/services/evol/relay.py +682 -0
  20. package/extensions/services/evol/relay_config.json5 +67 -0
  21. package/extensions/services/evol/routes/__init__.py +1 -0
  22. package/extensions/services/evol/routes/routes_management_ws.py +127 -0
  23. package/extensions/services/evol/routes/routes_rpc.py +89 -0
  24. package/extensions/services/evol/routes/routes_test.py +61 -0
  25. package/extensions/services/evol/server.py +875 -0
  26. package/extensions/services/evol/static/css/style.css +1200 -0
  27. package/extensions/services/evol/static/index.html +781 -0
  28. package/extensions/services/evol/static/index_evol.html +14 -0
  29. package/extensions/services/evol/static/js/app.js +6304 -0
  30. package/extensions/services/evol/static/js/auth.js +326 -0
  31. package/extensions/services/evol/static/js/dialog.js +285 -0
  32. package/extensions/services/evol/static/js/evol-app-fixed.js +50 -0
  33. package/extensions/services/evol/static/js/evol-app.js +1949 -0
  34. package/extensions/services/evol/static/js/evol-app.js.bak +1800 -0
  35. package/extensions/services/evol/static/js/kernel-client-example.js +228 -0
  36. package/extensions/services/evol/static/js/kernel-client.js +396 -0
  37. package/extensions/services/evol/static/js/main.js +141 -0
  38. package/extensions/services/evol/static/js/registry-tests.js +585 -0
  39. package/extensions/services/evol/static/js/stats.js +217 -0
  40. package/extensions/services/evol/static/js/token-manager.js +175 -0
  41. package/extensions/services/evol/static/pairing.html +248 -0
  42. package/extensions/services/evol/static/test_registry.html +262 -0
  43. package/extensions/services/evol/static/test_relay.html +462 -0
  44. package/extensions/services/evol/stats_manager.py +240 -0
  45. package/extensions/services/model_service/entry.py +23 -1
  46. package/extensions/services/proxy/.claude/settings.local.json +13 -0
  47. package/extensions/services/proxy/CHANGELOG_20260308.md +258 -0
  48. package/extensions/services/proxy/_fix_prints.py +133 -0
  49. package/extensions/services/proxy/_fix_prints2.py +87 -0
  50. package/extensions/services/proxy/agentcp/LICENCE +178 -0
  51. package/extensions/services/proxy/agentcp/README copy.md +85 -0
  52. package/extensions/services/proxy/agentcp/README.md +260 -0
  53. package/extensions/services/proxy/agentcp/__init__.py +16 -0
  54. package/extensions/services/proxy/agentcp/agent.py +4 -0
  55. package/extensions/services/proxy/agentcp/agentcp.py +2494 -0
  56. package/extensions/services/proxy/agentcp/agentprofile.json +89 -0
  57. package/extensions/services/proxy/agentcp/ap/__init__.py +16 -0
  58. package/extensions/services/proxy/agentcp/ap/ap_client.py +316 -0
  59. package/extensions/services/proxy/agentcp/assets/images/wechat_qr.png +0 -0
  60. package/extensions/services/proxy/agentcp/backup/metrics.json +31 -0
  61. package/extensions/services/proxy/agentcp/base/__init__.py +20 -0
  62. package/extensions/services/proxy/agentcp/base/auth_client.py +257 -0
  63. package/extensions/services/proxy/agentcp/base/client.py +112 -0
  64. package/extensions/services/proxy/agentcp/base/env.py +34 -0
  65. package/extensions/services/proxy/agentcp/base/html_util.py +336 -0
  66. package/extensions/services/proxy/agentcp/base/log.py +98 -0
  67. package/extensions/services/proxy/agentcp/ca/__init__.py +17 -0
  68. package/extensions/services/proxy/agentcp/ca/ca_client.py +414 -0
  69. package/extensions/services/proxy/agentcp/ca/ca_root.py +74 -0
  70. package/extensions/services/proxy/agentcp/context/__init__.py +20 -0
  71. package/extensions/services/proxy/agentcp/context/context.py +73 -0
  72. package/extensions/services/proxy/agentcp/context/exceptions.py +114 -0
  73. package/extensions/services/proxy/agentcp/create_profile.py +125 -0
  74. package/extensions/services/proxy/agentcp/create_profile_weather.py +125 -0
  75. package/extensions/services/proxy/agentcp/db/__init__.py +15 -0
  76. package/extensions/services/proxy/agentcp/db/db_mananger.py +550 -0
  77. package/extensions/services/proxy/agentcp/docs/UDP_HEARTBEAT_FIX_REPORT.md +265 -0
  78. package/extensions/services/proxy/agentcp/docs/heartbeat_issue_analysis.md +291 -0
  79. package/extensions/services/proxy/agentcp/file/__init__.py +16 -0
  80. package/extensions/services/proxy/agentcp/file/file_client.py +141 -0
  81. package/extensions/services/proxy/agentcp/file/wss_binary_message.py +137 -0
  82. package/extensions/services/proxy/agentcp/hcp.py +299 -0
  83. package/extensions/services/proxy/agentcp/heartbeat/__init__.py +16 -0
  84. package/extensions/services/proxy/agentcp/heartbeat/heartbeat_client.py +360 -0
  85. package/extensions/services/proxy/agentcp/improved_scheduler.py +498 -0
  86. package/extensions/services/proxy/agentcp/llm_agent_utils.py +249 -0
  87. package/extensions/services/proxy/agentcp/llm_server.py +172 -0
  88. package/extensions/services/proxy/agentcp/mermaid.py +210 -0
  89. package/extensions/services/proxy/agentcp/message.py +149 -0
  90. package/extensions/services/proxy/agentcp/metrics.py +256 -0
  91. package/extensions/services/proxy/agentcp/monitoring/__init__.py +20 -0
  92. package/extensions/services/proxy/agentcp/monitoring/global_monitor.py +27 -0
  93. package/extensions/services/proxy/agentcp/monitoring/metrics_store.py +325 -0
  94. package/extensions/services/proxy/agentcp/monitoring/monitoring_service.py +269 -0
  95. package/extensions/services/proxy/agentcp/monitoring/sliding_window.py +222 -0
  96. package/extensions/services/proxy/agentcp/monitoring/standalone_reader.py +224 -0
  97. package/extensions/services/proxy/agentcp/msg/__init__.py +21 -0
  98. package/extensions/services/proxy/agentcp/msg/connection_manager.py +456 -0
  99. package/extensions/services/proxy/agentcp/msg/message_client.py +2058 -0
  100. package/extensions/services/proxy/agentcp/msg/message_serialize.py +263 -0
  101. package/extensions/services/proxy/agentcp/msg/open_ai_message.py +88 -0
  102. package/extensions/services/proxy/agentcp/msg/session_manager.py +1062 -0
  103. package/extensions/services/proxy/agentcp/msg/stream_client.py +267 -0
  104. package/extensions/services/proxy/agentcp/msg/websocket_file_receiver.py +89 -0
  105. package/extensions/services/proxy/agentcp/msg/ws_logger.py +685 -0
  106. package/extensions/services/proxy/agentcp/msg/wss_binary_message.py +137 -0
  107. package/extensions/services/proxy/agentcp/requirements.txt +7 -0
  108. package/extensions/services/proxy/agentcp/samples/agent_graph/README.md +37 -0
  109. package/extensions/services/proxy/agentcp/samples/agent_graph/agentprofile.json +89 -0
  110. package/extensions/services/proxy/agentcp/samples/agent_graph/create_profile.py +138 -0
  111. package/extensions/services/proxy/agentcp/samples/agent_graph/main.py +164 -0
  112. package/extensions/services/proxy/agentcp/samples/agent_use/create_profile.py +123 -0
  113. package/extensions/services/proxy/agentcp/samples/agent_use/llm/create_profile.py +129 -0
  114. package/extensions/services/proxy/agentcp/samples/agent_use/llm/env.json +5 -0
  115. package/extensions/services/proxy/agentcp/samples/agent_use/llm/main.py +146 -0
  116. package/extensions/services/proxy/agentcp/samples/agent_use/main.py +123 -0
  117. package/extensions/services/proxy/agentcp/samples/agent_use/readme.md +379 -0
  118. package/extensions/services/proxy/agentcp/samples/agent_use/search/create_profile.py +129 -0
  119. package/extensions/services/proxy/agentcp/samples/agent_use/search/main.py +28 -0
  120. package/extensions/services/proxy/agentcp/samples/agent_use/tool/create_profile.py +129 -0
  121. package/extensions/services/proxy/agentcp/samples/agent_use/tool/main.py +20 -0
  122. package/extensions/services/proxy/agentcp/samples/ali_amap/README.md +97 -0
  123. package/extensions/services/proxy/agentcp/samples/ali_amap/amap_agent.py +88 -0
  124. package/extensions/services/proxy/agentcp/samples/ali_amap/create_profile.py +125 -0
  125. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/powershell.py +228 -0
  126. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/software.py +63 -0
  127. package/extensions/services/proxy/agentcp/samples/compute_agent/agent/tools.py +36 -0
  128. package/extensions/services/proxy/agentcp/samples/compute_agent/browser_user.py +41 -0
  129. package/extensions/services/proxy/agentcp/samples/deepseek/README.md +79 -0
  130. package/extensions/services/proxy/agentcp/samples/deepseek/create_profile.py +126 -0
  131. package/extensions/services/proxy/agentcp/samples/deepseek/deepseek.py +42 -0
  132. package/extensions/services/proxy/agentcp/samples/dify_chat/README.md +78 -0
  133. package/extensions/services/proxy/agentcp/samples/dify_chat/create_profile.py +126 -0
  134. package/extensions/services/proxy/agentcp/samples/dify_chat/dify_chat.py +47 -0
  135. package/extensions/services/proxy/agentcp/samples/dify_workflow/README.md +78 -0
  136. package/extensions/services/proxy/agentcp/samples/dify_workflow/create_profile.py +126 -0
  137. package/extensions/services/proxy/agentcp/samples/dify_workflow/dify_workflow.py +46 -0
  138. package/extensions/services/proxy/agentcp/samples/executor/README.md +44 -0
  139. package/extensions/services/proxy/agentcp/samples/executor/agentprofile.json +89 -0
  140. package/extensions/services/proxy/agentcp/samples/executor/create_profile.py +139 -0
  141. package/extensions/services/proxy/agentcp/samples/executor/main.py +160 -0
  142. package/extensions/services/proxy/agentcp/samples/filereader/README.md +45 -0
  143. package/extensions/services/proxy/agentcp/samples/filereader/agentprofile.json +90 -0
  144. package/extensions/services/proxy/agentcp/samples/filereader/create_profile.py +137 -0
  145. package/extensions/services/proxy/agentcp/samples/filereader/main.py +253 -0
  146. package/extensions/services/proxy/agentcp/samples/filewriter/README.md +38 -0
  147. package/extensions/services/proxy/agentcp/samples/filewriter/agentprofile.json +91 -0
  148. package/extensions/services/proxy/agentcp/samples/filewriter/create_profile.py +138 -0
  149. package/extensions/services/proxy/agentcp/samples/filewriter/main.py +289 -0
  150. package/extensions/services/proxy/agentcp/samples/hcp/README.md +85 -0
  151. package/extensions/services/proxy/agentcp/samples/hcp/acp_weather_agent.zip +0 -0
  152. package/extensions/services/proxy/agentcp/samples/hcp/create_profile.py +125 -0
  153. package/extensions/services/proxy/agentcp/samples/hcp/hcp.py +237 -0
  154. package/extensions/services/proxy/agentcp/samples/helloworld/README.md +68 -0
  155. package/extensions/services/proxy/agentcp/samples/helloworld/hello_world.py +40 -0
  156. package/extensions/services/proxy/agentcp/samples/llm_agent/MEADME.md +117 -0
  157. package/extensions/services/proxy/agentcp/samples/llm_agent/create_profile.py +125 -0
  158. package/extensions/services/proxy/agentcp/samples/llm_agent/qwen_agent.py +136 -0
  159. package/extensions/services/proxy/agentcp/samples/local_llm_agent/README.md +90 -0
  160. package/extensions/services/proxy/agentcp/samples/local_llm_agent/create_profile.py +125 -0
  161. package/extensions/services/proxy/agentcp/samples/local_llm_agent/main.py +49 -0
  162. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/README.md +55 -0
  163. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/create_profile.py +125 -0
  164. package/extensions/services/proxy/agentcp/samples/query_llm_from_agent/main.py +23 -0
  165. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/README.md +103 -0
  166. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/create_profile.py +125 -0
  167. package/extensions/services/proxy/agentcp/samples/query_weather_api_agent/main.py +69 -0
  168. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/README.md +58 -0
  169. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/create_profile.py +125 -0
  170. package/extensions/services/proxy/agentcp/samples/query_weather_from_agent/main.py +25 -0
  171. package/extensions/services/proxy/agentcp/samples/qwen3/README.md +71 -0
  172. package/extensions/services/proxy/agentcp/samples/qwen3/create_profile.py +126 -0
  173. package/extensions/services/proxy/agentcp/samples/qwen3/qwen3.py +37 -0
  174. package/extensions/services/proxy/agentcp/samples/qwen3_tools/README.md +133 -0
  175. package/extensions/services/proxy/agentcp/samples/qwen3_tools/create_profile.py +126 -0
  176. package/extensions/services/proxy/agentcp/samples/qwen3_tools/qwen3_tools.py +98 -0
  177. package/extensions/services/proxy/agentcp/samples/search/create_profile_qwen.py +125 -0
  178. package/extensions/services/proxy/agentcp/samples/search/create_profile_search.py +125 -0
  179. package/extensions/services/proxy/agentcp/samples/search/qwen_agent.py +136 -0
  180. package/extensions/services/proxy/agentcp/samples/search/search_agent.py +170 -0
  181. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/README.md +89 -0
  182. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/create_profile.py +125 -0
  183. package/extensions/services/proxy/agentcp/samples/wrapper_agently_to_agent/main.py +44 -0
  184. package/extensions/services/proxy/agentcp/utils/__init__.py +15 -0
  185. package/extensions/services/proxy/agentcp/utils/file_util.py +117 -0
  186. package/extensions/services/proxy/agentcp/utils/proxy_bypass.py +99 -0
  187. package/extensions/services/proxy/agentcp/workflow.py +203 -0
  188. package/extensions/services/proxy/console_auth.py +109 -0
  189. package/extensions/services/proxy/evol/__init__.py +1 -0
  190. package/extensions/services/proxy/evol/config.py +37 -0
  191. package/extensions/services/proxy/evol/http/__init__.py +1 -0
  192. package/extensions/services/proxy/evol/http/async_http.py +551 -0
  193. package/extensions/services/proxy/evol/log.py +28 -0
  194. package/extensions/services/proxy/evol/presenter/__init__.py +2 -0
  195. package/extensions/services/proxy/evol/presenter/agentIdPresenter.py +1031 -0
  196. package/extensions/services/proxy/evol/presenter/apikeyPresenter.py +106 -0
  197. package/extensions/services/proxy/evol/presenter/configPresenter.py +1281 -0
  198. package/extensions/services/proxy/evol/presenter/userPresenter.py +477 -0
  199. package/extensions/services/proxy/evol/server/__init__.py +1 -0
  200. package/extensions/services/proxy/evol/server/claude_proxy_async.py +3430 -0
  201. package/extensions/services/proxy/evol/server/openclaw_proxy.py +1861 -0
  202. package/extensions/services/proxy/evol/server/proxy_config.py +15 -0
  203. package/extensions/services/proxy/evol/server/proxy_engine.py +501 -0
  204. package/extensions/services/proxy/evol/version.py +24 -0
  205. package/extensions/services/proxy/logs/websocket.log +260 -0
  206. package/extensions/services/proxy/main.py +240 -0
  207. package/extensions/services/proxy/requirements.txt +13 -0
  208. package/extensions/services/proxy/server.py +271 -0
  209. package/extensions/services/watchdog/entry.py +42 -16
  210. package/extensions/services/watchdog/module.md +1 -0
  211. package/extensions/services/watchdog/monitor.py +34 -4
  212. package/extensions/services/web/module.md +1 -1
  213. package/extensions/services/web/server.py +30 -18
  214. package/extensions/services/web/static/js/token-manager.js +10 -10
  215. package/kernel/entry.py +1 -1
  216. package/kernel/module.md +25 -1
  217. package/kernel/registry_store.py +2 -26
  218. package/kernel/rpc_router.py +36 -10
  219. package/kernel/server.py +106 -17
  220. package/kite_cli/commands/deps_install.py +67 -0
  221. package/kite_cli/commands/env_check.py +45 -0
  222. package/kite_cli/commands/prepare.py +49 -0
  223. package/kite_cli/commands/venv_setup.py +56 -0
  224. package/kite_cli/main.py +29 -1
  225. package/launcher/entry.py +306 -21
  226. package/launcher/module.md +9 -0
  227. package/launcher/module_scanner.py +11 -1
  228. package/main.py +4 -1
  229. package/package.json +8 -1
  230. package/python_version.json +4 -0
  231. package/requirements.txt +38 -0
  232. package/scripts/env-manager.js +328 -0
  233. package/scripts/python-env.js +79 -0
  234. package/scripts/scan_dependencies.py +461 -0
  235. 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()