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,1105 @@
1
+ """
2
+ Serverless Provider - 金山云 Serverless 计算引擎 (AgentEngine 托管)
3
+
4
+ 架构:
5
+ - Build 阶段: 客户端使用本地 AK/SK 直接上传代码包到 KS3
6
+ - Deploy 阶段: 客户端调用 AgentEngine Server API 发起部署
7
+ """
8
+
9
+ import os
10
+ import json
11
+ import logging
12
+ import shutil
13
+ import time
14
+ import yaml
15
+ from pathlib import Path
16
+ from typing import Dict, Any, List, Optional
17
+ import click
18
+
19
+ from ksadk.builders.ks3_uploader import KS3Uploader
20
+ from ksadk.builders.code_builder import CodeBuilder
21
+ from ksadk.deployment.agent_access import get_latest_agent_access
22
+ from ksadk.deployment.base import (
23
+ BaseDeployProvider,
24
+ DeployTarget,
25
+ DeployResult,
26
+ DeployStatus,
27
+ PackageInfo,
28
+ )
29
+ from ksadk.deployment.registry import DeployProviderRegistry
30
+ from ksadk.deployment.ui_config import resolve_ui_config, ui_config_to_state_fields
31
+ from ksadk.builders.container_builder import (
32
+ ContainerBuilder,
33
+ registry_kind_label,
34
+ resolve_registry_credentials,
35
+ )
36
+ from ksadk.configs.settings import DEFAULT_RUNTIME_TIMEZONE
37
+ from ksadk.configs.env_registry import ENV_VAR_REGISTRY
38
+ from ksadk.configs.global_config import get_env_from_global_config
39
+ from ksadk.api import AgentEngineClient, DryRunExit
40
+
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ _DEPLOY_PROCESS_ENV_ALLOWLIST = frozenset(
46
+ {
47
+ spec.name
48
+ for spec in ENV_VAR_REGISTRY
49
+ if spec.module
50
+ not in {
51
+ "builders",
52
+ "cli",
53
+ "configs",
54
+ "web",
55
+ }
56
+ }
57
+ ) | frozenset(
58
+ {
59
+ "E2B_API_KEY",
60
+ "E2B_API_URL",
61
+ "OPENAI_API_BASE",
62
+ "OPENAI_API_KEY",
63
+ "OPENAI_BASE_URL",
64
+ "OPENAI_MODEL_NAME",
65
+ "SKILL_SPACE_ID",
66
+ "KSYUN_ACCESS_KEY",
67
+ "KSYUN_ACCOUNT_ID",
68
+ "KSYUN_REGION",
69
+ "KSYUN_SECRET_KEY",
70
+ }
71
+ )
72
+ _DEPLOY_PROCESS_ENV_PREFIXES = ("KSADK_", "OPENAI_", "KSYUN_", "E2B_")
73
+ _DEPLOY_PROCESS_ENV_DENYLIST = frozenset(
74
+ {
75
+ spec.name
76
+ for spec in ENV_VAR_REGISTRY
77
+ if spec.module in {"builders", "cli", "configs", "web"}
78
+ }
79
+ ) | frozenset(
80
+ {
81
+ "KSADK_GLOBAL_CONFIG_ENV_KEYS",
82
+ "KSADK_UPDATED_AT",
83
+ "KSADK_VERSION",
84
+ }
85
+ )
86
+
87
+
88
+ def _should_forward_process_env(name: str) -> bool:
89
+ if name in _DEPLOY_PROCESS_ENV_DENYLIST:
90
+ return False
91
+ return name in _DEPLOY_PROCESS_ENV_ALLOWLIST or name.startswith(_DEPLOY_PROCESS_ENV_PREFIXES)
92
+
93
+
94
+ @DeployProviderRegistry.register("serverless")
95
+ @DeployProviderRegistry.register("kcf")
96
+ @DeployProviderRegistry.register("kce")
97
+ class ServerlessProvider(BaseDeployProvider):
98
+ """金山云 Serverless 计算引擎 (AgentEngine Server 托管)
99
+
100
+ 重构后直接继承 BaseDeployProvider,不再依赖 DockerProvider。
101
+ Container 模式使用 ContainerBuilder 进行打包和构建。
102
+ """
103
+
104
+ name = "serverless"
105
+ display_name = "AgentEngine Serverless (Managed)"
106
+ description = "部署到金山云 Serverless (via AgentEngine Server)"
107
+
108
+ supports_streaming = True
109
+ supports_scaling = True
110
+ requires_image_registry = False
111
+
112
+ def __init__(self, config: Dict[str, Any] = None):
113
+ self.config = config or {}
114
+
115
+ @staticmethod
116
+ def _image_credential_from_env(image_ref: str) -> Optional[Dict[str, str]]:
117
+ username, password, registry_kind = resolve_registry_credentials(image_ref)
118
+ if username and password:
119
+ return {
120
+ "endpoint": image_ref.split("/", 1)[0],
121
+ "username": username,
122
+ "password": password,
123
+ }
124
+
125
+ if password and not username and registry_kind != "personal_kcr":
126
+ click.secho(
127
+ f" ⚠️ 检测到 KCR_PASSWORD 但缺少 KCR_USERNAME,已忽略{registry_kind_label(registry_kind)}镜像凭证;"
128
+ "企业版 KCR 和第三方镜像仓库必须配置 KCR_USERNAME + KCR_PASSWORD",
129
+ fg="yellow",
130
+ )
131
+ elif registry_kind == "personal_kcr":
132
+ click.secho(
133
+ " ⚠️ 未配置个人版 KCR 镜像凭证 (KSYUN_ACCOUNT_ID/KCR_PASSWORD),私有镜像可能无法拉取",
134
+ fg="yellow",
135
+ )
136
+ else:
137
+ click.secho(
138
+ f" ⚠️ 未配置{registry_kind_label(registry_kind)}镜像凭证 "
139
+ "(KCR_USERNAME/KCR_PASSWORD),私有镜像可能无法拉取",
140
+ fg="yellow",
141
+ )
142
+ return None
143
+
144
+ @staticmethod
145
+ def _parse_code_artifact_path(artifact_path: str) -> tuple[Optional[str], Optional[str]]:
146
+ """解析代码包路径,返回 (bucket, object_key)。"""
147
+ if not artifact_path:
148
+ return None, None
149
+
150
+ path = artifact_path.strip()
151
+ if not path:
152
+ return None, None
153
+
154
+ if path.startswith("ks3://"):
155
+ raw = path[6:]
156
+ parts = raw.split("/", 1)
157
+ bucket = parts[0].strip() if parts else ""
158
+ object_key = parts[1].strip() if len(parts) > 1 else ""
159
+ return bucket or None, object_key or None
160
+
161
+ if path.startswith("http://") or path.startswith("https://"):
162
+ from urllib.parse import urlparse
163
+
164
+ parsed = urlparse(path)
165
+ bucket = (parsed.netloc.split(".", 1)[0] if parsed.netloc else "").strip()
166
+ object_key = parsed.path.lstrip("/").strip()
167
+ return bucket or None, object_key or None
168
+
169
+ if "/" in path:
170
+ bucket, object_key = path.split("/", 1)
171
+ return bucket.strip() or None, object_key.strip() or None
172
+
173
+ return None, None
174
+
175
+ @staticmethod
176
+ def _load_project_env_vars(env_file: Path) -> Dict[str, str]:
177
+ """读取项目 .env,并修正 UTF-8 BOM 导致的脏 key。"""
178
+ from dotenv import dotenv_values
179
+
180
+ raw_env = dotenv_values(env_file, encoding="utf-8-sig")
181
+ env_vars: Dict[str, str] = {}
182
+ for key, value in raw_env.items():
183
+ if not key or value is None:
184
+ continue
185
+ clean_key = str(key).lstrip("\ufeff").strip()
186
+ if not clean_key:
187
+ continue
188
+ env_vars[clean_key] = str(value)
189
+ return env_vars
190
+
191
+ @classmethod
192
+ def _load_deploy_env_vars(
193
+ cls,
194
+ project_dir: str | Path,
195
+ explicit_env_vars: Optional[Dict[str, str]] = None,
196
+ ) -> tuple[Dict[str, str], bool, int]:
197
+ """读取部署时注入到托管运行时的环境变量。
198
+
199
+ 全局配置作为兜底,项目 .env 作为项目级覆盖;真实 .env 文件不会随
200
+ Code/Container 制品打包,只通过 deploy payload 注入到 Pod 环境变量。
201
+ """
202
+ env_vars: Dict[str, str] = dict(get_env_from_global_config())
203
+ env_file = Path(project_dir) / ".env"
204
+ project_env_count = 0
205
+ for key, value in sorted(os.environ.items()):
206
+ if value and _should_forward_process_env(key):
207
+ env_vars.setdefault(key, value)
208
+ if env_file.exists():
209
+ project_env = cls._load_project_env_vars(env_file)
210
+ project_env_count = len(project_env)
211
+ env_vars.update(project_env)
212
+ env_vars.update(explicit_env_vars or {})
213
+ env_vars.setdefault("TZ", DEFAULT_RUNTIME_TIMEZONE)
214
+ return env_vars, env_file.exists(), project_env_count
215
+
216
+ @staticmethod
217
+ def _inject_ui_runtime_env(
218
+ env_vars: Dict[str, str],
219
+ ui_state: Dict[str, Any],
220
+ local_state: Dict[str, Any],
221
+ ) -> Dict[str, str]:
222
+ """Expose resolved UI config to the runtime pod without packaging local state."""
223
+
224
+ profile = ui_state.get("ui_profile")
225
+ path = ui_state.get("ui_path")
226
+ url = ui_state.get("ui_url")
227
+ bundle_path = (
228
+ ui_state.get("ui_bundle_path")
229
+ or ui_state.get("ui_bundle_dir")
230
+ or local_state.get("ui_bundle_path")
231
+ or local_state.get("ui_bundle_dir")
232
+ )
233
+
234
+ if profile:
235
+ env_vars["KSADK_UI_PROFILE"] = str(profile)
236
+ if path:
237
+ env_vars["KSADK_UI_PATH"] = str(path)
238
+ if url:
239
+ env_vars["KSADK_UI_URL"] = str(url)
240
+ if bundle_path:
241
+ env_vars["KSADK_UI_BUNDLE_PATH"] = str(bundle_path)
242
+ return env_vars
243
+
244
+ @staticmethod
245
+ def _persist_build_metadata(package_info: PackageInfo) -> None:
246
+ """持久化最近一次成功构建的制品信息,供后续命中缓存。"""
247
+ metadata_file = Path(package_info.project_dir) / ".agentengine" / "build-metadata.json"
248
+ metadata_file.parent.mkdir(parents=True, exist_ok=True)
249
+
250
+ payload = {
251
+ "name": package_info.name,
252
+ "framework": package_info.framework,
253
+ "build_dir": package_info.build_dir,
254
+ "project_dir": package_info.project_dir,
255
+ "entry_point": package_info.entry_point,
256
+ "image": package_info.image,
257
+ "metadata": package_info.metadata,
258
+ }
259
+
260
+ with open(metadata_file, "w", encoding="utf-8") as f:
261
+ json.dump(payload, f, indent=2, ensure_ascii=False)
262
+
263
+ @staticmethod
264
+ def _serialize_network_config(target: DeployTarget) -> Optional[Dict[str, Any]]:
265
+ network = getattr(target, "network", None)
266
+ if network is None:
267
+ return None
268
+
269
+ payload: Dict[str, Any] = {
270
+ "enable_public_access": bool(getattr(network, "enable_public_access", False)),
271
+ "enable_vpc_access": bool(getattr(network, "enable_vpc_access", False)),
272
+ }
273
+
274
+ for field in ("vpc_id", "subnet_id", "security_group_id", "availability_zone"):
275
+ value = str(getattr(network, field, "") or "").strip()
276
+ if value:
277
+ payload[field] = value
278
+
279
+ if not payload["enable_public_access"] and not payload["enable_vpc_access"] and len(payload) == 2:
280
+ return None
281
+ return payload
282
+
283
+ @staticmethod
284
+ def _serialize_storage_config(target: DeployTarget) -> Optional[Dict[str, Any]]:
285
+ storage = getattr(target, "storage", None)
286
+ if storage is None:
287
+ return None
288
+
289
+ mount_path = str(getattr(storage, "mount_path", "") or "").strip()
290
+ size_gi = getattr(storage, "size_gi", None)
291
+ if not mount_path and size_gi is None:
292
+ return None
293
+
294
+ payload: Dict[str, Any] = {}
295
+ if mount_path:
296
+ payload["mount_path"] = mount_path
297
+ if size_gi is not None:
298
+ payload["size_gi"] = int(size_gi)
299
+ return payload
300
+
301
+ @staticmethod
302
+ def _format_elapsed(started_at: float) -> str:
303
+ elapsed = max(0.0, time.monotonic() - started_at)
304
+ if elapsed < 60:
305
+ return f"{elapsed:.1f}s"
306
+ minutes = int(elapsed // 60)
307
+ seconds = int(elapsed % 60)
308
+ return f"{minutes}m{seconds:02d}s"
309
+
310
+
311
+ async def validate_config(self, target: DeployTarget) -> tuple[bool, str]:
312
+ """验证配置: 确保已配置 AgentEngine Server"""
313
+
314
+ server_url = os.getenv("AGENTENGINE_SERVER_URL")
315
+
316
+ if not server_url:
317
+ click.echo("⚠️ 未配置 AGENTENGINE_SERVER_URL,将尝试使用默认 Region 配置")
318
+
319
+ # Container 模式: 检查 Docker 是否可用
320
+ artifact_type = target.extra.get("artifact_type", "Code")
321
+ if artifact_type == "Container":
322
+ import subprocess
323
+ try:
324
+ result = subprocess.run(["docker", "version"], capture_output=True, timeout=10)
325
+ if result.returncode != 0:
326
+ return False, "Docker 未正常运行"
327
+ except FileNotFoundError:
328
+ return False, "未找到 docker 命令,请确保已安装 Docker"
329
+ except Exception as e:
330
+ return False, f"Docker 检查失败: {e}"
331
+
332
+ return True, ""
333
+
334
+ async def package(self, project_dir: str, detection_result: Any, config: Dict[str, Any] = None) -> PackageInfo:
335
+ """打包项目
336
+
337
+ Code 模式: 返回基本 PackageInfo,实际构建在 build() 中进行
338
+ Container 模式: 使用 ContainerBuilder 打包
339
+ """
340
+ project_path = Path(project_dir)
341
+
342
+ # 创建基本 PackageInfo
343
+ package_info = PackageInfo(
344
+ name=detection_result.name or project_path.name,
345
+ framework=detection_result.type.value,
346
+ build_dir=str(project_path / ".agentengine" / "build"),
347
+ project_dir=str(project_path),
348
+ entry_point=detection_result.entry_point,
349
+ metadata={}
350
+ )
351
+
352
+ # 尝试加载之前保存的构建元数据 (ks3_path 等)
353
+ try:
354
+ metadata_file = project_path / ".agentengine" / "build-metadata.json"
355
+ if metadata_file.exists():
356
+ import json
357
+ with open(metadata_file, "r") as f:
358
+ saved_data = json.load(f)
359
+ if "metadata" in saved_data:
360
+ count = 0
361
+ for k, v in saved_data["metadata"].items():
362
+ if v:
363
+ package_info.metadata[k] = v
364
+ count += 1
365
+ if count > 0:
366
+ click.echo(f" 📦 已加载上次构建元数据: {count} 项")
367
+ except Exception as e:
368
+ click.secho(f"⚠️ 加载构建元数据失败: {e}", fg="yellow")
369
+
370
+ return package_info
371
+
372
+
373
+
374
+ async def build(self, package_info: PackageInfo, target: DeployTarget) -> PackageInfo:
375
+ """构建 & 上传 (客户端直传 KS3)"""
376
+ artifact_type = target.extra.get("artifact_type", "Code")
377
+
378
+ if artifact_type == "Code":
379
+ # 1. 检查是否已有 KS3 路径
380
+ # 优先级: CLI传入 > Metadata缓存 (仅当 !no_cache)
381
+ cli_ks3_path = target.extra.get("ks3_path")
382
+ cached_ks3_path = package_info.metadata.get("ks3_path")
383
+ no_cache = target.extra.get("no_cache", False)
384
+ repackage = target.extra.get("repackage", False)
385
+
386
+ # 如果显式传入了 ks3-path,直接使用
387
+ if cli_ks3_path:
388
+ package_info.metadata["ks3_path"] = cli_ks3_path
389
+ return package_info
390
+
391
+ # 如果没有 no_cache 且有缓存,才使用缓存
392
+ if not no_cache and not repackage and cached_ks3_path:
393
+ logger.info(f"Using cached bundle: {cached_ks3_path}")
394
+ return package_info
395
+
396
+ # 2. 构建 ZIP 包 (委托给 CodeBuilder)
397
+
398
+ # 传递配置,包括 no_cache
399
+ builder_config = target.extra.copy()
400
+ builder_config["no_cache"] = no_cache
401
+ builder_config["repackage"] = repackage
402
+
403
+ # 实例化 Builder
404
+ # 注意: CodeBuilder 目前设计为直接操作 project_dir,
405
+ # 这里传入原始 project_dir (package_info.project_dir)
406
+ builder = CodeBuilder(Path(package_info.project_dir), config=builder_config)
407
+
408
+ # 执行构建
409
+ build_result = builder.build()
410
+
411
+ if not build_result.success:
412
+ raise Exception(f"构建失败: {build_result.error_message}")
413
+
414
+ zip_path = build_result.artifact_path
415
+ # click.echo(f" ✅ ZIP 已生成: {zip_path}")
416
+
417
+ # 3. 直接上传 KS3 (使用本地 AK/SK)
418
+ # 3. 直接上传 KS3 (使用本地 AK/SK)
419
+ click.echo("\n正在上传代码包到 KS3...")
420
+ upload_started_at = time.monotonic()
421
+
422
+ ks3_bucket = target.extra.get("ks3_bucket")
423
+ upload_region = "cn-beijing-6" if target.region == "pre-online" else target.region
424
+
425
+ uploader = KS3Uploader(region=upload_region, bucket=ks3_bucket)
426
+
427
+ # 使用时间戳确保每次上传的代码包路径唯一,支持真正的版本回滚
428
+ from datetime import datetime
429
+ timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
430
+ object_key = f"agents/{package_info.name}/code_{timestamp}.zip"
431
+
432
+ ks3_path = await uploader.upload(zip_path, object_key)
433
+
434
+ if not ks3_path:
435
+ raise Exception("KS3 上传失败")
436
+
437
+ logger.info(f"Upload success: {ks3_path}")
438
+ click.echo(f" ✓ 上传耗时: {self._format_elapsed(upload_started_at)}")
439
+ package_info.metadata["ks3_path"] = ks3_path
440
+
441
+ elif artifact_type == "Container":
442
+ # Container 模式: 使用 ContainerBuilder 构建并推送镜像
443
+ cli_image = target.extra.get("image") or package_info.image
444
+ if cli_image:
445
+ package_info.image = cli_image
446
+ package_info.metadata["image"] = cli_image
447
+ self._persist_build_metadata(package_info)
448
+ return package_info
449
+
450
+ registry = target.extra.get("registry", "")
451
+ tag = target.extra.get("tag", "latest")
452
+ no_cache = target.extra.get("no_cache", False)
453
+ cached_image = package_info.metadata.get("image")
454
+
455
+ if not no_cache and cached_image:
456
+ logger.info(f"Using cached image: {cached_image}")
457
+ package_info.image = cached_image
458
+ return package_info
459
+
460
+ builder = ContainerBuilder(
461
+ project_dir=Path(package_info.project_dir),
462
+ tag=tag,
463
+ registry=registry,
464
+ no_cache=no_cache
465
+ )
466
+
467
+ # 构建镜像
468
+ build_result = builder.build()
469
+ if not build_result.success:
470
+ raise Exception(f"镜像构建失败: {build_result.error_message}")
471
+
472
+ image_name = build_result.metadata.get("image")
473
+ package_info.image = image_name
474
+ package_info.metadata["image"] = image_name
475
+
476
+ # 推送镜像
477
+ if not builder.push(image_name):
478
+ raise Exception("镜像推送失败")
479
+
480
+ click.secho(f"✅ 镜像已推送: {image_name}", fg="green")
481
+
482
+ self._persist_build_metadata(package_info)
483
+
484
+ return package_info
485
+
486
+ async def deploy(self, package_info: PackageInfo, target: DeployTarget) -> DeployResult:
487
+ """通过 AgentEngine Server 部署
488
+
489
+ 逻辑:
490
+ - 读取本地 .agentengine.state 文件获取 agent_id
491
+ - 如果有 agent_id → 执行更新 (endpoint 不变)
492
+ - 如果没有 → 创建新 Agent,保存 agent_id 到状态文件
493
+ """
494
+
495
+ server_url = os.getenv("AGENTENGINE_SERVER_URL")
496
+ click.echo(f"\n🚀 开始部署到 Serverless (Managed by {server_url})...")
497
+
498
+ # 读取本地状态文件
499
+ project_dir = package_info.project_dir
500
+ state_file = Path(project_dir) / ".agentengine.state"
501
+ local_state = self._load_state(state_file)
502
+ project_config = self._load_project_config(Path(project_dir))
503
+ runtime_state = self._merge_ui_runtime_state(
504
+ local_state=local_state,
505
+ project_config=project_config,
506
+ )
507
+ existing_agent_id = local_state.get("agent_id")
508
+ resolved_ui = resolve_ui_config(
509
+ framework=package_info.framework,
510
+ state=runtime_state,
511
+ cli_profile=target.extra.get("ui_profile"),
512
+ cli_path=target.extra.get("ui_path"),
513
+ cli_url=target.extra.get("ui_url"),
514
+ )
515
+ ui_state = ui_config_to_state_fields(resolved_ui)
516
+ if project_config.get("ui_bundle_path") and not ui_state.get("ui_bundle_path"):
517
+ ui_state["ui_bundle_path"] = project_config.get("ui_bundle_path")
518
+ if project_config.get("ui_bundle_dir") and not ui_state.get("ui_bundle_dir"):
519
+ ui_state["ui_bundle_dir"] = project_config.get("ui_bundle_dir")
520
+
521
+ # 构造请求 payload
522
+ artifact_type = target.extra.get("artifact_type", "Code")
523
+ artifact_path = ""
524
+ if artifact_type == "Code":
525
+ original_artifact_path = package_info.metadata.get("ks3_path", "")
526
+ artifact_path = original_artifact_path
527
+
528
+ # 转换为内网地址 (如果 serverless 无法解析 ks3://)
529
+ if artifact_path.startswith("ks3://"):
530
+ try:
531
+ from ksadk.common.constants import get_ks3_endpoints
532
+
533
+ # 确定 KS3 Region code (pre-online -> cn-beijing)
534
+ ks3_region = target.extra.get("ks3_region")
535
+ if not ks3_region:
536
+ ks3_region = "cn-beijing-6" if target.region == "pre-online" else target.region
537
+
538
+ _, internal_endpoint = get_ks3_endpoints(ks3_region)
539
+
540
+ if internal_endpoint:
541
+ # ks3://bucket/key -> http://bucket.internal_endpoint/key
542
+ bucket, key = self._parse_code_artifact_path(artifact_path)
543
+ if bucket and key:
544
+ artifact_path = f"http://{bucket}.{internal_endpoint}/{key}"
545
+ click.echo(f" Converted artifact path: {artifact_path}")
546
+ except Exception as e:
547
+ logger.warning(f"Failed to convert ks3 path to internal URL: {e}")
548
+
549
+ if not artifact_path:
550
+ raise ValueError(
551
+ "❌ 未找到代码包路径 (ks3_path)。\n"
552
+ " 在 Serverless 模式下,必须先上传代码包。\n"
553
+ " 👉 请先执行: agentengine build --mode code --push\n"
554
+ " 或者在 deploy 命令中使用 --ks3-path 指定路径。"
555
+ )
556
+
557
+ parsed_bucket, parsed_object_key = self._parse_code_artifact_path(original_artifact_path)
558
+ if not parsed_bucket or not parsed_object_key:
559
+ raise ValueError(
560
+ "❌ ks3_path 格式无效,缺少代码包对象路径。\n"
561
+ f" 当前值: {original_artifact_path or '(empty)'}\n"
562
+ " 期望格式: ks3://<bucket>/<object-key>\n"
563
+ " 👉 请重新执行: agentengine build --mode code --push\n"
564
+ " 或者传入完整的 --ks3-path。"
565
+ )
566
+
567
+ else:
568
+ artifact_path = package_info.image
569
+
570
+ # 构建 KS3 凭证
571
+ ks3_config = None
572
+ if artifact_type == "Code":
573
+ from ksadk.common.auth import AWSV4Auth
574
+ auth = AWSV4Auth() # 读取本地 AK/SK
575
+ if auth.access_key_id and auth.secret_access_key:
576
+ # 智能推断 Bucket 和 Region
577
+ bucket_name = target.extra.get("ks3_bucket")
578
+ if not bucket_name:
579
+ bucket_name, _ = self._parse_code_artifact_path(
580
+ package_info.metadata.get("ks3_path", "")
581
+ )
582
+
583
+ # 如果没有提供,尝试从 artifact_path 解析
584
+ if not bucket_name:
585
+ if artifact_path.startswith("ks3://"):
586
+ try:
587
+ # ks3://bucket/key -> bucket
588
+ bucket_name = artifact_path.split("/")[2]
589
+ logger.info(f"Extracted bucket from ks3:// path: {bucket_name}")
590
+ except IndexError:
591
+ pass
592
+ elif artifact_path.startswith("http") and "." in artifact_path:
593
+ try:
594
+ # http://bucket.endpoint/key -> bucket
595
+ from urllib.parse import urlparse
596
+ parsed = urlparse(artifact_path)
597
+ bucket_name = parsed.netloc.split(".")[0]
598
+ logger.info(f"Extracted bucket from HTTP URL: {bucket_name}")
599
+ except Exception as e:
600
+ logger.warning(f"Failed to extract bucket from URL: {e}")
601
+
602
+ # 如果仍然没有,使用智能默认值(与 KS3Uploader 逻辑一致)
603
+ if not bucket_name:
604
+ account_id = os.getenv("KSYUN_ACCOUNT_ID")
605
+ upload_region = "cn-beijing-6" if target.region == "pre-online" else target.region
606
+
607
+ if not account_id:
608
+ raise ValueError(
609
+ "❌ 缺少 KSYUN_ACCOUNT_ID 环境变量\n"
610
+ " Bucket 名称格式必须为: agentengine-{account_id}-{region}\n"
611
+ " 请在 .env 文件中设置: KSYUN_ACCOUNT_ID=你的账号ID"
612
+ )
613
+
614
+ bucket_name = f"agentengine-{account_id}-{upload_region}"
615
+ logger.info(f"Using default bucket: {bucket_name}")
616
+
617
+ # Region 逻辑需与 Build 阶段保持一致 (pre-online -> cn-beijing-6)
618
+ ks3_region = target.extra.get("ks3_region")
619
+ if not ks3_region:
620
+ ks3_region = "cn-beijing-6" if target.region == "pre-online" else target.region
621
+
622
+ ks3_config = {
623
+ "access_key": auth.access_key_id,
624
+ "secret_key": auth.secret_access_key,
625
+ "region": ks3_region,
626
+ "bucket": bucket_name,
627
+ }
628
+ logger.info(f"📦 KS3 Config: bucket={bucket_name}, region={ks3_region}")
629
+
630
+ try:
631
+ # 获取 dry_run 标识
632
+ is_dry_run = target.extra.get("dry_run", False)
633
+
634
+ async with AgentEngineClient(region=target.region, dry_run=is_dry_run) as client:
635
+ agent_exists = False
636
+
637
+ if existing_agent_id:
638
+ # 有本地状态 → 先检查服务器上是否存在
639
+ click.echo(f" 检测到本地状态: {existing_agent_id}")
640
+
641
+ try:
642
+ # 尝试获取 agent,确认是否存在
643
+ existing_agent = await client.get_agent(existing_agent_id)
644
+ if existing_agent:
645
+ agent_exists = True
646
+ except Exception as e:
647
+ # Agent 不存在或查询失败
648
+ err_msg = str(e).lower()
649
+ if "not found" in err_msg or "404" in err_msg or "不存在" in err_msg:
650
+ click.secho(f" ⚠️ 服务器上未找到 Agent {existing_agent_id},将创建新 Agent", fg="yellow")
651
+ agent_exists = False
652
+ # 如果是 DryRun 抛出的异常(表明请求本来会发出去但被拦截了),我们认为 Agent 可能存在也可能不存在
653
+ # 但为了安全起见,在 DryRun 模式下我们假设它存在并走更新路径,或者简单地打印日志
654
+ elif "Dry Run" in str(e):
655
+ click.secho(f" [Dry Run] 假设 Agent {existing_agent_id} 存在", fg="cyan")
656
+ agent_exists = True
657
+ else:
658
+ # 其他错误,重新抛出
659
+ raise
660
+
661
+ if agent_exists:
662
+ # Agent 存在 → 执行更新
663
+ click.echo(f" 执行热更新 (endpoint 保持不变)...")
664
+
665
+ update_data = {
666
+ "artifact_type": artifact_type,
667
+ "artifact_path": artifact_path,
668
+ "resources": {
669
+ "cpu": target.resources.cpu,
670
+ "memory": target.resources.memory
671
+ },
672
+ "scaling": {
673
+ "min_replicas": target.scaling.min_replicas,
674
+ "max_replicas": target.scaling.max_replicas,
675
+ "concurrency": target.scaling.concurrency
676
+ },
677
+ "observability": {
678
+ "langfuse_enabled": target.extra.get("enable_observability", True)
679
+ },
680
+ "ui_config": {
681
+ "profile": resolved_ui.profile,
682
+ "path": resolved_ui.path,
683
+ "url": resolved_ui.url,
684
+ },
685
+ }
686
+
687
+ if ks3_config:
688
+ update_data["ks3"] = ks3_config
689
+ elif artifact_type == "Container":
690
+ image_credential = self._image_credential_from_env(artifact_path)
691
+ if image_credential:
692
+ update_data["image_credential"] = image_credential
693
+ click.echo(
694
+ f" 🔑 镜像凭证: {image_credential['username']}@{image_credential['endpoint']}"
695
+ )
696
+
697
+ # 加载全局配置 + 本地 .env,并通过部署参数注入到运行时环境变量。
698
+ env_vars, env_file_exists, project_env_count = self._load_deploy_env_vars(
699
+ project_dir,
700
+ target.extra.get("env_vars") or {},
701
+ )
702
+ env_vars = self._inject_ui_runtime_env(env_vars, ui_state, local_state)
703
+ if env_vars:
704
+ update_data["env_vars"] = env_vars
705
+ if env_file_exists:
706
+ click.echo(
707
+ f" 📦 更新环境变量: {len(env_vars)} 项 "
708
+ f"(全局配置 + {project_env_count} 项 from .env)"
709
+ )
710
+ else:
711
+ click.echo(f" 📦 更新环境变量: {len(env_vars)} 项 from 全局配置")
712
+
713
+ network_config = self._serialize_network_config(target)
714
+ if network_config:
715
+ update_data["network"] = network_config
716
+
717
+ storage_config = self._serialize_storage_config(target)
718
+ if storage_config:
719
+ update_data["storage"] = storage_config
720
+
721
+ # 注入更新时间戳,强制触发 Rolling Update (Pod 重启)
722
+ if "env_vars" not in update_data:
723
+ update_data["env_vars"] = {}
724
+
725
+ import time
726
+ update_data["env_vars"]["KSADK_UPDATED_AT"] = str(int(time.time()))
727
+
728
+ # DEBUG: 打印 Payload 确认 trigger 是否存在
729
+ click.echo(f" 🔄 更新 Trigger: KSADK_UPDATED_AT={update_data['env_vars']['KSADK_UPDATED_AT']}")
730
+
731
+ res = await client.update_agent(existing_agent_id, update_data)
732
+ latest_access = {}
733
+ if not is_dry_run:
734
+ latest_access = await get_latest_agent_access(
735
+ client,
736
+ agent_id=existing_agent_id,
737
+ include_api_key=True,
738
+ on_error=lambda exc: logger.warning(
739
+ "Failed to refresh quick access for %s: %s",
740
+ existing_agent_id,
741
+ exc,
742
+ ),
743
+ )
744
+
745
+ # 如果是 Dry Run,手动构造假响应以避免崩溃
746
+ if is_dry_run and not res:
747
+ res = {"name": package_info.name, "endpoint": "http://dry-run-endpoint"}
748
+
749
+ # 更新本地状态 (保留旧字段如 api_key)
750
+ new_state = local_state.copy()
751
+ new_state.update({
752
+ "agent_id": latest_access.get("agent_id") or existing_agent_id,
753
+ "name": latest_access.get("name") or res.get("name"),
754
+ "region": target.region,
755
+ "endpoint": latest_access.get("endpoint") or res.get("endpoint"),
756
+ "updated_at": self._now_iso(),
757
+ **ui_state,
758
+ })
759
+ if latest_access.get("api_key"):
760
+ new_state["api_key"] = latest_access["api_key"]
761
+ self._save_state(state_file, new_state)
762
+
763
+ return DeployResult(
764
+ status=DeployStatus.DEPLOYING,
765
+ agent_id=existing_agent_id,
766
+ agent_name=res.get("name"),
767
+ endpoint=res.get("endpoint"),
768
+ message=f"✅ Agent 已更新: {existing_agent_id}"
769
+ )
770
+ # else: agent_exists = False,继续执行创建逻辑
771
+
772
+ # 没有本地状态,或本地状态对应的 Agent 在服务器上不存在 → 创建新 Agent
773
+ if not existing_agent_id or not agent_exists:
774
+ click.echo(f" 创建新 Agent: {package_info.name}")
775
+
776
+ request_data = {
777
+ "name": package_info.name,
778
+ "framework": package_info.framework,
779
+ "artifact_type": artifact_type,
780
+ "artifact_path": artifact_path,
781
+ "region": target.region,
782
+ "instance_id": target.extra.get("instance_id", "default"),
783
+ "resources": {
784
+ "cpu": target.resources.cpu,
785
+ "memory": target.resources.memory
786
+ },
787
+ "scaling": {
788
+ "min_replicas": target.scaling.min_replicas,
789
+ "max_replicas": target.scaling.max_replicas,
790
+ "concurrency": target.scaling.concurrency
791
+ },
792
+ "observability": {
793
+ "langfuse_enabled": target.extra.get("enable_observability", True)
794
+ },
795
+ "ui_config": {
796
+ "profile": resolved_ui.profile,
797
+ "path": resolved_ui.path,
798
+ "url": resolved_ui.url,
799
+ },
800
+ }
801
+
802
+ if ks3_config:
803
+ request_data["ks3"] = ks3_config
804
+
805
+ # Container 模式: 传递镜像凭证
806
+ if artifact_type == "Container":
807
+ image_credential = self._image_credential_from_env(artifact_path)
808
+ if image_credential:
809
+ request_data["image_credential"] = image_credential
810
+ click.echo(
811
+ f" 🔑 镜像凭证: {image_credential['username']}@{image_credential['endpoint']}"
812
+ )
813
+
814
+ # 加载全局配置 + 本地 .env,并通过部署参数注入到运行时环境变量。
815
+ env_vars, env_file_exists, project_env_count = self._load_deploy_env_vars(
816
+ project_dir,
817
+ target.extra.get("env_vars") or {},
818
+ )
819
+ env_vars = self._inject_ui_runtime_env(env_vars, ui_state, local_state)
820
+ if env_vars:
821
+ if env_file_exists:
822
+ click.echo(
823
+ f" 📦 加载环境变量: {len(env_vars)} 项 "
824
+ f"(全局配置 + {project_env_count} 项 from .env)"
825
+ )
826
+ else:
827
+ click.echo(f" 📦 加载环境变量: {len(env_vars)} 项 from 全局配置")
828
+
829
+ if env_vars:
830
+ request_data["env_vars"] = env_vars
831
+
832
+ network_config = self._serialize_network_config(target)
833
+ if network_config:
834
+ request_data["network"] = network_config
835
+
836
+ storage_config = self._serialize_storage_config(target)
837
+ if storage_config:
838
+ request_data["storage"] = storage_config
839
+
840
+ # 获取 Account ID (用于 Server 端的 user_id)
841
+ extra_headers = {}
842
+ ksyun_account_id = os.getenv("KSYUN_ACCOUNT_ID")
843
+ if ksyun_account_id:
844
+ extra_headers["X-Ksc-Account-Id"] = ksyun_account_id
845
+
846
+ # 重新构造一个带 Header 的 client
847
+ async with AgentEngineClient(region=target.region, extra_headers=extra_headers, dry_run=is_dry_run) as new_client:
848
+ res = await new_client.create_agent(request_data)
849
+
850
+ # 如果是 Dry Run,手动构造假响应
851
+ if is_dry_run and not res:
852
+ res = {
853
+ "agent_id": "dry-run-agent-id",
854
+ "name": package_info.name,
855
+ "endpoint": "http://dry-run-endpoint",
856
+ "api_key": "dry-run-key"
857
+ }
858
+
859
+ # CreateAgentProduct 返回 order_id,需要轮询 list_agents 获取 agent_id
860
+ new_agent_id = res.get("agent_id")
861
+ order_id = res.get("order_id")
862
+ agent_name = res.get("name") or package_info.name
863
+ agent_endpoint = res.get("endpoint")
864
+ agent_api_key = res.get("api_key")
865
+
866
+ if order_id and not new_agent_id:
867
+ click.echo(f" 📋 订单已创建: {order_id},等待实例创建...")
868
+ import time
869
+ for i in range(12): # 最多等 60s
870
+ time.sleep(5)
871
+ try:
872
+ detail = await client.get_agent(name=package_info.name, include_api_key=True)
873
+ qa = detail.get("quick_access", {})
874
+ basic = detail.get("basic", {})
875
+ new_agent_id = basic.get("agent_id")
876
+ agent_name = basic.get("name") or agent_name
877
+ agent_endpoint = qa.get("public_endpoint")
878
+ agent_api_key = qa.get("api_key")
879
+ if new_agent_id:
880
+ click.secho(f" ✅ 实例已创建: {new_agent_id}", fg="green")
881
+ break
882
+ except Exception:
883
+ pass
884
+ click.echo(f" ⏳ 等待中... ({(i+1)*5}s)")
885
+
886
+ if not new_agent_id:
887
+ click.secho(" ⚠️ 实例仍在创建中,稍后使用 'agentengine status' 查看", fg="yellow")
888
+
889
+ latest_access = {}
890
+ if new_agent_id and not is_dry_run:
891
+ latest_access = await get_latest_agent_access(
892
+ new_client,
893
+ agent_id=new_agent_id,
894
+ include_api_key=True,
895
+ retry_delays=(0.3, 0.7, 1.0),
896
+ on_error=lambda exc: logger.warning(
897
+ "Failed to refresh quick access for %s: %s",
898
+ new_agent_id,
899
+ exc,
900
+ ),
901
+ )
902
+ new_agent_id = latest_access.get("agent_id") or new_agent_id
903
+ agent_name = latest_access.get("name") or agent_name
904
+ agent_endpoint = latest_access.get("endpoint") or agent_endpoint
905
+ agent_api_key = latest_access.get("api_key") or agent_api_key
906
+
907
+ # 保存 agent_id 到本地状态文件
908
+ self._save_state(state_file, {
909
+ "agent_id": new_agent_id,
910
+ "name": agent_name,
911
+ "region": target.region,
912
+ "endpoint": agent_endpoint,
913
+ "api_key": agent_api_key, # 只在首次保存
914
+ "order_id": order_id,
915
+ "created_at": self._now_iso(),
916
+ **ui_state,
917
+ })
918
+
919
+ click.echo(f" 💾 已保存状态到 .agentengine.state")
920
+
921
+ return DeployResult(
922
+ status=DeployStatus.DEPLOYING,
923
+ agent_id=new_agent_id,
924
+ agent_name=agent_name,
925
+ endpoint=agent_endpoint,
926
+ api_key=agent_api_key,
927
+ message=f"✅ Agent ID: {new_agent_id or '(创建中)'} (首次部署, 订单: {order_id or '-'})"
928
+ )
929
+
930
+ except DryRunExit as e:
931
+ return DeployResult(
932
+ status=DeployStatus.SKIPPED,
933
+ message="✅ Dry Run Completed: 请求已打印,未执行实际变更。",
934
+ metadata={"dry_run_request": e.payload or {}},
935
+ )
936
+
937
+ except Exception as e:
938
+ logger.error(f"Deploy failed: {e}")
939
+
940
+ # 检测名称冲突
941
+ err_msg = str(e)
942
+ if "Conflict" in err_msg or "409" in err_msg or "already exists" in err_msg:
943
+ return DeployResult(
944
+ status=DeployStatus.FAILED,
945
+ message=f"❌ 部署失败: Agent 名称 '{package_info.name}' 已存在。\n"
946
+ f" 提示: 请检查是否重复创建。\n"
947
+ f" 👉 解决方法: 请在 agentengine.yaml 中修改 'name' 字段 (如添加后缀) 后重试。"
948
+ )
949
+
950
+ return DeployResult(
951
+ status=DeployStatus.FAILED,
952
+ message=f"Server 请求失败: {str(e)}"
953
+ )
954
+
955
+ async def get_status(self, agent_id: str, target: DeployTarget) -> DeployResult:
956
+ """获取 Agent 状态"""
957
+ dry_run = target.extra.get("dry_run", False)
958
+ try:
959
+ async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
960
+ res = await client.get_agent(agent_id)
961
+
962
+ status_map = {
963
+ "running": DeployStatus.RUNNING,
964
+ "ready": DeployStatus.RUNNING,
965
+ "creating": DeployStatus.DEPLOYING,
966
+ "updating": DeployStatus.UPDATING,
967
+ "terminating": DeployStatus.STOPPING,
968
+ "scaling": DeployStatus.UPDATING,
969
+ "failed": DeployStatus.FAILED,
970
+ "error": DeployStatus.FAILED,
971
+ "unknown": DeployStatus.UNKNOWN
972
+ }
973
+
974
+ # 使状态匹配不区分大小写,以兼容服务端的改动 (Creating -> CREATING)
975
+ current_status = (res.get("status") or "").lower()
976
+ return DeployResult(
977
+ status=status_map.get(current_status, DeployStatus.UNKNOWN),
978
+ agent_id=res.get("agent_id"),
979
+ agent_name=res.get("name"),
980
+ endpoint=res.get("endpoint"),
981
+ message=f"Status: {res.get('status')} ({res.get('phase')})"
982
+ )
983
+ except DryRunExit:
984
+ return DeployResult(status=DeployStatus.SKIPPED, message="Dry Run executed.")
985
+ except Exception as e:
986
+ return DeployResult(
987
+ status=DeployStatus.UNKNOWN,
988
+ message=f"查询失败: {e}"
989
+ )
990
+
991
+ async def destroy(self, agent_id: str, target: DeployTarget) -> bool:
992
+ """销毁 Agent"""
993
+ dry_run = target.extra.get("dry_run", False)
994
+ project_dir = str(target.extra.get("project_dir") or "").strip()
995
+ state_file = (Path(project_dir).resolve() if project_dir else Path(".").resolve()) / ".agentengine.state"
996
+
997
+ try:
998
+ async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
999
+ click.echo(f"正在通过 Server 删除 Agent: {agent_id}...")
1000
+ success = await client.delete_agent(agent_id)
1001
+ if success and not dry_run and state_file.exists():
1002
+ local_state = self._load_state(state_file)
1003
+ if str(local_state.get("agent_id") or "").strip() == str(agent_id).strip():
1004
+ try:
1005
+ os.remove(state_file)
1006
+ logger.info(f"Deleted local state file: {state_file}")
1007
+ except Exception as cleanup_error:
1008
+ logger.warning(f"Failed to delete local state file {state_file}: {cleanup_error}")
1009
+ return success
1010
+ except DryRunExit:
1011
+ # 让异常冒泡给 CLI 处理
1012
+ raise
1013
+ except Exception as e:
1014
+ logger.error(f"Failed to delete agent: {e}")
1015
+ return False
1016
+
1017
+ async def list_agents(self, target: DeployTarget) -> List[DeployResult]:
1018
+ """列出所有 Agent"""
1019
+ dry_run = target.extra.get("dry_run", False)
1020
+ try:
1021
+ async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
1022
+ res = await client.list_agents()
1023
+
1024
+ results = []
1025
+ for agent in res.get("Agents", []):
1026
+ current_status = (agent.get("status") or "").lower()
1027
+ results.append(DeployResult(
1028
+ status=DeployStatus.RUNNING if current_status in ("running", "ready") else (
1029
+ DeployStatus.DEPLOYING if current_status == "creating" else DeployStatus.UNKNOWN
1030
+ ),
1031
+ agent_id=agent.get("agent_id"),
1032
+ agent_name=agent.get("name"),
1033
+ endpoint=agent.get("endpoint"),
1034
+ message=agent.get("status")
1035
+ ))
1036
+ return results
1037
+ except DryRunExit:
1038
+ raise
1039
+ except Exception as e:
1040
+ logger.error(f"List agents failed: {e}")
1041
+ return []
1042
+
1043
+ async def invoke(self, agent_id: str, message: str, target: DeployTarget) -> str:
1044
+ """调用 Agent"""
1045
+ dry_run = target.extra.get("dry_run", False)
1046
+ try:
1047
+ async with AgentEngineClient(region=target.region, dry_run=dry_run) as client:
1048
+ response = await client.chat(agent_id, message, stream=False)
1049
+ return response.get("output", "")
1050
+ except DryRunExit:
1051
+ raise
1052
+ except Exception as e:
1053
+ return f"Error: {e}"
1054
+
1055
+ def _load_state(self, state_file: Path) -> Dict[str, Any]:
1056
+ """读取本地状态文件"""
1057
+ if state_file.exists():
1058
+ try:
1059
+ with open(state_file, 'r', encoding='utf-8') as f:
1060
+ return yaml.safe_load(f) or {}
1061
+ except Exception as e:
1062
+ logger.warning(f"Failed to load state file: {e}")
1063
+ return {}
1064
+
1065
+ def _load_project_config(self, project_dir: Path) -> Dict[str, Any]:
1066
+ """读取项目配置文件。"""
1067
+ for file_name in ("agentengine.yaml", "ksadk.yaml"):
1068
+ config_file = project_dir / file_name
1069
+ if not config_file.exists():
1070
+ continue
1071
+ try:
1072
+ with open(config_file, "r", encoding="utf-8-sig") as f:
1073
+ return yaml.safe_load(f) or {}
1074
+ except Exception as e:
1075
+ logger.warning(f"Failed to load project config {config_file}: {e}")
1076
+ return {}
1077
+
1078
+ def _merge_ui_runtime_state(
1079
+ self,
1080
+ *,
1081
+ local_state: Dict[str, Any],
1082
+ project_config: Dict[str, Any],
1083
+ ) -> Dict[str, Any]:
1084
+ """合并状态文件与项目配置中的 UI 运行时信息。"""
1085
+ merged = dict(local_state or {})
1086
+ for key in ("ui_profile", "ui_path", "ui_url", "ui_bundle_path", "ui_bundle_dir"):
1087
+ value = project_config.get(key)
1088
+ if value and not merged.get(key):
1089
+ merged[key] = value
1090
+ return merged
1091
+
1092
+ def _save_state(self, state_file: Path, state: Dict[str, Any]) -> None:
1093
+ """保存状态到本地文件"""
1094
+ import yaml
1095
+ try:
1096
+ with open(state_file, 'w', encoding='utf-8') as f:
1097
+ yaml.dump(state, f, default_flow_style=False, allow_unicode=True)
1098
+ logger.debug(f"State saved to {state_file}")
1099
+ except Exception as e:
1100
+ logger.warning(f"Failed to save state file: {e}")
1101
+
1102
+ def _now_iso(self) -> str:
1103
+ """返回当前时间 ISO 格式"""
1104
+ from datetime import datetime
1105
+ return datetime.now().isoformat()