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