@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,1281 @@
1
+ import json
2
+ import os
3
+ import threading
4
+ import time
5
+
6
+ from ..log import logger
7
+
8
+ class configPresenter:
9
+ _lock = threading.Lock()
10
+ _config_dir_initialized = False
11
+ searchPreviewEnabled = False
12
+ userName = "default"
13
+ KEY_CLAUDE_AGENT_NAME = "claude_agent_name" # Claude代理Agent名称
14
+ KEY_GEMINI_AGENT_NAME = "gemini_agent_name" # Gemini代理Agent名称
15
+
16
+ @staticmethod
17
+ def get_config_dir():
18
+ """获取配置文件目录,确保PyInstaller打包后也能正常工作"""
19
+ import platform
20
+
21
+ if platform.system() == "Windows":
22
+ app_path = os.path.join(os.getenv("APPDATA", "~"), "evol",configPresenter.userName)
23
+ else:
24
+ home_dir = os.path.expanduser("~")
25
+ if os.access(home_dir, os.W_OK):
26
+ app_path = os.path.join(home_dir, ".evol",configPresenter.userName)
27
+
28
+ else:
29
+ import tempfile
30
+ app_path = os.path.join(tempfile.gettempdir(), "evol",configPresenter.userName)
31
+ logger.debug(f"using config dir: {app_path}")
32
+ return app_path
33
+
34
+ @staticmethod
35
+ def ensure_config_dir():
36
+ """确保配置目录存在,只在需要时创建一次"""
37
+ if not configPresenter._config_dir_initialized:
38
+ config_dir = configPresenter.get_config_dir()
39
+ try:
40
+ os.makedirs(config_dir, exist_ok=True)
41
+ configPresenter._config_dir_initialized = True
42
+ except Exception as e:
43
+ print(f"创建配置目录失败: {e}")
44
+ # 降级到临时目录
45
+ import tempfile
46
+
47
+ config_dir = tempfile.gettempdir()
48
+ return True
49
+
50
+ @staticmethod
51
+ def get_claude_agent_name():
52
+ """获取Claude智能体名称"""
53
+ """获取Claude代理Agent名称,如果未设置则返回默认值"""
54
+ #return "claude_code_proxy.aid.pub"
55
+ agent_name = configPresenter.getSetting(configPresenter.KEY_CLAUDE_AGENT_NAME)
56
+ if agent_name is None:
57
+ return "claude_code_proxy_1.aid.pub" # 默认值
58
+ #return "claude_code_proxy.aid.pub"
59
+ return agent_name
60
+
61
+ @staticmethod
62
+ def get_gemini_agent_name():
63
+ """获取Gemini智能体名称"""
64
+ """获取Gemini代理Agent名称,如果未设置则返回默认值"""
65
+ agent_name = configPresenter.getSetting(configPresenter.KEY_GEMINI_AGENT_NAME)
66
+ if agent_name is None:
67
+ return "gemini_proxy.aid.pub" # 默认值
68
+ return agent_name
69
+
70
+
71
+ @staticmethod
72
+ def get_config_file_path(workspaceName = ""):
73
+ """获取配置文件的完整路径"""
74
+ if not workspaceName:
75
+ configPresenter.ensure_config_dir() # 确保目录存在
76
+ config_dir = configPresenter.get_config_dir()
77
+ return os.path.join(config_dir, "key_value_store.json")
78
+ else:
79
+ from evol.presenter.workPresenter import workPresenter
80
+ workspace = workPresenter.get_evol_workspace_path(workspaceName)
81
+ return os.path.join(workspace, "key_value_store.json")
82
+
83
+ @staticmethod
84
+ def getSetting(key,workspaceName = ""):
85
+ """
86
+ 从本地文件读取指定键的值
87
+ """
88
+ filepath = configPresenter.get_config_file_path(workspaceName)
89
+ try:
90
+ with open(filepath, "r", encoding="utf-8") as f:
91
+ content = f.read().strip()
92
+ if not content: # 如果文件为空
93
+ return None
94
+ data = json.loads(content)
95
+ value = data.get(key)
96
+ return value
97
+ except FileNotFoundError:
98
+ return None
99
+ except json.JSONDecodeError as e:
100
+ logger.error(f"JSON格式错误: {e}")
101
+ # 如果JSON格式错误,重新创建一个空的配置文件
102
+ try:
103
+ with open(filepath, "w", encoding="utf-8") as f:
104
+ json.dump({}, f, indent=4, ensure_ascii=False)
105
+ logger.info("已重新创建配置文件")
106
+ except Exception as write_error:
107
+ logger.error(f"重新创建配置文件失败: {write_error}")
108
+ return None
109
+ except Exception as e:
110
+ logger.exception(f"读取设置时出错: {e}")
111
+ return None
112
+
113
+ @staticmethod
114
+ def setSetting(key, value,workspaceName = ""):
115
+ """
116
+ 将键值对存储到本地文件(线程安全版本)
117
+ """
118
+ logger.info(f"开始保存设置: {key} = {value}")
119
+ try:
120
+ with configPresenter._lock: # 使用线程锁保护临界区
121
+ filepath = configPresenter.get_config_file_path(workspaceName)
122
+ if not os.path.exists(filepath):
123
+ with open(filepath, "w", encoding="utf-8") as f:
124
+ json.dump({}, f, indent=4, ensure_ascii=False)
125
+
126
+ # 读取现有数据
127
+ try:
128
+ with open(filepath, "r", encoding="utf-8") as f:
129
+ data = json.load(f)
130
+ except (json.JSONDecodeError, FileNotFoundError):
131
+ data = {}
132
+
133
+ # 更新数据
134
+ old_value = data.get(key, "未设置")
135
+ data[key] = value
136
+
137
+ # 写回文件
138
+ with open(filepath, "w", encoding="utf-8") as f:
139
+ json.dump(data, f, indent=4, ensure_ascii=False)
140
+ except Exception as e:
141
+ logger.exception(f"保存设置时出错: {e}")
142
+ return False
143
+ return True
144
+
145
+ @staticmethod
146
+ def setCurrentModelInfo(workspaceName,modelId,providerId):
147
+ configPresenter.setSetting("currentModel", modelId,workspaceName)
148
+ configPresenter.setSetting("currentProvider", providerId,workspaceName)
149
+
150
+ @staticmethod
151
+ def getCurrentModelInfo(workspaceName):
152
+ currentModel = configPresenter.getSetting("currentModel",workspaceName)
153
+ currentProvider = configPresenter.getSetting("currentProvider",workspaceName)
154
+ return {"modelId": currentModel, "providerId": currentProvider}
155
+
156
+ @staticmethod
157
+ def setProviderById(key,value):
158
+ pass
159
+
160
+ @staticmethod
161
+ def deleteProviders(id):
162
+ pass
163
+
164
+ @staticmethod
165
+ def addProviders(value):
166
+ pass
167
+
168
+ @staticmethod
169
+ def get_fsss2_file_path():
170
+ """获取fsss2文件的完整路径(用于存储token)"""
171
+ configPresenter.ensure_config_dir()
172
+ config_dir = configPresenter.get_config_dir()
173
+ return os.path.join(config_dir, "fsss2")
174
+
175
+ @staticmethod
176
+ def get_token():
177
+ """
178
+ 获取token(优先从fsss2文件读取)
179
+ 读取逻辑:
180
+ 1. 先读取fsss2文件
181
+ 2. 如果没读取到,再读取原来位置的token
182
+ 3. 如果原位置有token,写入fsss2文件再返回
183
+ """
184
+ fsss2_path = configPresenter.get_fsss2_file_path()
185
+
186
+ # 1. 先尝试从fsss2文件读取
187
+ try:
188
+ with open(fsss2_path, "r", encoding="utf-8") as f:
189
+ token = f.read().strip()
190
+ if token:
191
+ return token
192
+ except FileNotFoundError:
193
+ pass
194
+ except Exception as e:
195
+ logger.error(f"读取fsss2文件时出错: {e}")
196
+
197
+ # 2. fsss2没读到,尝试从原位置读取
198
+ old_token = configPresenter.getSetting("token")
199
+ if old_token:
200
+ # 3. 原位置有token,写入fsss2文件
201
+ try:
202
+ with open(fsss2_path, "w", encoding="utf-8") as f:
203
+ f.write(old_token)
204
+ logger.info("已将token从原位置迁移到fsss2文件")
205
+ except Exception as e:
206
+ logger.error(f"迁移token到fsss2文件时出错: {e}")
207
+ return old_token
208
+
209
+ return None
210
+
211
+ @staticmethod
212
+ def set_token(token: str):
213
+ """
214
+ 设置token(写入fsss2文件)
215
+ 如果token为空或None,删除fsss2文件以清除token
216
+ """
217
+ fsss2_path = configPresenter.get_fsss2_file_path()
218
+
219
+ # 如果token为空,删除文件以清除token
220
+ if not token:
221
+ try:
222
+ if os.path.exists(fsss2_path):
223
+ os.remove(fsss2_path)
224
+ logger.info("token已清除(fsss2文件已删除)")
225
+ return True
226
+ except Exception as e:
227
+ logger.exception(f"清除token时出错: {e}")
228
+ return False
229
+
230
+ try:
231
+ with open(fsss2_path, "w", encoding="utf-8") as f:
232
+ f.write(token)
233
+ logger.info("token已保存到fsss2文件")
234
+ return True
235
+ except Exception as e:
236
+ logger.exception(f"保存token到fsss2文件时出错: {e}")
237
+ return False
238
+
239
+ @staticmethod
240
+ def set_account_info(username: str, password: str):
241
+ account_info = {"username": username, "password": password}
242
+ return configPresenter.setSetting("account_info", json.dumps(account_info))
243
+
244
+ @staticmethod
245
+ def get_account_info():
246
+ try:
247
+ user_info = configPresenter.getSetting("account_info")
248
+ if user_info is None:
249
+ return None
250
+ return json.loads(user_info)
251
+ except:
252
+ return None
253
+
254
+ @staticmethod
255
+ def set_user_info(aid: str, password: str):
256
+ user_info = {"aid": aid, "password": password}
257
+ return configPresenter.setSetting("user_info", json.dumps(user_info))
258
+
259
+ @staticmethod
260
+ def get_user_info():
261
+ try:
262
+ user_info = configPresenter.getSetting("user_info")
263
+ if user_info is None:
264
+ return None
265
+ return json.loads(user_info)
266
+ except:
267
+ return None
268
+
269
+ @staticmethod
270
+ def getSearchPreviewEnabled():
271
+ return configPresenter.searchPreviewEnabled
272
+
273
+ @staticmethod
274
+ def getCustomSearchEngines():
275
+ return 'baidu'
276
+
277
+ @staticmethod
278
+ def getProviders():
279
+ return []
280
+
281
+ @staticmethod
282
+ def getDefaultProviders():
283
+ return ""
284
+
285
+ @staticmethod
286
+ def getLanguage():
287
+ return "zh-CN"
288
+
289
+ @staticmethod
290
+ def getModelDefaultConfig(modal):
291
+ return "";
292
+
293
+ @staticmethod
294
+ def getArtifactsEffectEnabled():
295
+ return False
296
+
297
+ @staticmethod
298
+ def setProviderById(key,value):
299
+ pass
300
+
301
+ @staticmethod
302
+ def deleteProviders(id):
303
+ pass
304
+
305
+ @staticmethod
306
+ def addProviders(value):
307
+ pass
308
+
309
+ @staticmethod
310
+ def is_autostart_enabled():
311
+ return False
312
+
313
+ @staticmethod
314
+ def disable_autostart():
315
+ return False
316
+
317
+ @staticmethod
318
+ def enable_autostart():
319
+ return False
320
+
321
+ @staticmethod
322
+ def get_statistics_cache(workspace_name: str):
323
+ """
324
+ 获取工作空间统计数据缓存
325
+
326
+ Args:
327
+ workspace_name (str): 工作空间名称
328
+
329
+ Returns:
330
+ dict: 缓存数据,包含timestamp和data字段,如果没有缓存则返回None
331
+ """
332
+ cache_key = f"statistics_cache_{workspace_name}"
333
+ return configPresenter.getSetting(cache_key, workspace_name)
334
+
335
+ @staticmethod
336
+ def set_statistics_cache(workspace_name: str, statistics_data: dict):
337
+ """
338
+ 设置工作空间统计数据缓存
339
+
340
+ Args:
341
+ workspace_name (str): 工作空间名称
342
+ statistics_data (dict): 统计数据
343
+
344
+ Returns:
345
+ bool: 是否设置成功
346
+ """
347
+ cache_key = f"statistics_cache_{workspace_name}"
348
+ cache_data = {
349
+ "timestamp": time.time(),
350
+ "data": statistics_data
351
+ }
352
+ return configPresenter.setSetting(cache_key, cache_data, workspace_name)
353
+
354
+ @staticmethod
355
+ def is_statistics_cache_valid(workspace_name: str, cache_timeout: int = 300):
356
+ """
357
+ 检查统计数据缓存是否有效(默认5分钟过期)
358
+
359
+ Args:
360
+ workspace_name (str): 工作空间名称
361
+ cache_timeout (int): 缓存超时时间(秒),默认300秒(5分钟)
362
+
363
+ Returns:
364
+ bool: 缓存是否有效
365
+ """
366
+ cache_data = configPresenter.get_statistics_cache(workspace_name)
367
+ if not cache_data or not isinstance(cache_data, dict):
368
+ return False
369
+
370
+ timestamp = cache_data.get("timestamp")
371
+ if not timestamp:
372
+ return False
373
+
374
+ current_time = time.time()
375
+ return (current_time - timestamp) < cache_timeout
376
+
377
+ @staticmethod
378
+ def get_default_download_path():
379
+ """
380
+ 获取系统默认下载路径
381
+
382
+ Returns:
383
+ str: 默认下载路径
384
+ """
385
+ import platform
386
+
387
+ if platform.system() == "Windows":
388
+ # Windows系统下载路径
389
+ downloads_path = os.path.join(os.path.expanduser("~"), "Downloads")
390
+ elif platform.system() == "Darwin": # macOS
391
+ downloads_path = os.path.join(os.path.expanduser("~"), "Downloads")
392
+ else: # Linux
393
+ downloads_path = os.path.join(os.path.expanduser("~"), "Downloads")
394
+
395
+ # 确保路径存在
396
+ if not os.path.exists(downloads_path):
397
+ try:
398
+ os.makedirs(downloads_path, exist_ok=True)
399
+ except Exception as e:
400
+ logger.error(f"创建下载目录失败: {e}")
401
+ # 降级到用户主目录
402
+ downloads_path = os.path.expanduser("~")
403
+
404
+ return downloads_path
405
+
406
+ @staticmethod
407
+ def get_download_path():
408
+ """
409
+ 获取下载路径设置,如果没有设置则返回系统默认下载路径
410
+
411
+ Returns:
412
+ str: 下载路径
413
+ """
414
+ download_path = configPresenter.getSetting("download_path")
415
+ if not download_path:
416
+ # 如果没有设置,获取系统默认下载路径并保存
417
+ default_path = configPresenter.get_default_download_path()
418
+ configPresenter.set_download_path(default_path)
419
+ return default_path
420
+ return download_path
421
+
422
+ @staticmethod
423
+ def set_download_path(path: str):
424
+ """
425
+ 设置下载路径
426
+
427
+ Args:
428
+ path (str): 下载路径
429
+
430
+ Returns:
431
+ bool: 是否设置成功
432
+ """
433
+ return configPresenter.setSetting("download_path", path)
434
+
435
+ # ============ 默认工作空间路径配置 ============
436
+
437
+ @staticmethod
438
+ def get_default_workspace_path():
439
+ """
440
+ 获取默认工作空间路径
441
+
442
+ Returns:
443
+ str: 默认工作空间路径,如果未设置则返回None
444
+ """
445
+ return configPresenter.getSetting("default_workspace_path")
446
+
447
+ @staticmethod
448
+ def get_default_workspace_base_path():
449
+ """
450
+ 获取默认工作空间基础路径(系统默认路径)
451
+
452
+ Returns:
453
+ str: 默认工作空间基础路径
454
+ """
455
+ return os.path.join(configPresenter.get_config_dir(), "workspace")
456
+
457
+ @staticmethod
458
+ def set_default_workspace_path(path: str):
459
+ """
460
+ 设置默认工作空间路径
461
+
462
+ Args:
463
+ path (str): 默认工作空间路径
464
+
465
+ Returns:
466
+ bool: 是否设置成功
467
+ """
468
+ return configPresenter.setSetting("default_workspace_path", path)
469
+
470
+ # ============ AI 网关配置管理 ============
471
+
472
+ KEY_AI_GATEWAY_TYPE = "ai_gateway_type"
473
+ KEY_AI_GATEWAY_CONFIGS = "ai_gateway_configs"
474
+
475
+ # 默认网关配置
476
+ DEFAULT_GATEWAY_CONFIGS = {
477
+ "local": {
478
+ "type": "local",
479
+ "name": "本地网关",
480
+ "description": "使用本地 ModelGate 服务",
481
+ "baseUrl": "http://127.0.0.1:13148",
482
+ "apiVersion": "/v1",
483
+ "timeout": 30000,
484
+ "retryCount": 3,
485
+ "enabled": True
486
+ },
487
+ "cloud": {
488
+ "type": "cloud",
489
+ "name": "Evol 网关",
490
+ "description": "使用 Evol 云端服务",
491
+ # 兜底配置,实际云端地址会由 userPresenter 的网关信息覆盖
492
+ "baseUrl": "https://mg-new.evolai.cn",
493
+ "apiVersion": "/v1",
494
+ "timeout": 30000,
495
+ "retryCount": 3,
496
+ "enabled": True
497
+ }
498
+ }
499
+
500
+ @staticmethod
501
+ def get_gateway_type():
502
+ """
503
+ 获取当前网关类型
504
+
505
+ Returns:
506
+ str: 'local' 或 'cloud',默认 'cloud'
507
+ """
508
+ gateway_type = configPresenter.getSetting(configPresenter.KEY_AI_GATEWAY_TYPE)
509
+ if not gateway_type:
510
+ # 默认使用云端网关
511
+ configPresenter.set_gateway_type("cloud")
512
+ return "cloud"
513
+ return gateway_type
514
+
515
+ @staticmethod
516
+ def set_gateway_type(gateway_type: str):
517
+ """
518
+ 设置当前网关类型
519
+
520
+ Args:
521
+ gateway_type (str): 'local', 'cloud', 或 'custom-{id}'
522
+
523
+ Returns:
524
+ bool: 是否设置成功
525
+ """
526
+ # 验证网关类型
527
+ if gateway_type in ["local", "cloud"]:
528
+ # 预设网关,直接设置
529
+ logger.info(f"Setting gateway type to: {gateway_type}")
530
+ return configPresenter.setSetting(configPresenter.KEY_AI_GATEWAY_TYPE, gateway_type)
531
+ elif gateway_type.startswith("custom-"):
532
+ # 自定义网关,验证是否存在
533
+ gateway_id = gateway_type.replace("custom-", "")
534
+ custom_gateway = configPresenter.get_custom_gateway(gateway_id)
535
+ if custom_gateway:
536
+ logger.info(f"Setting gateway type to custom: {gateway_id}")
537
+ return configPresenter.setSetting(configPresenter.KEY_AI_GATEWAY_TYPE, gateway_type)
538
+ else:
539
+ logger.error(f"Custom gateway not found: {gateway_id}")
540
+ return False
541
+ else:
542
+ logger.error(f"Invalid gateway type: {gateway_type}")
543
+ return False
544
+
545
+ @staticmethod
546
+ def get_gateway_configs():
547
+ """
548
+ 获取所有网关配置
549
+
550
+ Returns:
551
+ dict: 所有网关配置
552
+ """
553
+ #configs = configPresenter.getSetting(configPresenter.KEY_AI_GATEWAY_CONFIGS)
554
+ #if not configs:
555
+ # 首次使用,保存默认配置
556
+ configPresenter.set_gateway_configs(configPresenter.DEFAULT_GATEWAY_CONFIGS)
557
+ return configPresenter.DEFAULT_GATEWAY_CONFIGS
558
+ #return configs
559
+
560
+ @staticmethod
561
+ def _apply_cloud_gateway_info(config: dict) -> dict:
562
+ if not isinstance(config, dict):
563
+ return config
564
+
565
+ try:
566
+ from .userPresenter import userPresenter
567
+
568
+ result = userPresenter.get_cloud_gateway_info()
569
+ gateway_info = None
570
+ if isinstance(result, dict):
571
+ gateway_info = result.get("gateway_info")
572
+
573
+ if isinstance(gateway_info, dict):
574
+ base_url = gateway_info.get("modelBaseUrl") or gateway_info.get("apiUrl")
575
+ if base_url:
576
+ updated = dict(config)
577
+ updated["baseUrl"] = base_url
578
+ return updated
579
+ except Exception:
580
+ return config
581
+
582
+ return config
583
+
584
+ @staticmethod
585
+ def set_gateway_configs(configs: dict):
586
+ """
587
+ 设置所有网关配置
588
+
589
+ Args:
590
+ configs (dict): 网关配置字典
591
+
592
+ Returns:
593
+ bool: 是否设置成功
594
+ """
595
+ return configPresenter.setSetting(configPresenter.KEY_AI_GATEWAY_CONFIGS, configs)
596
+
597
+ @staticmethod
598
+ def get_gateway_config(gateway_type: str = None):
599
+ """
600
+ 获取指定网关的配置(支持自定义网关)
601
+
602
+ Args:
603
+ gateway_type (str): 网关类型,不传则使用当前网关
604
+
605
+ Returns:
606
+ dict: 网关配置
607
+ """
608
+ if not gateway_type:
609
+ gateway_type = configPresenter.get_gateway_type()
610
+
611
+ # 检查是否为自定义网关
612
+ if gateway_type and gateway_type.startswith("custom-"):
613
+ gateway_id = gateway_type.replace("custom-", "")
614
+ custom_config = configPresenter.get_custom_gateway(gateway_id)
615
+ if custom_config:
616
+ logger.debug(f"Using custom gateway config: {gateway_id}")
617
+ return custom_config
618
+ else:
619
+ logger.warning(f"Custom gateway not found: {gateway_id}, using default cloud gateway")
620
+ return configPresenter.DEFAULT_GATEWAY_CONFIGS.get("cloud", {})
621
+
622
+ # 原有逻辑:本地或云端网关
623
+ configs = configPresenter.get_gateway_configs()
624
+ config = configs.get(gateway_type)
625
+
626
+ if not config:
627
+ logger.warning(f"Gateway config not found: {gateway_type}, using default")
628
+ return configPresenter.DEFAULT_GATEWAY_CONFIGS.get(gateway_type, {})
629
+
630
+ if gateway_type == "cloud":
631
+ return configPresenter._apply_cloud_gateway_info(config)
632
+
633
+ return config
634
+
635
+ @staticmethod
636
+ def get_current_gateway_base_url():
637
+ """
638
+ 获取当前网关的 Base URL
639
+
640
+ Returns:
641
+ str: 当前网关的基础 URL
642
+ """
643
+ config = configPresenter.get_gateway_config()
644
+ return config.get("baseUrl", "http://127.0.0.1:13148")
645
+
646
+ @staticmethod
647
+ def get_current_gateway_api_url():
648
+ """
649
+ 获取当前网关的 API URL(含版本)
650
+
651
+ Returns:
652
+ str: 当前网关的 API URL
653
+ """
654
+ config = configPresenter.get_gateway_config()
655
+ base_url = config.get("baseUrl", "http://127.0.0.1:13148")
656
+ api_version = config.get("apiVersion", "/v1")
657
+ return f"{base_url}{api_version}"
658
+
659
+ @staticmethod
660
+ def get_current_gateway_api_key():
661
+ """
662
+ 获取当前网关的 API Key
663
+
664
+ Returns:
665
+ str: 当前网关的 API Key,如果没有则返回空字符串
666
+ """
667
+ config = configPresenter.get_gateway_config()
668
+ return config.get("apiKey", "")
669
+
670
+ @staticmethod
671
+ def build_gateway_url(endpoint: str, include_version: bool = True, gateway_type: str = None):
672
+ """
673
+ 构建完整的网关端点 URL
674
+
675
+ Args:
676
+ endpoint (str): 端点路径
677
+ include_version (bool): 是否包含版本号
678
+ gateway_type (str): 网关类型,不传则使用当前网关
679
+
680
+ Returns:
681
+ str: 完整的 URL
682
+ """
683
+ config = configPresenter.get_gateway_config(gateway_type)
684
+ base_url = config.get("baseUrl", "http://127.0.0.1:13148")
685
+
686
+ if include_version:
687
+ api_version = config.get("apiVersion", "/v1")
688
+ base = f"{base_url}{api_version}"
689
+ else:
690
+ base = base_url
691
+
692
+ # 确保 endpoint 以 / 开头
693
+ if not endpoint.startswith("/"):
694
+ endpoint = f"/{endpoint}"
695
+
696
+ return f"{base}{endpoint}"
697
+
698
+ # ============ 自定义网关管理 ============
699
+
700
+ KEY_CUSTOM_GATEWAYS = "custom_gateways"
701
+
702
+ @staticmethod
703
+ def get_custom_gateways():
704
+ """
705
+ 获取所有自定义网关配置
706
+
707
+ Returns:
708
+ dict: 自定义网关配置字典,格式为 {gateway_id: config}
709
+ """
710
+ gateways = configPresenter.getSetting(configPresenter.KEY_CUSTOM_GATEWAYS)
711
+ return gateways if gateways else {}
712
+
713
+ @staticmethod
714
+ def add_custom_gateway(config=None):
715
+ """
716
+ 添加自定义网关
717
+
718
+ Args:
719
+ config (dict): 网关配置,包含 name, baseUrl, apiKey, apiType 等字段
720
+
721
+ Returns:
722
+ dict: {"success": bool, "id": str} 添加结果
723
+ """
724
+ import uuid
725
+
726
+ try:
727
+ logger.info(f"add_custom_gateway called with config: {config}")
728
+ logger.info(f"config type: {type(config)}")
729
+
730
+ if config is None:
731
+ logger.error("Config is None!")
732
+ return {"success": False, "id": "", "error": "Config is None"}
733
+
734
+ if not isinstance(config, dict):
735
+ logger.error(f"Config is not a dict, it's {type(config)}")
736
+ return {"success": False, "id": "", "error": f"Config must be dict, got {type(config)}"}
737
+
738
+ # 生成唯一ID
739
+ gateway_id = str(uuid.uuid4())[:8]
740
+
741
+ gateways = configPresenter.get_custom_gateways()
742
+
743
+ # 根据API类型确定API版本
744
+ api_type = config.get("apiType", "openai")
745
+ api_version = "/v1" # OpenAI风格默认使用 /v1
746
+
747
+ # 构建完整的网关配置
748
+ gateways[gateway_id] = {
749
+ "id": gateway_id,
750
+ "type": f"custom-{gateway_id}",
751
+ "name": config.get("name", "自定义网关"),
752
+ "description": config.get("description", f"自定义网关 - {config.get('baseUrl', '')}"),
753
+ "baseUrl": config.get("baseUrl", ""),
754
+ "apiKey": config.get("apiKey", ""),
755
+ "apiType": api_type,
756
+ "apiVersion": api_version, # 根据API类型自动设置
757
+ "timeout": config.get("timeout", 30000),
758
+ "retryCount": config.get("retryCount", 3),
759
+ "enabled": True,
760
+ "isCustom": True,
761
+ "createdAt": time.strftime("%Y-%m-%dT%H:%M:%SZ")
762
+ }
763
+
764
+ success = configPresenter.setSetting(configPresenter.KEY_CUSTOM_GATEWAYS, gateways)
765
+
766
+ if success:
767
+ logger.info(f"Added custom gateway: {gateway_id} - {config.get('name')} (API Type: {api_type})")
768
+ else:
769
+ logger.error(f"Failed to add custom gateway: {config.get('name')}")
770
+
771
+ return {"success": success, "id": gateway_id}
772
+ except Exception as e:
773
+ logger.exception(f"Exception in add_custom_gateway: {e}")
774
+ return {"success": False, "id": "", "error": str(e)}
775
+
776
+ @staticmethod
777
+ def update_custom_gateway(gateway_id: str, config: dict):
778
+ """
779
+ 更新自定义网关配置
780
+
781
+ Args:
782
+ gateway_id (str): 网关ID
783
+ config (dict): 要更新的配置字段
784
+
785
+ Returns:
786
+ bool: 是否更新成功
787
+ """
788
+ gateways = configPresenter.get_custom_gateways()
789
+
790
+ if gateway_id not in gateways:
791
+ logger.error(f"Custom gateway not found: {gateway_id}")
792
+ return False
793
+
794
+ # 保留原有的ID、类型和创建时间
795
+ original = gateways[gateway_id]
796
+
797
+ # 如果更新了apiType,需要更新对应的apiVersion
798
+ if "apiType" in config:
799
+ api_type = config.get("apiType", "openai")
800
+ config["apiVersion"] = "/v1" # OpenAI风格使用 /v1
801
+
802
+ # 更新配置(合并新旧配置)
803
+ updated_config = {
804
+ **original,
805
+ **config,
806
+ "id": gateway_id, # 确保ID不变
807
+ "type": f"custom-{gateway_id}", # 确保type不变
808
+ "createdAt": original.get("createdAt"), # 确保创建时间不变
809
+ "isCustom": True # 确保isCustom标记不变
810
+ }
811
+
812
+ gateways[gateway_id] = updated_config
813
+
814
+ success = configPresenter.setSetting(configPresenter.KEY_CUSTOM_GATEWAYS, gateways)
815
+
816
+ if success:
817
+ logger.info(f"Updated custom gateway: {gateway_id} (API Type: {updated_config.get('apiType', 'openai')})")
818
+ else:
819
+ logger.error(f"Failed to update custom gateway: {gateway_id}")
820
+
821
+ return success
822
+
823
+ @staticmethod
824
+ def delete_custom_gateway(gateway_id: str):
825
+ """
826
+ 删除自定义网关
827
+
828
+ Args:
829
+ gateway_id (str): 网关ID
830
+
831
+ Returns:
832
+ bool: 是否删除成功
833
+ """
834
+ gateways = configPresenter.get_custom_gateways()
835
+
836
+ if gateway_id not in gateways:
837
+ logger.warning(f"Custom gateway not found for deletion: {gateway_id}")
838
+ return False
839
+
840
+ # 检查是否正在使用该网关
841
+ current_gateway_type = configPresenter.get_gateway_type()
842
+ if current_gateway_type == f"custom-{gateway_id}":
843
+ logger.warning(f"Cannot delete custom gateway {gateway_id}: currently in use")
844
+ # 自动切换到云端网关
845
+ configPresenter.set_gateway_type("cloud")
846
+ logger.info("Switched to cloud gateway")
847
+
848
+ del gateways[gateway_id]
849
+
850
+ success = configPresenter.setSetting(configPresenter.KEY_CUSTOM_GATEWAYS, gateways)
851
+
852
+ if success:
853
+ logger.info(f"Deleted custom gateway: {gateway_id}")
854
+ else:
855
+ logger.error(f"Failed to delete custom gateway: {gateway_id}")
856
+
857
+ return success
858
+
859
+ @staticmethod
860
+ def get_custom_gateway(gateway_id: str):
861
+ """
862
+ 获取指定的自定义网关配置
863
+
864
+ Args:
865
+ gateway_id (str): 网关ID
866
+
867
+ Returns:
868
+ dict: 网关配置,如果不存在则返回None
869
+ """
870
+ gateways = configPresenter.get_custom_gateways()
871
+ return gateways.get(gateway_id)
872
+
873
+ # ============ 应用设置配置管理 ============
874
+
875
+ KEY_APP_SETTINGS_HTTP_RULE = "app_settings_http_rule"
876
+
877
+ @staticmethod
878
+ def get_app_settings_http_rule():
879
+ """
880
+ 获取应用设置 - 允许局域网访问配置
881
+
882
+ Returns:
883
+ bool: True 允许局域网访问, False 不允许(默认)
884
+ """
885
+ result = configPresenter.getSetting(configPresenter.KEY_APP_SETTINGS_HTTP_RULE)
886
+ if result is None:
887
+ return False
888
+ # 处理字符串类型的返回值
889
+ if isinstance(result, str):
890
+ return result.lower() == 'true'
891
+ return bool(result)
892
+
893
+ @staticmethod
894
+ def set_app_settings_http_rule(enabled: bool):
895
+ """
896
+ 设置应用设置 - 允许局域网访问配置
897
+
898
+ Args:
899
+ enabled (bool): True 允许局域网访问, False 不允许
900
+
901
+ Returns:
902
+ bool: 是否设置成功
903
+ """
904
+ return configPresenter.setSetting(configPresenter.KEY_APP_SETTINGS_HTTP_RULE, enabled)
905
+
906
+ # ============ 网络代理配置管理 ============
907
+
908
+ # 类变量:内存缓存
909
+ _proxy_cache = None # None 表示未加载,True/False 表示配置值
910
+ KEY_USE_SYSTEM_PROXY = "use_system_proxy"
911
+
912
+ @staticmethod
913
+ def get_use_system_proxy():
914
+ """
915
+ 获取是否使用系统代理配置(优先从内存缓存读取)
916
+
917
+ Returns:
918
+ bool: True 表示使用系统代理, False 表示不使用(默认)
919
+ """
920
+ # 优先从缓存读取
921
+ if configPresenter._proxy_cache is not None:
922
+ return configPresenter._proxy_cache
923
+
924
+ # 从硬盘加载
925
+ result = configPresenter.getSetting(configPresenter.KEY_USE_SYSTEM_PROXY)
926
+ if result is None:
927
+ value = False # 默认不使用代理
928
+ elif isinstance(result, str):
929
+ value = result.lower() == 'true'
930
+ else:
931
+ value = bool(result)
932
+
933
+ # 更新缓存
934
+ configPresenter._proxy_cache = value
935
+ return value
936
+
937
+ @staticmethod
938
+ def set_use_system_proxy(use_proxy: bool):
939
+ """
940
+ 设置是否使用系统代理配置(先更新内存,再写硬盘)
941
+
942
+ Args:
943
+ use_proxy (bool): True 表示使用系统代理, False 表示不使用
944
+
945
+ Returns:
946
+ bool: 是否设置成功
947
+ """
948
+ # 1. 先更新内存缓存
949
+ configPresenter._proxy_cache = use_proxy
950
+
951
+ # 2. 再写硬盘
952
+ return configPresenter.setSetting(configPresenter.KEY_USE_SYSTEM_PROXY, use_proxy)
953
+
954
+ @staticmethod
955
+ def load_proxy_config():
956
+ """
957
+ 启动时加载代理配置到内存(可选调用,懒加载也可以)
958
+
959
+ Returns:
960
+ bool: 当前的代理配置值
961
+ """
962
+ return configPresenter.get_use_system_proxy()
963
+
964
+ # ============ Tab-MCP 绑定配置管理 ============
965
+
966
+ KEY_TAB_MCP_BINDINGS = "tab_mcp_bindings"
967
+
968
+ # 默认Tab-MCP绑定配置(硬编码回退值)
969
+ # V1格式:字符串表示单个MCP名称
970
+ # 后续V2格式:对象可支持多MCP或SubAgent
971
+ DEFAULT_TAB_MCP_BINDINGS = {
972
+ "files": "Evol记忆",
973
+ "web": "Evol浏览器自动化",
974
+ "pdf": "PDF助手",
975
+ "excel": "Evol记忆",
976
+ "word": "Evol记忆",
977
+ "terminal": "Evol终端控制器",
978
+ }
979
+
980
+ @staticmethod
981
+ def get_tab_mcp_bindings():
982
+ """
983
+ 获取Tab-MCP绑定配置
984
+
985
+ Returns:
986
+ dict: Tab类型到MCP服务名称的映射,格式为 {tabType: mcpServerName}
987
+ """
988
+ bindings = configPresenter.getSetting(configPresenter.KEY_TAB_MCP_BINDINGS)
989
+ if not bindings or not isinstance(bindings, dict):
990
+ # 返回默认配置
991
+ return dict(configPresenter.DEFAULT_TAB_MCP_BINDINGS)
992
+ # 合并默认值,确保新增的tabType有默认配置
993
+ return {**configPresenter.DEFAULT_TAB_MCP_BINDINGS, **bindings}
994
+
995
+ @staticmethod
996
+ def set_tab_mcp_bindings(bindings: dict):
997
+ """
998
+ 设置Tab-MCP绑定配置
999
+
1000
+ Args:
1001
+ bindings (dict): Tab类型到MCP服务名称的映射
1002
+
1003
+ Returns:
1004
+ bool: 是否设置成功
1005
+ """
1006
+ return configPresenter.setSetting(configPresenter.KEY_TAB_MCP_BINDINGS, bindings)
1007
+
1008
+ @staticmethod
1009
+ def get_mcp_for_tab_type(tab_type: str):
1010
+ """
1011
+ 获取指定Tab类型的默认MCP服务名称
1012
+
1013
+ Args:
1014
+ tab_type (str): Tab类型
1015
+
1016
+ Returns:
1017
+ str: MCP服务名称,如果未配置返回空字符串
1018
+ """
1019
+ bindings = configPresenter.get_tab_mcp_bindings()
1020
+ return bindings.get(tab_type, "")
1021
+
1022
+ @staticmethod
1023
+ def set_mcp_for_tab_type(tab_type: str, mcp_server_name: str):
1024
+ """
1025
+ 设置指定Tab类型的MCP服务
1026
+
1027
+ Args:
1028
+ tab_type (str): Tab类型
1029
+ mcp_server_name (str): MCP服务名称,空字符串表示重置为默认值
1030
+
1031
+ Returns:
1032
+ bool: 是否设置成功
1033
+ """
1034
+ bindings = configPresenter.get_tab_mcp_bindings()
1035
+
1036
+ if mcp_server_name == '':
1037
+ # 空字符串表示重置:删除该key,让默认值生效
1038
+ bindings.pop(tab_type, None)
1039
+ else:
1040
+ # 设置用户自定义的MCP服务
1041
+ bindings[tab_type] = mcp_server_name
1042
+
1043
+ return configPresenter.set_tab_mcp_bindings(bindings)
1044
+
1045
+ # ============ 远程访问控制配置管理 ============
1046
+
1047
+ KEY_REMOTE_ACCESS_ENABLED = "remote_access_enabled"
1048
+ KEY_DISABLED_PAIRING_CODES = "disabled_pairing_codes"
1049
+
1050
+ @staticmethod
1051
+ def get_remote_access_enabled():
1052
+ """
1053
+ 获取远程访问总开关状态
1054
+
1055
+ Returns:
1056
+ bool: True 允许远程访问, False 禁止远程访问(默认)
1057
+ """
1058
+ result = configPresenter.getSetting(configPresenter.KEY_REMOTE_ACCESS_ENABLED)
1059
+ if result is None:
1060
+ return False
1061
+ if isinstance(result, str):
1062
+ return result.lower() == 'true'
1063
+ return bool(result)
1064
+
1065
+ @staticmethod
1066
+ def set_remote_access_enabled(enabled: bool):
1067
+ """
1068
+ 设置远程访问总开关
1069
+
1070
+ Args:
1071
+ enabled (bool): True 允许远程访问, False 禁止远程访问
1072
+
1073
+ Returns:
1074
+ bool: 是否设置成功
1075
+ """
1076
+ return configPresenter.setSetting(configPresenter.KEY_REMOTE_ACCESS_ENABLED, enabled)
1077
+
1078
+ @staticmethod
1079
+ def get_disabled_pairing_codes():
1080
+ """
1081
+ 获取禁用的配对码列表
1082
+
1083
+ Returns:
1084
+ list: 禁用的配对码列表
1085
+ """
1086
+ result = configPresenter.getSetting(configPresenter.KEY_DISABLED_PAIRING_CODES)
1087
+ if result is None:
1088
+ return []
1089
+ if isinstance(result, str):
1090
+ # 支持逗号分隔的字符串格式
1091
+ if not result.strip():
1092
+ return []
1093
+ return [code.strip() for code in result.split(',') if code.strip()]
1094
+ if isinstance(result, list):
1095
+ return result
1096
+ return []
1097
+
1098
+ @staticmethod
1099
+ def set_disabled_pairing_codes(codes: list):
1100
+ """
1101
+ 设置禁用的配对码列表
1102
+
1103
+ Args:
1104
+ codes (list): 禁用的配对码列表
1105
+
1106
+ Returns:
1107
+ bool: 是否设置成功
1108
+ """
1109
+ return configPresenter.setSetting(configPresenter.KEY_DISABLED_PAIRING_CODES, codes)
1110
+
1111
+ @staticmethod
1112
+ def add_disabled_pairing_code(code: str):
1113
+ """
1114
+ 添加一个禁用的配对码
1115
+
1116
+ Args:
1117
+ code (str): 要禁用的配对码
1118
+
1119
+ Returns:
1120
+ bool: 是否添加成功
1121
+ """
1122
+ if not code or not code.strip():
1123
+ return False
1124
+ codes = configPresenter.get_disabled_pairing_codes()
1125
+ code = code.strip()
1126
+ if code not in codes:
1127
+ codes.append(code)
1128
+ return configPresenter.set_disabled_pairing_codes(codes)
1129
+ return True
1130
+
1131
+ @staticmethod
1132
+ def remove_disabled_pairing_code(code: str):
1133
+ """
1134
+ 移除一个禁用的配对码
1135
+
1136
+ Args:
1137
+ code (str): 要移除的配对码
1138
+
1139
+ Returns:
1140
+ bool: 是否移除成功
1141
+ """
1142
+ if not code or not code.strip():
1143
+ return False
1144
+ codes = configPresenter.get_disabled_pairing_codes()
1145
+ code = code.strip()
1146
+ if code in codes:
1147
+ codes.remove(code)
1148
+ return configPresenter.set_disabled_pairing_codes(codes)
1149
+ return True
1150
+
1151
+ @staticmethod
1152
+ def is_pairing_code_disabled(code: str):
1153
+ """
1154
+ 检查配对码是否被禁用
1155
+
1156
+ Args:
1157
+ code (str): 配对码
1158
+
1159
+ Returns:
1160
+ bool: True 表示被禁用, False 表示未被禁用
1161
+ """
1162
+ if not code or not code.strip():
1163
+ return False
1164
+ codes = configPresenter.get_disabled_pairing_codes()
1165
+ return code.strip() in codes
1166
+
1167
+ @staticmethod
1168
+ def get_remote_access_config():
1169
+ """
1170
+ 获取远程访问配置(总开关 + 禁用的配对码)
1171
+
1172
+ Returns:
1173
+ dict: {
1174
+ "enabled": bool,
1175
+ "disabled_codes": list
1176
+ }
1177
+ """
1178
+ return {
1179
+ "enabled": configPresenter.get_remote_access_enabled(),
1180
+ "disabled_codes": configPresenter.get_disabled_pairing_codes()
1181
+ }
1182
+
1183
+ @staticmethod
1184
+ def set_remote_access_config(enabled: bool = None, disabled_codes: list = None):
1185
+ """
1186
+ 设置远程访问配置
1187
+
1188
+ Args:
1189
+ enabled (bool): 总开关状态(可选)
1190
+ disabled_codes (list): 禁用的配对码列表(可选)
1191
+
1192
+ Returns:
1193
+ dict: {"success": bool, "message": str}
1194
+ """
1195
+ try:
1196
+ if enabled is not None:
1197
+ configPresenter.set_remote_access_enabled(enabled)
1198
+ if disabled_codes is not None:
1199
+ configPresenter.set_disabled_pairing_codes(disabled_codes)
1200
+ return {"success": True, "message": "配置已更新"}
1201
+ except Exception as e:
1202
+ return {"success": False, "message": str(e)}
1203
+
1204
+ # ============ 用户默认系统提示词配置管理 ============
1205
+
1206
+ KEY_USER_DEFAULT_SYSTEM_PROMPT = "user_default_system_prompt"
1207
+
1208
+ @staticmethod
1209
+ def get_user_default_system_prompt(workspace_name: str):
1210
+ """
1211
+ 获取用户默认系统提示词(workspace级别)
1212
+
1213
+ Args:
1214
+ workspace_name (str): 任务空间名称
1215
+
1216
+ Returns:
1217
+ dict: 提示词数据字典 {'userCustom': str, 'version': str},如果未设置返回默认值
1218
+ """
1219
+ try:
1220
+ prompt_data = configPresenter.getSetting(
1221
+ configPresenter.KEY_USER_DEFAULT_SYSTEM_PROMPT,
1222
+ workspace_name
1223
+ )
1224
+
1225
+ if not prompt_data:
1226
+ # 返回默认值
1227
+ return {'userCustom': '', 'version': '1.0'}
1228
+
1229
+ # 兼容旧格式(字符串)
1230
+ if isinstance(prompt_data, str):
1231
+ try:
1232
+ return json.loads(prompt_data)
1233
+ except json.JSONDecodeError:
1234
+ # 如果不是JSON,当作纯文本处理
1235
+ return {'userCustom': prompt_data, 'version': '1.0'}
1236
+
1237
+ # 新格式(字典)
1238
+ return prompt_data
1239
+ except Exception as e:
1240
+ logger.exception(f"获取用户默认系统提示词失败: {e}")
1241
+ return {'userCustom': '', 'version': '1.0'}
1242
+
1243
+ @staticmethod
1244
+ def set_user_default_system_prompt(workspace_name: str, prompt_data: dict):
1245
+ """
1246
+ 设置用户默认系统提示词(workspace级别)
1247
+
1248
+ Args:
1249
+ workspace_name (str): 任务空间名称
1250
+ prompt_data (dict): 提示词数据 {'userCustom': str, 'version': str}
1251
+
1252
+ Returns:
1253
+ bool: 是否设置成功
1254
+ """
1255
+ try:
1256
+ if not isinstance(prompt_data, dict):
1257
+ logger.error(f"prompt_data必须是字典类型,当前类型: {type(prompt_data)}")
1258
+ return False
1259
+
1260
+ # 确保包含必要字段
1261
+ if 'userCustom' not in prompt_data:
1262
+ prompt_data['userCustom'] = ''
1263
+ if 'version' not in prompt_data:
1264
+ prompt_data['version'] = '1.0'
1265
+
1266
+ success = configPresenter.setSetting(
1267
+ configPresenter.KEY_USER_DEFAULT_SYSTEM_PROMPT,
1268
+ prompt_data,
1269
+ workspace_name
1270
+ )
1271
+
1272
+ if success:
1273
+ logger.info(f"已保存用户默认系统提示词到workspace: {workspace_name}")
1274
+ else:
1275
+ logger.error(f"保存用户默认系统提示词失败: {workspace_name}")
1276
+
1277
+ return success
1278
+ except Exception as e:
1279
+ logger.exception(f"设置用户默认系统提示词失败: {e}")
1280
+ return False
1281
+