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
@@ -0,0 +1,873 @@
1
+ """RunnerRuntimeAdapter — 把现有 ``BaseRunner`` 对齐到 G0.3 ``RuntimeAdapter`` (goal-07)。
2
+
3
+ 不推倒现有 runner(ADK/LangGraph 的 stream/invoke 已在跑),在它们之上实现平台六动词。
4
+ **cancel 不再是空壳**:实现冻结的 cancel 状态机——活跃 turn interrupt(关闭流)/
5
+ 无活跃 turn 记 pending / **级联丢弃 pending 工具审批** / 返回 :class:`CancelResult`。
6
+
7
+ 框架差异(resume 目标、checkpoint 粒度)由子类钩子 ``_resume_native`` 与
8
+ ``_checkpoint_capability`` 诚实声明(ADK forward-only vs LangGraph time-travel)。
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import copy
15
+ import inspect
16
+ import json
17
+ import logging
18
+ from collections.abc import Mapping
19
+ from contextlib import nullcontext
20
+ from dataclasses import dataclass, field
21
+ from typing import Any, AsyncIterator, Dict, Optional, cast
22
+
23
+ from ksadk.conversations.runtime_input import _runner_name
24
+ from ksadk.conversations.runtime_observability import (
25
+ _conversation_span_scope,
26
+ _set_conversation_input_attributes,
27
+ _set_conversation_output_attributes,
28
+ _set_conversation_span_attributes,
29
+ _set_conversation_usage_attributes,
30
+ )
31
+ from ksadk.events.runtime_event import EventType, RuntimeEvent
32
+ from ksadk.runners.base_runner import BaseRunner
33
+ from ksadk.runtime.adapter import (
34
+ BaseRuntime,
35
+ CancelResult,
36
+ CheckpointCapability,
37
+ CheckpointDescriptor,
38
+ ResumePayload,
39
+ ResumeTarget,
40
+ RunHandle,
41
+ RuntimeAdapter,
42
+ StartRequest,
43
+ )
44
+ from ksadk.runtime.preprocessing import PreparedRuntimeStart, prepare_runtime_start
45
+ from ksadk.runtime_context import platform_invocation_scope
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ _STREAM_STOP = object()
50
+ _ResumeKey = tuple[str, str, str, str, str]
51
+
52
+
53
+ async def _anext_or_stop(gen: AsyncIterator[Any]) -> Any:
54
+ """取下一个 chunk;流结束返回 _STREAM_STOP sentinel(便于竞速)。"""
55
+ try:
56
+ return await gen.__anext__()
57
+ except StopAsyncIteration:
58
+ return _STREAM_STOP
59
+
60
+
61
+ def _a2ui_surface_event(chunk: Any) -> tuple[str, dict[str, Any]] | None:
62
+ """Recognize a validated A2UI tool envelope and make it a first-class event.
63
+
64
+ The dynamic ``generate_a2ui`` tool returns official v0.9 operations as a
65
+ JSON tool result. Tool results are otherwise opaque to the runtime, which
66
+ would leave AG-UI with nothing to project until a page reload reconstructs
67
+ history. Convert exactly that envelope at the runtime boundary so it is
68
+ streamed, persisted, and replayed like every other A2UI surface.
69
+ """
70
+
71
+ if not isinstance(chunk, dict):
72
+ return None
73
+ value = chunk.get("tool_output", chunk.get("output"))
74
+ if value is not None and hasattr(value, "content"):
75
+ value = value.content
76
+ if isinstance(value, str):
77
+ try:
78
+ value = json.loads(value)
79
+ except (TypeError, ValueError):
80
+ return None
81
+ if not isinstance(value, Mapping):
82
+ return None
83
+ operations_raw = value.get("a2ui_operations")
84
+ if not isinstance(operations_raw, list) or not operations_raw:
85
+ return None
86
+ operations = [dict(operation) for operation in operations_raw if isinstance(operation, Mapping)]
87
+ if not operations:
88
+ return None
89
+
90
+ known: list[tuple[str, str]] = []
91
+ for operation in operations:
92
+ for key, event_type in (
93
+ ("createSurface", EventType.A2UI_SURFACE_BEGIN),
94
+ ("updateComponents", EventType.A2UI_SURFACE_UPDATE),
95
+ ("updateDataModel", EventType.A2UI_SURFACE_UPDATE),
96
+ ("deleteSurface", EventType.A2UI_SURFACE_END),
97
+ ):
98
+ detail = operation.get(key)
99
+ if isinstance(detail, Mapping) and isinstance(detail.get("surfaceId"), str):
100
+ surface_id = detail["surfaceId"].strip()
101
+ if surface_id:
102
+ known.append((surface_id, event_type))
103
+ break
104
+ if not known:
105
+ return None
106
+ surface_ids = {surface_id for surface_id, _event_type in known}
107
+ if len(surface_ids) != 1:
108
+ logger.warning("ignoring A2UI tool result with multiple surfaces")
109
+ return None
110
+ surface_id = known[0][0]
111
+ event_type = (
112
+ EventType.A2UI_SURFACE_BEGIN
113
+ if any(kind == EventType.A2UI_SURFACE_BEGIN for _surface_id, kind in known)
114
+ else known[0][1]
115
+ )
116
+ return event_type, {"surface_id": surface_id, "operations": operations}
117
+
118
+
119
+ def _coerce_literal(value: Any, allowed: tuple[str, ...], default: str) -> Any:
120
+ """把 value 收窄到 allowed 之一(不在则回退 default),供 Literal 字段使用。"""
121
+ return value if value in allowed else default
122
+
123
+
124
+ class _RunnerAsBaseRuntime(BaseRuntime):
125
+ """把 ``BaseRunner`` 包装为 G0.3 ``BaseRuntime``(原生能力面)。"""
126
+
127
+ def __init__(self, runner: BaseRunner, runtime_type: str) -> None:
128
+ self._runner = runner
129
+ self.runtime_type = runtime_type
130
+
131
+ @property
132
+ def runner(self) -> BaseRunner:
133
+ return self._runner
134
+
135
+ def native_capabilities(self) -> dict[str, Any]:
136
+ caps = getattr(self._runner, "get_runtime_capabilities", None)
137
+ if callable(caps):
138
+ try:
139
+ return dict(caps())
140
+ except Exception: # noqa: BLE001
141
+ pass
142
+ return {"Framework": self.runtime_type}
143
+
144
+
145
+ @dataclass
146
+ class _ActiveRun:
147
+ """一次进行中的 run(adapter 用于 cancel 追踪)。"""
148
+
149
+ invocation_id: str
150
+ session_id: str
151
+ stream: Optional[AsyncIterator[RuntimeEvent]] = None
152
+ pending_approvals: set[str] = field(default_factory=set)
153
+ interrupt_event: asyncio.Event = field(default_factory=asyncio.Event)
154
+ cancellation_ack: asyncio.Event = field(default_factory=asyncio.Event)
155
+ chunk_task: Optional[asyncio.Task[Any]] = None
156
+ resume_key: Optional[_ResumeKey] = None
157
+ resume_fingerprint: Any = None
158
+ skip_runner: bool = False
159
+ done: bool = False
160
+
161
+
162
+ class RunnerRuntimeAdapter(RuntimeAdapter):
163
+ """把 ``BaseRunner`` 映射为平台六动词的通用 adapter。"""
164
+
165
+ def __init__(self, runner: BaseRunner, *, runtime_type: str) -> None:
166
+ super().__init__(_RunnerAsBaseRuntime(runner, runtime_type))
167
+ self._runner = runner
168
+ self._runtime_type = runtime_type
169
+ self._active_runs: dict[str, _ActiveRun] = {}
170
+ self._known_runs: set[str] = set()
171
+ self._run_sessions: dict[str, str] = {}
172
+ self._pending_cancels: set[str] = set()
173
+ self._resume_decisions: dict[_ResumeKey, Any] = {}
174
+ self._consumed_resumes: set[_ResumeKey] = set()
175
+ self._seq = 0
176
+ # 可观测:最近一次 cancel 级联丢弃的 pending 审批集(contract test 断言用)。
177
+ self.last_cancel_dropped_approvals: set[str] = set()
178
+
179
+ # ---- 框架钩子(子类按需 override) ----
180
+
181
+ def _checkpoint_capability(self) -> CheckpointCapability:
182
+ """诚实暴露 checkpoint 粒度。默认读 runner.describe_checkpoint_capability。"""
183
+ raw = {}
184
+ describe = getattr(self._runner, "describe_checkpoint_capability", None)
185
+ if callable(describe):
186
+ try:
187
+ raw = dict(describe())
188
+ except Exception: # noqa: BLE001
189
+ raw = {}
190
+ supported = bool(raw.get("Supported"))
191
+ granularity = _coerce_literal(
192
+ raw.get("Granularity"),
193
+ ("delta", "snapshot", "none"),
194
+ "snapshot" if supported else "none",
195
+ )
196
+ rollback_scope = _coerce_literal(
197
+ raw.get("RollbackScope"),
198
+ ("turn", "invocation", "none"),
199
+ "invocation" if supported else "none",
200
+ )
201
+ return CheckpointCapability(
202
+ supported=supported,
203
+ granularity=granularity,
204
+ rollback_scope=rollback_scope,
205
+ fork_supported=bool(raw.get("ForkSupported", False)),
206
+ durable=bool(raw.get("Durable", False)),
207
+ shared_across_pods=bool(raw.get("SharedAcrossPods", False)),
208
+ reason=str(raw.get("Reason") or ""),
209
+ )
210
+
211
+ async def _resume_native(
212
+ self,
213
+ handle: RunHandle,
214
+ target: ResumeTarget,
215
+ payload: Optional[ResumePayload],
216
+ ) -> Optional[dict]:
217
+ """框架原生 resume:返回注入下一次 ``stream()`` 的 runner_input 覆盖。
218
+
219
+ 返回的 dict 会被 :meth:`_build_runner_input` 合并,以框架 runner 真正消费的
220
+ ``checkpoint_resume`` + ``framework_ref`` 形式驱动真实恢复(而非仅写
221
+ ``native_ref`` 摆设)。默认通用形式;ADK/LangGraph 子类覆盖为各自框架结构。
222
+ 返回 ``None`` 表示本框架无可注入的 resume 输入(退化为普通 stream)。
223
+ """
224
+ return {
225
+ "checkpoint_resume": True,
226
+ "run_id": target.id,
227
+ "framework_ref": {self._runtime_type: {f"{target.kind}": target.id}},
228
+ "input": payload.data if payload else None,
229
+ }
230
+
231
+ # ---- 六动词 ----
232
+
233
+ async def start(self, request: StartRequest) -> RunHandle:
234
+ run_id = str(request.metadata.get("invocation_id") or self._next_invocation_id())
235
+ prepared_start = await prepare_runtime_start(request, self._runner)
236
+ handle = RunHandle(
237
+ run_id=run_id,
238
+ session_id=request.session_id,
239
+ runtime_type=self._runtime_type,
240
+ native_ref={"user_id": request.user_id, "agent_id": request.agent_id},
241
+ )
242
+ self._known_runs.add(run_id)
243
+ self._run_sessions[run_id] = request.session_id
244
+ self._active_runs[run_id] = _ActiveRun(invocation_id=run_id, session_id=request.session_id)
245
+ # 暂存 start 输入,供 stream() 使用。
246
+ self._active_runs[run_id].__dict__["_start_request"] = request
247
+ if prepared_start is not None:
248
+ self._active_runs[run_id].__dict__["_prepared_start"] = prepared_start
249
+ return handle
250
+
251
+ def stream(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
252
+ return self._stream_events(handle)
253
+
254
+ def is_handle_attached(self, handle: RunHandle) -> bool:
255
+ return (
256
+ handle.runtime_type == self._runtime_type
257
+ and handle.run_id in self._known_runs
258
+ and self._run_sessions.get(handle.run_id) == handle.session_id
259
+ )
260
+
261
+ async def attach(self, handle: RunHandle) -> RunHandle:
262
+ """Restore a persisted handle through an explicit native runner seam.
263
+
264
+ Merely repopulating ``_known_runs`` would make validation pass without
265
+ restoring framework state. A runner therefore has to expose
266
+ ``attach_runtime_handle(handle)`` and prove that its durable backend can
267
+ resolve the referenced session/checkpoint.
268
+ """
269
+ if self.is_handle_attached(handle):
270
+ return handle
271
+ if handle.runtime_type != self._runtime_type:
272
+ raise ValueError(
273
+ f"run handle runtime {handle.runtime_type!r} does not match "
274
+ f"adapter {self._runtime_type!r}"
275
+ )
276
+ attach = getattr(self._runner, "attach_runtime_handle", None)
277
+ if not callable(attach):
278
+ raise RuntimeError(
279
+ f"runner for {self._runtime_type!r} has no durable "
280
+ "attach_runtime_handle capability"
281
+ )
282
+ restored = attach(handle)
283
+ if inspect.isawaitable(restored):
284
+ restored = await restored
285
+ if restored is False or restored is None:
286
+ raise RuntimeError(
287
+ f"runner could not restore persisted run {handle.run_id!r} "
288
+ f"for session {handle.session_id!r}"
289
+ )
290
+ if isinstance(restored, RunHandle) and restored != handle:
291
+ raise ValueError("native runner attached a different run handle")
292
+ self._known_runs.add(handle.run_id)
293
+ self._run_sessions[handle.run_id] = handle.session_id
294
+ return handle
295
+
296
+ async def cancel(self, handle: RunHandle) -> CancelResult:
297
+ run_id = handle.run_id
298
+ run = self._active_runs.get(run_id)
299
+ # 活跃 turn = 正在 streaming(run.stream 非 None 且未 done);
300
+ # start 过但未 streaming / 已结束 → 无活跃 turn(记 pending 或 not_running)。
301
+ is_active = run is not None and not run.done and run.stream is not None
302
+ if not is_active:
303
+ if run_id in self._known_runs:
304
+ # 无活跃 turn 但 invocation 已知 → 记 pending,下个 turn 消费。
305
+ self._pending_cancels.add(run_id)
306
+ return CancelResult.PENDING_CANCEL_RECORDED
307
+ return CancelResult.NOT_RUNNING
308
+ # 有活跃 turn:interrupt + 级联丢弃 pending 审批。
309
+ assert run is not None # is_active 蕴含 run 非 None
310
+ try:
311
+ await self._interrupt_active_run(run)
312
+ # 级联丢弃该 turn 的 pending 工具审批(先快照供观测)。
313
+ self.last_cancel_dropped_approvals = set(run.pending_approvals)
314
+ run.pending_approvals.clear()
315
+ run.done = True
316
+ self._active_runs.pop(run_id, None)
317
+ self._pending_cancels.discard(run_id)
318
+ return CancelResult.INTERRUPTED_ACTIVE_TURN
319
+ except Exception: # noqa: BLE001
320
+ logger.exception("cancel active run %s 失败", run_id)
321
+ return CancelResult.FAILED
322
+
323
+ async def resume(
324
+ self,
325
+ handle: RunHandle,
326
+ target: ResumeTarget,
327
+ payload: Optional[ResumePayload],
328
+ ) -> RunHandle:
329
+ if (
330
+ handle.runtime_type != self._runtime_type
331
+ or handle.run_id not in self._known_runs
332
+ or self._run_sessions.get(handle.run_id) != handle.session_id
333
+ ):
334
+ raise ValueError(f"unknown run handle for {self._runtime_type}: {handle.run_id!r}")
335
+ if handle.run_id in self._pending_cancels:
336
+ return handle
337
+ self._require_native_checkpoint_capability()
338
+ self._known_runs.add(handle.run_id)
339
+ # _resume_native 返回的 runner_input 覆盖存到 run 上,下一次 stream() 经
340
+ # _build_runner_input 消费,以框架原生方式真驱动恢复。
341
+ override = await self._resume_native(handle, target, payload)
342
+ resume_key = (
343
+ handle.run_id,
344
+ handle.session_id,
345
+ target.kind,
346
+ target.id,
347
+ str(payload.call_id or "") if payload is not None else "",
348
+ )
349
+ resume_fingerprint = (
350
+ (payload.kind, copy.deepcopy(payload.data)) if payload is not None else None
351
+ )
352
+ existing_fingerprint = self._resume_decisions.get(resume_key, _STREAM_STOP)
353
+ if existing_fingerprint is not _STREAM_STOP:
354
+ if existing_fingerprint != resume_fingerprint:
355
+ raise ValueError(f"conflicting resume for checkpoint {target.id!r}")
356
+ current = self._active_runs.get(handle.run_id)
357
+ if current is not None and not current.done and current.resume_key == resume_key:
358
+ return handle
359
+ if resume_key in self._consumed_resumes:
360
+ self._active_runs[handle.run_id] = _ActiveRun(
361
+ invocation_id=handle.run_id,
362
+ session_id=handle.session_id,
363
+ resume_key=resume_key,
364
+ resume_fingerprint=resume_fingerprint,
365
+ skip_runner=True,
366
+ )
367
+ return handle
368
+
369
+ current = self._active_runs.get(handle.run_id)
370
+ if current is not None and not current.done and current.resume_key is not None:
371
+ if current.resume_key == resume_key:
372
+ if current.resume_fingerprint == resume_fingerprint:
373
+ return handle
374
+ raise ValueError(f"conflicting resume for checkpoint {target.id!r}")
375
+ raise ValueError(f"run {handle.run_id!r} already has an active or pending resume")
376
+
377
+ run = _ActiveRun(
378
+ invocation_id=handle.run_id,
379
+ session_id=handle.session_id,
380
+ resume_key=resume_key,
381
+ resume_fingerprint=resume_fingerprint,
382
+ )
383
+ if override:
384
+ run.__dict__["_resume_input_override"] = override
385
+ self._resume_decisions[resume_key] = resume_fingerprint
386
+ self._active_runs[handle.run_id] = run
387
+ return handle
388
+
389
+ async def checkpoint(self, handle: RunHandle) -> CheckpointDescriptor:
390
+ capability = self._require_native_checkpoint_capability()
391
+ checkpoint_id = str(handle.native_ref.get("checkpoint_id") or "").strip()
392
+ if not checkpoint_id:
393
+ raise RuntimeError(
394
+ f"{self._runtime_type} runner has no native checkpoint for run "
395
+ f"{handle.run_id!r}"
396
+ )
397
+ return CheckpointDescriptor(
398
+ checkpoint_id=checkpoint_id,
399
+ invocation_id=handle.run_id,
400
+ capability=capability,
401
+ ref=dict(handle.native_ref),
402
+ )
403
+
404
+ def _require_native_checkpoint_capability(self) -> CheckpointCapability:
405
+ """Fail closed when a runner cannot create or resume a native checkpoint."""
406
+ capability = self._checkpoint_capability()
407
+ if capability.supported:
408
+ return capability
409
+ detail = capability.reason or "runner does not expose framework checkpoints"
410
+ raise RuntimeError(
411
+ f"{self._runtime_type} native checkpoint capability is unavailable: {detail}"
412
+ )
413
+
414
+ async def close(self, handle: RunHandle) -> None:
415
+ run = self._active_runs.pop(handle.run_id, None)
416
+ if run is not None:
417
+ await self._interrupt_active_run(run)
418
+ run.done = True
419
+ resume_keys = {
420
+ key
421
+ for key in self._resume_decisions
422
+ if key[0] == handle.run_id and key[1] == handle.session_id
423
+ }
424
+ for key in resume_keys:
425
+ self._resume_decisions.pop(key, None)
426
+ self._consumed_resumes.difference_update(resume_keys)
427
+ self._pending_cancels.discard(handle.run_id)
428
+ self._known_runs.discard(handle.run_id)
429
+ self._run_sessions.pop(handle.run_id, None)
430
+ close = getattr(self._runner, "close", None)
431
+ if callable(close):
432
+ result = close()
433
+ if inspect.isawaitable(result):
434
+ await result
435
+
436
+ # ---- 内部:cancel 中断 ----
437
+
438
+ async def _interrupt_active_run(self, run: _ActiveRun) -> None:
439
+ """中断活跃 turn:置 interrupt 事件,由 stream 消费侧的竞速循环取消在途 chunk task。
440
+
441
+ 机制链(经实证,见 test_adapter_contract.test_cancel_active_turn_returns_interrupted):
442
+ ``interrupt_event.set()`` → :meth:`_map_runner_stream` 的 ``asyncio.wait`` 竞速命中 →
443
+ ``chunk_task.cancel()`` → ``CancelledError`` 传入 runner 生成器正在 in-flight 的
444
+ ``__anext__`` await(ADK ``run_async`` 的 LLM/tool 调用就在其中),从而真实打断执行,
445
+ 而非仅在事件间停止消费。
446
+
447
+ 前提与边界(诚实声明):ADK / LangGraph 均**不暴露原生 cancel 方法**(Runner 只有
448
+ run/run_async/close 等),因此 asyncio 任务取消是**唯一且正确**的中断机制。它要求
449
+ runner 的执行发生在其 stream async generator 的 ``__anext__`` 内(ADK/LG 流式即如此);
450
+ 若某 runner 把实际工作放到与 ``__anext__`` 解耦的后台 task,则不在本机制覆盖范围。
451
+ 不在此跨 task 对正被另一 task 迭代的 async generator 直接 aclose(不可靠)。
452
+ """
453
+ run.interrupt_event.set()
454
+ if run.stream is None:
455
+ run.cancellation_ack.set()
456
+ return
457
+ if run.chunk_task is not None:
458
+ run.chunk_task.cancel()
459
+ await asyncio.wait_for(run.cancellation_ack.wait(), timeout=2)
460
+
461
+ # ---- 内部:stream → RuntimeEvent ----
462
+
463
+ def _next_invocation_id(self) -> str:
464
+ self._seq += 1
465
+ return f"inv_{self._runtime_type}_{self._seq}"
466
+
467
+ def _next_seq(self) -> int:
468
+ self._seq += 1
469
+ return self._seq
470
+
471
+ async def _stream_events(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
472
+ run = self._active_runs.get(handle.run_id)
473
+ if run is None:
474
+ run = _ActiveRun(invocation_id=handle.run_id, session_id=handle.session_id)
475
+ self._active_runs[handle.run_id] = run
476
+
477
+ # 消费 pending cancel:start 时若已记 pending,立即中断该 turn。
478
+ if handle.run_id in self._pending_cancels:
479
+ self._pending_cancels.discard(handle.run_id)
480
+ yield self._event(
481
+ handle,
482
+ EventType.RUN_CANCELED,
483
+ {
484
+ "status": "cancelled",
485
+ "cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
486
+ },
487
+ )
488
+ return
489
+
490
+ if run.skip_runner:
491
+ run.done = True
492
+ self._active_runs.pop(handle.run_id, None)
493
+ yield self._event(
494
+ handle,
495
+ EventType.RUN_COMPLETED,
496
+ {"status": "already_resumed"},
497
+ )
498
+ return
499
+
500
+ if run.resume_key is not None:
501
+ self._consumed_resumes.add(run.resume_key)
502
+
503
+ yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
504
+
505
+ request = run.__dict__.get("_start_request")
506
+ runner_input = self._build_runner_input(handle, request)
507
+ gen: Optional[AsyncIterator[RuntimeEvent]] = None
508
+ terminal_event_seen = False
509
+ approval_interrupted = False
510
+ try:
511
+ gen = self._map_runner_stream(handle, runner_input)
512
+ run.stream = gen
513
+ async for event in gen:
514
+ yield event
515
+ if event.event_type == EventType.APPROVAL_REQUESTED:
516
+ approval_interrupted = True
517
+ if event.event_type in {
518
+ EventType.RUN_COMPLETED,
519
+ EventType.RUN_FAILED,
520
+ EventType.RUN_CANCELED,
521
+ EventType.RUN_INTERRUPTED,
522
+ }:
523
+ terminal_event_seen = True
524
+ if event.event_type in {EventType.RUN_FAILED, EventType.RUN_CANCELED}:
525
+ return
526
+
527
+ if run.interrupt_event.is_set() and not terminal_event_seen:
528
+ yield self._event(
529
+ handle,
530
+ EventType.RUN_CANCELED,
531
+ {
532
+ "status": "cancelled",
533
+ "cancel_result": CancelResult.INTERRUPTED_ACTIVE_TURN.value,
534
+ },
535
+ )
536
+ elif approval_interrupted and not terminal_event_seen:
537
+ yield self._event(
538
+ handle,
539
+ EventType.RUN_INTERRUPTED,
540
+ {"status": "input_required"},
541
+ )
542
+ elif not terminal_event_seen:
543
+ yield self._event(handle, EventType.RUN_COMPLETED, {"status": "completed"})
544
+ finally:
545
+ run.stream = None
546
+ run.done = True
547
+ self._active_runs.pop(handle.run_id, None)
548
+
549
+ def _build_runner_input(self, handle: RunHandle, request: Optional[StartRequest]) -> dict:
550
+ # resume 覆盖优先:_resume_native 注入的 checkpoint_resume + framework_ref
551
+ # 直接作为 runner 输入,驱动框架原生恢复。
552
+ run = self._active_runs.get(handle.run_id)
553
+ override = run.__dict__.get("_resume_input_override") if run is not None else None
554
+ if override is not None:
555
+ merged = {
556
+ "input": override.get("input"),
557
+ "session_id": handle.session_id,
558
+ "invocation_id": handle.run_id,
559
+ "metadata": dict(override.get("metadata") or {}),
560
+ }
561
+ for key, value in override.items():
562
+ if key not in ("input", "metadata"):
563
+ merged[key] = value
564
+ return merged
565
+
566
+ prepared_start = run.__dict__.get("_prepared_start") if run is not None else None
567
+ if isinstance(prepared_start, PreparedRuntimeStart):
568
+ return dict(prepared_start.runner_input)
569
+
570
+ metadata: Dict[str, Any] = {}
571
+ request_config: Dict[str, Any] = {}
572
+ input_value: Any = ""
573
+ if request is not None:
574
+ input_value = request.input
575
+ metadata = dict(request.metadata or {})
576
+ request_config = dict(request.config or {})
577
+ return {
578
+ **request_config,
579
+ "input": input_value,
580
+ "session_id": handle.session_id,
581
+ "invocation_id": handle.run_id,
582
+ "metadata": metadata,
583
+ }
584
+
585
+ async def _map_runner_stream(
586
+ self, handle: RunHandle, runner_input: dict
587
+ ) -> AsyncIterator[RuntimeEvent]:
588
+ run = self._active_runs.get(handle.run_id)
589
+ interrupt = run.interrupt_event if run is not None else None
590
+ prepared_start = run.__dict__.get("_prepared_start") if run is not None else None
591
+ invocation_context = (
592
+ prepared_start.context if isinstance(prepared_start, PreparedRuntimeStart) else None
593
+ )
594
+ scope = (
595
+ platform_invocation_scope(invocation_context)
596
+ if invocation_context is not None
597
+ else nullcontext()
598
+ )
599
+ runner_name = _runner_name(self._runner)
600
+ accumulated_output: list[str] = []
601
+ usage: dict[str, Any] = {}
602
+ runner_gen: Optional[AsyncIterator[Any]] = None
603
+ async with _conversation_span_scope(runner_name) as span:
604
+ if isinstance(prepared_start, PreparedRuntimeStart):
605
+ _set_conversation_span_attributes(
606
+ span,
607
+ agent_id=str(handle.native_ref.get("agent_id") or "agent"),
608
+ user_id=str(handle.native_ref.get("user_id") or "user"),
609
+ session_id=handle.session_id,
610
+ invocation_id=handle.run_id,
611
+ runner_name=runner_name,
612
+ model=prepared_start.context.model,
613
+ response_id=prepared_start.response_id,
614
+ )
615
+ _set_conversation_input_attributes(span, prepared_start.input_text)
616
+ try:
617
+ with scope:
618
+ stream_result = self._runner.stream(runner_input)
619
+ if inspect.iscoroutine(stream_result):
620
+ # runner.stream 若声明为 async def -> AsyncIterator(非 async generator),
621
+ # 调用返回 coroutine,需 await 得到迭代器。
622
+ stream_result = await stream_result
623
+ runner_gen = cast(AsyncIterator[Any], stream_result)
624
+ while True:
625
+ # 竞速:下一个 runner chunk vs cancel 中断事件。
626
+ chunk_task = asyncio.ensure_future(_anext_or_stop(runner_gen))
627
+ if run is not None:
628
+ run.chunk_task = chunk_task
629
+ wait_set = {chunk_task}
630
+ interrupt_task = (
631
+ asyncio.ensure_future(interrupt.wait())
632
+ if interrupt is not None
633
+ else None
634
+ )
635
+ if interrupt_task is not None:
636
+ wait_set.add(interrupt_task)
637
+ done, pending = await asyncio.wait(
638
+ wait_set, return_when=asyncio.FIRST_COMPLETED
639
+ )
640
+ for task in pending:
641
+ task.cancel()
642
+ if pending:
643
+ await asyncio.gather(*pending, return_exceptions=True)
644
+ if interrupt_task is not None and interrupt_task in done:
645
+ # cancel 中断:安全关闭 runner 流(同一 task)并停止。
646
+ chunk_task.cancel()
647
+ await asyncio.gather(chunk_task, return_exceptions=True)
648
+ return
649
+ try:
650
+ chunk = chunk_task.result()
651
+ except asyncio.CancelledError:
652
+ if interrupt is not None and interrupt.is_set():
653
+ return
654
+ raise
655
+ finally:
656
+ if run is not None:
657
+ run.chunk_task = None
658
+ if chunk is _STREAM_STOP:
659
+ return
660
+ if isinstance(chunk, dict):
661
+ chunk_type = str(chunk.get("type") or "")
662
+ if chunk_type in {"final", "text", "text_delta"}:
663
+ text = self._coerce(
664
+ chunk.get("delta") or chunk.get("output") or chunk.get("data")
665
+ )
666
+ if text:
667
+ accumulated_output.append(text)
668
+ raw_usage = chunk.get("usage")
669
+ if isinstance(raw_usage, dict):
670
+ usage.update(raw_usage)
671
+ event = self._chunk_to_event(handle, run, chunk)
672
+ if event is not None:
673
+ yield event
674
+ a2ui_surface = _a2ui_surface_event(chunk)
675
+ if a2ui_surface is not None:
676
+ event_type, payload = a2ui_surface
677
+ yield self._event(handle, event_type, payload)
678
+ finally:
679
+ if accumulated_output:
680
+ _set_conversation_output_attributes(span, "".join(accumulated_output))
681
+ _set_conversation_usage_attributes(span, usage)
682
+ if runner_gen is not None:
683
+ aclose = getattr(runner_gen, "aclose", None)
684
+ if callable(aclose):
685
+ try:
686
+ await aclose()
687
+ except Exception: # noqa: BLE001
688
+ pass
689
+ if run is not None:
690
+ run.cancellation_ack.set()
691
+
692
+ def _chunk_to_event(
693
+ self, handle: RunHandle, run: Optional[_ActiveRun], chunk: Any
694
+ ) -> Optional[RuntimeEvent]:
695
+ if not isinstance(chunk, dict):
696
+ return self._event(
697
+ handle, EventType.TEXT_DELTA, {"text": str(chunk)}, phase="commentary"
698
+ )
699
+ chunk_type = chunk.get("type")
700
+ if chunk_type in ("reasoning", "reasoning_delta", "thinking"):
701
+ text = self._coerce(
702
+ chunk.get("delta")
703
+ or chunk.get("content")
704
+ or chunk.get("output")
705
+ or chunk.get("data")
706
+ )
707
+ if not text:
708
+ return None
709
+ event_type = (
710
+ EventType.REASONING_COMPLETED
711
+ if chunk.get("status") in ("completed", "done")
712
+ else EventType.REASONING_DELTA
713
+ )
714
+ return self._event(
715
+ handle,
716
+ event_type,
717
+ {"text": text},
718
+ phase="commentary",
719
+ )
720
+ if chunk_type in ("tool_call", "tool_start"):
721
+ call_id = str(
722
+ chunk.get("tool_call_id")
723
+ or chunk.get("call_id")
724
+ or chunk.get("run_id")
725
+ or chunk.get("id")
726
+ or ""
727
+ )
728
+ name = str(chunk.get("tool_name") or chunk.get("name") or "tool")
729
+ return self._event(
730
+ handle,
731
+ EventType.TOOL_CALL_BEGIN,
732
+ {
733
+ "call_id": call_id or name,
734
+ "name": name,
735
+ "args": chunk.get("tool_args", chunk.get("args")),
736
+ },
737
+ )
738
+ if chunk_type in ("tool_result", "tool_end"):
739
+ call_id = str(
740
+ chunk.get("tool_call_id")
741
+ or chunk.get("call_id")
742
+ or chunk.get("run_id")
743
+ or chunk.get("id")
744
+ or ""
745
+ )
746
+ name = str(chunk.get("tool_name") or chunk.get("name") or "tool")
747
+ return self._event(
748
+ handle,
749
+ EventType.TOOL_CALL_END,
750
+ {
751
+ "call_id": call_id or name,
752
+ "name": name,
753
+ "result": chunk.get("tool_output", chunk.get("output")),
754
+ "error": chunk.get("error"),
755
+ },
756
+ )
757
+ if chunk_type in ("interrupt", "approval", "approval_required"):
758
+ detail = chunk.get("interrupt_info") or chunk.get("detail") or {}
759
+ detail_id = detail.get("approval_request_id") if isinstance(detail, dict) else None
760
+ call_id = str(
761
+ chunk.get("call_id")
762
+ or chunk.get("approval_id")
763
+ or chunk.get("id")
764
+ or detail_id
765
+ or ""
766
+ )
767
+ if run is not None and call_id:
768
+ run.pending_approvals.add(call_id)
769
+ if call_id:
770
+ pending_approval_ids = handle.native_ref.setdefault("pending_approval_ids", [])
771
+ if call_id not in pending_approval_ids:
772
+ pending_approval_ids.append(call_id)
773
+ return self._event(
774
+ handle,
775
+ EventType.APPROVAL_REQUESTED,
776
+ {
777
+ "approval_id": call_id,
778
+ "call_id": call_id,
779
+ "kind": "tool",
780
+ "detail": detail,
781
+ },
782
+ )
783
+ if chunk_type == "checkpoint":
784
+ metadata = chunk.get("metadata") or {}
785
+ agentengine = metadata.get("agentengine") if isinstance(metadata, dict) else {}
786
+ framework_ref = (
787
+ agentengine.get("framework_ref") if isinstance(agentengine, dict) else {}
788
+ ) or {}
789
+ runtime_ref = (
790
+ framework_ref.get(self._runtime_type) if isinstance(framework_ref, dict) else {}
791
+ ) or {}
792
+ checkpoint_id = str(
793
+ runtime_ref.get("checkpoint_id") if isinstance(runtime_ref, dict) else ""
794
+ )
795
+ if not checkpoint_id:
796
+ return None
797
+ handle.native_ref["checkpoint_id"] = checkpoint_id
798
+ known_checkpoint_ids = handle.native_ref.setdefault("known_checkpoint_ids", [])
799
+ if checkpoint_id not in known_checkpoint_ids:
800
+ known_checkpoint_ids.append(checkpoint_id)
801
+ handle.native_ref["framework_ref"] = framework_ref
802
+ if isinstance(runtime_ref, dict):
803
+ handle.native_ref.update(runtime_ref)
804
+ return self._event(
805
+ handle,
806
+ EventType.CHECKPOINT_CREATED,
807
+ {
808
+ "checkpoint_id": checkpoint_id,
809
+ "granularity": self._checkpoint_capability().granularity,
810
+ "resume_target": framework_ref,
811
+ },
812
+ )
813
+ if chunk_type == "graph_update":
814
+ return self._event(
815
+ handle,
816
+ EventType.RUN_PROGRESS,
817
+ {
818
+ "status": "in_progress",
819
+ "node": str(chunk.get("node") or ""),
820
+ "state_update": self._coerce(chunk.get("output")),
821
+ },
822
+ )
823
+ if chunk_type == "error":
824
+ error = self._coerce(chunk.get("message") or chunk.get("error"))
825
+ return self._event(
826
+ handle,
827
+ EventType.RUN_FAILED,
828
+ {
829
+ "status": "failed",
830
+ "error": error or "runner failed",
831
+ },
832
+ )
833
+ if chunk_type == "final":
834
+ return self._event(
835
+ handle,
836
+ EventType.TEXT_COMPLETED,
837
+ {"text": self._coerce(chunk.get("output"))},
838
+ phase="final_answer",
839
+ )
840
+ text = self._coerce(chunk.get("delta") or chunk.get("output") or chunk.get("data"))
841
+ if not text:
842
+ return None
843
+ return self._event(handle, EventType.TEXT_DELTA, {"text": text}, phase="commentary")
844
+
845
+ def _event(
846
+ self,
847
+ handle: RunHandle,
848
+ event_type: str,
849
+ payload: dict,
850
+ *,
851
+ phase: Optional[str] = None,
852
+ ) -> RuntimeEvent:
853
+ return RuntimeEvent.create(
854
+ event_type,
855
+ agent_id=str(handle.native_ref.get("agent_id") or "agent"),
856
+ user_id=str(handle.native_ref.get("user_id") or "user"),
857
+ session_id=handle.session_id,
858
+ invocation_id=handle.run_id,
859
+ seq_id=self._next_seq(),
860
+ phase=phase,
861
+ payload=payload,
862
+ )
863
+
864
+ @staticmethod
865
+ def _coerce(value: Any) -> str:
866
+ if value is None:
867
+ return ""
868
+ if isinstance(value, str):
869
+ return value
870
+ return str(value)
871
+
872
+
873
+ __all__ = ["RunnerRuntimeAdapter", "_RunnerAsBaseRuntime"]