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,1101 @@
1
+ """Space-scoped A2A discovery and authorized data-plane calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import logging
8
+ import os
9
+ import uuid
10
+ from collections.abc import AsyncIterator, Mapping
11
+ from contextlib import AsyncExitStack, asynccontextmanager
12
+ from dataclasses import dataclass
13
+ from datetime import datetime, timezone
14
+ from http.cookies import CookieError, SimpleCookie
15
+ from typing import Any
16
+ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
17
+
18
+ import httpx
19
+ from a2a.client import ClientCallContext, ClientConfig, create_client
20
+ from a2a.types import (
21
+ AgentCard,
22
+ CancelTaskRequest,
23
+ GetTaskRequest,
24
+ Message,
25
+ Part,
26
+ Role,
27
+ SendMessageConfiguration,
28
+ SendMessageRequest,
29
+ SubscribeToTaskRequest,
30
+ TaskState,
31
+ )
32
+ from google.protobuf.json_format import MessageToDict, ParseDict
33
+
34
+ from ksadk.a2a.control_plane import (
35
+ ENV_A2A_CONTROL_PLANE_URL,
36
+ A2AControlPlane,
37
+ A2ARouteInterface,
38
+ CredentialInjection,
39
+ DiscoveredAgent,
40
+ InternalA2AControlPlaneClient,
41
+ PreparedA2AOperation,
42
+ SpaceAgentPage,
43
+ )
44
+ from ksadk.a2a.event_adapter import A2AEventAdapter
45
+ from ksadk.a2a.external_transport import A2AExternalTransport
46
+ from ksadk.a2a.ids import require_a2a_resource_id
47
+ from ksadk.a2a.task_event_dispatcher import A2ATaskEventDispatcher
48
+ from ksadk.a2a.task_event_outbox import (
49
+ A2ATaskEventOutbox,
50
+ InMemoryA2ATaskEventOutbox,
51
+ SQLiteA2ATaskEventOutbox,
52
+ )
53
+ from ksadk.events.runtime_event import RuntimeEvent
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ ENV_A2A_SPACE_IDS = "KSADK_A2A_SPACE_IDS"
58
+ ENV_A2A_ENABLE_PUBLIC_EGRESS = "KSADK_A2A_ENABLE_PUBLIC_EGRESS"
59
+
60
+ ERR_PUBLIC_EGRESS_DISABLED = "A2A_PUBLIC_EGRESS_DISABLED"
61
+ MAX_A2A_MESSAGE_BYTES = 1024 * 1024
62
+ MAX_A2A_MESSAGE_PARTS = 64
63
+ MAX_A2A_MESSAGE_ID_LENGTH = 128
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class A2APlatformTask:
68
+ """AgentEngine task locator with an optional latest remote A2A snapshot."""
69
+
70
+ id: str
71
+ remote_task: Any | None = None
72
+
73
+
74
+ def _utc_now() -> str:
75
+ return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
76
+
77
+
78
+ def _canonical_proto(value: Any) -> dict[str, Any]:
79
+ return MessageToDict(value, preserving_proto_field_name=False)
80
+
81
+
82
+ def _canonical_sha256(value: Any) -> str:
83
+ payload = _canonical_proto(value)
84
+ encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
85
+ return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
86
+
87
+
88
+ def _present_message_field(value: Any, field_name: str) -> Any | None:
89
+ has_field = getattr(value, "HasField", None)
90
+ if callable(has_field):
91
+ try:
92
+ if not has_field(field_name):
93
+ return None
94
+ except ValueError:
95
+ pass
96
+ return getattr(value, field_name, None)
97
+
98
+
99
+ class A2ASpaceClient:
100
+ """Discovers Space members and performs permit-authorized A2A calls."""
101
+
102
+ def __init__(
103
+ self,
104
+ space_id: str,
105
+ backend: A2AControlPlane,
106
+ *,
107
+ egress_enabled: bool = False,
108
+ httpx_client: httpx.AsyncClient | None = None,
109
+ external_transport: A2AExternalTransport | None = None,
110
+ event_sink: Any | None = None,
111
+ event_outbox: A2ATaskEventOutbox | None = None,
112
+ event_dispatcher: A2ATaskEventDispatcher | None = None,
113
+ ) -> None:
114
+ require_a2a_resource_id(space_id, "a2a-space-", field_name="space_id")
115
+ if external_transport is not None and not isinstance(
116
+ external_transport, A2AExternalTransport
117
+ ):
118
+ raise TypeError("external_transport must implement A2AExternalTransport")
119
+ self._space_id = space_id
120
+ self._backend = backend
121
+ self._egress_enabled = egress_enabled
122
+ self._httpx_client = httpx_client
123
+ self._external_transport = external_transport
124
+ self._event_sink = event_sink
125
+ if event_dispatcher is not None and event_outbox is not None:
126
+ raise ValueError("pass either event_dispatcher or event_outbox, not both")
127
+ self._owns_event_dispatcher = event_dispatcher is None
128
+ self._event_dispatcher = event_dispatcher or A2ATaskEventDispatcher(
129
+ event_outbox or InMemoryA2ATaskEventOutbox(),
130
+ backend,
131
+ )
132
+ self._event_adapter = A2AEventAdapter()
133
+ self._agents_by_id: dict[str, DiscoveredAgent] = {}
134
+ self._agents_by_task: dict[str, DiscoveredAgent] = {}
135
+ self._seq = 0
136
+ self._persisted_wire_events: set[str] = set()
137
+
138
+ @classmethod
139
+ def from_env(
140
+ cls,
141
+ *,
142
+ space_id: str | None = None,
143
+ backend: A2AControlPlane | None = None,
144
+ httpx_client: httpx.AsyncClient | None = None,
145
+ external_transport: A2AExternalTransport | None = None,
146
+ egress_enabled: bool | None = None,
147
+ event_sink: Any | None = None,
148
+ event_outbox: A2ATaskEventOutbox | None = None,
149
+ event_dispatcher: A2ATaskEventDispatcher | None = None,
150
+ ) -> "A2ASpaceClient":
151
+ selected_space_id = str(space_id or "").strip()
152
+ if selected_space_id:
153
+ require_a2a_resource_id(
154
+ selected_space_id,
155
+ "a2a-space-",
156
+ field_name="space_id",
157
+ )
158
+ else:
159
+ raw_space_ids = str(os.getenv(ENV_A2A_SPACE_IDS) or "").strip()
160
+ if not raw_space_ids:
161
+ raise ValueError(
162
+ f"missing {ENV_A2A_SPACE_IDS}; pass space_id or add the Runtime Agent "
163
+ "to an A2A Space first"
164
+ )
165
+ try:
166
+ configured_space_ids = json.loads(raw_space_ids)
167
+ except json.JSONDecodeError as exc:
168
+ raise ValueError(f"{ENV_A2A_SPACE_IDS} must be a JSON string array") from exc
169
+ if not isinstance(configured_space_ids, list) or not configured_space_ids:
170
+ raise ValueError(f"{ENV_A2A_SPACE_IDS} must be a non-empty JSON string array")
171
+ if len(configured_space_ids) > 100:
172
+ raise ValueError(f"{ENV_A2A_SPACE_IDS} cannot contain more than 100 Space IDs")
173
+ normalized_space_ids: list[str] = []
174
+ for index, configured_space_id in enumerate(configured_space_ids):
175
+ if not isinstance(configured_space_id, str):
176
+ raise ValueError(f"{ENV_A2A_SPACE_IDS}[{index}] must be an A2A Space ID string")
177
+ normalized = configured_space_id.strip()
178
+ require_a2a_resource_id(
179
+ normalized,
180
+ "a2a-space-",
181
+ field_name=f"{ENV_A2A_SPACE_IDS}[{index}]",
182
+ )
183
+ normalized_space_ids.append(normalized)
184
+ if len(set(normalized_space_ids)) != len(normalized_space_ids):
185
+ raise ValueError(f"{ENV_A2A_SPACE_IDS} must not contain duplicate Space IDs")
186
+ if len(normalized_space_ids) != 1:
187
+ raise ValueError(
188
+ f"{ENV_A2A_SPACE_IDS} contains multiple Space IDs; pass space_id explicitly"
189
+ )
190
+ selected_space_id = normalized_space_ids[0]
191
+ if backend is None:
192
+ control_plane_url = str(os.getenv(ENV_A2A_CONTROL_PLANE_URL) or "").strip()
193
+ if not control_plane_url:
194
+ raise ValueError(f"missing {ENV_A2A_CONTROL_PLANE_URL}")
195
+ backend = InternalA2AControlPlaneClient(
196
+ control_plane_url,
197
+ httpx_client=httpx_client,
198
+ )
199
+ if egress_enabled is None:
200
+ raw_egress = os.getenv(ENV_A2A_ENABLE_PUBLIC_EGRESS) or ""
201
+ egress_enabled = raw_egress.strip().lower() in {"1", "true", "yes", "on"}
202
+ if event_outbox is None and event_dispatcher is None:
203
+ event_outbox = SQLiteA2ATaskEventOutbox()
204
+ return cls(
205
+ selected_space_id,
206
+ backend,
207
+ egress_enabled=egress_enabled,
208
+ httpx_client=httpx_client,
209
+ external_transport=external_transport,
210
+ event_sink=event_sink,
211
+ event_outbox=event_outbox,
212
+ event_dispatcher=event_dispatcher,
213
+ )
214
+
215
+ async def discover(
216
+ self,
217
+ prompt: str | None = None,
218
+ *,
219
+ skill: str | None = None,
220
+ include_blocked: bool = False,
221
+ ) -> list[DiscoveredAgent]:
222
+ page = await self._backend.list_space_agents(
223
+ self._space_id,
224
+ prompt=prompt,
225
+ skill_id=skill,
226
+ include_blocked=include_blocked,
227
+ )
228
+ for agent in page.agents:
229
+ require_a2a_resource_id(
230
+ agent.agent_id,
231
+ "a2a-agent-",
232
+ field_name="DiscoveredAgent.agent_id",
233
+ )
234
+ require_a2a_resource_id(
235
+ agent.version_id,
236
+ "a2a-version-",
237
+ field_name="DiscoveredAgent.version_id",
238
+ )
239
+ self._agents_by_id[agent.agent_id] = agent
240
+ return page.agents
241
+
242
+ def _check_egress(self, agent: DiscoveredAgent) -> None:
243
+ if agent.route_kind == "external_public" and not self._egress_enabled:
244
+ raise PermissionError(
245
+ f"{ERR_PUBLIC_EGRESS_DISABLED}: external Agent {agent.agent_id} requires "
246
+ "Network.EnablePublicAccess"
247
+ )
248
+
249
+ @property
250
+ def event_dispatcher(self) -> A2ATaskEventDispatcher:
251
+ """Runtime-scoped task event dispatcher used by this client."""
252
+
253
+ return self._event_dispatcher
254
+
255
+ async def aclose(self, *, flush_timeout_seconds: float = 5.0) -> None:
256
+ """Stop the dispatcher only when this standalone client created it."""
257
+
258
+ if self._owns_event_dispatcher:
259
+ await self._event_dispatcher.stop(flush_timeout_seconds=flush_timeout_seconds)
260
+
261
+ async def __aenter__(self) -> "A2ASpaceClient":
262
+ return self
263
+
264
+ async def __aexit__(self, *args: Any) -> None:
265
+ await self.aclose()
266
+
267
+ async def _resolve_agent(self, agent_id: str) -> DiscoveredAgent:
268
+ agent = self._agents_by_id.get(agent_id)
269
+ if agent is None:
270
+ await self.discover()
271
+ agent = self._agents_by_id.get(agent_id)
272
+ if agent is None:
273
+ raise KeyError(f"Agent {agent_id!r} is not discoverable in Space {self._space_id}")
274
+ self._check_egress(agent)
275
+ return agent
276
+
277
+ async def send_message(
278
+ self,
279
+ agent_id: str,
280
+ message: str | Message,
281
+ *,
282
+ return_immediately: bool = False,
283
+ idempotency_token: str | None = None,
284
+ ) -> A2APlatformTask:
285
+ agent = await self._resolve_agent(agent_id)
286
+ normalized = self._normalize_initial_message(message)
287
+ prepared = await self._backend.prepare_call(
288
+ space_id=self._space_id,
289
+ target_agent_id=agent.agent_id,
290
+ expected_version_id=agent.version_id,
291
+ message_id=normalized.message_id,
292
+ message_sha256=_canonical_sha256(normalized),
293
+ idempotency_token=idempotency_token or f"idem-{uuid.uuid4().hex}",
294
+ )
295
+ self._validate_prepared_target(agent, prepared)
296
+ handle = await self._send_prepared_message(
297
+ prepared,
298
+ agent,
299
+ normalized,
300
+ return_immediately=return_immediately,
301
+ )
302
+ self._agents_by_task[prepared.platform_task_id] = agent
303
+ await self._record_agent_for_task(prepared.platform_task_id, agent)
304
+ return handle
305
+
306
+ async def continue_task(
307
+ self,
308
+ task_id: str,
309
+ message: str | Message,
310
+ *,
311
+ return_immediately: bool = False,
312
+ idempotency_token: str | None = None,
313
+ ) -> A2APlatformTask:
314
+ require_a2a_resource_id(task_id, "a2a-task-", field_name="task_id")
315
+ normalized = self._normalize_initial_message(message)
316
+ prepared = await self._backend.prepare_task_operation(
317
+ platform_task_id=task_id,
318
+ operation="send_message",
319
+ message_id=normalized.message_id,
320
+ message_sha256=_canonical_sha256(normalized),
321
+ idempotency_token=idempotency_token or f"idem-{uuid.uuid4().hex}",
322
+ )
323
+ self._validate_prepared_ids(prepared)
324
+ remote_task = self._require_remote_task(prepared)
325
+ normalized.task_id = remote_task.remote_task_id
326
+ if remote_task.remote_context_id:
327
+ normalized.context_id = remote_task.remote_context_id
328
+ agent = self._agent_from_prepared(prepared)
329
+ return await self._send_prepared_message(
330
+ prepared,
331
+ agent,
332
+ normalized,
333
+ return_immediately=return_immediately,
334
+ )
335
+
336
+ async def _send_prepared_message(
337
+ self,
338
+ prepared: PreparedA2AOperation,
339
+ agent: DiscoveredAgent,
340
+ message: Message,
341
+ *,
342
+ return_immediately: bool,
343
+ ) -> A2APlatformTask:
344
+ first_task = None
345
+ remote_task_id = prepared.remote_task.remote_task_id if prepared.remote_task else None
346
+ remote_context_id = prepared.remote_task.remote_context_id if prepared.remote_task else None
347
+ operation_instance_id = self._operation_instance_id(prepared)
348
+ async with self._operation_client(agent, prepared) as (client, context):
349
+ request = SendMessageRequest(
350
+ message=message,
351
+ configuration=SendMessageConfiguration(return_immediately=return_immediately),
352
+ )
353
+ wire_position = 0
354
+ async for response in client.send_message(request, context=context):
355
+ response_task = _present_message_field(response, "task")
356
+ if response_task is not None and str(getattr(response_task, "id", None) or ""):
357
+ first_task = first_task or response_task
358
+ observed_task_id = str(response_task.id)
359
+ observed_context_id = str(response_task.context_id or "") or None
360
+ if remote_task_id is None:
361
+ await self._bind_task(prepared.platform_task_id, response_task)
362
+ elif (remote_task_id, remote_context_id) != (
363
+ observed_task_id,
364
+ observed_context_id,
365
+ ):
366
+ raise RuntimeError("A2A_REMOTE_BINDING_CONFLICT")
367
+ remote_task_id = observed_task_id
368
+ remote_context_id = observed_context_id
369
+ if remote_task_id is not None:
370
+ self._validate_remote_task_observation(
371
+ remote_task_id,
372
+ remote_context_id,
373
+ response,
374
+ )
375
+ await self._project_stream_item(
376
+ prepared.platform_task_id,
377
+ response,
378
+ agent,
379
+ wire_position=wire_position,
380
+ operation_instance_id=operation_instance_id,
381
+ )
382
+ wire_position += 1
383
+ if return_immediately and first_task is not None:
384
+ break
385
+ return A2APlatformTask(
386
+ id=prepared.platform_task_id,
387
+ remote_task=first_task,
388
+ )
389
+
390
+ async def subscribe(self, task_id: str):
391
+ require_a2a_resource_id(task_id, "a2a-task-", field_name="task_id")
392
+ prepared = await self._backend.prepare_task_operation(
393
+ platform_task_id=task_id,
394
+ operation="subscribe_to_task",
395
+ )
396
+ self._validate_prepared_ids(prepared)
397
+ agent = self._agent_from_prepared(prepared)
398
+ async for item, _ in self._iter_subscription(prepared, agent):
399
+ yield item
400
+
401
+ async def _iter_subscription(
402
+ self,
403
+ prepared: PreparedA2AOperation,
404
+ agent: DiscoveredAgent,
405
+ ):
406
+ remote_task = self._require_remote_task(prepared)
407
+ operation_instance_id = self._operation_instance_id(prepared)
408
+ async with self._operation_client(agent, prepared) as (client, context):
409
+ wire_position = 0
410
+ async for event in client.subscribe(
411
+ SubscribeToTaskRequest(id=remote_task.remote_task_id),
412
+ context=context,
413
+ ):
414
+ self._validate_remote_task_observation(
415
+ remote_task.remote_task_id,
416
+ remote_task.remote_context_id,
417
+ event,
418
+ )
419
+ persisted = await self._project_stream_item(
420
+ prepared.platform_task_id,
421
+ event,
422
+ agent,
423
+ wire_position=wire_position,
424
+ operation_instance_id=operation_instance_id,
425
+ )
426
+ wire_position += 1
427
+ yield event, persisted
428
+
429
+ async def cancel(
430
+ self,
431
+ task_id: str,
432
+ *,
433
+ idempotency_token: str | None = None,
434
+ ) -> A2APlatformTask:
435
+ require_a2a_resource_id(task_id, "a2a-task-", field_name="task_id")
436
+ prepared = await self._backend.prepare_task_operation(
437
+ platform_task_id=task_id,
438
+ operation="cancel_task",
439
+ idempotency_token=idempotency_token or f"idem-{uuid.uuid4().hex}",
440
+ )
441
+ self._validate_prepared_ids(prepared)
442
+ remote_task_ref = self._require_remote_task(prepared)
443
+ agent = self._agent_from_prepared(prepared)
444
+ async with self._operation_client(agent, prepared) as (client, context):
445
+ remote_task = await client.cancel_task(
446
+ CancelTaskRequest(id=remote_task_ref.remote_task_id), context=context
447
+ )
448
+ self._validate_remote_task_observation(
449
+ remote_task_ref.remote_task_id,
450
+ remote_task_ref.remote_context_id,
451
+ remote_task,
452
+ )
453
+ await self._project_stream_item(
454
+ task_id,
455
+ remote_task,
456
+ agent,
457
+ wire_position=0,
458
+ operation_instance_id=self._operation_instance_id(prepared),
459
+ )
460
+ return A2APlatformTask(
461
+ id=task_id,
462
+ remote_task=remote_task,
463
+ )
464
+
465
+ async def get_task(self, task_id: str) -> A2APlatformTask:
466
+ require_a2a_resource_id(task_id, "a2a-task-", field_name="task_id")
467
+ prepared = await self._backend.prepare_task_operation(
468
+ platform_task_id=task_id,
469
+ operation="get_task",
470
+ )
471
+ self._validate_prepared_ids(prepared)
472
+ remote_task_ref = self._require_remote_task(prepared)
473
+ agent = self._agent_from_prepared(prepared)
474
+ async with self._operation_client(agent, prepared) as (client, context):
475
+ remote_task = await client.get_task(
476
+ GetTaskRequest(id=remote_task_ref.remote_task_id), context=context
477
+ )
478
+ self._validate_remote_task_observation(
479
+ remote_task_ref.remote_task_id,
480
+ remote_task_ref.remote_context_id,
481
+ remote_task,
482
+ )
483
+ await self._project_stream_item(
484
+ task_id,
485
+ remote_task,
486
+ agent,
487
+ wire_position=0,
488
+ operation_instance_id=self._operation_instance_id(prepared),
489
+ )
490
+ return A2APlatformTask(
491
+ id=task_id,
492
+ remote_task=remote_task,
493
+ )
494
+
495
+ def _normalize_initial_message(self, message: str | Message) -> Message:
496
+ if isinstance(message, str):
497
+ message = Message(
498
+ role=Role.ROLE_USER,
499
+ parts=[Part(text=message)],
500
+ message_id=f"message-{uuid.uuid4().hex}",
501
+ )
502
+ if getattr(message, "task_id", "") or getattr(message, "context_id", ""):
503
+ raise ValueError("caller must not provide remote task_id/context_id")
504
+ if message.role != Role.ROLE_USER:
505
+ raise ValueError("A2A Message role must be user")
506
+ if not 1 <= len(message.parts) <= MAX_A2A_MESSAGE_PARTS:
507
+ raise ValueError("A2A Message parts must contain 1-64 items")
508
+ if not getattr(message, "message_id", ""):
509
+ message.message_id = f"message-{uuid.uuid4().hex}"
510
+ if len(message.message_id) > MAX_A2A_MESSAGE_ID_LENGTH:
511
+ raise ValueError("A2A Message message_id must contain 1-128 characters")
512
+ encoded = json.dumps(
513
+ _canonical_proto(message),
514
+ ensure_ascii=False,
515
+ sort_keys=True,
516
+ separators=(",", ":"),
517
+ ).encode("utf-8")
518
+ if len(encoded) > MAX_A2A_MESSAGE_BYTES:
519
+ raise ValueError("A2A Message canonical JSON exceeds 1 MiB")
520
+ return message
521
+
522
+ @staticmethod
523
+ def _validate_prepared_target(
524
+ agent: DiscoveredAgent,
525
+ prepared: PreparedA2AOperation,
526
+ ) -> None:
527
+ A2ASpaceClient._validate_prepared_ids(prepared)
528
+ if prepared.target.agent_id != agent.agent_id:
529
+ raise RuntimeError("PrepareA2ACall returned a different target Agent")
530
+ if prepared.target.version_id != agent.version_id:
531
+ raise RuntimeError("PrepareA2ACall returned a different target version")
532
+ if agent.card_sha256 and prepared.target.card_sha256 != agent.card_sha256:
533
+ raise RuntimeError("PrepareA2ACall returned a different AgentCard hash")
534
+
535
+ @staticmethod
536
+ def _validate_prepared_ids(prepared: PreparedA2AOperation) -> None:
537
+ require_a2a_resource_id(
538
+ prepared.platform_task_id,
539
+ "a2a-task-",
540
+ field_name="PreparedA2AOperation.platform_task_id",
541
+ )
542
+ require_a2a_resource_id(
543
+ prepared.target.agent_id,
544
+ "a2a-agent-",
545
+ field_name="PreparedA2AOperation.target.agent_id",
546
+ )
547
+ require_a2a_resource_id(
548
+ prepared.target.version_id,
549
+ "a2a-version-",
550
+ field_name="PreparedA2AOperation.target.version_id",
551
+ )
552
+
553
+ @staticmethod
554
+ def _require_remote_task(prepared: PreparedA2AOperation):
555
+ remote_task = prepared.remote_task
556
+ if remote_task is None or not remote_task.remote_task_id:
557
+ raise RuntimeError("A2A_REMOTE_TASK_NOT_BOUND")
558
+ return remote_task
559
+
560
+ @staticmethod
561
+ def _operation_instance_id(prepared: PreparedA2AOperation) -> str:
562
+ return hashlib.sha256(prepared.call_permit.encode("utf-8")).hexdigest()
563
+
564
+ @staticmethod
565
+ def _validate_remote_task_observation(
566
+ expected_task_id: str,
567
+ expected_context_id: str | None,
568
+ item: Any,
569
+ ) -> None:
570
+ task = _present_message_field(item, "task")
571
+ if task is None and hasattr(item, "status") and hasattr(item, "id"):
572
+ task = item
573
+ status_update = _present_message_field(item, "status_update")
574
+ artifact_update = _present_message_field(item, "artifact_update")
575
+ message = _present_message_field(item, "message")
576
+ candidate = task or status_update or artifact_update or message
577
+ if candidate is None:
578
+ return
579
+ observed_task_id = str(
580
+ getattr(candidate, "id", "") or getattr(candidate, "task_id", "") or ""
581
+ )
582
+ observed_context_id = str(getattr(candidate, "context_id", "") or "") or None
583
+ if observed_task_id and observed_task_id != expected_task_id:
584
+ raise RuntimeError("A2A_REMOTE_BINDING_CONFLICT")
585
+ if observed_context_id != expected_context_id:
586
+ raise RuntimeError("A2A_REMOTE_BINDING_CONFLICT")
587
+
588
+ def _agent_from_prepared(self, prepared: PreparedA2AOperation) -> DiscoveredAgent:
589
+ cached = self._agents_by_id.get(prepared.target.agent_id)
590
+ if cached is not None and cached.version_id == prepared.target.version_id:
591
+ return cached
592
+ card = self._route_only_card(prepared.target.agent_id, prepared.target.version_id)
593
+ return DiscoveredAgent(
594
+ agent_id=prepared.target.agent_id,
595
+ version_id=prepared.target.version_id,
596
+ source="hosted" if prepared.route.kind == "hosted_gateway" else "external",
597
+ agent_card=card,
598
+ card_sha256=prepared.target.card_sha256,
599
+ route_kind=prepared.route.kind,
600
+ )
601
+
602
+ def _route_only_card(self, name: str, version: str) -> AgentCard:
603
+ return ParseDict(
604
+ {
605
+ "name": name,
606
+ "description": "AgentEngine prepared A2A route",
607
+ "version": version,
608
+ "supportedInterfaces": [],
609
+ "capabilities": {},
610
+ "defaultInputModes": ["text/plain"],
611
+ "defaultOutputModes": ["text/plain"],
612
+ "skills": [],
613
+ },
614
+ AgentCard(),
615
+ )
616
+
617
+ @asynccontextmanager
618
+ async def _operation_client(
619
+ self,
620
+ agent: DiscoveredAgent,
621
+ prepared: PreparedA2AOperation,
622
+ ) -> AsyncIterator[tuple[Any, ClientCallContext]]:
623
+ # Product bootstrap starts the shared dispatcher during lifespan startup.
624
+ # Standalone/from_env clients own their dispatcher and must start it here.
625
+ await self._event_dispatcher.start()
626
+ injection = CredentialInjection()
627
+ headers: dict[str, str]
628
+ async with AsyncExitStack() as exit_stack:
629
+ if prepared.route.kind == "hosted_gateway":
630
+ headers = {
631
+ "Authorization": f"Bearer {self._backend.gateway_token()}",
632
+ "X-AgentEngine-A2A-Permit": prepared.call_permit,
633
+ }
634
+ http = self._httpx_client
635
+ else:
636
+ if prepared.route.kind == "external_public" and not self._egress_enabled:
637
+ raise PermissionError(ERR_PUBLIC_EGRESS_DISABLED)
638
+ if self._external_transport is None:
639
+ raise RuntimeError(
640
+ "A2A_EGRESS_TRANSPORT_REQUIRED: external calls require a Runtime network "
641
+ "guard transport"
642
+ )
643
+ lease = await exit_stack.enter_async_context(
644
+ self._external_transport.open_for_route(
645
+ prepared.route.interface,
646
+ route_kind=prepared.route.kind,
647
+ )
648
+ )
649
+ http = lease.httpx_client
650
+ injection = await self._backend.resolve_credential(
651
+ platform_task_id=prepared.platform_task_id,
652
+ credential_handle=prepared.credential_handle,
653
+ call_permit=prepared.call_permit,
654
+ )
655
+ headers = dict(injection.headers)
656
+ if injection.cookies:
657
+ if any(name.lower() == "cookie" for name in headers):
658
+ raise RuntimeError(
659
+ "A2A_CREDENTIAL_INJECTION_CONFLICT: Cookie header and cookie injection "
660
+ "cannot both be present"
661
+ )
662
+ headers["Cookie"] = self._cookie_header(injection.cookies)
663
+ route = prepared.route.interface
664
+ if injection.query:
665
+ route = A2ARouteInterface(
666
+ url=self._url_with_query(route.url, injection.query),
667
+ protocol_binding=route.protocol_binding,
668
+ protocol_version=route.protocol_version,
669
+ )
670
+ route_card = self._card_for_route(agent.agent_card, route)
671
+ owned_http = None
672
+ if http is None:
673
+ owned_http = httpx.AsyncClient(trust_env=False)
674
+ http = owned_http
675
+ client = await create_client(
676
+ agent=route_card,
677
+ client_config=ClientConfig(httpx_client=http, streaming=True),
678
+ )
679
+ try:
680
+ yield client, ClientCallContext(service_parameters=headers or None)
681
+ finally:
682
+ if owned_http is not None:
683
+ await client.close()
684
+ await owned_http.aclose()
685
+
686
+ @staticmethod
687
+ def _url_with_query(url: str, query: dict[str, str]) -> str:
688
+ parsed = urlsplit(url)
689
+ values = parse_qsl(parsed.query, keep_blank_values=True)
690
+ existing = {name for name, _ in values}
691
+ collision = existing.intersection(query)
692
+ if collision:
693
+ raise RuntimeError(
694
+ "A2A_CREDENTIAL_INJECTION_CONFLICT: credential query collides with route query: "
695
+ f"{sorted(collision)}"
696
+ )
697
+ values.extend(query.items())
698
+ return urlunsplit(
699
+ (parsed.scheme, parsed.netloc, parsed.path, urlencode(values), parsed.fragment)
700
+ )
701
+
702
+ @staticmethod
703
+ def _cookie_header(cookies: dict[str, str]) -> str:
704
+ jar = SimpleCookie()
705
+ try:
706
+ for name, value in cookies.items():
707
+ jar[name] = value
708
+ except CookieError as exc:
709
+ raise RuntimeError(
710
+ "A2A_CREDENTIAL_INJECTION_CONFLICT: invalid credential cookie"
711
+ ) from exc
712
+ return jar.output(header="", sep="; ").strip()
713
+
714
+ @staticmethod
715
+ def _card_for_route(card: AgentCard, route: A2ARouteInterface) -> AgentCard:
716
+ payload = _canonical_proto(card)
717
+ payload["supportedInterfaces"] = [
718
+ {
719
+ "url": route.url,
720
+ "protocolBinding": route.protocol_binding,
721
+ "protocolVersion": route.protocol_version,
722
+ }
723
+ ]
724
+ return ParseDict(payload, AgentCard())
725
+
726
+ async def _bind_task(self, platform_task_id: str, remote_task: Any) -> None:
727
+ await self._backend.bind_remote_task(
728
+ platform_task_id=platform_task_id,
729
+ remote_task_id=str(remote_task.id),
730
+ remote_context_id=str(remote_task.context_id or "") or None,
731
+ observed_at=_utc_now(),
732
+ )
733
+
734
+ async def _project_stream_item(
735
+ self,
736
+ platform_task_id: str,
737
+ item: Any,
738
+ agent: DiscoveredAgent,
739
+ *,
740
+ wire_position: int,
741
+ operation_instance_id: str,
742
+ ) -> list[RuntimeEvent]:
743
+ runtime_events = self._stream_item_to_events(
744
+ item,
745
+ agent,
746
+ wire_position=wire_position,
747
+ invocation_id=platform_task_id,
748
+ )
749
+ platform_events = self._platform_events(
750
+ item,
751
+ platform_task_id,
752
+ operation_instance_id=operation_instance_id,
753
+ wire_position=wire_position,
754
+ )
755
+ if platform_events:
756
+ await self._event_dispatcher.enqueue(
757
+ platform_task_id=platform_task_id,
758
+ events=platform_events,
759
+ )
760
+ return await self._persist_events(runtime_events)
761
+
762
+ def _platform_events(
763
+ self,
764
+ item: Any,
765
+ platform_task_id: str,
766
+ *,
767
+ operation_instance_id: str,
768
+ wire_position: int,
769
+ ) -> list[dict[str, Any]]:
770
+ events: list[dict[str, Any]] = []
771
+
772
+ def append_event(
773
+ kind: str,
774
+ payload: dict[str, Any],
775
+ *,
776
+ status: str | None = None,
777
+ occurred_at: str | None = None,
778
+ ) -> None:
779
+ events.append(
780
+ self._platform_event(
781
+ kind,
782
+ payload,
783
+ platform_task_id,
784
+ operation_instance_id=operation_instance_id,
785
+ wire_position=wire_position,
786
+ event_ordinal=len(events),
787
+ status=status,
788
+ occurred_at=occurred_at,
789
+ )
790
+ )
791
+
792
+ task = _present_message_field(item, "task")
793
+ if task is None and hasattr(item, "status") and hasattr(item, "id"):
794
+ task = item
795
+ status_update = _present_message_field(item, "status_update")
796
+ artifact_update = _present_message_field(item, "artifact_update")
797
+ message = _present_message_field(item, "message")
798
+ if task is not None and getattr(task, "status", None) is not None:
799
+ payload = _canonical_proto(task.status)
800
+ state_name = TaskState.Name(task.status.state)
801
+ append_event(
802
+ "status",
803
+ payload,
804
+ status=state_name.removeprefix("TASK_STATE_").lower(),
805
+ occurred_at=str(payload.get("timestamp") or _utc_now()),
806
+ )
807
+ for artifact in getattr(task, "artifacts", None) or []:
808
+ append_event(
809
+ "artifact",
810
+ {
811
+ "Artifact": _canonical_proto(artifact),
812
+ "Append": False,
813
+ "LastChunk": True,
814
+ },
815
+ )
816
+ if status_update is not None and getattr(status_update, "status", None) is not None:
817
+ payload = _canonical_proto(status_update.status)
818
+ state_name = TaskState.Name(status_update.status.state)
819
+ append_event(
820
+ "status",
821
+ payload,
822
+ status=state_name.removeprefix("TASK_STATE_").lower(),
823
+ occurred_at=str(payload.get("timestamp") or _utc_now()),
824
+ )
825
+ if artifact_update is not None and getattr(artifact_update, "artifact", None) is not None:
826
+ append_event(
827
+ "artifact",
828
+ {
829
+ "Artifact": _canonical_proto(artifact_update.artifact),
830
+ "Append": bool(getattr(artifact_update, "append", False)),
831
+ "LastChunk": bool(getattr(artifact_update, "last_chunk", False)),
832
+ },
833
+ )
834
+ if message is not None:
835
+ payload = _canonical_proto(message)
836
+ append_event("message", payload)
837
+ append_event(
838
+ "status",
839
+ {"state": "TASK_STATE_COMPLETED", "message": payload},
840
+ status="completed",
841
+ )
842
+ return events
843
+
844
+ @staticmethod
845
+ def _platform_event(
846
+ kind: str,
847
+ payload: dict[str, Any],
848
+ platform_task_id: str,
849
+ *,
850
+ operation_instance_id: str,
851
+ wire_position: int,
852
+ event_ordinal: int,
853
+ status: str | None = None,
854
+ occurred_at: str | None = None,
855
+ ) -> dict[str, Any]:
856
+ source_id = hashlib.sha256(
857
+ (
858
+ f"{platform_task_id}:{operation_instance_id}:{wire_position}:{event_ordinal}:{kind}"
859
+ ).encode("utf-8")
860
+ ).hexdigest()
861
+ event: dict[str, Any] = {
862
+ "SourceEventId": source_id,
863
+ "EventKind": kind,
864
+ "Payload": payload,
865
+ "OccurredAt": occurred_at or _utc_now(),
866
+ }
867
+ if status:
868
+ event["Status"] = status
869
+ return event
870
+
871
+ async def flush_pending_events(self) -> int:
872
+ """Deliver all currently queued platform event batches or raise on failure."""
873
+
874
+ return await self._event_dispatcher.drain(raise_on_error=True)
875
+
876
+ def _next_seq(self) -> int:
877
+ self._seq += 1
878
+ return self._seq
879
+
880
+ def _event_ctx(
881
+ self,
882
+ agent: DiscoveredAgent,
883
+ invocation_id: str,
884
+ *,
885
+ event_id: str | None = None,
886
+ ) -> dict[str, Any]:
887
+ return {
888
+ "agent_id": agent.agent_id,
889
+ "user_id": "a2a_space",
890
+ "session_id": self._space_id,
891
+ "invocation_id": invocation_id,
892
+ "seq_id": self._next_seq(),
893
+ "event_id": event_id,
894
+ }
895
+
896
+ def task_to_event(self, task: Any, agent: DiscoveredAgent) -> RuntimeEvent:
897
+ return self._event_adapter.task_status_to_event(
898
+ task.status, **self._event_ctx(agent, invocation_id=str(task.id))
899
+ )
900
+
901
+ def _stream_item_to_events(
902
+ self,
903
+ item: Any,
904
+ agent: DiscoveredAgent,
905
+ *,
906
+ wire_position: int = 0,
907
+ invocation_id: str | None = None,
908
+ ) -> list[RuntimeEvent]:
909
+ events: list[RuntimeEvent] = []
910
+ task = _present_message_field(item, "task")
911
+ if task is None and hasattr(item, "status") and hasattr(item, "id"):
912
+ task = item
913
+ status_update = _present_message_field(item, "status_update")
914
+ artifact_update = _present_message_field(item, "artifact_update")
915
+ message = _present_message_field(item, "message")
916
+ resolved_invocation_id = invocation_id or str(
917
+ getattr(item, "task_id", None)
918
+ or getattr(task, "id", "")
919
+ or getattr(status_update, "task_id", "")
920
+ or getattr(artifact_update, "task_id", "")
921
+ or getattr(message, "task_id", "")
922
+ or ""
923
+ )
924
+
925
+ def ctx(kind: str, value: Any) -> dict[str, Any]:
926
+ metadata = getattr(value, "metadata", None)
927
+ native_event_id = ""
928
+ if metadata is not None:
929
+ if isinstance(metadata, Mapping):
930
+ metadata_dict = dict(metadata)
931
+ else:
932
+ try:
933
+ metadata_dict = MessageToDict(metadata, preserving_proto_field_name=True)
934
+ except (AttributeError, TypeError, ValueError):
935
+ metadata_dict = {}
936
+ native_event_id = str(
937
+ metadata_dict.get("event_id") or metadata_dict.get("ksadk_event_id") or ""
938
+ )
939
+ message_id = str(getattr(value, "message_id", "") or "")
940
+ artifact = getattr(value, "artifact", None)
941
+ artifact_id = str(
942
+ getattr(value, "artifact_id", "") or getattr(artifact, "artifact_id", "") or ""
943
+ )
944
+ source_id = native_event_id or message_id or artifact_id
945
+ event_id = uuid.uuid5(
946
+ uuid.NAMESPACE_URL,
947
+ f"ksadk:a2a:{resolved_invocation_id}:{wire_position}:{kind}:{source_id}",
948
+ ).hex
949
+ return self._event_ctx(agent, invocation_id=resolved_invocation_id, event_id=event_id)
950
+
951
+ if task is not None and getattr(task, "status", None) is not None:
952
+ task_status_message = _present_message_field(task.status, "message")
953
+ task_status_text = A2AEventAdapter._parts_text(
954
+ getattr(task_status_message, "parts", None)
955
+ )
956
+ task_is_terminal = task.status.state in {
957
+ TaskState.TASK_STATE_COMPLETED,
958
+ TaskState.TASK_STATE_FAILED,
959
+ TaskState.TASK_STATE_CANCELED,
960
+ TaskState.TASK_STATE_REJECTED,
961
+ }
962
+ if not task_is_terminal:
963
+ events.append(
964
+ self._event_adapter.task_status_to_event(
965
+ task.status,
966
+ **ctx("task", task),
967
+ )
968
+ )
969
+ if task_status_text:
970
+ events.append(
971
+ self._event_adapter.message_to_event(
972
+ task_status_text,
973
+ final=task_is_terminal,
974
+ **ctx("task-status-message", task_status_message),
975
+ )
976
+ )
977
+ if task_is_terminal:
978
+ events.append(
979
+ self._event_adapter.task_status_to_event(
980
+ task.status,
981
+ **ctx("task", task),
982
+ )
983
+ )
984
+ if status_update is not None and getattr(status_update, "status", None) is not None:
985
+ status_message = _present_message_field(status_update.status, "message")
986
+ text = A2AEventAdapter._parts_text(getattr(status_message, "parts", None))
987
+ terminal_states = {
988
+ TaskState.TASK_STATE_COMPLETED,
989
+ TaskState.TASK_STATE_FAILED,
990
+ TaskState.TASK_STATE_CANCELED,
991
+ TaskState.TASK_STATE_REJECTED,
992
+ }
993
+ is_terminal = status_update.status.state in terminal_states
994
+ if not is_terminal:
995
+ events.append(
996
+ self._event_adapter.task_status_to_event(
997
+ status_update.status, **ctx("status", status_update)
998
+ )
999
+ )
1000
+ if text:
1001
+ events.append(
1002
+ self._event_adapter.message_to_event(
1003
+ text,
1004
+ final=is_terminal,
1005
+ **ctx("status-message", status_message),
1006
+ )
1007
+ )
1008
+ if is_terminal:
1009
+ events.append(
1010
+ self._event_adapter.task_status_to_event(
1011
+ status_update.status, **ctx("status", status_update)
1012
+ )
1013
+ )
1014
+ if artifact_update is not None and getattr(artifact_update, "artifact", None) is not None:
1015
+ artifact = artifact_update.artifact
1016
+ events.append(
1017
+ self._event_adapter.artifact_to_event(artifact, **ctx("artifact", artifact_update))
1018
+ )
1019
+ artifact_text = A2AEventAdapter._parts_text(getattr(artifact, "parts", None))
1020
+ if artifact_text and str(getattr(artifact, "name", "") or "") == "response":
1021
+ events.append(
1022
+ self._event_adapter.message_to_event(
1023
+ artifact_text,
1024
+ final=bool(getattr(artifact_update, "last_chunk", False)),
1025
+ **ctx("artifact-text", artifact_update),
1026
+ )
1027
+ )
1028
+ if message is not None:
1029
+ text = A2AEventAdapter._parts_text(getattr(message, "parts", None))
1030
+ if text:
1031
+ events.append(
1032
+ self._event_adapter.message_to_event(
1033
+ text, final=True, **ctx("message", message)
1034
+ )
1035
+ )
1036
+ return events
1037
+
1038
+ async def _persist_events(self, events: list[RuntimeEvent]) -> list[RuntimeEvent]:
1039
+ existing_ids = set(self._persisted_wire_events)
1040
+ if self._event_sink is not None:
1041
+ list_events = getattr(self._event_sink, "list", None)
1042
+ if callable(list_events) and events:
1043
+ persisted_before = await list_events(events[0].session_id)
1044
+ existing_ids.update(event.event_id for event in persisted_before)
1045
+ fresh = [event for event in events if event.event_id not in existing_ids]
1046
+ if not fresh:
1047
+ return []
1048
+ if self._event_sink is not None:
1049
+ append = getattr(self._event_sink, "append", None)
1050
+ if append is None:
1051
+ raise TypeError("event_sink must provide async append(events)")
1052
+ persisted = await append(fresh)
1053
+ if persisted is not None:
1054
+ fresh = list(persisted)
1055
+ self._persisted_wire_events.update(event.event_id for event in fresh)
1056
+ return fresh
1057
+
1058
+ async def subscribe_events(self, task_id: str):
1059
+ require_a2a_resource_id(task_id, "a2a-task-", field_name="task_id")
1060
+ prepared = await self._backend.prepare_task_operation(
1061
+ platform_task_id=task_id,
1062
+ operation="subscribe_to_task",
1063
+ )
1064
+ self._validate_prepared_ids(prepared)
1065
+ agent = self._agent_from_prepared(prepared)
1066
+ async for _, persisted in self._iter_subscription(prepared, agent):
1067
+ for event in persisted:
1068
+ yield event
1069
+
1070
+ async def subscribe_persisted_events(
1071
+ self,
1072
+ *,
1073
+ after_seq_id: int = 0,
1074
+ timeout: float = 1.0,
1075
+ ) -> AsyncIterator[RuntimeEvent]:
1076
+ subscribe = getattr(self._event_sink, "subscribe_session", None)
1077
+ if subscribe is None:
1078
+ raise RuntimeError("event_sink does not support subscribe_session cursor replay")
1079
+ async for event in subscribe(
1080
+ self._space_id,
1081
+ after_seq_id=after_seq_id,
1082
+ timeout=timeout,
1083
+ ):
1084
+ yield event
1085
+
1086
+ async def _record_agent_for_task(self, task_id: str, agent: DiscoveredAgent) -> None:
1087
+ set_task_agent = getattr(self._event_sink, "set_task_agent", None)
1088
+ if callable(set_task_agent):
1089
+ await set_task_agent(self._space_id, task_id, agent.agent_id)
1090
+
1091
+
1092
+ __all__ = [
1093
+ "A2AExternalTransport",
1094
+ "A2APlatformTask",
1095
+ "A2ASpaceClient",
1096
+ "DiscoveredAgent",
1097
+ "ENV_A2A_ENABLE_PUBLIC_EGRESS",
1098
+ "ENV_A2A_SPACE_IDS",
1099
+ "ERR_PUBLIC_EGRESS_DISABLED",
1100
+ "SpaceAgentPage",
1101
+ ]