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,423 @@
|
|
|
1
|
+
"""A2ARuntimeTaskAdapter — A2A Task 与 Runtime session/run/checkpoint/cancel 的映射 (goal-05 §7.2)。
|
|
2
|
+
|
|
3
|
+
| A2A | Runtime |
|
|
4
|
+
|---|---|
|
|
5
|
+
| ``context_id`` | ``session_id`` |
|
|
6
|
+
| ``task_id`` | SDK TaskStore 主键;run/checkpoint 引用保存在 Runtime-local resume store |
|
|
7
|
+
| working | active invocation |
|
|
8
|
+
| input-required | pending interaction + checkpoint/resume token |
|
|
9
|
+
| canceled | ``RuntimeAdapter.cancel(invocation_id)`` 成功后的终态 |
|
|
10
|
+
| artifact/message | RuntimeEvent artifact/text/data |
|
|
11
|
+
|
|
12
|
+
cancel 一律走 G0.3 冻结的 ``RuntimeAdapter.cancel``(含 pending-cancel 语义),
|
|
13
|
+
不在 executor/本模块自造一套。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from collections.abc import AsyncIterator, Mapping
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
from google.protobuf.json_format import MessageToDict
|
|
23
|
+
from google.protobuf.message import Message as ProtobufMessage
|
|
24
|
+
|
|
25
|
+
from ksadk.a2a.context_store import A2AContextStore
|
|
26
|
+
from ksadk.a2a.identity import A2AIngressIdentity
|
|
27
|
+
from ksadk.a2a.resume_store import (
|
|
28
|
+
A2AResumePayloadKind,
|
|
29
|
+
A2AResumeState,
|
|
30
|
+
A2AResumeStateStore,
|
|
31
|
+
InMemoryA2AResumeStateStore,
|
|
32
|
+
)
|
|
33
|
+
from ksadk.events import RuntimeEvent
|
|
34
|
+
from ksadk.runtime.adapter import (
|
|
35
|
+
CancelResult,
|
|
36
|
+
ResumePayload,
|
|
37
|
+
ResumeTarget,
|
|
38
|
+
RunHandle,
|
|
39
|
+
RuntimeAdapter,
|
|
40
|
+
StartRequest,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class A2ARuntimeTaskAdapter:
|
|
47
|
+
"""A2A Task ↔ Runtime 的映射器。
|
|
48
|
+
|
|
49
|
+
持有一个 G0.3 ``RuntimeAdapter``(由 runtime registry / runner bridge 提供),
|
|
50
|
+
把 A2A 侧的 task/cancel/input-required 映射到 Runtime 六动词。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
runtime_adapter: RuntimeAdapter,
|
|
56
|
+
*,
|
|
57
|
+
runtime_type: str = "local",
|
|
58
|
+
context_store: A2AContextStore | None = None,
|
|
59
|
+
resume_state_store: A2AResumeStateStore | None = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
self._adapter = runtime_adapter
|
|
62
|
+
self._runtime_type = runtime_type
|
|
63
|
+
self._context_store = context_store
|
|
64
|
+
self._resume_state_store = resume_state_store or InMemoryA2AResumeStateStore()
|
|
65
|
+
self._handles_by_task_key: dict[tuple[str, str, str], RunHandle] = {}
|
|
66
|
+
self._accepted_canceled_tasks: set[tuple[str, str, str]] = set()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def runtime_adapter(self) -> RuntimeAdapter:
|
|
70
|
+
return self._adapter
|
|
71
|
+
|
|
72
|
+
# ---- 映射:task_id/context_id ↔ session/invocation ----
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def session_id_from_context(context_id: Optional[str]) -> str:
|
|
76
|
+
"""§7.2: A2A context_id ↔ Runtime session_id。"""
|
|
77
|
+
return str(context_id or "")
|
|
78
|
+
|
|
79
|
+
def handle_for_task(
|
|
80
|
+
self,
|
|
81
|
+
*,
|
|
82
|
+
task_id: str,
|
|
83
|
+
invocation_id: str,
|
|
84
|
+
session_id: str,
|
|
85
|
+
native_ref: Optional[dict[str, Any]] = None,
|
|
86
|
+
) -> RunHandle:
|
|
87
|
+
"""从 Task metadata 还原 Runtime 句柄(run_id = invocation_id)。"""
|
|
88
|
+
return RunHandle(
|
|
89
|
+
run_id=invocation_id or task_id,
|
|
90
|
+
session_id=session_id,
|
|
91
|
+
runtime_type=self._runtime_type,
|
|
92
|
+
native_ref=native_ref or {},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# ---- cancel:走 RuntimeAdapter.cancel ----
|
|
96
|
+
|
|
97
|
+
async def cancel_task(self, task_id: str, context: Any) -> CancelResult:
|
|
98
|
+
"""取消 A2A task → RuntimeAdapter.cancel。
|
|
99
|
+
|
|
100
|
+
只使用 ``start`` 返回的进程内真实 handle,或 Runtime-local resume store 中
|
|
101
|
+
持久化的 handle;找不到时返回 NOT_RUNNING,不按 task_id 构造假 handle。
|
|
102
|
+
"""
|
|
103
|
+
await self.prepare_context(context)
|
|
104
|
+
task_key = self._task_key(task_id, context)
|
|
105
|
+
handle = self._handles_by_task_key.get(task_key)
|
|
106
|
+
restored = handle is None
|
|
107
|
+
if handle is None:
|
|
108
|
+
handle = await self._restore_handle_from_store(task_id, context)
|
|
109
|
+
if handle is None or not self._handle_matches_context(handle, context):
|
|
110
|
+
return CancelResult.NOT_RUNNING
|
|
111
|
+
try:
|
|
112
|
+
if restored:
|
|
113
|
+
await self._adapter.attach(handle)
|
|
114
|
+
result = await self._adapter.cancel(handle)
|
|
115
|
+
except Exception as exc: # noqa: BLE001
|
|
116
|
+
logger.error("A2A runtime cancel failed (%s)", type(exc).__name__)
|
|
117
|
+
return CancelResult.FAILED
|
|
118
|
+
if result in {
|
|
119
|
+
CancelResult.INTERRUPTED_ACTIVE_TURN,
|
|
120
|
+
CancelResult.PENDING_CANCEL_RECORDED,
|
|
121
|
+
}:
|
|
122
|
+
self._accepted_canceled_tasks.add(task_key)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
# ---- input-required → checkpoint/resume token ----
|
|
126
|
+
|
|
127
|
+
def build_resume_target(self, *, invocation_id: str) -> ResumeTarget:
|
|
128
|
+
"""input-required 的恢复目标(精确到 invocation)。"""
|
|
129
|
+
return ResumeTarget(kind="invocation_id", id=invocation_id)
|
|
130
|
+
|
|
131
|
+
def build_resume_payload(self, *, call_id: str | None, answer: Any) -> ResumePayload:
|
|
132
|
+
"""input-required 的回包(HITL 回答 / 审批决定)。"""
|
|
133
|
+
return ResumePayload(kind="hitl_answer", call_id=call_id, data=answer)
|
|
134
|
+
|
|
135
|
+
async def start_task(
|
|
136
|
+
self,
|
|
137
|
+
*,
|
|
138
|
+
task_id: str,
|
|
139
|
+
context: Any,
|
|
140
|
+
input_data: Any,
|
|
141
|
+
) -> RunHandle:
|
|
142
|
+
"""通过 RuntimeAdapter 启动 A2A task,返回后续共用的真实 handle。"""
|
|
143
|
+
await self.prepare_context(context)
|
|
144
|
+
context_metadata = self._as_dict(getattr(context, "metadata", None))
|
|
145
|
+
context_metadata.pop("user_id", None)
|
|
146
|
+
context_metadata.pop("agent_id", None)
|
|
147
|
+
tenant = self._trusted_tenant(context)
|
|
148
|
+
handle = await self._adapter.start(
|
|
149
|
+
StartRequest(
|
|
150
|
+
input=input_data,
|
|
151
|
+
user_id=tenant,
|
|
152
|
+
session_id=self._extract_session_id(context),
|
|
153
|
+
agent_id=None,
|
|
154
|
+
metadata={**context_metadata, "invocation_id": task_id},
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
self._handles_by_task_key[self._task_key(task_id, context)] = handle
|
|
158
|
+
return handle
|
|
159
|
+
|
|
160
|
+
async def persist_resume_state(
|
|
161
|
+
self,
|
|
162
|
+
*,
|
|
163
|
+
task_id: str,
|
|
164
|
+
context: Any,
|
|
165
|
+
handle: RunHandle,
|
|
166
|
+
checkpoint_id: str | None,
|
|
167
|
+
call_id: str | None,
|
|
168
|
+
payload_kind: A2AResumePayloadKind,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Store input-required recovery state locally, never in A2A wire metadata."""
|
|
171
|
+
if checkpoint_id:
|
|
172
|
+
handle.native_ref["checkpoint_id"] = checkpoint_id
|
|
173
|
+
known_checkpoint_ids = handle.native_ref.setdefault("known_checkpoint_ids", [])
|
|
174
|
+
if checkpoint_id not in known_checkpoint_ids:
|
|
175
|
+
known_checkpoint_ids.append(checkpoint_id)
|
|
176
|
+
if call_id:
|
|
177
|
+
pending_approval_ids = handle.native_ref.setdefault("pending_approval_ids", [])
|
|
178
|
+
if call_id not in pending_approval_ids:
|
|
179
|
+
pending_approval_ids.append(call_id)
|
|
180
|
+
target = ResumeTarget(
|
|
181
|
+
kind="checkpoint_id" if checkpoint_id else "invocation_id",
|
|
182
|
+
id=str(checkpoint_id or handle.run_id),
|
|
183
|
+
)
|
|
184
|
+
await self._resume_state_store.put(
|
|
185
|
+
self._resume_key(task_id, context),
|
|
186
|
+
A2AResumeState(
|
|
187
|
+
handle=handle,
|
|
188
|
+
target=target,
|
|
189
|
+
payload_kind=payload_kind,
|
|
190
|
+
call_id=call_id,
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
async def resume_task(
|
|
195
|
+
self,
|
|
196
|
+
task_id: str,
|
|
197
|
+
context: Any,
|
|
198
|
+
*,
|
|
199
|
+
answer: Any,
|
|
200
|
+
) -> RunHandle:
|
|
201
|
+
"""Resume from Runtime-local state associated with the protocol Task."""
|
|
202
|
+
await self.prepare_context(context)
|
|
203
|
+
handle, target, payload = await self.validate_resume_task(
|
|
204
|
+
task_id,
|
|
205
|
+
context,
|
|
206
|
+
answer=answer,
|
|
207
|
+
)
|
|
208
|
+
task_key = self._task_key(task_id, context)
|
|
209
|
+
local_handle = self._handles_by_task_key.get(task_key)
|
|
210
|
+
if local_handle is None:
|
|
211
|
+
attached_handle = await self._adapter.attach(handle)
|
|
212
|
+
if attached_handle != handle:
|
|
213
|
+
raise ValueError("RuntimeAdapter.attach returned a different run handle")
|
|
214
|
+
elif local_handle != handle:
|
|
215
|
+
raise ValueError("persisted run_handle conflicts with the active task handle")
|
|
216
|
+
resumed_handle = await self._adapter.resume(handle, target, payload)
|
|
217
|
+
if resumed_handle != handle:
|
|
218
|
+
raise ValueError("RuntimeAdapter.resume returned a different run handle")
|
|
219
|
+
self._handles_by_task_key[task_key] = resumed_handle
|
|
220
|
+
return resumed_handle
|
|
221
|
+
|
|
222
|
+
async def validate_resume_task(
|
|
223
|
+
self,
|
|
224
|
+
task_id: str,
|
|
225
|
+
context: Any,
|
|
226
|
+
*,
|
|
227
|
+
answer: Any,
|
|
228
|
+
) -> tuple[RunHandle, ResumeTarget, ResumePayload]:
|
|
229
|
+
"""Validate an A2A resume command without changing task/runtime state."""
|
|
230
|
+
task = getattr(context, "current_task", None)
|
|
231
|
+
if str(getattr(task, "id", "") or "") != task_id:
|
|
232
|
+
raise ValueError("resume token does not belong to the requested task")
|
|
233
|
+
state = await self._resume_state_store.get(self._resume_key(task_id, context))
|
|
234
|
+
if state is None:
|
|
235
|
+
raise ValueError("input-required task has no Runtime-local resume state")
|
|
236
|
+
handle = state.handle
|
|
237
|
+
target = state.target
|
|
238
|
+
if target.kind not in {"checkpoint_id", "invocation_id"} or not target.id:
|
|
239
|
+
raise ValueError("invalid resume_target in Runtime-local resume state")
|
|
240
|
+
payload = self._resume_payload(state, answer=answer)
|
|
241
|
+
if not self._handle_matches_context(handle, context):
|
|
242
|
+
raise ValueError("run_handle does not match A2A context or task adapter")
|
|
243
|
+
return handle, target, payload
|
|
244
|
+
|
|
245
|
+
def stream_task(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
|
|
246
|
+
"""恢复后订阅同一个 RuntimeAdapter 的同一 handle。"""
|
|
247
|
+
return self._adapter.stream(handle)
|
|
248
|
+
|
|
249
|
+
def was_cancel_accepted(self, task_id: str, context: Any, handle: RunHandle) -> bool:
|
|
250
|
+
"""Return whether cancel was accepted for this exact runtime handle."""
|
|
251
|
+
task_key = self._task_key(task_id, context)
|
|
252
|
+
return (
|
|
253
|
+
self._handles_by_task_key.get(task_key) == handle
|
|
254
|
+
and task_key in self._accepted_canceled_tasks
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
async def forget_task(self, task_id: str, context: Any, handle: RunHandle) -> None:
|
|
258
|
+
"""Release process-local tracking after a terminal task state."""
|
|
259
|
+
task_key = self._task_key(task_id, context)
|
|
260
|
+
if self._handles_by_task_key.get(task_key) == handle:
|
|
261
|
+
self._handles_by_task_key.pop(task_key, None)
|
|
262
|
+
self._accepted_canceled_tasks.discard(task_key)
|
|
263
|
+
await self.clear_resume_state(task_id, context)
|
|
264
|
+
|
|
265
|
+
async def clear_resume_state(self, task_id: str, context: Any) -> None:
|
|
266
|
+
"""Discard private recovery material while preserving active cancel bookkeeping."""
|
|
267
|
+
|
|
268
|
+
await self._resume_state_store.delete(self._resume_key(task_id, context))
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def answer_from_context(context: Any) -> Any:
|
|
272
|
+
"""优先读取 A2A ``Part.data``,保留 false/0/空串/null。"""
|
|
273
|
+
message = getattr(context, "message", None)
|
|
274
|
+
for part in getattr(message, "parts", ()) or ():
|
|
275
|
+
data = getattr(part, "data", None)
|
|
276
|
+
which_oneof = getattr(data, "WhichOneof", None)
|
|
277
|
+
if (
|
|
278
|
+
not isinstance(data, ProtobufMessage)
|
|
279
|
+
or not callable(which_oneof)
|
|
280
|
+
or which_oneof("kind") is None
|
|
281
|
+
):
|
|
282
|
+
continue
|
|
283
|
+
converted = MessageToDict(data, preserving_proto_field_name=True)
|
|
284
|
+
if isinstance(converted, Mapping):
|
|
285
|
+
normalized = dict(converted)
|
|
286
|
+
if set(normalized) == {"value"}:
|
|
287
|
+
return normalized["value"]
|
|
288
|
+
return normalized
|
|
289
|
+
return converted
|
|
290
|
+
return context.get_user_input()
|
|
291
|
+
|
|
292
|
+
# ---- metadata 提取 ----
|
|
293
|
+
|
|
294
|
+
@staticmethod
|
|
295
|
+
def _extract_session_id(context: Any) -> str:
|
|
296
|
+
call_context = getattr(context, "call_context", None)
|
|
297
|
+
state = getattr(call_context, "state", None)
|
|
298
|
+
if isinstance(state, Mapping):
|
|
299
|
+
internal_session_id = str(state.get("a2a_internal_session_id") or "")
|
|
300
|
+
if internal_session_id:
|
|
301
|
+
return internal_session_id
|
|
302
|
+
# A follow-up A2A message may carry only task_id. The durable Task is the
|
|
303
|
+
# authority for the original context/session across requests and restarts.
|
|
304
|
+
current_task = getattr(context, "current_task", None)
|
|
305
|
+
task_context_id = getattr(current_task, "context_id", None)
|
|
306
|
+
return str(task_context_id or getattr(context, "context_id", "") or "")
|
|
307
|
+
|
|
308
|
+
async def prepare_context(self, context: Any) -> str:
|
|
309
|
+
"""Resolve the verified external context before adapter state is accessed."""
|
|
310
|
+
|
|
311
|
+
if self._context_store is None:
|
|
312
|
+
return self._extract_session_id(context)
|
|
313
|
+
call_context = getattr(context, "call_context", None)
|
|
314
|
+
state = getattr(call_context, "state", None)
|
|
315
|
+
if not isinstance(state, dict):
|
|
316
|
+
raise PermissionError("verified Gateway identity is required for A2A context mapping")
|
|
317
|
+
cached = str(state.get("a2a_internal_session_id") or "")
|
|
318
|
+
if cached:
|
|
319
|
+
return cached
|
|
320
|
+
identity = state.get("a2a_identity")
|
|
321
|
+
if not isinstance(identity, A2AIngressIdentity):
|
|
322
|
+
raise PermissionError("verified Gateway identity is required for A2A context mapping")
|
|
323
|
+
current_task = getattr(context, "current_task", None)
|
|
324
|
+
external_context_id = str(
|
|
325
|
+
getattr(current_task, "context_id", "")
|
|
326
|
+
or getattr(context, "context_id", "")
|
|
327
|
+
or getattr(context, "task_id", "")
|
|
328
|
+
or ""
|
|
329
|
+
)
|
|
330
|
+
if current_task is not None:
|
|
331
|
+
internal_session_id = await self._context_store.get(
|
|
332
|
+
identity.context_identity(),
|
|
333
|
+
external_context_id,
|
|
334
|
+
)
|
|
335
|
+
if internal_session_id is None:
|
|
336
|
+
raise RuntimeError("A2A_CONTEXT_MAPPING_NOT_FOUND")
|
|
337
|
+
else:
|
|
338
|
+
internal_session_id = await self._context_store.resolve_or_create(
|
|
339
|
+
identity.context_identity(),
|
|
340
|
+
external_context_id,
|
|
341
|
+
)
|
|
342
|
+
state["a2a_internal_session_id"] = internal_session_id
|
|
343
|
+
return internal_session_id
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _trusted_tenant(context: Any) -> str:
|
|
347
|
+
call_context = getattr(context, "call_context", None)
|
|
348
|
+
return str(getattr(call_context, "tenant", "") or "anonymous")
|
|
349
|
+
|
|
350
|
+
def _task_key(self, task_id: str, context: Any) -> tuple[str, str, str]:
|
|
351
|
+
return (
|
|
352
|
+
self._trusted_tenant(context),
|
|
353
|
+
self._extract_session_id(context),
|
|
354
|
+
task_id,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def _restore_handle_from_store(self, task_id: str, context: Any) -> RunHandle | None:
|
|
358
|
+
task = getattr(context, "current_task", None)
|
|
359
|
+
if str(getattr(task, "id", "") or "") != task_id:
|
|
360
|
+
return None
|
|
361
|
+
state = await self._resume_state_store.get(self._resume_key(task_id, context))
|
|
362
|
+
return state.handle if state is not None else None
|
|
363
|
+
|
|
364
|
+
def _handle_matches_context(self, handle: RunHandle, context: Any) -> bool:
|
|
365
|
+
return (
|
|
366
|
+
handle.session_id == self._extract_session_id(context)
|
|
367
|
+
and handle.runtime_type == self._runtime_type
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def _resume_key(self, task_id: str, context: Any) -> str:
|
|
371
|
+
tenant, session_id, normalized_task_id = self._task_key(task_id, context)
|
|
372
|
+
return "\x1f".join((tenant, session_id, normalized_task_id))
|
|
373
|
+
|
|
374
|
+
@classmethod
|
|
375
|
+
def _resume_payload(cls, state: A2AResumeState, *, answer: Any) -> ResumePayload:
|
|
376
|
+
data = (
|
|
377
|
+
cls._approval_decision(answer)
|
|
378
|
+
if state.payload_kind == "approval_decision"
|
|
379
|
+
else answer
|
|
380
|
+
)
|
|
381
|
+
return ResumePayload(kind=state.payload_kind, call_id=state.call_id, data=data)
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def _approval_decision(answer: Any) -> dict[str, list[dict[str, Any]]]:
|
|
385
|
+
if isinstance(answer, Mapping):
|
|
386
|
+
if "decisions" in answer:
|
|
387
|
+
decisions = answer.get("decisions")
|
|
388
|
+
if not isinstance(decisions, list) or not decisions:
|
|
389
|
+
raise ValueError("unknown approval decision")
|
|
390
|
+
normalized = [dict(item) for item in decisions if isinstance(item, Mapping)]
|
|
391
|
+
if len(normalized) != len(decisions):
|
|
392
|
+
raise ValueError("unknown approval decision")
|
|
393
|
+
if any(
|
|
394
|
+
str(item.get("type") or "").strip().lower() not in {"approve", "edit", "reject"}
|
|
395
|
+
for item in normalized
|
|
396
|
+
):
|
|
397
|
+
raise ValueError("unknown approval decision")
|
|
398
|
+
return {"decisions": normalized}
|
|
399
|
+
decision = dict(answer)
|
|
400
|
+
decision_type = str(decision.get("type") or "").strip().lower()
|
|
401
|
+
if decision_type not in {"approve", "edit", "reject"}:
|
|
402
|
+
raise ValueError("unknown approval decision")
|
|
403
|
+
decision["type"] = decision_type
|
|
404
|
+
return {"decisions": [decision]}
|
|
405
|
+
token = str(answer).strip().lower() if answer is not None else ""
|
|
406
|
+
if token not in {"approve", "edit", "reject"}:
|
|
407
|
+
raise ValueError(f"unknown approval decision: {answer!r}")
|
|
408
|
+
return {"decisions": [{"type": token}]}
|
|
409
|
+
|
|
410
|
+
@staticmethod
|
|
411
|
+
def _as_dict(value: Any) -> dict[str, Any]:
|
|
412
|
+
descriptor = getattr(value, "DESCRIPTOR", None)
|
|
413
|
+
if descriptor is not None:
|
|
414
|
+
converted = MessageToDict(value, preserving_proto_field_name=True)
|
|
415
|
+
return dict(converted) if isinstance(converted, Mapping) else {}
|
|
416
|
+
if isinstance(value, Mapping):
|
|
417
|
+
return dict(value)
|
|
418
|
+
if value is None:
|
|
419
|
+
return {}
|
|
420
|
+
return {}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
__all__ = ["A2ARuntimeTaskAdapter"]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Runtime-shared durable dispatcher for AgentEngine A2A task events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any, Protocol
|
|
8
|
+
|
|
9
|
+
from ksadk.a2a.task_event_outbox import A2ATaskEventOutbox
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class A2ATaskEventSink(Protocol):
|
|
15
|
+
async def append_task_events(
|
|
16
|
+
self,
|
|
17
|
+
*,
|
|
18
|
+
platform_task_id: str,
|
|
19
|
+
events: list[dict[str, Any]],
|
|
20
|
+
) -> dict[str, Any]: ...
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class A2ATaskEventDispatcher:
|
|
24
|
+
"""One Runtime-owned outbox dispatcher shared by all Space clients."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
outbox: A2ATaskEventOutbox,
|
|
29
|
+
task_sink: A2ATaskEventSink,
|
|
30
|
+
*,
|
|
31
|
+
retry_interval_seconds: float = 1.0,
|
|
32
|
+
task_sink_timeout_seconds: float = 5.0,
|
|
33
|
+
) -> None:
|
|
34
|
+
if retry_interval_seconds <= 0:
|
|
35
|
+
raise ValueError("retry_interval_seconds must be positive")
|
|
36
|
+
if task_sink_timeout_seconds <= 0:
|
|
37
|
+
raise ValueError("task_sink_timeout_seconds must be positive")
|
|
38
|
+
self._outbox = outbox
|
|
39
|
+
self._task_sink = task_sink
|
|
40
|
+
self._retry_interval_seconds = retry_interval_seconds
|
|
41
|
+
self._task_sink_timeout_seconds = task_sink_timeout_seconds
|
|
42
|
+
self._drain_lock = asyncio.Lock()
|
|
43
|
+
self._stop_event = asyncio.Event()
|
|
44
|
+
self._wake_event = asyncio.Event()
|
|
45
|
+
self._background_task: asyncio.Task[None] | None = None
|
|
46
|
+
self._started = False
|
|
47
|
+
self._last_error: str | None = None
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def outbox(self) -> A2ATaskEventOutbox:
|
|
51
|
+
return self._outbox
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def degraded(self) -> bool:
|
|
55
|
+
return self._last_error is not None
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def last_error(self) -> str | None:
|
|
59
|
+
return self._last_error
|
|
60
|
+
|
|
61
|
+
async def ensure_ready(self) -> None:
|
|
62
|
+
await self._outbox.initialize()
|
|
63
|
+
|
|
64
|
+
async def ensure_writable(self) -> None:
|
|
65
|
+
await self.ensure_ready()
|
|
66
|
+
await self._outbox.ensure_writable()
|
|
67
|
+
|
|
68
|
+
async def start(self) -> None:
|
|
69
|
+
await self.ensure_ready()
|
|
70
|
+
await self.ensure_writable()
|
|
71
|
+
if self._started:
|
|
72
|
+
return
|
|
73
|
+
self._started = True
|
|
74
|
+
self._stop_event.clear()
|
|
75
|
+
self._background_task = asyncio.create_task(
|
|
76
|
+
self._run(),
|
|
77
|
+
name="ksadk-a2a-task-event-dispatcher",
|
|
78
|
+
)
|
|
79
|
+
self._wake_event.set()
|
|
80
|
+
|
|
81
|
+
async def stop(self, *, flush_timeout_seconds: float = 5.0) -> None:
|
|
82
|
+
if flush_timeout_seconds < 0:
|
|
83
|
+
raise ValueError("flush_timeout_seconds cannot be negative")
|
|
84
|
+
self._stop_event.set()
|
|
85
|
+
task = self._background_task
|
|
86
|
+
self._background_task = None
|
|
87
|
+
self._started = False
|
|
88
|
+
if task is not None:
|
|
89
|
+
task.cancel()
|
|
90
|
+
try:
|
|
91
|
+
await task
|
|
92
|
+
except asyncio.CancelledError:
|
|
93
|
+
pass
|
|
94
|
+
if flush_timeout_seconds == 0:
|
|
95
|
+
return
|
|
96
|
+
try:
|
|
97
|
+
await asyncio.wait_for(
|
|
98
|
+
self.drain(raise_on_error=False),
|
|
99
|
+
timeout=flush_timeout_seconds,
|
|
100
|
+
)
|
|
101
|
+
except TimeoutError:
|
|
102
|
+
logger.warning("A2A task event outbox flush timed out; pending batches were retained")
|
|
103
|
+
|
|
104
|
+
async def enqueue(
|
|
105
|
+
self,
|
|
106
|
+
*,
|
|
107
|
+
platform_task_id: str,
|
|
108
|
+
events: list[dict[str, Any]],
|
|
109
|
+
) -> str:
|
|
110
|
+
await self.ensure_writable()
|
|
111
|
+
batch_id = str(
|
|
112
|
+
await self._outbox.enqueue(
|
|
113
|
+
platform_task_id=platform_task_id,
|
|
114
|
+
events=events,
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
self._wake_event.set()
|
|
118
|
+
return batch_id
|
|
119
|
+
|
|
120
|
+
async def drain(self, *, raise_on_error: bool) -> int:
|
|
121
|
+
await self.ensure_ready()
|
|
122
|
+
delivered = 0
|
|
123
|
+
async with self._drain_lock:
|
|
124
|
+
while True:
|
|
125
|
+
batches = await self._outbox.pending(limit=100)
|
|
126
|
+
if not batches:
|
|
127
|
+
self._last_error = None
|
|
128
|
+
return delivered
|
|
129
|
+
for batch in batches:
|
|
130
|
+
try:
|
|
131
|
+
await asyncio.wait_for(
|
|
132
|
+
self._task_sink.append_task_events(
|
|
133
|
+
platform_task_id=batch.platform_task_id,
|
|
134
|
+
events=batch.events,
|
|
135
|
+
),
|
|
136
|
+
timeout=self._task_sink_timeout_seconds,
|
|
137
|
+
)
|
|
138
|
+
except Exception as exc:
|
|
139
|
+
error = str(getattr(exc, "error_code", "") or type(exc).__name__)
|
|
140
|
+
self._last_error = error
|
|
141
|
+
await self._outbox.record_failure(batch.batch_id, error)
|
|
142
|
+
if raise_on_error:
|
|
143
|
+
raise
|
|
144
|
+
logger.warning(
|
|
145
|
+
"A2A task event batch remains in local outbox: task=%s batch=%s",
|
|
146
|
+
batch.platform_task_id,
|
|
147
|
+
batch.batch_id,
|
|
148
|
+
)
|
|
149
|
+
return delivered
|
|
150
|
+
await self._outbox.acknowledge(batch.batch_id)
|
|
151
|
+
delivered += 1
|
|
152
|
+
|
|
153
|
+
async def _run(self) -> None:
|
|
154
|
+
while not self._stop_event.is_set():
|
|
155
|
+
await self.drain(raise_on_error=False)
|
|
156
|
+
try:
|
|
157
|
+
await asyncio.wait_for(
|
|
158
|
+
self._wake_event.wait(),
|
|
159
|
+
timeout=self._retry_interval_seconds,
|
|
160
|
+
)
|
|
161
|
+
except TimeoutError:
|
|
162
|
+
pass
|
|
163
|
+
finally:
|
|
164
|
+
self._wake_event.clear()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = ["A2ATaskEventDispatcher", "A2ATaskEventSink"]
|