@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,15 @@
1
+ """代理配置 - 封装不同代理的差异点"""
2
+ from dataclasses import dataclass
3
+ from typing import Callable, Any
4
+
5
+
6
+ @dataclass
7
+ class ProxyConfig:
8
+ """代理配置 - 封装两个代理的差异点"""
9
+ target_aid_getter: Callable[[str], str] # 获取目标AID: api_key -> aid
10
+ request_converter: Callable[[dict], dict] # 请求格式转换
11
+ response_converter: Callable[[dict], dict] # 响应格式转换
12
+ stream_handler: Callable # 流式响应处理
13
+ error_formatter: Callable[[Exception], Any] # 错误格式化
14
+ model_validator: Callable[[str], bool] # 模型验证
15
+ proxy_type: str # "claude" | "openclaw"
@@ -0,0 +1,501 @@
1
+ """
2
+ 统一代理引擎 - 处理所有共同业务逻辑
3
+
4
+ 注意事项:
5
+ 1. 必须通过 claude_proxy_async 模块访问全局变量(agentId, async_session_manager 等),
6
+ 不能用 from ... import 导入,因为那样会拿到导入时刻的快照值,后续重建后不会更新。
7
+ 2. 路径前缀剥离必须覆盖所有代理类型。
8
+ 3. 错误响应必须保留上游返回的 http_status 和 headers,不能自作主张改为 500。
9
+ 4. response_converter 只应用于非流式的普通成功响应,且仅在响应体本身是有效 dict 时才转换。
10
+ 5. x-goog-api-key 是 Gemini 的 API Key header,必须支持。
11
+ """
12
+ import asyncio
13
+ import json
14
+ import time
15
+ import uuid
16
+ import traceback
17
+ from datetime import datetime
18
+ from typing import Optional
19
+ from fastapi import Request, HTTPException
20
+ from fastapi.responses import Response
21
+ from .proxy_config import ProxyConfig
22
+
23
+
24
+ # 所有需要剥离的路径前缀
25
+ _PATH_PREFIXES_TO_STRIP = [
26
+ "/claude-proxy",
27
+ "/codex-proxy",
28
+ "/gemini-proxy",
29
+ "/openclaw-proxy",
30
+ ]
31
+
32
+
33
+ def _strip_proxy_prefix(path: str) -> str:
34
+ """剥离路径中的代理前缀"""
35
+ for prefix in _PATH_PREFIXES_TO_STRIP:
36
+ if path.startswith(prefix):
37
+ return path[len(prefix):]
38
+ return path
39
+
40
+
41
+ async def proxy_engine(
42
+ request: Request,
43
+ config: ProxyConfig,
44
+ x_api_key: Optional[str] = None
45
+ ):
46
+ """
47
+ 统一的代理引擎 - 处理所有共同业务逻辑
48
+
49
+ 核心流程:
50
+ 1. API Key 提取
51
+ 2. 用户验证 + 积分预扣除
52
+ 3. 读取并解析请求体 + 模型验证 + 请求转换
53
+ 4. 构建代理消息
54
+ 5. AgentID 在线检查 + 重建
55
+ 6. 会话管理(获取/创建/重试)
56
+ 7. 消息发送 + 响应等待(含超时重建重试)
57
+ 8. 响应处理(错误 / 流式 / 普通)
58
+ 9. 积分确认或释放
59
+ """
60
+ # ✅ 关键:通过模块引用访问全局变量,而非 from import(避免快照问题)
61
+ import evol.server.claude_proxy_async as _claude_mod
62
+ from ..presenter.agentIdPresenter import evol_agentId_online
63
+ from ..version import __version__, __cmp_version__
64
+
65
+ # 初始化变量
66
+ reserved_credits = 0
67
+ user_id = None
68
+ request_id = str(uuid.uuid4())
69
+ request_success = False
70
+ perf_start = time.time()
71
+ perf_timings = {}
72
+ proxy_logger = _claude_mod.get_proxy_logger()
73
+
74
+ # 检查 AsyncSessionManager 是否已初始化
75
+ if _claude_mod.async_session_manager is None:
76
+ raise HTTPException(status_code=503, detail="AsyncSessionManager 未初始化,请稍后重试")
77
+
78
+ try:
79
+ request_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
80
+ client_ip = request.client.host if request.client else "unknown"
81
+
82
+ proxy_logger.info("=" * 80)
83
+ proxy_logger.info(f"{config.proxy_type.upper()} 代理请求开始 - {request.method} {request.url.path}")
84
+ proxy_logger.info(f"客户端IP: {client_ip}, 请求时间: {request_time}, 追踪ID: {request_id}")
85
+
86
+ # ========== 1. API Key 提取 ==========
87
+ # 兼容 x-api-key / Authorization: Bearer / x-goog-api-key (Gemini)
88
+ headers_dict = {}
89
+ skip_headers = {'host', 'content-length'}
90
+ for key, value in request.headers.items():
91
+ if key.lower() not in skip_headers:
92
+ headers_dict[key] = value
93
+
94
+ if not x_api_key:
95
+ x_api_key = request.headers.get("x-api-key")
96
+
97
+ if not x_api_key:
98
+ auth_header = request.headers.get("authorization", "")
99
+ if auth_header.startswith("Bearer "):
100
+ x_api_key = auth_header[7:]
101
+
102
+ if not x_api_key:
103
+ x_api_key = headers_dict.get("x-goog-api-key")
104
+
105
+ proxy_logger.info(f"API Key: {x_api_key[:20] if x_api_key else 'None'}... (已脱敏)")
106
+
107
+ # ========== 2. 积分余额检查 + 预扣除 ==========
108
+ try:
109
+ step_start = time.time()
110
+ user_info = await asyncio.to_thread(_claude_mod.userPresenter.get_user_info)
111
+ perf_timings['get_user_info'] = (time.time() - step_start) * 1000
112
+
113
+ if user_info.get("status") != "success":
114
+ raise HTTPException(status_code=401, detail="无法获取用户信息")
115
+
116
+ user_id = user_info["user_info"].get("user_id")
117
+ if not user_id:
118
+ raise HTTPException(status_code=401, detail="用户ID无效")
119
+
120
+ step_start = time.time()
121
+ required_credits = 20
122
+ reserve_result = await _claude_mod.credits_lock_manager.check_and_reserve_credits(
123
+ user_id=user_id,
124
+ required_amount=required_credits,
125
+ balance_fetcher=None,
126
+ request_id=request_id
127
+ )
128
+ perf_timings['credits_reserve'] = (time.time() - step_start) * 1000
129
+
130
+ # 降级兜底:积分服务失败时不阻塞请求
131
+ if not reserve_result["success"]:
132
+ reserve_result["success"] = True
133
+ reserve_result["balance"] = 10000
134
+ reserve_result['available'] = 10000
135
+
136
+ if not reserve_result["success"]:
137
+ error_msg = reserve_result.get("error", "积分检查失败")
138
+ balance = reserve_result.get("balance", 0)
139
+ available = reserve_result.get("available", 0)
140
+
141
+ if "获取余额失败" in error_msg or "API返回错误" in error_msg:
142
+ raise HTTPException(status_code=503, detail=f"积分余额服务不可用,请稍后重试: {error_msg}")
143
+ elif balance == 0:
144
+ raise HTTPException(status_code=503, detail="积分余额为0,无法继续使用")
145
+ elif "不足" in error_msg:
146
+ raise HTTPException(status_code=503, detail=f"积分不足: 余额={balance}, 可用={available}, 需要={required_credits}")
147
+ else:
148
+ raise HTTPException(status_code=503, detail=f"积分余额检查失败: {error_msg}")
149
+
150
+ reserved_credits = required_credits
151
+ proxy_logger.info(f"✅ 积分预扣除成功: {required_credits}")
152
+
153
+ except HTTPException:
154
+ raise
155
+ except Exception as e:
156
+ proxy_logger.error(f"❌ 积分余额检查异常: {str(e)}")
157
+ raise HTTPException(status_code=503, detail=f"积分余额检查服务异常,请稍后重试: {str(e)}")
158
+
159
+ # ========== 3. 读取请求体 + 解析 + 模型验证 ==========
160
+ step_start = time.time()
161
+ request_body = await request.body()
162
+ perf_timings['read_body'] = (time.time() - step_start) * 1000
163
+ proxy_logger.info(f"请求体大小: {len(request_body)} bytes")
164
+
165
+ step_start = time.time()
166
+ trace_id = str(uuid.uuid4())
167
+ bodyjson = await asyncio.to_thread(json.loads, request_body.decode('utf-8'))
168
+ perf_timings['json_parse'] = (time.time() - step_start) * 1000
169
+
170
+ # 根据请求路径做模型校验(与原逻辑一致)
171
+ request_path = str(request.url.path)
172
+ model_name = bodyjson.get("model", "")
173
+
174
+ if "/claude-proxy/" in request_path:
175
+ if model_name and "claude" not in model_name.lower():
176
+ raise HTTPException(status_code=400, detail=f"不支持该模型 {model_name},请使用 Claude CLI 内置模型")
177
+ elif "/codex-proxy/" in request_path:
178
+ if model_name and "gpt" not in model_name.lower():
179
+ raise HTTPException(status_code=400, detail=f"不支持该模型 {model_name},请使用 Codex CLI 内置模型")
180
+ elif "/gemini-proxy/" in request_path:
181
+ if model_name and "gemini" not in model_name.lower():
182
+ raise HTTPException(status_code=400, detail=f"不支持该模型 {model_name},请使用 Gemini CLI 内置模型")
183
+ else:
184
+ # 其他代理类型使用 config 的模型验证器
185
+ if not config.model_validator(model_name):
186
+ raise HTTPException(status_code=400, detail=f"不支持该模型: {model_name}")
187
+
188
+ # 请求格式转换(Claude/Codex/Gemini 不转换,OpenClaw 转换 OpenAI→Claude)
189
+ converted_body = config.request_converter(bodyjson)
190
+
191
+ # ========== 4. 构建代理消息 ==========
192
+ proxy_message = {
193
+ "type": "claude_proxy",
194
+ "status": "success",
195
+ "timestamp": int(time.time() * 1000),
196
+ "trace_id": trace_id,
197
+ "content": {
198
+ "path": _strip_proxy_prefix(request_path),
199
+ "method": request.method,
200
+ "headers": headers_dict,
201
+ "body": converted_body,
202
+ "source": "evol",
203
+ "version": __version__,
204
+ "cmp_version": __cmp_version__
205
+ }
206
+ }
207
+
208
+ proxy_logger.info(f"代理消息构建完成,trace_id: {trace_id}")
209
+ proxy_logger.info(f"请求路径: {request_path}")
210
+
211
+ # ========== 5. 检查用户登录 ==========
212
+ if not _claude_mod.userPresenter.is_logged_in():
213
+ raise HTTPException(status_code=401, detail="用户未登录,请打开 Evol 登录")
214
+
215
+ # ========== 6. 检查 AgentID 是否在线 ==========
216
+ if _claude_mod.agentId is None or not _claude_mod.agentId.is_online_success:
217
+ proxy_logger.warning("AgentID 未连接,执行完全重建...")
218
+
219
+ time_since_last = time.time() - _claude_mod._last_full_agentcp_rebuild_time
220
+ if time_since_last < _claude_mod._full_agentcp_rebuild_cooldown:
221
+ remaining = (_claude_mod._full_agentcp_rebuild_cooldown - time_since_last) / 60
222
+ raise HTTPException(
223
+ status_code=503,
224
+ detail=f"连接异常,系统正在恢复中,请 {int(remaining)+1} 分钟后重试,或重启Evol"
225
+ )
226
+
227
+ rebuild_success = await asyncio.to_thread(_claude_mod._full_rebuild_agentcp_system)
228
+
229
+ if not rebuild_success:
230
+ raise HTTPException(status_code=503, detail="Service Unavailable: 连接恢复失败,请尝试重启Evol")
231
+
232
+ # 重建后重新检查
233
+ if _claude_mod.agentId is None or not _claude_mod.agentId.is_online_success:
234
+ raise HTTPException(status_code=503, detail="Service Unavailable: 连接恢复异常,请尝试重启Evol")
235
+
236
+ proxy_logger.info("AgentID 完全重建成功,继续处理请求")
237
+
238
+ # ========== 7. AgentID 在线,开始处理请求 ==========
239
+ if _claude_mod.agentId and _claude_mod.agentId.is_online_success:
240
+ proxy_logger.info("Agent在线,开始处理请求")
241
+
242
+ session_id = None
243
+ max_session_retries = 2
244
+
245
+ for session_attempt in range(max_session_retries):
246
+ step_start = time.time()
247
+ proxy_logger.info(f"获取或创建session (尝试 {session_attempt + 1}/{max_session_retries})")
248
+ session_id = await _claude_mod.async_session_manager.get_session(x_api_key, _claude_mod.agentId)
249
+ perf_timings['get_session'] = (time.time() - step_start) * 1000
250
+
251
+ if session_id:
252
+ break
253
+ elif session_attempt < max_session_retries - 1:
254
+ proxy_logger.warning("Session创建失败,尝试重新上线...")
255
+ try:
256
+ _claude_mod._clear_async_session_manager_cache()
257
+ await asyncio.to_thread(_claude_mod.agentId.online)
258
+
259
+ if _claude_mod.agentId.is_online_success:
260
+ evol_agentId_online(_claude_mod.agentId)
261
+ _claude_mod._register_disconnect_callback(_claude_mod.agentId, disable_auto_reconnect=True)
262
+ else:
263
+ proxy_logger.error("agentId.online() 后仍未上线")
264
+ break
265
+ except Exception as retry_error:
266
+ proxy_logger.error(f"重新上线过程异常: {str(retry_error)}")
267
+ break
268
+
269
+ if session_id:
270
+ proxy_logger.info(f"Session获取成功: {session_id}")
271
+
272
+ # 注册响应事件
273
+ response_event = asyncio.Event()
274
+ _claude_mod.async_session_manager._pending_requests[trace_id] = response_event
275
+ _claude_mod.async_session_manager.register_request_timestamp(trace_id)
276
+
277
+ try:
278
+ # 发送消息
279
+ claude_agent_name = await asyncio.to_thread(_claude_mod.configPresenter.get_claude_agent_name)
280
+ proxy_logger.info(f"发送消息到Agent: {claude_agent_name}")
281
+
282
+ step_start = time.time()
283
+ await asyncio.to_thread(
284
+ _claude_mod.agentId.send_message, session_id, [claude_agent_name], proxy_message
285
+ )
286
+ perf_timings['send_message'] = (time.time() - step_start) * 1000
287
+
288
+ # 等待响应
289
+ proxy_logger.info(f"等待响应, trace_id: {trace_id}")
290
+ wait_start = time.time()
291
+
292
+ try:
293
+ await asyncio.wait_for(response_event.wait(), timeout=300)
294
+ perf_timings['wait_response'] = (time.time() - wait_start) * 1000
295
+ proxy_logger.info(f"收到响应,等待时间: {perf_timings['wait_response']:.2f}ms")
296
+ _claude_mod._reset_no_response_count()
297
+ except asyncio.TimeoutError:
298
+ no_response_count = _claude_mod._increment_no_response_count()
299
+ proxy_logger.warning(f"请求超时,连续无响应: {no_response_count}/{_claude_mod._consecutive_no_response_threshold}")
300
+
301
+ if _claude_mod._should_trigger_rebuild():
302
+ try:
303
+ rebuild_result = await _claude_mod.force_rebuild_agentcp_system(bypass_cooldown=True)
304
+ _claude_mod._reset_no_response_count()
305
+
306
+ if rebuild_result["success"]:
307
+ if _claude_mod.agentId is None or not _claude_mod.agentId.is_online_success:
308
+ raise HTTPException(status_code=503, detail="连接重建后仍然离线,请稍后重试")
309
+
310
+ # 重试请求
311
+ retry_trace_id = f"retry_{trace_id}"
312
+ if isinstance(proxy_message.get("content"), dict):
313
+ proxy_message["content"]["trace_id"] = retry_trace_id
314
+ proxy_message["trace_id"] = retry_trace_id
315
+
316
+ retry_response_event = asyncio.Event()
317
+ _claude_mod.async_session_manager._pending_requests[retry_trace_id] = retry_response_event
318
+ _claude_mod.async_session_manager.register_request_timestamp(retry_trace_id)
319
+
320
+ try:
321
+ claude_agent_name = await asyncio.to_thread(_claude_mod.configPresenter.get_claude_agent_name)
322
+ await asyncio.to_thread(
323
+ _claude_mod.agentId.send_message, session_id, [claude_agent_name], proxy_message
324
+ )
325
+
326
+ try:
327
+ await asyncio.wait_for(retry_response_event.wait(), timeout=300)
328
+ except asyncio.TimeoutError:
329
+ raise HTTPException(status_code=504, detail="自动重建并重试后仍然超时,请检查网络连接或联系技术支持")
330
+
331
+ retry_result_data = _claude_mod.async_session_manager._request_result_map.get(retry_trace_id)
332
+ if retry_result_data:
333
+ _claude_mod.async_session_manager._request_result_map[trace_id] = retry_result_data
334
+ perf_timings['wait_response'] = (time.time() - wait_start) * 1000
335
+ perf_timings['auto_rebuild_retry'] = True
336
+ else:
337
+ raise HTTPException(status_code=503, detail="自动重试后仍未收到响应数据,请稍后重试")
338
+ finally:
339
+ _claude_mod.async_session_manager._pending_requests.pop(retry_trace_id, None)
340
+ _claude_mod.async_session_manager._request_result_map.pop(retry_trace_id, None)
341
+ _claude_mod.async_session_manager.unregister_request_timestamp(retry_trace_id)
342
+ else:
343
+ raise HTTPException(status_code=503, detail=f"连接自动重建失败: {rebuild_result['message']},请尝试重启 Evol")
344
+
345
+ except HTTPException:
346
+ raise
347
+ except Exception as rebuild_error:
348
+ raise HTTPException(status_code=503, detail=f"连接自动重建异常: {str(rebuild_error)},请尝试重启 Evol")
349
+ else:
350
+ raise HTTPException(
351
+ status_code=504,
352
+ detail=f"请求超时,请重试(连续超时 {no_response_count}/{_claude_mod._consecutive_no_response_threshold} 次)"
353
+ )
354
+
355
+ # ========== 8. 响应处理 ==========
356
+ result_data = _claude_mod.async_session_manager._request_result_map.get(trace_id)
357
+ if not result_data:
358
+ proxy_logger.error(f"未收到响应数据,trace_id: {trace_id}")
359
+ raise HTTPException(status_code=503, detail="No response received")
360
+
361
+ result_type = result_data.get("result_type", "")
362
+ response_msg = result_data.get("result", {})
363
+ proxy_logger.info(f"收到响应,类型: {result_type}, trace_id: {trace_id}")
364
+
365
+ content = response_msg.get("content", {}) if isinstance(response_msg, dict) else response_msg
366
+
367
+ # 8.1 处理错误响应 - 保留上游的 http_status 和 headers
368
+ if result_type == "error":
369
+ error_message = content if isinstance(content, str) else str(content)
370
+ http_status = response_msg.get("http_status", 503) if isinstance(response_msg, dict) else 503
371
+ resp_headers = response_msg.get("headers", {}) if isinstance(response_msg, dict) else {}
372
+ proxy_logger.error(f"代理返回错误: {error_message}, trace_id: {trace_id}")
373
+ return Response(
374
+ content=error_message,
375
+ status_code=http_status,
376
+ headers=resp_headers,
377
+ media_type=resp_headers.get('content-type', "application/json")
378
+ )
379
+
380
+ # 8.2 处理流式响应
381
+ if result_type == "text/event-stream":
382
+ stream_url = content if isinstance(content, str) else content.get("url", "")
383
+ if stream_url:
384
+ proxy_logger.info(f"开始流式响应: {stream_url}, trace_id: {trace_id}")
385
+ request_success = True
386
+ return await config.stream_handler(stream_url)
387
+ else:
388
+ proxy_logger.error(f"流式响应缺少URL, trace_id: {trace_id}")
389
+ raise HTTPException(status_code=504, detail="Stream URL missing")
390
+
391
+ # 8.3 处理普通成功响应
392
+ proxy_logger.info("处理普通成功响应")
393
+ if isinstance(content, dict):
394
+ status_code = content.get("status_code", 200)
395
+ response_headers = content.get("headers", {})
396
+ response_body = content.get("body", "")
397
+
398
+ # 移除会导致客户端解析失败的 headers
399
+ for header in ['Content-Encoding', 'Transfer-Encoding', 'Content-Length']:
400
+ response_headers.pop(header, None)
401
+ else:
402
+ status_code = 200
403
+ response_headers = {"Content-Type": "application/json"}
404
+ response_body = json.dumps(content) if not isinstance(content, str) else content
405
+
406
+ # 响应格式转换(仅当 response_body 是 dict 时才交给 converter)
407
+ if isinstance(response_body, dict):
408
+ response_body = config.response_converter(response_body)
409
+
410
+ # 确保 response_body 是可编码的类型
411
+ if isinstance(response_body, dict):
412
+ response_body = json.dumps(response_body, ensure_ascii=False)
413
+ elif isinstance(response_body, (list, tuple)):
414
+ response_body = json.dumps(response_body, ensure_ascii=False)
415
+ elif not isinstance(response_body, (str, bytes)):
416
+ response_body = str(response_body)
417
+
418
+ real_response = response_body if isinstance(response_body, bytes) else response_body.encode('utf-8')
419
+
420
+ # 性能统计
421
+ total_time = (time.time() - perf_start) * 1000
422
+ perf_timings['total'] = total_time
423
+
424
+ proxy_logger.info("=" * 80)
425
+ proxy_logger.info(f"代理请求处理完成 - 总耗时: {total_time:.2f}ms")
426
+ for step_name, duration in perf_timings.items():
427
+ if step_name != 'total' and isinstance(duration, (int, float)):
428
+ percentage = (duration / total_time * 100) if total_time > 0 else 0
429
+ proxy_logger.info(f" - {step_name}: {duration:.2f}ms ({percentage:.1f}%)")
430
+ proxy_logger.info("=" * 80)
431
+
432
+ request_success = True
433
+
434
+ return Response(
435
+ content=real_response,
436
+ status_code=status_code,
437
+ headers=response_headers,
438
+ media_type=response_headers.get('Content-Type', "application/json")
439
+ )
440
+
441
+ except asyncio.TimeoutError:
442
+ proxy_logger.error(f"代理请求超时: {trace_id}")
443
+ raise HTTPException(status_code=504, detail="Gateway Timeout")
444
+ except HTTPException:
445
+ raise
446
+ except Exception as e:
447
+ error_details = {
448
+ "error_type": type(e).__name__,
449
+ "error_message": str(e),
450
+ "trace_id": trace_id,
451
+ }
452
+ full_traceback = traceback.format_exc()
453
+ proxy_logger.error(f"等待代理响应失败: {str(e)}")
454
+ proxy_logger.error(f"错误详情: {error_details}")
455
+ proxy_logger.error(f"完整堆栈跟踪:\n{full_traceback}")
456
+ raise HTTPException(status_code=500, detail=f"Proxy Error: {str(e)}")
457
+ finally:
458
+ # ✅ 延迟清理 trace_id,避免竞态条件
459
+ try:
460
+ await asyncio.sleep(0.1)
461
+ except asyncio.CancelledError:
462
+ pass
463
+ _claude_mod.async_session_manager._pending_requests.pop(trace_id, None)
464
+ _claude_mod.async_session_manager._request_result_map.pop(trace_id, None)
465
+ _claude_mod.async_session_manager.unregister_request_timestamp(trace_id)
466
+
467
+ else:
468
+ # 循环重试后仍然失败
469
+ proxy_logger.error("获取或创建代理会话失败(已重试)")
470
+ raise HTTPException(status_code=503, detail="Service Unavailable: Cannot get or create session")
471
+ else:
472
+ proxy_logger.error("AgentID 未连接(兜底)")
473
+ raise HTTPException(status_code=503, detail="Service Unavailable: 连接失败,请尝试重新启动 Evol")
474
+
475
+ except HTTPException:
476
+ raise
477
+ except Exception as e:
478
+ full_traceback = traceback.format_exc()
479
+ proxy_logger.error(f"代理请求处理失败: {str(e)}")
480
+ proxy_logger.error(f"异常堆栈:\n{full_traceback}")
481
+ raise HTTPException(status_code=500, detail=f"Proxy Error: {str(e)}")
482
+ finally:
483
+ # ========== 9. 积分预扣除的释放或确认 ==========
484
+ if reserved_credits > 0 and user_id:
485
+ try:
486
+ if request_success:
487
+ await _claude_mod.credits_lock_manager.confirm_credits_usage(
488
+ user_id=user_id,
489
+ amount=reserved_credits,
490
+ request_id=request_id
491
+ )
492
+ proxy_logger.info(f"✅ 积分使用已确认: {reserved_credits}")
493
+ else:
494
+ await _claude_mod.credits_lock_manager.release_reserved_credits(
495
+ user_id=user_id,
496
+ amount=reserved_credits,
497
+ request_id=request_id
498
+ )
499
+ proxy_logger.info(f"🔄 积分预扣除已释放: {reserved_credits}")
500
+ except Exception as e:
501
+ proxy_logger.error(f"❌ 积分处理失败: {str(e)}")
@@ -0,0 +1,24 @@
1
+ # 应用版本配置
2
+ __version__ = "0.0.30" # 与package.json保持一致
3
+ __code__ = 135
4
+ __cmp_version__ = "2.0.43"
5
+ APP_NAME = "Evol"
6
+
7
+ def get_version():
8
+ """获取当前版本"""
9
+ return __version__
10
+
11
+ def get_version_info():
12
+ """获取详细版本信息"""
13
+ import sys
14
+ import platform
15
+
16
+ return {
17
+ "version": __version__,
18
+ "cmp_version": __cmp_version__,
19
+ "code": __code__,
20
+ "app_name": APP_NAME,
21
+ "python_version": sys.version,
22
+ "platform": platform.platform(),
23
+ "architecture": platform.machine()
24
+ }