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.
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
- agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
- ksadk/a2a/__init__.py +178 -11
- ksadk/a2a/bootstrap.py +334 -0
- ksadk/a2a/card.py +120 -0
- ksadk/a2a/context_store.py +203 -0
- ksadk/a2a/control_plane.py +818 -0
- ksadk/a2a/credential.py +99 -0
- ksadk/a2a/event_adapter.py +149 -0
- ksadk/a2a/executor.py +406 -89
- ksadk/a2a/external_transport.py +516 -0
- ksadk/a2a/identity.py +304 -0
- ksadk/a2a/ids.py +22 -0
- ksadk/a2a/langgraph.py +285 -0
- ksadk/a2a/resume_store.py +201 -0
- ksadk/a2a/routes.py +105 -0
- ksadk/a2a/server.py +107 -60
- ksadk/a2a/space_client.py +1101 -0
- ksadk/a2a/task_adapter.py +423 -0
- ksadk/a2a/task_event_dispatcher.py +167 -0
- ksadk/a2a/task_event_outbox.py +326 -0
- ksadk/a2a/task_store.py +206 -0
- ksadk/a2ui/__init__.py +27 -0
- ksadk/a2ui/core.py +186 -0
- ksadk/a2ui/models.py +166 -0
- ksadk/a2ui/renderer.py +198 -0
- ksadk/agui/__init__.py +9 -0
- ksadk/agui/a2ui_projection.py +92 -0
- ksadk/agui/agent.py +857 -0
- ksadk/agui/config.py +86 -0
- ksadk/agui/routes.py +55 -0
- ksadk/api/client.py +233 -191
- ksadk/builders/__init__.py +2 -0
- ksadk/builders/base.py +16 -14
- ksadk/builders/code_builder.py +318 -236
- ksadk/builders/container_builder.py +187 -167
- ksadk/builders/framework_requirements.py +29 -11
- ksadk/builders/ks3_uploader.py +58 -27
- ksadk/builders/managed_runtime_builder.py +145 -0
- ksadk/builders/mcp_builder.py +91 -92
- ksadk/builders/requirements_utils.py +1 -5
- ksadk/cli/__init__.py +89 -171
- ksadk/cli/cmd_a2a.py +254 -70
- ksadk/cli/cmd_agent.py +19 -5
- ksadk/cli/cmd_build.py +158 -29
- ksadk/cli/cmd_completion.py +47 -35
- ksadk/cli/cmd_config.py +267 -198
- ksadk/cli/cmd_create.py +500 -720
- ksadk/cli/cmd_dashboard.py +120 -45
- ksadk/cli/cmd_deploy.py +117 -58
- ksadk/cli/cmd_destroy.py +44 -14
- ksadk/cli/cmd_files.py +63 -24
- ksadk/cli/cmd_hermes.py +168 -89
- ksadk/cli/cmd_invoke.py +184 -122
- ksadk/cli/cmd_launch.py +64 -28
- ksadk/cli/cmd_mcp.py +98 -64
- ksadk/cli/cmd_model.py +14 -5
- ksadk/cli/cmd_openclaw.py +483 -227
- ksadk/cli/cmd_replay.py +71 -0
- ksadk/cli/cmd_run.py +33 -10
- ksadk/cli/cmd_status.py +80 -31
- ksadk/cli/cmd_version.py +20 -13
- ksadk/cli/cmd_web.py +89 -13
- ksadk/cli/deploy_utils.py +20 -20
- ksadk/cli/dry_run.py +5 -3
- ksadk/cli/error_utils.py +37 -12
- ksadk/cli/global_options.py +1 -2
- ksadk/cli/local_runtime.py +1 -2
- ksadk/cli/model_catalog.py +6 -2
- ksadk/cli/network_options.py +34 -14
- ksadk/cli/resource_common.py +6 -2
- ksadk/cli/storage.py +0 -1
- ksadk/cli/ui.py +5 -3
- ksadk/cli/workflow_common.py +51 -17
- ksadk/codex/__init__.py +12 -0
- ksadk/codex/client.py +408 -0
- ksadk/codex/phase.py +143 -0
- ksadk/codex/runtime.py +449 -0
- ksadk/common/aicp_env.py +0 -1
- ksadk/common/auth.py +2 -2
- ksadk/common/constants.py +1 -3
- ksadk/common/llm_utils.py +17 -13
- ksadk/compat/__init__.py +5 -0
- ksadk/compat/adk_compat.py +200 -0
- ksadk/compat/copilotkit_a2ui.py +330 -0
- ksadk/configs/__init__.py +12 -12
- ksadk/configs/env_registry.py +500 -100
- ksadk/configs/global_config.py +2 -1
- ksadk/configs/settings.py +151 -124
- ksadk/conversations/__init__.py +7 -5
- ksadk/conversations/attachment_storage.py +16 -11
- ksadk/conversations/attachments.py +44 -36
- ksadk/conversations/compaction_pipeline.py +12 -12
- ksadk/conversations/compaction_prompt.py +9 -5
- ksadk/conversations/context.py +221 -6
- ksadk/conversations/message_projection.py +438 -51
- ksadk/conversations/model_context.py +12 -6
- ksadk/conversations/model_options.py +0 -1
- ksadk/conversations/normalize.py +20 -16
- ksadk/conversations/reasoning_markup.py +1 -1
- ksadk/conversations/runtime.py +339 -5255
- ksadk/conversations/runtime_compaction.py +333 -0
- ksadk/conversations/runtime_constants.py +27 -0
- ksadk/conversations/runtime_governance.py +185 -0
- ksadk/conversations/runtime_input.py +652 -0
- ksadk/conversations/runtime_invocation.py +395 -0
- ksadk/conversations/runtime_metadata.py +475 -0
- ksadk/conversations/runtime_observability.py +355 -0
- ksadk/conversations/runtime_payloads.py +272 -0
- ksadk/conversations/runtime_persistence.py +440 -0
- ksadk/conversations/runtime_preparation.py +418 -0
- ksadk/conversations/runtime_resume.py +816 -0
- ksadk/conversations/runtime_stream_events.py +902 -0
- ksadk/conversations/runtime_streaming.py +547 -0
- ksadk/conversations/semantic_summary.py +17 -5
- ksadk/conversations/session_title.py +6 -3
- ksadk/deployment/__init__.py +15 -16
- ksadk/deployment/agent_access.py +12 -23
- ksadk/deployment/base.py +51 -38
- ksadk/deployment/manager.py +63 -65
- ksadk/deployment/providers/serverless.py +309 -202
- ksadk/deployment/registry.py +38 -32
- ksadk/deployment/state.py +11 -11
- ksadk/deployment/ui_config.py +1 -1
- ksadk/detection/__init__.py +1 -1
- ksadk/detection/detector.py +146 -83
- ksadk/detection/mcp_detector.py +9 -6
- ksadk/events/__init__.py +25 -0
- ksadk/events/parser.py +191 -0
- ksadk/events/replay.py +37 -0
- ksadk/events/runtime_event.py +264 -0
- ksadk/events/store.py +317 -0
- ksadk/harness/__init__.py +34 -0
- ksadk/harness/app.py +278 -0
- ksadk/harness/config.py +181 -0
- ksadk/harness/reasoner.py +105 -0
- ksadk/harness/runner.py +211 -0
- ksadk/harness/sandbox.py +141 -0
- ksadk/harness/tools.py +222 -0
- ksadk/hermes_terminal.py +23 -12
- ksadk/identity/resolver.py +15 -9
- ksadk/ids.py +32 -0
- ksadk/knowledge_base/adk_tool.py +1 -1
- ksadk/knowledge_base/client.py +23 -29
- ksadk/managed_runtime.py +188 -0
- ksadk/markdown.py +5 -2
- ksadk/mcp_runtime/__init__.py +9 -6
- ksadk/memory/__init__.py +1 -1
- ksadk/memory/adk/__init__.py +2 -0
- ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
- ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
- ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
- ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
- ksadk/memory/adk/long_term_memory.py +31 -35
- ksadk/memory/adk/resilient_session_service.py +4 -5
- ksadk/memory/adk/short_term_memory.py +12 -21
- ksadk/memory/adk_tool.py +3 -2
- ksadk/memory/backends/base.py +19 -19
- ksadk/memory/backends/memory.py +24 -25
- ksadk/memory/backends/redis.py +31 -30
- ksadk/memory/langchain_tool.py +2 -1
- ksadk/memory/manager.py +52 -47
- ksadk/memory/service.py +10 -8
- ksadk/model_policy.py +15 -7
- ksadk/model_proxy/__init__.py +51 -0
- ksadk/model_proxy/bootstrap.py +80 -0
- ksadk/model_proxy/cache.py +165 -0
- ksadk/model_proxy/config.py +45 -0
- ksadk/model_proxy/demo_webui.py +115 -0
- ksadk/model_proxy/detect.py +145 -0
- ksadk/model_proxy/gate.py +68 -0
- ksadk/model_proxy/namespace.py +173 -0
- ksadk/model_proxy/protocol_proxy.py +22 -0
- ksadk/model_proxy/server.py +300 -0
- ksadk/model_proxy/transform.py +691 -0
- ksadk/openclaw_gateway.py +29 -14
- ksadk/runners/adk_runner.py +345 -120
- ksadk/runners/base_runner.py +46 -29
- ksadk/runners/codex_runner.py +132 -0
- ksadk/runners/deepagents_runner.py +0 -1
- ksadk/runners/factory.py +17 -5
- ksadk/runners/langchain_runner.py +29 -742
- ksadk/runners/langgraph_runner.py +352 -90
- ksadk/runners/patch_langchain.py +16 -16
- ksadk/runners/remote_runner.py +134 -33
- ksadk/runners/usage_accumulator.py +1 -0
- ksadk/runners/utils/langfuse.py +3 -3
- ksadk/runners/utils/loader.py +7 -7
- ksadk/runtime/__init__.py +41 -0
- ksadk/runtime/adapter.py +345 -0
- ksadk/runtime/framework_adapters.py +124 -0
- ksadk/runtime/preprocessing.py +118 -0
- ksadk/runtime/runner_adapter.py +873 -0
- ksadk/runtime_context.py +18 -3
- ksadk/runtime_state.py +0 -1
- ksadk/sandbox/__init__.py +4 -1
- ksadk/sandbox/backends/__init__.py +4 -1
- ksadk/sandbox/backends/e2b.py +13 -5
- ksadk/sandbox/backends/local_process.py +11 -3
- ksadk/sandbox/base.py +10 -15
- ksadk/sandbox/factory.py +14 -4
- ksadk/sandbox/registry.py +57 -6
- ksadk/server/api_models.py +15 -5
- ksadk/server/app.py +455 -3885
- ksadk/server/composition.py +21 -0
- ksadk/server/factory.py +624 -0
- ksadk/server/routes/__init__.py +1 -0
- ksadk/server/routes/checkpoint_resolution.py +116 -0
- ksadk/server/routes/common.py +713 -0
- ksadk/server/routes/control.py +327 -0
- ksadk/server/routes/dependencies.py +98 -0
- ksadk/server/routes/misc.py +448 -0
- ksadk/server/routes/models.py +443 -0
- ksadk/server/routes/openai_compat.py +207 -0
- ksadk/server/routes/projection.py +795 -0
- ksadk/server/routes/routers.py +52 -0
- ksadk/server/routes/run.py +726 -0
- ksadk/server/routes/sessions.py +649 -0
- ksadk/server/routes/streaming.py +223 -0
- ksadk/server/routes/workspace.py +381 -0
- ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
- ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
- ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
- ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
- ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
- ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
- ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
- ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
- ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
- ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
- ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
- ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
- ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
- ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
- ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
- ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
- ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
- ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
- ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
- ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
- ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
- ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
- ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
- ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
- ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
- ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
- ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
- ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
- ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
- ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
- ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
- ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
- ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
- ksadk/server/static/index.html +2 -2
- ksadk/server/terminal_sessions.py +111 -58
- ksadk/sessions/__init__.py +69 -19
- ksadk/sessions/base.py +31 -11
- ksadk/sessions/continuity.py +2 -3
- ksadk/sessions/in_memory.py +46 -2
- ksadk/sessions/local_service.py +148 -17
- ksadk/sessions/postgres_service.py +143 -25
- ksadk/sessions/resilient.py +36 -8
- ksadk/skills/models.py +17 -5
- ksadk/skills/package_store.py +5 -2
- ksadk/skills/runtime/agent.py +20 -5
- ksadk/skills/runtime/artifacts.py +1 -5
- ksadk/skills/runtime/backends/e2b.py +10 -4
- ksadk/skills/runtime/backends/local.py +15 -3
- ksadk/skills/runtime/base.py +1 -2
- ksadk/skills/runtime/executor.py +10 -3
- ksadk/skills/runtime/factory.py +4 -2
- ksadk/skills/runtime/loader.py +1 -2
- ksadk/skills/runtime/registry.py +15 -10
- ksadk/skills/runtime/request.py +3 -1
- ksadk/skills/service_client.py +38 -17
- ksadk/skills/tool_defs.py +15 -10
- ksadk/terminal_exec_policy.py +3 -4
- ksadk/tools/gateway.py +92 -15
- ksadk/tools/result_budget.py +12 -4
- ksadk/toolsets/__init__.py +134 -36
- ksadk/toolsets/platform.py +21 -5
- ksadk/toolsets/sandbox.py +82 -16
- ksadk/toolsets/skills.py +200 -39
- ksadk/toolsets/web.py +123 -26
- ksadk/toolsets/workspace.py +212 -101
- ksadk/tracing/exporters/inmemory_exporter.py +62 -51
- ksadk/tracing/exporters/langfuse_exporter.py +91 -76
- ksadk/tracing/setup.py +69 -53
- ksadk/tracing/span_utils.py +5 -2
- ksadk/tui/loop.py +78 -30
- ksadk/tui/stream_render.py +1 -0
- ksadk/ui_config.py +5 -3
- ksadk/version.py +1 -1
- ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
- agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
- ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
- ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
- ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
- ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
- ksadk/server/static/assets/channel-CTuydARX.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
- ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
- ksadk/server/static/assets/index-DAURbCsP.css +0 -1
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
]
|