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,818 @@
1
+ """Typed client for the AgentEngine A2A runtime control-plane contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import binascii
7
+ import errno
8
+ import json
9
+ import os
10
+ import stat
11
+ import time
12
+ from abc import ABC, abstractmethod
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+ from typing import Any, Literal, get_args
16
+
17
+ import httpx
18
+ from a2a.types import AgentCard
19
+ from google.protobuf.json_format import ParseDict, ParseError
20
+
21
+ from ksadk.a2a.ids import require_a2a_resource_id
22
+
23
+ ENV_A2A_CONTROL_PLANE_URL = "KSADK_A2A_CONTROL_PLANE_URL"
24
+ ENV_A2A_TOKEN_DIR = "KSADK_A2A_TOKEN_DIR"
25
+ DEFAULT_A2A_TOKEN_DIR = "/var/run/secrets/agentengine/a2a"
26
+ MAX_WORKLOAD_TOKEN_BYTES = 16 * 1024
27
+
28
+ AUDIENCE_REGISTRY = "a2a-registry"
29
+ AUDIENCE_TASK_SINK = "a2a-task-sink"
30
+ AUDIENCE_CREDENTIAL_BROKER = "credential-broker"
31
+ AUDIENCE_GATEWAY = "a2a-gateway"
32
+
33
+ A2AOperation = Literal[
34
+ "send_message",
35
+ "get_task",
36
+ "subscribe_to_task",
37
+ "cancel_task",
38
+ ]
39
+ A2A_OPERATIONS = frozenset(get_args(A2AOperation))
40
+
41
+ A2AInternalAction = Literal[
42
+ "ListA2ASpaceAgents",
43
+ "PrepareA2ACall",
44
+ "PrepareA2ATaskOperation",
45
+ "BindA2ARemoteTask",
46
+ "AppendA2ATaskEvents",
47
+ "ResolveA2ACredential",
48
+ ]
49
+ A2A_INTERNAL_ACTIONS = frozenset(get_args(A2AInternalAction))
50
+ A2A_INTERNAL_PATH_PREFIX = "/agentengine/internal/v1/a2a"
51
+
52
+
53
+ def build_a2a_internal_action_path(action: A2AInternalAction) -> str:
54
+ """Return the explicitly registered Runtime internal Action path."""
55
+
56
+ if action not in A2A_INTERNAL_ACTIONS:
57
+ raise ValueError(f"unsupported A2A internal Action: {action}")
58
+ return f"{A2A_INTERNAL_PATH_PREFIX}/{action}"
59
+
60
+
61
+ class A2AControlPlaneError(RuntimeError):
62
+ """Stable error returned by an AgentEngine A2A Action."""
63
+
64
+ def __init__(
65
+ self,
66
+ *,
67
+ code: int,
68
+ message: str,
69
+ error_code: str,
70
+ retryable: bool = False,
71
+ field: str | None = None,
72
+ details: dict[str, Any] | None = None,
73
+ request_id: str = "",
74
+ action: str = "",
75
+ ) -> None:
76
+ super().__init__(f"{error_code}: {message}" if error_code else message)
77
+ self.code = code
78
+ self.message = message
79
+ self.error_code = error_code
80
+ self.retryable = retryable
81
+ self.field = field
82
+ self.details = dict(details or {})
83
+ self.request_id = request_id
84
+ self.action = action
85
+
86
+
87
+ class WorkloadTokenProvider(ABC):
88
+ """Returns a short-lived workload token for one exact audience."""
89
+
90
+ @abstractmethod
91
+ def get_token(self, audience: str) -> str:
92
+ raise NotImplementedError
93
+
94
+
95
+ class FileWorkloadTokenProvider(WorkloadTokenProvider):
96
+ """Reads audience-specific projected JWT files before every request."""
97
+
98
+ def __init__(self, token_dir: str | os.PathLike[str] | None = None) -> None:
99
+ configured = token_dir or os.getenv(ENV_A2A_TOKEN_DIR) or DEFAULT_A2A_TOKEN_DIR
100
+ self._token_dir = Path(configured)
101
+
102
+ def get_token(self, audience: str) -> str:
103
+ if not audience or "/" in audience or audience in {".", ".."}:
104
+ raise ValueError(f"invalid workload token audience: {audience!r}")
105
+ path = self._token_dir / f"{audience}.jwt"
106
+ if path.is_symlink():
107
+ raise RuntimeError(
108
+ f"workload token for audience {audience!r} must not be a symlink: {path}"
109
+ )
110
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
111
+ try:
112
+ descriptor = os.open(path, flags)
113
+ except OSError as exc:
114
+ if exc.errno == errno.ELOOP:
115
+ raise RuntimeError(
116
+ f"workload token for audience {audience!r} must not be a symlink: {path}"
117
+ ) from exc
118
+ raise RuntimeError(f"missing workload token for audience {audience!r}: {path}") from exc
119
+ try:
120
+ token_stat = os.fstat(descriptor)
121
+ if not stat.S_ISREG(token_stat.st_mode):
122
+ raise RuntimeError(
123
+ f"workload token for audience {audience!r} is not a regular file: {path}"
124
+ )
125
+ if token_stat.st_size > MAX_WORKLOAD_TOKEN_BYTES:
126
+ raise RuntimeError(
127
+ f"workload token for audience {audience!r} exceeds 16 KiB: {path}"
128
+ )
129
+ if stat.S_IMODE(token_stat.st_mode) != 0o400:
130
+ raise RuntimeError(
131
+ f"workload token for audience {audience!r} must have mode 0400: {path}"
132
+ )
133
+ with os.fdopen(descriptor, "rb", closefd=False) as token_file:
134
+ raw_token = token_file.read(MAX_WORKLOAD_TOKEN_BYTES + 1)
135
+ if len(raw_token) > MAX_WORKLOAD_TOKEN_BYTES:
136
+ raise RuntimeError(
137
+ f"workload token for audience {audience!r} exceeds 16 KiB: {path}"
138
+ )
139
+ token = raw_token.decode("ascii").strip()
140
+ except UnicodeError as exc:
141
+ raise RuntimeError(
142
+ f"workload token for audience {audience!r} must be ASCII: {path}"
143
+ ) from exc
144
+ finally:
145
+ os.close(descriptor)
146
+ if not token:
147
+ raise RuntimeError(f"empty workload token for audience {audience!r}: {path}")
148
+ self._validate_expiry(token, audience=audience, path=path)
149
+ return token
150
+
151
+ @staticmethod
152
+ def _validate_expiry(token: str, *, audience: str, path: Path) -> None:
153
+ parts = token.split(".")
154
+ if len(parts) != 3:
155
+ raise RuntimeError(f"workload token for audience {audience!r} is not a JWT: {path}")
156
+ try:
157
+ payload_segment = parts[1] + "=" * (-len(parts[1]) % 4)
158
+ payload = json.loads(base64.urlsafe_b64decode(payload_segment).decode("utf-8"))
159
+ expires_at = int(payload["exp"])
160
+ except (binascii.Error, KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
161
+ raise RuntimeError(
162
+ f"workload token for audience {audience!r} has no valid exp claim: {path}"
163
+ ) from exc
164
+ token_audience = payload.get("aud")
165
+ accepted_audiences = (
166
+ {str(item) for item in token_audience}
167
+ if isinstance(token_audience, list)
168
+ else {str(token_audience)}
169
+ )
170
+ if audience not in accepted_audiences:
171
+ raise RuntimeError(f"workload token audience mismatch for {audience!r}: {path}")
172
+ if expires_at <= int(time.time()):
173
+ raise RuntimeError(f"workload token for audience {audience!r} is expired: {path}")
174
+
175
+
176
+ @dataclass(frozen=True)
177
+ class A2ARouteInterface:
178
+ url: str
179
+ protocol_binding: str
180
+ protocol_version: str
181
+
182
+
183
+ @dataclass(frozen=True)
184
+ class A2ARoute:
185
+ kind: str
186
+ interface: A2ARouteInterface
187
+
188
+
189
+ @dataclass(frozen=True)
190
+ class A2ATarget:
191
+ agent_id: str
192
+ version_id: str
193
+ card_sha256: str
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class RemoteTaskReference:
198
+ remote_task_id: str
199
+ remote_context_id: str | None = None
200
+
201
+
202
+ @dataclass(frozen=True)
203
+ class PreparedA2AOperation:
204
+ platform_task_id: str
205
+ target: A2ATarget
206
+ route: A2ARoute
207
+ call_permit: str
208
+ call_permit_expires_at: str
209
+ credential_handle: str | None = None
210
+ remote_task: RemoteTaskReference | None = None
211
+
212
+
213
+ @dataclass(frozen=True)
214
+ class CredentialInjection:
215
+ headers: dict[str, str] = field(default_factory=dict)
216
+ query: dict[str, str] = field(default_factory=dict)
217
+ cookies: dict[str, str] = field(default_factory=dict)
218
+ expires_at: str | None = None
219
+
220
+
221
+ @dataclass
222
+ class DiscoveredAgent:
223
+ agent_id: str
224
+ version_id: str
225
+ source: str
226
+ agent_card: AgentCard
227
+ card_sha256: str = ""
228
+ callable: bool = True
229
+ blocked_reason: str | None = None
230
+ route_kind: str = ""
231
+
232
+
233
+ @dataclass
234
+ class SpaceAgentPage:
235
+ agents: list[DiscoveredAgent] = field(default_factory=list)
236
+ etag: str | None = None
237
+ next_cursor: str | None = None
238
+ not_modified: bool = False
239
+
240
+
241
+ class A2AControlPlane(ABC):
242
+ """Control-plane seam required by the product A2ASpaceClient."""
243
+
244
+ @abstractmethod
245
+ async def list_space_agents(
246
+ self,
247
+ space_id: str,
248
+ *,
249
+ prompt: str | None = None,
250
+ skill_id: str | None = None,
251
+ include_blocked: bool = False,
252
+ if_none_match: str | None = None,
253
+ cursor: str | None = None,
254
+ page_size: int = 50,
255
+ ) -> SpaceAgentPage:
256
+ raise NotImplementedError
257
+
258
+ @abstractmethod
259
+ async def prepare_call(
260
+ self,
261
+ *,
262
+ space_id: str,
263
+ target_agent_id: str,
264
+ expected_version_id: str | None,
265
+ message_id: str,
266
+ message_sha256: str,
267
+ idempotency_token: str,
268
+ ) -> PreparedA2AOperation:
269
+ raise NotImplementedError
270
+
271
+ @abstractmethod
272
+ async def prepare_task_operation(
273
+ self,
274
+ *,
275
+ platform_task_id: str,
276
+ operation: A2AOperation,
277
+ message_id: str | None = None,
278
+ message_sha256: str | None = None,
279
+ idempotency_token: str | None = None,
280
+ ) -> PreparedA2AOperation:
281
+ raise NotImplementedError
282
+
283
+ @abstractmethod
284
+ async def bind_remote_task(
285
+ self,
286
+ *,
287
+ platform_task_id: str,
288
+ remote_task_id: str,
289
+ remote_context_id: str | None,
290
+ observed_at: str,
291
+ ) -> dict[str, Any]:
292
+ raise NotImplementedError
293
+
294
+ @abstractmethod
295
+ async def append_task_events(
296
+ self,
297
+ *,
298
+ platform_task_id: str,
299
+ events: list[dict[str, Any]],
300
+ ) -> dict[str, Any]:
301
+ raise NotImplementedError
302
+
303
+ @abstractmethod
304
+ async def resolve_credential(
305
+ self,
306
+ *,
307
+ platform_task_id: str,
308
+ credential_handle: str | None,
309
+ call_permit: str,
310
+ ) -> CredentialInjection:
311
+ raise NotImplementedError
312
+
313
+ @abstractmethod
314
+ def gateway_token(self) -> str:
315
+ raise NotImplementedError
316
+
317
+
318
+ class InternalA2AControlPlaneClient(A2AControlPlane):
319
+ """Workload-authenticated client for Runtime internal A2A Actions."""
320
+
321
+ def __init__(
322
+ self,
323
+ base_url: str,
324
+ *,
325
+ token_provider: WorkloadTokenProvider | None = None,
326
+ httpx_client: httpx.AsyncClient | None = None,
327
+ timeout: float = 15.0,
328
+ ) -> None:
329
+ if not base_url:
330
+ raise ValueError(
331
+ f"InternalA2AControlPlaneClient requires {ENV_A2A_CONTROL_PLANE_URL}"
332
+ )
333
+ self._base_url = base_url.rstrip("/")
334
+ self._token_provider = token_provider or FileWorkloadTokenProvider()
335
+ self._client = httpx_client
336
+ self._timeout = timeout
337
+
338
+ async def _post(
339
+ self,
340
+ action: A2AInternalAction,
341
+ *,
342
+ audience: str,
343
+ payload: dict[str, Any],
344
+ ) -> dict[str, Any]:
345
+ token = self._token_provider.get_token(audience)
346
+ client = self._client or httpx.AsyncClient(timeout=self._timeout)
347
+ owns_client = self._client is None
348
+ try:
349
+ try:
350
+ response = await client.post(
351
+ f"{self._base_url}{build_a2a_internal_action_path(action)}",
352
+ json=payload,
353
+ headers={"Authorization": f"Bearer {token}"},
354
+ )
355
+ except httpx.HTTPError as exc:
356
+ raise A2AControlPlaneError(
357
+ code=503,
358
+ message="A2A control plane is unavailable",
359
+ error_code="A2A_CONTROL_PLANE_UNAVAILABLE",
360
+ retryable=True,
361
+ action=action,
362
+ ) from exc
363
+ try:
364
+ envelope = response.json()
365
+ except ValueError:
366
+ unavailable = response.status_code >= 500
367
+ raise A2AControlPlaneError(
368
+ code=response.status_code if unavailable else 502,
369
+ message="A2A control plane returned a non-JSON response",
370
+ error_code=(
371
+ "A2A_CONTROL_PLANE_UNAVAILABLE"
372
+ if unavailable
373
+ else "A2A_CONTROL_PLANE_INVALID_RESPONSE"
374
+ ),
375
+ retryable=unavailable,
376
+ action=action,
377
+ )
378
+ if not isinstance(envelope, dict):
379
+ raise A2AControlPlaneError(
380
+ code=response.status_code if response.is_error else 502,
381
+ message="A2A control plane response must be an object",
382
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
383
+ retryable=response.status_code >= 500,
384
+ action=action,
385
+ )
386
+ try:
387
+ action_code = int(envelope.get("Code") or 0)
388
+ except (TypeError, ValueError) as exc:
389
+ raise A2AControlPlaneError(
390
+ code=502,
391
+ message="A2A control plane response Code must be an integer",
392
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
393
+ request_id=str(envelope.get("RequestId") or ""),
394
+ action=action,
395
+ ) from exc
396
+ if response.is_error or action_code != 0:
397
+ self._raise_action_error(response, envelope, action)
398
+ data = envelope.get("Data")
399
+ if not isinstance(data, dict):
400
+ raise A2AControlPlaneError(
401
+ code=response.status_code,
402
+ message="A2A control plane response Data must be an object",
403
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
404
+ request_id=str(envelope.get("RequestId") or ""),
405
+ action=action,
406
+ )
407
+ return data
408
+ finally:
409
+ if owns_client:
410
+ await client.aclose()
411
+
412
+ @staticmethod
413
+ def _raise_action_error(
414
+ response: httpx.Response,
415
+ envelope: dict[str, Any],
416
+ action: str,
417
+ ) -> None:
418
+ raw_data = envelope.get("Data")
419
+ data: dict[str, Any] = raw_data if isinstance(raw_data, dict) else {}
420
+ raise A2AControlPlaneError(
421
+ code=int(envelope.get("Code") or response.status_code),
422
+ message=str(envelope.get("Message") or response.reason_phrase or "A2A action failed"),
423
+ error_code=str(data.get("ErrorCode") or "A2A_CONTROL_PLANE_ERROR"),
424
+ retryable=bool(data.get("Retryable", response.status_code >= 500)),
425
+ field=str(data.get("Field")) if data.get("Field") is not None else None,
426
+ details=data.get("Details") if isinstance(data.get("Details"), dict) else {},
427
+ request_id=str(envelope.get("RequestId") or ""),
428
+ action=str(envelope.get("Action") or action),
429
+ )
430
+
431
+ async def list_space_agents(
432
+ self,
433
+ space_id: str,
434
+ *,
435
+ prompt: str | None = None,
436
+ skill_id: str | None = None,
437
+ include_blocked: bool = False,
438
+ if_none_match: str | None = None,
439
+ cursor: str | None = None,
440
+ page_size: int = 50,
441
+ ) -> SpaceAgentPage:
442
+ payload: dict[str, Any] = {"A2ASpaceId": space_id}
443
+ if prompt:
444
+ payload["Prompt"] = prompt
445
+ if skill_id:
446
+ payload["SkillId"] = skill_id
447
+ if include_blocked:
448
+ payload["IncludeBlocked"] = True
449
+ if if_none_match:
450
+ payload["IfNoneMatch"] = if_none_match
451
+ if cursor:
452
+ payload["Cursor"] = cursor
453
+ if page_size != 50:
454
+ payload["PageSize"] = page_size
455
+ data = await self._post(
456
+ "ListA2ASpaceAgents",
457
+ audience=AUDIENCE_REGISTRY,
458
+ payload=payload,
459
+ )
460
+ return SpaceAgentPage(
461
+ agents=[_discovered_agent_from_wire(item) for item in data.get("Agents") or []],
462
+ etag=_optional_str(data.get("ETag")),
463
+ next_cursor=_optional_str(data.get("NextCursor")),
464
+ not_modified=bool(data.get("NotModified", False)),
465
+ )
466
+
467
+ async def prepare_call(
468
+ self,
469
+ *,
470
+ space_id: str,
471
+ target_agent_id: str,
472
+ expected_version_id: str | None,
473
+ message_id: str,
474
+ message_sha256: str,
475
+ idempotency_token: str,
476
+ ) -> PreparedA2AOperation:
477
+ payload: dict[str, Any] = {
478
+ "A2ASpaceId": space_id,
479
+ "TargetA2AAgentId": target_agent_id,
480
+ "MessageId": message_id,
481
+ "MessageSha256": message_sha256,
482
+ "IdempotencyToken": idempotency_token,
483
+ }
484
+ if expected_version_id:
485
+ payload["ExpectedVersionId"] = expected_version_id
486
+ data = await self._post("PrepareA2ACall", audience=AUDIENCE_REGISTRY, payload=payload)
487
+ return _prepared_operation_from_wire(data)
488
+
489
+ async def prepare_task_operation(
490
+ self,
491
+ *,
492
+ platform_task_id: str,
493
+ operation: A2AOperation,
494
+ message_id: str | None = None,
495
+ message_sha256: str | None = None,
496
+ idempotency_token: str | None = None,
497
+ ) -> PreparedA2AOperation:
498
+ _validate_task_operation_request(
499
+ operation=operation,
500
+ message_id=message_id,
501
+ message_sha256=message_sha256,
502
+ idempotency_token=idempotency_token,
503
+ )
504
+ payload: dict[str, Any] = {"A2ATaskId": platform_task_id, "Operation": operation}
505
+ if message_id:
506
+ payload["MessageId"] = message_id
507
+ if message_sha256:
508
+ payload["MessageSha256"] = message_sha256
509
+ if idempotency_token:
510
+ payload["IdempotencyToken"] = idempotency_token
511
+ data = await self._post(
512
+ "PrepareA2ATaskOperation",
513
+ audience=AUDIENCE_REGISTRY,
514
+ payload=payload,
515
+ )
516
+ return _prepared_operation_from_wire(data)
517
+
518
+ async def bind_remote_task(
519
+ self,
520
+ *,
521
+ platform_task_id: str,
522
+ remote_task_id: str,
523
+ remote_context_id: str | None,
524
+ observed_at: str,
525
+ ) -> dict[str, Any]:
526
+ payload: dict[str, Any] = {
527
+ "A2ATaskId": platform_task_id,
528
+ "RemoteTaskId": remote_task_id,
529
+ "ObservedAt": observed_at,
530
+ }
531
+ if remote_context_id:
532
+ payload["RemoteContextId"] = remote_context_id
533
+ return await self._post(
534
+ "BindA2ARemoteTask",
535
+ audience=AUDIENCE_TASK_SINK,
536
+ payload=payload,
537
+ )
538
+
539
+ async def append_task_events(
540
+ self,
541
+ *,
542
+ platform_task_id: str,
543
+ events: list[dict[str, Any]],
544
+ ) -> dict[str, Any]:
545
+ return await self._post(
546
+ "AppendA2ATaskEvents",
547
+ audience=AUDIENCE_TASK_SINK,
548
+ payload={"A2ATaskId": platform_task_id, "Events": events},
549
+ )
550
+
551
+ async def resolve_credential(
552
+ self,
553
+ *,
554
+ platform_task_id: str,
555
+ credential_handle: str | None,
556
+ call_permit: str,
557
+ ) -> CredentialInjection:
558
+ data = await self._post(
559
+ "ResolveA2ACredential",
560
+ audience=AUDIENCE_CREDENTIAL_BROKER,
561
+ payload={
562
+ "A2ATaskId": platform_task_id,
563
+ "CredentialHandle": credential_handle,
564
+ "CallPermit": call_permit,
565
+ },
566
+ )
567
+ raw_injection = data.get("Injection")
568
+ injection: dict[str, Any] = raw_injection if isinstance(raw_injection, dict) else {}
569
+ return CredentialInjection(
570
+ headers=_credential_string_map(
571
+ injection.get("Headers"), field_name="Injection.Headers"
572
+ ),
573
+ query=_credential_string_map(injection.get("Query"), field_name="Injection.Query"),
574
+ cookies=_credential_string_map(
575
+ injection.get("Cookies"), field_name="Injection.Cookies"
576
+ ),
577
+ expires_at=_optional_str(data.get("ExpiresAt")),
578
+ )
579
+
580
+ def gateway_token(self) -> str:
581
+ return self._token_provider.get_token(AUDIENCE_GATEWAY)
582
+
583
+
584
+ def _credential_string_map(value: Any, *, field_name: str) -> dict[str, str]:
585
+ if value is None:
586
+ return {}
587
+ if not isinstance(value, dict) or any(
588
+ not isinstance(key, str) or not isinstance(item, str) for key, item in value.items()
589
+ ):
590
+ raise A2AControlPlaneError(
591
+ code=502,
592
+ message=f"control-plane response field {field_name} must be a string map",
593
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
594
+ field=field_name,
595
+ )
596
+ return dict(value)
597
+
598
+
599
+ def _validate_task_operation_request(
600
+ *,
601
+ operation: str,
602
+ message_id: str | None,
603
+ message_sha256: str | None,
604
+ idempotency_token: str | None,
605
+ ) -> None:
606
+ if operation not in A2A_OPERATIONS:
607
+ raise ValueError(f"unsupported A2A task operation: {operation!r}")
608
+ if operation == "send_message":
609
+ if not message_id or not message_sha256 or not idempotency_token:
610
+ raise ValueError(
611
+ "send_message requires message_id, message_sha256, and idempotency_token"
612
+ )
613
+ return
614
+ if message_id or message_sha256:
615
+ raise ValueError(f"{operation} does not accept message fields")
616
+ if operation == "cancel_task":
617
+ if not idempotency_token:
618
+ raise ValueError("cancel_task requires idempotency_token")
619
+ return
620
+ if idempotency_token:
621
+ raise ValueError(f"{operation} does not accept idempotency_token")
622
+
623
+
624
+ def _optional_str(value: Any) -> str | None:
625
+ return str(value) if value is not None else None
626
+
627
+
628
+ def _required_str(value: Any, *, field_name: str, prefix: str | None = None) -> str:
629
+ result = str(value or "")
630
+ if not result:
631
+ raise A2AControlPlaneError(
632
+ code=502,
633
+ message=f"control-plane response field {field_name} is missing or invalid",
634
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
635
+ field=field_name,
636
+ )
637
+ if prefix is not None:
638
+ try:
639
+ require_a2a_resource_id(result, prefix, field_name=field_name)
640
+ except ValueError as exc:
641
+ raise A2AControlPlaneError(
642
+ code=502,
643
+ message=f"control-plane response field {field_name} has an invalid resource ID",
644
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
645
+ field=field_name,
646
+ ) from exc
647
+ return result
648
+
649
+
650
+ def _agent_card_from_wire(payload: Any) -> AgentCard:
651
+ if not isinstance(payload, dict):
652
+ raise A2AControlPlaneError(
653
+ code=502,
654
+ message="AgentCard must be an object",
655
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
656
+ )
657
+ try:
658
+ return ParseDict(payload, AgentCard())
659
+ except (ParseError, TypeError, ValueError) as exc:
660
+ raise A2AControlPlaneError(
661
+ code=502,
662
+ message="AgentCard does not match the A2A 1.0 schema",
663
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
664
+ field="AgentCard",
665
+ ) from exc
666
+
667
+
668
+ def _discovered_agent_from_wire(item: dict[str, Any]) -> DiscoveredAgent:
669
+ if not isinstance(item, dict):
670
+ raise A2AControlPlaneError(
671
+ code=502,
672
+ message="ListA2ASpaceAgents item must be an object",
673
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
674
+ field="Agents",
675
+ )
676
+ route_kind = str(item.get("RouteKind") or "")
677
+ if route_kind not in {"hosted_gateway", "external_public", "external_vpc"}:
678
+ raise A2AControlPlaneError(
679
+ code=502,
680
+ message="ListA2ASpaceAgents returned an unknown RouteKind",
681
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
682
+ field="RouteKind",
683
+ )
684
+ source = _required_str(item.get("Source"), field_name="Source")
685
+ if source not in {"hosted", "external"}:
686
+ raise A2AControlPlaneError(
687
+ code=502,
688
+ message="ListA2ASpaceAgents returned an unknown Source",
689
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
690
+ field="Source",
691
+ )
692
+ return DiscoveredAgent(
693
+ agent_id=_required_str(
694
+ item.get("A2AAgentId"), field_name="A2AAgentId", prefix="a2a-agent-"
695
+ ),
696
+ version_id=_required_str(
697
+ item.get("VersionId"), field_name="VersionId", prefix="a2a-version-"
698
+ ),
699
+ source=source,
700
+ agent_card=_agent_card_from_wire(item.get("AgentCard")),
701
+ card_sha256=str(item.get("CardSha256") or ""),
702
+ callable=bool(item.get("Callable", False)),
703
+ blocked_reason=_optional_str(item.get("BlockedReason")),
704
+ route_kind=route_kind,
705
+ )
706
+
707
+
708
+ def _prepared_operation_from_wire(data: dict[str, Any]) -> PreparedA2AOperation:
709
+ raw_target = data.get("Target")
710
+ target: dict[str, Any] = raw_target if isinstance(raw_target, dict) else {}
711
+ raw_route = data.get("Route")
712
+ route: dict[str, Any] = raw_route if isinstance(raw_route, dict) else {}
713
+ raw_interface = route.get("Interface")
714
+ interface: dict[str, Any] = raw_interface if isinstance(raw_interface, dict) else {}
715
+ raw_remote_task = data.get("RemoteTask")
716
+ if raw_remote_task is not None and not isinstance(raw_remote_task, dict):
717
+ raise A2AControlPlaneError(
718
+ code=502,
719
+ message="control-plane response field RemoteTask must be an object or null",
720
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
721
+ field="RemoteTask",
722
+ )
723
+ remote_task: dict[str, Any] | None = (
724
+ raw_remote_task if isinstance(raw_remote_task, dict) else None
725
+ )
726
+ route_kind = _required_str(route.get("Kind"), field_name="Route.Kind")
727
+ if route_kind not in {"hosted_gateway", "external_public", "external_vpc"}:
728
+ raise A2AControlPlaneError(
729
+ code=502,
730
+ message="prepared operation returned an unknown route kind",
731
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
732
+ field="Route.Kind",
733
+ )
734
+ protocol_binding = _required_str(
735
+ interface.get("ProtocolBinding"), field_name="Route.Interface.ProtocolBinding"
736
+ )
737
+ if protocol_binding not in {"JSONRPC", "HTTP+JSON"}:
738
+ raise A2AControlPlaneError(
739
+ code=502,
740
+ message="prepared operation returned an unsupported protocol binding",
741
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
742
+ field="Route.Interface.ProtocolBinding",
743
+ )
744
+ protocol_version = _required_str(
745
+ interface.get("ProtocolVersion"), field_name="Route.Interface.ProtocolVersion"
746
+ )
747
+ if protocol_version != "1.0":
748
+ raise A2AControlPlaneError(
749
+ code=502,
750
+ message="prepared operation returned an unsupported protocol version",
751
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
752
+ field="Route.Interface.ProtocolVersion",
753
+ )
754
+ return PreparedA2AOperation(
755
+ platform_task_id=_required_str(
756
+ data.get("A2ATaskId"), field_name="A2ATaskId", prefix="a2a-task-"
757
+ ),
758
+ target=A2ATarget(
759
+ agent_id=_required_str(
760
+ target.get("A2AAgentId"),
761
+ field_name="Target.A2AAgentId",
762
+ prefix="a2a-agent-",
763
+ ),
764
+ version_id=_required_str(
765
+ target.get("VersionId"),
766
+ field_name="Target.VersionId",
767
+ prefix="a2a-version-",
768
+ ),
769
+ card_sha256=_required_str(target.get("CardSha256"), field_name="Target.CardSha256"),
770
+ ),
771
+ route=A2ARoute(
772
+ kind=route_kind,
773
+ interface=A2ARouteInterface(
774
+ url=_required_str(interface.get("Url"), field_name="Route.Interface.Url"),
775
+ protocol_binding=protocol_binding,
776
+ protocol_version=protocol_version,
777
+ ),
778
+ ),
779
+ call_permit=_required_str(data.get("CallPermit"), field_name="CallPermit"),
780
+ call_permit_expires_at=_required_str(
781
+ data.get("CallPermitExpiresAt"), field_name="CallPermitExpiresAt"
782
+ ),
783
+ credential_handle=_optional_str(data.get("CredentialHandle")),
784
+ remote_task=(
785
+ RemoteTaskReference(
786
+ remote_task_id=_required_str(
787
+ remote_task.get("RemoteTaskId"), field_name="RemoteTask.RemoteTaskId"
788
+ ),
789
+ remote_context_id=_optional_str(remote_task.get("RemoteContextId")),
790
+ )
791
+ if remote_task is not None
792
+ else None
793
+ ),
794
+ )
795
+
796
+
797
+ __all__ = [
798
+ "A2AInternalAction",
799
+ "A2A_INTERNAL_ACTIONS",
800
+ "A2A_INTERNAL_PATH_PREFIX",
801
+ "A2AControlPlane",
802
+ "A2AControlPlaneError",
803
+ "A2AOperation",
804
+ "A2ARoute",
805
+ "A2ARouteInterface",
806
+ "A2ATarget",
807
+ "CredentialInjection",
808
+ "DiscoveredAgent",
809
+ "ENV_A2A_CONTROL_PLANE_URL",
810
+ "ENV_A2A_TOKEN_DIR",
811
+ "FileWorkloadTokenProvider",
812
+ "InternalA2AControlPlaneClient",
813
+ "PreparedA2AOperation",
814
+ "RemoteTaskReference",
815
+ "SpaceAgentPage",
816
+ "WorkloadTokenProvider",
817
+ "build_a2a_internal_action_path",
818
+ ]