agentengine-sdk-python 0.7.0__py3-none-any.whl → 0.8.1__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 (450) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/METADATA +52 -20
  2. agentengine_sdk_python-0.8.1.dist-info/RECORD +644 -0
  3. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +180 -11
  5. ksadk/a2a/bootstrap.py +332 -0
  6. ksadk/a2a/card.py +15 -0
  7. ksadk/a2a/context_store.py +203 -0
  8. ksadk/a2a/control_plane.py +1056 -0
  9. ksadk/a2a/credential.py +99 -0
  10. ksadk/a2a/event_adapter.py +149 -0
  11. ksadk/a2a/executor.py +361 -137
  12. ksadk/a2a/external_transport.py +516 -0
  13. ksadk/a2a/identity.py +304 -0
  14. ksadk/a2a/ids.py +22 -0
  15. ksadk/a2a/langgraph.py +285 -0
  16. ksadk/a2a/resume_store.py +201 -0
  17. ksadk/a2a/routes.py +112 -0
  18. ksadk/a2a/server.py +102 -62
  19. ksadk/a2a/service_env.py +33 -0
  20. ksadk/a2a/space_client.py +1122 -0
  21. ksadk/a2a/task_adapter.py +423 -0
  22. ksadk/a2a/task_event_dispatcher.py +167 -0
  23. ksadk/a2a/task_event_outbox.py +326 -0
  24. ksadk/a2a/task_store.py +206 -0
  25. ksadk/a2a_card.py +101 -0
  26. ksadk/a2ui/__init__.py +27 -0
  27. ksadk/a2ui/core.py +186 -0
  28. ksadk/a2ui/models.py +166 -0
  29. ksadk/a2ui/renderer.py +198 -0
  30. ksadk/agui/__init__.py +9 -0
  31. ksadk/agui/a2ui_projection.py +92 -0
  32. ksadk/agui/agent.py +872 -0
  33. ksadk/agui/config.py +92 -0
  34. ksadk/agui/routes.py +47 -0
  35. ksadk/api/client.py +238 -204
  36. ksadk/api/environment.py +27 -0
  37. ksadk/builders/__init__.py +2 -0
  38. ksadk/builders/base.py +16 -14
  39. ksadk/builders/code_builder.py +375 -270
  40. ksadk/builders/container_builder.py +240 -200
  41. ksadk/builders/framework_requirements.py +31 -12
  42. ksadk/builders/ks3_uploader.py +60 -29
  43. ksadk/builders/managed_runtime_builder.py +173 -0
  44. ksadk/builders/mcp_builder.py +91 -92
  45. ksadk/builders/requirements_utils.py +1 -5
  46. ksadk/cli/__init__.py +97 -171
  47. ksadk/cli/cmd_a2a.py +255 -84
  48. ksadk/cli/cmd_agent.py +19 -5
  49. ksadk/cli/cmd_build.py +158 -29
  50. ksadk/cli/cmd_completion.py +47 -35
  51. ksadk/cli/cmd_config.py +267 -198
  52. ksadk/cli/cmd_create.py +507 -740
  53. ksadk/cli/cmd_dashboard.py +120 -45
  54. ksadk/cli/cmd_deploy.py +135 -59
  55. ksadk/cli/cmd_destroy.py +44 -14
  56. ksadk/cli/cmd_eval.py +386 -0
  57. ksadk/cli/cmd_files.py +63 -24
  58. ksadk/cli/cmd_hermes.py +313 -135
  59. ksadk/cli/cmd_invoke.py +184 -122
  60. ksadk/cli/cmd_launch.py +76 -28
  61. ksadk/cli/cmd_mcp.py +98 -64
  62. ksadk/cli/cmd_model.py +14 -5
  63. ksadk/cli/cmd_openclaw.py +564 -268
  64. ksadk/cli/cmd_replay.py +71 -0
  65. ksadk/cli/cmd_run.py +58 -73
  66. ksadk/cli/cmd_status.py +80 -31
  67. ksadk/cli/cmd_studio.py +110 -0
  68. ksadk/cli/cmd_version.py +20 -13
  69. ksadk/cli/cmd_web.py +95 -18
  70. ksadk/cli/deploy_utils.py +20 -20
  71. ksadk/cli/dry_run.py +7 -4
  72. ksadk/cli/env_options.py +83 -0
  73. ksadk/cli/error_utils.py +37 -12
  74. ksadk/cli/global_options.py +4 -3
  75. ksadk/cli/local_runtime.py +1 -2
  76. ksadk/cli/model_catalog.py +6 -2
  77. ksadk/cli/network_options.py +41 -19
  78. ksadk/cli/resource_common.py +6 -2
  79. ksadk/cli/runtime_bootstrap.py +51 -0
  80. ksadk/cli/storage.py +0 -1
  81. ksadk/cli/ui.py +11 -3
  82. ksadk/cli/workflow_common.py +52 -18
  83. ksadk/codex/__init__.py +12 -0
  84. ksadk/codex/client.py +1042 -0
  85. ksadk/codex/phase.py +143 -0
  86. ksadk/codex/runtime.py +940 -0
  87. ksadk/common/aicp_env.py +0 -1
  88. ksadk/common/auth.py +2 -2
  89. ksadk/common/constants.py +1 -3
  90. ksadk/common/kop_client.py +232 -0
  91. ksadk/common/llm_utils.py +17 -13
  92. ksadk/compat/__init__.py +5 -0
  93. ksadk/compat/adk_compat.py +200 -0
  94. ksadk/compat/copilotkit_a2ui.py +330 -0
  95. ksadk/configs/__init__.py +12 -14
  96. ksadk/configs/env_registry.py +661 -116
  97. ksadk/configs/global_config.py +3 -2
  98. ksadk/configs/settings.py +133 -209
  99. ksadk/conversations/__init__.py +85 -92
  100. ksadk/conversations/attachment_storage.py +16 -11
  101. ksadk/conversations/attachments.py +44 -36
  102. ksadk/conversations/compaction_pipeline.py +12 -12
  103. ksadk/conversations/compaction_prompt.py +9 -5
  104. ksadk/conversations/context.py +221 -6
  105. ksadk/conversations/message_projection.py +438 -51
  106. ksadk/conversations/model_context.py +12 -6
  107. ksadk/conversations/model_options.py +0 -1
  108. ksadk/conversations/normalize.py +20 -16
  109. ksadk/conversations/reasoning_markup.py +1 -1
  110. ksadk/conversations/runtime.py +339 -5255
  111. ksadk/conversations/runtime_compaction.py +333 -0
  112. ksadk/conversations/runtime_constants.py +27 -0
  113. ksadk/conversations/runtime_governance.py +185 -0
  114. ksadk/conversations/runtime_input.py +660 -0
  115. ksadk/conversations/runtime_invocation.py +395 -0
  116. ksadk/conversations/runtime_metadata.py +475 -0
  117. ksadk/conversations/runtime_observability.py +367 -0
  118. ksadk/conversations/runtime_payloads.py +272 -0
  119. ksadk/conversations/runtime_persistence.py +440 -0
  120. ksadk/conversations/runtime_preparation.py +426 -0
  121. ksadk/conversations/runtime_resume.py +836 -0
  122. ksadk/conversations/runtime_stream_events.py +902 -0
  123. ksadk/conversations/runtime_streaming.py +700 -0
  124. ksadk/conversations/semantic_summary.py +17 -5
  125. ksadk/conversations/session_title.py +6 -3
  126. ksadk/deployment/__init__.py +15 -16
  127. ksadk/deployment/agent_access.py +12 -23
  128. ksadk/deployment/base.py +51 -38
  129. ksadk/deployment/managed_runtime.py +33 -0
  130. ksadk/deployment/manager.py +85 -71
  131. ksadk/deployment/providers/serverless.py +379 -210
  132. ksadk/deployment/registry.py +38 -32
  133. ksadk/deployment/state.py +11 -11
  134. ksadk/deployment/ui_config.py +1 -1
  135. ksadk/detection/__init__.py +1 -1
  136. ksadk/detection/detector.py +146 -83
  137. ksadk/detection/mcp_detector.py +9 -6
  138. ksadk/evaluation/__init__.py +84 -0
  139. ksadk/evaluation/a2a_adapter.py +444 -0
  140. ksadk/evaluation/adapters.py +54 -0
  141. ksadk/evaluation/contracts.py +409 -0
  142. ksadk/evaluation/evalset.py +351 -0
  143. ksadk/evaluation/evaluators.py +461 -0
  144. ksadk/evaluation/executor.py +118 -0
  145. ksadk/evaluation/storage.py +68 -0
  146. ksadk/evaluation/target.py +73 -0
  147. ksadk/events/__init__.py +25 -0
  148. ksadk/events/parser.py +191 -0
  149. ksadk/events/replay.py +37 -0
  150. ksadk/events/runtime_event.py +273 -0
  151. ksadk/events/store.py +317 -0
  152. ksadk/harness/__init__.py +35 -0
  153. ksadk/harness/app.py +269 -0
  154. ksadk/harness/config.py +181 -0
  155. ksadk/harness/reasoner.py +105 -0
  156. ksadk/harness/runtime.py +364 -0
  157. ksadk/harness/sandbox.py +141 -0
  158. ksadk/harness/tools.py +222 -0
  159. ksadk/hermes_terminal.py +28 -12
  160. ksadk/identity/resolver.py +15 -9
  161. ksadk/ids.py +32 -0
  162. ksadk/knowledge_base/adk_tool.py +1 -1
  163. ksadk/knowledge_base/client.py +38 -33
  164. ksadk/managed_a2a_card.py +160 -0
  165. ksadk/managed_runtime.py +195 -0
  166. ksadk/markdown.py +5 -2
  167. ksadk/mcp_runtime/__init__.py +9 -6
  168. ksadk/memory/__init__.py +1 -1
  169. ksadk/memory/adk/__init__.py +2 -0
  170. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  171. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  172. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  173. ksadk/memory/adk/backends/sdk_ltm_backend.py +36 -36
  174. ksadk/memory/adk/long_term_memory.py +34 -37
  175. ksadk/memory/adk/resilient_session_service.py +4 -5
  176. ksadk/memory/adk/short_term_memory.py +12 -21
  177. ksadk/memory/adk_tool.py +3 -2
  178. ksadk/memory/backends/base.py +19 -19
  179. ksadk/memory/backends/memory.py +24 -25
  180. ksadk/memory/backends/redis.py +31 -30
  181. ksadk/memory/langchain_tool.py +2 -1
  182. ksadk/memory/manager.py +52 -47
  183. ksadk/memory/service.py +16 -6
  184. ksadk/memory/tool.py +4 -0
  185. ksadk/model_policy.py +15 -7
  186. ksadk/model_proxy/__init__.py +51 -0
  187. ksadk/model_proxy/bootstrap.py +80 -0
  188. ksadk/model_proxy/cache.py +165 -0
  189. ksadk/model_proxy/config.py +66 -0
  190. ksadk/model_proxy/demo_webui.py +115 -0
  191. ksadk/model_proxy/detect.py +145 -0
  192. ksadk/model_proxy/gate.py +68 -0
  193. ksadk/model_proxy/namespace.py +201 -0
  194. ksadk/model_proxy/protocol_proxy.py +22 -0
  195. ksadk/model_proxy/server.py +443 -0
  196. ksadk/model_proxy/transform.py +735 -0
  197. ksadk/openclaw_gateway.py +29 -14
  198. ksadk/runners/adk_runner.py +354 -146
  199. ksadk/runners/base_runner.py +71 -29
  200. ksadk/runners/deepagents_runner.py +0 -1
  201. ksadk/runners/factory.py +18 -5
  202. ksadk/runners/langchain_runner.py +29 -742
  203. ksadk/runners/langgraph_runner.py +417 -97
  204. ksadk/runners/patch_langchain.py +16 -16
  205. ksadk/runners/remote_runner.py +134 -33
  206. ksadk/runners/usage_accumulator.py +1 -0
  207. ksadk/runners/utils/__init__.py +0 -10
  208. ksadk/runners/utils/loader.py +23 -7
  209. ksadk/runtime/__init__.py +67 -0
  210. ksadk/runtime/adapter.py +412 -0
  211. ksadk/runtime/conversation_execution.py +553 -0
  212. ksadk/runtime/executor.py +251 -0
  213. ksadk/runtime/factory.py +156 -0
  214. ksadk/runtime/framework_adapters.py +118 -0
  215. ksadk/runtime/launch.py +48 -0
  216. ksadk/runtime/preprocessing.py +131 -0
  217. ksadk/runtime/runner_adapter.py +1032 -0
  218. ksadk/runtime/runner_loading.py +36 -0
  219. ksadk/runtime_context.py +18 -3
  220. ksadk/runtime_state.py +0 -1
  221. ksadk/sandbox/__init__.py +4 -1
  222. ksadk/sandbox/backends/__init__.py +4 -1
  223. ksadk/sandbox/backends/e2b.py +13 -5
  224. ksadk/sandbox/backends/local_process.py +11 -3
  225. ksadk/sandbox/base.py +10 -15
  226. ksadk/sandbox/factory.py +14 -4
  227. ksadk/sandbox/registry.py +57 -6
  228. ksadk/server/__init__.py +39 -4
  229. ksadk/server/api_models.py +15 -5
  230. ksadk/server/app.py +24 -3928
  231. ksadk/server/composition.py +47 -0
  232. ksadk/server/factory.py +622 -0
  233. ksadk/server/routes/__init__.py +1 -0
  234. ksadk/server/routes/checkpoint_resolution.py +116 -0
  235. ksadk/server/routes/common.py +682 -0
  236. ksadk/server/routes/control.py +331 -0
  237. ksadk/server/routes/dependencies.py +98 -0
  238. ksadk/server/routes/misc.py +425 -0
  239. ksadk/server/routes/models.py +451 -0
  240. ksadk/server/routes/openai_compat.py +229 -0
  241. ksadk/server/routes/projection.py +848 -0
  242. ksadk/server/routes/routers.py +52 -0
  243. ksadk/server/routes/run.py +519 -0
  244. ksadk/server/routes/sessions.py +644 -0
  245. ksadk/server/routes/streaming.py +265 -0
  246. ksadk/server/routes/workspace.py +414 -0
  247. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-CakCqxEN.js} +1 -1
  248. ksadk/server/static/assets/CodeBlock-CynLknyO.js +9 -0
  249. ksadk/server/static/assets/MathMessageMarkdown-3sQdOysS.js +8 -0
  250. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +303 -0
  251. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +1 -0
  252. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-DrgjcKhl.js} +1 -1
  253. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +1 -0
  254. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-BXoVRbQ6.js} +1 -1
  255. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-BnaB3Ywv.js} +1 -1
  256. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-Dw4weZcp.js} +1 -1
  257. ksadk/server/static/assets/channel-Dt3rQTFI.js +1 -0
  258. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-ChjpLK7j.js} +1 -1
  259. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-B9drTgOW.js} +1 -1
  260. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-CK4ROATy.js} +1 -1
  261. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B9KtTTgw.js} +1 -1
  262. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-1O1mki3m.js} +1 -1
  263. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-B_fH2YuH.js} +1 -1
  264. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D6rFLaVV.js} +1 -1
  265. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +1 -0
  266. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +1 -0
  267. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +1 -0
  268. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-ChNs_Mdk.js} +1 -1
  269. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-D0QXg_MQ.js} +1 -1
  270. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-DBDDKwBQ.js} +1 -1
  271. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-BkwDOxmO.js} +1 -1
  272. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Bv8waYvY.js} +1 -1
  273. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-e1VECDdu.js} +1 -1
  274. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-Ds9adfRl.js} +1 -1
  275. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-4N2NMVYg.js} +1 -1
  276. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-B-jNR6Vn.js} +1 -1
  277. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-B5J2uhNk.js} +1 -1
  278. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-B5oS1zcj.js} +1 -1
  279. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BB3-OwTQ.js} +1 -1
  280. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-BIlKoW0k.js} +1 -1
  281. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-BjOdlDjj.js} +1 -1
  282. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-C1sz5Eo3.js} +1 -1
  283. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-CWV4lDr1.js} +1 -1
  284. ksadk/server/static/assets/{dist-D-xOD048.js → dist-CmZHzlwZ.js} +1 -1
  285. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-D6MpCZtz.js} +1 -1
  286. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-DJN_6Y39.js} +1 -1
  287. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-DQU9EWw-.js} +1 -1
  288. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-DWKByxWV.js} +1 -1
  289. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-Dli7qW-B.js} +1 -1
  290. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-DsHPdk3W.js} +1 -1
  291. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-X4TnqwOK.js} +1 -1
  292. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-_pHPTyZ-.js} +1 -1
  293. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-fhOuWds5.js} +1 -1
  294. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-gLnrGsoU.js} +1 -1
  295. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-j6Dxmf_j.js} +1 -1
  296. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-o2OVmKvt.js} +1 -1
  297. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-BPItFKH6.js} +1 -1
  298. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +1 -0
  299. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-BbYB9w5Y.js} +1 -1
  300. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-TQXa0KXv.js} +1 -1
  301. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +1 -0
  302. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-bKblCnpa.js} +1 -1
  303. ksadk/server/static/assets/index-R52pJJvg.js +177 -0
  304. ksadk/server/static/assets/index-jJr3_955.css +2 -0
  305. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +1 -0
  306. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-BGVT7J_i.js} +1 -1
  307. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-K0OOfVna.js} +1 -1
  308. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-8LeOJaZp.js} +1 -1
  309. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-C6uCgZrW.js} +1 -1
  310. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-CYWseY5E.js} +1 -1
  311. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-hdBcYzM8.js} +1 -1
  312. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-BWrDjSZM.js} +2 -2
  313. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-Bjy_2v4_.js} +1 -1
  314. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +1 -0
  315. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +1 -0
  316. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-GmmqNiVH.js} +1 -1
  317. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-BmkRbwCg.js} +1 -1
  318. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +1 -0
  319. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DSWFqL7c.js} +1 -1
  320. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-l6iXmU65.js} +1 -1
  321. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-Cq6UBeQB.js} +1 -1
  322. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-qzTQpg3Y.js} +1 -1
  323. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +1 -0
  324. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-DEz24vV0.js} +1 -1
  325. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +1 -0
  326. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +1 -0
  327. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-CMCixki2.js} +1 -1
  328. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +1 -0
  329. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-CVzB8F9K.js} +1 -1
  330. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-BFBqBLc1.js} +1 -1
  331. ksadk/server/static/index.html +2 -2
  332. ksadk/server/terminal_sessions.py +111 -58
  333. ksadk/sessions/__init__.py +69 -19
  334. ksadk/sessions/base.py +31 -11
  335. ksadk/sessions/continuity.py +2 -3
  336. ksadk/sessions/in_memory.py +46 -2
  337. ksadk/sessions/local_service.py +148 -17
  338. ksadk/sessions/postgres_service.py +143 -25
  339. ksadk/sessions/resilient.py +36 -8
  340. ksadk/skills/models.py +17 -5
  341. ksadk/skills/package_store.py +5 -2
  342. ksadk/skills/runtime/agent.py +20 -5
  343. ksadk/skills/runtime/artifacts.py +1 -5
  344. ksadk/skills/runtime/backends/e2b.py +10 -4
  345. ksadk/skills/runtime/backends/local.py +15 -3
  346. ksadk/skills/runtime/base.py +1 -2
  347. ksadk/skills/runtime/executor.py +10 -3
  348. ksadk/skills/runtime/factory.py +4 -2
  349. ksadk/skills/runtime/loader.py +1 -2
  350. ksadk/skills/runtime/registry.py +15 -10
  351. ksadk/skills/runtime/request.py +3 -1
  352. ksadk/skills/service_client.py +47 -16
  353. ksadk/skills/tool_defs.py +15 -10
  354. ksadk/studio/__init__.py +5 -0
  355. ksadk/studio/agent_avatar_assets.py +120 -0
  356. ksadk/studio/agent_lifecycle.py +51 -0
  357. ksadk/studio/api.py +1046 -0
  358. ksadk/studio/api_catalog_routes.py +324 -0
  359. ksadk/studio/api_contracts.py +177 -0
  360. ksadk/studio/api_helpers.py +132 -0
  361. ksadk/studio/authoring.py +524 -0
  362. ksadk/studio/authoring_coordinator.py +285 -0
  363. ksadk/studio/builder.py +320 -0
  364. ksadk/studio/capabilities.py +351 -0
  365. ksadk/studio/cloud.py +278 -0
  366. ksadk/studio/codex_agent_service.py +583 -0
  367. ksadk/studio/codex_builder.py +324 -0
  368. ksadk/studio/codex_manifest.py +278 -0
  369. ksadk/studio/codex_run.py +352 -0
  370. ksadk/studio/compiler.py +189 -0
  371. ksadk/studio/contracts.py +644 -0
  372. ksadk/studio/errors.py +47 -0
  373. ksadk/studio/evaluation.py +192 -0
  374. ksadk/studio/event_store.py +284 -0
  375. ksadk/studio/framework_run.py +114 -0
  376. ksadk/studio/identifiers.py +33 -0
  377. ksadk/studio/mcp_runtime.py +229 -0
  378. ksadk/studio/model_client.py +543 -0
  379. ksadk/studio/model_profile_service.py +214 -0
  380. ksadk/studio/operations.py +243 -0
  381. ksadk/studio/otel_trace.py +754 -0
  382. ksadk/studio/pagination.py +182 -0
  383. ksadk/studio/python_tool_inspection.py +183 -0
  384. ksadk/studio/repository.py +306 -0
  385. ksadk/studio/resource_catalog.py +1217 -0
  386. ksadk/studio/run_service.py +707 -0
  387. ksadk/studio/runtime_catalog.py +74 -0
  388. ksadk/studio/runtime_source.py +339 -0
  389. ksadk/studio/service.py +1141 -0
  390. ksadk/studio/shared_web.py +994 -0
  391. ksadk/studio/skill_discovery.py +628 -0
  392. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  393. ksadk/studio/static/assets/index-CaEN0eTR.css +1 -0
  394. ksadk/studio/static/assets/index-DI2eY_NH.js +252 -0
  395. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  396. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  397. ksadk/studio/static/index.html +15 -0
  398. ksadk/studio/templates.py +630 -0
  399. ksadk/studio/validator.py +246 -0
  400. ksadk/studio/workspace.py +137 -0
  401. ksadk/terminal_exec_policy.py +3 -4
  402. ksadk/tools/gateway.py +101 -15
  403. ksadk/tools/result_budget.py +12 -4
  404. ksadk/toolsets/__init__.py +161 -37
  405. ksadk/toolsets/a2a.py +426 -0
  406. ksadk/toolsets/platform.py +21 -5
  407. ksadk/toolsets/sandbox.py +82 -16
  408. ksadk/toolsets/skills.py +200 -39
  409. ksadk/toolsets/web.py +123 -26
  410. ksadk/toolsets/workspace.py +212 -101
  411. ksadk/tracing/__init__.py +19 -21
  412. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  413. ksadk/tracing/setup.py +135 -506
  414. ksadk/tracing/span_utils.py +0 -41
  415. ksadk/tui/loop.py +78 -30
  416. ksadk/tui/stream_render.py +1 -0
  417. ksadk/ui_config.py +5 -3
  418. ksadk/version.py +1 -1
  419. ksadk_runtime_common/memory_backend/registry.py +56 -56
  420. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  421. ksadk_runtime_common/workspace_files/router.py +8 -2
  422. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  423. ksadk/a2a/card_builder.py +0 -64
  424. ksadk/a2a/client.py +0 -309
  425. ksadk/runners/utils/langfuse.py +0 -230
  426. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  427. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  428. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  429. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  430. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  431. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  432. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  435. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  436. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  437. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  438. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  439. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  440. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  441. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  442. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  443. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  444. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  445. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  446. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  447. ksadk/tracing/exporters/langfuse_exporter.py +0 -445
  448. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/entry_points.txt +0 -0
  449. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/licenses/LICENSE +0 -0
  450. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1056 @@
1
+ """Typed client for the AgentEngine A2A runtime control-plane contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import base64
7
+ import binascii
8
+ import errno
9
+ import json
10
+ import os
11
+ import stat
12
+ import time
13
+ import uuid
14
+ from abc import ABC, abstractmethod
15
+ from dataclasses import dataclass, field
16
+ from pathlib import Path
17
+ from typing import Any, Literal, get_args
18
+
19
+ import httpx
20
+ from a2a.types import AgentCard
21
+ from google.protobuf.json_format import ParseDict, ParseError
22
+
23
+ from ksadk.a2a.ids import require_a2a_resource_id
24
+ from ksadk.common.kop_client import KOPClient, KOPError
25
+
26
+ ENV_A2A_CONTROL_PLANE_URL = "KSADK_A2A_CONTROL_PLANE_URL"
27
+ ENV_A2A_TOKEN_DIR = "KSADK_A2A_TOKEN_DIR"
28
+ DEFAULT_A2A_TOKEN_DIR = "/var/run/secrets/agentengine/a2a"
29
+ MAX_WORKLOAD_TOKEN_BYTES = 16 * 1024
30
+
31
+ AUDIENCE_REGISTRY = "a2a-registry"
32
+ AUDIENCE_TASK_SINK = "a2a-task-sink"
33
+ AUDIENCE_CREDENTIAL_BROKER = "credential-broker"
34
+ AUDIENCE_GATEWAY = "a2a-gateway"
35
+
36
+ A2AOperation = Literal[
37
+ "send_message",
38
+ "get_task",
39
+ "subscribe_to_task",
40
+ "cancel_task",
41
+ ]
42
+ A2A_OPERATIONS = frozenset(get_args(A2AOperation))
43
+
44
+ A2AInternalAction = Literal[
45
+ "ListA2ASpaceAgents",
46
+ "PrepareA2ACall",
47
+ "PrepareA2ATaskOperation",
48
+ "BindA2ARemoteTask",
49
+ "AppendA2ATaskEvents",
50
+ "ResolveA2ACredential",
51
+ ]
52
+ A2A_INTERNAL_ACTIONS = frozenset(get_args(A2AInternalAction))
53
+ A2A_INTERNAL_PATH_PREFIX = "/agentengine/internal/v1/a2a"
54
+
55
+
56
+ def build_a2a_internal_action_path(action: A2AInternalAction) -> str:
57
+ """Return the explicitly registered Runtime internal Action path."""
58
+
59
+ if action not in A2A_INTERNAL_ACTIONS:
60
+ raise ValueError(f"unsupported A2A internal Action: {action}")
61
+ return f"{A2A_INTERNAL_PATH_PREFIX}/{action}"
62
+
63
+
64
+ class A2AControlPlaneError(RuntimeError):
65
+ """Stable error returned by an AgentEngine A2A Action."""
66
+
67
+ def __init__(
68
+ self,
69
+ *,
70
+ code: int,
71
+ message: str,
72
+ error_code: str,
73
+ retryable: bool = False,
74
+ field: str | None = None,
75
+ details: dict[str, Any] | None = None,
76
+ request_id: str = "",
77
+ action: str = "",
78
+ ) -> None:
79
+ super().__init__(f"{error_code}: {message}" if error_code else message)
80
+ self.code = code
81
+ self.message = message
82
+ self.error_code = error_code
83
+ self.retryable = retryable
84
+ self.field = field
85
+ self.details = dict(details or {})
86
+ self.request_id = request_id
87
+ self.action = action
88
+
89
+
90
+ class WorkloadTokenProvider(ABC):
91
+ """Returns a short-lived workload token for one exact audience."""
92
+
93
+ @abstractmethod
94
+ def get_token(self, audience: str) -> str:
95
+ raise NotImplementedError
96
+
97
+
98
+ class FileWorkloadTokenProvider(WorkloadTokenProvider):
99
+ """Reads audience-specific projected JWT files before every request."""
100
+
101
+ def __init__(self, token_dir: str | os.PathLike[str] | None = None) -> None:
102
+ configured = token_dir or os.getenv(ENV_A2A_TOKEN_DIR) or DEFAULT_A2A_TOKEN_DIR
103
+ self._token_dir = Path(configured)
104
+
105
+ def get_token(self, audience: str) -> str:
106
+ if not audience or "/" in audience or audience in {".", ".."}:
107
+ raise ValueError(f"invalid workload token audience: {audience!r}")
108
+ path = self._token_dir / f"{audience}.jwt"
109
+ if path.is_symlink():
110
+ raise RuntimeError(
111
+ f"workload token for audience {audience!r} must not be a symlink: {path}"
112
+ )
113
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
114
+ try:
115
+ descriptor = os.open(path, flags)
116
+ except OSError as exc:
117
+ if exc.errno == errno.ELOOP:
118
+ raise RuntimeError(
119
+ f"workload token for audience {audience!r} must not be a symlink: {path}"
120
+ ) from exc
121
+ raise RuntimeError(f"missing workload token for audience {audience!r}: {path}") from exc
122
+ try:
123
+ token_stat = os.fstat(descriptor)
124
+ if not stat.S_ISREG(token_stat.st_mode):
125
+ raise RuntimeError(
126
+ f"workload token for audience {audience!r} is not a regular file: {path}"
127
+ )
128
+ if token_stat.st_size > MAX_WORKLOAD_TOKEN_BYTES:
129
+ raise RuntimeError(
130
+ f"workload token for audience {audience!r} exceeds 16 KiB: {path}"
131
+ )
132
+ if stat.S_IMODE(token_stat.st_mode) != 0o400:
133
+ raise RuntimeError(
134
+ f"workload token for audience {audience!r} must have mode 0400: {path}"
135
+ )
136
+ with os.fdopen(descriptor, "rb", closefd=False) as token_file:
137
+ raw_token = token_file.read(MAX_WORKLOAD_TOKEN_BYTES + 1)
138
+ if len(raw_token) > MAX_WORKLOAD_TOKEN_BYTES:
139
+ raise RuntimeError(
140
+ f"workload token for audience {audience!r} exceeds 16 KiB: {path}"
141
+ )
142
+ token = raw_token.decode("ascii").strip()
143
+ except UnicodeError as exc:
144
+ raise RuntimeError(
145
+ f"workload token for audience {audience!r} must be ASCII: {path}"
146
+ ) from exc
147
+ finally:
148
+ os.close(descriptor)
149
+ if not token:
150
+ raise RuntimeError(f"empty workload token for audience {audience!r}: {path}")
151
+ self._validate_expiry(token, audience=audience, path=path)
152
+ return token
153
+
154
+ @staticmethod
155
+ def _validate_expiry(token: str, *, audience: str, path: Path) -> None:
156
+ parts = token.split(".")
157
+ if len(parts) != 3:
158
+ raise RuntimeError(f"workload token for audience {audience!r} is not a JWT: {path}")
159
+ try:
160
+ payload_segment = parts[1] + "=" * (-len(parts[1]) % 4)
161
+ payload = json.loads(base64.urlsafe_b64decode(payload_segment).decode("utf-8"))
162
+ expires_at = int(payload["exp"])
163
+ except (binascii.Error, KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
164
+ raise RuntimeError(
165
+ f"workload token for audience {audience!r} has no valid exp claim: {path}"
166
+ ) from exc
167
+ token_audience = payload.get("aud")
168
+ accepted_audiences = (
169
+ {str(item) for item in token_audience}
170
+ if isinstance(token_audience, list)
171
+ else {str(token_audience)}
172
+ )
173
+ if audience not in accepted_audiences:
174
+ raise RuntimeError(f"workload token audience mismatch for {audience!r}: {path}")
175
+ if expires_at <= int(time.time()):
176
+ raise RuntimeError(f"workload token for audience {audience!r} is expired: {path}")
177
+
178
+
179
+ @dataclass(frozen=True)
180
+ class A2ARouteInterface:
181
+ url: str
182
+ protocol_binding: str
183
+ protocol_version: str
184
+
185
+
186
+ @dataclass(frozen=True)
187
+ class A2ARoute:
188
+ kind: str
189
+ interface: A2ARouteInterface
190
+
191
+
192
+ @dataclass(frozen=True)
193
+ class A2ATarget:
194
+ agent_id: str
195
+ version_id: str
196
+ card_sha256: str
197
+
198
+
199
+ @dataclass(frozen=True)
200
+ class RemoteTaskReference:
201
+ remote_task_id: str
202
+ remote_context_id: str | None = None
203
+
204
+
205
+ @dataclass(frozen=True)
206
+ class PreparedA2AOperation:
207
+ platform_task_id: str
208
+ target: A2ATarget
209
+ route: A2ARoute
210
+ call_permit: str
211
+ call_permit_expires_at: str
212
+ credential_handle: str | None = None
213
+ remote_task: RemoteTaskReference | None = None
214
+
215
+
216
+ @dataclass(frozen=True)
217
+ class CredentialInjection:
218
+ headers: dict[str, str] = field(default_factory=dict)
219
+ query: dict[str, str] = field(default_factory=dict)
220
+ cookies: dict[str, str] = field(default_factory=dict)
221
+ expires_at: str | None = None
222
+
223
+
224
+ @dataclass
225
+ class DiscoveredAgent:
226
+ agent_id: str
227
+ version_id: str
228
+ source: str
229
+ agent_card: AgentCard
230
+ card_sha256: str = ""
231
+ callable: bool = True
232
+ blocked_reason: str | None = None
233
+ route_kind: str = ""
234
+
235
+
236
+ @dataclass
237
+ class SpaceAgentPage:
238
+ agents: list[DiscoveredAgent] = field(default_factory=list)
239
+ etag: str | None = None
240
+ next_cursor: str | None = None
241
+ not_modified: bool = False
242
+
243
+
244
+ class A2AControlPlane(ABC):
245
+ """Control-plane seam required by the product A2ASpaceClient."""
246
+
247
+ @abstractmethod
248
+ async def list_space_agents(
249
+ self,
250
+ space_id: str,
251
+ *,
252
+ prompt: str | None = None,
253
+ skill_id: str | None = None,
254
+ include_blocked: bool = False,
255
+ if_none_match: str | None = None,
256
+ cursor: str | None = None,
257
+ page_size: int = 50,
258
+ ) -> SpaceAgentPage:
259
+ raise NotImplementedError
260
+
261
+ @abstractmethod
262
+ async def prepare_call(
263
+ self,
264
+ *,
265
+ space_id: str,
266
+ target_agent_id: str,
267
+ expected_version_id: str | None,
268
+ message_id: str,
269
+ message_sha256: str,
270
+ idempotency_token: str,
271
+ ) -> PreparedA2AOperation:
272
+ raise NotImplementedError
273
+
274
+ @abstractmethod
275
+ async def prepare_task_operation(
276
+ self,
277
+ *,
278
+ platform_task_id: str,
279
+ operation: A2AOperation,
280
+ message_id: str | None = None,
281
+ message_sha256: str | None = None,
282
+ idempotency_token: str | None = None,
283
+ ) -> PreparedA2AOperation:
284
+ raise NotImplementedError
285
+
286
+ @abstractmethod
287
+ async def bind_remote_task(
288
+ self,
289
+ *,
290
+ platform_task_id: str,
291
+ remote_task_id: str,
292
+ remote_context_id: str | None,
293
+ observed_at: str,
294
+ ) -> dict[str, Any]:
295
+ raise NotImplementedError
296
+
297
+ @abstractmethod
298
+ async def append_task_events(
299
+ self,
300
+ *,
301
+ platform_task_id: str,
302
+ events: list[dict[str, Any]],
303
+ ) -> dict[str, Any]:
304
+ raise NotImplementedError
305
+
306
+ @abstractmethod
307
+ async def resolve_credential(
308
+ self,
309
+ *,
310
+ platform_task_id: str,
311
+ credential_handle: str | None,
312
+ call_permit: str,
313
+ ) -> CredentialInjection:
314
+ raise NotImplementedError
315
+
316
+ @abstractmethod
317
+ def gateway_token(self) -> str:
318
+ raise NotImplementedError
319
+
320
+
321
+ class InternalA2AControlPlaneClient(A2AControlPlane):
322
+ """Workload-authenticated client for Runtime internal A2A Actions."""
323
+
324
+ def __init__(
325
+ self,
326
+ base_url: str,
327
+ *,
328
+ token_provider: WorkloadTokenProvider | None = None,
329
+ httpx_client: httpx.AsyncClient | None = None,
330
+ timeout: float = 15.0,
331
+ ) -> None:
332
+ if not base_url:
333
+ raise ValueError(
334
+ f"InternalA2AControlPlaneClient requires {ENV_A2A_CONTROL_PLANE_URL}"
335
+ )
336
+ self._base_url = base_url.rstrip("/")
337
+ self._token_provider = token_provider or FileWorkloadTokenProvider()
338
+ self._client = httpx_client
339
+ self._timeout = timeout
340
+
341
+ async def _post(
342
+ self,
343
+ action: A2AInternalAction,
344
+ *,
345
+ audience: str,
346
+ payload: dict[str, Any],
347
+ ) -> dict[str, Any]:
348
+ token = self._token_provider.get_token(audience)
349
+ client = self._client or httpx.AsyncClient(timeout=self._timeout)
350
+ owns_client = self._client is None
351
+ try:
352
+ try:
353
+ response = await client.post(
354
+ f"{self._base_url}{build_a2a_internal_action_path(action)}",
355
+ json=payload,
356
+ headers={"Authorization": f"Bearer {token}"},
357
+ )
358
+ except httpx.HTTPError as exc:
359
+ raise A2AControlPlaneError(
360
+ code=503,
361
+ message="A2A control plane is unavailable",
362
+ error_code="A2A_CONTROL_PLANE_UNAVAILABLE",
363
+ retryable=True,
364
+ action=action,
365
+ ) from exc
366
+ try:
367
+ envelope = response.json()
368
+ except ValueError:
369
+ unavailable = response.status_code >= 500
370
+ raise A2AControlPlaneError(
371
+ code=response.status_code if unavailable else 502,
372
+ message="A2A control plane returned a non-JSON response",
373
+ error_code=(
374
+ "A2A_CONTROL_PLANE_UNAVAILABLE"
375
+ if unavailable
376
+ else "A2A_CONTROL_PLANE_INVALID_RESPONSE"
377
+ ),
378
+ retryable=unavailable,
379
+ action=action,
380
+ )
381
+ if not isinstance(envelope, dict):
382
+ raise A2AControlPlaneError(
383
+ code=response.status_code if response.is_error else 502,
384
+ message="A2A control plane response must be an object",
385
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
386
+ retryable=response.status_code >= 500,
387
+ action=action,
388
+ )
389
+ try:
390
+ action_code = int(envelope.get("Code") or 0)
391
+ except (TypeError, ValueError) as exc:
392
+ raise A2AControlPlaneError(
393
+ code=502,
394
+ message="A2A control plane response Code must be an integer",
395
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
396
+ request_id=str(envelope.get("RequestId") or ""),
397
+ action=action,
398
+ ) from exc
399
+ if response.is_error or action_code != 0:
400
+ self._raise_action_error(response, envelope, action)
401
+ data = envelope.get("Data")
402
+ if not isinstance(data, dict):
403
+ raise A2AControlPlaneError(
404
+ code=response.status_code,
405
+ message="A2A control plane response Data must be an object",
406
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
407
+ request_id=str(envelope.get("RequestId") or ""),
408
+ action=action,
409
+ )
410
+ return data
411
+ finally:
412
+ if owns_client:
413
+ await client.aclose()
414
+
415
+ @staticmethod
416
+ def _raise_action_error(
417
+ response: httpx.Response,
418
+ envelope: dict[str, Any],
419
+ action: str,
420
+ ) -> None:
421
+ raw_data = envelope.get("Data")
422
+ data: dict[str, Any] = raw_data if isinstance(raw_data, dict) else {}
423
+ raise A2AControlPlaneError(
424
+ code=int(envelope.get("Code") or response.status_code),
425
+ message=str(envelope.get("Message") or response.reason_phrase or "A2A action failed"),
426
+ error_code=str(data.get("ErrorCode") or "A2A_CONTROL_PLANE_ERROR"),
427
+ retryable=bool(data.get("Retryable", response.status_code >= 500)),
428
+ field=str(data.get("Field")) if data.get("Field") is not None else None,
429
+ details=data.get("Details") if isinstance(data.get("Details"), dict) else {},
430
+ request_id=str(envelope.get("RequestId") or ""),
431
+ action=str(envelope.get("Action") or action),
432
+ )
433
+
434
+ async def list_space_agents(
435
+ self,
436
+ space_id: str,
437
+ *,
438
+ prompt: str | None = None,
439
+ skill_id: str | None = None,
440
+ include_blocked: bool = False,
441
+ if_none_match: str | None = None,
442
+ cursor: str | None = None,
443
+ page_size: int = 50,
444
+ ) -> SpaceAgentPage:
445
+ payload: dict[str, Any] = {"A2ASpaceId": space_id}
446
+ if prompt:
447
+ payload["Prompt"] = prompt
448
+ if skill_id:
449
+ payload["SkillId"] = skill_id
450
+ if include_blocked:
451
+ payload["IncludeBlocked"] = True
452
+ if if_none_match:
453
+ payload["IfNoneMatch"] = if_none_match
454
+ if cursor:
455
+ payload["Cursor"] = cursor
456
+ if page_size != 50:
457
+ payload["PageSize"] = page_size
458
+ data = await self._post(
459
+ "ListA2ASpaceAgents",
460
+ audience=AUDIENCE_REGISTRY,
461
+ payload=payload,
462
+ )
463
+ return SpaceAgentPage(
464
+ agents=[_discovered_agent_from_wire(item) for item in data.get("Agents") or []],
465
+ etag=_optional_str(data.get("ETag")),
466
+ next_cursor=_optional_str(data.get("NextCursor")),
467
+ not_modified=bool(data.get("NotModified", False)),
468
+ )
469
+
470
+ async def prepare_call(
471
+ self,
472
+ *,
473
+ space_id: str,
474
+ target_agent_id: str,
475
+ expected_version_id: str | None,
476
+ message_id: str,
477
+ message_sha256: str,
478
+ idempotency_token: str,
479
+ ) -> PreparedA2AOperation:
480
+ payload: dict[str, Any] = {
481
+ "A2ASpaceId": space_id,
482
+ "TargetA2AAgentId": target_agent_id,
483
+ "MessageId": message_id,
484
+ "MessageSha256": message_sha256,
485
+ "IdempotencyToken": idempotency_token,
486
+ }
487
+ if expected_version_id:
488
+ payload["ExpectedVersionId"] = expected_version_id
489
+ data = await self._post("PrepareA2ACall", audience=AUDIENCE_REGISTRY, payload=payload)
490
+ return _prepared_operation_from_wire(data)
491
+
492
+ async def prepare_task_operation(
493
+ self,
494
+ *,
495
+ platform_task_id: str,
496
+ operation: A2AOperation,
497
+ message_id: str | None = None,
498
+ message_sha256: str | None = None,
499
+ idempotency_token: str | None = None,
500
+ ) -> PreparedA2AOperation:
501
+ _validate_task_operation_request(
502
+ operation=operation,
503
+ message_id=message_id,
504
+ message_sha256=message_sha256,
505
+ idempotency_token=idempotency_token,
506
+ )
507
+ payload: dict[str, Any] = {"A2ATaskId": platform_task_id, "Operation": operation}
508
+ if message_id:
509
+ payload["MessageId"] = message_id
510
+ if message_sha256:
511
+ payload["MessageSha256"] = message_sha256
512
+ if idempotency_token:
513
+ payload["IdempotencyToken"] = idempotency_token
514
+ data = await self._post(
515
+ "PrepareA2ATaskOperation",
516
+ audience=AUDIENCE_REGISTRY,
517
+ payload=payload,
518
+ )
519
+ return _prepared_operation_from_wire(data)
520
+
521
+ async def bind_remote_task(
522
+ self,
523
+ *,
524
+ platform_task_id: str,
525
+ remote_task_id: str,
526
+ remote_context_id: str | None,
527
+ observed_at: str,
528
+ ) -> dict[str, Any]:
529
+ payload: dict[str, Any] = {
530
+ "A2ATaskId": platform_task_id,
531
+ "RemoteTaskId": remote_task_id,
532
+ "ObservedAt": observed_at,
533
+ }
534
+ if remote_context_id:
535
+ payload["RemoteContextId"] = remote_context_id
536
+ return await self._post(
537
+ "BindA2ARemoteTask",
538
+ audience=AUDIENCE_TASK_SINK,
539
+ payload=payload,
540
+ )
541
+
542
+ async def append_task_events(
543
+ self,
544
+ *,
545
+ platform_task_id: str,
546
+ events: list[dict[str, Any]],
547
+ ) -> dict[str, Any]:
548
+ return await self._post(
549
+ "AppendA2ATaskEvents",
550
+ audience=AUDIENCE_TASK_SINK,
551
+ payload={"A2ATaskId": platform_task_id, "Events": events},
552
+ )
553
+
554
+ async def resolve_credential(
555
+ self,
556
+ *,
557
+ platform_task_id: str,
558
+ credential_handle: str | None,
559
+ call_permit: str,
560
+ ) -> CredentialInjection:
561
+ data = await self._post(
562
+ "ResolveA2ACredential",
563
+ audience=AUDIENCE_CREDENTIAL_BROKER,
564
+ payload={
565
+ "A2ATaskId": platform_task_id,
566
+ "CredentialHandle": credential_handle,
567
+ "CallPermit": call_permit,
568
+ },
569
+ )
570
+ raw_injection = data.get("Injection")
571
+ injection: dict[str, Any] = raw_injection if isinstance(raw_injection, dict) else {}
572
+ return CredentialInjection(
573
+ headers=_credential_string_map(
574
+ injection.get("Headers"), field_name="Injection.Headers"
575
+ ),
576
+ query=_credential_string_map(injection.get("Query"), field_name="Injection.Query"),
577
+ cookies=_credential_string_map(
578
+ injection.get("Cookies"), field_name="Injection.Cookies"
579
+ ),
580
+ expires_at=_optional_str(data.get("ExpiresAt")),
581
+ )
582
+
583
+ def gateway_token(self) -> str:
584
+ return self._token_provider.get_token(AUDIENCE_GATEWAY)
585
+
586
+
587
+ class A2AAgentCardClient(A2AControlPlane):
588
+ """Card-driven control-plane client for hosted/external A2A discovery.
589
+
590
+ 发现面通过 KOP 调 server 对外 Action (ListAToASpaceAgents) 拿到 space 内
591
+ 每个 agent 的完整投影后 AgentCard(含 callable url);调用面直接用
592
+ card.url 发 A2A 请求,不回连 server 做 permit/credential。
593
+ runtime 只需一个 service_url(KOP 对外 API,默认 aicp.api.ksyun.com),
594
+ 与 ksadk/skills 同款;card.url 由 server 投影,runtime 不需要知道 gateway 域名。
595
+
596
+ KOP 鉴权(AICP AK/SK 签名)复用 ksadk.common.kop_client.KOPClient,
597
+ 和 skill/mcp 共用同一公共层。
598
+ """
599
+
600
+ def __init__(
601
+ self,
602
+ service_url: str,
603
+ *,
604
+ service_token: str = "",
605
+ httpx_client: httpx.AsyncClient | None = None,
606
+ timeout: float = 15.0,
607
+ ) -> None:
608
+ if not service_url:
609
+ raise ValueError("A2AAgentCardClient requires a non-empty service_url")
610
+ self._service_url = service_url.rstrip("/")
611
+ self._service_token = service_token
612
+ self._client = httpx_client
613
+ self._timeout = timeout
614
+ self._cards_by_agent: dict[str, tuple[AgentCard, str, str]] = {}
615
+ self._kop = KOPClient(base_url=service_url, service_token=service_token, timeout=timeout)
616
+
617
+ async def _post_action(self, action: str, payload: dict[str, Any]) -> dict[str, Any]:
618
+ try:
619
+ return await asyncio.to_thread(self._kop.post_action, action, payload)
620
+ except KOPError as exc:
621
+ raise A2AControlPlaneError(
622
+ code=exc.code,
623
+ message=exc.message,
624
+ error_code=f"KOP_{exc.code}",
625
+ retryable=exc.code >= 500,
626
+ action=action, # type: ignore[arg-type]
627
+ ) from exc
628
+
629
+ async def list_space_agents(
630
+ self,
631
+ space_id: str,
632
+ *,
633
+ prompt: str | None = None,
634
+ skill_id: str | None = None,
635
+ include_blocked: bool = False,
636
+ if_none_match: str | None = None,
637
+ cursor: str | None = None,
638
+ page_size: int = 50,
639
+ ) -> SpaceAgentPage:
640
+ _ = (prompt, skill_id, if_none_match)
641
+ payload: dict[str, Any] = {
642
+ "A2ASpaceId": space_id,
643
+ "Status": "available" if not include_blocked else "",
644
+ "PageSize": page_size,
645
+ }
646
+ if cursor:
647
+ payload["PageNumber"] = int(cursor) if str(cursor).isdigit() else 1
648
+ data = await self._post_action("ListAToASpaceAgents", payload)
649
+ agents: list[DiscoveredAgent] = []
650
+ for item in data.get("Agents") or []:
651
+ if not isinstance(item, dict):
652
+ continue
653
+ a2a_agent_id = str(item.get("A2AAgentId") or "").strip()
654
+ if not a2a_agent_id:
655
+ continue
656
+ # server 用纯 uuid,ksadk 内部要求 a2a-agent- 前缀;补前缀适配契约。
657
+ if not a2a_agent_id.startswith("a2a-agent-"):
658
+ a2a_agent_id = f"a2a-agent-{a2a_agent_id}"
659
+ invocation_status = str(item.get("InvocationStatus") or "")
660
+ if invocation_status and invocation_status != "available":
661
+ continue
662
+ card_payload = item.get("AgentCard")
663
+ if not isinstance(card_payload, dict):
664
+ continue
665
+ try:
666
+ card = _agent_card_from_wire(card_payload)
667
+ except A2AControlPlaneError:
668
+ continue
669
+ sha = str(item.get("CardSha256") or "")
670
+ version_id = str(item.get("VersionId") or "") or sha
671
+ if version_id and not version_id.startswith("a2a-version-"):
672
+ version_id = f"a2a-version-{version_id}"
673
+ source = str(item.get("Source") or "hosted")
674
+ self._cards_by_agent[a2a_agent_id] = (card, sha, version_id)
675
+ agents.append(
676
+ DiscoveredAgent(
677
+ agent_id=a2a_agent_id,
678
+ version_id=version_id,
679
+ source=source if source in {"hosted", "external"} else "hosted",
680
+ agent_card=card,
681
+ card_sha256=sha,
682
+ callable=True,
683
+ route_kind="hosted_gateway" if source == "hosted" else "external_public",
684
+ )
685
+ )
686
+ return SpaceAgentPage(agents=agents)
687
+
688
+ async def _resolve_card(self, target_agent_id: str) -> tuple[AgentCard, str, str]:
689
+ cached = self._cards_by_agent.get(target_agent_id)
690
+ if cached is not None:
691
+ return cached
692
+ await self.list_space_agents(os.getenv("KSADK_A2A_SPACE_ID", ""), page_size=100)
693
+ cached = self._cards_by_agent.get(target_agent_id)
694
+ if cached is None:
695
+ raise A2AControlPlaneError(
696
+ code=404,
697
+ message=f"A2A agent {target_agent_id} not discoverable",
698
+ error_code="A2A_AGENT_NOT_FOUND",
699
+ action="PrepareA2ACall", # type: ignore[arg-type]
700
+ )
701
+ return cached
702
+
703
+ async def _direct_prepared(
704
+ self,
705
+ *,
706
+ target_agent_id: str,
707
+ version_id: str,
708
+ card: AgentCard,
709
+ card_sha256: str,
710
+ ) -> PreparedA2AOperation:
711
+ interfaces = list(card.supported_interfaces or [])
712
+ interface = interfaces[0] if interfaces else None
713
+ if interface is None or not interface.url:
714
+ raise A2AControlPlaneError(
715
+ code=502,
716
+ message="discovered agent card has no callable interface url",
717
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
718
+ action="PrepareA2ACall", # type: ignore[arg-type]
719
+ )
720
+ route = A2ARoute(
721
+ kind="hosted_gateway",
722
+ interface=A2ARouteInterface(
723
+ url=str(interface.url),
724
+ protocol_binding=str(interface.protocol_binding or "JSONRPC"),
725
+ protocol_version=str(interface.protocol_version or "1.0"),
726
+ ),
727
+ )
728
+ return PreparedA2AOperation(
729
+ platform_task_id=f"a2a-task-{uuid.uuid4().hex}",
730
+ target=A2ATarget(
731
+ agent_id=target_agent_id,
732
+ version_id=version_id,
733
+ card_sha256=card_sha256,
734
+ ),
735
+ route=route,
736
+ call_permit="",
737
+ call_permit_expires_at="",
738
+ )
739
+
740
+ async def prepare_call(
741
+ self,
742
+ *,
743
+ space_id: str,
744
+ target_agent_id: str,
745
+ expected_version_id: str | None,
746
+ message_id: str,
747
+ message_sha256: str,
748
+ idempotency_token: str,
749
+ ) -> PreparedA2AOperation:
750
+ _ = (space_id, expected_version_id, message_id, message_sha256, idempotency_token)
751
+ card, sha, version_id = await self._resolve_card(target_agent_id)
752
+ return await self._direct_prepared(
753
+ target_agent_id=target_agent_id,
754
+ version_id=version_id,
755
+ card=card,
756
+ card_sha256=sha,
757
+ )
758
+
759
+ async def prepare_task_operation(
760
+ self,
761
+ *,
762
+ platform_task_id: str,
763
+ operation: A2AOperation,
764
+ message_id: str | None = None,
765
+ message_sha256: str | None = None,
766
+ idempotency_token: str | None = None,
767
+ agent_id: str | None = None,
768
+ ) -> PreparedA2AOperation:
769
+ _ = (operation, message_id, message_sha256, idempotency_token)
770
+ target_id = agent_id or platform_task_id
771
+ card, sha, version_id = await self._resolve_card(target_id)
772
+ return await self._direct_prepared(
773
+ target_agent_id=target_id,
774
+ version_id=version_id,
775
+ card=card,
776
+ card_sha256=sha,
777
+ )
778
+
779
+ async def bind_remote_task(
780
+ self,
781
+ *,
782
+ platform_task_id: str,
783
+ remote_task_id: str,
784
+ remote_context_id: str | None,
785
+ observed_at: str,
786
+ ) -> dict[str, Any]:
787
+ _ = (platform_task_id, remote_context_id, observed_at)
788
+ return {"RemoteTaskId": remote_task_id, "Bound": True}
789
+
790
+ async def append_task_events(
791
+ self,
792
+ *,
793
+ platform_task_id: str,
794
+ events: list[dict[str, Any]],
795
+ ) -> dict[str, Any]:
796
+ _ = (platform_task_id, events)
797
+ return {"Appended": len(events)}
798
+
799
+ async def resolve_credential(
800
+ self,
801
+ *,
802
+ platform_task_id: str,
803
+ credential_handle: str | None,
804
+ call_permit: str,
805
+ ) -> CredentialInjection:
806
+ _ = (platform_task_id, credential_handle, call_permit)
807
+ return CredentialInjection()
808
+
809
+ def gateway_token(self) -> str:
810
+ return self._service_token
811
+
812
+
813
+ def _credential_string_map(value: Any, *, field_name: str) -> dict[str, str]:
814
+ if value is None:
815
+ return {}
816
+ if not isinstance(value, dict) or any(
817
+ not isinstance(key, str) or not isinstance(item, str) for key, item in value.items()
818
+ ):
819
+ raise A2AControlPlaneError(
820
+ code=502,
821
+ message=f"control-plane response field {field_name} must be a string map",
822
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
823
+ field=field_name,
824
+ )
825
+ return dict(value)
826
+
827
+
828
+ def _validate_task_operation_request(
829
+ *,
830
+ operation: str,
831
+ message_id: str | None,
832
+ message_sha256: str | None,
833
+ idempotency_token: str | None,
834
+ ) -> None:
835
+ if operation not in A2A_OPERATIONS:
836
+ raise ValueError(f"unsupported A2A task operation: {operation!r}")
837
+ if operation == "send_message":
838
+ if not message_id or not message_sha256 or not idempotency_token:
839
+ raise ValueError(
840
+ "send_message requires message_id, message_sha256, and idempotency_token"
841
+ )
842
+ return
843
+ if message_id or message_sha256:
844
+ raise ValueError(f"{operation} does not accept message fields")
845
+ if operation == "cancel_task":
846
+ if not idempotency_token:
847
+ raise ValueError("cancel_task requires idempotency_token")
848
+ return
849
+ if idempotency_token:
850
+ raise ValueError(f"{operation} does not accept idempotency_token")
851
+
852
+
853
+ def _optional_str(value: Any) -> str | None:
854
+ return str(value) if value is not None else None
855
+
856
+
857
+ def _required_str(value: Any, *, field_name: str, prefix: str | None = None) -> str:
858
+ result = str(value or "")
859
+ if not result:
860
+ raise A2AControlPlaneError(
861
+ code=502,
862
+ message=f"control-plane response field {field_name} is missing or invalid",
863
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
864
+ field=field_name,
865
+ )
866
+ if prefix is not None:
867
+ try:
868
+ require_a2a_resource_id(result, prefix, field_name=field_name)
869
+ except ValueError as exc:
870
+ raise A2AControlPlaneError(
871
+ code=502,
872
+ message=f"control-plane response field {field_name} has an invalid resource ID",
873
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
874
+ field=field_name,
875
+ ) from exc
876
+ return result
877
+
878
+
879
+ def _agent_card_from_wire(payload: Any) -> AgentCard:
880
+ if not isinstance(payload, dict):
881
+ raise A2AControlPlaneError(
882
+ code=502,
883
+ message="AgentCard must be an object",
884
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
885
+ )
886
+ # 兼容 0.3 card:顶层 url/preferredTransport 折叠进 supportedInterfaces[0]。
887
+ if not payload.get("supportedInterfaces") and payload.get("url"):
888
+ payload = dict(payload)
889
+ payload["supportedInterfaces"] = [{
890
+ "url": payload["url"],
891
+ "protocolBinding": payload.get("preferredTransport", "JSONRPC"),
892
+ "protocolVersion": payload.get("protocolVersion", "0.3"),
893
+ }]
894
+ try:
895
+ return ParseDict(payload, AgentCard(), ignore_unknown_fields=True)
896
+ except (ParseError, TypeError, ValueError) as exc:
897
+ raise A2AControlPlaneError(
898
+ code=502,
899
+ message="AgentCard does not match the A2A 1.0 schema",
900
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
901
+ field="AgentCard",
902
+ ) from exc
903
+
904
+
905
+ def _discovered_agent_from_wire(item: dict[str, Any]) -> DiscoveredAgent:
906
+ if not isinstance(item, dict):
907
+ raise A2AControlPlaneError(
908
+ code=502,
909
+ message="ListA2ASpaceAgents item must be an object",
910
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
911
+ field="Agents",
912
+ )
913
+ route_kind = str(item.get("RouteKind") or "")
914
+ if route_kind not in {"hosted_gateway", "external_public", "external_vpc"}:
915
+ raise A2AControlPlaneError(
916
+ code=502,
917
+ message="ListA2ASpaceAgents returned an unknown RouteKind",
918
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
919
+ field="RouteKind",
920
+ )
921
+ source = _required_str(item.get("Source"), field_name="Source")
922
+ if source not in {"hosted", "external"}:
923
+ raise A2AControlPlaneError(
924
+ code=502,
925
+ message="ListA2ASpaceAgents returned an unknown Source",
926
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
927
+ field="Source",
928
+ )
929
+ return DiscoveredAgent(
930
+ agent_id=_required_str(
931
+ item.get("A2AAgentId"), field_name="A2AAgentId", prefix="a2a-agent-"
932
+ ),
933
+ version_id=_required_str(
934
+ item.get("VersionId"), field_name="VersionId", prefix="a2a-version-"
935
+ ),
936
+ source=source,
937
+ agent_card=_agent_card_from_wire(item.get("AgentCard")),
938
+ card_sha256=str(item.get("CardSha256") or ""),
939
+ callable=bool(item.get("Callable", False)),
940
+ blocked_reason=_optional_str(item.get("BlockedReason")),
941
+ route_kind=route_kind,
942
+ )
943
+
944
+
945
+ def _prepared_operation_from_wire(data: dict[str, Any]) -> PreparedA2AOperation:
946
+ raw_target = data.get("Target")
947
+ target: dict[str, Any] = raw_target if isinstance(raw_target, dict) else {}
948
+ raw_route = data.get("Route")
949
+ route: dict[str, Any] = raw_route if isinstance(raw_route, dict) else {}
950
+ raw_interface = route.get("Interface")
951
+ interface: dict[str, Any] = raw_interface if isinstance(raw_interface, dict) else {}
952
+ raw_remote_task = data.get("RemoteTask")
953
+ if raw_remote_task is not None and not isinstance(raw_remote_task, dict):
954
+ raise A2AControlPlaneError(
955
+ code=502,
956
+ message="control-plane response field RemoteTask must be an object or null",
957
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
958
+ field="RemoteTask",
959
+ )
960
+ remote_task: dict[str, Any] | None = (
961
+ raw_remote_task if isinstance(raw_remote_task, dict) else None
962
+ )
963
+ route_kind = _required_str(route.get("Kind"), field_name="Route.Kind")
964
+ if route_kind not in {"hosted_gateway", "external_public", "external_vpc"}:
965
+ raise A2AControlPlaneError(
966
+ code=502,
967
+ message="prepared operation returned an unknown route kind",
968
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
969
+ field="Route.Kind",
970
+ )
971
+ protocol_binding = _required_str(
972
+ interface.get("ProtocolBinding"), field_name="Route.Interface.ProtocolBinding"
973
+ )
974
+ if protocol_binding not in {"JSONRPC", "HTTP+JSON"}:
975
+ raise A2AControlPlaneError(
976
+ code=502,
977
+ message="prepared operation returned an unsupported protocol binding",
978
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
979
+ field="Route.Interface.ProtocolBinding",
980
+ )
981
+ protocol_version = _required_str(
982
+ interface.get("ProtocolVersion"), field_name="Route.Interface.ProtocolVersion"
983
+ )
984
+ if protocol_version != "1.0":
985
+ raise A2AControlPlaneError(
986
+ code=502,
987
+ message="prepared operation returned an unsupported protocol version",
988
+ error_code="A2A_CONTROL_PLANE_INVALID_RESPONSE",
989
+ field="Route.Interface.ProtocolVersion",
990
+ )
991
+ return PreparedA2AOperation(
992
+ platform_task_id=_required_str(
993
+ data.get("A2ATaskId"), field_name="A2ATaskId", prefix="a2a-task-"
994
+ ),
995
+ target=A2ATarget(
996
+ agent_id=_required_str(
997
+ target.get("A2AAgentId"),
998
+ field_name="Target.A2AAgentId",
999
+ prefix="a2a-agent-",
1000
+ ),
1001
+ version_id=_required_str(
1002
+ target.get("VersionId"),
1003
+ field_name="Target.VersionId",
1004
+ prefix="a2a-version-",
1005
+ ),
1006
+ card_sha256=_required_str(target.get("CardSha256"), field_name="Target.CardSha256"),
1007
+ ),
1008
+ route=A2ARoute(
1009
+ kind=route_kind,
1010
+ interface=A2ARouteInterface(
1011
+ url=_required_str(interface.get("Url"), field_name="Route.Interface.Url"),
1012
+ protocol_binding=protocol_binding,
1013
+ protocol_version=protocol_version,
1014
+ ),
1015
+ ),
1016
+ call_permit=_required_str(data.get("CallPermit"), field_name="CallPermit"),
1017
+ call_permit_expires_at=_required_str(
1018
+ data.get("CallPermitExpiresAt"), field_name="CallPermitExpiresAt"
1019
+ ),
1020
+ credential_handle=_optional_str(data.get("CredentialHandle")),
1021
+ remote_task=(
1022
+ RemoteTaskReference(
1023
+ remote_task_id=_required_str(
1024
+ remote_task.get("RemoteTaskId"), field_name="RemoteTask.RemoteTaskId"
1025
+ ),
1026
+ remote_context_id=_optional_str(remote_task.get("RemoteContextId")),
1027
+ )
1028
+ if remote_task is not None
1029
+ else None
1030
+ ),
1031
+ )
1032
+
1033
+
1034
+ __all__ = [
1035
+ "A2AInternalAction",
1036
+ "A2A_INTERNAL_ACTIONS",
1037
+ "A2A_INTERNAL_PATH_PREFIX",
1038
+ "A2AAgentCardClient",
1039
+ "A2AControlPlane",
1040
+ "A2AControlPlaneError",
1041
+ "A2AOperation",
1042
+ "A2ARoute",
1043
+ "A2ARouteInterface",
1044
+ "A2ATarget",
1045
+ "CredentialInjection",
1046
+ "DiscoveredAgent",
1047
+ "ENV_A2A_CONTROL_PLANE_URL",
1048
+ "ENV_A2A_TOKEN_DIR",
1049
+ "FileWorkloadTokenProvider",
1050
+ "InternalA2AControlPlaneClient",
1051
+ "PreparedA2AOperation",
1052
+ "RemoteTaskReference",
1053
+ "SpaceAgentPage",
1054
+ "WorkloadTokenProvider",
1055
+ "build_a2a_internal_action_path",
1056
+ ]