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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
  2. agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
  3. ksadk/a2a/__init__.py +178 -11
  4. ksadk/a2a/bootstrap.py +334 -0
  5. ksadk/a2a/card.py +120 -0
  6. ksadk/a2a/context_store.py +203 -0
  7. ksadk/a2a/control_plane.py +818 -0
  8. ksadk/a2a/credential.py +99 -0
  9. ksadk/a2a/event_adapter.py +149 -0
  10. ksadk/a2a/executor.py +406 -89
  11. ksadk/a2a/external_transport.py +516 -0
  12. ksadk/a2a/identity.py +304 -0
  13. ksadk/a2a/ids.py +22 -0
  14. ksadk/a2a/langgraph.py +285 -0
  15. ksadk/a2a/resume_store.py +201 -0
  16. ksadk/a2a/routes.py +105 -0
  17. ksadk/a2a/server.py +107 -60
  18. ksadk/a2a/space_client.py +1101 -0
  19. ksadk/a2a/task_adapter.py +423 -0
  20. ksadk/a2a/task_event_dispatcher.py +167 -0
  21. ksadk/a2a/task_event_outbox.py +326 -0
  22. ksadk/a2a/task_store.py +206 -0
  23. ksadk/a2ui/__init__.py +27 -0
  24. ksadk/a2ui/core.py +186 -0
  25. ksadk/a2ui/models.py +166 -0
  26. ksadk/a2ui/renderer.py +198 -0
  27. ksadk/agui/__init__.py +9 -0
  28. ksadk/agui/a2ui_projection.py +92 -0
  29. ksadk/agui/agent.py +857 -0
  30. ksadk/agui/config.py +86 -0
  31. ksadk/agui/routes.py +55 -0
  32. ksadk/api/client.py +233 -191
  33. ksadk/builders/__init__.py +2 -0
  34. ksadk/builders/base.py +16 -14
  35. ksadk/builders/code_builder.py +318 -236
  36. ksadk/builders/container_builder.py +187 -167
  37. ksadk/builders/framework_requirements.py +29 -11
  38. ksadk/builders/ks3_uploader.py +58 -27
  39. ksadk/builders/managed_runtime_builder.py +145 -0
  40. ksadk/builders/mcp_builder.py +91 -92
  41. ksadk/builders/requirements_utils.py +1 -5
  42. ksadk/cli/__init__.py +89 -171
  43. ksadk/cli/cmd_a2a.py +254 -70
  44. ksadk/cli/cmd_agent.py +19 -5
  45. ksadk/cli/cmd_build.py +158 -29
  46. ksadk/cli/cmd_completion.py +47 -35
  47. ksadk/cli/cmd_config.py +267 -198
  48. ksadk/cli/cmd_create.py +500 -720
  49. ksadk/cli/cmd_dashboard.py +120 -45
  50. ksadk/cli/cmd_deploy.py +117 -58
  51. ksadk/cli/cmd_destroy.py +44 -14
  52. ksadk/cli/cmd_files.py +63 -24
  53. ksadk/cli/cmd_hermes.py +168 -89
  54. ksadk/cli/cmd_invoke.py +184 -122
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +98 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +483 -227
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +33 -10
  61. ksadk/cli/cmd_status.py +80 -31
  62. ksadk/cli/cmd_version.py +20 -13
  63. ksadk/cli/cmd_web.py +89 -13
  64. ksadk/cli/deploy_utils.py +20 -20
  65. ksadk/cli/dry_run.py +5 -3
  66. ksadk/cli/error_utils.py +37 -12
  67. ksadk/cli/global_options.py +1 -2
  68. ksadk/cli/local_runtime.py +1 -2
  69. ksadk/cli/model_catalog.py +6 -2
  70. ksadk/cli/network_options.py +34 -14
  71. ksadk/cli/resource_common.py +6 -2
  72. ksadk/cli/storage.py +0 -1
  73. ksadk/cli/ui.py +5 -3
  74. ksadk/cli/workflow_common.py +51 -17
  75. ksadk/codex/__init__.py +12 -0
  76. ksadk/codex/client.py +408 -0
  77. ksadk/codex/phase.py +143 -0
  78. ksadk/codex/runtime.py +449 -0
  79. ksadk/common/aicp_env.py +0 -1
  80. ksadk/common/auth.py +2 -2
  81. ksadk/common/constants.py +1 -3
  82. ksadk/common/llm_utils.py +17 -13
  83. ksadk/compat/__init__.py +5 -0
  84. ksadk/compat/adk_compat.py +200 -0
  85. ksadk/compat/copilotkit_a2ui.py +330 -0
  86. ksadk/configs/__init__.py +12 -12
  87. ksadk/configs/env_registry.py +500 -100
  88. ksadk/configs/global_config.py +2 -1
  89. ksadk/configs/settings.py +151 -124
  90. ksadk/conversations/__init__.py +7 -5
  91. ksadk/conversations/attachment_storage.py +16 -11
  92. ksadk/conversations/attachments.py +44 -36
  93. ksadk/conversations/compaction_pipeline.py +12 -12
  94. ksadk/conversations/compaction_prompt.py +9 -5
  95. ksadk/conversations/context.py +221 -6
  96. ksadk/conversations/message_projection.py +438 -51
  97. ksadk/conversations/model_context.py +12 -6
  98. ksadk/conversations/model_options.py +0 -1
  99. ksadk/conversations/normalize.py +20 -16
  100. ksadk/conversations/reasoning_markup.py +1 -1
  101. ksadk/conversations/runtime.py +339 -5255
  102. ksadk/conversations/runtime_compaction.py +333 -0
  103. ksadk/conversations/runtime_constants.py +27 -0
  104. ksadk/conversations/runtime_governance.py +185 -0
  105. ksadk/conversations/runtime_input.py +652 -0
  106. ksadk/conversations/runtime_invocation.py +395 -0
  107. ksadk/conversations/runtime_metadata.py +475 -0
  108. ksadk/conversations/runtime_observability.py +355 -0
  109. ksadk/conversations/runtime_payloads.py +272 -0
  110. ksadk/conversations/runtime_persistence.py +440 -0
  111. ksadk/conversations/runtime_preparation.py +418 -0
  112. ksadk/conversations/runtime_resume.py +816 -0
  113. ksadk/conversations/runtime_stream_events.py +902 -0
  114. ksadk/conversations/runtime_streaming.py +547 -0
  115. ksadk/conversations/semantic_summary.py +17 -5
  116. ksadk/conversations/session_title.py +6 -3
  117. ksadk/deployment/__init__.py +15 -16
  118. ksadk/deployment/agent_access.py +12 -23
  119. ksadk/deployment/base.py +51 -38
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +309 -202
  122. ksadk/deployment/registry.py +38 -32
  123. ksadk/deployment/state.py +11 -11
  124. ksadk/deployment/ui_config.py +1 -1
  125. ksadk/detection/__init__.py +1 -1
  126. ksadk/detection/detector.py +146 -83
  127. ksadk/detection/mcp_detector.py +9 -6
  128. ksadk/events/__init__.py +25 -0
  129. ksadk/events/parser.py +191 -0
  130. ksadk/events/replay.py +37 -0
  131. ksadk/events/runtime_event.py +264 -0
  132. ksadk/events/store.py +317 -0
  133. ksadk/harness/__init__.py +34 -0
  134. ksadk/harness/app.py +278 -0
  135. ksadk/harness/config.py +181 -0
  136. ksadk/harness/reasoner.py +105 -0
  137. ksadk/harness/runner.py +211 -0
  138. ksadk/harness/sandbox.py +141 -0
  139. ksadk/harness/tools.py +222 -0
  140. ksadk/hermes_terminal.py +23 -12
  141. ksadk/identity/resolver.py +15 -9
  142. ksadk/ids.py +32 -0
  143. ksadk/knowledge_base/adk_tool.py +1 -1
  144. ksadk/knowledge_base/client.py +23 -29
  145. ksadk/managed_runtime.py +188 -0
  146. ksadk/markdown.py +5 -2
  147. ksadk/mcp_runtime/__init__.py +9 -6
  148. ksadk/memory/__init__.py +1 -1
  149. ksadk/memory/adk/__init__.py +2 -0
  150. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  151. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  152. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  153. ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
  154. ksadk/memory/adk/long_term_memory.py +31 -35
  155. ksadk/memory/adk/resilient_session_service.py +4 -5
  156. ksadk/memory/adk/short_term_memory.py +12 -21
  157. ksadk/memory/adk_tool.py +3 -2
  158. ksadk/memory/backends/base.py +19 -19
  159. ksadk/memory/backends/memory.py +24 -25
  160. ksadk/memory/backends/redis.py +31 -30
  161. ksadk/memory/langchain_tool.py +2 -1
  162. ksadk/memory/manager.py +52 -47
  163. ksadk/memory/service.py +10 -8
  164. ksadk/model_policy.py +15 -7
  165. ksadk/model_proxy/__init__.py +51 -0
  166. ksadk/model_proxy/bootstrap.py +80 -0
  167. ksadk/model_proxy/cache.py +165 -0
  168. ksadk/model_proxy/config.py +45 -0
  169. ksadk/model_proxy/demo_webui.py +115 -0
  170. ksadk/model_proxy/detect.py +145 -0
  171. ksadk/model_proxy/gate.py +68 -0
  172. ksadk/model_proxy/namespace.py +173 -0
  173. ksadk/model_proxy/protocol_proxy.py +22 -0
  174. ksadk/model_proxy/server.py +300 -0
  175. ksadk/model_proxy/transform.py +691 -0
  176. ksadk/openclaw_gateway.py +29 -14
  177. ksadk/runners/adk_runner.py +345 -120
  178. ksadk/runners/base_runner.py +46 -29
  179. ksadk/runners/codex_runner.py +132 -0
  180. ksadk/runners/deepagents_runner.py +0 -1
  181. ksadk/runners/factory.py +17 -5
  182. ksadk/runners/langchain_runner.py +29 -742
  183. ksadk/runners/langgraph_runner.py +352 -90
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +134 -33
  186. ksadk/runners/usage_accumulator.py +1 -0
  187. ksadk/runners/utils/langfuse.py +3 -3
  188. ksadk/runners/utils/loader.py +7 -7
  189. ksadk/runtime/__init__.py +41 -0
  190. ksadk/runtime/adapter.py +345 -0
  191. ksadk/runtime/framework_adapters.py +124 -0
  192. ksadk/runtime/preprocessing.py +118 -0
  193. ksadk/runtime/runner_adapter.py +873 -0
  194. ksadk/runtime_context.py +18 -3
  195. ksadk/runtime_state.py +0 -1
  196. ksadk/sandbox/__init__.py +4 -1
  197. ksadk/sandbox/backends/__init__.py +4 -1
  198. ksadk/sandbox/backends/e2b.py +13 -5
  199. ksadk/sandbox/backends/local_process.py +11 -3
  200. ksadk/sandbox/base.py +10 -15
  201. ksadk/sandbox/factory.py +14 -4
  202. ksadk/sandbox/registry.py +57 -6
  203. ksadk/server/api_models.py +15 -5
  204. ksadk/server/app.py +455 -3885
  205. ksadk/server/composition.py +21 -0
  206. ksadk/server/factory.py +624 -0
  207. ksadk/server/routes/__init__.py +1 -0
  208. ksadk/server/routes/checkpoint_resolution.py +116 -0
  209. ksadk/server/routes/common.py +713 -0
  210. ksadk/server/routes/control.py +327 -0
  211. ksadk/server/routes/dependencies.py +98 -0
  212. ksadk/server/routes/misc.py +448 -0
  213. ksadk/server/routes/models.py +443 -0
  214. ksadk/server/routes/openai_compat.py +207 -0
  215. ksadk/server/routes/projection.py +795 -0
  216. ksadk/server/routes/routers.py +52 -0
  217. ksadk/server/routes/run.py +726 -0
  218. ksadk/server/routes/sessions.py +649 -0
  219. ksadk/server/routes/streaming.py +223 -0
  220. ksadk/server/routes/workspace.py +381 -0
  221. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
  222. ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
  224. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
  226. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
  227. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
  228. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
  239. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
  240. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
  241. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
  242. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
  272. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
  273. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
  275. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
  276. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
  277. ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
  278. ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
  279. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
  280. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
  288. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
  289. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
  290. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
  292. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
  293. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
  297. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
  298. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
  299. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
  300. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
  301. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
  302. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
  303. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
  305. ksadk/server/static/index.html +2 -2
  306. ksadk/server/terminal_sessions.py +111 -58
  307. ksadk/sessions/__init__.py +69 -19
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +2 -3
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +143 -25
  313. ksadk/sessions/resilient.py +36 -8
  314. ksadk/skills/models.py +17 -5
  315. ksadk/skills/package_store.py +5 -2
  316. ksadk/skills/runtime/agent.py +20 -5
  317. ksadk/skills/runtime/artifacts.py +1 -5
  318. ksadk/skills/runtime/backends/e2b.py +10 -4
  319. ksadk/skills/runtime/backends/local.py +15 -3
  320. ksadk/skills/runtime/base.py +1 -2
  321. ksadk/skills/runtime/executor.py +10 -3
  322. ksadk/skills/runtime/factory.py +4 -2
  323. ksadk/skills/runtime/loader.py +1 -2
  324. ksadk/skills/runtime/registry.py +15 -10
  325. ksadk/skills/runtime/request.py +3 -1
  326. ksadk/skills/service_client.py +38 -17
  327. ksadk/skills/tool_defs.py +15 -10
  328. ksadk/terminal_exec_policy.py +3 -4
  329. ksadk/tools/gateway.py +92 -15
  330. ksadk/tools/result_budget.py +12 -4
  331. ksadk/toolsets/__init__.py +134 -36
  332. ksadk/toolsets/platform.py +21 -5
  333. ksadk/toolsets/sandbox.py +82 -16
  334. ksadk/toolsets/skills.py +200 -39
  335. ksadk/toolsets/web.py +123 -26
  336. ksadk/toolsets/workspace.py +212 -101
  337. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  338. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  339. ksadk/tracing/setup.py +69 -53
  340. ksadk/tracing/span_utils.py +5 -2
  341. ksadk/tui/loop.py +78 -30
  342. ksadk/tui/stream_render.py +1 -0
  343. ksadk/ui_config.py +5 -3
  344. ksadk/version.py +1 -1
  345. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  346. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  347. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  348. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  349. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  350. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  351. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  352. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  353. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  354. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  355. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  356. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  357. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  358. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  359. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  360. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  361. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  362. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  364. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  365. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  366. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  367. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  368. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  369. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  370. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  371. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
ksadk/cli/cmd_create.py CHANGED
@@ -7,11 +7,12 @@ import platform
7
7
  import re
8
8
  import shlex
9
9
  import shutil
10
-
11
10
  from pathlib import Path
12
11
 
13
12
  import click
14
13
  import questionary
14
+ from rich.markup import escape
15
+
15
16
  from ksadk.cli.cmd_config import custom_style
16
17
  from ksadk.cli.ui import (
17
18
  print_error,
@@ -112,10 +113,10 @@ model = LiteLlm(
112
113
 
113
114
  def hello(name: str) -> dict:
114
115
  """问候工具
115
-
116
+
116
117
  Args:
117
118
  name: 名字
118
-
119
+
119
120
  Returns:
120
121
  问候语
121
122
  """
@@ -133,7 +134,7 @@ root_agent = Agent(
133
134
  },
134
135
  "langchain": {
135
136
  "agent.py": '''"""
136
- {package_name} - LangChain Agent
137
+ {package_name} - LangChain Agent (create_agent)
137
138
  """
138
139
 
139
140
  import os
@@ -142,9 +143,8 @@ from dotenv import load_dotenv
142
143
 
143
144
  load_dotenv(Path(__file__).parent.parent / ".env")
144
145
 
146
+ from langchain.agents import create_agent
145
147
  from langchain_openai import ChatOpenAI
146
- from langchain_core.prompts import ChatPromptTemplate
147
- from langchain_core.output_parsers import StrOutputParser
148
148
 
149
149
  llm = ChatOpenAI(
150
150
  model=os.getenv("OPENAI_MODEL_NAME", "glm-5.2"),
@@ -153,12 +153,28 @@ llm = ChatOpenAI(
153
153
  streaming=True,
154
154
  )
155
155
 
156
- prompt = ChatPromptTemplate.from_messages([
157
- ("system", "你是一个友好的助手。请用中文回复。"),
158
- ("human", "{{input}}")
159
- ])
160
156
 
161
- root_agent = prompt | llm | StrOutputParser()
157
+ def hello(name: str) -> str:
158
+ """问候工具
159
+
160
+ Args:
161
+ name: 名字
162
+
163
+ Returns:
164
+ 问候语
165
+ """
166
+ return f"你好, {{name}}!"
167
+
168
+
169
+ # create_agent 是新版 LangChain 定义 agent 的推荐方式,自带工具调用;
170
+ # 可选 middleware=[HumanInTheLoopMiddleware(...)](人工审批 HITL)与
171
+ # checkpointer(会话持久化)。detector 将本项目判为 LANGCHAIN,
172
+ # 经 LangChainRunner 运行。
173
+ root_agent = create_agent(
174
+ model=llm,
175
+ tools=[hello],
176
+ system_prompt="你是一个友好的助手。请用中文回复。",
177
+ )
162
178
  ''',
163
179
  },
164
180
  "langgraph": {
@@ -243,42 +259,46 @@ root_agent = None
243
259
 
244
260
  def _detect_framework(content: str) -> str:
245
261
  """检测 Agent 文件使用的框架"""
246
- if 'from deepagents' in content or 'import deepagents' in content or 'create_deep_agent(' in content:
247
- return 'deepagents'
248
- elif 'from langgraph' in content or 'import langgraph' in content:
249
- return 'langgraph'
250
- elif 'from google.adk' in content or 'import google.adk' in content:
251
- return 'adk'
252
- elif 'from langchain' in content or 'import langchain' in content:
253
- return 'langchain'
254
- return 'unknown'
262
+ if (
263
+ "from deepagents" in content
264
+ or "import deepagents" in content
265
+ or "create_deep_agent(" in content
266
+ ):
267
+ return "deepagents"
268
+ elif "from langgraph" in content or "import langgraph" in content:
269
+ return "langgraph"
270
+ elif "from google.adk" in content or "import google.adk" in content:
271
+ return "adk"
272
+ elif "from langchain" in content or "import langchain" in content:
273
+ return "langchain"
274
+ return "unknown"
255
275
 
256
276
 
257
277
  def _detect_agent_variable(content: str) -> str | None:
258
278
  """检测 Agent 变量名"""
259
279
  import re
260
-
280
+
261
281
  # 优先级匹配: root_agent > compiled graph > StateGraph > Agent()
262
282
  # Only module-level exports are valid entry variables. Service-style
263
283
  # projects often build a local `graph` inside init_agent_resources(), which
264
284
  # must not be treated as importable module state.
265
285
  patterns = [
266
- (r'^(root_agent)\s*=', 'root_agent'),
267
- (r'^(root_agent)\s*:\s*[^=]+\s*=', 'root_agent'), # type annotated root_agent
268
- (r'^(\w+)\s*=\s*\w*graph\w*\.compile\(', None), # e.g., agent = graph.compile()
269
- (r'^(\w+)\s*=\s*StateGraph', None), # e.g., graph = StateGraph(...)
270
- (r'^(\w+)\s*=\s*Agent\(', None), # ADK: agent = Agent(...)
271
- (r'^(\w+)\s*=\s*create_react_agent\(', None), # create_react_agent
272
- (r'^(\w+)\s*=\s*create_deep_agent\(', None), # deepagents create_deep_agent
273
- (r'^(\w+)\s*=\s*create_agent\(', None), # langchain create_agent
274
- (r'^(\w+)\s*=\s*build_agent\(', None), # adapter style build_agent
286
+ (r"^(root_agent)\s*=", "root_agent"),
287
+ (r"^(root_agent)\s*:\s*[^=]+\s*=", "root_agent"), # type annotated root_agent
288
+ (r"^(\w+)\s*=\s*\w*graph\w*\.compile\(", None), # e.g., agent = graph.compile()
289
+ (r"^(\w+)\s*=\s*StateGraph", None), # e.g., graph = StateGraph(...)
290
+ (r"^(\w+)\s*=\s*Agent\(", None), # ADK: agent = Agent(...)
291
+ (r"^(\w+)\s*=\s*create_react_agent\(", None), # create_react_agent
292
+ (r"^(\w+)\s*=\s*create_deep_agent\(", None), # deepagents create_deep_agent
293
+ (r"^(\w+)\s*=\s*create_agent\(", None), # langchain create_agent
294
+ (r"^(\w+)\s*=\s*build_agent\(", None), # adapter style build_agent
275
295
  ]
276
-
296
+
277
297
  for pattern, fixed_name in patterns:
278
298
  match = re.search(pattern, content, re.MULTILINE | re.IGNORECASE)
279
299
  if match:
280
300
  return fixed_name if fixed_name else match.group(1)
281
-
301
+
282
302
  return None
283
303
 
284
304
 
@@ -367,7 +387,11 @@ def _load_entry_from_langgraph_json(directory: Path) -> tuple[Path, str] | None:
367
387
  path_part = path_part.strip().removeprefix("./")
368
388
  agent_var = agent_var.strip() or "root_agent"
369
389
  entry_path = directory / Path(path_part.replace("\\", "/"))
370
- if entry_path.exists() and entry_path.is_file() and _entry_exposes_variable(entry_path, agent_var):
390
+ if (
391
+ entry_path.exists()
392
+ and entry_path.is_file()
393
+ and _entry_exposes_variable(entry_path, agent_var)
394
+ ):
371
395
  return entry_path, agent_var
372
396
  return None
373
397
 
@@ -389,7 +413,7 @@ def _load_framework_from_agentengine_yaml(directory: Path) -> str | None:
389
413
  if not isinstance(framework, str):
390
414
  return None
391
415
  framework = framework.strip().lower()
392
- if framework in {"adk", "langchain", "langgraph", "deepagents", "openclaw", "hermes"}:
416
+ if framework in {"adk", "langchain", "langgraph", "deepagents", "openclaw", "hermes", "codex"}:
393
417
  return framework
394
418
  return None
395
419
 
@@ -501,35 +525,37 @@ def _fix_dotenv_paths_in_file(file_path: Path, depth: int = 1) -> bool:
501
525
  返回是否修改了文件
502
526
  """
503
527
  import re
504
-
528
+
505
529
  try:
506
- content = file_path.read_text(encoding='utf-8')
530
+ content = file_path.read_text(encoding="utf-8")
507
531
  except Exception:
508
532
  return False
509
-
533
+
510
534
  # 构建替换的 parent 链
511
- parent_chain = '.parent' * depth
512
-
535
+ parent_chain = ".parent" * depth
536
+
513
537
  # 匹配各种 load_dotenv 模式
514
538
  patterns = [
515
539
  # load_dotenv(Path(__file__).parent / ".env")
516
- (r'(load_dotenv\s*\(\s*Path\s*\(\s*__file__\s*\)\s*)\.parent(\s*/\s*["\']\.env["\'])',
517
- rf'\1{parent_chain}\2'),
540
+ (
541
+ r'(load_dotenv\s*\(\s*Path\s*\(\s*__file__\s*\)\s*)\.parent(\s*/\s*["\']\.env["\'])',
542
+ rf"\1{parent_chain}\2",
543
+ ),
518
544
  # load_dotenv(Path(__file__).parent.parent / ".env") -> 根据 depth 调整
519
545
  ]
520
-
546
+
521
547
  modified = False
522
548
  new_content = content
523
-
549
+
524
550
  for pattern, replacement in patterns:
525
551
  new_content_temp = re.sub(pattern, replacement, new_content)
526
552
  if new_content_temp != new_content:
527
553
  new_content = new_content_temp
528
554
  modified = True
529
-
555
+
530
556
  if modified:
531
- file_path.write_text(new_content, encoding='utf-8')
532
-
557
+ file_path.write_text(new_content, encoding="utf-8")
558
+
533
559
  return modified
534
560
 
535
561
 
@@ -539,76 +565,76 @@ def _fix_dotenv_paths_recursive(directory: Path, depth: int = 1) -> int:
539
565
  返回修复的文件数量
540
566
  """
541
567
  fixed_count = 0
542
-
543
- for py_file in directory.rglob('*.py'):
568
+
569
+ for py_file in directory.rglob("*.py"):
544
570
  # 计算相对深度
545
571
  rel_path = py_file.relative_to(directory)
546
572
  file_depth = len(rel_path.parts) + depth - 1 # 相对于项目根目录
547
-
573
+
548
574
  if _fix_dotenv_paths_in_file(py_file, file_depth):
549
575
  fixed_count += 1
550
-
576
+
551
577
  return fixed_count
552
578
 
553
579
 
554
580
  def _fix_nested_imports(file_path: Path, subdirs: list[str]) -> bool:
555
581
  """
556
582
  修复文件中的嵌套目录导入
557
-
583
+
558
584
  当源目录包含子包目录(如 my_agent/)时,
559
585
  入口文件中的 `from my_agent import xxx` 需要改为 `from .my_agent import xxx`
560
-
586
+
561
587
  Args:
562
588
  file_path: 需要修复的文件
563
589
  subdirs: 同级子目录名列表
564
-
590
+
565
591
  Returns:
566
592
  是否修改了文件
567
593
  """
568
594
  import re
569
-
595
+
570
596
  if not subdirs:
571
597
  return False
572
-
598
+
573
599
  try:
574
- content = file_path.read_text(encoding='utf-8')
600
+ content = file_path.read_text(encoding="utf-8")
575
601
  except Exception:
576
602
  return False
577
-
603
+
578
604
  original = content
579
-
605
+
580
606
  # 为每个子目录生成修复规则
581
607
  for subdir in subdirs:
582
608
  # from my_agent import xxx -> from .my_agent import xxx
583
- pattern = rf'^(from\s+)({re.escape(subdir)})(\s+import\s+)'
584
- replacement = rf'\1.\2\3'
609
+ pattern = rf"^(from\s+)({re.escape(subdir)})(\s+import\s+)"
610
+ replacement = r"\1.\2\3"
585
611
  content = re.sub(pattern, replacement, content, flags=re.MULTILINE)
586
-
612
+
587
613
  # import my_agent -> from . import my_agent
588
614
  # 只处理独立的 import 语句
589
- pattern_import = rf'^import\s+({re.escape(subdir)})\s*$'
590
- replacement_import = rf'from . import \1'
615
+ pattern_import = rf"^import\s+({re.escape(subdir)})\s*$"
616
+ replacement_import = r"from . import \1"
591
617
  content = re.sub(pattern_import, replacement_import, content, flags=re.MULTILINE)
592
-
618
+
593
619
  if content != original:
594
- file_path.write_text(content, encoding='utf-8')
620
+ file_path.write_text(content, encoding="utf-8")
595
621
  return True
596
-
622
+
597
623
  return False
598
624
 
599
625
 
600
626
  def _generate_env_content(global_env: dict) -> str:
601
- """生成 .env 文件内容"""
602
- api_key = global_env.get("OPENAI_API_KEY", "")
627
+ """Generate project-local configuration without copying credentials.
628
+
629
+ ``init`` may read a global profile to retain non-secret endpoint defaults, but
630
+ it must not persist the caller's credentials in a newly created directory.
631
+ """
603
632
  base_url = global_env.get("OPENAI_BASE_URL", "")
604
633
  model_name = global_env.get("OPENAI_MODEL_NAME", "")
605
- ks_ak = global_env.get("KSYUN_ACCESS_KEY", "")
606
- ks_sk = global_env.get("KSYUN_SECRET_KEY", "")
607
634
  ks_region = global_env.get("KSYUN_REGION", "cn-beijing-6")
608
- ks_account = global_env.get("KSYUN_ACCOUNT_ID", "")
609
-
610
- env_content = f"""# 模型配置
611
- OPENAI_API_KEY={api_key}
635
+
636
+ env_content = """# 模型配置
637
+ # OPENAI_API_KEY=
612
638
  """
613
639
  if base_url:
614
640
  env_content += f"OPENAI_BASE_URL={base_url}\n"
@@ -618,110 +644,131 @@ OPENAI_API_KEY={api_key}
618
644
  env_content += f"OPENAI_MODEL_NAME={model_name}\n"
619
645
  else:
620
646
  env_content += "# OPENAI_MODEL_NAME=\n"
621
-
622
- env_content += f"""
647
+
648
+ env_content += """
623
649
  # 金山云配置
624
650
  """
625
- if ks_ak:
626
- env_content += f"KSYUN_ACCESS_KEY={ks_ak}\n"
627
- else:
628
- env_content += "# KSYUN_ACCESS_KEY=\n"
629
- if ks_sk:
630
- env_content += f"KSYUN_SECRET_KEY={ks_sk}\n"
631
- else:
632
- env_content += "# KSYUN_SECRET_KEY=\n"
651
+ env_content += "# KSYUN_ACCESS_KEY=\n"
652
+ env_content += "# KSYUN_SECRET_KEY=\n"
633
653
  env_content += f"KSYUN_REGION={ks_region}\n"
634
- if ks_account:
635
- env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
636
- else:
637
- env_content += "# KSYUN_ACCOUNT_ID=\n"
638
-
654
+ env_content += "# KSYUN_ACCOUNT_ID=\n"
655
+
639
656
  return env_content
640
657
 
641
658
 
659
+ def _generate_codex_env_content(global_env: dict) -> str:
660
+ """Generate the intentionally small local-only environment for a Codex project.
661
+
662
+ A ManagedRuntime manifest is declarative and must never acquire cloud, storage or
663
+ observability credentials merely because the developer ran ``init``. The three
664
+ OpenAI-compatible values are enough for native ``ksadk web`` debugging. The
665
+ optional proxy knobs are documented rather than copied from the global profile,
666
+ because their upstream key can be different from the model key.
667
+ """
668
+ base_url = global_env.get("OPENAI_BASE_URL", "")
669
+ model_name = global_env.get("OPENAI_MODEL_NAME", "")
670
+ lines = [
671
+ "# 本地 Codex 模型配置(仅用于 ksadk web,不会进入 ManagedRuntime bundle)",
672
+ "# OPENAI_API_KEY=",
673
+ f"OPENAI_BASE_URL={base_url}" if base_url else "# OPENAI_BASE_URL=",
674
+ f"OPENAI_MODEL_NAME={model_name}" if model_name else "# OPENAI_MODEL_NAME=glm-5.2",
675
+ "",
676
+ "# 可选:自定义上游仅支持 Chat Completions 时,强制启用协议转换代理",
677
+ "# KSADK_CODEX_USE_PROXY=1",
678
+ "# KSADK_PROXY_UPSTREAM_BASE=",
679
+ "# KSADK_PROXY_UPSTREAM_KEY=",
680
+ "",
681
+ ]
682
+ return "\n".join(lines)
683
+
684
+
642
685
  def _generate_requirements_from_imports(directory: Path, framework: str) -> str:
643
686
  """
644
687
  扫描目录中的 Python 文件,从 import 语句生成 requirements.txt
645
688
  """
646
689
  import re
647
-
690
+
648
691
  # 常用包名到 PyPI 包名的映射
649
692
  import_to_package = {
650
- 'langchain': 'langchain',
651
- 'langchain_openai': 'langchain-openai',
652
- 'langchain_anthropic': 'langchain-anthropic',
653
- 'langchain_core': 'langchain-core',
654
- 'langchain_community': 'langchain-community',
655
- 'langgraph': 'langgraph',
656
- 'deepagents': 'deepagents',
657
- 'openai': 'openai',
658
- 'anthropic': 'anthropic',
659
- 'dotenv': 'python-dotenv',
660
- 'pydantic': 'pydantic',
661
- 'httpx': 'httpx',
662
- 'requests': 'requests',
663
- 'google.adk': 'google-adk',
664
- 'google.genai': 'google-genai',
665
- 'tiktoken': 'tiktoken',
666
- 'faiss': 'faiss-cpu',
667
- 'chromadb': 'chromadb',
668
- 'tavily': 'tavily-python',
693
+ "langchain": "langchain",
694
+ "langchain_openai": "langchain-openai",
695
+ "langchain_anthropic": "langchain-anthropic",
696
+ "langchain_core": "langchain-core",
697
+ "langchain_community": "langchain-community",
698
+ "langgraph": "langgraph",
699
+ "deepagents": "deepagents",
700
+ "openai": "openai",
701
+ "anthropic": "anthropic",
702
+ "dotenv": "python-dotenv",
703
+ "pydantic": "pydantic",
704
+ "httpx": "httpx",
705
+ "requests": "requests",
706
+ "google.adk": "google-adk",
707
+ "google.genai": "google-genai",
708
+ "tiktoken": "tiktoken",
709
+ "faiss": "faiss-cpu",
710
+ "chromadb": "chromadb",
711
+ "tavily": "tavily-python",
669
712
  }
670
-
713
+
671
714
  found_packages = set()
672
-
715
+
673
716
  # 扫描所有 .py 文件
674
- for py_file in directory.rglob('*.py'):
717
+ for py_file in directory.rglob("*.py"):
675
718
  try:
676
- content = py_file.read_text(encoding='utf-8')
719
+ content = py_file.read_text(encoding="utf-8")
677
720
  except Exception:
678
721
  continue
679
-
722
+
680
723
  # 匹配 import 语句
681
724
  # from xxx import yyy
682
725
  # import xxx
683
726
  import_patterns = [
684
- r'^from\s+([\w\.]+)',
685
- r'^import\s+([\w\.]+)',
727
+ r"^from\s+([\w\.]+)",
728
+ r"^import\s+([\w\.]+)",
686
729
  ]
687
-
730
+
688
731
  for pattern in import_patterns:
689
732
  for match in re.finditer(pattern, content, re.MULTILINE):
690
- module = match.group(1).split('.')[0] # 取顶级模块
733
+ module = match.group(1).split(".")[0] # 取顶级模块
691
734
  full_module = match.group(1)
692
-
735
+
693
736
  # 检查是否在映射中
694
737
  if full_module in import_to_package:
695
738
  found_packages.add(import_to_package[full_module])
696
739
  elif module in import_to_package:
697
740
  found_packages.add(import_to_package[module])
698
741
  # 检查常见的下划线包名
699
- elif module.replace('_', '-') in ['langchain-openai', 'langchain-anthropic',
700
- 'langchain-core', 'langchain-community']:
701
- found_packages.add(module.replace('_', '-'))
702
-
742
+ elif module.replace("_", "-") in [
743
+ "langchain-openai",
744
+ "langchain-anthropic",
745
+ "langchain-core",
746
+ "langchain-community",
747
+ ]:
748
+ found_packages.add(module.replace("_", "-"))
749
+
703
750
  # 确保框架相关的核心依赖在列表中
704
- if framework == 'langgraph':
705
- found_packages.add('langgraph')
706
- found_packages.add('langchain')
707
- found_packages.add('langchain-openai')
708
- elif framework == 'deepagents':
709
- found_packages.add('deepagents')
710
- found_packages.add('langgraph')
711
- found_packages.add('langchain')
712
- found_packages.add('langchain-openai')
713
- elif framework == 'langchain':
714
- found_packages.add('langchain')
715
- found_packages.add('langchain-openai')
716
- elif framework == 'adk':
717
- found_packages.add('google-adk')
718
-
751
+ if framework == "langgraph":
752
+ found_packages.add("langgraph")
753
+ found_packages.add("langchain")
754
+ found_packages.add("langchain-openai")
755
+ elif framework == "deepagents":
756
+ found_packages.add("deepagents")
757
+ found_packages.add("langgraph")
758
+ found_packages.add("langchain")
759
+ found_packages.add("langchain-openai")
760
+ elif framework == "langchain":
761
+ found_packages.add("langchain")
762
+ found_packages.add("langchain-openai")
763
+ elif framework == "adk":
764
+ found_packages.add("google-adk")
765
+
719
766
  # 总是添加 python-dotenv
720
- found_packages.add('python-dotenv')
721
-
767
+ found_packages.add("python-dotenv")
768
+
722
769
  # 按字母顺序排序
723
770
  sorted_packages = sorted(found_packages)
724
- return '\n'.join(sorted_packages) + '\n'
771
+ return "\n".join(sorted_packages) + "\n"
725
772
 
726
773
 
727
774
  def _analyze_langgraph_state(content: str) -> dict:
@@ -752,7 +799,9 @@ def _analyze_langgraph_state(content: str) -> dict:
752
799
  preferred = ("query", "question", "prompt", "user_input", "input")
753
800
  input_field = next((field for field in preferred if field in candidates), None)
754
801
 
755
- if candidates and ("TypedDict" in content or "StateGraph(" in content or "langgraph" in content):
802
+ if candidates and (
803
+ "TypedDict" in content or "StateGraph(" in content or "langgraph" in content
804
+ ):
756
805
  return {"kind": "custom", "input_field": input_field}
757
806
  if "StateGraph(" in content or "from langgraph" in content or "import langgraph" in content:
758
807
  return {"kind": "ambiguous", "input_field": input_field}
@@ -789,15 +838,15 @@ def _generate_langgraph_adapter_content(
789
838
  ) -> str:
790
839
  input_field = analysis.get("input_field")
791
840
  if input_field:
792
- return_body = f''' return {{
841
+ return_body = f""" return {{
793
842
  "{input_field}": payload.get("input", ""),
794
- }}'''
843
+ }}"""
795
844
  else:
796
- return_body = ''' # TODO: Map AgentEngine's chat payload to your LangGraph State.
845
+ return_body = """ # TODO: Map AgentEngine's chat payload to your LangGraph State.
797
846
  # Common examples:
798
847
  # return {"query": payload.get("input", "")}
799
848
  # return {"question": payload.get("input", ""), "context": []}
800
- return dict(payload)'''
849
+ return dict(payload)"""
801
850
 
802
851
  return f'''"""
803
852
  AgentEngine adapter generated for a LangGraph project.
@@ -919,7 +968,9 @@ def _detect_deepagents_service_project(
919
968
  def _generate_deepagents_service_adapter_content(analysis: dict) -> str:
920
969
  init_module = analysis["init_module"]
921
970
  runnable_module = analysis.get("runnable_module")
922
- runnable_module_constant = f'RUNNABLE_MODULE = "{runnable_module}"' if runnable_module else "RUNNABLE_MODULE = None"
971
+ runnable_module_constant = (
972
+ f'RUNNABLE_MODULE = "{runnable_module}"' if runnable_module else "RUNNABLE_MODULE = None"
973
+ )
923
974
  runnable_build = " return None\n"
924
975
  if runnable_module:
925
976
  runnable_build = """ try:
@@ -1040,7 +1091,14 @@ class AgentEngineDeepAgentsServiceAdapter:
1040
1091
  return self._normalize_result(result)
1041
1092
 
1042
1093
  if hasattr(self._agent, "ainvoke"):
1043
- result = await self._agent.ainvoke({{"messages": payload.get("messages", [])}} if isinstance(payload, dict) and payload.get("messages") else payload, config=config, **kwargs)
1094
+ agent_payload = (
1095
+ {{"messages": payload.get("messages", [])}}
1096
+ if isinstance(payload, dict) and payload.get("messages")
1097
+ else payload
1098
+ )
1099
+ result = await self._agent.ainvoke(
1100
+ agent_payload, config=config, **kwargs
1101
+ )
1044
1102
  return self._normalize_result(result)
1045
1103
  result = self._agent.invoke(payload, config=config, **kwargs)
1046
1104
  return self._normalize_result(result)
@@ -1050,7 +1108,10 @@ class AgentEngineDeepAgentsServiceAdapter:
1050
1108
  loop = asyncio.get_running_loop()
1051
1109
  except RuntimeError:
1052
1110
  return asyncio.run(self.ainvoke(payload, config=config, **kwargs))
1053
- raise RuntimeError("Synchronous invoke cannot run while an event loop is already active; use ainvoke instead")
1111
+ raise RuntimeError(
1112
+ "Synchronous invoke cannot run while an event loop is already active; "
1113
+ "use ainvoke instead"
1114
+ )
1054
1115
 
1055
1116
 
1056
1117
  def ksadk_prepare_state(payload: dict, session_context: dict) -> dict:
@@ -1093,55 +1154,55 @@ def _wrap_agent_file(from_agent_path: Path, project_name: str, framework: str, a
1093
1154
  """包装单个 Agent 文件到新项目"""
1094
1155
  project_path = Path(project_name)
1095
1156
  package_name = _runtime_package_name_or_exit(project_path.name)
1096
-
1157
+
1097
1158
  if project_path.exists():
1098
1159
  print_error(f"目录 '{project_name}' 已存在")
1099
1160
  raise SystemExit(1)
1100
-
1161
+
1101
1162
  print_title("包装 Agent 文件")
1102
1163
  print_kv("创建项目", project_name)
1103
1164
  print_kv("框架", framework)
1104
1165
  print_kv("包装文件", str(from_agent_path))
1105
1166
  print_kv("Agent 变量", agent_var)
1106
-
1167
+
1107
1168
  # 创建目录
1108
1169
  (project_path / package_name).mkdir(parents=True)
1109
-
1170
+
1110
1171
  # 复制源文件并修复 .env 路径
1111
1172
  source_filename = from_agent_path.name
1112
1173
  dest_path = project_path / package_name / source_filename
1113
-
1174
+
1114
1175
  # 读取源文件内容
1115
- source_content = from_agent_path.read_text(encoding='utf-8')
1116
-
1176
+ source_content = from_agent_path.read_text(encoding="utf-8")
1177
+
1117
1178
  # 修复 load_dotenv 路径:parent -> parent.parent (因为文件被移到了子目录)
1118
1179
  fixed_content = re.sub(
1119
1180
  r'(load_dotenv\s*\(\s*Path\s*\(\s*__file__\s*\)\s*\.parent)(\s*/\s*["\']\.env["\'])',
1120
- r'\1.parent\2',
1121
- source_content
1181
+ r"\1.parent\2",
1182
+ source_content,
1122
1183
  )
1123
-
1184
+
1124
1185
  if fixed_content != source_content:
1125
1186
  print_info("已自动修复 .env 加载路径")
1126
-
1187
+
1127
1188
  # 写入目标文件
1128
- dest_path.write_text(fixed_content, encoding='utf-8')
1129
-
1189
+ dest_path.write_text(fixed_content, encoding="utf-8")
1190
+
1130
1191
  # 检测全局配置
1131
1192
  from ksadk.configs.global_config import (
1132
- global_config_exists,
1133
1193
  get_env_from_global_config,
1194
+ global_config_exists,
1134
1195
  )
1135
-
1196
+
1136
1197
  global_env = {}
1137
1198
  if global_config_exists():
1138
1199
  global_env = get_env_from_global_config()
1139
1200
  if global_env:
1140
- print_info("检测到全局配置,已自动填充凭证")
1141
-
1201
+ print_info("检测到全局配置;项目 .env 仅保留非敏感默认值,凭证不会被复制")
1202
+
1142
1203
  # 生成 .env
1143
1204
  (project_path / ".env").write_text(_generate_env_content(global_env), encoding="utf-8-sig")
1144
-
1205
+
1145
1206
  # LangGraph custom-state 项目生成 adapter,避免直接猜业务 State 语义。
1146
1207
  entry_relative = Path(source_filename)
1147
1208
  export_agent_var = agent_var
@@ -1174,23 +1235,29 @@ def _wrap_agent_file(from_agent_path: Path, project_name: str, framework: str, a
1174
1235
 
1175
1236
  # 生成 agentengine.yaml
1176
1237
  entry_module = entry_relative.with_suffix("").name
1177
- (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置 (Wrapped)
1238
+ (project_path / "agentengine.yaml").write_text(
1239
+ f"""# AgentEngine 项目配置 (Wrapped)
1178
1240
  name: {package_name}
1179
1241
  version: "1.0.0"
1180
1242
 
1181
1243
  framework: {framework}
1182
1244
  entry_point: {package_name}/{entry_relative}
1183
1245
  agent_variable: {export_agent_var}
1184
- """, encoding="utf-8-sig")
1185
-
1246
+ """,
1247
+ encoding="utf-8-sig",
1248
+ )
1249
+
1186
1250
  # 生成 __init__.py
1187
- (project_path / package_name / "__init__.py").write_text(f'''"""
1251
+ (project_path / package_name / "__init__.py").write_text(
1252
+ f'''"""
1188
1253
  {project_name} - Wrapped Agent
1189
1254
  """
1190
1255
  from .{entry_module} import {export_agent_var} as root_agent
1191
1256
  __all__ = ["root_agent"]
1192
- ''', encoding="utf-8-sig")
1193
-
1257
+ ''',
1258
+ encoding="utf-8-sig",
1259
+ )
1260
+
1194
1261
  # 生成 requirements.txt
1195
1262
  reqs = ""
1196
1263
  if framework == "langchain":
@@ -1202,9 +1269,10 @@ __all__ = ["root_agent"]
1202
1269
  elif framework == "adk":
1203
1270
  reqs = "google-adk\npython-dotenv\n"
1204
1271
  (project_path / "requirements.txt").write_text(reqs, encoding="utf-8")
1205
-
1272
+
1206
1273
  # 生成 README.md
1207
- (project_path / "README.md").write_text(f"""# {project_name}
1274
+ (project_path / "README.md").write_text(
1275
+ f"""# {project_name}
1208
1276
 
1209
1277
  Wrapped Agent project (from `{from_agent_path.name}`).
1210
1278
 
@@ -1214,36 +1282,36 @@ Wrapped Agent project (from `{from_agent_path.name}`).
1214
1282
  cd {project_name}
1215
1283
  agentengine run -i .
1216
1284
  ```
1217
- """, encoding="utf-8-sig")
1218
-
1285
+ """,
1286
+ encoding="utf-8-sig",
1287
+ )
1288
+
1219
1289
  print_success("包装完成")
1220
1290
  print_rule("快速开始")
1221
1291
  _print_quick_start_commands(project_name, ["agentengine run -i ."])
1222
1292
 
1223
1293
 
1224
- def _wrap_agent_directory(from_agent_dir: Path, project_name: str, framework: str,
1225
- entry_file: Path, agent_var: str):
1294
+ def _wrap_agent_directory(
1295
+ from_agent_dir: Path, project_name: str, framework: str, entry_file: Path, agent_var: str
1296
+ ):
1226
1297
  """包装 Agent 目录到新项目"""
1227
- import shutil
1228
-
1298
+
1229
1299
  project_path = Path(project_name)
1230
1300
  package_name = _runtime_package_name_or_exit(project_path.name)
1231
- source_dir_name = from_agent_dir.name
1232
-
1233
1301
  if project_path.exists():
1234
1302
  print_error(f"目录 '{project_name}' 已存在")
1235
1303
  raise SystemExit(1)
1236
-
1304
+
1237
1305
  print_title("包装 Agent 目录")
1238
1306
  print_kv("创建项目", project_name)
1239
1307
  print_kv("框架", framework)
1240
1308
  print_kv("包装目录", str(from_agent_dir))
1241
1309
  print_kv("入口文件", entry_file.name)
1242
1310
  print_kv("Agent 变量", agent_var)
1243
-
1311
+
1244
1312
  # 创建项目目录
1245
1313
  project_path.mkdir(parents=True)
1246
-
1314
+
1247
1315
  # 复制整个源目录到项目中(作为 package)
1248
1316
  dest_package_path = project_path / package_name
1249
1317
  ignore_names = {
@@ -1272,42 +1340,46 @@ def _wrap_agent_directory(from_agent_dir: Path, project_name: str, framework: st
1272
1340
  return ignored
1273
1341
 
1274
1342
  shutil.copytree(from_agent_dir, dest_package_path, ignore=_ignore_copytree)
1275
-
1343
+
1276
1344
  print_info(f"已复制 {sum(1 for _ in dest_package_path.rglob('*.py'))} 个 Python 文件")
1277
-
1345
+
1278
1346
  # 递归修复 .env 路径
1279
1347
  fixed_count = _fix_dotenv_paths_recursive(dest_package_path, depth=2)
1280
1348
  if fixed_count > 0:
1281
1349
  print_info(f"已自动修复 {fixed_count} 个文件的 .env 加载路径")
1282
-
1350
+
1283
1351
  # 修复嵌套目录导入路径
1284
1352
  # 查找源目录中的子目录(作为 Python 包)
1285
- subdirs = [d.name for d in from_agent_dir.iterdir()
1286
- if d.is_dir() and not d.name.startswith('.') and not d.name.startswith('_')
1287
- and (d / '__init__.py').exists()]
1288
-
1353
+ subdirs = [
1354
+ d.name
1355
+ for d in from_agent_dir.iterdir()
1356
+ if d.is_dir()
1357
+ and not d.name.startswith(".")
1358
+ and not d.name.startswith("_")
1359
+ and (d / "__init__.py").exists()
1360
+ ]
1361
+
1289
1362
  if subdirs:
1290
1363
  # 修复入口文件中的导入
1291
1364
  dest_entry_file = dest_package_path / entry_file.relative_to(from_agent_dir)
1292
1365
  if _fix_nested_imports(dest_entry_file, subdirs):
1293
1366
  print_info(f"已修复嵌套目录导入: {', '.join(subdirs)}")
1294
-
1295
-
1367
+
1296
1368
  # 检测全局配置
1297
1369
  from ksadk.configs.global_config import (
1298
- global_config_exists,
1299
1370
  get_env_from_global_config,
1371
+ global_config_exists,
1300
1372
  )
1301
-
1373
+
1302
1374
  global_env = {}
1303
1375
  if global_config_exists():
1304
1376
  global_env = get_env_from_global_config()
1305
1377
  if global_env:
1306
- print_info("检测到全局配置,已自动填充凭证")
1307
-
1378
+ print_info("检测到全局配置;项目 .env 仅保留非敏感默认值,凭证不会被复制")
1379
+
1308
1380
  # 生成 .env
1309
1381
  (project_path / ".env").write_text(_generate_env_content(global_env), encoding="utf-8-sig")
1310
-
1382
+
1311
1383
  # 生成 agentengine.yaml
1312
1384
  # entry_point 相对于项目根目录
1313
1385
  entry_relative = entry_file.relative_to(from_agent_dir)
@@ -1350,72 +1422,79 @@ def _wrap_agent_directory(from_agent_dir: Path, project_name: str, framework: st
1350
1422
  export_agent_var = "root_agent"
1351
1423
  print_warn("LangGraph state 检测: ambiguous adapter generated, review required")
1352
1424
 
1353
- (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置 (Wrapped Directory)
1425
+ (project_path / "agentengine.yaml").write_text(
1426
+ f"""# AgentEngine 项目配置 (Wrapped Directory)
1354
1427
  name: {package_name}
1355
1428
  version: "1.0.0"
1356
1429
 
1357
1430
  framework: {framework}
1358
1431
  entry_point: {package_name}/{entry_relative}
1359
1432
  agent_variable: {export_agent_var}
1360
- """, encoding="utf-8-sig")
1361
-
1433
+ """,
1434
+ encoding="utf-8-sig",
1435
+ )
1436
+
1362
1437
  # 确保 __init__.py 正确导出 root_agent
1363
1438
  init_file = dest_package_path / "__init__.py"
1364
1439
  entry_module = ".".join(entry_relative.with_suffix("").parts) # e.g. src.agentengine_adapter
1365
1440
  expected_export_line = f"from .{entry_module} import {export_agent_var} as root_agent"
1366
-
1441
+
1367
1442
  # 检查现有 __init__.py 是否已导出 root_agent
1368
1443
  init_has_export = False
1369
1444
  if init_file.exists():
1370
- init_content = init_file.read_text(encoding='utf-8')
1445
+ init_content = init_file.read_text(encoding="utf-8")
1371
1446
  if expected_export_line in init_content:
1372
1447
  init_has_export = True
1373
-
1448
+
1374
1449
  if not init_has_export:
1375
1450
  # 追加或创建导出语句
1376
- export_code = f'''
1451
+ export_code = f"""
1377
1452
  # AgentEngine 导出 (自动添加)
1378
1453
  {expected_export_line}
1379
1454
  __all__ = ["root_agent"]
1380
- '''
1455
+ """
1381
1456
  if init_file.exists():
1382
1457
  # 追加到现有文件
1383
- with open(init_file, 'a', encoding='utf-8') as f:
1458
+ with open(init_file, "a", encoding="utf-8") as f:
1384
1459
  f.write(export_code)
1385
1460
  print_info("已修复 __init__.py 导出")
1386
1461
  else:
1387
- init_file.write_text(f'''"""
1462
+ init_file.write_text(
1463
+ f'''"""
1388
1464
  {project_name} - Wrapped Agent
1389
1465
  """
1390
1466
  {expected_export_line}
1391
1467
  __all__ = ["root_agent"]
1392
- ''', encoding="utf-8-sig")
1393
-
1468
+ ''',
1469
+ encoding="utf-8-sig",
1470
+ )
1471
+
1394
1472
  # 处理 requirements.txt
1395
1473
  source_requirements = from_agent_dir / "requirements.txt"
1396
1474
  dest_requirements = project_path / "requirements.txt"
1397
-
1475
+
1398
1476
  if source_requirements.exists():
1399
1477
  # 如果源目录有 requirements.txt,复制并补充必要的依赖
1400
1478
  import shutil as shutil_req
1479
+
1401
1480
  shutil_req.copy(source_requirements, dest_requirements)
1402
1481
  print_info("已复制 requirements.txt")
1403
-
1482
+
1404
1483
  # 检查是否缺少必要依赖,追加
1405
- existing_reqs = dest_requirements.read_text(encoding='utf-8').lower()
1484
+ existing_reqs = dest_requirements.read_text(encoding="utf-8").lower()
1406
1485
  missing = []
1407
- if framework == "langgraph" and 'langgraph' not in existing_reqs:
1486
+ if framework == "langgraph" and "langgraph" not in existing_reqs:
1408
1487
  missing.append("langgraph")
1409
1488
  if framework == "deepagents":
1410
- if 'deepagents' not in existing_reqs:
1489
+ if "deepagents" not in existing_reqs:
1411
1490
  missing.append("deepagents")
1412
- if 'langgraph' not in existing_reqs:
1491
+ if "langgraph" not in existing_reqs:
1413
1492
  missing.append("langgraph")
1414
- if 'python-dotenv' not in existing_reqs and 'dotenv' not in existing_reqs:
1493
+ if "python-dotenv" not in existing_reqs and "dotenv" not in existing_reqs:
1415
1494
  missing.append("python-dotenv")
1416
-
1495
+
1417
1496
  if missing:
1418
- with open(dest_requirements, 'a', encoding='utf-8') as f:
1497
+ with open(dest_requirements, "a", encoding="utf-8") as f:
1419
1498
  f.write("\n# Added by agentengine\n")
1420
1499
  for pkg in missing:
1421
1500
  f.write(f"{pkg}\n")
@@ -1425,9 +1504,10 @@ __all__ = ["root_agent"]
1425
1504
  reqs = _generate_requirements_from_imports(dest_package_path, framework)
1426
1505
  dest_requirements.write_text(reqs, encoding="utf-8")
1427
1506
  print_info(f"已自动生成 requirements.txt ({reqs.count(chr(10))} 个依赖)")
1428
-
1507
+
1429
1508
  # 生成 README.md
1430
- (project_path / "README.md").write_text(f"""# {project_name}
1509
+ (project_path / "README.md").write_text(
1510
+ f"""# {project_name}
1431
1511
 
1432
1512
  Wrapped Agent project (from `{from_agent_dir.name}/`).
1433
1513
 
@@ -1437,14 +1517,16 @@ Wrapped Agent project (from `{from_agent_dir.name}/`).
1437
1517
  cd {project_name}
1438
1518
  agentengine run -i .
1439
1519
  ```
1440
- """, encoding="utf-8-sig")
1441
-
1520
+ """,
1521
+ encoding="utf-8-sig",
1522
+ )
1523
+
1442
1524
  # === 运行时验证 ===
1443
1525
  print_rule("验证 Agent 加载")
1444
1526
  import subprocess
1445
1527
  import sys
1446
-
1447
- verify_code = f'''
1528
+
1529
+ verify_code = f"""
1448
1530
  import sys
1449
1531
  sys.path.insert(0, ".")
1450
1532
  try:
@@ -1453,16 +1535,16 @@ try:
1453
1535
  except Exception as e:
1454
1536
  print("ERROR:" + str(e))
1455
1537
  sys.exit(1)
1456
- '''
1457
-
1538
+ """
1539
+
1458
1540
  result = subprocess.run(
1459
1541
  [sys.executable, "-c", verify_code],
1460
1542
  cwd=str(project_path),
1461
1543
  capture_output=True,
1462
1544
  text=True,
1463
- timeout=30
1545
+ timeout=30,
1464
1546
  )
1465
-
1547
+
1466
1548
  if result.returncode == 0:
1467
1549
  output = result.stdout.strip()
1468
1550
  if output.startswith("TYPE:"):
@@ -1474,47 +1556,16 @@ except Exception as e:
1474
1556
  error_msg = result.stderr or result.stdout
1475
1557
  print_warn(f"Agent 加载警告: {error_msg[:200]}")
1476
1558
  print_info("提示: 请检查依赖是否已安装,或代码是否有错误")
1477
-
1559
+
1478
1560
  print_success("包装完成")
1479
1561
  print_rule("快速开始")
1480
1562
  _print_quick_start_commands(project_name, ["agentengine run -i ."])
1481
1563
 
1482
1564
 
1483
- def _write_hermes_project_template(project_path: Path, project_name: str, package_name: str) -> None:
1484
- """生成 Hermes container-first 项目骨架。"""
1485
- repo_template_root = Path(__file__).resolve().parents[2] / "deploy" / "hermes"
1486
- if repo_template_root.exists():
1487
- replacements = {
1488
- "{{PROJECT_NAME}}": project_name,
1489
- "{{PACKAGE_NAME}}": package_name,
1490
- }
1491
- for source in repo_template_root.rglob("*"):
1492
- relative = source.relative_to(repo_template_root)
1493
- if source.is_dir():
1494
- (project_path / relative).mkdir(parents=True, exist_ok=True)
1495
- continue
1496
- destination_relative = relative
1497
- if source.name.endswith(".template"):
1498
- destination_relative = relative.with_name(source.name[:-9])
1499
- content = source.read_text(encoding="utf-8")
1500
- for key, value in replacements.items():
1501
- content = content.replace(key, value)
1502
- destination = project_path / destination_relative
1503
- destination.parent.mkdir(parents=True, exist_ok=True)
1504
- destination.write_text(content, encoding="utf-8-sig")
1505
- continue
1506
- destination = project_path / destination_relative
1507
- destination.parent.mkdir(parents=True, exist_ok=True)
1508
- shutil.copy2(source, destination)
1509
- entrypoint = project_path / "entrypoint.sh"
1510
- if entrypoint.exists():
1511
- entrypoint.chmod(0o755)
1512
- return
1513
-
1514
- runtime_dir = project_path / "runtime"
1515
- runtime_dir.mkdir(parents=True, exist_ok=True)
1516
-
1517
- (project_path / "agentengine.yaml").write_text(f"""# AgentEngine Hermes 项目配置
1565
+ def _write_hermes_project_config(project_path: Path, package_name: str) -> None:
1566
+ """Write the deployable Hermes config; the runtime image is platform-owned."""
1567
+ (project_path / "agentengine.yaml").write_text(
1568
+ f"""# AgentEngine Hermes project configuration
1518
1569
  name: {package_name}
1519
1570
  version: "1.0.0"
1520
1571
 
@@ -1532,353 +1583,90 @@ deploy:
1532
1583
  min_replicas: 1
1533
1584
  max_replicas: 1
1534
1585
  concurrency: 1000
1535
- """, encoding="utf-8-sig")
1536
-
1537
- (project_path / "Dockerfile").write_text("""FROM python:3.12-slim
1538
-
1539
- ENV PYTHONUNBUFFERED=1 \\
1540
- PYTHONDONTWRITEBYTECODE=1 \\
1541
- PIP_NO_CACHE_DIR=1 \\
1542
- PORT=8080 \\
1543
- API_SERVER_HOST=127.0.0.1 \\
1544
- API_SERVER_PORT=8642 \\
1545
- HERMES_DASHBOARD_HOST=127.0.0.1 \\
1546
- HERMES_DASHBOARD_PORT=9119
1547
-
1548
- WORKDIR /app
1549
-
1550
- RUN apt-get update \\
1551
- && apt-get install -y --no-install-recommends bash curl tini \\
1552
- && rm -rf /var/lib/apt/lists/*
1553
-
1554
- RUN pip install --no-cache-dir \\
1555
- "fastapi>=0.100" \\
1556
- "uvicorn[standard]>=0.23" \\
1557
- "httpx>=0.24" \\
1558
- "pyyaml>=6.0" \\
1559
- "python-dotenv>=1.0" \\
1560
- "hermes-agent==0.9.0"
1561
-
1562
- COPY runtime ./runtime
1563
- COPY entrypoint.sh ./entrypoint.sh
1564
- RUN chmod +x /app/entrypoint.sh
1565
-
1566
- EXPOSE 8080
1567
-
1568
- ENTRYPOINT ["tini", "--"]
1569
- CMD ["/app/entrypoint.sh"]
1570
- """, encoding="utf-8")
1571
-
1572
- (project_path / "entrypoint.sh").write_text("""#!/usr/bin/env bash
1573
- set -euo pipefail
1574
-
1575
- export HOME="${HOME:-/home/agent}"
1576
- export PORT="${PORT:-8080}"
1577
- export API_SERVER_HOST="${API_SERVER_HOST:-127.0.0.1}"
1578
- export API_SERVER_PORT="${API_SERVER_PORT:-8642}"
1579
- export HERMES_DASHBOARD_HOST="${HERMES_DASHBOARD_HOST:-127.0.0.1}"
1580
- export HERMES_DASHBOARD_PORT="${HERMES_DASHBOARD_PORT:-9119}"
1581
- export API_SERVER_ENABLED="${API_SERVER_ENABLED:-true}"
1582
-
1583
- mkdir -p "${HOME}/.hermes"
1584
-
1585
- cat > "${HOME}/.hermes/.env" <<EOF
1586
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
1587
- OPENAI_BASE_URL=${OPENAI_BASE_URL:-}
1588
- OPENAI_MODEL_NAME=${OPENAI_MODEL_NAME:-}
1589
- API_SERVER_ENABLED=${API_SERVER_ENABLED}
1590
- API_SERVER_KEY=${API_SERVER_KEY:-}
1591
- API_SERVER_HOST=${API_SERVER_HOST}
1592
- API_SERVER_PORT=${API_SERVER_PORT}
1593
- EOF
1594
-
1595
- cat > "${HOME}/.hermes/config.yaml" <<EOF
1596
- model:
1597
- provider: custom
1598
- default: "${OPENAI_MODEL_NAME:-}"
1599
- base_url: "${OPENAI_BASE_URL:-}"
1600
- api_server:
1601
- enabled: true
1602
- host: "${API_SERVER_HOST}"
1603
- port: ${API_SERVER_PORT}
1604
- EOF
1605
-
1606
- hermes gateway run --replace &
1607
- HERMES_API_PID=$!
1608
-
1609
- hermes dashboard --host "${HERMES_DASHBOARD_HOST}" --port "${HERMES_DASHBOARD_PORT}" --no-open &
1610
- HERMES_DASHBOARD_PID=$!
1611
-
1612
- cleanup() {
1613
- kill "${HERMES_API_PID}" "${HERMES_DASHBOARD_PID}" 2>/dev/null || true
1614
- }
1615
- trap cleanup EXIT
1616
-
1617
- exec uvicorn runtime.app:app --host 0.0.0.0 --port "${PORT}"
1618
- """, encoding="utf-8")
1619
-
1620
- (runtime_dir / "__init__.py").write_text("", encoding="utf-8")
1621
- (runtime_dir / "app.py").write_text("""from __future__ import annotations
1622
-
1623
- import asyncio
1624
- import contextlib
1625
- import json
1626
- import os
1627
- import pty
1628
- import select
1629
- import signal
1630
- import termios
1631
- from typing import Iterable
1632
-
1633
- import httpx
1634
- from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
1635
- from fastapi.responses import Response
1636
- from starlette.websockets import WebSocketState
1637
-
1638
-
1639
- TERMINAL_SUBPROTOCOL = "ks-terminal.v1"
1640
- SHELL_METACHARS = set("|&;<>()$`\\\\\\n\\r")
1641
- SINGLE_READONLY = {"status", "doctor", "version", "insights"}
1642
- NESTED_READONLY = {
1643
- "sessions": {"list": (2, 2), "show": (3, 3), "export": (3, 3)},
1644
- "config": {"show": (2, 2), "check": (2, 2), "path": (2, 2), "env-path": (2, 2)},
1645
- "skills": {"list": (2, 2), "audit": (2, 2), "check": (2, 2)},
1646
- "tools": {"list": (2, 2)},
1647
- "cron": {"list": (2, 2), "status": (2, 2)},
1648
- "gateway": {"status": (2, 2)},
1649
- }
1650
-
1651
- app = FastAPI()
1652
-
1653
-
1654
- def _api_base() -> str:
1655
- return f"http://{os.getenv('API_SERVER_HOST', '127.0.0.1')}:{os.getenv('API_SERVER_PORT', '8642')}"
1656
-
1657
-
1658
- def _dashboard_base() -> str:
1659
- return f"http://{os.getenv('HERMES_DASHBOARD_HOST', '127.0.0.1')}:{os.getenv('HERMES_DASHBOARD_PORT', '9119')}"
1660
-
1661
-
1662
- def _validate_exec_argv(argv: Iterable[str]) -> list[str]:
1663
- normalized = [str(item).strip() for item in argv]
1664
- if not normalized:
1665
- raise ValueError("missing argv")
1666
- for item in normalized:
1667
- if not item or item.startswith("-") or any(char in SHELL_METACHARS for char in item):
1668
- raise ValueError(f"unsafe argv: {item}")
1669
- if normalized[0] in SINGLE_READONLY:
1670
- if len(normalized) != 1:
1671
- raise ValueError("unsupported argv")
1672
- return normalized
1673
- nested = NESTED_READONLY.get(normalized[0])
1674
- if not nested or len(normalized) < 2:
1675
- raise ValueError("unsupported argv")
1676
- bounds = nested.get(normalized[1])
1677
- if not bounds:
1678
- raise ValueError("unsupported argv")
1679
- if not (bounds[0] <= len(normalized) <= bounds[1]):
1680
- raise ValueError("unsupported argv")
1681
- return normalized
1682
-
1683
-
1684
- async def _proxy_http(request: Request, base_url: str, path: str) -> Response:
1685
- target = f"{base_url}/{path.lstrip('/')}"
1686
- if request.url.query:
1687
- target = f"{target}?{request.url.query}"
1688
- headers = {
1689
- key: value
1690
- for key, value in request.headers.items()
1691
- if key.lower() not in {"host", "content-length"}
1692
- }
1693
- async with httpx.AsyncClient(timeout=None) as client:
1694
- upstream = await client.request(
1695
- request.method,
1696
- target,
1697
- headers=headers,
1698
- content=await request.body(),
1699
- )
1700
- response_headers = {
1701
- key: value
1702
- for key, value in upstream.headers.items()
1703
- if key.lower() not in {"content-encoding", "transfer-encoding", "connection"}
1704
- }
1705
- return Response(upstream.content, status_code=upstream.status_code, headers=response_headers)
1706
-
1707
-
1708
- @app.get("/health")
1709
- async def health() -> dict:
1710
- return {"ok": True}
1711
-
1712
-
1713
- @app.api_route("/v1/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
1714
- async def proxy_api(path: str, request: Request) -> Response:
1715
- return await _proxy_http(request, _api_base(), f"v1/{path}")
1716
-
1717
-
1718
- def _set_winsize(fd: int, rows: int, cols: int) -> None:
1719
- termios.tcsetwinsize(fd, (int(rows or 24), int(cols or 80)))
1720
-
1721
-
1722
- async def _pty_reader(ws: WebSocket, fd: int) -> None:
1723
- loop = asyncio.get_running_loop()
1724
- while True:
1725
- await loop.run_in_executor(None, lambda: select.select([fd], [], [], None))
1726
- data = os.read(fd, 4096)
1727
- if not data:
1728
- return
1729
- await ws.send_bytes(data)
1730
-
1586
+ """,
1587
+ encoding="utf-8-sig",
1588
+ )
1731
1589
 
1732
- async def _wait_process(pid: int) -> int:
1733
- loop = asyncio.get_running_loop()
1734
- _, status = await loop.run_in_executor(None, os.waitpid, pid, 0)
1735
- if os.WIFEXITED(status):
1736
- return os.WEXITSTATUS(status)
1737
- if os.WIFSIGNALED(status):
1738
- return 128 + os.WTERMSIG(status)
1739
- return status
1740
1590
 
1591
+ def _write_codex_project_config(project_path: Path, package_name: str) -> None:
1592
+ """Write the minimal codex runtime project:yaml 驱动,无 package/agent.py。
1741
1593
 
1742
- @app.websocket("/_ksadk/terminal/ws")
1743
- async def terminal_ws(ws: WebSocket) -> None:
1744
- if TERMINAL_SUBPROTOCOL not in (ws.headers.get("sec-websocket-protocol") or ""):
1745
- await ws.close(code=4400, reason="missing ks-terminal.v1 subprotocol")
1746
- return
1747
- await ws.accept(subprotocol=TERMINAL_SUBPROTOCOL)
1748
- pid = None
1749
- fd = None
1750
- receive_task = None
1751
- reader_task = None
1752
- wait_task = None
1753
- try:
1754
- first = await ws.receive_text()
1755
- payload = json.loads(first)
1756
- if payload.get("type") != "start":
1757
- raise ValueError("first frame must be start")
1758
- mode = payload.get("mode")
1759
- argv = payload.get("argv") or []
1760
- if mode == "tui":
1761
- command = ["hermes", "chat"]
1762
- elif mode == "exec":
1763
- command = ["hermes", *_validate_exec_argv(argv)]
1764
- else:
1765
- raise ValueError("unsupported mode")
1766
-
1767
- pid, fd = pty.fork()
1768
- if pid == 0:
1769
- os.execvp(command[0], command)
1770
-
1771
- _set_winsize(fd, int(payload.get("rows") or 24), int(payload.get("cols") or 80))
1772
- await ws.send_text(json.dumps({"type": "ready"}))
1773
- reader_task = asyncio.create_task(_pty_reader(ws, fd))
1774
- wait_task = asyncio.create_task(_wait_process(pid))
1775
- receive_task = asyncio.create_task(ws.receive())
1776
-
1777
- while True:
1778
- done, _pending = await asyncio.wait({wait_task, receive_task}, return_when=asyncio.FIRST_COMPLETED)
1779
- if wait_task in done:
1780
- if reader_task:
1781
- reader_task.cancel()
1782
- if receive_task:
1783
- receive_task.cancel()
1784
- code = wait_task.result()
1785
- if ws.client_state == WebSocketState.CONNECTED:
1786
- await ws.send_text(json.dumps({"type": "exit", "code": code}))
1787
- return
1594
+ codex 的 agent 逻辑由 prompt 承载(CodexRunner.load_agent 是 no-op),所以只生成
1595
+ canonical agentengine.yaml(framework: codex,model+prompt)+ requirements +
1596
+ README。model/prompt 经 CodexRunner 传入 codex thread(开发者指令)。
1597
+ """
1598
+ (project_path / "agentengine.yaml").write_text(
1599
+ f"""# AgentEngine codex runtime 项目配置
1600
+ name: {package_name}
1601
+ version: "1.0.0"
1788
1602
 
1789
- message = receive_task.result()
1790
- receive_task = asyncio.create_task(ws.receive())
1791
- if message.get("bytes") is not None:
1792
- os.write(fd, message["bytes"])
1793
- continue
1794
- text = message.get("text")
1795
- if not text:
1796
- continue
1797
- control = json.loads(text)
1798
- if control.get("type") == "resize":
1799
- _set_winsize(fd, int(control.get("rows") or 24), int(control.get("cols") or 80))
1800
- elif control.get("type") == "signal":
1801
- sig = signal.SIGINT if control.get("signal") == "SIGINT" else signal.SIGTERM
1802
- os.kill(pid, sig)
1803
- elif control.get("type") == "stdin_eof":
1804
- os.close(fd)
1805
- fd = None
1806
- except WebSocketDisconnect:
1807
- return
1808
- except Exception as exc:
1809
- if ws.client_state == WebSocketState.CONNECTED:
1810
- await ws.send_text(json.dumps({"type": "error", "message": str(exc)}))
1811
- await ws.close()
1812
- finally:
1813
- for task in (receive_task, reader_task):
1814
- if task and not task.done():
1815
- task.cancel()
1816
- if pid is not None and (wait_task is None or not wait_task.done()):
1817
- with contextlib.suppress(ProcessLookupError):
1818
- os.kill(pid, signal.SIGTERM)
1819
- if wait_task and not wait_task.done():
1820
- wait_task.cancel()
1821
- if fd is not None:
1822
- with contextlib.suppress(OSError):
1823
- os.close(fd)
1824
-
1825
-
1826
- @app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
1827
- async def proxy_dashboard(path: str, request: Request) -> Response:
1828
- return await _proxy_http(request, _dashboard_base(), path)
1829
- """, encoding="utf-8")
1830
-
1831
- (project_path / "README.md").write_text(f"""# {project_name}
1832
-
1833
- Hermes AgentEngine container-first 项目。
1603
+ # 框架:codex(开发态 CodexRunner → 部署态 codex-runtime)
1604
+ framework: codex
1605
+ artifact_type: ManagedRuntime
1606
+
1607
+ # 托管运行时版本可选;省略时云端由 Runtime catalog 选择默认值。
1608
+ # 本地 `ksadk web` 会使用已安装的 openai-codex 并提示该版本未锁定。
1609
+ runtime:
1610
+ name: codex
1611
+
1612
+ # codex 的模型与开发者指令(CodexRunner 读取并传入 codex thread)
1613
+ model: glm-5.2
1614
+ prompt: |
1615
+ 你是 codex 编码助手。简洁回答,能跑命令验证就跑(shell 工具),中文回复。
1616
+ """,
1617
+ encoding="utf-8-sig",
1618
+ )
1619
+ (project_path / "requirements.txt").write_text(
1620
+ "ksadk[codex]\n",
1621
+ encoding="utf-8", # 无 BOM:pip 解析 BOM 会报 Invalid requirement
1622
+ )
1623
+ (project_path / "README.md").write_text(
1624
+ f"""# {package_name} — codex runtime agent
1834
1625
 
1835
1626
  ## 快速开始
1836
1627
 
1837
1628
  ```bash
1838
- cd {project_name}
1839
-
1840
- # 1. 编辑 .env,填写模型配置
1841
- vim .env
1842
-
1843
- # 2. 部署平台预置 Hermes runtime 镜像
1844
- agentengine hermes deploy
1845
-
1846
- # 3. 打开 Hermes 管理 UI
1847
- agentengine hermes open
1848
-
1849
- # 4. 进入 pod 内 Hermes 原生 TUI
1850
- agentengine invoke
1851
-
1852
- # 5. 使用统一 hosted chat
1853
- agentengine hermes open --chat
1854
-
1855
- # 6. 受限只读运维子命令
1856
- agentengine hermes exec <agent> -- status
1857
-
1858
- # 7. Pairing 审批透传
1859
- agentengine hermes pairing <agent> -- list
1629
+ # 1. 编辑 .env 填 OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL_NAME
1630
+ # 2. 本地运行(浏览器对话,codex 自动探测并启用代理)
1631
+ agentengine web .
1632
+ # 或
1633
+ ksadk web .
1860
1634
  ```
1861
1635
 
1862
- ## Runtime Contract
1636
+ ## 说明
1863
1637
 
1864
- - `/` 反代到 Hermes dashboard 管理 UI
1865
- - `/chat` 由 AgentEngine hosted UI/router 处理
1866
- - `/v1/*` 反代到 Hermes OpenAI-compatible API server
1867
- - `/_ksadk/terminal/ws` 提供 `ks-terminal.v1` 原生 TUI/exec/pairing websocket
1868
- """, encoding="utf-8-sig")
1638
+ - codex 的 agent 逻辑由 `agentengine.yaml` 的 `prompt`(开发者指令)承载,无 agent.py。
1639
+ - codex 自动探测模型协议:OpenAI 官方直连,星流 chat 模型自动启用转换代理。
1640
+ - `agentengine build .` 只生成 YAML manifest bundle,不打包本机 Codex 二进制。若
1641
+ `runtime.version` 未指定,build 需要连接到已配置 Runtime catalog;离线构建请显式锁定版本。
1642
+ - 部署时服务端解析 ManagedRuntime 版本和 Linux 镜像 digest。
1643
+ """,
1644
+ encoding="utf-8-sig",
1645
+ )
1869
1646
 
1870
1647
 
1871
- @click.command(context_settings=dict(help_option_names=['-h', '--help']))
1872
- @click.argument('project_name', required=False)
1873
- @click.option('--framework', '-f', type=click.Choice(['adk', 'langchain', 'langgraph', 'deepagents', 'openclaw', 'hermes']),
1874
- default='langgraph', help='框架类型 (default: langgraph)')
1875
- @click.option('--from-agent', 'from_agent_path', type=click.Path(exists=True),
1876
- help='包装现有 Agent 文件或目录')
1648
+ @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
1649
+ @click.argument("project_name", required=False)
1650
+ @click.option(
1651
+ "--framework",
1652
+ "-f",
1653
+ type=click.Choice(
1654
+ ["adk", "langchain", "langgraph", "deepagents", "openclaw", "hermes", "codex"]
1655
+ ),
1656
+ default="langgraph",
1657
+ help="框架类型 (default: langgraph)",
1658
+ )
1659
+ @click.option(
1660
+ "--from-agent",
1661
+ "from_agent_path",
1662
+ type=click.Path(exists=True),
1663
+ help="包装现有 Agent 文件或目录",
1664
+ )
1877
1665
  def create(project_name: str, framework: str, from_agent_path: str):
1878
1666
  """创建新的 Agent 项目
1879
-
1667
+
1880
1668
  PROJECT_NAME: 项目名称
1881
-
1669
+
1882
1670
  使用 --from-agent 可包装现有代码:
1883
1671
  agentengine init --from-agent ./my_agent.py # 单文件
1884
1672
  agentengine init --from-agent ./my_agent/ # 目录
@@ -1886,158 +1674,151 @@ def create(project_name: str, framework: str, from_agent_path: str):
1886
1674
  # === 包装模式 ===
1887
1675
  if from_agent_path:
1888
1676
  from_path = Path(from_agent_path)
1889
-
1677
+
1890
1678
  # === 目录模式 ===
1891
1679
  if from_path.is_dir():
1892
1680
  print_info(f"扫描目录: {from_path}")
1893
-
1681
+
1894
1682
  # 查找入口文件
1895
1683
  entry_result = _find_entry_file(from_path)
1896
1684
  if not entry_result:
1897
- print_error("未找到有效的入口文件 (agent.py, main.py, __init__.py 或包含 Agent 定义的文件)")
1685
+ print_error(
1686
+ "未找到有效的入口文件 (agent.py, main.py, __init__.py 或包含 Agent 定义的文件)"
1687
+ )
1898
1688
  raise SystemExit(1)
1899
-
1689
+
1900
1690
  entry_file, detected_var = entry_result
1901
1691
  print_info(f"检测到入口: {entry_file.name}")
1902
1692
  print_info(f"检测到变量: {detected_var}")
1903
-
1693
+
1904
1694
  # 检测框架:优先读取已有 agentengine.yaml
1905
- detected_framework = _load_framework_from_agentengine_yaml(from_path) or 'unknown'
1906
- if detected_framework != 'unknown':
1695
+ detected_framework = _load_framework_from_agentengine_yaml(from_path) or "unknown"
1696
+ if detected_framework != "unknown":
1907
1697
  print_info(f"从 agentengine.yaml 检测到框架: {detected_framework}")
1908
1698
  else:
1909
- entry_content = entry_file.read_text(encoding='utf-8')
1699
+ entry_content = entry_file.read_text(encoding="utf-8")
1910
1700
  detected_framework = _detect_framework(entry_content)
1911
-
1701
+
1912
1702
  # 如果入口文件未检测到框架,扫描整个目录
1913
- if detected_framework == 'unknown':
1703
+ if detected_framework == "unknown":
1914
1704
  for py_file in _iter_python_files(from_path):
1915
1705
  try:
1916
- content = py_file.read_text(encoding='utf-8')
1706
+ content = py_file.read_text(encoding="utf-8")
1917
1707
  except Exception:
1918
1708
  continue
1919
1709
  detected_framework = _detect_framework(content)
1920
- if detected_framework != 'unknown':
1710
+ if detected_framework != "unknown":
1921
1711
  break
1922
-
1923
- if detected_framework == 'unknown':
1712
+
1713
+ if detected_framework == "unknown":
1924
1714
  print_warn("无法自动检测框架,将使用默认 langgraph")
1925
- detected_framework = 'langgraph'
1715
+ detected_framework = "langgraph"
1926
1716
  else:
1927
1717
  print_info(f"检测到框架: {detected_framework}")
1928
-
1718
+
1929
1719
  # 如果没有指定项目名,使用目录名
1930
1720
  if not project_name:
1931
- project_name = from_path.name.replace('_', '-')
1932
-
1933
- _wrap_agent_directory(from_path, project_name, detected_framework, entry_file, detected_var)
1721
+ project_name = from_path.name.replace("_", "-")
1722
+
1723
+ _wrap_agent_directory(
1724
+ from_path, project_name, detected_framework, entry_file, detected_var
1725
+ )
1934
1726
  return
1935
-
1727
+
1936
1728
  # === 单文件模式 ===
1937
1729
  else:
1938
- content = from_path.read_text(encoding='utf-8')
1939
-
1730
+ content = from_path.read_text(encoding="utf-8")
1731
+
1940
1732
  # 自动检测框架
1941
1733
  detected_framework = _detect_framework(content)
1942
- if detected_framework == 'unknown':
1734
+ if detected_framework == "unknown":
1943
1735
  print_warn("无法自动检测框架,将使用默认 langgraph")
1944
- detected_framework = 'langgraph'
1736
+ detected_framework = "langgraph"
1945
1737
  else:
1946
1738
  print_info(f"检测到框架: {detected_framework}")
1947
-
1739
+
1948
1740
  # 自动检测 Agent 变量
1949
- detected_var = _detect_agent_variable(content)
1950
- if not detected_var:
1741
+ detected_agent_var = _detect_agent_variable(content)
1742
+ if not detected_agent_var:
1951
1743
  print_warn("无法自动检测 Agent 变量,将使用默认 root_agent")
1952
- detected_var = 'root_agent'
1744
+ detected_agent_var = "root_agent"
1953
1745
  else:
1954
- print_info(f"检测到变量: {detected_var}")
1955
-
1746
+ print_info(f"检测到变量: {detected_agent_var}")
1747
+
1956
1748
  # 如果没有指定项目名,使用文件名
1957
1749
  if not project_name:
1958
- project_name = from_path.stem.replace('_', '-')
1959
-
1960
- _wrap_agent_file(from_path, project_name, detected_framework, detected_var)
1750
+ project_name = from_path.stem.replace("_", "-")
1751
+
1752
+ _wrap_agent_file(from_path, project_name, detected_framework, detected_agent_var)
1961
1753
  return
1962
-
1754
+
1963
1755
  # === 正常模板模式 ===
1964
1756
  # 如果没有提供项目名称,进入交互模式
1965
1757
  if not project_name:
1966
1758
  print_title("初始化新项目")
1967
-
1968
- project_name = questionary.text(
1969
- "请输入项目名称:",
1970
- style=custom_style
1971
- ).ask()
1972
-
1759
+
1760
+ project_name = questionary.text("请输入项目名称:", style=custom_style).ask()
1761
+
1973
1762
  if not project_name:
1974
1763
  print_error("取消创建")
1975
1764
  raise SystemExit(0)
1976
-
1765
+
1977
1766
  framework = questionary.select(
1978
1767
  "请选择开发框架:",
1979
- choices=['langgraph', 'langchain', 'deepagents', 'adk', 'openclaw', 'hermes'],
1980
- default='langgraph',
1981
- style=custom_style
1768
+ choices=["langgraph", "langchain", "deepagents", "adk", "openclaw", "hermes", "codex"],
1769
+ default="langgraph",
1770
+ style=custom_style,
1982
1771
  ).ask()
1983
-
1772
+
1984
1773
  if not framework:
1985
1774
  print_error("取消创建")
1986
1775
  raise SystemExit(0)
1987
-
1776
+
1988
1777
  project_path = Path(project_name)
1989
-
1778
+
1990
1779
  if project_path.exists():
1991
1780
  print_error(f"目录 '{project_name}' 已存在")
1992
1781
  raise SystemExit(1)
1993
-
1782
+
1994
1783
  print_kv("创建项目", project_name)
1995
1784
  print_kv("框架", framework)
1996
-
1785
+
1997
1786
  package_name = _runtime_package_name_or_exit(project_path.name)
1998
1787
  project_path.mkdir(parents=True)
1999
- if framework not in {"openclaw", "hermes"}:
1788
+ if framework not in {"openclaw", "hermes", "codex"}:
2000
1789
  (project_path / package_name).mkdir(parents=True)
2001
-
1790
+
2002
1791
  # 检测全局配置
2003
1792
  from ksadk.configs.global_config import (
2004
- global_config_exists,
2005
1793
  get_env_from_global_config,
1794
+ global_config_exists,
2006
1795
  )
2007
-
1796
+
2008
1797
  global_env = {}
2009
1798
  if global_config_exists():
2010
1799
  global_env = get_env_from_global_config()
2011
1800
  if global_env:
2012
- print_info("检测到全局配置,已自动填充凭证")
2013
-
1801
+ print_info("检测到全局配置;项目 .env 仅保留非敏感默认值,凭证不会被复制")
1802
+
2014
1803
  # .env - 生成配置文件
2015
- # 如果有全局配置,使用全局配置的值;否则使用占位符
2016
- # 如果有全局配置,使用全局配置的值;否则使用空字符串
2017
- api_key = global_env.get("OPENAI_API_KEY", "")
1804
+ # 仅继承非敏感的 endpoint/model/region 默认值;绝不复制凭证。
2018
1805
  base_url = global_env.get("OPENAI_BASE_URL", "")
2019
1806
  model_name = global_env.get("OPENAI_MODEL_NAME", "")
2020
-
2021
- ks_ak = global_env.get("KSYUN_ACCESS_KEY", "")
2022
- ks_sk = global_env.get("KSYUN_SECRET_KEY", "")
1807
+
2023
1808
  ks_region = global_env.get("KSYUN_REGION", "cn-beijing-6")
2024
- ks_account = global_env.get("KSYUN_ACCOUNT_ID", "")
2025
-
1809
+
2026
1810
  # 构建 .env 内容
2027
1811
  if framework == "openclaw":
2028
1812
  env_content = f"""# ======================
2029
1813
  # OpenClaw 标准部署最小配置
2030
1814
  # ======================
2031
- KSYUN_ACCESS_KEY={ks_ak}
2032
- KSYUN_SECRET_KEY={ks_sk}
1815
+ # KSYUN_ACCESS_KEY=
1816
+ # KSYUN_SECRET_KEY=
2033
1817
  KSYUN_REGION={ks_region}
2034
1818
  """
2035
- if ks_account:
2036
- env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2037
- else:
2038
- env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
1819
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2039
1820
 
2040
- env_content += f"\nOPENAI_API_KEY={api_key}\n"
1821
+ env_content += "\n# OPENAI_API_KEY=\n"
2041
1822
  if base_url:
2042
1823
  env_content += f"OPENAI_BASE_URL={base_url}\n"
2043
1824
  else:
@@ -2051,18 +1832,13 @@ KSYUN_REGION={ks_region}
2051
1832
  env_content = f"""# ======================
2052
1833
  # Hermes 标准部署最小配置
2053
1834
  # ======================
2054
- KSYUN_ACCESS_KEY={ks_ak}
2055
- KSYUN_SECRET_KEY={ks_sk}
1835
+ # KSYUN_ACCESS_KEY=
1836
+ # KSYUN_SECRET_KEY=
2056
1837
  KSYUN_REGION={ks_region}
2057
1838
  """
2058
- if ks_account:
2059
- env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2060
- else:
2061
- env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
1839
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2062
1840
 
2063
- env_content += f"""
2064
- OPENAI_API_KEY={api_key}
2065
- """
1841
+ env_content += "\n# OPENAI_API_KEY=\n"
2066
1842
  if base_url:
2067
1843
  env_content += f"OPENAI_BASE_URL={base_url}\n"
2068
1844
  else:
@@ -2083,7 +1859,6 @@ HERMES_DASHBOARD_PORT=9119
2083
1859
  PORT=8080
2084
1860
  # HERMES_CONTEXT_LENGTH=200000
2085
1861
  # HERMES_FALLBACK_MODEL=deepseek-v4-pro
2086
- # HERMES_IMAGE=hub.kce.ksyun.com/agentengine-public/hermes-agent:2026.5.29.2-ksadk-v1
2087
1862
  """
2088
1863
  env_example_content = """# ======================
2089
1864
  # Hermes 标准部署最小配置示例
@@ -2106,17 +1881,16 @@ HERMES_DASHBOARD_PORT=9119
2106
1881
  PORT=8080
2107
1882
  # HERMES_CONTEXT_LENGTH=200000
2108
1883
  # HERMES_FALLBACK_MODEL=deepseek-v4-pro
2109
- # HERMES_IMAGE=hub.kce.ksyun.com/agentengine-public/hermes-agent:2026.5.29.2-ksadk-v1
2110
1884
  """
1885
+ elif framework == "codex":
1886
+ env_content = _generate_codex_env_content(global_env)
2111
1887
  else:
2112
- langfuse_public = global_env.get("LANGFUSE_PUBLIC_KEY", "")
2113
- langfuse_secret = global_env.get("LANGFUSE_SECRET_KEY", "")
2114
1888
  langfuse_url = global_env.get("LANGFUSE_BASE_URL", "")
2115
1889
 
2116
- env_content = f"""# ======================
1890
+ env_content = """# ======================
2117
1891
  # 模型配置 (必填, 可以从星流平台获取https://ksp.console.ksyun.com/#/apiKey)
2118
1892
  # ======================
2119
- OPENAI_API_KEY={api_key}
1893
+ # OPENAI_API_KEY=
2120
1894
  """
2121
1895
 
2122
1896
  # 可选字段:如果有值则启用,否则注释掉
@@ -2135,15 +1909,8 @@ OPENAI_API_KEY={api_key}
2135
1909
  # 可观测性 (可选)
2136
1910
  # ======================
2137
1911
  """
2138
- if langfuse_public:
2139
- env_content += f"LANGFUSE_PUBLIC_KEY={langfuse_public}\n"
2140
- else:
2141
- env_content += "# LANGFUSE_PUBLIC_KEY=pk-xxx\n"
2142
-
2143
- if langfuse_secret:
2144
- env_content += f"LANGFUSE_SECRET_KEY={langfuse_secret}\n"
2145
- else:
2146
- env_content += "# LANGFUSE_SECRET_KEY=sk-xxx\n"
1912
+ env_content += "# LANGFUSE_PUBLIC_KEY=pk-xxx\n"
1913
+ env_content += "# LANGFUSE_SECRET_KEY=sk-xxx\n"
2147
1914
 
2148
1915
  if langfuse_url:
2149
1916
  env_content += f"LANGFUSE_BASE_URL={langfuse_url}\n"
@@ -2155,23 +1922,13 @@ OPENAI_API_KEY={api_key}
2155
1922
  # 金山云配置 (可选,需要部署时必选)
2156
1923
  # ======================
2157
1924
  """
2158
- if ks_ak:
2159
- env_content += f"KSYUN_ACCESS_KEY={ks_ak}\n"
2160
- else:
2161
- env_content += "# KSYUN_ACCESS_KEY=your-api-key-here\n"
2162
-
2163
- if ks_sk:
2164
- env_content += f"KSYUN_SECRET_KEY={ks_sk}\n"
2165
- else:
2166
- env_content += "# KSYUN_SECRET_KEY=your-api-secret-here\n"
1925
+ env_content += "# KSYUN_ACCESS_KEY=your-api-key-here\n"
1926
+ env_content += "# KSYUN_SECRET_KEY=your-api-secret-here\n"
2167
1927
 
2168
1928
  env_content += f"KSYUN_REGION={ks_region}\n"
2169
1929
 
2170
- if ks_account:
2171
- env_content += f"KSYUN_ACCOUNT_ID={ks_account}\n"
2172
- else:
2173
- env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
2174
-
1930
+ env_content += "# KSYUN_ACCOUNT_ID=your-account-id\n"
1931
+
2175
1932
  # 使用 utf-8-sig 编码 (带 BOM),确保 Windows 程序正确识别为 UTF-8
2176
1933
  (project_path / ".env").write_text(env_content, encoding="utf-8-sig")
2177
1934
  if framework == "hermes":
@@ -2185,16 +1942,35 @@ OPENAI_API_KEY={api_key}
2185
1942
  print_info("部署前如需覆盖模型/网关参数,可先编辑 .env")
2186
1943
  return
2187
1944
  if framework == "hermes":
2188
- _write_hermes_project_template(project_path, project_name, package_name)
1945
+ _write_hermes_project_config(project_path, package_name)
2189
1946
  print_success("项目创建成功")
2190
1947
  print_rule("快速开始")
2191
1948
  print_info("快速开始 (复制并执行):")
2192
1949
  _print_quick_start_commands(project_name, ["agentengine hermes deploy"])
2193
1950
  print_info("部署前如需覆盖模型/运行时参数,可先编辑 .env")
2194
1951
  return
2195
-
1952
+ if framework == "codex":
1953
+ _write_codex_project_config(project_path, package_name)
1954
+ print_success("项目创建成功")
1955
+ print_rule("快速开始")
1956
+ print_info("快速开始 (复制并执行):")
1957
+ _print_quick_start_commands(project_name, ["ksadk web ."])
1958
+ print_info("编辑 .env 填星流 OPENAI_API_KEY/BASE/MODEL_NAME;codex 自动探测并启用代理")
1959
+ # codex 本地环境检测(不阻断):缺 openai-codex SDK 时提醒
1960
+ import importlib.util
1961
+
1962
+ if importlib.util.find_spec("openai_codex") is None:
1963
+ print_warn(
1964
+ escape(
1965
+ "缺少 codex runtime SDK:请先 `pip install 'ksadk[codex]'`"
1966
+ "(内含 codex CLI 二进制)"
1967
+ )
1968
+ )
1969
+ return
1970
+
2196
1971
  # agentengine.yaml - Agent 配置
2197
- (project_path / "agentengine.yaml").write_text(f"""# AgentEngine 项目配置
1972
+ (project_path / "agentengine.yaml").write_text(
1973
+ f"""# AgentEngine 项目配置
2198
1974
  name: {package_name}
2199
1975
  version: "1.0.0"
2200
1976
 
@@ -2209,23 +1985,27 @@ agent_variable: root_agent
2209
1985
  # deploy:
2210
1986
  # timeout: 300
2211
1987
  # memory: 512
2212
- """, encoding="utf-8-sig")
2213
-
1988
+ """,
1989
+ encoding="utf-8-sig",
1990
+ )
1991
+
2214
1992
  # __init__.py
2215
- (project_path / package_name / "__init__.py").write_text(f'''"""
1993
+ (project_path / package_name / "__init__.py").write_text(
1994
+ f'''"""
2216
1995
  {project_name} - KsADK Agent
2217
1996
  """
2218
1997
  from .agent import root_agent
2219
1998
  __all__ = ["root_agent"]
2220
- ''', encoding="utf-8-sig")
2221
-
1999
+ ''',
2000
+ encoding="utf-8-sig",
2001
+ )
2002
+
2222
2003
  # agent.py
2223
2004
  template = TEMPLATES[framework]["agent.py"]
2224
2005
  (project_path / package_name / "agent.py").write_text(
2225
- template.format(package_name=package_name),
2226
- encoding="utf-8-sig"
2006
+ template.format(package_name=package_name), encoding="utf-8-sig"
2227
2007
  )
2228
-
2008
+
2229
2009
  # README.md
2230
2010
  if framework == "openclaw":
2231
2011
  readme = f"""# {project_name}
@@ -2292,12 +2072,12 @@ agentengine deploy . # 部署到云端
2292
2072
  reqs += "deepagents\nlangchain\nlangchain-openai\nlanggraph\npython-dotenv\n"
2293
2073
  elif framework == "adk":
2294
2074
  reqs += "google-adk\npython-dotenv\n"
2295
-
2075
+
2296
2076
  (project_path / "requirements.txt").write_text(reqs, encoding="utf-8")
2297
-
2077
+
2298
2078
  print_success("项目创建成功")
2299
2079
  print_rule("快速开始")
2300
-
2080
+
2301
2081
  print_info("快速开始 (复制并执行):")
2302
2082
  _print_quick_start_commands(project_name, ["agentengine config"])
2303
2083
  print_info("或直接运行 (环境变量中需包含模型 API Key):")