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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
  2. agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
  3. ksadk/a2a/__init__.py +178 -11
  4. ksadk/a2a/bootstrap.py +334 -0
  5. ksadk/a2a/card.py +120 -0
  6. ksadk/a2a/context_store.py +203 -0
  7. ksadk/a2a/control_plane.py +818 -0
  8. ksadk/a2a/credential.py +99 -0
  9. ksadk/a2a/event_adapter.py +149 -0
  10. ksadk/a2a/executor.py +406 -89
  11. ksadk/a2a/external_transport.py +516 -0
  12. ksadk/a2a/identity.py +304 -0
  13. ksadk/a2a/ids.py +22 -0
  14. ksadk/a2a/langgraph.py +285 -0
  15. ksadk/a2a/resume_store.py +201 -0
  16. ksadk/a2a/routes.py +105 -0
  17. ksadk/a2a/server.py +107 -60
  18. ksadk/a2a/space_client.py +1101 -0
  19. ksadk/a2a/task_adapter.py +423 -0
  20. ksadk/a2a/task_event_dispatcher.py +167 -0
  21. ksadk/a2a/task_event_outbox.py +326 -0
  22. ksadk/a2a/task_store.py +206 -0
  23. ksadk/a2ui/__init__.py +27 -0
  24. ksadk/a2ui/core.py +186 -0
  25. ksadk/a2ui/models.py +166 -0
  26. ksadk/a2ui/renderer.py +198 -0
  27. ksadk/agui/__init__.py +9 -0
  28. ksadk/agui/a2ui_projection.py +92 -0
  29. ksadk/agui/agent.py +857 -0
  30. ksadk/agui/config.py +86 -0
  31. ksadk/agui/routes.py +55 -0
  32. ksadk/api/client.py +233 -191
  33. ksadk/builders/__init__.py +2 -0
  34. ksadk/builders/base.py +16 -14
  35. ksadk/builders/code_builder.py +318 -236
  36. ksadk/builders/container_builder.py +187 -167
  37. ksadk/builders/framework_requirements.py +29 -11
  38. ksadk/builders/ks3_uploader.py +58 -27
  39. ksadk/builders/managed_runtime_builder.py +145 -0
  40. ksadk/builders/mcp_builder.py +91 -92
  41. ksadk/builders/requirements_utils.py +1 -5
  42. ksadk/cli/__init__.py +89 -171
  43. ksadk/cli/cmd_a2a.py +254 -70
  44. ksadk/cli/cmd_agent.py +19 -5
  45. ksadk/cli/cmd_build.py +158 -29
  46. ksadk/cli/cmd_completion.py +47 -35
  47. ksadk/cli/cmd_config.py +267 -198
  48. ksadk/cli/cmd_create.py +500 -720
  49. ksadk/cli/cmd_dashboard.py +120 -45
  50. ksadk/cli/cmd_deploy.py +117 -58
  51. ksadk/cli/cmd_destroy.py +44 -14
  52. ksadk/cli/cmd_files.py +63 -24
  53. ksadk/cli/cmd_hermes.py +168 -89
  54. ksadk/cli/cmd_invoke.py +184 -122
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +98 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +483 -227
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +33 -10
  61. ksadk/cli/cmd_status.py +80 -31
  62. ksadk/cli/cmd_version.py +20 -13
  63. ksadk/cli/cmd_web.py +89 -13
  64. ksadk/cli/deploy_utils.py +20 -20
  65. ksadk/cli/dry_run.py +5 -3
  66. ksadk/cli/error_utils.py +37 -12
  67. ksadk/cli/global_options.py +1 -2
  68. ksadk/cli/local_runtime.py +1 -2
  69. ksadk/cli/model_catalog.py +6 -2
  70. ksadk/cli/network_options.py +34 -14
  71. ksadk/cli/resource_common.py +6 -2
  72. ksadk/cli/storage.py +0 -1
  73. ksadk/cli/ui.py +5 -3
  74. ksadk/cli/workflow_common.py +51 -17
  75. ksadk/codex/__init__.py +12 -0
  76. ksadk/codex/client.py +408 -0
  77. ksadk/codex/phase.py +143 -0
  78. ksadk/codex/runtime.py +449 -0
  79. ksadk/common/aicp_env.py +0 -1
  80. ksadk/common/auth.py +2 -2
  81. ksadk/common/constants.py +1 -3
  82. ksadk/common/llm_utils.py +17 -13
  83. ksadk/compat/__init__.py +5 -0
  84. ksadk/compat/adk_compat.py +200 -0
  85. ksadk/compat/copilotkit_a2ui.py +330 -0
  86. ksadk/configs/__init__.py +12 -12
  87. ksadk/configs/env_registry.py +500 -100
  88. ksadk/configs/global_config.py +2 -1
  89. ksadk/configs/settings.py +151 -124
  90. ksadk/conversations/__init__.py +7 -5
  91. ksadk/conversations/attachment_storage.py +16 -11
  92. ksadk/conversations/attachments.py +44 -36
  93. ksadk/conversations/compaction_pipeline.py +12 -12
  94. ksadk/conversations/compaction_prompt.py +9 -5
  95. ksadk/conversations/context.py +221 -6
  96. ksadk/conversations/message_projection.py +438 -51
  97. ksadk/conversations/model_context.py +12 -6
  98. ksadk/conversations/model_options.py +0 -1
  99. ksadk/conversations/normalize.py +20 -16
  100. ksadk/conversations/reasoning_markup.py +1 -1
  101. ksadk/conversations/runtime.py +339 -5255
  102. ksadk/conversations/runtime_compaction.py +333 -0
  103. ksadk/conversations/runtime_constants.py +27 -0
  104. ksadk/conversations/runtime_governance.py +185 -0
  105. ksadk/conversations/runtime_input.py +652 -0
  106. ksadk/conversations/runtime_invocation.py +395 -0
  107. ksadk/conversations/runtime_metadata.py +475 -0
  108. ksadk/conversations/runtime_observability.py +355 -0
  109. ksadk/conversations/runtime_payloads.py +272 -0
  110. ksadk/conversations/runtime_persistence.py +440 -0
  111. ksadk/conversations/runtime_preparation.py +418 -0
  112. ksadk/conversations/runtime_resume.py +816 -0
  113. ksadk/conversations/runtime_stream_events.py +902 -0
  114. ksadk/conversations/runtime_streaming.py +547 -0
  115. ksadk/conversations/semantic_summary.py +17 -5
  116. ksadk/conversations/session_title.py +6 -3
  117. ksadk/deployment/__init__.py +15 -16
  118. ksadk/deployment/agent_access.py +12 -23
  119. ksadk/deployment/base.py +51 -38
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +309 -202
  122. ksadk/deployment/registry.py +38 -32
  123. ksadk/deployment/state.py +11 -11
  124. ksadk/deployment/ui_config.py +1 -1
  125. ksadk/detection/__init__.py +1 -1
  126. ksadk/detection/detector.py +146 -83
  127. ksadk/detection/mcp_detector.py +9 -6
  128. ksadk/events/__init__.py +25 -0
  129. ksadk/events/parser.py +191 -0
  130. ksadk/events/replay.py +37 -0
  131. ksadk/events/runtime_event.py +264 -0
  132. ksadk/events/store.py +317 -0
  133. ksadk/harness/__init__.py +34 -0
  134. ksadk/harness/app.py +278 -0
  135. ksadk/harness/config.py +181 -0
  136. ksadk/harness/reasoner.py +105 -0
  137. ksadk/harness/runner.py +211 -0
  138. ksadk/harness/sandbox.py +141 -0
  139. ksadk/harness/tools.py +222 -0
  140. ksadk/hermes_terminal.py +23 -12
  141. ksadk/identity/resolver.py +15 -9
  142. ksadk/ids.py +32 -0
  143. ksadk/knowledge_base/adk_tool.py +1 -1
  144. ksadk/knowledge_base/client.py +23 -29
  145. ksadk/managed_runtime.py +188 -0
  146. ksadk/markdown.py +5 -2
  147. ksadk/mcp_runtime/__init__.py +9 -6
  148. ksadk/memory/__init__.py +1 -1
  149. ksadk/memory/adk/__init__.py +2 -0
  150. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  151. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  152. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  153. ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
  154. ksadk/memory/adk/long_term_memory.py +31 -35
  155. ksadk/memory/adk/resilient_session_service.py +4 -5
  156. ksadk/memory/adk/short_term_memory.py +12 -21
  157. ksadk/memory/adk_tool.py +3 -2
  158. ksadk/memory/backends/base.py +19 -19
  159. ksadk/memory/backends/memory.py +24 -25
  160. ksadk/memory/backends/redis.py +31 -30
  161. ksadk/memory/langchain_tool.py +2 -1
  162. ksadk/memory/manager.py +52 -47
  163. ksadk/memory/service.py +10 -8
  164. ksadk/model_policy.py +15 -7
  165. ksadk/model_proxy/__init__.py +51 -0
  166. ksadk/model_proxy/bootstrap.py +80 -0
  167. ksadk/model_proxy/cache.py +165 -0
  168. ksadk/model_proxy/config.py +45 -0
  169. ksadk/model_proxy/demo_webui.py +115 -0
  170. ksadk/model_proxy/detect.py +145 -0
  171. ksadk/model_proxy/gate.py +68 -0
  172. ksadk/model_proxy/namespace.py +173 -0
  173. ksadk/model_proxy/protocol_proxy.py +22 -0
  174. ksadk/model_proxy/server.py +300 -0
  175. ksadk/model_proxy/transform.py +691 -0
  176. ksadk/openclaw_gateway.py +29 -14
  177. ksadk/runners/adk_runner.py +345 -120
  178. ksadk/runners/base_runner.py +46 -29
  179. ksadk/runners/codex_runner.py +132 -0
  180. ksadk/runners/deepagents_runner.py +0 -1
  181. ksadk/runners/factory.py +17 -5
  182. ksadk/runners/langchain_runner.py +29 -742
  183. ksadk/runners/langgraph_runner.py +352 -90
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +134 -33
  186. ksadk/runners/usage_accumulator.py +1 -0
  187. ksadk/runners/utils/langfuse.py +3 -3
  188. ksadk/runners/utils/loader.py +7 -7
  189. ksadk/runtime/__init__.py +41 -0
  190. ksadk/runtime/adapter.py +345 -0
  191. ksadk/runtime/framework_adapters.py +124 -0
  192. ksadk/runtime/preprocessing.py +118 -0
  193. ksadk/runtime/runner_adapter.py +873 -0
  194. ksadk/runtime_context.py +18 -3
  195. ksadk/runtime_state.py +0 -1
  196. ksadk/sandbox/__init__.py +4 -1
  197. ksadk/sandbox/backends/__init__.py +4 -1
  198. ksadk/sandbox/backends/e2b.py +13 -5
  199. ksadk/sandbox/backends/local_process.py +11 -3
  200. ksadk/sandbox/base.py +10 -15
  201. ksadk/sandbox/factory.py +14 -4
  202. ksadk/sandbox/registry.py +57 -6
  203. ksadk/server/api_models.py +15 -5
  204. ksadk/server/app.py +455 -3885
  205. ksadk/server/composition.py +21 -0
  206. ksadk/server/factory.py +624 -0
  207. ksadk/server/routes/__init__.py +1 -0
  208. ksadk/server/routes/checkpoint_resolution.py +116 -0
  209. ksadk/server/routes/common.py +713 -0
  210. ksadk/server/routes/control.py +327 -0
  211. ksadk/server/routes/dependencies.py +98 -0
  212. ksadk/server/routes/misc.py +448 -0
  213. ksadk/server/routes/models.py +443 -0
  214. ksadk/server/routes/openai_compat.py +207 -0
  215. ksadk/server/routes/projection.py +795 -0
  216. ksadk/server/routes/routers.py +52 -0
  217. ksadk/server/routes/run.py +726 -0
  218. ksadk/server/routes/sessions.py +649 -0
  219. ksadk/server/routes/streaming.py +223 -0
  220. ksadk/server/routes/workspace.py +381 -0
  221. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
  222. ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
  224. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
  226. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
  227. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
  228. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
  239. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
  240. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
  241. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
  242. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
  272. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
  273. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
  275. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
  276. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
  277. ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
  278. ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
  279. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
  280. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
  288. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
  289. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
  290. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
  292. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
  293. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
  297. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
  298. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
  299. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
  300. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
  301. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
  302. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
  303. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
  305. ksadk/server/static/index.html +2 -2
  306. ksadk/server/terminal_sessions.py +111 -58
  307. ksadk/sessions/__init__.py +69 -19
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +2 -3
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +143 -25
  313. ksadk/sessions/resilient.py +36 -8
  314. ksadk/skills/models.py +17 -5
  315. ksadk/skills/package_store.py +5 -2
  316. ksadk/skills/runtime/agent.py +20 -5
  317. ksadk/skills/runtime/artifacts.py +1 -5
  318. ksadk/skills/runtime/backends/e2b.py +10 -4
  319. ksadk/skills/runtime/backends/local.py +15 -3
  320. ksadk/skills/runtime/base.py +1 -2
  321. ksadk/skills/runtime/executor.py +10 -3
  322. ksadk/skills/runtime/factory.py +4 -2
  323. ksadk/skills/runtime/loader.py +1 -2
  324. ksadk/skills/runtime/registry.py +15 -10
  325. ksadk/skills/runtime/request.py +3 -1
  326. ksadk/skills/service_client.py +38 -17
  327. ksadk/skills/tool_defs.py +15 -10
  328. ksadk/terminal_exec_policy.py +3 -4
  329. ksadk/tools/gateway.py +92 -15
  330. ksadk/tools/result_budget.py +12 -4
  331. ksadk/toolsets/__init__.py +134 -36
  332. ksadk/toolsets/platform.py +21 -5
  333. ksadk/toolsets/sandbox.py +82 -16
  334. ksadk/toolsets/skills.py +200 -39
  335. ksadk/toolsets/web.py +123 -26
  336. ksadk/toolsets/workspace.py +212 -101
  337. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  338. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  339. ksadk/tracing/setup.py +69 -53
  340. ksadk/tracing/span_utils.py +5 -2
  341. ksadk/tui/loop.py +78 -30
  342. ksadk/tui/stream_render.py +1 -0
  343. ksadk/ui_config.py +5 -3
  344. ksadk/version.py +1 -1
  345. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  346. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  347. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  348. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  349. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  350. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  351. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  352. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  353. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  354. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  355. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  356. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  357. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  358. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  359. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  360. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  361. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  362. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  364. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  365. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  366. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  367. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  368. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  369. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  370. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  371. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,423 @@
1
+ """A2ARuntimeTaskAdapter — A2A Task 与 Runtime session/run/checkpoint/cancel 的映射 (goal-05 §7.2)。
2
+
3
+ | A2A | Runtime |
4
+ |---|---|
5
+ | ``context_id`` | ``session_id`` |
6
+ | ``task_id`` | SDK TaskStore 主键;run/checkpoint 引用保存在 Runtime-local resume store |
7
+ | working | active invocation |
8
+ | input-required | pending interaction + checkpoint/resume token |
9
+ | canceled | ``RuntimeAdapter.cancel(invocation_id)`` 成功后的终态 |
10
+ | artifact/message | RuntimeEvent artifact/text/data |
11
+
12
+ cancel 一律走 G0.3 冻结的 ``RuntimeAdapter.cancel``(含 pending-cancel 语义),
13
+ 不在 executor/本模块自造一套。
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import logging
19
+ from collections.abc import AsyncIterator, Mapping
20
+ from typing import Any, Optional
21
+
22
+ from google.protobuf.json_format import MessageToDict
23
+ from google.protobuf.message import Message as ProtobufMessage
24
+
25
+ from ksadk.a2a.context_store import A2AContextStore
26
+ from ksadk.a2a.identity import A2AIngressIdentity
27
+ from ksadk.a2a.resume_store import (
28
+ A2AResumePayloadKind,
29
+ A2AResumeState,
30
+ A2AResumeStateStore,
31
+ InMemoryA2AResumeStateStore,
32
+ )
33
+ from ksadk.events import RuntimeEvent
34
+ from ksadk.runtime.adapter import (
35
+ CancelResult,
36
+ ResumePayload,
37
+ ResumeTarget,
38
+ RunHandle,
39
+ RuntimeAdapter,
40
+ StartRequest,
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ class A2ARuntimeTaskAdapter:
47
+ """A2A Task ↔ Runtime 的映射器。
48
+
49
+ 持有一个 G0.3 ``RuntimeAdapter``(由 runtime registry / runner bridge 提供),
50
+ 把 A2A 侧的 task/cancel/input-required 映射到 Runtime 六动词。
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ runtime_adapter: RuntimeAdapter,
56
+ *,
57
+ runtime_type: str = "local",
58
+ context_store: A2AContextStore | None = None,
59
+ resume_state_store: A2AResumeStateStore | None = None,
60
+ ) -> None:
61
+ self._adapter = runtime_adapter
62
+ self._runtime_type = runtime_type
63
+ self._context_store = context_store
64
+ self._resume_state_store = resume_state_store or InMemoryA2AResumeStateStore()
65
+ self._handles_by_task_key: dict[tuple[str, str, str], RunHandle] = {}
66
+ self._accepted_canceled_tasks: set[tuple[str, str, str]] = set()
67
+
68
+ @property
69
+ def runtime_adapter(self) -> RuntimeAdapter:
70
+ return self._adapter
71
+
72
+ # ---- 映射:task_id/context_id ↔ session/invocation ----
73
+
74
+ @staticmethod
75
+ def session_id_from_context(context_id: Optional[str]) -> str:
76
+ """§7.2: A2A context_id ↔ Runtime session_id。"""
77
+ return str(context_id or "")
78
+
79
+ def handle_for_task(
80
+ self,
81
+ *,
82
+ task_id: str,
83
+ invocation_id: str,
84
+ session_id: str,
85
+ native_ref: Optional[dict[str, Any]] = None,
86
+ ) -> RunHandle:
87
+ """从 Task metadata 还原 Runtime 句柄(run_id = invocation_id)。"""
88
+ return RunHandle(
89
+ run_id=invocation_id or task_id,
90
+ session_id=session_id,
91
+ runtime_type=self._runtime_type,
92
+ native_ref=native_ref or {},
93
+ )
94
+
95
+ # ---- cancel:走 RuntimeAdapter.cancel ----
96
+
97
+ async def cancel_task(self, task_id: str, context: Any) -> CancelResult:
98
+ """取消 A2A task → RuntimeAdapter.cancel。
99
+
100
+ 只使用 ``start`` 返回的进程内真实 handle,或 Runtime-local resume store 中
101
+ 持久化的 handle;找不到时返回 NOT_RUNNING,不按 task_id 构造假 handle。
102
+ """
103
+ await self.prepare_context(context)
104
+ task_key = self._task_key(task_id, context)
105
+ handle = self._handles_by_task_key.get(task_key)
106
+ restored = handle is None
107
+ if handle is None:
108
+ handle = await self._restore_handle_from_store(task_id, context)
109
+ if handle is None or not self._handle_matches_context(handle, context):
110
+ return CancelResult.NOT_RUNNING
111
+ try:
112
+ if restored:
113
+ await self._adapter.attach(handle)
114
+ result = await self._adapter.cancel(handle)
115
+ except Exception as exc: # noqa: BLE001
116
+ logger.error("A2A runtime cancel failed (%s)", type(exc).__name__)
117
+ return CancelResult.FAILED
118
+ if result in {
119
+ CancelResult.INTERRUPTED_ACTIVE_TURN,
120
+ CancelResult.PENDING_CANCEL_RECORDED,
121
+ }:
122
+ self._accepted_canceled_tasks.add(task_key)
123
+ return result
124
+
125
+ # ---- input-required → checkpoint/resume token ----
126
+
127
+ def build_resume_target(self, *, invocation_id: str) -> ResumeTarget:
128
+ """input-required 的恢复目标(精确到 invocation)。"""
129
+ return ResumeTarget(kind="invocation_id", id=invocation_id)
130
+
131
+ def build_resume_payload(self, *, call_id: str | None, answer: Any) -> ResumePayload:
132
+ """input-required 的回包(HITL 回答 / 审批决定)。"""
133
+ return ResumePayload(kind="hitl_answer", call_id=call_id, data=answer)
134
+
135
+ async def start_task(
136
+ self,
137
+ *,
138
+ task_id: str,
139
+ context: Any,
140
+ input_data: Any,
141
+ ) -> RunHandle:
142
+ """通过 RuntimeAdapter 启动 A2A task,返回后续共用的真实 handle。"""
143
+ await self.prepare_context(context)
144
+ context_metadata = self._as_dict(getattr(context, "metadata", None))
145
+ context_metadata.pop("user_id", None)
146
+ context_metadata.pop("agent_id", None)
147
+ tenant = self._trusted_tenant(context)
148
+ handle = await self._adapter.start(
149
+ StartRequest(
150
+ input=input_data,
151
+ user_id=tenant,
152
+ session_id=self._extract_session_id(context),
153
+ agent_id=None,
154
+ metadata={**context_metadata, "invocation_id": task_id},
155
+ )
156
+ )
157
+ self._handles_by_task_key[self._task_key(task_id, context)] = handle
158
+ return handle
159
+
160
+ async def persist_resume_state(
161
+ self,
162
+ *,
163
+ task_id: str,
164
+ context: Any,
165
+ handle: RunHandle,
166
+ checkpoint_id: str | None,
167
+ call_id: str | None,
168
+ payload_kind: A2AResumePayloadKind,
169
+ ) -> None:
170
+ """Store input-required recovery state locally, never in A2A wire metadata."""
171
+ if checkpoint_id:
172
+ handle.native_ref["checkpoint_id"] = checkpoint_id
173
+ known_checkpoint_ids = handle.native_ref.setdefault("known_checkpoint_ids", [])
174
+ if checkpoint_id not in known_checkpoint_ids:
175
+ known_checkpoint_ids.append(checkpoint_id)
176
+ if call_id:
177
+ pending_approval_ids = handle.native_ref.setdefault("pending_approval_ids", [])
178
+ if call_id not in pending_approval_ids:
179
+ pending_approval_ids.append(call_id)
180
+ target = ResumeTarget(
181
+ kind="checkpoint_id" if checkpoint_id else "invocation_id",
182
+ id=str(checkpoint_id or handle.run_id),
183
+ )
184
+ await self._resume_state_store.put(
185
+ self._resume_key(task_id, context),
186
+ A2AResumeState(
187
+ handle=handle,
188
+ target=target,
189
+ payload_kind=payload_kind,
190
+ call_id=call_id,
191
+ ),
192
+ )
193
+
194
+ async def resume_task(
195
+ self,
196
+ task_id: str,
197
+ context: Any,
198
+ *,
199
+ answer: Any,
200
+ ) -> RunHandle:
201
+ """Resume from Runtime-local state associated with the protocol Task."""
202
+ await self.prepare_context(context)
203
+ handle, target, payload = await self.validate_resume_task(
204
+ task_id,
205
+ context,
206
+ answer=answer,
207
+ )
208
+ task_key = self._task_key(task_id, context)
209
+ local_handle = self._handles_by_task_key.get(task_key)
210
+ if local_handle is None:
211
+ attached_handle = await self._adapter.attach(handle)
212
+ if attached_handle != handle:
213
+ raise ValueError("RuntimeAdapter.attach returned a different run handle")
214
+ elif local_handle != handle:
215
+ raise ValueError("persisted run_handle conflicts with the active task handle")
216
+ resumed_handle = await self._adapter.resume(handle, target, payload)
217
+ if resumed_handle != handle:
218
+ raise ValueError("RuntimeAdapter.resume returned a different run handle")
219
+ self._handles_by_task_key[task_key] = resumed_handle
220
+ return resumed_handle
221
+
222
+ async def validate_resume_task(
223
+ self,
224
+ task_id: str,
225
+ context: Any,
226
+ *,
227
+ answer: Any,
228
+ ) -> tuple[RunHandle, ResumeTarget, ResumePayload]:
229
+ """Validate an A2A resume command without changing task/runtime state."""
230
+ task = getattr(context, "current_task", None)
231
+ if str(getattr(task, "id", "") or "") != task_id:
232
+ raise ValueError("resume token does not belong to the requested task")
233
+ state = await self._resume_state_store.get(self._resume_key(task_id, context))
234
+ if state is None:
235
+ raise ValueError("input-required task has no Runtime-local resume state")
236
+ handle = state.handle
237
+ target = state.target
238
+ if target.kind not in {"checkpoint_id", "invocation_id"} or not target.id:
239
+ raise ValueError("invalid resume_target in Runtime-local resume state")
240
+ payload = self._resume_payload(state, answer=answer)
241
+ if not self._handle_matches_context(handle, context):
242
+ raise ValueError("run_handle does not match A2A context or task adapter")
243
+ return handle, target, payload
244
+
245
+ def stream_task(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
246
+ """恢复后订阅同一个 RuntimeAdapter 的同一 handle。"""
247
+ return self._adapter.stream(handle)
248
+
249
+ def was_cancel_accepted(self, task_id: str, context: Any, handle: RunHandle) -> bool:
250
+ """Return whether cancel was accepted for this exact runtime handle."""
251
+ task_key = self._task_key(task_id, context)
252
+ return (
253
+ self._handles_by_task_key.get(task_key) == handle
254
+ and task_key in self._accepted_canceled_tasks
255
+ )
256
+
257
+ async def forget_task(self, task_id: str, context: Any, handle: RunHandle) -> None:
258
+ """Release process-local tracking after a terminal task state."""
259
+ task_key = self._task_key(task_id, context)
260
+ if self._handles_by_task_key.get(task_key) == handle:
261
+ self._handles_by_task_key.pop(task_key, None)
262
+ self._accepted_canceled_tasks.discard(task_key)
263
+ await self.clear_resume_state(task_id, context)
264
+
265
+ async def clear_resume_state(self, task_id: str, context: Any) -> None:
266
+ """Discard private recovery material while preserving active cancel bookkeeping."""
267
+
268
+ await self._resume_state_store.delete(self._resume_key(task_id, context))
269
+
270
+ @staticmethod
271
+ def answer_from_context(context: Any) -> Any:
272
+ """优先读取 A2A ``Part.data``,保留 false/0/空串/null。"""
273
+ message = getattr(context, "message", None)
274
+ for part in getattr(message, "parts", ()) or ():
275
+ data = getattr(part, "data", None)
276
+ which_oneof = getattr(data, "WhichOneof", None)
277
+ if (
278
+ not isinstance(data, ProtobufMessage)
279
+ or not callable(which_oneof)
280
+ or which_oneof("kind") is None
281
+ ):
282
+ continue
283
+ converted = MessageToDict(data, preserving_proto_field_name=True)
284
+ if isinstance(converted, Mapping):
285
+ normalized = dict(converted)
286
+ if set(normalized) == {"value"}:
287
+ return normalized["value"]
288
+ return normalized
289
+ return converted
290
+ return context.get_user_input()
291
+
292
+ # ---- metadata 提取 ----
293
+
294
+ @staticmethod
295
+ def _extract_session_id(context: Any) -> str:
296
+ call_context = getattr(context, "call_context", None)
297
+ state = getattr(call_context, "state", None)
298
+ if isinstance(state, Mapping):
299
+ internal_session_id = str(state.get("a2a_internal_session_id") or "")
300
+ if internal_session_id:
301
+ return internal_session_id
302
+ # A follow-up A2A message may carry only task_id. The durable Task is the
303
+ # authority for the original context/session across requests and restarts.
304
+ current_task = getattr(context, "current_task", None)
305
+ task_context_id = getattr(current_task, "context_id", None)
306
+ return str(task_context_id or getattr(context, "context_id", "") or "")
307
+
308
+ async def prepare_context(self, context: Any) -> str:
309
+ """Resolve the verified external context before adapter state is accessed."""
310
+
311
+ if self._context_store is None:
312
+ return self._extract_session_id(context)
313
+ call_context = getattr(context, "call_context", None)
314
+ state = getattr(call_context, "state", None)
315
+ if not isinstance(state, dict):
316
+ raise PermissionError("verified Gateway identity is required for A2A context mapping")
317
+ cached = str(state.get("a2a_internal_session_id") or "")
318
+ if cached:
319
+ return cached
320
+ identity = state.get("a2a_identity")
321
+ if not isinstance(identity, A2AIngressIdentity):
322
+ raise PermissionError("verified Gateway identity is required for A2A context mapping")
323
+ current_task = getattr(context, "current_task", None)
324
+ external_context_id = str(
325
+ getattr(current_task, "context_id", "")
326
+ or getattr(context, "context_id", "")
327
+ or getattr(context, "task_id", "")
328
+ or ""
329
+ )
330
+ if current_task is not None:
331
+ internal_session_id = await self._context_store.get(
332
+ identity.context_identity(),
333
+ external_context_id,
334
+ )
335
+ if internal_session_id is None:
336
+ raise RuntimeError("A2A_CONTEXT_MAPPING_NOT_FOUND")
337
+ else:
338
+ internal_session_id = await self._context_store.resolve_or_create(
339
+ identity.context_identity(),
340
+ external_context_id,
341
+ )
342
+ state["a2a_internal_session_id"] = internal_session_id
343
+ return internal_session_id
344
+
345
+ @staticmethod
346
+ def _trusted_tenant(context: Any) -> str:
347
+ call_context = getattr(context, "call_context", None)
348
+ return str(getattr(call_context, "tenant", "") or "anonymous")
349
+
350
+ def _task_key(self, task_id: str, context: Any) -> tuple[str, str, str]:
351
+ return (
352
+ self._trusted_tenant(context),
353
+ self._extract_session_id(context),
354
+ task_id,
355
+ )
356
+
357
+ async def _restore_handle_from_store(self, task_id: str, context: Any) -> RunHandle | None:
358
+ task = getattr(context, "current_task", None)
359
+ if str(getattr(task, "id", "") or "") != task_id:
360
+ return None
361
+ state = await self._resume_state_store.get(self._resume_key(task_id, context))
362
+ return state.handle if state is not None else None
363
+
364
+ def _handle_matches_context(self, handle: RunHandle, context: Any) -> bool:
365
+ return (
366
+ handle.session_id == self._extract_session_id(context)
367
+ and handle.runtime_type == self._runtime_type
368
+ )
369
+
370
+ def _resume_key(self, task_id: str, context: Any) -> str:
371
+ tenant, session_id, normalized_task_id = self._task_key(task_id, context)
372
+ return "\x1f".join((tenant, session_id, normalized_task_id))
373
+
374
+ @classmethod
375
+ def _resume_payload(cls, state: A2AResumeState, *, answer: Any) -> ResumePayload:
376
+ data = (
377
+ cls._approval_decision(answer)
378
+ if state.payload_kind == "approval_decision"
379
+ else answer
380
+ )
381
+ return ResumePayload(kind=state.payload_kind, call_id=state.call_id, data=data)
382
+
383
+ @staticmethod
384
+ def _approval_decision(answer: Any) -> dict[str, list[dict[str, Any]]]:
385
+ if isinstance(answer, Mapping):
386
+ if "decisions" in answer:
387
+ decisions = answer.get("decisions")
388
+ if not isinstance(decisions, list) or not decisions:
389
+ raise ValueError("unknown approval decision")
390
+ normalized = [dict(item) for item in decisions if isinstance(item, Mapping)]
391
+ if len(normalized) != len(decisions):
392
+ raise ValueError("unknown approval decision")
393
+ if any(
394
+ str(item.get("type") or "").strip().lower() not in {"approve", "edit", "reject"}
395
+ for item in normalized
396
+ ):
397
+ raise ValueError("unknown approval decision")
398
+ return {"decisions": normalized}
399
+ decision = dict(answer)
400
+ decision_type = str(decision.get("type") or "").strip().lower()
401
+ if decision_type not in {"approve", "edit", "reject"}:
402
+ raise ValueError("unknown approval decision")
403
+ decision["type"] = decision_type
404
+ return {"decisions": [decision]}
405
+ token = str(answer).strip().lower() if answer is not None else ""
406
+ if token not in {"approve", "edit", "reject"}:
407
+ raise ValueError(f"unknown approval decision: {answer!r}")
408
+ return {"decisions": [{"type": token}]}
409
+
410
+ @staticmethod
411
+ def _as_dict(value: Any) -> dict[str, Any]:
412
+ descriptor = getattr(value, "DESCRIPTOR", None)
413
+ if descriptor is not None:
414
+ converted = MessageToDict(value, preserving_proto_field_name=True)
415
+ return dict(converted) if isinstance(converted, Mapping) else {}
416
+ if isinstance(value, Mapping):
417
+ return dict(value)
418
+ if value is None:
419
+ return {}
420
+ return {}
421
+
422
+
423
+ __all__ = ["A2ARuntimeTaskAdapter"]
@@ -0,0 +1,167 @@
1
+ """Runtime-shared durable dispatcher for AgentEngine A2A task events."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from typing import Any, Protocol
8
+
9
+ from ksadk.a2a.task_event_outbox import A2ATaskEventOutbox
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class A2ATaskEventSink(Protocol):
15
+ async def append_task_events(
16
+ self,
17
+ *,
18
+ platform_task_id: str,
19
+ events: list[dict[str, Any]],
20
+ ) -> dict[str, Any]: ...
21
+
22
+
23
+ class A2ATaskEventDispatcher:
24
+ """One Runtime-owned outbox dispatcher shared by all Space clients."""
25
+
26
+ def __init__(
27
+ self,
28
+ outbox: A2ATaskEventOutbox,
29
+ task_sink: A2ATaskEventSink,
30
+ *,
31
+ retry_interval_seconds: float = 1.0,
32
+ task_sink_timeout_seconds: float = 5.0,
33
+ ) -> None:
34
+ if retry_interval_seconds <= 0:
35
+ raise ValueError("retry_interval_seconds must be positive")
36
+ if task_sink_timeout_seconds <= 0:
37
+ raise ValueError("task_sink_timeout_seconds must be positive")
38
+ self._outbox = outbox
39
+ self._task_sink = task_sink
40
+ self._retry_interval_seconds = retry_interval_seconds
41
+ self._task_sink_timeout_seconds = task_sink_timeout_seconds
42
+ self._drain_lock = asyncio.Lock()
43
+ self._stop_event = asyncio.Event()
44
+ self._wake_event = asyncio.Event()
45
+ self._background_task: asyncio.Task[None] | None = None
46
+ self._started = False
47
+ self._last_error: str | None = None
48
+
49
+ @property
50
+ def outbox(self) -> A2ATaskEventOutbox:
51
+ return self._outbox
52
+
53
+ @property
54
+ def degraded(self) -> bool:
55
+ return self._last_error is not None
56
+
57
+ @property
58
+ def last_error(self) -> str | None:
59
+ return self._last_error
60
+
61
+ async def ensure_ready(self) -> None:
62
+ await self._outbox.initialize()
63
+
64
+ async def ensure_writable(self) -> None:
65
+ await self.ensure_ready()
66
+ await self._outbox.ensure_writable()
67
+
68
+ async def start(self) -> None:
69
+ await self.ensure_ready()
70
+ await self.ensure_writable()
71
+ if self._started:
72
+ return
73
+ self._started = True
74
+ self._stop_event.clear()
75
+ self._background_task = asyncio.create_task(
76
+ self._run(),
77
+ name="ksadk-a2a-task-event-dispatcher",
78
+ )
79
+ self._wake_event.set()
80
+
81
+ async def stop(self, *, flush_timeout_seconds: float = 5.0) -> None:
82
+ if flush_timeout_seconds < 0:
83
+ raise ValueError("flush_timeout_seconds cannot be negative")
84
+ self._stop_event.set()
85
+ task = self._background_task
86
+ self._background_task = None
87
+ self._started = False
88
+ if task is not None:
89
+ task.cancel()
90
+ try:
91
+ await task
92
+ except asyncio.CancelledError:
93
+ pass
94
+ if flush_timeout_seconds == 0:
95
+ return
96
+ try:
97
+ await asyncio.wait_for(
98
+ self.drain(raise_on_error=False),
99
+ timeout=flush_timeout_seconds,
100
+ )
101
+ except TimeoutError:
102
+ logger.warning("A2A task event outbox flush timed out; pending batches were retained")
103
+
104
+ async def enqueue(
105
+ self,
106
+ *,
107
+ platform_task_id: str,
108
+ events: list[dict[str, Any]],
109
+ ) -> str:
110
+ await self.ensure_writable()
111
+ batch_id = str(
112
+ await self._outbox.enqueue(
113
+ platform_task_id=platform_task_id,
114
+ events=events,
115
+ )
116
+ )
117
+ self._wake_event.set()
118
+ return batch_id
119
+
120
+ async def drain(self, *, raise_on_error: bool) -> int:
121
+ await self.ensure_ready()
122
+ delivered = 0
123
+ async with self._drain_lock:
124
+ while True:
125
+ batches = await self._outbox.pending(limit=100)
126
+ if not batches:
127
+ self._last_error = None
128
+ return delivered
129
+ for batch in batches:
130
+ try:
131
+ await asyncio.wait_for(
132
+ self._task_sink.append_task_events(
133
+ platform_task_id=batch.platform_task_id,
134
+ events=batch.events,
135
+ ),
136
+ timeout=self._task_sink_timeout_seconds,
137
+ )
138
+ except Exception as exc:
139
+ error = str(getattr(exc, "error_code", "") or type(exc).__name__)
140
+ self._last_error = error
141
+ await self._outbox.record_failure(batch.batch_id, error)
142
+ if raise_on_error:
143
+ raise
144
+ logger.warning(
145
+ "A2A task event batch remains in local outbox: task=%s batch=%s",
146
+ batch.platform_task_id,
147
+ batch.batch_id,
148
+ )
149
+ return delivered
150
+ await self._outbox.acknowledge(batch.batch_id)
151
+ delivered += 1
152
+
153
+ async def _run(self) -> None:
154
+ while not self._stop_event.is_set():
155
+ await self.drain(raise_on_error=False)
156
+ try:
157
+ await asyncio.wait_for(
158
+ self._wake_event.wait(),
159
+ timeout=self._retry_interval_seconds,
160
+ )
161
+ except TimeoutError:
162
+ pass
163
+ finally:
164
+ self._wake_event.clear()
165
+
166
+
167
+ __all__ = ["A2ATaskEventDispatcher", "A2ATaskEventSink"]