agentengine-sdk-python 0.6.4__py3-none-any.whl → 0.7.0__py3-none-any.whl

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 (486) hide show
  1. agentengine_sdk_python-0.7.0.dist-info/METADATA +181 -0
  2. agentengine_sdk_python-0.7.0.dist-info/RECORD +482 -0
  3. {agentengine_sdk_python-0.6.4.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +1 -1
  4. agentengine_sdk_python-0.7.0.dist-info/entry_points.txt +3 -0
  5. agentengine_sdk_python-0.7.0.dist-info/licenses/LICENSE +175 -0
  6. agentengine_sdk_python-0.7.0.dist-info/top_level.txt +2 -0
  7. ksadk/__init__.py +13 -0
  8. ksadk/__main__.py +4 -0
  9. ksadk/a2a/__init__.py +15 -0
  10. ksadk/a2a/card_builder.py +64 -0
  11. ksadk/a2a/client.py +309 -0
  12. ksadk/a2a/executor.py +201 -0
  13. ksadk/a2a/server.py +79 -0
  14. ksadk/agents/__init__.py +20 -0
  15. ksadk/agents/base.py +370 -0
  16. ksadk/agents/context.py +38 -0
  17. ksadk/agents/event.py +35 -0
  18. ksadk/agents/loop_agent.py +71 -0
  19. ksadk/agents/parallel_agent.py +109 -0
  20. ksadk/agents/runner_adapter.py +24 -0
  21. ksadk/agents/sequential_agent.py +34 -0
  22. ksadk/api/__init__.py +3 -0
  23. ksadk/api/client.py +2246 -0
  24. ksadk/builders/__init__.py +20 -0
  25. ksadk/builders/base.py +66 -0
  26. ksadk/builders/code_builder.py +1990 -0
  27. ksadk/builders/container_builder.py +786 -0
  28. ksadk/builders/framework_requirements.py +59 -0
  29. ksadk/builders/ks3_uploader.py +389 -0
  30. ksadk/builders/mcp_builder.py +476 -0
  31. ksadk/builders/requirements_utils.py +75 -0
  32. ksadk/cli/__init__.py +531 -0
  33. ksadk/cli/__main__.py +4 -0
  34. ksadk/cli/agent_ref.py +204 -0
  35. ksadk/cli/cmd_a2a.py +167 -0
  36. ksadk/cli/cmd_agent.py +201 -0
  37. ksadk/cli/cmd_build.py +270 -0
  38. ksadk/cli/cmd_completion.py +304 -0
  39. ksadk/cli/cmd_config.py +758 -0
  40. ksadk/cli/cmd_create.py +2304 -0
  41. ksadk/cli/cmd_dashboard.py +900 -0
  42. ksadk/cli/cmd_deploy.py +704 -0
  43. ksadk/cli/cmd_destroy.py +345 -0
  44. ksadk/cli/cmd_files.py +1227 -0
  45. ksadk/cli/cmd_hermes.py +1263 -0
  46. ksadk/cli/cmd_invoke.py +1503 -0
  47. ksadk/cli/cmd_launch.py +564 -0
  48. ksadk/cli/cmd_mcp.py +1179 -0
  49. ksadk/cli/cmd_model.py +302 -0
  50. ksadk/cli/cmd_openclaw.py +3922 -0
  51. ksadk/cli/cmd_run.py +239 -0
  52. ksadk/cli/cmd_status.py +588 -0
  53. ksadk/cli/cmd_version.py +522 -0
  54. ksadk/cli/cmd_web.py +288 -0
  55. ksadk/cli/deploy_utils.py +87 -0
  56. ksadk/cli/dry_run.py +214 -0
  57. ksadk/cli/env_options.py +82 -0
  58. ksadk/cli/error_utils.py +525 -0
  59. ksadk/cli/global_options.py +31 -0
  60. ksadk/cli/local_runtime.py +97 -0
  61. ksadk/cli/model_catalog.py +136 -0
  62. ksadk/cli/network_options.py +412 -0
  63. ksadk/cli/resource_common.py +621 -0
  64. ksadk/cli/storage.py +84 -0
  65. ksadk/cli/ui.py +431 -0
  66. ksadk/cli/workflow_common.py +552 -0
  67. ksadk/common/__init__.py +14 -0
  68. ksadk/common/aicp_env.py +128 -0
  69. ksadk/common/auth.py +190 -0
  70. ksadk/common/constants.py +84 -0
  71. ksadk/common/llm_utils.py +61 -0
  72. ksadk/configs/__init__.py +61 -0
  73. ksadk/configs/env_registry.py +343 -0
  74. ksadk/configs/global_config.py +200 -0
  75. ksadk/configs/settings.py +706 -0
  76. ksadk/conversations/__init__.py +95 -0
  77. ksadk/conversations/attachment_storage.py +379 -0
  78. ksadk/conversations/attachments.py +774 -0
  79. ksadk/conversations/compaction_pipeline.py +387 -0
  80. ksadk/conversations/compaction_prompt.py +128 -0
  81. ksadk/conversations/context.py +303 -0
  82. ksadk/conversations/message_projection.py +269 -0
  83. ksadk/conversations/model_context.py +370 -0
  84. ksadk/conversations/model_options.py +136 -0
  85. ksadk/conversations/normalize.py +446 -0
  86. ksadk/conversations/reasoning_markup.py +91 -0
  87. ksadk/conversations/run_kinds.py +61 -0
  88. ksadk/conversations/run_status.py +53 -0
  89. ksadk/conversations/runtime.py +5276 -0
  90. ksadk/conversations/semantic_summary.py +359 -0
  91. ksadk/conversations/session_title.py +254 -0
  92. ksadk/deployment/__init__.py +83 -0
  93. ksadk/deployment/agent_access.py +226 -0
  94. ksadk/deployment/base.py +229 -0
  95. ksadk/deployment/manager.py +332 -0
  96. ksadk/deployment/providers/__init__.py +10 -0
  97. ksadk/deployment/providers/serverless.py +1115 -0
  98. ksadk/deployment/registry.py +120 -0
  99. ksadk/deployment/state.py +78 -0
  100. ksadk/deployment/ui_config.py +21 -0
  101. ksadk/detection/__init__.py +7 -0
  102. ksadk/detection/detector.py +431 -0
  103. ksadk/detection/mcp_detector.py +250 -0
  104. ksadk/hermes_terminal.py +467 -0
  105. ksadk/identity/__init__.py +17 -0
  106. ksadk/identity/resolver.py +385 -0
  107. ksadk/knowledge_base/__init__.py +62 -0
  108. ksadk/knowledge_base/adk_tool.py +47 -0
  109. ksadk/knowledge_base/client.py +297 -0
  110. ksadk/knowledge_base/langchain_tool.py +55 -0
  111. ksadk/knowledge_base/service.py +66 -0
  112. ksadk/knowledge_base/tool.py +50 -0
  113. ksadk/markdown.py +169 -0
  114. ksadk/mcp_runtime/__init__.py +231 -0
  115. ksadk/memory/__init__.py +57 -0
  116. ksadk/memory/adk/__init__.py +51 -0
  117. ksadk/memory/adk/backends/__init__.py +5 -0
  118. ksadk/memory/adk/backends/base_ltm_backend.py +52 -0
  119. ksadk/memory/adk/backends/http_ltm_backend.py +183 -0
  120. ksadk/memory/adk/backends/inmemory_ltm_backend.py +96 -0
  121. ksadk/memory/adk/backends/sdk_ltm_backend.py +456 -0
  122. ksadk/memory/adk/long_term_memory.py +273 -0
  123. ksadk/memory/adk/resilient_session_service.py +268 -0
  124. ksadk/memory/adk/short_term_memory.py +304 -0
  125. ksadk/memory/adk_tool.py +31 -0
  126. ksadk/memory/backends/__init__.py +6 -0
  127. ksadk/memory/backends/base.py +103 -0
  128. ksadk/memory/backends/memory.py +112 -0
  129. ksadk/memory/backends/redis.py +146 -0
  130. ksadk/memory/langchain_tool.py +39 -0
  131. ksadk/memory/ltm_backend_factory.py +24 -0
  132. ksadk/memory/manager.py +215 -0
  133. ksadk/memory/service.py +172 -0
  134. ksadk/memory/tool.py +96 -0
  135. ksadk/model_policy.py +223 -0
  136. ksadk/openclaw_gateway.py +378 -0
  137. ksadk/runners/__init__.py +8 -0
  138. ksadk/runners/adk_runner.py +1994 -0
  139. ksadk/runners/base_runner.py +305 -0
  140. ksadk/runners/deepagents_runner.py +13 -0
  141. ksadk/runners/factory.py +73 -0
  142. ksadk/runners/langchain_runner.py +751 -0
  143. ksadk/runners/langgraph_runner.py +985 -0
  144. ksadk/runners/patch_langchain.py +187 -0
  145. ksadk/runners/remote_runner.py +792 -0
  146. ksadk/runners/usage_accumulator.py +38 -0
  147. ksadk/runners/utils/__init__.py +19 -0
  148. ksadk/runners/utils/langfuse.py +230 -0
  149. ksadk/runners/utils/loader.py +62 -0
  150. ksadk/runtime_context.py +166 -0
  151. ksadk/runtime_state.py +25 -0
  152. ksadk/sandbox/__init__.py +28 -0
  153. ksadk/sandbox/backends/__init__.py +9 -0
  154. ksadk/sandbox/backends/e2b.py +154 -0
  155. ksadk/sandbox/backends/local_process.py +131 -0
  156. ksadk/sandbox/base.py +101 -0
  157. ksadk/sandbox/factory.py +53 -0
  158. ksadk/sandbox/registry.py +187 -0
  159. ksadk/server/__init__.py +7 -0
  160. ksadk/server/api_models.py +73 -0
  161. ksadk/server/app.py +3931 -0
  162. ksadk/server/static/assets/ArtifactsPanel-D-SSn2ee.js +1 -0
  163. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +9 -0
  164. ksadk/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  165. ksadk/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  166. ksadk/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  167. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  168. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  169. ksadk/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  170. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  171. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  172. ksadk/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  173. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  174. ksadk/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  175. ksadk/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  176. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  177. ksadk/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  178. ksadk/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  179. ksadk/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  180. ksadk/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  181. ksadk/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  182. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  183. ksadk/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  184. ksadk/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  185. ksadk/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  186. ksadk/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  187. ksadk/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  188. ksadk/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  189. ksadk/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  190. ksadk/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  191. ksadk/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  192. ksadk/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  193. ksadk/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  194. ksadk/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  195. ksadk/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  196. ksadk/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  197. ksadk/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  198. ksadk/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  199. ksadk/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  200. ksadk/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  201. ksadk/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  202. ksadk/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  203. ksadk/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  204. ksadk/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  205. ksadk/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  206. ksadk/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  207. ksadk/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  208. ksadk/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  209. ksadk/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  210. ksadk/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  211. ksadk/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  212. ksadk/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  213. ksadk/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  214. ksadk/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  215. ksadk/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  216. ksadk/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  217. ksadk/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  218. ksadk/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  219. ksadk/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  220. ksadk/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  221. ksadk/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  222. ksadk/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +8 -0
  224. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +1 -0
  225. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +303 -0
  226. ksadk/server/static/assets/NativeTerminalPanel-CHOd7Rch.css +1 -0
  227. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +1 -0
  228. ksadk/server/static/assets/addon-fit-DthTIhi3.js +1 -0
  229. ksadk/server/static/assets/apl-CnwPGSsG.js +1 -0
  230. ksadk/server/static/assets/arc-BA13sHc9.js +1 -0
  231. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
  232. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-DzJzgVoL.js +36 -0
  233. ksadk/server/static/assets/asciiarmor-qTkVPQu6.js +1 -0
  234. ksadk/server/static/assets/asn1-Dr8qZg38.js +1 -0
  235. ksadk/server/static/assets/asterisk-Bja0s6e1.js +1 -0
  236. ksadk/server/static/assets/blockDiagram-GPEHLZMM-UTdVcZhn.js +132 -0
  237. ksadk/server/static/assets/brainfuck-D5EjA2JK.js +1 -0
  238. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dix0GD4m.js +10 -0
  239. ksadk/server/static/assets/channel-CTuydARX.js +1 -0
  240. ksadk/server/static/assets/chunk-2J33WTMH-C2JxuZ42.js +1 -0
  241. ksadk/server/static/assets/chunk-4BX2VUAB-Be57QPgD.js +1 -0
  242. ksadk/server/static/assets/chunk-55IACEB6-WjSuy4nv.js +1 -0
  243. ksadk/server/static/assets/chunk-727SXJPM-BOovP-b5.js +206 -0
  244. ksadk/server/static/assets/chunk-AQP2D5EJ-DOmZNmWj.js +231 -0
  245. ksadk/server/static/assets/chunk-FMBD7UC4-DzwlodkU.js +15 -0
  246. ksadk/server/static/assets/chunk-ND2GUHAM-DMqAikEU.js +1 -0
  247. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
  248. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
  249. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
  250. ksadk/server/static/assets/clike-DHH8Ad3s.js +1 -0
  251. ksadk/server/static/assets/clojure-BflJGmDX.js +1 -0
  252. ksadk/server/static/assets/cmake-CyVbuzPu.js +1 -0
  253. ksadk/server/static/assets/cobol-B_OZ4V-R.js +1 -0
  254. ksadk/server/static/assets/coffeescript-B2pKbFk8.js +1 -0
  255. ksadk/server/static/assets/commonlisp-CcllspGY.js +1 -0
  256. ksadk/server/static/assets/cose-bilkent-S5V4N54A-Zhy2GBnP.js +1 -0
  257. ksadk/server/static/assets/crystal-M5-qzICs.js +1 -0
  258. ksadk/server/static/assets/css-CYpP4FRV.js +1 -0
  259. ksadk/server/static/assets/cypher-p9eYesGn.js +1 -0
  260. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +321 -0
  261. ksadk/server/static/assets/d-BhMjBjQP.js +1 -0
  262. ksadk/server/static/assets/dagre-BM42HDAG-CbKrtIUz.js +4 -0
  263. ksadk/server/static/assets/dagre-Bx709z4p.js +1 -0
  264. ksadk/server/static/assets/defaultLocale-C8Fc0cco.js +1 -0
  265. ksadk/server/static/assets/diagram-2AECGRRQ-BRApgQRK.js +43 -0
  266. ksadk/server/static/assets/diagram-5GNKFQAL-D0xbnDiK.js +10 -0
  267. ksadk/server/static/assets/diagram-KO2AKTUF-CVugVI9L.js +3 -0
  268. ksadk/server/static/assets/diagram-LMA3HP47-DozUGgee.js +24 -0
  269. ksadk/server/static/assets/diagram-OG6HWLK6-ZOYsKlru.js +24 -0
  270. ksadk/server/static/assets/diff-ChtP43wD.js +1 -0
  271. ksadk/server/static/assets/dist-2hInLS_J.js +9 -0
  272. ksadk/server/static/assets/dist-8EEyGgDx.js +1 -0
  273. ksadk/server/static/assets/dist-8_SfK1IP.js +1 -0
  274. ksadk/server/static/assets/dist-B0NP_Z0z.js +23 -0
  275. ksadk/server/static/assets/dist-BIJVDMuB.js +1 -0
  276. ksadk/server/static/assets/dist-BmlMPsCc.js +2 -0
  277. ksadk/server/static/assets/dist-BrCcQ47T.js +1 -0
  278. ksadk/server/static/assets/dist-BssVmjIo.js +6 -0
  279. ksadk/server/static/assets/dist-C3HIIaN5.js +1 -0
  280. ksadk/server/static/assets/dist-CPBtqRTW.js +1 -0
  281. ksadk/server/static/assets/dist-CkEKJFtk.js +1 -0
  282. ksadk/server/static/assets/dist-D-JMdWPQ.js +1 -0
  283. ksadk/server/static/assets/dist-D-xOD048.js +1 -0
  284. ksadk/server/static/assets/dist-D57wnGWd.js +1 -0
  285. ksadk/server/static/assets/dist-DL_NKc_z.js +1 -0
  286. ksadk/server/static/assets/dist-DMezvBGJ.js +1 -0
  287. ksadk/server/static/assets/dist-DmFaq_Iy.js +1 -0
  288. ksadk/server/static/assets/dist-Dp0k7bWI.js +1 -0
  289. ksadk/server/static/assets/dist-DpKtt4Bo.js +1 -0
  290. ksadk/server/static/assets/dist-DyOkZmze.js +11 -0
  291. ksadk/server/static/assets/dist-DyWsb78Q.js +1 -0
  292. ksadk/server/static/assets/dist-gX6GOLKq.js +1 -0
  293. ksadk/server/static/assets/dockerfile-BLkNEvjs.js +1 -0
  294. ksadk/server/static/assets/dtd-DTF72YNO.js +1 -0
  295. ksadk/server/static/assets/dylan-C6jEEEk-.js +1 -0
  296. ksadk/server/static/assets/ebnf-D4c0_ac3.js +1 -0
  297. ksadk/server/static/assets/ecl-BSZaIHnp.js +1 -0
  298. ksadk/server/static/assets/eiffel-BEjRio4Q.js +1 -0
  299. ksadk/server/static/assets/elm-Cshzl8qu.js +1 -0
  300. ksadk/server/static/assets/erDiagram-TEJ5UH35-Bk-9bgE5.js +85 -0
  301. ksadk/server/static/assets/erlang-DaB2Rkuy.js +1 -0
  302. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
  303. ksadk/server/static/assets/factor-D7LVTn2l.js +1 -0
  304. ksadk/server/static/assets/fcl-ClOhbFR7.js +1 -0
  305. ksadk/server/static/assets/flowDiagram-I6XJVG4X-Dn9kZVuZ.js +162 -0
  306. ksadk/server/static/assets/forth-Cezjo90N.js +1 -0
  307. ksadk/server/static/assets/fortran-Bt6PBEDR.js +1 -0
  308. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-BGxXlJqm.js +292 -0
  309. ksadk/server/static/assets/gas-BHEdbvp9.js +1 -0
  310. ksadk/server/static/assets/gherkin-oBAE_ms0.js +1 -0
  311. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
  312. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-C7Q0X-wi.js +106 -0
  313. ksadk/server/static/assets/graphlib-B8gBHxth.js +1 -0
  314. ksadk/server/static/assets/groovy-BC3IOsC8.js +1 -0
  315. ksadk/server/static/assets/haskell-BRsoo5mP.js +1 -0
  316. ksadk/server/static/assets/haxe-DIz0ZZqd.js +1 -0
  317. ksadk/server/static/assets/http-CLVgA2GD.js +1 -0
  318. ksadk/server/static/assets/idl-BUZw3wgd.js +1 -0
  319. ksadk/server/static/assets/index-C00Lwg3L.js +165 -0
  320. ksadk/server/static/assets/index-DAURbCsP.css +1 -0
  321. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
  322. ksadk/server/static/assets/infoDiagram-5YYISTIA-BO_CSqq5.js +2 -0
  323. ksadk/server/static/assets/init-D6jRqBbL.js +1 -0
  324. ksadk/server/static/assets/ishikawaDiagram-YF4QCWOH-eYLPLd46.js +70 -0
  325. ksadk/server/static/assets/javascript-9Tg8ixDm.js +1 -0
  326. ksadk/server/static/assets/journeyDiagram-JHISSGLW-De9geSb0.js +139 -0
  327. ksadk/server/static/assets/julia-CQfgDRfP.js +1 -0
  328. ksadk/server/static/assets/kanban-definition-UN3LZRKU-Cs80kQto.js +89 -0
  329. ksadk/server/static/assets/katex-DSrCFD8n.js +265 -0
  330. ksadk/server/static/assets/linear-CN9sbKTF.js +1 -0
  331. ksadk/server/static/assets/livescript-BXxG0Yva.js +1 -0
  332. ksadk/server/static/assets/lua-CiA1ziua.js +1 -0
  333. ksadk/server/static/assets/mathematica-u8YMmbU0.js +1 -0
  334. ksadk/server/static/assets/mbox-BEMVcqjs.js +1 -0
  335. ksadk/server/static/assets/mermaid-parser.core-DlgSBU8l.js +161 -0
  336. ksadk/server/static/assets/mindmap-definition-RKZ34NQL-9afz7Oug.js +96 -0
  337. ksadk/server/static/assets/mirc-C9zpzAZU.js +1 -0
  338. ksadk/server/static/assets/mllike-B45xgp2S.js +1 -0
  339. ksadk/server/static/assets/modelica-CqHI_q-D.js +1 -0
  340. ksadk/server/static/assets/mscgen-qgSLujhx.js +1 -0
  341. ksadk/server/static/assets/mumps-CW_TBmxz.js +1 -0
  342. ksadk/server/static/assets/nginx-BMaDbqW3.js +1 -0
  343. ksadk/server/static/assets/nsis-AQ_alPln.js +1 -0
  344. ksadk/server/static/assets/ntriples-d28e9m0E.js +1 -0
  345. ksadk/server/static/assets/octave-D2Q8cUp1.js +1 -0
  346. ksadk/server/static/assets/ordinal-hYBb2elL.js +1 -0
  347. ksadk/server/static/assets/oz-BwTct_5T.js +1 -0
  348. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
  349. ksadk/server/static/assets/pascal-Dp_KdFgX.js +1 -0
  350. ksadk/server/static/assets/perl-CdEHsPfU.js +1 -0
  351. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
  352. ksadk/server/static/assets/pieDiagram-4H26LBE5-DPv_VstN.js +30 -0
  353. ksadk/server/static/assets/pig-AF4Hwhju.js +1 -0
  354. ksadk/server/static/assets/powershell-CzG71I4L.js +1 -0
  355. ksadk/server/static/assets/properties-C357ku5U.js +1 -0
  356. ksadk/server/static/assets/protobuf-D1ij_kNL.js +1 -0
  357. ksadk/server/static/assets/pug-Bugr-47h.js +1 -0
  358. ksadk/server/static/assets/puppet-B4hC2X4m.js +1 -0
  359. ksadk/server/static/assets/python-BqYw9LYJ.js +1 -0
  360. ksadk/server/static/assets/q-B9NhS7L_.js +1 -0
  361. ksadk/server/static/assets/quadrantDiagram-W4KKPZXB-o77wZ1XK.js +7 -0
  362. ksadk/server/static/assets/r-DADKenSm.js +1 -0
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
  364. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-BYVw81VC.js +84 -0
  365. ksadk/server/static/assets/rpm-gpDK5sbt.js +1 -0
  366. ksadk/server/static/assets/ruby-DsYpTuWg.js +1 -0
  367. ksadk/server/static/assets/sankeyDiagram-5OEKKPKP-B7nZFMKT.js +40 -0
  368. ksadk/server/static/assets/sas-C2OyD2Y6.js +1 -0
  369. ksadk/server/static/assets/scheme-CyDdOh7e.js +1 -0
  370. ksadk/server/static/assets/sequenceDiagram-3UESZ5HK-BGRRrFw5.js +162 -0
  371. ksadk/server/static/assets/shell-Du5Qg9im.js +1 -0
  372. ksadk/server/static/assets/sieve-BAxa3IjF.js +1 -0
  373. ksadk/server/static/assets/simple-mode-DRpGK0lJ.js +1 -0
  374. ksadk/server/static/assets/smalltalk-BLp5-jwC.js +1 -0
  375. ksadk/server/static/assets/solr-BBopId9w.js +1 -0
  376. ksadk/server/static/assets/sparql-Dz-mlCAC.js +1 -0
  377. ksadk/server/static/assets/spreadsheet-CTIncYxj.js +1 -0
  378. ksadk/server/static/assets/sql-DcCJ9jop.js +1 -0
  379. ksadk/server/static/assets/stateDiagram-AJRCARHV-BUfHCoPs.js +1 -0
  380. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
  381. ksadk/server/static/assets/stex-sDrdk2BW.js +1 -0
  382. ksadk/server/static/assets/stylus-DLLZ8dgm.js +1 -0
  383. ksadk/server/static/assets/swift-DKB6_1j6.js +1 -0
  384. ksadk/server/static/assets/tcl-BGfruO1u.js +1 -0
  385. ksadk/server/static/assets/textile-DWBqthVk.js +1 -0
  386. ksadk/server/static/assets/tiddlywiki-VlUiK86J.js +1 -0
  387. ksadk/server/static/assets/tiki-CxVlf5KZ.js +1 -0
  388. ksadk/server/static/assets/timeline-definition-PNZ67QCA-FbCTGrf9.js +120 -0
  389. ksadk/server/static/assets/toml-LvoKBwCs.js +1 -0
  390. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
  391. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
  392. ksadk/server/static/assets/troff-C2dAgh7P.js +1 -0
  393. ksadk/server/static/assets/ttcn-BePYh-hG.js +1 -0
  394. ksadk/server/static/assets/ttcn-cfg-CY0aot7Q.js +1 -0
  395. ksadk/server/static/assets/turtle-BDLWE2cD.js +1 -0
  396. ksadk/server/static/assets/vb-BthdM_4N.js +1 -0
  397. ksadk/server/static/assets/vbscript-D9f6rSsU.js +1 -0
  398. ksadk/server/static/assets/velocity-CqftRsjg.js +1 -0
  399. ksadk/server/static/assets/vennDiagram-CIIHVFJN-DWgwtPsJ.js +34 -0
  400. ksadk/server/static/assets/verilog-D-x6ne0Z.js +1 -0
  401. ksadk/server/static/assets/vhdl-CMrOq2q1.js +1 -0
  402. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-2x8G-tTM.js +78 -0
  404. ksadk/server/static/assets/webidl-D5jiJvOU.js +1 -0
  405. ksadk/server/static/assets/xquery-fj_R-kMw.js +1 -0
  406. ksadk/server/static/assets/xterm-DooSxjI5.js +36 -0
  407. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-Drma3AYl.js +7 -0
  408. ksadk/server/static/assets/yacas-CXXgtw0p.js +1 -0
  409. ksadk/server/static/assets/z80-CL1naaMV.js +1 -0
  410. ksadk/server/static/favicon.svg +1 -0
  411. ksadk/server/static/icons.svg +24 -0
  412. ksadk/server/static/index.html +14 -0
  413. ksadk/server/terminal_sessions.py +551 -0
  414. ksadk/sessions/__init__.py +279 -0
  415. ksadk/sessions/base.py +354 -0
  416. ksadk/sessions/continuity.py +337 -0
  417. ksadk/sessions/errors.py +7 -0
  418. ksadk/sessions/in_memory.py +273 -0
  419. ksadk/sessions/local_service.py +949 -0
  420. ksadk/sessions/postgres_service.py +901 -0
  421. ksadk/sessions/resilience.py +52 -0
  422. ksadk/sessions/resilient.py +360 -0
  423. ksadk/sessions/sqlite_service.py +13 -0
  424. ksadk/skills/__init__.py +9 -0
  425. ksadk/skills/loader.py +46 -0
  426. ksadk/skills/models.py +120 -0
  427. ksadk/skills/package_store.py +104 -0
  428. ksadk/skills/runtime/__init__.py +17 -0
  429. ksadk/skills/runtime/agent.py +183 -0
  430. ksadk/skills/runtime/artifacts.py +39 -0
  431. ksadk/skills/runtime/backends/__init__.py +1 -0
  432. ksadk/skills/runtime/backends/disabled.py +8 -0
  433. ksadk/skills/runtime/backends/e2b.py +168 -0
  434. ksadk/skills/runtime/backends/local.py +95 -0
  435. ksadk/skills/runtime/base.py +103 -0
  436. ksadk/skills/runtime/executor.py +219 -0
  437. ksadk/skills/runtime/factory.py +37 -0
  438. ksadk/skills/runtime/loader.py +131 -0
  439. ksadk/skills/runtime/registry.py +150 -0
  440. ksadk/skills/runtime/request.py +67 -0
  441. ksadk/skills/service_client.py +239 -0
  442. ksadk/skills/service_env.py +58 -0
  443. ksadk/skills/tool_defs.py +221 -0
  444. ksadk/terminal_client.py +69 -0
  445. ksadk/terminal_exec_policy.py +229 -0
  446. ksadk/tools/__init__.py +17 -0
  447. ksadk/tools/gateway.py +230 -0
  448. ksadk/tools/result_budget.py +154 -0
  449. ksadk/toolsets/__init__.py +854 -0
  450. ksadk/toolsets/_langchain.py +11 -0
  451. ksadk/toolsets/platform.py +93 -0
  452. ksadk/toolsets/sandbox.py +310 -0
  453. ksadk/toolsets/skills.py +217 -0
  454. ksadk/toolsets/web.py +266 -0
  455. ksadk/toolsets/workspace.py +917 -0
  456. ksadk/toolsets/workspace_state.py +44 -0
  457. ksadk/tracing/__init__.py +37 -0
  458. ksadk/tracing/exporters/__init__.py +1 -0
  459. ksadk/tracing/exporters/inmemory_exporter.py +116 -0
  460. ksadk/tracing/exporters/langfuse_exporter.py +445 -0
  461. ksadk/tracing/setup.py +892 -0
  462. ksadk/tracing/span_utils.py +114 -0
  463. ksadk/tui/__init__.py +27 -0
  464. ksadk/tui/loop.py +1877 -0
  465. ksadk/tui/stream_render.py +68 -0
  466. ksadk/ui_config.py +168 -0
  467. ksadk/version.py +4 -0
  468. ksadk_runtime_common/__init__.py +20 -0
  469. ksadk_runtime_common/memory_backend/__init__.py +29 -0
  470. ksadk_runtime_common/memory_backend/manifest.py +79 -0
  471. ksadk_runtime_common/memory_backend/providers/__init__.py +6 -0
  472. ksadk_runtime_common/memory_backend/providers/lancedb.py +151 -0
  473. ksadk_runtime_common/memory_backend/providers/mem0.py +74 -0
  474. ksadk_runtime_common/memory_backend/registry.py +56 -0
  475. ksadk_runtime_common/memory_backend/render.py +71 -0
  476. ksadk_runtime_common/schemas/memory_backend_manifest.schema.json +148 -0
  477. ksadk_runtime_common/workspace_files/__init__.py +29 -0
  478. ksadk_runtime_common/workspace_files/bootstrap.py +56 -0
  479. ksadk_runtime_common/workspace_files/constants.py +9 -0
  480. ksadk_runtime_common/workspace_files/path_utils.py +71 -0
  481. ksadk_runtime_common/workspace_files/preview.py +96 -0
  482. ksadk_runtime_common/workspace_files/router.py +254 -0
  483. agentengine_sdk_python/__init__.py +0 -3
  484. agentengine_sdk_python-0.6.4.dist-info/METADATA +0 -48
  485. agentengine_sdk_python-0.6.4.dist-info/RECORD +0 -5
  486. agentengine_sdk_python-0.6.4.dist-info/top_level.txt +0 -1
@@ -0,0 +1,2304 @@
1
+ """
2
+ ksadk create - 创建项目模板
3
+ """
4
+
5
+ import json
6
+ import platform
7
+ import re
8
+ import shlex
9
+ import shutil
10
+
11
+ from pathlib import Path
12
+
13
+ import click
14
+ import questionary
15
+ from ksadk.cli.cmd_config import custom_style
16
+ from ksadk.cli.ui import (
17
+ print_error,
18
+ print_info,
19
+ print_kv,
20
+ print_rule,
21
+ print_success,
22
+ print_title,
23
+ print_warn,
24
+ )
25
+
26
+
27
+ def _quote_powershell_literal(value: str) -> str:
28
+ return "'" + str(value).replace("'", "''") + "'"
29
+
30
+
31
+ def _quote_cmd_path(value: str) -> str:
32
+ return '"' + str(value).replace('"', '""') + '"'
33
+
34
+
35
+ def _quick_start_command_lines(
36
+ project_name: str,
37
+ commands: list[str],
38
+ *,
39
+ system: str | None = None,
40
+ ) -> list[str]:
41
+ system_name = system or platform.system()
42
+ normalized_commands = [str(command).strip() for command in commands if str(command).strip()]
43
+ if not normalized_commands:
44
+ return []
45
+
46
+ if system_name == "Windows":
47
+ cmd_chain = " && ".join(normalized_commands)
48
+ return [
49
+ "PowerShell:",
50
+ f"Set-Location -LiteralPath {_quote_powershell_literal(project_name)}",
51
+ *normalized_commands,
52
+ "cmd.exe:",
53
+ f"cd /d {_quote_cmd_path(project_name)} && {cmd_chain}",
54
+ ]
55
+
56
+ return [f"cd {shlex.quote(str(project_name))} && {' && '.join(normalized_commands)}"]
57
+
58
+
59
+ def _print_quick_start_commands(project_name: str, commands: list[str]) -> None:
60
+ for line in _quick_start_command_lines(project_name, commands):
61
+ print_info(line)
62
+
63
+
64
+ _RUNTIME_AGENT_NAME_MAX_LENGTH = 63
65
+
66
+
67
+ def _normalize_runtime_package_name(project_name: str) -> str:
68
+ """Derive a portable package/runtime name from the project directory name."""
69
+ package_name = re.sub(r"[^A-Za-z0-9_]", "_", project_name.replace("-", "_"))
70
+ if not package_name:
71
+ package_name = "agent"
72
+ if not re.match(r"^[A-Za-z_]", package_name):
73
+ package_name = f"agent_{package_name}"
74
+ if len(package_name) > _RUNTIME_AGENT_NAME_MAX_LENGTH:
75
+ raise ValueError(
76
+ "生成的 Agent 名称 "
77
+ f"'{package_name}' 超过运行时 {_RUNTIME_AGENT_NAME_MAX_LENGTH} 字符限制"
78
+ )
79
+ return package_name
80
+
81
+
82
+ def _runtime_package_name_or_exit(project_name: str) -> str:
83
+ try:
84
+ return _normalize_runtime_package_name(project_name)
85
+ except ValueError as exc:
86
+ print_error(str(exc))
87
+ raise SystemExit(1) from exc
88
+
89
+
90
+ TEMPLATES = {
91
+ "adk": {
92
+ "agent.py": '''"""
93
+ {package_name} - ADK Agent
94
+ """
95
+
96
+ import os
97
+ from pathlib import Path
98
+ from dotenv import load_dotenv
99
+
100
+ load_dotenv(Path(__file__).parent.parent / ".env")
101
+
102
+ from google.adk.agents import Agent
103
+ from google.adk.models.lite_llm import LiteLlm
104
+
105
+ model = LiteLlm(
106
+ model=f"openai/{{os.getenv('OPENAI_MODEL_NAME', 'glm-5.2')}}",
107
+ api_base=os.getenv("OPENAI_BASE_URL"),
108
+ api_key=os.getenv("OPENAI_API_KEY"),
109
+ stream=True, # 启用流式输出
110
+ )
111
+
112
+
113
+ def hello(name: str) -> dict:
114
+ """问候工具
115
+
116
+ Args:
117
+ name: 名字
118
+
119
+ Returns:
120
+ 问候语
121
+ """
122
+ return {{"message": f"你好, {{name}}!"}}
123
+
124
+
125
+ root_agent = Agent(
126
+ name="{package_name}",
127
+ model=model,
128
+ description="ADK示例 Agent",
129
+ instruction="你是一个友好的助手。请用中文回复。",
130
+ tools=[hello],
131
+ )
132
+ ''',
133
+ },
134
+ "langchain": {
135
+ "agent.py": '''"""
136
+ {package_name} - LangChain Agent
137
+ """
138
+
139
+ import os
140
+ from pathlib import Path
141
+ from dotenv import load_dotenv
142
+
143
+ load_dotenv(Path(__file__).parent.parent / ".env")
144
+
145
+ from langchain_openai import ChatOpenAI
146
+ from langchain_core.prompts import ChatPromptTemplate
147
+ from langchain_core.output_parsers import StrOutputParser
148
+
149
+ llm = ChatOpenAI(
150
+ model=os.getenv("OPENAI_MODEL_NAME", "glm-5.2"),
151
+ base_url=os.getenv("OPENAI_BASE_URL"),
152
+ api_key=os.getenv("OPENAI_API_KEY"),
153
+ streaming=True,
154
+ )
155
+
156
+ prompt = ChatPromptTemplate.from_messages([
157
+ ("system", "你是一个友好的助手。请用中文回复。"),
158
+ ("human", "{{input}}")
159
+ ])
160
+
161
+ root_agent = prompt | llm | StrOutputParser()
162
+ ''',
163
+ },
164
+ "langgraph": {
165
+ "agent.py": '''"""
166
+ {package_name} - LangGraph Agent
167
+ """
168
+
169
+ import os
170
+ from pathlib import Path
171
+ from dotenv import load_dotenv
172
+
173
+ load_dotenv(Path(__file__).parent.parent / ".env")
174
+
175
+ from langchain_openai import ChatOpenAI
176
+ from langgraph.graph import StateGraph, END
177
+ from typing import TypedDict, Annotated
178
+ import operator
179
+
180
+ llm = ChatOpenAI(
181
+ model=os.getenv("OPENAI_MODEL_NAME", "glm-5.2"),
182
+ base_url=os.getenv("OPENAI_BASE_URL"),
183
+ api_key=os.getenv("OPENAI_API_KEY"),
184
+ streaming=True,
185
+ )
186
+
187
+
188
+ class State(TypedDict):
189
+ messages: Annotated[list, operator.add]
190
+
191
+
192
+ def chat(state: State):
193
+ messages = state["messages"]
194
+ response = llm.invoke(messages)
195
+ return {{"messages": [response]}}
196
+
197
+
198
+ graph = StateGraph(State)
199
+ graph.add_node("chat", chat)
200
+ graph.set_entry_point("chat")
201
+ graph.add_edge("chat", END)
202
+
203
+ root_agent = graph.compile()
204
+ ''',
205
+ },
206
+ "deepagents": {
207
+ "agent.py": '''"""
208
+ {package_name} - DeepAgents Agent
209
+ """
210
+
211
+ import os
212
+ from pathlib import Path
213
+ from dotenv import load_dotenv
214
+
215
+ load_dotenv(Path(__file__).parent.parent / ".env")
216
+
217
+ from deepagents import create_deep_agent
218
+ from langchain_openai import ChatOpenAI
219
+
220
+ llm = ChatOpenAI(
221
+ model=os.getenv("OPENAI_MODEL_NAME", "glm-5.2"),
222
+ base_url=os.getenv("OPENAI_BASE_URL"),
223
+ api_key=os.getenv("OPENAI_API_KEY"),
224
+ streaming=True,
225
+ )
226
+
227
+ root_agent = create_deep_agent(model=llm)
228
+ ''',
229
+ },
230
+ "openclaw": {
231
+ "agent.py": '''"""
232
+ {package_name} - OpenClaw placeholder
233
+
234
+ OpenClaw 使用预构建容器运行,当前目录主要用于保存 .env 和 .agentengine.state。
235
+ 此入口仅用于兼容项目模板结构。
236
+ """
237
+
238
+ root_agent = None
239
+ ''',
240
+ },
241
+ }
242
+
243
+
244
+ def _detect_framework(content: str) -> str:
245
+ """检测 Agent 文件使用的框架"""
246
+ if 'from deepagents' in content or 'import deepagents' in content or 'create_deep_agent(' in content:
247
+ return 'deepagents'
248
+ elif 'from langgraph' in content or 'import langgraph' in content:
249
+ return 'langgraph'
250
+ elif 'from google.adk' in content or 'import google.adk' in content:
251
+ return 'adk'
252
+ elif 'from langchain' in content or 'import langchain' in content:
253
+ return 'langchain'
254
+ return 'unknown'
255
+
256
+
257
+ def _detect_agent_variable(content: str) -> str | None:
258
+ """检测 Agent 变量名"""
259
+ import re
260
+
261
+ # 优先级匹配: root_agent > compiled graph > StateGraph > Agent()
262
+ # Only module-level exports are valid entry variables. Service-style
263
+ # projects often build a local `graph` inside init_agent_resources(), which
264
+ # must not be treated as importable module state.
265
+ patterns = [
266
+ (r'^(root_agent)\s*=', 'root_agent'),
267
+ (r'^(root_agent)\s*:\s*[^=]+\s*=', 'root_agent'), # type annotated root_agent
268
+ (r'^(\w+)\s*=\s*\w*graph\w*\.compile\(', None), # e.g., agent = graph.compile()
269
+ (r'^(\w+)\s*=\s*StateGraph', None), # e.g., graph = StateGraph(...)
270
+ (r'^(\w+)\s*=\s*Agent\(', None), # ADK: agent = Agent(...)
271
+ (r'^(\w+)\s*=\s*create_react_agent\(', None), # create_react_agent
272
+ (r'^(\w+)\s*=\s*create_deep_agent\(', None), # deepagents create_deep_agent
273
+ (r'^(\w+)\s*=\s*create_agent\(', None), # langchain create_agent
274
+ (r'^(\w+)\s*=\s*build_agent\(', None), # adapter style build_agent
275
+ ]
276
+
277
+ for pattern, fixed_name in patterns:
278
+ match = re.search(pattern, content, re.MULTILINE | re.IGNORECASE)
279
+ if match:
280
+ return fixed_name if fixed_name else match.group(1)
281
+
282
+ return None
283
+
284
+
285
+ def _has_agent_variable(content: str, agent_var: str) -> bool:
286
+ """Best-effort static check that a module exposes the configured agent variable."""
287
+ import re
288
+
289
+ if not agent_var:
290
+ return False
291
+ escaped = re.escape(agent_var)
292
+ patterns = [
293
+ rf"^{escaped}\s*=",
294
+ rf"^{escaped}\s*:\s*[^=]+=",
295
+ rf"^from\s+[\.\w]+\s+import\s+.*\b{escaped}\b",
296
+ rf"^import\s+[\.\w]+\s+as\s+{escaped}\b",
297
+ ]
298
+ return any(re.search(pattern, content, re.MULTILINE) for pattern in patterns)
299
+
300
+
301
+ def _read_text_sig(path: Path) -> str:
302
+ return path.read_text(encoding="utf-8-sig")
303
+
304
+
305
+ def _entry_exposes_variable(entry_path: Path, agent_var: str) -> bool:
306
+ try:
307
+ content = _read_text_sig(entry_path)
308
+ except Exception:
309
+ return False
310
+ return _has_agent_variable(content, agent_var) or _detect_agent_variable(content) == agent_var
311
+
312
+
313
+ def _load_entry_from_agentengine_yaml(directory: Path) -> tuple[Path, str] | None:
314
+ """
315
+ 从已有 agentengine.yaml 读取入口信息
316
+ 返回 (入口文件路径, Agent 变量名) 或 None
317
+ """
318
+ config_path = directory / "agentengine.yaml"
319
+ if not config_path.exists():
320
+ return None
321
+
322
+ try:
323
+ import yaml # type: ignore
324
+
325
+ config = yaml.safe_load(config_path.read_text(encoding="utf-8-sig")) or {}
326
+ except Exception:
327
+ return None
328
+
329
+ entry_point = config.get("entry_point")
330
+ if not entry_point or not isinstance(entry_point, str):
331
+ return None
332
+
333
+ # 兼容 Windows 路径分隔符
334
+ entry_path = directory / Path(entry_point.replace("\\", "/"))
335
+ if not entry_path.exists() or not entry_path.is_file():
336
+ return None
337
+
338
+ agent_var = config.get("agent_variable")
339
+ if not isinstance(agent_var, str) or not agent_var.strip():
340
+ agent_var = "root_agent"
341
+
342
+ if not _entry_exposes_variable(entry_path, agent_var):
343
+ return None
344
+
345
+ return entry_path, agent_var
346
+
347
+
348
+ def _load_entry_from_langgraph_json(directory: Path) -> tuple[Path, str] | None:
349
+ """Read LangGraph's graph spec and return the first valid local graph target."""
350
+ config_path = directory / "langgraph.json"
351
+ if not config_path.exists():
352
+ return None
353
+
354
+ try:
355
+ config = json.loads(config_path.read_text(encoding="utf-8-sig"))
356
+ except Exception:
357
+ return None
358
+
359
+ graphs = config.get("graphs")
360
+ if not isinstance(graphs, dict):
361
+ return None
362
+
363
+ for target in graphs.values():
364
+ if not isinstance(target, str) or ":" not in target:
365
+ continue
366
+ path_part, agent_var = target.rsplit(":", 1)
367
+ path_part = path_part.strip().removeprefix("./")
368
+ agent_var = agent_var.strip() or "root_agent"
369
+ entry_path = directory / Path(path_part.replace("\\", "/"))
370
+ if entry_path.exists() and entry_path.is_file() and _entry_exposes_variable(entry_path, agent_var):
371
+ return entry_path, agent_var
372
+ return None
373
+
374
+
375
+ def _load_framework_from_agentengine_yaml(directory: Path) -> str | None:
376
+ """从已有 agentengine.yaml 读取 framework"""
377
+ config_path = directory / "agentengine.yaml"
378
+ if not config_path.exists():
379
+ return None
380
+
381
+ try:
382
+ import yaml # type: ignore
383
+
384
+ config = yaml.safe_load(config_path.read_text(encoding="utf-8-sig")) or {}
385
+ except Exception:
386
+ return None
387
+
388
+ framework = config.get("framework")
389
+ if not isinstance(framework, str):
390
+ return None
391
+ framework = framework.strip().lower()
392
+ if framework in {"adk", "langchain", "langgraph", "deepagents", "openclaw", "hermes"}:
393
+ return framework
394
+ return None
395
+
396
+
397
+ def _iter_python_files(directory: Path):
398
+ """递归迭代目录下的 Python 文件(排除常见无关目录)"""
399
+ excluded_dirs = {
400
+ ".git",
401
+ ".venv",
402
+ "venv",
403
+ "__pycache__",
404
+ ".pytest_cache",
405
+ ".mypy_cache",
406
+ ".ruff_cache",
407
+ ".tox",
408
+ "node_modules",
409
+ }
410
+
411
+ for py_file in directory.rglob("*.py"):
412
+ rel_parts = py_file.relative_to(directory).parts
413
+ if any(part in excluded_dirs or part.startswith(".venv") for part in rel_parts):
414
+ continue
415
+ yield py_file
416
+
417
+
418
+ def _find_entry_file(directory: Path) -> tuple[Path, str] | None:
419
+ """
420
+ 在目录中查找入口文件
421
+ 返回 (入口文件路径, Agent 变量名) 或 None
422
+ """
423
+ # 1) 优先读取已有 agentengine.yaml(兼容已配置项目)
424
+ config_entry = _load_entry_from_agentengine_yaml(directory)
425
+ if config_entry:
426
+ entry_path, configured_var = config_entry
427
+ try:
428
+ content = entry_path.read_text(encoding="utf-8")
429
+ detected_var = _detect_agent_variable(content)
430
+ return (entry_path, detected_var or configured_var)
431
+ except Exception:
432
+ return (entry_path, configured_var)
433
+
434
+ # 2) LangGraph/DeepAgents Studio projects often declare the true graph in langgraph.json.
435
+ graph_entry = _load_entry_from_langgraph_json(directory)
436
+ if graph_entry:
437
+ return graph_entry
438
+
439
+ # 3) Service-style DeepAgents projects may only expose their graph through
440
+ # async init_agent_resources() and FastAPI lifespan. Return the init module
441
+ # so the wrapper can generate an AgentEngine adapter instead of pointing at
442
+ # a non-existent top-level graph variable.
443
+ service_entry = _find_deepagents_service_entry(directory)
444
+ if service_entry:
445
+ return service_entry
446
+
447
+ # 4) 按候选文件名递归查找(agent.py/main.py/app.py/...)
448
+ entry_candidates = ["agent.py", "main.py", "app.py", "agentengine_adapter.py", "__init__.py"]
449
+ for candidate in entry_candidates:
450
+ candidate_files = sorted(
451
+ (p for p in _iter_python_files(directory) if p.name == candidate),
452
+ key=lambda p: (len(p.relative_to(directory).parts), str(p)),
453
+ )
454
+ for entry_path in candidate_files:
455
+ try:
456
+ content = entry_path.read_text(encoding="utf-8")
457
+ except Exception:
458
+ continue
459
+ agent_var = _detect_agent_variable(content)
460
+ if agent_var:
461
+ return (entry_path, agent_var)
462
+
463
+ # 5) 全量扫描,按得分选择最可能入口
464
+ best_match: tuple[int, Path, str] | None = None
465
+ for py_file in _iter_python_files(directory):
466
+ if py_file.name.startswith("_") and py_file.name != "__init__.py":
467
+ continue
468
+ try:
469
+ content = py_file.read_text(encoding="utf-8")
470
+ except Exception:
471
+ continue
472
+
473
+ agent_var = _detect_agent_variable(content)
474
+ if not agent_var:
475
+ continue
476
+
477
+ rel = py_file.relative_to(directory)
478
+ depth = len(rel.parts)
479
+ score = 0
480
+ if agent_var == "root_agent":
481
+ score += 100
482
+ if py_file.name in entry_candidates:
483
+ score += 50
484
+ if "src" in rel.parts:
485
+ score += 20
486
+ score -= depth
487
+
488
+ if best_match is None or score > best_match[0]:
489
+ best_match = (score, py_file, agent_var)
490
+
491
+ if best_match:
492
+ return (best_match[1], best_match[2])
493
+
494
+ return None
495
+
496
+
497
+ def _fix_dotenv_paths_in_file(file_path: Path, depth: int = 1) -> bool:
498
+ """
499
+ 修复单个文件中的 load_dotenv 路径
500
+ depth: 文件相对于项目根目录的深度(用于确定需要多少个 .parent)
501
+ 返回是否修改了文件
502
+ """
503
+ import re
504
+
505
+ try:
506
+ content = file_path.read_text(encoding='utf-8')
507
+ except Exception:
508
+ return False
509
+
510
+ # 构建替换的 parent 链
511
+ parent_chain = '.parent' * depth
512
+
513
+ # 匹配各种 load_dotenv 模式
514
+ patterns = [
515
+ # load_dotenv(Path(__file__).parent / ".env")
516
+ (r'(load_dotenv\s*\(\s*Path\s*\(\s*__file__\s*\)\s*)\.parent(\s*/\s*["\']\.env["\'])',
517
+ rf'\1{parent_chain}\2'),
518
+ # load_dotenv(Path(__file__).parent.parent / ".env") -> 根据 depth 调整
519
+ ]
520
+
521
+ modified = False
522
+ new_content = content
523
+
524
+ for pattern, replacement in patterns:
525
+ new_content_temp = re.sub(pattern, replacement, new_content)
526
+ if new_content_temp != new_content:
527
+ new_content = new_content_temp
528
+ modified = True
529
+
530
+ if modified:
531
+ file_path.write_text(new_content, encoding='utf-8')
532
+
533
+ return modified
534
+
535
+
536
+ def _fix_dotenv_paths_recursive(directory: Path, depth: int = 1) -> int:
537
+ """
538
+ 递归修复目录中所有 .py 文件的 load_dotenv 路径
539
+ 返回修复的文件数量
540
+ """
541
+ fixed_count = 0
542
+
543
+ for py_file in directory.rglob('*.py'):
544
+ # 计算相对深度
545
+ rel_path = py_file.relative_to(directory)
546
+ file_depth = len(rel_path.parts) + depth - 1 # 相对于项目根目录
547
+
548
+ if _fix_dotenv_paths_in_file(py_file, file_depth):
549
+ fixed_count += 1
550
+
551
+ return fixed_count
552
+
553
+
554
+ def _fix_nested_imports(file_path: Path, subdirs: list[str]) -> bool:
555
+ """
556
+ 修复文件中的嵌套目录导入
557
+
558
+ 当源目录包含子包目录(如 my_agent/)时,
559
+ 入口文件中的 `from my_agent import xxx` 需要改为 `from .my_agent import xxx`
560
+
561
+ Args:
562
+ file_path: 需要修复的文件
563
+ subdirs: 同级子目录名列表
564
+
565
+ Returns:
566
+ 是否修改了文件
567
+ """
568
+ import re
569
+
570
+ if not subdirs:
571
+ return False
572
+
573
+ try:
574
+ content = file_path.read_text(encoding='utf-8')
575
+ except Exception:
576
+ return False
577
+
578
+ original = content
579
+
580
+ # 为每个子目录生成修复规则
581
+ for subdir in subdirs:
582
+ # from my_agent import xxx -> from .my_agent import xxx
583
+ pattern = rf'^(from\s+)({re.escape(subdir)})(\s+import\s+)'
584
+ replacement = rf'\1.\2\3'
585
+ content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
586
+
587
+ # import my_agent -> from . import my_agent
588
+ # 只处理独立的 import 语句
589
+ pattern_import = rf'^import\s+({re.escape(subdir)})\s*$'
590
+ replacement_import = rf'from . import \1'
591
+ content = re.sub(pattern_import, replacement_import, content, flags=re.MULTILINE)
592
+
593
+ if content != original:
594
+ file_path.write_text(content, encoding='utf-8')
595
+ return True
596
+
597
+ return False
598
+
599
+
600
+ def _generate_env_content(global_env: dict) -> str:
601
+ """生成 .env 文件内容"""
602
+ api_key = global_env.get("OPENAI_API_KEY", "")
603
+ base_url = global_env.get("OPENAI_BASE_URL", "")
604
+ model_name = global_env.get("OPENAI_MODEL_NAME", "")
605
+ ks_ak = global_env.get("KSYUN_ACCESS_KEY", "")
606
+ ks_sk = global_env.get("KSYUN_SECRET_KEY", "")
607
+ ks_region = global_env.get("KSYUN_REGION", "cn-beijing-6")
608
+ ks_account = global_env.get("KSYUN_ACCOUNT_ID", "")
609
+
610
+ env_content = f"""# 模型配置
611
+ OPENAI_API_KEY={api_key}
612
+ """
613
+ if base_url:
614
+ env_content += f"OPENAI_BASE_URL={base_url}\n"
615
+ else:
616
+ env_content += "# OPENAI_BASE_URL=\n"
617
+ if model_name:
618
+ env_content += f"OPENAI_MODEL_NAME={model_name}\n"
619
+ else:
620
+ env_content += "# OPENAI_MODEL_NAME=\n"
621
+
622
+ env_content += f"""
623
+ # 金山云配置
624
+ """
625
+ if ks_ak:
626
+ env_content += f"KSYUN_ACCESS_KEY={ks_ak}\n"
627
+ else:
628
+ env_content += "# KSYUN_ACCESS_KEY=\n"
629
+ if ks_sk:
630
+ env_content += f"KSYUN_SECRET_KEY={ks_sk}\n"
631
+ else:
632
+ env_content += "# KSYUN_SECRET_KEY=\n"
633
+ env_content += f"KSYUN_REGION={ks_region}\n"
634
+ if ks_account:
635
+ env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
636
+ else:
637
+ env_content += "# KSYUN_ACCOUNT_ID=\n"
638
+
639
+ return env_content
640
+
641
+
642
+ def _generate_requirements_from_imports(directory: Path, framework: str) -> str:
643
+ """
644
+ 扫描目录中的 Python 文件,从 import 语句生成 requirements.txt
645
+ """
646
+ import re
647
+
648
+ # 常用包名到 PyPI 包名的映射
649
+ import_to_package = {
650
+ 'langchain': 'langchain',
651
+ 'langchain_openai': 'langchain-openai',
652
+ 'langchain_anthropic': 'langchain-anthropic',
653
+ 'langchain_core': 'langchain-core',
654
+ 'langchain_community': 'langchain-community',
655
+ 'langgraph': 'langgraph',
656
+ 'deepagents': 'deepagents',
657
+ 'openai': 'openai',
658
+ 'anthropic': 'anthropic',
659
+ 'dotenv': 'python-dotenv',
660
+ 'pydantic': 'pydantic',
661
+ 'httpx': 'httpx',
662
+ 'requests': 'requests',
663
+ 'google.adk': 'google-adk',
664
+ 'google.genai': 'google-genai',
665
+ 'tiktoken': 'tiktoken',
666
+ 'faiss': 'faiss-cpu',
667
+ 'chromadb': 'chromadb',
668
+ 'tavily': 'tavily-python',
669
+ }
670
+
671
+ found_packages = set()
672
+
673
+ # 扫描所有 .py 文件
674
+ for py_file in directory.rglob('*.py'):
675
+ try:
676
+ content = py_file.read_text(encoding='utf-8')
677
+ except Exception:
678
+ continue
679
+
680
+ # 匹配 import 语句
681
+ # from xxx import yyy
682
+ # import xxx
683
+ import_patterns = [
684
+ r'^from\s+([\w\.]+)',
685
+ r'^import\s+([\w\.]+)',
686
+ ]
687
+
688
+ for pattern in import_patterns:
689
+ for match in re.finditer(pattern, content, re.MULTILINE):
690
+ module = match.group(1).split('.')[0] # 取顶级模块
691
+ full_module = match.group(1)
692
+
693
+ # 检查是否在映射中
694
+ if full_module in import_to_package:
695
+ found_packages.add(import_to_package[full_module])
696
+ elif module in import_to_package:
697
+ found_packages.add(import_to_package[module])
698
+ # 检查常见的下划线包名
699
+ elif module.replace('_', '-') in ['langchain-openai', 'langchain-anthropic',
700
+ 'langchain-core', 'langchain-community']:
701
+ found_packages.add(module.replace('_', '-'))
702
+
703
+ # 确保框架相关的核心依赖在列表中
704
+ if framework == 'langgraph':
705
+ found_packages.add('langgraph')
706
+ found_packages.add('langchain')
707
+ found_packages.add('langchain-openai')
708
+ elif framework == 'deepagents':
709
+ found_packages.add('deepagents')
710
+ found_packages.add('langgraph')
711
+ found_packages.add('langchain')
712
+ found_packages.add('langchain-openai')
713
+ elif framework == 'langchain':
714
+ found_packages.add('langchain')
715
+ found_packages.add('langchain-openai')
716
+ elif framework == 'adk':
717
+ found_packages.add('google-adk')
718
+
719
+ # 总是添加 python-dotenv
720
+ found_packages.add('python-dotenv')
721
+
722
+ # 按字母顺序排序
723
+ sorted_packages = sorted(found_packages)
724
+ return '\n'.join(sorted_packages) + '\n'
725
+
726
+
727
+ def _analyze_langgraph_state(content: str) -> dict:
728
+ """Best-effort LangGraph state shape detection for adapter scaffolding."""
729
+ import re
730
+
731
+ message_markers = (
732
+ "MessagesState",
733
+ "add_messages",
734
+ 'state["messages"]',
735
+ "state['messages']",
736
+ '"messages":',
737
+ "'messages':",
738
+ "messages:",
739
+ )
740
+ if any(marker in content for marker in message_markers):
741
+ return {"kind": "messages", "input_field": None}
742
+
743
+ state_keys = set(re.findall(r"state\s*\[\s*[\"']([A-Za-z_][A-Za-z0-9_]*)[\"']\s*\]", content))
744
+ typed_fields = set(
745
+ re.findall(
746
+ r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(?:str|Optional\[str\]|list|dict|Any)",
747
+ content,
748
+ flags=re.MULTILINE,
749
+ )
750
+ )
751
+ candidates = [key for key in [*state_keys, *typed_fields] if key != "messages"]
752
+ preferred = ("query", "question", "prompt", "user_input", "input")
753
+ input_field = next((field for field in preferred if field in candidates), None)
754
+
755
+ if candidates and ("TypedDict" in content or "StateGraph(" in content or "langgraph" in content):
756
+ return {"kind": "custom", "input_field": input_field}
757
+ if "StateGraph(" in content or "from langgraph" in content or "import langgraph" in content:
758
+ return {"kind": "ambiguous", "input_field": input_field}
759
+ return {"kind": "unknown", "input_field": None}
760
+
761
+
762
+ def _langgraph_analysis_for_path(path: Path) -> dict:
763
+ if path.is_dir():
764
+ chunks: list[str] = []
765
+ skip_dirs = {".git", "__pycache__", ".venv", "venv", "env", ".mypy_cache", ".pytest_cache"}
766
+ for py_file in sorted(path.rglob("*.py")):
767
+ if any(part in skip_dirs for part in py_file.parts):
768
+ continue
769
+ try:
770
+ chunks.append(py_file.read_text(encoding="utf-8"))
771
+ except Exception:
772
+ return {"kind": "ambiguous", "input_field": None}
773
+ if chunks:
774
+ return _analyze_langgraph_state("\n\n".join(chunks))
775
+ return {"kind": "ambiguous", "input_field": None}
776
+
777
+ try:
778
+ content = path.read_text(encoding="utf-8")
779
+ except Exception:
780
+ return {"kind": "ambiguous", "input_field": None}
781
+ return _analyze_langgraph_state(content)
782
+
783
+
784
+ def _generate_langgraph_adapter_content(
785
+ *,
786
+ import_module: str,
787
+ agent_var: str,
788
+ analysis: dict,
789
+ ) -> str:
790
+ input_field = analysis.get("input_field")
791
+ if input_field:
792
+ return_body = f''' return {{
793
+ "{input_field}": payload.get("input", ""),
794
+ }}'''
795
+ else:
796
+ return_body = ''' # TODO: Map AgentEngine's chat payload to your LangGraph State.
797
+ # Common examples:
798
+ # return {"query": payload.get("input", "")}
799
+ # return {"question": payload.get("input", ""), "context": []}
800
+ return dict(payload)'''
801
+
802
+ return f'''"""
803
+ AgentEngine adapter generated for a LangGraph project.
804
+
805
+ Review ksadk_prepare_state if your graph uses a custom State TypedDict.
806
+ """
807
+
808
+ from {import_module} import {agent_var} as root_agent
809
+
810
+
811
+ def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
812
+ """Map AgentEngine chat input to the LangGraph State expected by root_agent."""
813
+ _ = session_context
814
+ {return_body}
815
+ '''
816
+
817
+
818
+ def _write_langgraph_adapter(
819
+ *,
820
+ package_dir: Path,
821
+ original_entry_relative: Path,
822
+ agent_var: str,
823
+ analysis: dict,
824
+ ) -> Path | None:
825
+ if analysis.get("kind") not in {"custom", "ambiguous"}:
826
+ return None
827
+
828
+ adapter_name = "agentengine_adapter.py"
829
+ if original_entry_relative.as_posix() == adapter_name:
830
+ adapter_name = "ksadk_agentengine_adapter.py"
831
+ adapter_relative = Path(adapter_name)
832
+ original_module = "." + ".".join(original_entry_relative.with_suffix("").parts)
833
+ adapter_content = _generate_langgraph_adapter_content(
834
+ import_module=original_module,
835
+ agent_var=agent_var,
836
+ analysis=analysis,
837
+ )
838
+ (package_dir / adapter_relative).write_text(adapter_content, encoding="utf-8")
839
+ return adapter_relative
840
+
841
+
842
+ def _find_python_file_containing(directory: Path, needle: str) -> Path | None:
843
+ skip_dirs = {".git", "__pycache__", ".venv", "venv", "env", ".mypy_cache", ".pytest_cache"}
844
+ for py_file in sorted(directory.rglob("*.py")):
845
+ if any(part in skip_dirs for part in py_file.relative_to(directory).parts):
846
+ continue
847
+ try:
848
+ if needle in py_file.read_text(encoding="utf-8-sig"):
849
+ return py_file
850
+ except Exception:
851
+ continue
852
+ return None
853
+
854
+
855
+ def _collect_python_text(directory: Path) -> str:
856
+ try:
857
+ return "\n".join(
858
+ py_file.read_text(encoding="utf-8-sig")
859
+ for py_file in sorted(_iter_python_files(directory))
860
+ )
861
+ except Exception:
862
+ return ""
863
+
864
+
865
+ def _find_deepagents_service_entry(directory: Path) -> tuple[Path, str] | None:
866
+ init_file = _find_python_file_containing(directory, "init_agent_resources")
867
+ if not init_file:
868
+ return None
869
+
870
+ project_text = _collect_python_text(directory)
871
+ service_markers = ("FastAPI(", "lifespan=", "add_routes(", "DeepAgentRunnable")
872
+ if "deepagents" not in project_text and "create_deep_agent(" not in project_text:
873
+ return None
874
+ if not any(marker in project_text for marker in service_markers):
875
+ return None
876
+
877
+ return init_file, "root_agent"
878
+
879
+
880
+ def _detect_deepagents_service_project(
881
+ *,
882
+ package_dir: Path,
883
+ entry_relative: Path,
884
+ agent_var: str,
885
+ ) -> dict | None:
886
+ """Detect service-style DeepAgents projects that initialize the graph in app lifespan."""
887
+ entry_path = package_dir / entry_relative
888
+ try:
889
+ entry_content = _read_text_sig(entry_path)
890
+ except Exception:
891
+ entry_content = ""
892
+
893
+ if _has_agent_variable(entry_content, agent_var):
894
+ return None
895
+
896
+ init_file = _find_python_file_containing(package_dir, "init_agent_resources")
897
+ if not init_file:
898
+ return None
899
+
900
+ project_text = _collect_python_text(package_dir) or entry_content
901
+
902
+ service_markers = ("FastAPI(", "lifespan=", "add_routes(", "DeepAgentRunnable")
903
+ if "deepagents" not in project_text and "create_deep_agent(" not in project_text:
904
+ return None
905
+ if not any(marker in project_text for marker in service_markers):
906
+ return None
907
+
908
+ runnable_file = _find_python_file_containing(package_dir, "class DeepAgentRunnable")
909
+ return {
910
+ "init_module": "." + ".".join(init_file.relative_to(package_dir).with_suffix("").parts),
911
+ "runnable_module": (
912
+ "." + ".".join(runnable_file.relative_to(package_dir).with_suffix("").parts)
913
+ if runnable_file
914
+ else None
915
+ ),
916
+ }
917
+
918
+
919
+ def _generate_deepagents_service_adapter_content(analysis: dict) -> str:
920
+ init_module = analysis["init_module"]
921
+ runnable_module = analysis.get("runnable_module")
922
+ runnable_module_constant = f'RUNNABLE_MODULE = "{runnable_module}"' if runnable_module else "RUNNABLE_MODULE = None"
923
+ runnable_build = " return None\n"
924
+ if runnable_module:
925
+ runnable_build = """ try:
926
+ runnable_module = importlib.import_module(RUNNABLE_MODULE, __package__)
927
+ deep_agent_runnable = getattr(runnable_module, "DeepAgentRunnable")
928
+ return deep_agent_runnable(agent, _NullLangfuseManager())
929
+ except Exception:
930
+ return None
931
+ """
932
+
933
+ return f'''"""
934
+ AgentEngine adapter generated for a service-style DeepAgents project.
935
+
936
+ It preserves the user's async resource initialization and exposes a root_agent
937
+ object that ksadk can load through the DeepAgents/LangGraph runner.
938
+ """
939
+
940
+ import asyncio
941
+ import importlib
942
+ from typing import Any
943
+
944
+ try:
945
+ from langchain_core.callbacks import BaseCallbackHandler
946
+ except Exception: # pragma: no cover - dependency may be absent during static checks
947
+ BaseCallbackHandler = object
948
+
949
+ INIT_MODULE = "{init_module}"
950
+ {runnable_module_constant}
951
+
952
+ class _NoopCallbackHandler(BaseCallbackHandler):
953
+ pass
954
+
955
+
956
+ class _NullLangfuseManager:
957
+ callback_handler = _NoopCallbackHandler()
958
+
959
+
960
+ class AgentEngineDeepAgentsServiceAdapter:
961
+ def __init__(self) -> None:
962
+ self._lock = asyncio.Lock()
963
+ self._initialized = False
964
+ self._agent = None
965
+ self._runnable = None
966
+ self._resources = ()
967
+
968
+ async def _ensure_initialized(self) -> None:
969
+ if self._initialized:
970
+ return
971
+ async with self._lock:
972
+ if self._initialized:
973
+ return
974
+ init_module = importlib.import_module(INIT_MODULE, __package__)
975
+ init_agent_resources = getattr(init_module, "init_agent_resources")
976
+ resources = await init_agent_resources()
977
+ if isinstance(resources, tuple):
978
+ self._agent = resources[0]
979
+ self._resources = resources[1:]
980
+ else:
981
+ self._agent = resources
982
+ self._resources = ()
983
+ self._runnable = self._build_runnable(self._agent)
984
+ self._initialized = True
985
+
986
+ def _build_runnable(self, agent: Any) -> Any:
987
+ {runnable_build}
988
+
989
+ @staticmethod
990
+ def _message_from_payload(payload: Any) -> str:
991
+ if isinstance(payload, dict):
992
+ if payload.get("message") is not None:
993
+ return str(payload.get("message") or "")
994
+ if payload.get("input") is not None:
995
+ return str(payload.get("input") or "")
996
+ messages = payload.get("messages")
997
+ if isinstance(messages, list) and messages:
998
+ last = messages[-1]
999
+ content = getattr(last, "content", None)
1000
+ if content is None and isinstance(last, dict):
1001
+ content = last.get("content")
1002
+ return str(content or "")
1003
+ return str(payload or "")
1004
+
1005
+ @staticmethod
1006
+ def _session_id_from_payload(payload: Any, config: dict | None) -> str | None:
1007
+ if isinstance(payload, dict):
1008
+ value = payload.get("session_id") or payload.get("thread_id")
1009
+ if value:
1010
+ return str(value)
1011
+ configurable = (config or {{}}).get("configurable", {{}})
1012
+ value = configurable.get("thread_id") if isinstance(configurable, dict) else None
1013
+ return str(value) if value else None
1014
+
1015
+ @staticmethod
1016
+ def _normalize_result(result: Any) -> dict:
1017
+ if isinstance(result, dict):
1018
+ if "output" in result:
1019
+ return result
1020
+ if "response" in result:
1021
+ return {{"output": result.get("response"), "raw": result}}
1022
+ if "messages" in result and result["messages"]:
1023
+ last = result["messages"][-1]
1024
+ content = getattr(last, "content", None)
1025
+ if content is None and isinstance(last, dict):
1026
+ content = last.get("content")
1027
+ return {{"output": content, "raw": result}}
1028
+ return {{"output": str(result) if result is not None else "", "raw": result}}
1029
+
1030
+ async def ainvoke(self, payload: Any, config: dict | None = None, **kwargs: Any) -> dict:
1031
+ await self._ensure_initialized()
1032
+ message = self._message_from_payload(payload)
1033
+ session_id = self._session_id_from_payload(payload, config)
1034
+ service_payload = {{"message": message}}
1035
+ if session_id:
1036
+ service_payload["thread_id"] = session_id
1037
+
1038
+ if self._runnable is not None and hasattr(self._runnable, "_ainvoke"):
1039
+ result = await self._runnable._ainvoke(service_payload, config=config, **kwargs)
1040
+ return self._normalize_result(result)
1041
+
1042
+ if hasattr(self._agent, "ainvoke"):
1043
+ result = await self._agent.ainvoke({{"messages": payload.get("messages", [])}} if isinstance(payload, dict) and payload.get("messages") else payload, config=config, **kwargs)
1044
+ return self._normalize_result(result)
1045
+ result = self._agent.invoke(payload, config=config, **kwargs)
1046
+ return self._normalize_result(result)
1047
+
1048
+ def invoke(self, payload: Any, config: dict | None = None, **kwargs: Any) -> dict:
1049
+ try:
1050
+ loop = asyncio.get_running_loop()
1051
+ except RuntimeError:
1052
+ return asyncio.run(self.ainvoke(payload, config=config, **kwargs))
1053
+ raise RuntimeError("Synchronous invoke cannot run while an event loop is already active; use ainvoke instead")
1054
+
1055
+
1056
+ def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
1057
+ message = payload.get("input") or payload.get("message") or ""
1058
+ return {{
1059
+ "message": message,
1060
+ "session_id": session_context.get("session_id"),
1061
+ "history": session_context.get("history", []),
1062
+ }}
1063
+
1064
+
1065
+ root_agent = AgentEngineDeepAgentsServiceAdapter()
1066
+ '''
1067
+
1068
+
1069
+ def _write_deepagents_service_adapter(
1070
+ *,
1071
+ package_dir: Path,
1072
+ original_entry_relative: Path,
1073
+ agent_var: str,
1074
+ ) -> Path | None:
1075
+ analysis = _detect_deepagents_service_project(
1076
+ package_dir=package_dir,
1077
+ entry_relative=original_entry_relative,
1078
+ agent_var=agent_var,
1079
+ )
1080
+ if not analysis:
1081
+ return None
1082
+
1083
+ adapter_name = "agentengine_adapter.py"
1084
+ if original_entry_relative.as_posix() == adapter_name:
1085
+ adapter_name = "ksadk_agentengine_adapter.py"
1086
+ adapter_relative = Path(adapter_name)
1087
+ adapter_content = _generate_deepagents_service_adapter_content(analysis)
1088
+ (package_dir / adapter_relative).write_text(adapter_content, encoding="utf-8")
1089
+ return adapter_relative
1090
+
1091
+
1092
+ def _wrap_agent_file(from_agent_path: Path, project_name: str, framework: str, agent_var: str):
1093
+ """包装单个 Agent 文件到新项目"""
1094
+ project_path = Path(project_name)
1095
+ package_name = _runtime_package_name_or_exit(project_path.name)
1096
+
1097
+ if project_path.exists():
1098
+ print_error(f"目录 '{project_name}' 已存在")
1099
+ raise SystemExit(1)
1100
+
1101
+ print_title("包装 Agent 文件")
1102
+ print_kv("创建项目", project_name)
1103
+ print_kv("框架", framework)
1104
+ print_kv("包装文件", str(from_agent_path))
1105
+ print_kv("Agent 变量", agent_var)
1106
+
1107
+ # 创建目录
1108
+ (project_path / package_name).mkdir(parents=True)
1109
+
1110
+ # 复制源文件并修复 .env 路径
1111
+ source_filename = from_agent_path.name
1112
+ dest_path = project_path / package_name / source_filename
1113
+
1114
+ # 读取源文件内容
1115
+ source_content = from_agent_path.read_text(encoding='utf-8')
1116
+
1117
+ # 修复 load_dotenv 路径:parent -> parent.parent (因为文件被移到了子目录)
1118
+ fixed_content = re.sub(
1119
+ r'(load_dotenv\s*\(\s*Path\s*\(\s*__file__\s*\)\s*\.parent)(\s*/\s*["\']\.env["\'])',
1120
+ r'\1.parent\2',
1121
+ source_content
1122
+ )
1123
+
1124
+ if fixed_content != source_content:
1125
+ print_info("已自动修复 .env 加载路径")
1126
+
1127
+ # 写入目标文件
1128
+ dest_path.write_text(fixed_content, encoding='utf-8')
1129
+
1130
+ # 检测全局配置
1131
+ from ksadk.configs.global_config import (
1132
+ global_config_exists,
1133
+ get_env_from_global_config,
1134
+ )
1135
+
1136
+ global_env = {}
1137
+ if global_config_exists():
1138
+ global_env = get_env_from_global_config()
1139
+ if global_env:
1140
+ print_info("检测到全局配置,已自动填充凭证")
1141
+
1142
+ # 生成 .env
1143
+ (project_path / ".env").write_text(_generate_env_content(global_env), encoding="utf-8-sig")
1144
+
1145
+ # LangGraph custom-state 项目生成 adapter,避免直接猜业务 State 语义。
1146
+ entry_relative = Path(source_filename)
1147
+ export_agent_var = agent_var
1148
+ if framework == "langgraph":
1149
+ analysis = _analyze_langgraph_state(fixed_content)
1150
+ if analysis["kind"] == "messages":
1151
+ print_info("LangGraph state 检测: messages-compatible")
1152
+ elif analysis["kind"] == "custom":
1153
+ adapter_relative = _write_langgraph_adapter(
1154
+ package_dir=project_path / package_name,
1155
+ original_entry_relative=entry_relative,
1156
+ agent_var=agent_var,
1157
+ analysis=analysis,
1158
+ )
1159
+ if adapter_relative:
1160
+ entry_relative = adapter_relative
1161
+ export_agent_var = "root_agent"
1162
+ print_info("LangGraph state 检测: custom-state adapter generated")
1163
+ elif analysis["kind"] == "ambiguous":
1164
+ adapter_relative = _write_langgraph_adapter(
1165
+ package_dir=project_path / package_name,
1166
+ original_entry_relative=entry_relative,
1167
+ agent_var=agent_var,
1168
+ analysis=analysis,
1169
+ )
1170
+ if adapter_relative:
1171
+ entry_relative = adapter_relative
1172
+ export_agent_var = "root_agent"
1173
+ print_warn("LangGraph state 检测: ambiguous adapter generated, review required")
1174
+
1175
+ # 生成 agentengine.yaml
1176
+ entry_module = entry_relative.with_suffix("").name
1177
+ (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置 (Wrapped)
1178
+ name: {package_name}
1179
+ version: "1.0.0"
1180
+
1181
+ framework: {framework}
1182
+ entry_point: {package_name}/{entry_relative}
1183
+ agent_variable: {export_agent_var}
1184
+ """, encoding="utf-8-sig")
1185
+
1186
+ # 生成 __init__.py
1187
+ (project_path / package_name / "__init__.py").write_text(f'''"""
1188
+ {project_name} - Wrapped Agent
1189
+ """
1190
+ from .{entry_module} import {export_agent_var} as root_agent
1191
+ __all__ = ["root_agent"]
1192
+ ''', encoding="utf-8-sig")
1193
+
1194
+ # 生成 requirements.txt
1195
+ reqs = ""
1196
+ if framework == "langchain":
1197
+ reqs = "langchain\nlangchain-openai\npython-dotenv\n"
1198
+ elif framework == "langgraph":
1199
+ reqs = "langchain\nlangchain-openai\nlanggraph\npython-dotenv\n"
1200
+ elif framework == "deepagents":
1201
+ reqs = "deepagents\nlangchain\nlangchain-openai\nlanggraph\npython-dotenv\n"
1202
+ elif framework == "adk":
1203
+ reqs = "google-adk\npython-dotenv\n"
1204
+ (project_path / "requirements.txt").write_text(reqs, encoding="utf-8")
1205
+
1206
+ # 生成 README.md
1207
+ (project_path / "README.md").write_text(f"""# {project_name}
1208
+
1209
+ Wrapped Agent project (from `{from_agent_path.name}`).
1210
+
1211
+ ## Quick Start
1212
+
1213
+ ```bash
1214
+ cd {project_name}
1215
+ agentengine run -i .
1216
+ ```
1217
+ """, encoding="utf-8-sig")
1218
+
1219
+ print_success("包装完成")
1220
+ print_rule("快速开始")
1221
+ _print_quick_start_commands(project_name, ["agentengine run -i ."])
1222
+
1223
+
1224
+ def _wrap_agent_directory(from_agent_dir: Path, project_name: str, framework: str,
1225
+ entry_file: Path, agent_var: str):
1226
+ """包装 Agent 目录到新项目"""
1227
+ import shutil
1228
+
1229
+ project_path = Path(project_name)
1230
+ package_name = _runtime_package_name_or_exit(project_path.name)
1231
+ source_dir_name = from_agent_dir.name
1232
+
1233
+ if project_path.exists():
1234
+ print_error(f"目录 '{project_name}' 已存在")
1235
+ raise SystemExit(1)
1236
+
1237
+ print_title("包装 Agent 目录")
1238
+ print_kv("创建项目", project_name)
1239
+ print_kv("框架", framework)
1240
+ print_kv("包装目录", str(from_agent_dir))
1241
+ print_kv("入口文件", entry_file.name)
1242
+ print_kv("Agent 变量", agent_var)
1243
+
1244
+ # 创建项目目录
1245
+ project_path.mkdir(parents=True)
1246
+
1247
+ # 复制整个源目录到项目中(作为 package)
1248
+ dest_package_path = project_path / package_name
1249
+ ignore_names = {
1250
+ ".git",
1251
+ ".venv",
1252
+ "venv",
1253
+ "__pycache__",
1254
+ ".pytest_cache",
1255
+ ".mypy_cache",
1256
+ ".ruff_cache",
1257
+ ".tox",
1258
+ "node_modules",
1259
+ ".idea",
1260
+ ".vscode",
1261
+ ".DS_Store",
1262
+ }
1263
+
1264
+ def _ignore_copytree(_dir: str, names: list[str]):
1265
+ ignored = set()
1266
+ for name in names:
1267
+ if name in ignore_names or name.startswith(".venv"):
1268
+ ignored.add(name)
1269
+ continue
1270
+ if name.endswith((".pyc", ".pyo")):
1271
+ ignored.add(name)
1272
+ return ignored
1273
+
1274
+ shutil.copytree(from_agent_dir, dest_package_path, ignore=_ignore_copytree)
1275
+
1276
+ print_info(f"已复制 {sum(1 for _ in dest_package_path.rglob('*.py'))} 个 Python 文件")
1277
+
1278
+ # 递归修复 .env 路径
1279
+ fixed_count = _fix_dotenv_paths_recursive(dest_package_path, depth=2)
1280
+ if fixed_count > 0:
1281
+ print_info(f"已自动修复 {fixed_count} 个文件的 .env 加载路径")
1282
+
1283
+ # 修复嵌套目录导入路径
1284
+ # 查找源目录中的子目录(作为 Python 包)
1285
+ subdirs = [d.name for d in from_agent_dir.iterdir()
1286
+ if d.is_dir() and not d.name.startswith('.') and not d.name.startswith('_')
1287
+ and (d / '__init__.py').exists()]
1288
+
1289
+ if subdirs:
1290
+ # 修复入口文件中的导入
1291
+ dest_entry_file = dest_package_path / entry_file.relative_to(from_agent_dir)
1292
+ if _fix_nested_imports(dest_entry_file, subdirs):
1293
+ print_info(f"已修复嵌套目录导入: {', '.join(subdirs)}")
1294
+
1295
+
1296
+ # 检测全局配置
1297
+ from ksadk.configs.global_config import (
1298
+ global_config_exists,
1299
+ get_env_from_global_config,
1300
+ )
1301
+
1302
+ global_env = {}
1303
+ if global_config_exists():
1304
+ global_env = get_env_from_global_config()
1305
+ if global_env:
1306
+ print_info("检测到全局配置,已自动填充凭证")
1307
+
1308
+ # 生成 .env
1309
+ (project_path / ".env").write_text(_generate_env_content(global_env), encoding="utf-8-sig")
1310
+
1311
+ # 生成 agentengine.yaml
1312
+ # entry_point 相对于项目根目录
1313
+ entry_relative = entry_file.relative_to(from_agent_dir)
1314
+ export_agent_var = agent_var
1315
+ if framework == "deepagents":
1316
+ adapter_relative = _write_deepagents_service_adapter(
1317
+ package_dir=dest_package_path,
1318
+ original_entry_relative=entry_relative,
1319
+ agent_var=agent_var,
1320
+ )
1321
+ if adapter_relative:
1322
+ entry_relative = adapter_relative
1323
+ export_agent_var = "root_agent"
1324
+ print_info("DeepAgents service adapter generated")
1325
+
1326
+ if framework == "langgraph":
1327
+ analysis = _langgraph_analysis_for_path(dest_package_path)
1328
+ if analysis["kind"] == "messages":
1329
+ print_info("LangGraph state 检测: messages-compatible")
1330
+ elif analysis["kind"] == "custom":
1331
+ adapter_relative = _write_langgraph_adapter(
1332
+ package_dir=dest_package_path,
1333
+ original_entry_relative=entry_relative,
1334
+ agent_var=agent_var,
1335
+ analysis=analysis,
1336
+ )
1337
+ if adapter_relative:
1338
+ entry_relative = adapter_relative
1339
+ export_agent_var = "root_agent"
1340
+ print_info("LangGraph state 检测: custom-state adapter generated")
1341
+ elif analysis["kind"] == "ambiguous":
1342
+ adapter_relative = _write_langgraph_adapter(
1343
+ package_dir=dest_package_path,
1344
+ original_entry_relative=entry_relative,
1345
+ agent_var=agent_var,
1346
+ analysis=analysis,
1347
+ )
1348
+ if adapter_relative:
1349
+ entry_relative = adapter_relative
1350
+ export_agent_var = "root_agent"
1351
+ print_warn("LangGraph state 检测: ambiguous adapter generated, review required")
1352
+
1353
+ (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置 (Wrapped Directory)
1354
+ name: {package_name}
1355
+ version: "1.0.0"
1356
+
1357
+ framework: {framework}
1358
+ entry_point: {package_name}/{entry_relative}
1359
+ agent_variable: {export_agent_var}
1360
+ """, encoding="utf-8-sig")
1361
+
1362
+ # 确保 __init__.py 正确导出 root_agent
1363
+ init_file = dest_package_path / "__init__.py"
1364
+ entry_module = ".".join(entry_relative.with_suffix("").parts) # e.g. src.agentengine_adapter
1365
+ expected_export_line = f"from .{entry_module} import {export_agent_var} as root_agent"
1366
+
1367
+ # 检查现有 __init__.py 是否已导出 root_agent
1368
+ init_has_export = False
1369
+ if init_file.exists():
1370
+ init_content = init_file.read_text(encoding='utf-8')
1371
+ if expected_export_line in init_content:
1372
+ init_has_export = True
1373
+
1374
+ if not init_has_export:
1375
+ # 追加或创建导出语句
1376
+ export_code = f'''
1377
+ # AgentEngine 导出 (自动添加)
1378
+ {expected_export_line}
1379
+ __all__ = ["root_agent"]
1380
+ '''
1381
+ if init_file.exists():
1382
+ # 追加到现有文件
1383
+ with open(init_file, 'a', encoding='utf-8') as f:
1384
+ f.write(export_code)
1385
+ print_info("已修复 __init__.py 导出")
1386
+ else:
1387
+ init_file.write_text(f'''"""
1388
+ {project_name} - Wrapped Agent
1389
+ """
1390
+ {expected_export_line}
1391
+ __all__ = ["root_agent"]
1392
+ ''', encoding="utf-8-sig")
1393
+
1394
+ # 处理 requirements.txt
1395
+ source_requirements = from_agent_dir / "requirements.txt"
1396
+ dest_requirements = project_path / "requirements.txt"
1397
+
1398
+ if source_requirements.exists():
1399
+ # 如果源目录有 requirements.txt,复制并补充必要的依赖
1400
+ import shutil as shutil_req
1401
+ shutil_req.copy(source_requirements, dest_requirements)
1402
+ print_info("已复制 requirements.txt")
1403
+
1404
+ # 检查是否缺少必要依赖,追加
1405
+ existing_reqs = dest_requirements.read_text(encoding='utf-8').lower()
1406
+ missing = []
1407
+ if framework == "langgraph" and 'langgraph' not in existing_reqs:
1408
+ missing.append("langgraph")
1409
+ if framework == "deepagents":
1410
+ if 'deepagents' not in existing_reqs:
1411
+ missing.append("deepagents")
1412
+ if 'langgraph' not in existing_reqs:
1413
+ missing.append("langgraph")
1414
+ if 'python-dotenv' not in existing_reqs and 'dotenv' not in existing_reqs:
1415
+ missing.append("python-dotenv")
1416
+
1417
+ if missing:
1418
+ with open(dest_requirements, 'a', encoding='utf-8') as f:
1419
+ f.write("\n# Added by agentengine\n")
1420
+ for pkg in missing:
1421
+ f.write(f"{pkg}\n")
1422
+ print_info(f"已补充依赖: {', '.join(missing)}")
1423
+ else:
1424
+ # 自动根据 import 生成 requirements.txt
1425
+ reqs = _generate_requirements_from_imports(dest_package_path, framework)
1426
+ dest_requirements.write_text(reqs, encoding="utf-8")
1427
+ print_info(f"已自动生成 requirements.txt ({reqs.count(chr(10))} 个依赖)")
1428
+
1429
+ # 生成 README.md
1430
+ (project_path / "README.md").write_text(f"""# {project_name}
1431
+
1432
+ Wrapped Agent project (from `{from_agent_dir.name}/`).
1433
+
1434
+ ## Quick Start
1435
+
1436
+ ```bash
1437
+ cd {project_name}
1438
+ agentengine run -i .
1439
+ ```
1440
+ """, encoding="utf-8-sig")
1441
+
1442
+ # === 运行时验证 ===
1443
+ print_rule("验证 Agent 加载")
1444
+ import subprocess
1445
+ import sys
1446
+
1447
+ verify_code = f'''
1448
+ import sys
1449
+ sys.path.insert(0, ".")
1450
+ try:
1451
+ from {package_name} import root_agent
1452
+ print("TYPE:" + type(root_agent).__name__)
1453
+ except Exception as e:
1454
+ print("ERROR:" + str(e))
1455
+ sys.exit(1)
1456
+ '''
1457
+
1458
+ result = subprocess.run(
1459
+ [sys.executable, "-c", verify_code],
1460
+ cwd=str(project_path),
1461
+ capture_output=True,
1462
+ text=True,
1463
+ timeout=30
1464
+ )
1465
+
1466
+ if result.returncode == 0:
1467
+ output = result.stdout.strip()
1468
+ if output.startswith("TYPE:"):
1469
+ agent_type = output.split("TYPE:")[1]
1470
+ print_success(f"Agent 加载成功! 类型: {agent_type}")
1471
+ else:
1472
+ print_success("Agent 加载成功!")
1473
+ else:
1474
+ error_msg = result.stderr or result.stdout
1475
+ print_warn(f"Agent 加载警告: {error_msg[:200]}")
1476
+ print_info("提示: 请检查依赖是否已安装,或代码是否有错误")
1477
+
1478
+ print_success("包装完成")
1479
+ print_rule("快速开始")
1480
+ _print_quick_start_commands(project_name, ["agentengine run -i ."])
1481
+
1482
+
1483
+ def _write_hermes_project_template(project_path: Path, project_name: str, package_name: str) -> None:
1484
+ """生成 Hermes container-first 项目骨架。"""
1485
+ repo_template_root = Path(__file__).resolve().parents[2] / "deploy" / "hermes"
1486
+ if repo_template_root.exists():
1487
+ replacements = {
1488
+ "{{PROJECT_NAME}}": project_name,
1489
+ "{{PACKAGE_NAME}}": package_name,
1490
+ }
1491
+ for source in repo_template_root.rglob("*"):
1492
+ relative = source.relative_to(repo_template_root)
1493
+ if source.is_dir():
1494
+ (project_path / relative).mkdir(parents=True, exist_ok=True)
1495
+ continue
1496
+ destination_relative = relative
1497
+ if source.name.endswith(".template"):
1498
+ destination_relative = relative.with_name(source.name[:-9])
1499
+ content = source.read_text(encoding="utf-8")
1500
+ for key, value in replacements.items():
1501
+ content = content.replace(key, value)
1502
+ destination = project_path / destination_relative
1503
+ destination.parent.mkdir(parents=True, exist_ok=True)
1504
+ destination.write_text(content, encoding="utf-8-sig")
1505
+ continue
1506
+ destination = project_path / destination_relative
1507
+ destination.parent.mkdir(parents=True, exist_ok=True)
1508
+ shutil.copy2(source, destination)
1509
+ entrypoint = project_path / "entrypoint.sh"
1510
+ if entrypoint.exists():
1511
+ entrypoint.chmod(0o755)
1512
+ return
1513
+
1514
+ runtime_dir = project_path / "runtime"
1515
+ runtime_dir.mkdir(parents=True, exist_ok=True)
1516
+
1517
+ (project_path / "agentengine.yaml").write_text(f"""# AgentEngine Hermes 项目配置
1518
+ name: {package_name}
1519
+ version: "1.0.0"
1520
+
1521
+ framework: hermes
1522
+ artifact_type: Container
1523
+
1524
+ ui_profile: hermes
1525
+ ui_path: /
1526
+
1527
+ deploy:
1528
+ resources:
1529
+ cpu: "2"
1530
+ memory: "4Gi"
1531
+ scaling:
1532
+ min_replicas: 1
1533
+ max_replicas: 1
1534
+ concurrency: 1000
1535
+ """, encoding="utf-8-sig")
1536
+
1537
+ (project_path / "Dockerfile").write_text("""FROM python:3.12-slim
1538
+
1539
+ ENV PYTHONUNBUFFERED=1 \\
1540
+ PYTHONDONTWRITEBYTECODE=1 \\
1541
+ PIP_NO_CACHE_DIR=1 \\
1542
+ PORT=8080 \\
1543
+ API_SERVER_HOST=127.0.0.1 \\
1544
+ API_SERVER_PORT=8642 \\
1545
+ HERMES_DASHBOARD_HOST=127.0.0.1 \\
1546
+ HERMES_DASHBOARD_PORT=9119
1547
+
1548
+ WORKDIR /app
1549
+
1550
+ RUN apt-get update \\
1551
+ && apt-get install -y --no-install-recommends bash curl tini \\
1552
+ && rm -rf /var/lib/apt/lists/*
1553
+
1554
+ RUN pip install --no-cache-dir \\
1555
+ "fastapi>=0.100" \\
1556
+ "uvicorn[standard]>=0.23" \\
1557
+ "httpx>=0.24" \\
1558
+ "pyyaml>=6.0" \\
1559
+ "python-dotenv>=1.0" \\
1560
+ "hermes-agent==0.9.0"
1561
+
1562
+ COPY runtime ./runtime
1563
+ COPY entrypoint.sh ./entrypoint.sh
1564
+ RUN chmod +x /app/entrypoint.sh
1565
+
1566
+ EXPOSE 8080
1567
+
1568
+ ENTRYPOINT ["tini", "--"]
1569
+ CMD ["/app/entrypoint.sh"]
1570
+ """, encoding="utf-8")
1571
+
1572
+ (project_path / "entrypoint.sh").write_text("""#!/usr/bin/env bash
1573
+ set -euo pipefail
1574
+
1575
+ export HOME="${HOME:-/home/agent}"
1576
+ export PORT="${PORT:-8080}"
1577
+ export API_SERVER_HOST="${API_SERVER_HOST:-127.0.0.1}"
1578
+ export API_SERVER_PORT="${API_SERVER_PORT:-8642}"
1579
+ export HERMES_DASHBOARD_HOST="${HERMES_DASHBOARD_HOST:-127.0.0.1}"
1580
+ export HERMES_DASHBOARD_PORT="${HERMES_DASHBOARD_PORT:-9119}"
1581
+ export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}"
1582
+
1583
+ mkdir -p "${HOME}/.hermes"
1584
+
1585
+ cat > "${HOME}/.hermes/.env" <<EOF
1586
+ OPENAI_API_KEY=${OPENAI_API_KEY:-}
1587
+ OPENAI_BASE_URL=${OPENAI_BASE_URL:-}
1588
+ OPENAI_MODEL_NAME=${OPENAI_MODEL_NAME:-}
1589
+ API_SERVER_ENABLED=${API_SERVER_ENABLED}
1590
+ API_SERVER_KEY=${API_SERVER_KEY:-}
1591
+ API_SERVER_HOST=${API_SERVER_HOST}
1592
+ API_SERVER_PORT=${API_SERVER_PORT}
1593
+ EOF
1594
+
1595
+ cat > "${HOME}/.hermes/config.yaml" <<EOF
1596
+ model:
1597
+ provider: custom
1598
+ default: "${OPENAI_MODEL_NAME:-}"
1599
+ base_url: "${OPENAI_BASE_URL:-}"
1600
+ api_server:
1601
+ enabled: true
1602
+ host: "${API_SERVER_HOST}"
1603
+ port: ${API_SERVER_PORT}
1604
+ EOF
1605
+
1606
+ hermes gateway run --replace &
1607
+ HERMES_API_PID=$!
1608
+
1609
+ hermes dashboard --host "${HERMES_DASHBOARD_HOST}" --port "${HERMES_DASHBOARD_PORT}" --no-open &
1610
+ HERMES_DASHBOARD_PID=$!
1611
+
1612
+ cleanup() {
1613
+ kill "${HERMES_API_PID}" "${HERMES_DASHBOARD_PID}" 2>/dev/null || true
1614
+ }
1615
+ trap cleanup EXIT
1616
+
1617
+ exec uvicorn runtime.app:app --host 0.0.0.0 --port "${PORT}"
1618
+ """, encoding="utf-8")
1619
+
1620
+ (runtime_dir / "__init__.py").write_text("", encoding="utf-8")
1621
+ (runtime_dir / "app.py").write_text("""from __future__ import annotations
1622
+
1623
+ import asyncio
1624
+ import contextlib
1625
+ import json
1626
+ import os
1627
+ import pty
1628
+ import select
1629
+ import signal
1630
+ import termios
1631
+ from typing import Iterable
1632
+
1633
+ import httpx
1634
+ from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
1635
+ from fastapi.responses import Response
1636
+ from starlette.websockets import WebSocketState
1637
+
1638
+
1639
+ TERMINAL_SUBPROTOCOL = "ks-terminal.v1"
1640
+ SHELL_METACHARS = set("|&;<>()$`\\\\\\n\\r")
1641
+ SINGLE_READONLY = {"status", "doctor", "version", "insights"}
1642
+ NESTED_READONLY = {
1643
+ "sessions": {"list": (2, 2), "show": (3, 3), "export": (3, 3)},
1644
+ "config": {"show": (2, 2), "check": (2, 2), "path": (2, 2), "env-path": (2, 2)},
1645
+ "skills": {"list": (2, 2), "audit": (2, 2), "check": (2, 2)},
1646
+ "tools": {"list": (2, 2)},
1647
+ "cron": {"list": (2, 2), "status": (2, 2)},
1648
+ "gateway": {"status": (2, 2)},
1649
+ }
1650
+
1651
+ app = FastAPI()
1652
+
1653
+
1654
+ def _api_base() -> str:
1655
+ return f"http://{os.getenv('API_SERVER_HOST', '127.0.0.1')}:{os.getenv('API_SERVER_PORT', '8642')}"
1656
+
1657
+
1658
+ def _dashboard_base() -> str:
1659
+ return f"http://{os.getenv('HERMES_DASHBOARD_HOST', '127.0.0.1')}:{os.getenv('HERMES_DASHBOARD_PORT', '9119')}"
1660
+
1661
+
1662
+ def _validate_exec_argv(argv: Iterable[str]) -> list[str]:
1663
+ normalized = [str(item).strip() for item in argv]
1664
+ if not normalized:
1665
+ raise ValueError("missing argv")
1666
+ for item in normalized:
1667
+ if not item or item.startswith("-") or any(char in SHELL_METACHARS for char in item):
1668
+ raise ValueError(f"unsafe argv: {item}")
1669
+ if normalized[0] in SINGLE_READONLY:
1670
+ if len(normalized) != 1:
1671
+ raise ValueError("unsupported argv")
1672
+ return normalized
1673
+ nested = NESTED_READONLY.get(normalized[0])
1674
+ if not nested or len(normalized) < 2:
1675
+ raise ValueError("unsupported argv")
1676
+ bounds = nested.get(normalized[1])
1677
+ if not bounds:
1678
+ raise ValueError("unsupported argv")
1679
+ if not (bounds[0] <= len(normalized) <= bounds[1]):
1680
+ raise ValueError("unsupported argv")
1681
+ return normalized
1682
+
1683
+
1684
+ async def _proxy_http(request: Request, base_url: str, path: str) -> Response:
1685
+ target = f"{base_url}/{path.lstrip('/')}"
1686
+ if request.url.query:
1687
+ target = f"{target}?{request.url.query}"
1688
+ headers = {
1689
+ key: value
1690
+ for key, value in request.headers.items()
1691
+ if key.lower() not in {"host", "content-length"}
1692
+ }
1693
+ async with httpx.AsyncClient(timeout=None) as client:
1694
+ upstream = await client.request(
1695
+ request.method,
1696
+ target,
1697
+ headers=headers,
1698
+ content=await request.body(),
1699
+ )
1700
+ response_headers = {
1701
+ key: value
1702
+ for key, value in upstream.headers.items()
1703
+ if key.lower() not in {"content-encoding", "transfer-encoding", "connection"}
1704
+ }
1705
+ return Response(upstream.content, status_code=upstream.status_code, headers=response_headers)
1706
+
1707
+
1708
+ @app.get("/health")
1709
+ async def health() -> dict:
1710
+ return {"ok": True}
1711
+
1712
+
1713
+ @app.api_route("/v1/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
1714
+ async def proxy_api(path: str, request: Request) -> Response:
1715
+ return await _proxy_http(request, _api_base(), f"v1/{path}")
1716
+
1717
+
1718
+ def _set_winsize(fd: int, rows: int, cols: int) -> None:
1719
+ termios.tcsetwinsize(fd, (int(rows or 24), int(cols or 80)))
1720
+
1721
+
1722
+ async def _pty_reader(ws: WebSocket, fd: int) -> None:
1723
+ loop = asyncio.get_running_loop()
1724
+ while True:
1725
+ await loop.run_in_executor(None, lambda: select.select([fd], [], [], None))
1726
+ data = os.read(fd, 4096)
1727
+ if not data:
1728
+ return
1729
+ await ws.send_bytes(data)
1730
+
1731
+
1732
+ async def _wait_process(pid: int) -> int:
1733
+ loop = asyncio.get_running_loop()
1734
+ _, status = await loop.run_in_executor(None, os.waitpid, pid, 0)
1735
+ if os.WIFEXITED(status):
1736
+ return os.WEXITSTATUS(status)
1737
+ if os.WIFSIGNALED(status):
1738
+ return 128 + os.WTERMSIG(status)
1739
+ return status
1740
+
1741
+
1742
+ @app.websocket("/_ksadk/terminal/ws")
1743
+ async def terminal_ws(ws: WebSocket) -> None:
1744
+ if TERMINAL_SUBPROTOCOL not in (ws.headers.get("sec-websocket-protocol") or ""):
1745
+ await ws.close(code=4400, reason="missing ks-terminal.v1 subprotocol")
1746
+ return
1747
+ await ws.accept(subprotocol=TERMINAL_SUBPROTOCOL)
1748
+ pid = None
1749
+ fd = None
1750
+ receive_task = None
1751
+ reader_task = None
1752
+ wait_task = None
1753
+ try:
1754
+ first = await ws.receive_text()
1755
+ payload = json.loads(first)
1756
+ if payload.get("type") != "start":
1757
+ raise ValueError("first frame must be start")
1758
+ mode = payload.get("mode")
1759
+ argv = payload.get("argv") or []
1760
+ if mode == "tui":
1761
+ command = ["hermes", "chat"]
1762
+ elif mode == "exec":
1763
+ command = ["hermes", *_validate_exec_argv(argv)]
1764
+ else:
1765
+ raise ValueError("unsupported mode")
1766
+
1767
+ pid, fd = pty.fork()
1768
+ if pid == 0:
1769
+ os.execvp(command[0], command)
1770
+
1771
+ _set_winsize(fd, int(payload.get("rows") or 24), int(payload.get("cols") or 80))
1772
+ await ws.send_text(json.dumps({"type": "ready"}))
1773
+ reader_task = asyncio.create_task(_pty_reader(ws, fd))
1774
+ wait_task = asyncio.create_task(_wait_process(pid))
1775
+ receive_task = asyncio.create_task(ws.receive())
1776
+
1777
+ while True:
1778
+ done, _pending = await asyncio.wait({wait_task, receive_task}, return_when=asyncio.FIRST_COMPLETED)
1779
+ if wait_task in done:
1780
+ if reader_task:
1781
+ reader_task.cancel()
1782
+ if receive_task:
1783
+ receive_task.cancel()
1784
+ code = wait_task.result()
1785
+ if ws.client_state == WebSocketState.CONNECTED:
1786
+ await ws.send_text(json.dumps({"type": "exit", "code": code}))
1787
+ return
1788
+
1789
+ message = receive_task.result()
1790
+ receive_task = asyncio.create_task(ws.receive())
1791
+ if message.get("bytes") is not None:
1792
+ os.write(fd, message["bytes"])
1793
+ continue
1794
+ text = message.get("text")
1795
+ if not text:
1796
+ continue
1797
+ control = json.loads(text)
1798
+ if control.get("type") == "resize":
1799
+ _set_winsize(fd, int(control.get("rows") or 24), int(control.get("cols") or 80))
1800
+ elif control.get("type") == "signal":
1801
+ sig = signal.SIGINT if control.get("signal") == "SIGINT" else signal.SIGTERM
1802
+ os.kill(pid, sig)
1803
+ elif control.get("type") == "stdin_eof":
1804
+ os.close(fd)
1805
+ fd = None
1806
+ except WebSocketDisconnect:
1807
+ return
1808
+ except Exception as exc:
1809
+ if ws.client_state == WebSocketState.CONNECTED:
1810
+ await ws.send_text(json.dumps({"type": "error", "message": str(exc)}))
1811
+ await ws.close()
1812
+ finally:
1813
+ for task in (receive_task, reader_task):
1814
+ if task and not task.done():
1815
+ task.cancel()
1816
+ if pid is not None and (wait_task is None or not wait_task.done()):
1817
+ with contextlib.suppress(ProcessLookupError):
1818
+ os.kill(pid, signal.SIGTERM)
1819
+ if wait_task and not wait_task.done():
1820
+ wait_task.cancel()
1821
+ if fd is not None:
1822
+ with contextlib.suppress(OSError):
1823
+ os.close(fd)
1824
+
1825
+
1826
+ @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
1827
+ async def proxy_dashboard(path: str, request: Request) -> Response:
1828
+ return await _proxy_http(request, _dashboard_base(), path)
1829
+ """, encoding="utf-8")
1830
+
1831
+ (project_path / "README.md").write_text(f"""# {project_name}
1832
+
1833
+ Hermes AgentEngine container-first 项目。
1834
+
1835
+ ## 快速开始
1836
+
1837
+ ```bash
1838
+ cd {project_name}
1839
+
1840
+ # 1. 编辑 .env,填写模型配置
1841
+ vim .env
1842
+
1843
+ # 2. 部署平台预置 Hermes runtime 镜像
1844
+ agentengine hermes deploy
1845
+
1846
+ # 3. 打开 Hermes 管理 UI
1847
+ agentengine hermes open
1848
+
1849
+ # 4. 进入 pod 内 Hermes 原生 TUI
1850
+ agentengine invoke
1851
+
1852
+ # 5. 使用统一 hosted chat
1853
+ agentengine hermes open --chat
1854
+
1855
+ # 6. 受限只读运维子命令
1856
+ agentengine hermes exec <agent> -- status
1857
+
1858
+ # 7. Pairing 审批透传
1859
+ agentengine hermes pairing <agent> -- list
1860
+ ```
1861
+
1862
+ ## Runtime Contract
1863
+
1864
+ - `/` 反代到 Hermes dashboard 管理 UI
1865
+ - `/chat` 由 AgentEngine hosted UI/router 处理
1866
+ - `/v1/*` 反代到 Hermes OpenAI-compatible API server
1867
+ - `/_ksadk/terminal/ws` 提供 `ks-terminal.v1` 原生 TUI/exec/pairing websocket
1868
+ """, encoding="utf-8-sig")
1869
+
1870
+
1871
+ @click.command(context_settings=dict(help_option_names=['-h', '--help']))
1872
+ @click.argument('project_name', required=False)
1873
+ @click.option('--framework', '-f', type=click.Choice(['adk', 'langchain', 'langgraph', 'deepagents', 'openclaw', 'hermes']),
1874
+ default='langgraph', help='框架类型 (default: langgraph)')
1875
+ @click.option('--from-agent', 'from_agent_path', type=click.Path(exists=True),
1876
+ help='包装现有 Agent 文件或目录')
1877
+ def create(project_name: str, framework: str, from_agent_path: str):
1878
+ """创建新的 Agent 项目
1879
+
1880
+ PROJECT_NAME: 项目名称
1881
+
1882
+ 使用 --from-agent 可包装现有代码:
1883
+ agentengine init --from-agent ./my_agent.py # 单文件
1884
+ agentengine init --from-agent ./my_agent/ # 目录
1885
+ """
1886
+ # === 包装模式 ===
1887
+ if from_agent_path:
1888
+ from_path = Path(from_agent_path)
1889
+
1890
+ # === 目录模式 ===
1891
+ if from_path.is_dir():
1892
+ print_info(f"扫描目录: {from_path}")
1893
+
1894
+ # 查找入口文件
1895
+ entry_result = _find_entry_file(from_path)
1896
+ if not entry_result:
1897
+ print_error("未找到有效的入口文件 (agent.py, main.py, __init__.py 或包含 Agent 定义的文件)")
1898
+ raise SystemExit(1)
1899
+
1900
+ entry_file, detected_var = entry_result
1901
+ print_info(f"检测到入口: {entry_file.name}")
1902
+ print_info(f"检测到变量: {detected_var}")
1903
+
1904
+ # 检测框架:优先读取已有 agentengine.yaml
1905
+ detected_framework = _load_framework_from_agentengine_yaml(from_path) or 'unknown'
1906
+ if detected_framework != 'unknown':
1907
+ print_info(f"从 agentengine.yaml 检测到框架: {detected_framework}")
1908
+ else:
1909
+ entry_content = entry_file.read_text(encoding='utf-8')
1910
+ detected_framework = _detect_framework(entry_content)
1911
+
1912
+ # 如果入口文件未检测到框架,扫描整个目录
1913
+ if detected_framework == 'unknown':
1914
+ for py_file in _iter_python_files(from_path):
1915
+ try:
1916
+ content = py_file.read_text(encoding='utf-8')
1917
+ except Exception:
1918
+ continue
1919
+ detected_framework = _detect_framework(content)
1920
+ if detected_framework != 'unknown':
1921
+ break
1922
+
1923
+ if detected_framework == 'unknown':
1924
+ print_warn("无法自动检测框架,将使用默认 langgraph")
1925
+ detected_framework = 'langgraph'
1926
+ else:
1927
+ print_info(f"检测到框架: {detected_framework}")
1928
+
1929
+ # 如果没有指定项目名,使用目录名
1930
+ if not project_name:
1931
+ project_name = from_path.name.replace('_', '-')
1932
+
1933
+ _wrap_agent_directory(from_path, project_name, detected_framework, entry_file, detected_var)
1934
+ return
1935
+
1936
+ # === 单文件模式 ===
1937
+ else:
1938
+ content = from_path.read_text(encoding='utf-8')
1939
+
1940
+ # 自动检测框架
1941
+ detected_framework = _detect_framework(content)
1942
+ if detected_framework == 'unknown':
1943
+ print_warn("无法自动检测框架,将使用默认 langgraph")
1944
+ detected_framework = 'langgraph'
1945
+ else:
1946
+ print_info(f"检测到框架: {detected_framework}")
1947
+
1948
+ # 自动检测 Agent 变量
1949
+ detected_var = _detect_agent_variable(content)
1950
+ if not detected_var:
1951
+ print_warn("无法自动检测 Agent 变量,将使用默认 root_agent")
1952
+ detected_var = 'root_agent'
1953
+ else:
1954
+ print_info(f"检测到变量: {detected_var}")
1955
+
1956
+ # 如果没有指定项目名,使用文件名
1957
+ if not project_name:
1958
+ project_name = from_path.stem.replace('_', '-')
1959
+
1960
+ _wrap_agent_file(from_path, project_name, detected_framework, detected_var)
1961
+ return
1962
+
1963
+ # === 正常模板模式 ===
1964
+ # 如果没有提供项目名称,进入交互模式
1965
+ if not project_name:
1966
+ print_title("初始化新项目")
1967
+
1968
+ project_name = questionary.text(
1969
+ "请输入项目名称:",
1970
+ style=custom_style
1971
+ ).ask()
1972
+
1973
+ if not project_name:
1974
+ print_error("取消创建")
1975
+ raise SystemExit(0)
1976
+
1977
+ framework = questionary.select(
1978
+ "请选择开发框架:",
1979
+ choices=['langgraph', 'langchain', 'deepagents', 'adk', 'openclaw', 'hermes'],
1980
+ default='langgraph',
1981
+ style=custom_style
1982
+ ).ask()
1983
+
1984
+ if not framework:
1985
+ print_error("取消创建")
1986
+ raise SystemExit(0)
1987
+
1988
+ project_path = Path(project_name)
1989
+
1990
+ if project_path.exists():
1991
+ print_error(f"目录 '{project_name}' 已存在")
1992
+ raise SystemExit(1)
1993
+
1994
+ print_kv("创建项目", project_name)
1995
+ print_kv("框架", framework)
1996
+
1997
+ package_name = _runtime_package_name_or_exit(project_path.name)
1998
+ project_path.mkdir(parents=True)
1999
+ if framework not in {"openclaw", "hermes"}:
2000
+ (project_path / package_name).mkdir(parents=True)
2001
+
2002
+ # 检测全局配置
2003
+ from ksadk.configs.global_config import (
2004
+ global_config_exists,
2005
+ get_env_from_global_config,
2006
+ )
2007
+
2008
+ global_env = {}
2009
+ if global_config_exists():
2010
+ global_env = get_env_from_global_config()
2011
+ if global_env:
2012
+ print_info("检测到全局配置,已自动填充凭证")
2013
+
2014
+ # .env - 生成配置文件
2015
+ # 如果有全局配置,使用全局配置的值;否则使用占位符
2016
+ # 如果有全局配置,使用全局配置的值;否则使用空字符串
2017
+ api_key = global_env.get("OPENAI_API_KEY", "")
2018
+ base_url = global_env.get("OPENAI_BASE_URL", "")
2019
+ model_name = global_env.get("OPENAI_MODEL_NAME", "")
2020
+
2021
+ ks_ak = global_env.get("KSYUN_ACCESS_KEY", "")
2022
+ ks_sk = global_env.get("KSYUN_SECRET_KEY", "")
2023
+ ks_region = global_env.get("KSYUN_REGION", "cn-beijing-6")
2024
+ ks_account = global_env.get("KSYUN_ACCOUNT_ID", "")
2025
+
2026
+ # 构建 .env 内容
2027
+ if framework == "openclaw":
2028
+ env_content = f"""# ======================
2029
+ # OpenClaw 标准部署最小配置
2030
+ # ======================
2031
+ KSYUN_ACCESS_KEY={ks_ak}
2032
+ KSYUN_SECRET_KEY={ks_sk}
2033
+ KSYUN_REGION={ks_region}
2034
+ """
2035
+ if ks_account:
2036
+ env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2037
+ else:
2038
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2039
+
2040
+ env_content += f"\nOPENAI_API_KEY={api_key}\n"
2041
+ if base_url:
2042
+ env_content += f"OPENAI_BASE_URL={base_url}\n"
2043
+ else:
2044
+ env_content += "# OPENAI_BASE_URL=http://kspmas.ksyun.com/v1\n"
2045
+
2046
+ if model_name:
2047
+ env_content += f"OPENAI_MODEL_NAME={model_name}\n"
2048
+ else:
2049
+ env_content += "# OPENAI_MODEL_NAME=glm-5.2\n"
2050
+ elif framework == "hermes":
2051
+ env_content = f"""# ======================
2052
+ # Hermes 标准部署最小配置
2053
+ # ======================
2054
+ KSYUN_ACCESS_KEY={ks_ak}
2055
+ KSYUN_SECRET_KEY={ks_sk}
2056
+ KSYUN_REGION={ks_region}
2057
+ """
2058
+ if ks_account:
2059
+ env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2060
+ else:
2061
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2062
+
2063
+ env_content += f"""
2064
+ OPENAI_API_KEY={api_key}
2065
+ """
2066
+ if base_url:
2067
+ env_content += f"OPENAI_BASE_URL={base_url}\n"
2068
+ else:
2069
+ env_content += "# OPENAI_BASE_URL=http://kspmas.ksyun.com/v1\n"
2070
+
2071
+ if model_name:
2072
+ env_content += f"OPENAI_MODEL_NAME={model_name}\n"
2073
+ else:
2074
+ env_content += "# OPENAI_MODEL_NAME=glm-5.2\n"
2075
+
2076
+ env_content += """
2077
+ # Hermes runtime
2078
+ API_SERVER_ENABLED=true
2079
+ API_SERVER_HOST=127.0.0.1
2080
+ API_SERVER_PORT=8642
2081
+ HERMES_DASHBOARD_HOST=127.0.0.1
2082
+ HERMES_DASHBOARD_PORT=9119
2083
+ PORT=8080
2084
+ # HERMES_CONTEXT_LENGTH=200000
2085
+ # HERMES_FALLBACK_MODEL=deepseek-v4-pro
2086
+ # HERMES_IMAGE=hub.kce.ksyun.com/agentengine-public/hermes-agent:2026.5.29.2-ksadk-v1
2087
+ """
2088
+ env_example_content = """# ======================
2089
+ # Hermes 标准部署最小配置示例
2090
+ # ======================
2091
+ KSYUN_ACCESS_KEY=your-access-key
2092
+ KSYUN_SECRET_KEY=your-secret-key
2093
+ KSYUN_REGION=cn-beijing-6
2094
+ # KSYUN_ACCOUNT_ID=your-account-id
2095
+
2096
+ OPENAI_API_KEY=your-model-api-key
2097
+ OPENAI_BASE_URL=https://kspmas.ksyun.com/v1/
2098
+ OPENAI_MODEL_NAME=glm-5.2
2099
+
2100
+ # Hermes runtime
2101
+ API_SERVER_ENABLED=true
2102
+ API_SERVER_HOST=127.0.0.1
2103
+ API_SERVER_PORT=8642
2104
+ HERMES_DASHBOARD_HOST=127.0.0.1
2105
+ HERMES_DASHBOARD_PORT=9119
2106
+ PORT=8080
2107
+ # HERMES_CONTEXT_LENGTH=200000
2108
+ # HERMES_FALLBACK_MODEL=deepseek-v4-pro
2109
+ # HERMES_IMAGE=hub.kce.ksyun.com/agentengine-public/hermes-agent:2026.5.29.2-ksadk-v1
2110
+ """
2111
+ else:
2112
+ langfuse_public = global_env.get("LANGFUSE_PUBLIC_KEY", "")
2113
+ langfuse_secret = global_env.get("LANGFUSE_SECRET_KEY", "")
2114
+ langfuse_url = global_env.get("LANGFUSE_BASE_URL", "")
2115
+
2116
+ env_content = f"""# ======================
2117
+ # 模型配置 (必填, 可以从星流平台获取https://ksp.console.ksyun.com/#/apiKey)
2118
+ # ======================
2119
+ OPENAI_API_KEY={api_key}
2120
+ """
2121
+
2122
+ # 可选字段:如果有值则启用,否则注释掉
2123
+ if base_url:
2124
+ env_content += f"OPENAI_BASE_URL={base_url}\n"
2125
+ else:
2126
+ env_content += "# OPENAI_BASE_URL=http://kspmas.ksyun.com/v1\n"
2127
+
2128
+ if model_name:
2129
+ env_content += f"OPENAI_MODEL_NAME={model_name}\n"
2130
+ else:
2131
+ env_content += "# OPENAI_MODEL_NAME=glm-5.2\n"
2132
+
2133
+ env_content += """
2134
+ # ======================
2135
+ # 可观测性 (可选)
2136
+ # ======================
2137
+ """
2138
+ if langfuse_public:
2139
+ env_content += f"LANGFUSE_PUBLIC_KEY={langfuse_public}\n"
2140
+ else:
2141
+ env_content += "# LANGFUSE_PUBLIC_KEY=pk-xxx\n"
2142
+
2143
+ if langfuse_secret:
2144
+ env_content += f"LANGFUSE_SECRET_KEY={langfuse_secret}\n"
2145
+ else:
2146
+ env_content += "# LANGFUSE_SECRET_KEY=sk-xxx\n"
2147
+
2148
+ if langfuse_url:
2149
+ env_content += f"LANGFUSE_BASE_URL={langfuse_url}\n"
2150
+ else:
2151
+ env_content += "# LANGFUSE_BASE_URL=https://cloud.langfuse.com\n"
2152
+
2153
+ env_content += """
2154
+ # ======================
2155
+ # 金山云配置 (可选,需要部署时必选)
2156
+ # ======================
2157
+ """
2158
+ if ks_ak:
2159
+ env_content += f"KSYUN_ACCESS_KEY={ks_ak}\n"
2160
+ else:
2161
+ env_content += "# KSYUN_ACCESS_KEY=your-api-key-here\n"
2162
+
2163
+ if ks_sk:
2164
+ env_content += f"KSYUN_SECRET_KEY={ks_sk}\n"
2165
+ else:
2166
+ env_content += "# KSYUN_SECRET_KEY=your-api-secret-here\n"
2167
+
2168
+ env_content += f"KSYUN_REGION={ks_region}\n"
2169
+
2170
+ if ks_account:
2171
+ env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2172
+ else:
2173
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2174
+
2175
+ # 使用 utf-8-sig 编码 (带 BOM),确保 Windows 程序正确识别为 UTF-8
2176
+ (project_path / ".env").write_text(env_content, encoding="utf-8-sig")
2177
+ if framework == "hermes":
2178
+ (project_path / ".env.example").write_text(env_example_content, encoding="utf-8-sig")
2179
+
2180
+ if framework == "openclaw":
2181
+ print_success("项目创建成功")
2182
+ print_rule("快速开始")
2183
+ print_info("快速开始 (复制并执行):")
2184
+ _print_quick_start_commands(project_name, ["agentengine openclaw deploy"])
2185
+ print_info("部署前如需覆盖模型/网关参数,可先编辑 .env")
2186
+ return
2187
+ if framework == "hermes":
2188
+ _write_hermes_project_template(project_path, project_name, package_name)
2189
+ print_success("项目创建成功")
2190
+ print_rule("快速开始")
2191
+ print_info("快速开始 (复制并执行):")
2192
+ _print_quick_start_commands(project_name, ["agentengine hermes deploy"])
2193
+ print_info("部署前如需覆盖模型/运行时参数,可先编辑 .env")
2194
+ return
2195
+
2196
+ # agentengine.yaml - Agent 配置
2197
+ (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置
2198
+ name: {package_name}
2199
+ version: "1.0.0"
2200
+
2201
+ # 框架类型: adk, langchain, langgraph, deepagents
2202
+ framework: {framework}
2203
+
2204
+ # Agent 入口
2205
+ entry_point: {package_name}/agent.py
2206
+ agent_variable: root_agent
2207
+
2208
+ # 部署配置 (可选)
2209
+ # deploy:
2210
+ # timeout: 300
2211
+ # memory: 512
2212
+ """, encoding="utf-8-sig")
2213
+
2214
+ # __init__.py
2215
+ (project_path / package_name / "__init__.py").write_text(f'''"""
2216
+ {project_name} - KsADK Agent
2217
+ """
2218
+ from .agent import root_agent
2219
+ __all__ = ["root_agent"]
2220
+ ''', encoding="utf-8-sig")
2221
+
2222
+ # agent.py
2223
+ template = TEMPLATES[framework]["agent.py"]
2224
+ (project_path / package_name / "agent.py").write_text(
2225
+ template.format(package_name=package_name),
2226
+ encoding="utf-8-sig"
2227
+ )
2228
+
2229
+ # README.md
2230
+ if framework == "openclaw":
2231
+ readme = f"""# {project_name}
2232
+
2233
+ 基于 AgentEngine 创建的 OpenClaw 项目。
2234
+
2235
+ ## 快速开始
2236
+
2237
+ ```bash
2238
+ cd {project_name}
2239
+
2240
+ # 1. 编辑 .env(可选,覆盖模型/网关参数)
2241
+ vim .env
2242
+
2243
+ # 2. 部署 OpenClaw(默认 trusted-proxy)
2244
+ agentengine openclaw deploy
2245
+
2246
+ # 3. 打开 Dashboard
2247
+ agentengine dashboard --share
2248
+ ```
2249
+ """
2250
+ else:
2251
+ readme = f"""# {project_name}
2252
+
2253
+ 基于 AgentEngine 创建的 {framework.upper()} Agent.
2254
+
2255
+ ## 快速开始
2256
+
2257
+ ```bash
2258
+ cd {project_name}
2259
+
2260
+ # 1. 编辑 .env 填写 API Key
2261
+ vim .env
2262
+
2263
+ # 2. 运行
2264
+ agentengine run -i . # 交互式
2265
+ agentengine web . # API Server
2266
+ agentengine deploy . # 部署到云端
2267
+ ```
2268
+
2269
+ ## 项目结构
2270
+
2271
+ ```
2272
+ {project_name}/
2273
+ ├── .env # 环境变量 (API Key 等)
2274
+ ├── agentengine.yaml # Agent 配置
2275
+ ├── requirements.txt # Python 依赖
2276
+ ├── {package_name}/
2277
+ │ ├── __init__.py
2278
+ │ └── agent.py # Agent 实现
2279
+ └── README.md
2280
+ ```
2281
+ """
2282
+
2283
+ (project_path / "README.md").write_text(readme, encoding="utf-8-sig")
2284
+
2285
+ # requirements.txt
2286
+ reqs = "requests_aws4auth\n" # Minimum required for ksadk.common.auth
2287
+ if framework == "langchain":
2288
+ reqs += "langchain\nlangchain-openai\npython-dotenv\n"
2289
+ elif framework == "langgraph":
2290
+ reqs += "langchain\nlangchain-openai\nlanggraph\npython-dotenv\n"
2291
+ elif framework == "deepagents":
2292
+ reqs += "deepagents\nlangchain\nlangchain-openai\nlanggraph\npython-dotenv\n"
2293
+ elif framework == "adk":
2294
+ reqs += "google-adk\npython-dotenv\n"
2295
+
2296
+ (project_path / "requirements.txt").write_text(reqs, encoding="utf-8")
2297
+
2298
+ print_success("项目创建成功")
2299
+ print_rule("快速开始")
2300
+
2301
+ print_info("快速开始 (复制并执行):")
2302
+ _print_quick_start_commands(project_name, ["agentengine config"])
2303
+ print_info("或直接运行 (环境变量中需包含模型 API Key):")
2304
+ _print_quick_start_commands(project_name, ["agentengine run -i ."])