agentengine-sdk-python 0.6.9__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 (384) hide show
  1. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +41 -23
  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 +252 -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 +533 -726
  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 +173 -90
  54. ksadk/cli/cmd_invoke.py +388 -159
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +103 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +488 -228
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +44 -14
  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 +38 -16
  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 +501 -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 +656 -0
  97. ksadk/conversations/model_context.py +22 -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 -5187
  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 +52 -39
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +326 -209
  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 +267 -0
  156. ksadk/memory/adk/short_term_memory.py +33 -27
  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 +1248 -164
  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 -530
  183. ksadk/runners/langgraph_runner.py +490 -95
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +264 -38
  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 -3743
  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-C0meuOdi.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-bj2rZnP5.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-2u1ltDIj.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.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-_N8bJ-EU.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-BMKM66B4.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-D99wf6_X.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-DLMjL01k.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-B67uF2uK.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-S4klkK5W.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.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-DtTmUan-.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.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-BR5s6Pws.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-DYPc5cvh.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.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-DPnuCB8c.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.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-mjB_yYkW.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.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-CwSec0Wa.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-agCVobiF.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-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.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 +86 -40
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +28 -5
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +216 -35
  313. ksadk/sessions/resilience.py +52 -0
  314. ksadk/sessions/resilient.py +388 -0
  315. ksadk/skills/models.py +17 -5
  316. ksadk/skills/package_store.py +5 -2
  317. ksadk/skills/runtime/agent.py +20 -5
  318. ksadk/skills/runtime/artifacts.py +1 -5
  319. ksadk/skills/runtime/backends/e2b.py +10 -4
  320. ksadk/skills/runtime/backends/local.py +15 -3
  321. ksadk/skills/runtime/base.py +1 -2
  322. ksadk/skills/runtime/executor.py +10 -3
  323. ksadk/skills/runtime/factory.py +4 -2
  324. ksadk/skills/runtime/loader.py +1 -2
  325. ksadk/skills/runtime/registry.py +15 -10
  326. ksadk/skills/runtime/request.py +3 -1
  327. ksadk/skills/service_client.py +38 -17
  328. ksadk/skills/tool_defs.py +15 -10
  329. ksadk/terminal_exec_policy.py +3 -4
  330. ksadk/tools/gateway.py +92 -15
  331. ksadk/tools/result_budget.py +12 -4
  332. ksadk/toolsets/__init__.py +136 -36
  333. ksadk/toolsets/platform.py +21 -5
  334. ksadk/toolsets/sandbox.py +82 -16
  335. ksadk/toolsets/skills.py +200 -39
  336. ksadk/toolsets/web.py +123 -26
  337. ksadk/toolsets/workspace.py +264 -114
  338. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  339. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  340. ksadk/tracing/setup.py +132 -53
  341. ksadk/tracing/span_utils.py +5 -2
  342. ksadk/tui/__init__.py +22 -7
  343. ksadk/tui/loop.py +1925 -0
  344. ksadk/tui/stream_render.py +69 -0
  345. ksadk/ui_config.py +5 -3
  346. ksadk/version.py +1 -1
  347. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  348. agentengine_sdk_python-0.6.9.dist-info/RECORD +0 -487
  349. ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +0 -9
  350. ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +0 -8
  351. ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +0 -303
  352. ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +0 -1
  353. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
  354. ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
  355. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
  356. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
  357. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
  358. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
  359. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
  360. ksadk/server/static/assets/index-BH7-pC0B.js +0 -165
  361. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  362. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
  363. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
  364. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
  365. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
  366. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
  367. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
  368. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
  369. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
  370. ksadk/tui/app.py +0 -400
  371. ksadk/tui/clipboard.py +0 -100
  372. ksadk/tui/widgets/__init__.py +0 -22
  373. ksadk/tui/widgets/assistant.py +0 -89
  374. ksadk/tui/widgets/base.py +0 -54
  375. ksadk/tui/widgets/chat_input.py +0 -246
  376. ksadk/tui/widgets/history.py +0 -156
  377. ksadk/tui/widgets/system.py +0 -87
  378. ksadk/tui/widgets/thinking.py +0 -110
  379. ksadk/tui/widgets/tool_call.py +0 -161
  380. ksadk/tui/widgets/user.py +0 -37
  381. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  382. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  383. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  384. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
@@ -4,24 +4,26 @@ LangGraphRunner - LangGraph 框架运行时
4
4
  直接透传 LangGraph 原生能力,最小化封装
5
5
  """
6
6
 
7
+ import base64
8
+ import inspect
7
9
  import os
8
- import uuid
9
10
  import re
11
+ import uuid
10
12
  from typing import Any, AsyncIterator, Dict, Mapping
11
- import base64
12
- from pathlib import Path
13
13
 
14
- from ksadk.runners.base_runner import BaseRunner
15
- from ksadk.sessions.continuity import LangGraphSessionAdapter
16
- from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
17
14
  from langgraph.types import Command
15
+
18
16
  from ksadk.conversations.attachments import classify_attachment_kind, read_attachment_uri_bytes
19
17
  from ksadk.conversations.reasoning_markup import ReasoningMarkupParser, strip_reasoning_markup
18
+ from ksadk.runners.base_runner import BaseRunner
19
+ from ksadk.runners.usage_accumulator import accumulate_usage
20
+ from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
21
+ from ksadk.sessions.continuity import LangGraphSessionAdapter
20
22
 
21
23
 
22
24
  class LangGraphRunner(BaseRunner):
23
25
  """LangGraph 框架运行时
24
-
26
+
25
27
  透传原生 LangGraph 功能,支持任意 State 格式
26
28
  """
27
29
 
@@ -39,7 +41,7 @@ class LangGraphRunner(BaseRunner):
39
41
  self._loaded_model_name = self.normalize_requested_model(
40
42
  os.getenv("OPENAI_MODEL_NAME") or os.getenv("MODEL_NAME")
41
43
  )
42
-
44
+
43
45
  if not hasattr(self._agent, "invoke"):
44
46
  raise TypeError("加载的对象不是有效的 LangGraph CompiledGraph")
45
47
 
@@ -56,10 +58,10 @@ class LangGraphRunner(BaseRunner):
56
58
 
57
59
  def describe_checkpoint_capability(self) -> dict[str, Any]:
58
60
  agent = getattr(self, "_agent", None)
59
- has_checkpointer = bool(
60
- getattr(agent, "checkpointer", None) or getattr(agent, "_checkpointer", None)
61
- )
62
- if not has_checkpointer:
61
+ checkpointer = getattr(agent, "checkpointer", None)
62
+ if checkpointer is None:
63
+ checkpointer = getattr(agent, "_checkpointer", None)
64
+ if checkpointer is None:
63
65
  return {
64
66
  "Supported": False,
65
67
  "Backend": "none",
@@ -69,7 +71,16 @@ class LangGraphRunner(BaseRunner):
69
71
  "Reason": "LangGraph graph has no configured checkpointer",
70
72
  }
71
73
 
72
- backend = str(os.getenv("KSADK_CHECKPOINT_BACKEND") or "").strip().lower()
74
+ checkpointer_type = type(checkpointer)
75
+ type_name = f"{checkpointer_type.__module__}.{checkpointer_type.__name__}".lower()
76
+ if "memory" in type_name or "inmemory" in type_name:
77
+ backend = "memory"
78
+ elif "sqlite" in type_name:
79
+ backend = "sqlite"
80
+ elif "postgres" in type_name:
81
+ backend = "postgres"
82
+ else:
83
+ backend = str(os.getenv("KSADK_CHECKPOINT_BACKEND") or "").strip().lower()
73
84
  if backend == "local":
74
85
  backend = "sqlite"
75
86
  if not backend:
@@ -86,7 +97,9 @@ class LangGraphRunner(BaseRunner):
86
97
  scope = "pod_local"
87
98
  durable = True
88
99
  shared = False
89
- reason = "SQLite checkpoint is durable for local web debugging but is not shared across pods"
100
+ reason = (
101
+ "SQLite checkpoint is durable for local web debugging but is not shared across pods"
102
+ )
90
103
  elif backend in {"memory", "inmemory"}:
91
104
  backend = "memory"
92
105
  scope = "process_local"
@@ -108,25 +121,23 @@ class LangGraphRunner(BaseRunner):
108
121
  capabilities = super().get_runtime_capabilities()
109
122
  capabilities["SessionContinuity"] = {
110
123
  "Supported": True,
111
- "Type": "checkpoint"
112
- if capabilities["Checkpoint"].get("Supported")
113
- else "semantic_replay",
114
- "Level": "runtime"
115
- if capabilities["Checkpoint"].get("Supported")
116
- else "semantic",
124
+ "Type": (
125
+ "checkpoint" if capabilities["Checkpoint"].get("Supported") else "semantic_replay"
126
+ ),
127
+ "Level": "runtime" if capabilities["Checkpoint"].get("Supported") else "semantic",
117
128
  "Reason": "",
118
129
  }
119
130
  return capabilities
120
131
 
121
132
  def _get_config(self, session_id: str) -> dict:
122
133
  """获取运行配置"""
123
- config = {"configurable": {"thread_id": session_id}}
124
-
134
+ config: dict[str, Any] = {"configurable": {"thread_id": session_id}}
135
+
125
136
  langfuse_callbacks = get_langfuse_callbacks()
126
137
  if langfuse_callbacks:
127
138
  config["callbacks"] = langfuse_callbacks
128
139
  config["metadata"] = get_langfuse_metadata(session_id)
129
-
140
+
130
141
  return config
131
142
 
132
143
  @staticmethod
@@ -153,7 +164,9 @@ class LangGraphRunner(BaseRunner):
153
164
 
154
165
  thread_id = str(checkpoint_ref.get("thread_id") or session_id or "").strip()
155
166
  if not thread_id:
156
- raise ValueError("checkpoint_resume requires session_id or framework_ref.langgraph.thread_id")
167
+ raise ValueError(
168
+ "checkpoint_resume requires session_id or framework_ref.langgraph.thread_id"
169
+ )
157
170
 
158
171
  next_config = dict(config)
159
172
  configurable = dict(next_config.get("configurable") or {})
@@ -163,6 +176,18 @@ class LangGraphRunner(BaseRunner):
163
176
  next_config["configurable"] = configurable
164
177
  return next_config
165
178
 
179
+ @staticmethod
180
+ def _checkpoint_resume_input(
181
+ value: Any,
182
+ *,
183
+ payload_provided: bool,
184
+ interrupt_id: str,
185
+ ) -> Command | None:
186
+ if not payload_provided:
187
+ return None
188
+ resume_value = {interrupt_id: value} if interrupt_id else value
189
+ return Command(resume=resume_value)
190
+
166
191
  @staticmethod
167
192
  def _checkpoint_ref_from_state(state: Any) -> dict[str, Any]:
168
193
  state_config = None
@@ -179,17 +204,15 @@ class LangGraphRunner(BaseRunner):
179
204
  checkpoint_id = str(configurable.get("checkpoint_id") or "").strip()
180
205
  if not thread_id or not checkpoint_id:
181
206
  return {}
182
- next_nodes_raw = state.get("next") if isinstance(state, dict) else getattr(state, "next", None)
207
+ next_nodes_raw = (
208
+ state.get("next") if isinstance(state, dict) else getattr(state, "next", None)
209
+ )
183
210
  next_nodes: list[str] = []
184
211
  if isinstance(next_nodes_raw, str):
185
212
  if next_nodes_raw.strip():
186
213
  next_nodes = [next_nodes_raw.strip()]
187
214
  elif isinstance(next_nodes_raw, (list, tuple, set)):
188
- next_nodes = [
189
- str(item).strip()
190
- for item in next_nodes_raw
191
- if str(item or "").strip()
192
- ]
215
+ next_nodes = [str(item).strip() for item in next_nodes_raw if str(item or "").strip()]
193
216
  return {
194
217
  "langgraph": {
195
218
  "thread_id": thread_id,
@@ -259,11 +282,24 @@ class LangGraphRunner(BaseRunner):
259
282
  return self._extract_usage(values)
260
283
  return {}
261
284
 
285
+ @staticmethod
286
+ def _latest_checkpoint_config(config: dict[str, Any]) -> dict[str, Any]:
287
+ """Select the newest state in a resumed thread, not the source checkpoint."""
288
+ latest_config = dict(config)
289
+ configurable = dict(latest_config.get("configurable") or {})
290
+ configurable.pop("checkpoint_id", None)
291
+ latest_config["configurable"] = configurable
292
+ return latest_config
293
+
262
294
  @staticmethod
263
295
  def _ambient_context_text(payload: Dict[str, Any]) -> str:
264
296
  sections: list[str] = []
265
297
  kb_context = payload.get("kb_context") or {}
266
- kb_text = str(kb_context.get("formatted_text") or "").strip() if isinstance(kb_context, dict) else ""
298
+ kb_text = (
299
+ str(kb_context.get("formatted_text") or "").strip()
300
+ if isinstance(kb_context, dict)
301
+ else ""
302
+ )
267
303
  if kb_text:
268
304
  sections.append(f"Knowledge base context:\n{kb_text}")
269
305
 
@@ -315,6 +351,14 @@ class LangGraphRunner(BaseRunner):
315
351
  prepared = prepare_state(dict(normalized_payload), session_context)
316
352
  if not isinstance(prepared, dict):
317
353
  raise TypeError("ksadk_prepare_state(payload, session_context) must return a dict")
354
+ # A custom state hook owns application state, but must not accidentally
355
+ # erase protocol namespaces that framework middleware consumes. In
356
+ # particular CopilotKit reads ``ag-ui`` to decide whether to inject the
357
+ # official A2UI tool, and reads ``copilotkit.actions`` for frontend
358
+ # actions. Keep these namespaces narrow and transport-owned.
359
+ for namespace in ("ag-ui", "copilotkit"):
360
+ if namespace in normalized_payload:
361
+ prepared.setdefault(namespace, normalized_payload[namespace])
318
362
  return prepared
319
363
 
320
364
  def _to_state(self, payload: Dict[str, Any], history: list) -> Dict[str, Any]:
@@ -328,7 +372,7 @@ class LangGraphRunner(BaseRunner):
328
372
  if "input" in normalized_payload and "messages" not in normalized_payload:
329
373
  from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
330
374
 
331
- messages = []
375
+ messages: list[Any] = []
332
376
  if system_text:
333
377
  messages.append(SystemMessage(content=system_text))
334
378
  for msg in history:
@@ -432,11 +476,11 @@ class LangGraphRunner(BaseRunner):
432
476
  )
433
477
  continue
434
478
 
435
- file_uri = attachment.get("file_uri")
436
- if not file_uri:
479
+ attachment_uri = attachment.get("file_uri")
480
+ if not attachment_uri:
437
481
  continue
438
482
 
439
- raw = read_attachment_uri_bytes(file_uri)
483
+ raw = read_attachment_uri_bytes(str(attachment_uri))
440
484
  if not raw:
441
485
  continue
442
486
 
@@ -479,22 +523,72 @@ class LangGraphRunner(BaseRunner):
479
523
  )
480
524
  return self._agent.invoke(payload, **kwargs)
481
525
 
526
+ async def _invoke_from_stream_events(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
527
+ chunks: list[dict[str, Any]] = []
528
+ accumulated_text = ""
529
+ final_chunk: dict[str, Any] | None = None
530
+ metadata: dict[str, Any] = {}
531
+
532
+ async for chunk in self.stream(input_data):
533
+ chunks.append(dict(chunk))
534
+ chunk_type = chunk.get("type")
535
+ if chunk_type == "text":
536
+ accumulated_text += str(chunk.get("delta") or "")
537
+ elif chunk_type == "final":
538
+ final_chunk = dict(chunk)
539
+ elif chunk_type == "checkpoint":
540
+ chunk_metadata = chunk.get("metadata")
541
+ if isinstance(chunk_metadata, Mapping):
542
+ metadata.update(dict(chunk_metadata))
543
+ elif chunk_type == "interrupt":
544
+ return {
545
+ "type": "interrupt",
546
+ "interrupt_info": chunk.get("interrupt_info"),
547
+ "session_id": chunk.get("session_id") or input_data.get("session_id"),
548
+ "output": (
549
+ chunk.get("interrupt_info", {}).get("message", "需要用户确认")
550
+ if isinstance(chunk.get("interrupt_info"), Mapping)
551
+ else "需要用户确认"
552
+ ),
553
+ "raw": {"chunks": chunks},
554
+ }
555
+
556
+ if final_chunk:
557
+ output_text = str(final_chunk.get("output") or accumulated_text)
558
+ final_metadata = final_chunk.get("metadata")
559
+ if isinstance(final_metadata, Mapping):
560
+ metadata = {**dict(final_metadata), **metadata}
561
+ result: dict[str, Any] = {"output": output_text, "raw": {"chunks": chunks}}
562
+ usage = final_chunk.get("usage")
563
+ if isinstance(usage, Mapping) and usage:
564
+ result["usage"] = dict(usage)
565
+ if metadata:
566
+ result["metadata"] = metadata
567
+ return result
568
+
569
+ return {"output": accumulated_text, "raw": {"chunks": chunks}}
570
+
482
571
  async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
483
572
  """调用 LangGraph 图
484
-
573
+
485
574
  支持两种输入格式:
486
575
  1. 简化格式: {"input": "hello"} - 自动转换为 messages
487
576
  2. 原生格式: {"messages": [...]} 或自定义 State - 直接透传
488
577
  """
489
578
  payload = dict(input_data)
579
+ force_graph_invoke = bool(payload.pop("_ksadk_force_graph_invoke", False))
580
+ if not force_graph_invoke and hasattr(self._agent, "astream_events"):
581
+ return await self._invoke_from_stream_events(payload)
582
+
490
583
  session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
491
584
  is_resume = payload.pop("resume", False)
492
585
  is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
586
+ resume_payload_provided = bool(payload.pop("resume_payload_provided", False))
587
+ resume_interrupt_id = str(payload.pop("resume_interrupt_id", "") or "")
588
+ resume_value = payload.get("input")
493
589
  checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
494
590
  history = payload.pop("history", [])
495
591
  native_context = self.build_native_context(payload.get("platform_context"))
496
- normalized_payload = self._strip_platform_context_fields(payload)
497
-
498
592
  config = self._get_config(session_id)
499
593
  if is_checkpoint_resume:
500
594
  config = self._apply_checkpoint_resume_config(
@@ -502,24 +596,29 @@ class LangGraphRunner(BaseRunner):
502
596
  session_id=session_id,
503
597
  checkpoint_ref=checkpoint_ref,
504
598
  )
505
-
599
+
506
600
  # 判断输入格式 / resume
507
601
  if is_checkpoint_resume:
508
- state = None
509
- elif self._has_prepare_state_hook():
510
- state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
602
+ state = resume_value
511
603
  elif is_resume:
512
- if "input" in normalized_payload and len(normalized_payload) == 1:
513
- state = normalized_payload["input"]
514
- else:
515
- state = normalized_payload
604
+ # ``Command(resume=...)`` is delivered to the graph's suspended
605
+ # interrupt. A custom prepare-state hook is for new user input;
606
+ # applying it here can rewrite an approval decision into ordinary
607
+ # graph state and turn an approved HITL action into a rejection.
608
+ state = resume_value
609
+ elif self._has_prepare_state_hook():
610
+ state = self._prepare_state_with_hook(payload, session_id, history)
516
611
  else:
517
612
  state = self._to_state(payload, history)
518
613
 
519
614
  try:
520
615
  if is_checkpoint_resume:
521
616
  result = await self._invoke_graph(
522
- None,
617
+ self._checkpoint_resume_input(
618
+ state,
619
+ payload_provided=resume_payload_provided,
620
+ interrupt_id=resume_interrupt_id,
621
+ ),
523
622
  config=config,
524
623
  context=native_context,
525
624
  )
@@ -547,7 +646,7 @@ class LangGraphRunner(BaseRunner):
547
646
  if metadata:
548
647
  output["metadata"] = {**(output.get("metadata") or {}), **metadata}
549
648
  return output
550
-
649
+
551
650
  except Exception as e:
552
651
  if "Interrupt" in type(e).__name__:
553
652
  interrupt_info = self._get_interrupt_info(self._agent.get_state(config))
@@ -555,7 +654,11 @@ class LangGraphRunner(BaseRunner):
555
654
  "type": "interrupt",
556
655
  "interrupt_info": interrupt_info,
557
656
  "session_id": session_id,
558
- "output": interrupt_info.get("message", "需要用户确认") if isinstance(interrupt_info, dict) else "需要用户确认",
657
+ "output": (
658
+ interrupt_info.get("message", "需要用户确认")
659
+ if isinstance(interrupt_info, dict)
660
+ else "需要用户确认"
661
+ ),
559
662
  }
560
663
  raise
561
664
 
@@ -564,16 +667,21 @@ class LangGraphRunner(BaseRunner):
564
667
  if isinstance(result, dict):
565
668
  # 自定义 output 字段是业务显式出参,优先于内部 messages state。
566
669
  if "output" in result:
567
- return result["output"]
670
+ return str(result["output"])
568
671
  # LangGraph 示例常用 answer 作为最终业务回答字段。
569
672
  if "answer" in result:
570
- return result["answer"]
673
+ return str(result["answer"])
571
674
  # 标准 messages 格式
572
675
  if "messages" in result:
573
676
  messages = result["messages"]
574
677
  if messages:
575
678
  last = messages[-1]
576
- return last.get("content", str(last)) if isinstance(last, dict) else getattr(last, "content", str(last))
679
+ content = (
680
+ last.get("content", str(last))
681
+ if isinstance(last, dict)
682
+ else getattr(last, "content", str(last))
683
+ )
684
+ return str(content)
577
685
  return str(result) if result else ""
578
686
 
579
687
  def _get_interrupt_info(self, state) -> dict:
@@ -583,12 +691,18 @@ class LangGraphRunner(BaseRunner):
583
691
  if hasattr(task, "interrupts") and task.interrupts:
584
692
  for intr in task.interrupts:
585
693
  if hasattr(intr, "value"):
586
- return intr.value
694
+ value = intr.value
695
+ info = dict(value) if isinstance(value, Mapping) else {"value": value}
696
+ interrupt_id = str(getattr(intr, "id", "") or "")
697
+ if interrupt_id:
698
+ info.setdefault("approval_request_id", interrupt_id)
699
+ return info
587
700
  return {}
588
701
 
589
702
  async def _stream_checkpoint_resume_updates(
590
703
  self,
591
704
  *,
705
+ stream_input: Any,
592
706
  config: dict[str, Any],
593
707
  context: dict[str, Any] | None,
594
708
  ) -> AsyncIterator[Dict[str, Any]]:
@@ -605,11 +719,17 @@ class LangGraphRunner(BaseRunner):
605
719
 
606
720
  latest_output: Any = None
607
721
  emitted_update = False
608
- async for update in self._agent.astream(None, **kwargs):
722
+ tool_names: dict[str, str] = {}
723
+ async for update in self._agent.astream(stream_input, **kwargs):
609
724
  emitted_update = True
610
725
  latest_output = update
611
726
  if isinstance(update, Mapping):
612
727
  for node_name, node_output in update.items():
728
+ for tool_event in self._tool_events_from_graph_update(
729
+ node_output,
730
+ tool_names=tool_names,
731
+ ):
732
+ yield tool_event
613
733
  yield {
614
734
  "type": "graph_update",
615
735
  "node": str(node_name),
@@ -618,14 +738,15 @@ class LangGraphRunner(BaseRunner):
618
738
  else:
619
739
  yield {"type": "graph_update", "output": update}
620
740
 
621
- state_usage = await self._latest_state_usage(config)
741
+ latest_config = self._latest_checkpoint_config(config)
742
+ state_usage = await self._latest_state_usage(latest_config)
622
743
  state_output = ""
623
744
  try:
624
745
  state = None
625
746
  if callable(getattr(self._agent, "aget_state", None)):
626
- state = await self._agent.aget_state(config)
747
+ state = await self._agent.aget_state(latest_config)
627
748
  elif callable(getattr(self._agent, "get_state", None)):
628
- state = self._agent.get_state(config)
749
+ state = self._agent.get_state(latest_config)
629
750
  values = getattr(state, "values", None)
630
751
  if values is not None:
631
752
  state_output = self._extract_output(values)
@@ -639,21 +760,93 @@ class LangGraphRunner(BaseRunner):
639
760
  **({"resume_noop": True} if not emitted_update else {}),
640
761
  }
641
762
 
642
- metadata = await self._latest_checkpoint_metadata(config)
763
+ metadata = await self._latest_checkpoint_metadata(latest_config)
643
764
  if metadata:
644
765
  yield {"type": "checkpoint", "metadata": metadata}
645
766
 
767
+ @staticmethod
768
+ def _tool_events_from_graph_update(
769
+ node_output: Any,
770
+ *,
771
+ tool_names: dict[str, str],
772
+ ) -> list[dict[str, Any]]:
773
+ """Preserve LangGraph update-mode tool messages as structured runner events."""
774
+
775
+ if isinstance(node_output, Mapping):
776
+ messages = node_output.get("messages")
777
+ else:
778
+ messages = getattr(node_output, "messages", None)
779
+ if not isinstance(messages, (list, tuple)):
780
+ return []
781
+
782
+ events: list[dict[str, Any]] = []
783
+ for message in messages:
784
+ if isinstance(message, Mapping):
785
+ tool_calls = message.get("tool_calls")
786
+ tool_call_id = message.get("tool_call_id")
787
+ message_name = message.get("name")
788
+ message_content = message.get("content")
789
+ message_status = message.get("status")
790
+ else:
791
+ tool_calls = getattr(message, "tool_calls", None)
792
+ tool_call_id = getattr(message, "tool_call_id", None)
793
+ message_name = getattr(message, "name", None)
794
+ message_content = getattr(message, "content", None)
795
+ message_status = getattr(message, "status", None)
796
+
797
+ if isinstance(tool_calls, (list, tuple)):
798
+ for tool_call in tool_calls:
799
+ if isinstance(tool_call, Mapping):
800
+ call_id = str(tool_call.get("id") or tool_call.get("tool_call_id") or "")
801
+ name = str(tool_call.get("name") or "tool")
802
+ args = tool_call.get("args")
803
+ else:
804
+ call_id = str(
805
+ getattr(tool_call, "id", None)
806
+ or getattr(tool_call, "tool_call_id", None)
807
+ or ""
808
+ )
809
+ name = str(getattr(tool_call, "name", None) or "tool")
810
+ args = getattr(tool_call, "args", None)
811
+ if call_id:
812
+ tool_names[call_id] = name
813
+ events.append(
814
+ {
815
+ "type": "tool_call",
816
+ "tool_call_id": call_id or name,
817
+ "tool_name": name,
818
+ "tool_args": args,
819
+ }
820
+ )
821
+
822
+ if tool_call_id:
823
+ call_id = str(tool_call_id)
824
+ name = str(message_name or tool_names.get(call_id) or "tool")
825
+ status = str(message_status or "").lower()
826
+ events.append(
827
+ {
828
+ "type": "tool_result",
829
+ "tool_call_id": call_id,
830
+ "tool_name": name,
831
+ "tool_output": message_content,
832
+ "error": message_content if status in {"error", "failed"} else None,
833
+ }
834
+ )
835
+ return events
836
+
646
837
  async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
647
838
  """流式调用 LangGraph 图"""
648
839
  payload = dict(input_data)
840
+ payload.pop("_ksadk_force_graph_invoke", None)
649
841
  session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
650
842
  history = payload.pop("history", [])
651
843
  is_resume = payload.pop("resume", False)
652
844
  is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
845
+ resume_payload_provided = bool(payload.pop("resume_payload_provided", False))
846
+ resume_interrupt_id = str(payload.pop("resume_interrupt_id", "") or "")
847
+ resume_value = payload.get("input")
653
848
  checkpoint_ref = self._extract_langgraph_checkpoint_ref(payload)
654
849
  native_context = self.build_native_context(payload.get("platform_context"))
655
- normalized_payload = self._strip_platform_context_fields(payload)
656
-
657
850
  invoke_payload = dict(payload)
658
851
  invoke_payload["session_id"] = session_id
659
852
  if history:
@@ -662,7 +855,9 @@ class LangGraphRunner(BaseRunner):
662
855
  invoke_payload["resume"] = True
663
856
  if is_checkpoint_resume:
664
857
  invoke_payload["checkpoint_resume"] = True
665
-
858
+ invoke_payload["resume_payload_provided"] = resume_payload_provided
859
+ invoke_payload["resume_interrupt_id"] = resume_interrupt_id
860
+
666
861
  config = self._get_config(session_id)
667
862
  if is_checkpoint_resume:
668
863
  config = self._apply_checkpoint_resume_config(
@@ -672,14 +867,13 @@ class LangGraphRunner(BaseRunner):
672
867
  )
673
868
 
674
869
  if is_checkpoint_resume:
675
- state = None
676
- elif self._has_prepare_state_hook():
677
- state = self._prepare_state_with_hook(payload, session_id, history, is_resume=is_resume)
870
+ state = resume_value
678
871
  elif is_resume:
679
- if "input" in normalized_payload and len(normalized_payload) == 1:
680
- state = normalized_payload["input"]
681
- else:
682
- state = normalized_payload
872
+ # Keep the interrupt value intact for ``Command(resume=...)``;
873
+ # prepare-state hooks only shape fresh user turns.
874
+ state = resume_value
875
+ elif self._has_prepare_state_hook():
876
+ state = self._prepare_state_with_hook(payload, session_id, history)
683
877
  else:
684
878
  state = self._to_state(payload, history)
685
879
 
@@ -689,10 +883,60 @@ class LangGraphRunner(BaseRunner):
689
883
  emitted_non_text_event = False
690
884
  final_output_text = ""
691
885
  final_output_usage: dict[str, Any] = {}
886
+ final_output_last_usage: dict[str, Any] = {}
887
+ model_run_usages: dict[str, dict[str, Any]] = {}
888
+ model_run_order: list[str] = []
889
+ stream_usage_run_keys: set[str] = set()
890
+ latest_stream_usage: dict[str, Any] = {}
891
+
892
+ def model_run_key(
893
+ event: Mapping[str, Any],
894
+ *,
895
+ fallback_key: str | None = None,
896
+ ) -> str:
897
+ raw_run_id = event.get("run_id")
898
+ return (
899
+ str(raw_run_id)
900
+ if raw_run_id
901
+ else fallback_key or f"model-event-{len(model_run_order)}"
902
+ )
903
+
904
+ def record_model_usage(
905
+ event: Mapping[str, Any],
906
+ usage: dict[str, Any],
907
+ *,
908
+ fallback_key: str | None = None,
909
+ ) -> None:
910
+ if not usage:
911
+ return
912
+ run_key = model_run_key(event, fallback_key=fallback_key)
913
+ if run_key not in model_run_usages:
914
+ model_run_order.append(run_key)
915
+ model_run_usages[run_key] = dict(usage)
916
+
917
+ def accumulated_model_usage() -> dict[str, Any]:
918
+ if len(model_run_order) == 1:
919
+ return dict(model_run_usages.get(model_run_order[0]) or {})
920
+ usage: dict[str, Any] = {}
921
+ for run_key in model_run_order:
922
+ usage = accumulate_usage(usage, model_run_usages.get(run_key) or {})
923
+ return usage
924
+
925
+ def latest_model_usage() -> dict[str, Any]:
926
+ for run_key in reversed(model_run_order):
927
+ usage = model_run_usages.get(run_key)
928
+ if usage:
929
+ return dict(usage)
930
+ return {}
692
931
 
693
932
  if is_checkpoint_resume and callable(getattr(self._agent, "astream", None)):
694
933
  try:
695
934
  async for chunk in self._stream_checkpoint_resume_updates(
935
+ stream_input=self._checkpoint_resume_input(
936
+ state,
937
+ payload_provided=resume_payload_provided,
938
+ interrupt_id=resume_interrupt_id,
939
+ ),
696
940
  config=config,
697
941
  context=native_context,
698
942
  ):
@@ -720,9 +964,23 @@ class LangGraphRunner(BaseRunner):
720
964
  return
721
965
 
722
966
  try:
723
- stream_input = None if is_checkpoint_resume else (Command(resume=state) if is_resume else state)
967
+ stream_input = (
968
+ self._checkpoint_resume_input(
969
+ state,
970
+ payload_provided=resume_payload_provided,
971
+ interrupt_id=resume_interrupt_id,
972
+ )
973
+ if is_checkpoint_resume
974
+ else (Command(resume=state) if is_resume else state)
975
+ )
976
+ # stream_mode 含 "custom" 才会产生 on_custom_stream 事件(custom writer);
977
+ # 保留默认 "values" 以兼容既有 on_chain_end/graph_update 消费。
724
978
  stream_kwargs = {"version": "v2", "config": config}
725
- if native_context and self._callable_accepts_keyword(self._agent.astream_events, "context"):
979
+ if self._callable_accepts_keyword(self._agent.astream_events, "stream_mode"):
980
+ stream_kwargs["stream_mode"] = ["values", "custom"]
981
+ if native_context and self._callable_accepts_keyword(
982
+ self._agent.astream_events, "context"
983
+ ):
726
984
  stream_kwargs["context"] = native_context
727
985
  async for event in self._agent.astream_events(stream_input, **stream_kwargs):
728
986
  event_kind = event.get("event", "")
@@ -731,12 +989,25 @@ class LangGraphRunner(BaseRunner):
731
989
  chunk = event.get("data", {}).get("chunk")
732
990
  if not chunk:
733
991
  continue
992
+ chunk_usage = self._extract_usage(chunk)
993
+ if chunk_usage:
994
+ # Some LangChain providers attach cumulative usage to
995
+ # every stream chunk, and LangChain may then sum those
996
+ # cumulative snapshots into an inflated
997
+ # on_chat_model_end usage. For a concrete model run,
998
+ # keep the latest stream snapshot and ignore the later
999
+ # end usage for that same run_id.
1000
+ latest_stream_usage = dict(chunk_usage)
1001
+ if event.get("run_id"):
1002
+ run_key = model_run_key(event)
1003
+ stream_usage_run_keys.add(run_key)
1004
+ record_model_usage(event, latest_stream_usage)
734
1005
 
735
1006
  # 推理内容
736
1007
  reasoning = getattr(chunk, "reasoning_content", None)
737
1008
  if not reasoning and hasattr(chunk, "additional_kwargs"):
738
1009
  reasoning = chunk.additional_kwargs.get("reasoning_content")
739
-
1010
+
740
1011
  if reasoning:
741
1012
  accumulated_reasoning += reasoning
742
1013
  yield {"delta": reasoning, "type": "thinking"}
@@ -746,9 +1017,9 @@ class LangGraphRunner(BaseRunner):
746
1017
  content = self._filter_tool_tags(chunk.content)
747
1018
  if isinstance(content, str):
748
1019
  if accumulated_reasoning and content.startswith(accumulated_reasoning):
749
- content = content[len(accumulated_reasoning):]
1020
+ content = content[len(accumulated_reasoning) :]
750
1021
  elif reasoning and content.startswith(reasoning):
751
- content = content[len(reasoning):]
1022
+ content = content[len(reasoning) :]
752
1023
  if content:
753
1024
  for part in inline_reasoning_parser.feed(content):
754
1025
  if not part.text or not part.text.strip():
@@ -760,6 +1031,68 @@ class LangGraphRunner(BaseRunner):
760
1031
  accumulated_text += part.text
761
1032
  yield {"delta": part.text, "type": "text"}
762
1033
 
1034
+ elif event_kind == "on_chat_model_end":
1035
+ data = event.get("data") or {}
1036
+ output = data.get("output") if isinstance(data, Mapping) else None
1037
+ usage = self._extract_usage(output) or self._extract_usage(data)
1038
+ last_usage = self._extract_last_usage(output) or self._extract_last_usage(data)
1039
+ run_key = model_run_key(event)
1040
+ if run_key not in stream_usage_run_keys:
1041
+ record_model_usage(event, last_usage or usage)
1042
+
1043
+ elif event_kind == "on_chain_stream":
1044
+ # node 内 get_stream_writer() 写入的自定义数据,经 stream_mode 含
1045
+ # "custom" 时,astream_events 包成 on_chain_stream,chunk 为
1046
+ # (mode, value) tuple:("custom", value) 是 writer 透传内容,
1047
+ # ("values", state) 是 state 快照(忽略,终态走 on_chain_end)。
1048
+ # 编排方常用 custom writer 把"调远端 agent/子图"的流式增量透传出来。
1049
+ chunk = event.get("data", {}).get("chunk")
1050
+ if not (
1051
+ isinstance(chunk, tuple) and len(chunk) == 2 and chunk[0] == "custom"
1052
+ ):
1053
+ continue
1054
+ data = chunk[1]
1055
+ if isinstance(data, str):
1056
+ accumulated_text += data
1057
+ yield {"delta": data, "type": "text"}
1058
+ continue
1059
+ if isinstance(data, Mapping):
1060
+ custom_type = str(data.get("type") or "text")
1061
+ if custom_type in ("tool_call", "tool_result"):
1062
+ # 结构化工具事件:透传完整 payload(tool_name/tool_args/
1063
+ # tool_output 等),不计入正文,供 UI 渲染工具卡片。
1064
+ out = {"type": custom_type}
1065
+ out.update({k: v for k, v in data.items() if k != "type"})
1066
+ yield out
1067
+ continue
1068
+ custom_delta = ""
1069
+ for key in ("delta", "text", "content", "output", "data"):
1070
+ value = data.get(key)
1071
+ if isinstance(value, str) and value:
1072
+ custom_delta = value
1073
+ break
1074
+ if not custom_delta:
1075
+ continue
1076
+ replace = bool(data.get("replace"))
1077
+ if custom_type == "thinking":
1078
+ accumulated_reasoning = (
1079
+ custom_delta
1080
+ if replace
1081
+ else accumulated_reasoning + custom_delta
1082
+ )
1083
+ else:
1084
+ accumulated_text = (
1085
+ custom_delta if replace else accumulated_text + custom_delta
1086
+ )
1087
+ out = {"delta": custom_delta, "type": custom_type}
1088
+ if replace:
1089
+ out["replace"] = True
1090
+ yield out
1091
+ continue
1092
+ if data is not None:
1093
+ accumulated_text += str(data)
1094
+ yield {"delta": str(data), "type": "text"}
1095
+
763
1096
  elif event_kind == "on_tool_start":
764
1097
  emitted_non_text_event = True
765
1098
  yield {
@@ -768,23 +1101,35 @@ class LangGraphRunner(BaseRunner):
768
1101
  "tool_args": event.get("data", {}).get("input", {}),
769
1102
  "run_id": event.get("run_id"),
770
1103
  }
771
-
1104
+
772
1105
  elif event_kind == "on_tool_end":
773
1106
  emitted_non_text_event = True
774
1107
  tool_output = event.get("data", {}).get("output", "")
1108
+ # LangGraph returns a ToolMessage here for normal tools.
1109
+ # Preserve its content instead of serializing the repr,
1110
+ # otherwise structured output such as A2UI envelopes becomes
1111
+ # unparsable. Keep the callback run_id below: it is paired
1112
+ # with the preceding ``on_tool_start`` event on this stream.
1113
+ normalized_output = getattr(tool_output, "content", tool_output)
1114
+ if isinstance(tool_output, Mapping) and "content" in tool_output:
1115
+ normalized_output = tool_output["content"]
775
1116
  yield {
776
1117
  "type": "tool_result",
777
1118
  "tool_name": event.get("name", "unknown"),
778
1119
  "tool_args": event.get("data", {}).get("input", {}),
779
- "tool_output": tool_output if isinstance(tool_output, dict) else (str(tool_output) if tool_output else ""),
1120
+ "tool_output": normalized_output,
780
1121
  "run_id": event.get("run_id"),
781
1122
  }
782
-
1123
+
783
1124
  elif event_kind == "on_chain_end":
784
1125
  output = event.get("data", {}).get("output", {})
785
1126
  if isinstance(output, dict) and "__interrupt__" in output:
786
1127
  emitted_non_text_event = True
787
- yield {"type": "interrupt", "interrupt_info": output["__interrupt__"], "session_id": session_id}
1128
+ yield {
1129
+ "type": "interrupt",
1130
+ "interrupt_info": output["__interrupt__"],
1131
+ "session_id": session_id,
1132
+ }
788
1133
  return
789
1134
  extracted_output = self._extract_output(output)
790
1135
  if extracted_output:
@@ -794,10 +1139,41 @@ class LangGraphRunner(BaseRunner):
794
1139
 
795
1140
  except Exception as e:
796
1141
  if "Interrupt" in type(e).__name__:
797
- yield {"type": "interrupt", "interrupt_info": self._get_interrupt_info(self._agent.get_state(config)), "session_id": session_id}
1142
+ yield {
1143
+ "type": "interrupt",
1144
+ "interrupt_info": self._get_interrupt_info(self._agent.get_state(config)),
1145
+ "session_id": session_id,
1146
+ }
798
1147
  return
799
1148
  raise
800
1149
 
1150
+ # goal-18(ksadk-web 人机交互):图因审批门(HITL)在流式中静默暂停时,
1151
+ # 这里把审批详情(action_requests)作为 approval 事件冒出,供 UI 渲染审批卡。
1152
+ # 此前流式路径只在 checkpoint 标 resumable,UI 拿不到"该批哪个工具/什么参数/允许哪些决定"。
1153
+ # 注:get_state 在部分 agent 上是 async,统一按 awaitable 处理;取不到则跳过,不破坏事件流。
1154
+ pending_approval = None
1155
+ try:
1156
+ _get_state = getattr(self._agent, "aget_state", None) or getattr(
1157
+ self._agent, "get_state", None
1158
+ )
1159
+ if _get_state is not None:
1160
+ _maybe_state = _get_state(config)
1161
+ if inspect.isawaitable(_maybe_state):
1162
+ _maybe_state = await _maybe_state
1163
+ pending_approval = self._get_interrupt_info(_maybe_state)
1164
+ except Exception:
1165
+ pending_approval = None
1166
+ if pending_approval:
1167
+ yield {
1168
+ "type": "approval",
1169
+ "interrupt_info": pending_approval,
1170
+ "session_id": session_id,
1171
+ }
1172
+ metadata = await self._latest_checkpoint_metadata(config)
1173
+ if metadata:
1174
+ yield {"type": "checkpoint", "metadata": metadata}
1175
+ return
1176
+
801
1177
  for part in inline_reasoning_parser.flush():
802
1178
  if not part.text or not part.text.strip():
803
1179
  continue
@@ -811,32 +1187,51 @@ class LangGraphRunner(BaseRunner):
811
1187
  if not accumulated_text:
812
1188
  if final_output_text:
813
1189
  final_chunk = {"output": final_output_text, "type": "final"}
814
- if final_output_usage:
815
- final_chunk["usage"] = final_output_usage
816
- if final_output_last_usage:
817
- final_chunk.setdefault("metadata", {})["last_usage"] = final_output_last_usage
1190
+ usage = accumulated_model_usage() or final_output_usage or latest_stream_usage
1191
+ last_usage = (
1192
+ latest_model_usage() or final_output_last_usage or latest_stream_usage or usage
1193
+ )
1194
+ if usage:
1195
+ final_chunk["usage"] = usage
1196
+ if last_usage:
1197
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
818
1198
  yield final_chunk
819
1199
  elif not emitted_non_text_event:
820
- result = await self.invoke(invoke_payload)
821
- final_chunk = {"output": result.get("output", ""), "type": "final"}
1200
+ result = await self.invoke({**invoke_payload, "_ksadk_force_graph_invoke": True})
1201
+ fallback_chunk: dict[str, Any] = {
1202
+ "output": result.get("output", ""),
1203
+ "type": "final",
1204
+ }
822
1205
  usage = self._extract_usage(result)
823
1206
  if usage:
824
- final_chunk["usage"] = usage
1207
+ fallback_chunk["usage"] = usage
825
1208
  last_usage = self._extract_last_usage(result)
826
1209
  if last_usage:
827
- final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
828
- yield final_chunk
829
- metadata = result.get("metadata") if isinstance(result, dict) else None
830
- if isinstance(metadata, dict) and metadata.get("agentengine"):
831
- yield {"type": "checkpoint", "metadata": metadata}
1210
+ fallback_chunk.setdefault("metadata", {})["last_usage"] = last_usage
1211
+ yield fallback_chunk
1212
+ checkpoint_metadata = result.get("metadata") if isinstance(result, dict) else None
1213
+ if isinstance(checkpoint_metadata, dict) and checkpoint_metadata.get("agentengine"):
1214
+ yield {"type": "checkpoint", "metadata": checkpoint_metadata}
832
1215
  return
833
1216
  else:
834
1217
  final_chunk = {"output": accumulated_text, "type": "final"}
835
- usage = await self._latest_state_usage(config)
1218
+ state_usage = await self._latest_state_usage(config)
1219
+ usage = (
1220
+ accumulated_model_usage()
1221
+ or state_usage
1222
+ or final_output_usage
1223
+ or latest_stream_usage
1224
+ )
836
1225
  if usage:
837
1226
  final_chunk["usage"] = usage
838
- # _latest_state_usage 返回末个 message usage,单次调用场景即 last_usage
839
- final_chunk.setdefault("metadata", {})["last_usage"] = usage
1227
+ last_usage = (
1228
+ latest_model_usage()
1229
+ or state_usage
1230
+ or final_output_last_usage
1231
+ or latest_stream_usage
1232
+ or usage
1233
+ )
1234
+ final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
840
1235
  yield final_chunk
841
1236
 
842
1237
  metadata = await self._latest_checkpoint_metadata(config)
@@ -847,6 +1242,6 @@ class LangGraphRunner(BaseRunner):
847
1242
  """过滤 <tool_call> 标签"""
848
1243
  if not isinstance(content, str):
849
1244
  return content
850
- content = re.sub(r'<tool_call>.*?</tool_call>', '', content, flags=re.DOTALL)
851
- content = re.sub(r'</?(?:tool_call|arg_key|arg_value)>', '', content)
1245
+ content = re.sub(r"<tool_call>.*?</tool_call>", "", content, flags=re.DOTALL)
1246
+ content = re.sub(r"</?(?:tool_call|arg_key|arg_value)>", "", content)
852
1247
  return content