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.
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/METADATA +52 -20
- agentengine_sdk_python-0.8.1.dist-info/RECORD +644 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/WHEEL +1 -1
- ksadk/a2a/__init__.py +180 -11
- ksadk/a2a/bootstrap.py +332 -0
- ksadk/a2a/card.py +15 -0
- ksadk/a2a/context_store.py +203 -0
- ksadk/a2a/control_plane.py +1056 -0
- ksadk/a2a/credential.py +99 -0
- ksadk/a2a/event_adapter.py +149 -0
- ksadk/a2a/executor.py +361 -137
- 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 +112 -0
- ksadk/a2a/server.py +102 -62
- ksadk/a2a/service_env.py +33 -0
- ksadk/a2a/space_client.py +1122 -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/a2a_card.py +101 -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 +872 -0
- ksadk/agui/config.py +92 -0
- ksadk/agui/routes.py +47 -0
- ksadk/api/client.py +238 -204
- ksadk/api/environment.py +27 -0
- ksadk/builders/__init__.py +2 -0
- ksadk/builders/base.py +16 -14
- ksadk/builders/code_builder.py +375 -270
- ksadk/builders/container_builder.py +240 -200
- ksadk/builders/framework_requirements.py +31 -12
- ksadk/builders/ks3_uploader.py +60 -29
- ksadk/builders/managed_runtime_builder.py +173 -0
- ksadk/builders/mcp_builder.py +91 -92
- ksadk/builders/requirements_utils.py +1 -5
- ksadk/cli/__init__.py +97 -171
- ksadk/cli/cmd_a2a.py +255 -84
- 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 +507 -740
- ksadk/cli/cmd_dashboard.py +120 -45
- ksadk/cli/cmd_deploy.py +135 -59
- ksadk/cli/cmd_destroy.py +44 -14
- ksadk/cli/cmd_eval.py +386 -0
- ksadk/cli/cmd_files.py +63 -24
- ksadk/cli/cmd_hermes.py +313 -135
- ksadk/cli/cmd_invoke.py +184 -122
- ksadk/cli/cmd_launch.py +76 -28
- ksadk/cli/cmd_mcp.py +98 -64
- ksadk/cli/cmd_model.py +14 -5
- ksadk/cli/cmd_openclaw.py +564 -268
- ksadk/cli/cmd_replay.py +71 -0
- ksadk/cli/cmd_run.py +58 -73
- ksadk/cli/cmd_status.py +80 -31
- ksadk/cli/cmd_studio.py +110 -0
- ksadk/cli/cmd_version.py +20 -13
- ksadk/cli/cmd_web.py +95 -18
- ksadk/cli/deploy_utils.py +20 -20
- ksadk/cli/dry_run.py +7 -4
- ksadk/cli/env_options.py +83 -0
- ksadk/cli/error_utils.py +37 -12
- ksadk/cli/global_options.py +4 -3
- ksadk/cli/local_runtime.py +1 -2
- ksadk/cli/model_catalog.py +6 -2
- ksadk/cli/network_options.py +41 -19
- ksadk/cli/resource_common.py +6 -2
- ksadk/cli/runtime_bootstrap.py +51 -0
- ksadk/cli/storage.py +0 -1
- ksadk/cli/ui.py +11 -3
- ksadk/cli/workflow_common.py +52 -18
- ksadk/codex/__init__.py +12 -0
- ksadk/codex/client.py +1042 -0
- ksadk/codex/phase.py +143 -0
- ksadk/codex/runtime.py +940 -0
- ksadk/common/aicp_env.py +0 -1
- ksadk/common/auth.py +2 -2
- ksadk/common/constants.py +1 -3
- ksadk/common/kop_client.py +232 -0
- 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 -14
- ksadk/configs/env_registry.py +661 -116
- ksadk/configs/global_config.py +3 -2
- ksadk/configs/settings.py +133 -209
- ksadk/conversations/__init__.py +85 -92
- 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 +660 -0
- ksadk/conversations/runtime_invocation.py +395 -0
- ksadk/conversations/runtime_metadata.py +475 -0
- ksadk/conversations/runtime_observability.py +367 -0
- ksadk/conversations/runtime_payloads.py +272 -0
- ksadk/conversations/runtime_persistence.py +440 -0
- ksadk/conversations/runtime_preparation.py +426 -0
- ksadk/conversations/runtime_resume.py +836 -0
- ksadk/conversations/runtime_stream_events.py +902 -0
- ksadk/conversations/runtime_streaming.py +700 -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/managed_runtime.py +33 -0
- ksadk/deployment/manager.py +85 -71
- ksadk/deployment/providers/serverless.py +379 -210
- 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/evaluation/__init__.py +84 -0
- ksadk/evaluation/a2a_adapter.py +444 -0
- ksadk/evaluation/adapters.py +54 -0
- ksadk/evaluation/contracts.py +409 -0
- ksadk/evaluation/evalset.py +351 -0
- ksadk/evaluation/evaluators.py +461 -0
- ksadk/evaluation/executor.py +118 -0
- ksadk/evaluation/storage.py +68 -0
- ksadk/evaluation/target.py +73 -0
- ksadk/events/__init__.py +25 -0
- ksadk/events/parser.py +191 -0
- ksadk/events/replay.py +37 -0
- ksadk/events/runtime_event.py +273 -0
- ksadk/events/store.py +317 -0
- ksadk/harness/__init__.py +35 -0
- ksadk/harness/app.py +269 -0
- ksadk/harness/config.py +181 -0
- ksadk/harness/reasoner.py +105 -0
- ksadk/harness/runtime.py +364 -0
- ksadk/harness/sandbox.py +141 -0
- ksadk/harness/tools.py +222 -0
- ksadk/hermes_terminal.py +28 -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 +38 -33
- ksadk/managed_a2a_card.py +160 -0
- ksadk/managed_runtime.py +195 -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 +36 -36
- ksadk/memory/adk/long_term_memory.py +34 -37
- 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 +16 -6
- ksadk/memory/tool.py +4 -0
- 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 +66 -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 +201 -0
- ksadk/model_proxy/protocol_proxy.py +22 -0
- ksadk/model_proxy/server.py +443 -0
- ksadk/model_proxy/transform.py +735 -0
- ksadk/openclaw_gateway.py +29 -14
- ksadk/runners/adk_runner.py +354 -146
- ksadk/runners/base_runner.py +71 -29
- ksadk/runners/deepagents_runner.py +0 -1
- ksadk/runners/factory.py +18 -5
- ksadk/runners/langchain_runner.py +29 -742
- ksadk/runners/langgraph_runner.py +417 -97
- ksadk/runners/patch_langchain.py +16 -16
- ksadk/runners/remote_runner.py +134 -33
- ksadk/runners/usage_accumulator.py +1 -0
- ksadk/runners/utils/__init__.py +0 -10
- ksadk/runners/utils/loader.py +23 -7
- ksadk/runtime/__init__.py +67 -0
- ksadk/runtime/adapter.py +412 -0
- ksadk/runtime/conversation_execution.py +553 -0
- ksadk/runtime/executor.py +251 -0
- ksadk/runtime/factory.py +156 -0
- ksadk/runtime/framework_adapters.py +118 -0
- ksadk/runtime/launch.py +48 -0
- ksadk/runtime/preprocessing.py +131 -0
- ksadk/runtime/runner_adapter.py +1032 -0
- ksadk/runtime/runner_loading.py +36 -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/__init__.py +39 -4
- ksadk/server/api_models.py +15 -5
- ksadk/server/app.py +24 -3928
- ksadk/server/composition.py +47 -0
- ksadk/server/factory.py +622 -0
- ksadk/server/routes/__init__.py +1 -0
- ksadk/server/routes/checkpoint_resolution.py +116 -0
- ksadk/server/routes/common.py +682 -0
- ksadk/server/routes/control.py +331 -0
- ksadk/server/routes/dependencies.py +98 -0
- ksadk/server/routes/misc.py +425 -0
- ksadk/server/routes/models.py +451 -0
- ksadk/server/routes/openai_compat.py +229 -0
- ksadk/server/routes/projection.py +848 -0
- ksadk/server/routes/routers.py +52 -0
- ksadk/server/routes/run.py +519 -0
- ksadk/server/routes/sessions.py +644 -0
- ksadk/server/routes/streaming.py +265 -0
- ksadk/server/routes/workspace.py +414 -0
- ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-CakCqxEN.js} +1 -1
- ksadk/server/static/assets/CodeBlock-CynLknyO.js +9 -0
- ksadk/server/static/assets/MathMessageMarkdown-3sQdOysS.js +8 -0
- ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +303 -0
- ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +1 -0
- ksadk/server/static/assets/{arc-BA13sHc9.js → arc-DrgjcKhl.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-BXoVRbQ6.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-BnaB3Ywv.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-Dw4weZcp.js} +1 -1
- ksadk/server/static/assets/channel-Dt3rQTFI.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-ChjpLK7j.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-B9drTgOW.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-CK4ROATy.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B9KtTTgw.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-1O1mki3m.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-B_fH2YuH.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D6rFLaVV.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-ChNs_Mdk.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-D0QXg_MQ.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-DBDDKwBQ.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-BkwDOxmO.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Bv8waYvY.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-e1VECDdu.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-Ds9adfRl.js} +1 -1
- ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-4N2NMVYg.js} +1 -1
- ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-B-jNR6Vn.js} +1 -1
- ksadk/server/static/assets/{dist-D57wnGWd.js → dist-B5J2uhNk.js} +1 -1
- ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-B5oS1zcj.js} +1 -1
- ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BB3-OwTQ.js} +1 -1
- ksadk/server/static/assets/{dist-2hInLS_J.js → dist-BIlKoW0k.js} +1 -1
- ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-BjOdlDjj.js} +1 -1
- ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-C1sz5Eo3.js} +1 -1
- ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-CWV4lDr1.js} +1 -1
- ksadk/server/static/assets/{dist-D-xOD048.js → dist-CmZHzlwZ.js} +1 -1
- ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-D6MpCZtz.js} +1 -1
- ksadk/server/static/assets/{dist-DyOkZmze.js → dist-DJN_6Y39.js} +1 -1
- ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-DQU9EWw-.js} +1 -1
- ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-DWKByxWV.js} +1 -1
- ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-Dli7qW-B.js} +1 -1
- ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-DsHPdk3W.js} +1 -1
- ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-X4TnqwOK.js} +1 -1
- ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-_pHPTyZ-.js} +1 -1
- ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-fhOuWds5.js} +1 -1
- ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-gLnrGsoU.js} +1 -1
- ksadk/server/static/assets/{dist-BssVmjIo.js → dist-j6Dxmf_j.js} +1 -1
- ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-o2OVmKvt.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-BPItFKH6.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-BbYB9w5Y.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-TQXa0KXv.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-bKblCnpa.js} +1 -1
- ksadk/server/static/assets/index-R52pJJvg.js +177 -0
- ksadk/server/static/assets/index-jJr3_955.css +2 -0
- ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-BGVT7J_i.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-K0OOfVna.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-8LeOJaZp.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-C6uCgZrW.js} +1 -1
- ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-CYWseY5E.js} +1 -1
- ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-hdBcYzM8.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-BWrDjSZM.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-Bjy_2v4_.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-GmmqNiVH.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-BmkRbwCg.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DSWFqL7c.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-l6iXmU65.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-Cq6UBeQB.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-qzTQpg3Y.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-DEz24vV0.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-CMCixki2.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-CVzB8F9K.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-BFBqBLc1.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 +47 -16
- ksadk/skills/tool_defs.py +15 -10
- ksadk/studio/__init__.py +5 -0
- ksadk/studio/agent_avatar_assets.py +120 -0
- ksadk/studio/agent_lifecycle.py +51 -0
- ksadk/studio/api.py +1046 -0
- ksadk/studio/api_catalog_routes.py +324 -0
- ksadk/studio/api_contracts.py +177 -0
- ksadk/studio/api_helpers.py +132 -0
- ksadk/studio/authoring.py +524 -0
- ksadk/studio/authoring_coordinator.py +285 -0
- ksadk/studio/builder.py +320 -0
- ksadk/studio/capabilities.py +351 -0
- ksadk/studio/cloud.py +278 -0
- ksadk/studio/codex_agent_service.py +583 -0
- ksadk/studio/codex_builder.py +324 -0
- ksadk/studio/codex_manifest.py +278 -0
- ksadk/studio/codex_run.py +352 -0
- ksadk/studio/compiler.py +189 -0
- ksadk/studio/contracts.py +644 -0
- ksadk/studio/errors.py +47 -0
- ksadk/studio/evaluation.py +192 -0
- ksadk/studio/event_store.py +284 -0
- ksadk/studio/framework_run.py +114 -0
- ksadk/studio/identifiers.py +33 -0
- ksadk/studio/mcp_runtime.py +229 -0
- ksadk/studio/model_client.py +543 -0
- ksadk/studio/model_profile_service.py +214 -0
- ksadk/studio/operations.py +243 -0
- ksadk/studio/otel_trace.py +754 -0
- ksadk/studio/pagination.py +182 -0
- ksadk/studio/python_tool_inspection.py +183 -0
- ksadk/studio/repository.py +306 -0
- ksadk/studio/resource_catalog.py +1217 -0
- ksadk/studio/run_service.py +707 -0
- ksadk/studio/runtime_catalog.py +74 -0
- ksadk/studio/runtime_source.py +339 -0
- ksadk/studio/service.py +1141 -0
- ksadk/studio/shared_web.py +994 -0
- ksadk/studio/skill_discovery.py +628 -0
- ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
- ksadk/studio/static/assets/index-CaEN0eTR.css +1 -0
- ksadk/studio/static/assets/index-DI2eY_NH.js +252 -0
- ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
- ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
- ksadk/studio/static/index.html +15 -0
- ksadk/studio/templates.py +630 -0
- ksadk/studio/validator.py +246 -0
- ksadk/studio/workspace.py +137 -0
- ksadk/terminal_exec_policy.py +3 -4
- ksadk/tools/gateway.py +101 -15
- ksadk/tools/result_budget.py +12 -4
- ksadk/toolsets/__init__.py +161 -37
- ksadk/toolsets/a2a.py +426 -0
- 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/__init__.py +19 -21
- ksadk/tracing/exporters/inmemory_exporter.py +62 -51
- ksadk/tracing/setup.py +135 -506
- ksadk/tracing/span_utils.py +0 -41
- 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/memory_backend/registry.py +56 -56
- ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
- ksadk_runtime_common/workspace_files/router.py +8 -2
- agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
- ksadk/a2a/card_builder.py +0 -64
- ksadk/a2a/client.py +0 -309
- ksadk/runners/utils/langfuse.py +0 -230
- 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
- ksadk/tracing/exporters/langfuse_exporter.py +0 -445
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/licenses/LICENSE +0 -0
- {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
|
+
]
|