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,1032 @@
|
|
|
1
|
+
"""RunnerRuntimeAdapter — 把现有 ``BaseRunner`` 对齐到 G0.3 ``RuntimeAdapter`` (goal-07)。
|
|
2
|
+
|
|
3
|
+
不推倒现有 runner(ADK/LangGraph 的 stream/invoke 已在跑),在它们之上实现平台六动词。
|
|
4
|
+
**cancel 不再是空壳**:实现冻结的 cancel 状态机——活跃 turn interrupt(关闭流)/
|
|
5
|
+
无活跃 turn 记 pending / **级联丢弃 pending 工具审批** / 返回 :class:`CancelResult`。
|
|
6
|
+
|
|
7
|
+
框架差异(resume 目标、checkpoint 粒度)由子类钩子 ``_resume_native`` 与
|
|
8
|
+
``_checkpoint_capability`` 诚实声明(ADK forward-only vs LangGraph time-travel)。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
import copy
|
|
15
|
+
import inspect
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
from collections.abc import Mapping
|
|
19
|
+
from contextlib import nullcontext
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any, AsyncIterator, Dict, Optional, cast
|
|
22
|
+
|
|
23
|
+
from ksadk.conversations.runtime_input import _runner_name
|
|
24
|
+
from ksadk.conversations.runtime_observability import (
|
|
25
|
+
_conversation_span_scope,
|
|
26
|
+
_set_conversation_input_attributes,
|
|
27
|
+
_set_conversation_output_attributes,
|
|
28
|
+
_set_conversation_span_attributes,
|
|
29
|
+
_set_conversation_usage_attributes,
|
|
30
|
+
)
|
|
31
|
+
from ksadk.events.runtime_event import EventType, RuntimeEvent
|
|
32
|
+
from ksadk.runners.base_runner import BaseRunner
|
|
33
|
+
from ksadk.runtime.adapter import (
|
|
34
|
+
RESUME_START_REQUEST_NATIVE_KEY,
|
|
35
|
+
BaseRuntime,
|
|
36
|
+
CancelResult,
|
|
37
|
+
CheckpointCapability,
|
|
38
|
+
CheckpointDescriptor,
|
|
39
|
+
ResumePayload,
|
|
40
|
+
ResumeTarget,
|
|
41
|
+
RunHandle,
|
|
42
|
+
RuntimeAdapter,
|
|
43
|
+
StartRequest,
|
|
44
|
+
)
|
|
45
|
+
from ksadk.runtime.preprocessing import PreparedRuntimeStart, prepare_runtime_start
|
|
46
|
+
from ksadk.runtime.runner_loading import ensure_runner_loaded
|
|
47
|
+
from ksadk.runtime_context import platform_invocation_scope
|
|
48
|
+
from ksadk.tools.gateway import approval_interrupt_info_from_result
|
|
49
|
+
|
|
50
|
+
logger = logging.getLogger(__name__)
|
|
51
|
+
|
|
52
|
+
_STREAM_STOP = object()
|
|
53
|
+
_ResumeKey = tuple[str, str, str, str, str]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
async def _anext_or_stop(gen: AsyncIterator[Any]) -> Any:
|
|
57
|
+
"""取下一个 chunk;流结束返回 _STREAM_STOP sentinel(便于竞速)。"""
|
|
58
|
+
try:
|
|
59
|
+
return await gen.__anext__()
|
|
60
|
+
except StopAsyncIteration:
|
|
61
|
+
return _STREAM_STOP
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _a2ui_surface_event(chunk: Any) -> tuple[str, dict[str, Any]] | None:
|
|
65
|
+
"""Recognize a validated A2UI tool envelope and make it a first-class event.
|
|
66
|
+
|
|
67
|
+
The dynamic ``generate_a2ui`` tool returns official v0.9 operations as a
|
|
68
|
+
JSON tool result. Tool results are otherwise opaque to the runtime, which
|
|
69
|
+
would leave AG-UI with nothing to project until a page reload reconstructs
|
|
70
|
+
history. Convert exactly that envelope at the runtime boundary so it is
|
|
71
|
+
streamed, persisted, and replayed like every other A2UI surface.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
if not isinstance(chunk, dict):
|
|
75
|
+
return None
|
|
76
|
+
value = chunk.get("tool_output", chunk.get("output"))
|
|
77
|
+
if value is not None and hasattr(value, "content"):
|
|
78
|
+
value = value.content
|
|
79
|
+
if isinstance(value, str):
|
|
80
|
+
try:
|
|
81
|
+
value = json.loads(value)
|
|
82
|
+
except (TypeError, ValueError):
|
|
83
|
+
return None
|
|
84
|
+
if not isinstance(value, Mapping):
|
|
85
|
+
return None
|
|
86
|
+
operations_raw = value.get("a2ui_operations")
|
|
87
|
+
if not isinstance(operations_raw, list) or not operations_raw:
|
|
88
|
+
return None
|
|
89
|
+
operations = [dict(operation) for operation in operations_raw if isinstance(operation, Mapping)]
|
|
90
|
+
if not operations:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
known: list[tuple[str, str]] = []
|
|
94
|
+
for operation in operations:
|
|
95
|
+
for key, event_type in (
|
|
96
|
+
("createSurface", EventType.A2UI_SURFACE_BEGIN),
|
|
97
|
+
("updateComponents", EventType.A2UI_SURFACE_UPDATE),
|
|
98
|
+
("updateDataModel", EventType.A2UI_SURFACE_UPDATE),
|
|
99
|
+
("deleteSurface", EventType.A2UI_SURFACE_END),
|
|
100
|
+
):
|
|
101
|
+
detail = operation.get(key)
|
|
102
|
+
if isinstance(detail, Mapping) and isinstance(detail.get("surfaceId"), str):
|
|
103
|
+
surface_id = detail["surfaceId"].strip()
|
|
104
|
+
if surface_id:
|
|
105
|
+
known.append((surface_id, event_type))
|
|
106
|
+
break
|
|
107
|
+
if not known:
|
|
108
|
+
return None
|
|
109
|
+
surface_ids = {surface_id for surface_id, _event_type in known}
|
|
110
|
+
if len(surface_ids) != 1:
|
|
111
|
+
logger.warning("ignoring A2UI tool result with multiple surfaces")
|
|
112
|
+
return None
|
|
113
|
+
surface_id = known[0][0]
|
|
114
|
+
event_type = (
|
|
115
|
+
EventType.A2UI_SURFACE_BEGIN
|
|
116
|
+
if any(kind == EventType.A2UI_SURFACE_BEGIN for _surface_id, kind in known)
|
|
117
|
+
else known[0][1]
|
|
118
|
+
)
|
|
119
|
+
return event_type, {"surface_id": surface_id, "operations": operations}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _coerce_literal(value: Any, allowed: tuple[str, ...], default: str) -> Any:
|
|
123
|
+
"""把 value 收窄到 allowed 之一(不在则回退 default),供 Literal 字段使用。"""
|
|
124
|
+
return value if value in allowed else default
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class _RunnerAsBaseRuntime(BaseRuntime):
|
|
128
|
+
"""把 ``BaseRunner`` 包装为 G0.3 ``BaseRuntime``(原生能力面)。"""
|
|
129
|
+
|
|
130
|
+
def __init__(self, runner: BaseRunner, runtime_type: str) -> None:
|
|
131
|
+
self._runner = runner
|
|
132
|
+
self.runtime_type = runtime_type
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def runner(self) -> BaseRunner:
|
|
136
|
+
return self._runner
|
|
137
|
+
|
|
138
|
+
def native_capabilities(self) -> dict[str, Any]:
|
|
139
|
+
caps = getattr(self._runner, "get_runtime_capabilities", None)
|
|
140
|
+
if callable(caps):
|
|
141
|
+
try:
|
|
142
|
+
return dict(caps())
|
|
143
|
+
except Exception: # noqa: BLE001
|
|
144
|
+
pass
|
|
145
|
+
return {"Framework": self.runtime_type}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass
|
|
149
|
+
class _ActiveRun:
|
|
150
|
+
"""一次进行中的 run(adapter 用于 cancel 追踪)。"""
|
|
151
|
+
|
|
152
|
+
invocation_id: str
|
|
153
|
+
session_id: str
|
|
154
|
+
stream: Optional[AsyncIterator[RuntimeEvent]] = None
|
|
155
|
+
pending_approvals: set[str] = field(default_factory=set)
|
|
156
|
+
interrupt_event: asyncio.Event = field(default_factory=asyncio.Event)
|
|
157
|
+
cancellation_ack: asyncio.Event = field(default_factory=asyncio.Event)
|
|
158
|
+
chunk_task: Optional[asyncio.Task[Any]] = None
|
|
159
|
+
resume_key: Optional[_ResumeKey] = None
|
|
160
|
+
resume_fingerprint: Any = None
|
|
161
|
+
skip_runner: bool = False
|
|
162
|
+
done: bool = False
|
|
163
|
+
completion_metrics: dict[str, Any] = field(default_factory=dict)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class RunnerRuntimeAdapter(RuntimeAdapter):
|
|
167
|
+
"""把 ``BaseRunner`` 映射为平台六动词的通用 adapter。"""
|
|
168
|
+
|
|
169
|
+
def __init__(self, runner: BaseRunner, *, runtime_type: str) -> None:
|
|
170
|
+
super().__init__(_RunnerAsBaseRuntime(runner, runtime_type))
|
|
171
|
+
self._runner = runner
|
|
172
|
+
self._runtime_type = runtime_type
|
|
173
|
+
self._active_runs: dict[str, _ActiveRun] = {}
|
|
174
|
+
self._known_runs: set[str] = set()
|
|
175
|
+
self._run_sessions: dict[str, str] = {}
|
|
176
|
+
self._pending_cancels: set[str] = set()
|
|
177
|
+
self._resume_decisions: dict[_ResumeKey, Any] = {}
|
|
178
|
+
self._consumed_resumes: set[_ResumeKey] = set()
|
|
179
|
+
self._seq = 0
|
|
180
|
+
# 可观测:最近一次 cancel 级联丢弃的 pending 审批集(contract test 断言用)。
|
|
181
|
+
self.last_cancel_dropped_approvals: set[str] = set()
|
|
182
|
+
|
|
183
|
+
# ---- 框架钩子(子类按需 override) ----
|
|
184
|
+
|
|
185
|
+
def _checkpoint_capability(self) -> CheckpointCapability:
|
|
186
|
+
"""诚实暴露 checkpoint 粒度。默认读 runner.describe_checkpoint_capability。"""
|
|
187
|
+
raw = {}
|
|
188
|
+
describe = getattr(self._runner, "describe_checkpoint_capability", None)
|
|
189
|
+
if callable(describe):
|
|
190
|
+
try:
|
|
191
|
+
raw = dict(describe())
|
|
192
|
+
except Exception: # noqa: BLE001
|
|
193
|
+
raw = {}
|
|
194
|
+
supported = bool(raw.get("Supported"))
|
|
195
|
+
granularity = _coerce_literal(
|
|
196
|
+
raw.get("Granularity"),
|
|
197
|
+
("delta", "snapshot", "none"),
|
|
198
|
+
"snapshot" if supported else "none",
|
|
199
|
+
)
|
|
200
|
+
rollback_scope = _coerce_literal(
|
|
201
|
+
raw.get("RollbackScope"),
|
|
202
|
+
("turn", "invocation", "none"),
|
|
203
|
+
"invocation" if supported else "none",
|
|
204
|
+
)
|
|
205
|
+
return CheckpointCapability(
|
|
206
|
+
supported=supported,
|
|
207
|
+
granularity=granularity,
|
|
208
|
+
rollback_scope=rollback_scope,
|
|
209
|
+
fork_supported=bool(raw.get("ForkSupported", False)),
|
|
210
|
+
durable=bool(raw.get("Durable", False)),
|
|
211
|
+
shared_across_pods=bool(raw.get("SharedAcrossPods", False)),
|
|
212
|
+
reason=str(raw.get("Reason") or ""),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
async def _resume_native(
|
|
216
|
+
self,
|
|
217
|
+
handle: RunHandle,
|
|
218
|
+
target: ResumeTarget,
|
|
219
|
+
payload: Optional[ResumePayload],
|
|
220
|
+
) -> Optional[dict]:
|
|
221
|
+
"""框架原生 resume:返回注入下一次 ``stream()`` 的 runner_input 覆盖。
|
|
222
|
+
|
|
223
|
+
返回的 dict 会被 :meth:`_build_runner_input` 合并,以框架 runner 真正消费的
|
|
224
|
+
``checkpoint_resume`` + ``framework_ref`` 形式驱动真实恢复(而非仅写
|
|
225
|
+
``native_ref`` 摆设)。默认通用形式;ADK/LangGraph 子类覆盖为各自框架结构。
|
|
226
|
+
返回 ``None`` 表示本框架无可注入的 resume 输入(退化为普通 stream)。
|
|
227
|
+
"""
|
|
228
|
+
return {
|
|
229
|
+
"checkpoint_resume": True,
|
|
230
|
+
"run_id": target.id,
|
|
231
|
+
"framework_ref": {self._runtime_type: {f"{target.kind}": target.id}},
|
|
232
|
+
"input": payload.data if payload else None,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ---- 六动词 ----
|
|
236
|
+
|
|
237
|
+
async def preflight(self) -> None:
|
|
238
|
+
"""Load the framework agent before a streaming HTTP response commits."""
|
|
239
|
+
|
|
240
|
+
ensure_runner_loaded(self._runner, runtime_type=self._runtime_type)
|
|
241
|
+
|
|
242
|
+
async def start(self, request: StartRequest) -> RunHandle:
|
|
243
|
+
ensure_runner_loaded(self._runner, runtime_type=self._runtime_type)
|
|
244
|
+
run_id = str(request.metadata.get("invocation_id") or self._next_invocation_id())
|
|
245
|
+
prepared_start = await prepare_runtime_start(request, self._runner)
|
|
246
|
+
handle = RunHandle(
|
|
247
|
+
run_id=run_id,
|
|
248
|
+
session_id=request.session_id,
|
|
249
|
+
runtime_type=self._runtime_type,
|
|
250
|
+
native_ref={"user_id": request.user_id, "agent_id": request.agent_id},
|
|
251
|
+
)
|
|
252
|
+
self._known_runs.add(run_id)
|
|
253
|
+
self._run_sessions[run_id] = request.session_id
|
|
254
|
+
self._active_runs[run_id] = _ActiveRun(invocation_id=run_id, session_id=request.session_id)
|
|
255
|
+
# 暂存 start 输入,供 stream() 使用。
|
|
256
|
+
self._active_runs[run_id].__dict__["_start_request"] = request
|
|
257
|
+
if prepared_start is not None:
|
|
258
|
+
self._active_runs[run_id].__dict__["_prepared_start"] = prepared_start
|
|
259
|
+
return handle
|
|
260
|
+
|
|
261
|
+
def stream(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
|
|
262
|
+
return self._stream_events(handle)
|
|
263
|
+
|
|
264
|
+
def is_handle_attached(self, handle: RunHandle) -> bool:
|
|
265
|
+
return (
|
|
266
|
+
handle.runtime_type == self._runtime_type
|
|
267
|
+
and handle.run_id in self._known_runs
|
|
268
|
+
and self._run_sessions.get(handle.run_id) == handle.session_id
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
async def attach(self, handle: RunHandle) -> RunHandle:
|
|
272
|
+
"""Restore a persisted handle through an explicit native runner seam.
|
|
273
|
+
|
|
274
|
+
Merely repopulating ``_known_runs`` would make validation pass without
|
|
275
|
+
restoring framework state. A runner therefore has to expose
|
|
276
|
+
``attach_runtime_handle(handle)`` and prove that its durable backend can
|
|
277
|
+
resolve the referenced session/checkpoint.
|
|
278
|
+
"""
|
|
279
|
+
if self.is_handle_attached(handle):
|
|
280
|
+
return handle
|
|
281
|
+
if handle.runtime_type != self._runtime_type:
|
|
282
|
+
raise ValueError(
|
|
283
|
+
f"run handle runtime {handle.runtime_type!r} does not match "
|
|
284
|
+
f"adapter {self._runtime_type!r}"
|
|
285
|
+
)
|
|
286
|
+
attach = getattr(self._runner, "attach_runtime_handle", None)
|
|
287
|
+
if not callable(attach):
|
|
288
|
+
raise RuntimeError(
|
|
289
|
+
f"runner for {self._runtime_type!r} has no durable "
|
|
290
|
+
"attach_runtime_handle capability"
|
|
291
|
+
)
|
|
292
|
+
restored = attach(handle)
|
|
293
|
+
if inspect.isawaitable(restored):
|
|
294
|
+
restored = await restored
|
|
295
|
+
if restored is False or restored is None:
|
|
296
|
+
raise RuntimeError(
|
|
297
|
+
f"runner could not restore persisted run {handle.run_id!r} "
|
|
298
|
+
f"for session {handle.session_id!r}"
|
|
299
|
+
)
|
|
300
|
+
if isinstance(restored, RunHandle) and restored != handle:
|
|
301
|
+
raise ValueError("native runner attached a different run handle")
|
|
302
|
+
self._known_runs.add(handle.run_id)
|
|
303
|
+
self._run_sessions[handle.run_id] = handle.session_id
|
|
304
|
+
return handle
|
|
305
|
+
|
|
306
|
+
async def cancel(self, handle: RunHandle) -> CancelResult:
|
|
307
|
+
run_id = handle.run_id
|
|
308
|
+
run = self._active_runs.get(run_id)
|
|
309
|
+
# 活跃 turn = 正在 streaming(run.stream 非 None 且未 done);
|
|
310
|
+
# start 过但未 streaming / 已结束 → 无活跃 turn(记 pending 或 not_running)。
|
|
311
|
+
is_active = run is not None and not run.done and run.stream is not None
|
|
312
|
+
if not is_active:
|
|
313
|
+
if run_id in self._known_runs:
|
|
314
|
+
# 无活跃 turn 但 invocation 已知 → 记 pending,下个 turn 消费。
|
|
315
|
+
self._pending_cancels.add(run_id)
|
|
316
|
+
return CancelResult.PENDING_CANCEL_RECORDED
|
|
317
|
+
return CancelResult.NOT_RUNNING
|
|
318
|
+
# 有活跃 turn:interrupt + 级联丢弃 pending 审批。
|
|
319
|
+
assert run is not None # is_active 蕴含 run 非 None
|
|
320
|
+
try:
|
|
321
|
+
await self._interrupt_active_run(run)
|
|
322
|
+
# 级联丢弃该 turn 的 pending 工具审批(先快照供观测)。
|
|
323
|
+
self.last_cancel_dropped_approvals = set(run.pending_approvals)
|
|
324
|
+
run.pending_approvals.clear()
|
|
325
|
+
run.done = True
|
|
326
|
+
self._active_runs.pop(run_id, None)
|
|
327
|
+
self._pending_cancels.discard(run_id)
|
|
328
|
+
return CancelResult.INTERRUPTED_ACTIVE_TURN
|
|
329
|
+
except Exception: # noqa: BLE001
|
|
330
|
+
logger.exception("cancel active run %s 失败", run_id)
|
|
331
|
+
return CancelResult.FAILED
|
|
332
|
+
|
|
333
|
+
async def resume(
|
|
334
|
+
self,
|
|
335
|
+
handle: RunHandle,
|
|
336
|
+
target: ResumeTarget,
|
|
337
|
+
payload: Optional[ResumePayload],
|
|
338
|
+
) -> RunHandle:
|
|
339
|
+
if (
|
|
340
|
+
handle.runtime_type != self._runtime_type
|
|
341
|
+
or handle.run_id not in self._known_runs
|
|
342
|
+
or self._run_sessions.get(handle.run_id) != handle.session_id
|
|
343
|
+
):
|
|
344
|
+
raise ValueError(f"unknown run handle for {self._runtime_type}: {handle.run_id!r}")
|
|
345
|
+
if handle.run_id in self._pending_cancels:
|
|
346
|
+
return handle
|
|
347
|
+
self._require_native_checkpoint_capability()
|
|
348
|
+
self._known_runs.add(handle.run_id)
|
|
349
|
+
prepared_start: PreparedRuntimeStart | None = None
|
|
350
|
+
raw_start_request = handle.native_ref.pop(RESUME_START_REQUEST_NATIVE_KEY, None)
|
|
351
|
+
if isinstance(raw_start_request, Mapping):
|
|
352
|
+
prepared_start = await prepare_runtime_start(
|
|
353
|
+
StartRequest.model_validate(raw_start_request), self._runner
|
|
354
|
+
)
|
|
355
|
+
# _resume_native 返回的 runner_input 覆盖存到 run 上,下一次 stream() 经
|
|
356
|
+
# _build_runner_input 消费,以框架原生方式真驱动恢复。
|
|
357
|
+
override = await self._resume_native(handle, target, payload)
|
|
358
|
+
resume_key = (
|
|
359
|
+
handle.run_id,
|
|
360
|
+
handle.session_id,
|
|
361
|
+
target.kind,
|
|
362
|
+
target.id,
|
|
363
|
+
str(payload.call_id or "") if payload is not None else "",
|
|
364
|
+
)
|
|
365
|
+
resume_fingerprint = (
|
|
366
|
+
(payload.kind, copy.deepcopy(payload.data)) if payload is not None else None
|
|
367
|
+
)
|
|
368
|
+
existing_fingerprint = self._resume_decisions.get(resume_key, _STREAM_STOP)
|
|
369
|
+
if existing_fingerprint is not _STREAM_STOP:
|
|
370
|
+
if existing_fingerprint != resume_fingerprint:
|
|
371
|
+
raise ValueError(f"conflicting resume for checkpoint {target.id!r}")
|
|
372
|
+
current = self._active_runs.get(handle.run_id)
|
|
373
|
+
if current is not None and not current.done and current.resume_key == resume_key:
|
|
374
|
+
return handle
|
|
375
|
+
if resume_key in self._consumed_resumes:
|
|
376
|
+
self._active_runs[handle.run_id] = _ActiveRun(
|
|
377
|
+
invocation_id=handle.run_id,
|
|
378
|
+
session_id=handle.session_id,
|
|
379
|
+
resume_key=resume_key,
|
|
380
|
+
resume_fingerprint=resume_fingerprint,
|
|
381
|
+
skip_runner=True,
|
|
382
|
+
)
|
|
383
|
+
return handle
|
|
384
|
+
|
|
385
|
+
current = self._active_runs.get(handle.run_id)
|
|
386
|
+
if current is not None and not current.done and current.resume_key is not None:
|
|
387
|
+
if current.resume_key == resume_key:
|
|
388
|
+
if current.resume_fingerprint == resume_fingerprint:
|
|
389
|
+
return handle
|
|
390
|
+
raise ValueError(f"conflicting resume for checkpoint {target.id!r}")
|
|
391
|
+
raise ValueError(f"run {handle.run_id!r} already has an active or pending resume")
|
|
392
|
+
|
|
393
|
+
run = _ActiveRun(
|
|
394
|
+
invocation_id=handle.run_id,
|
|
395
|
+
session_id=handle.session_id,
|
|
396
|
+
resume_key=resume_key,
|
|
397
|
+
resume_fingerprint=resume_fingerprint,
|
|
398
|
+
)
|
|
399
|
+
if prepared_start is not None:
|
|
400
|
+
run.__dict__["_prepared_start"] = prepared_start
|
|
401
|
+
if override:
|
|
402
|
+
run.__dict__["_resume_input_override"] = override
|
|
403
|
+
self._resume_decisions[resume_key] = resume_fingerprint
|
|
404
|
+
self._active_runs[handle.run_id] = run
|
|
405
|
+
return handle
|
|
406
|
+
|
|
407
|
+
async def checkpoint(self, handle: RunHandle) -> CheckpointDescriptor:
|
|
408
|
+
capability = self._require_native_checkpoint_capability()
|
|
409
|
+
checkpoint_id = str(handle.native_ref.get("checkpoint_id") or "").strip()
|
|
410
|
+
if not checkpoint_id:
|
|
411
|
+
raise RuntimeError(
|
|
412
|
+
f"{self._runtime_type} runner has no native checkpoint for run "
|
|
413
|
+
f"{handle.run_id!r}"
|
|
414
|
+
)
|
|
415
|
+
return CheckpointDescriptor(
|
|
416
|
+
checkpoint_id=checkpoint_id,
|
|
417
|
+
invocation_id=handle.run_id,
|
|
418
|
+
capability=capability,
|
|
419
|
+
ref=dict(handle.native_ref),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def _require_native_checkpoint_capability(self) -> CheckpointCapability:
|
|
423
|
+
"""Fail closed when a runner cannot create or resume a native checkpoint."""
|
|
424
|
+
capability = self._checkpoint_capability()
|
|
425
|
+
if capability.supported:
|
|
426
|
+
return capability
|
|
427
|
+
detail = capability.reason or "runner does not expose framework checkpoints"
|
|
428
|
+
raise RuntimeError(
|
|
429
|
+
f"{self._runtime_type} native checkpoint capability is unavailable: {detail}"
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
async def close(self, handle: RunHandle) -> None:
|
|
433
|
+
run = self._active_runs.pop(handle.run_id, None)
|
|
434
|
+
if run is not None:
|
|
435
|
+
await self._interrupt_active_run(run)
|
|
436
|
+
run.done = True
|
|
437
|
+
resume_keys = {
|
|
438
|
+
key
|
|
439
|
+
for key in self._resume_decisions
|
|
440
|
+
if key[0] == handle.run_id and key[1] == handle.session_id
|
|
441
|
+
}
|
|
442
|
+
for key in resume_keys:
|
|
443
|
+
self._resume_decisions.pop(key, None)
|
|
444
|
+
self._consumed_resumes.difference_update(resume_keys)
|
|
445
|
+
self._pending_cancels.discard(handle.run_id)
|
|
446
|
+
self._known_runs.discard(handle.run_id)
|
|
447
|
+
self._run_sessions.pop(handle.run_id, None)
|
|
448
|
+
close = getattr(self._runner, "close", None)
|
|
449
|
+
if callable(close):
|
|
450
|
+
result = close()
|
|
451
|
+
if inspect.isawaitable(result):
|
|
452
|
+
await result
|
|
453
|
+
|
|
454
|
+
# ---- 内部:cancel 中断 ----
|
|
455
|
+
|
|
456
|
+
async def _interrupt_active_run(self, run: _ActiveRun) -> None:
|
|
457
|
+
"""中断活跃 turn:置 interrupt 事件,由 stream 消费侧的竞速循环取消在途 chunk task。
|
|
458
|
+
|
|
459
|
+
机制链(经实证,见 test_adapter_contract.test_cancel_active_turn_returns_interrupted):
|
|
460
|
+
``interrupt_event.set()`` → :meth:`_map_runner_stream` 的 ``asyncio.wait`` 竞速命中 →
|
|
461
|
+
``chunk_task.cancel()`` → ``CancelledError`` 传入 runner 生成器正在 in-flight 的
|
|
462
|
+
``__anext__`` await(ADK ``run_async`` 的 LLM/tool 调用就在其中),从而真实打断执行,
|
|
463
|
+
而非仅在事件间停止消费。
|
|
464
|
+
|
|
465
|
+
前提与边界(诚实声明):ADK / LangGraph 均**不暴露原生 cancel 方法**(Runner 只有
|
|
466
|
+
run/run_async/close 等),因此 asyncio 任务取消是**唯一且正确**的中断机制。它要求
|
|
467
|
+
runner 的执行发生在其 stream async generator 的 ``__anext__`` 内(ADK/LG 流式即如此);
|
|
468
|
+
若某 runner 把实际工作放到与 ``__anext__`` 解耦的后台 task,则不在本机制覆盖范围。
|
|
469
|
+
不在此跨 task 对正被另一 task 迭代的 async generator 直接 aclose(不可靠)。
|
|
470
|
+
"""
|
|
471
|
+
run.interrupt_event.set()
|
|
472
|
+
if run.stream is None:
|
|
473
|
+
run.cancellation_ack.set()
|
|
474
|
+
return
|
|
475
|
+
if run.chunk_task is not None:
|
|
476
|
+
run.chunk_task.cancel()
|
|
477
|
+
await asyncio.wait_for(run.cancellation_ack.wait(), timeout=2)
|
|
478
|
+
|
|
479
|
+
# ---- 内部:stream → RuntimeEvent ----
|
|
480
|
+
|
|
481
|
+
def _next_invocation_id(self) -> str:
|
|
482
|
+
self._seq += 1
|
|
483
|
+
return f"inv_{self._runtime_type}_{self._seq}"
|
|
484
|
+
|
|
485
|
+
def _next_seq(self) -> int:
|
|
486
|
+
self._seq += 1
|
|
487
|
+
return self._seq
|
|
488
|
+
|
|
489
|
+
async def _stream_events(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
|
|
490
|
+
run = self._active_runs.get(handle.run_id)
|
|
491
|
+
if run is None:
|
|
492
|
+
run = _ActiveRun(invocation_id=handle.run_id, session_id=handle.session_id)
|
|
493
|
+
self._active_runs[handle.run_id] = run
|
|
494
|
+
|
|
495
|
+
# 消费 pending cancel:start 时若已记 pending,立即中断该 turn。
|
|
496
|
+
if handle.run_id in self._pending_cancels:
|
|
497
|
+
self._pending_cancels.discard(handle.run_id)
|
|
498
|
+
yield self._event(
|
|
499
|
+
handle,
|
|
500
|
+
EventType.RUN_CANCELED,
|
|
501
|
+
{
|
|
502
|
+
"status": "cancelled",
|
|
503
|
+
"cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
|
|
504
|
+
},
|
|
505
|
+
)
|
|
506
|
+
return
|
|
507
|
+
|
|
508
|
+
if run.skip_runner:
|
|
509
|
+
run.done = True
|
|
510
|
+
self._active_runs.pop(handle.run_id, None)
|
|
511
|
+
yield self._event(
|
|
512
|
+
handle,
|
|
513
|
+
EventType.RUN_COMPLETED,
|
|
514
|
+
self._completion_payload(handle, status="already_resumed"),
|
|
515
|
+
)
|
|
516
|
+
return
|
|
517
|
+
|
|
518
|
+
if run.resume_key is not None:
|
|
519
|
+
self._consumed_resumes.add(run.resume_key)
|
|
520
|
+
|
|
521
|
+
yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
|
|
522
|
+
|
|
523
|
+
request = run.__dict__.get("_start_request")
|
|
524
|
+
runner_input = self._build_runner_input(handle, request)
|
|
525
|
+
gen: Optional[AsyncIterator[RuntimeEvent]] = None
|
|
526
|
+
terminal_event_seen = False
|
|
527
|
+
approval_interrupted = False
|
|
528
|
+
try:
|
|
529
|
+
gen = self._map_runner_stream(handle, runner_input)
|
|
530
|
+
run.stream = gen
|
|
531
|
+
async for event in gen:
|
|
532
|
+
yield event
|
|
533
|
+
if event.event_type == EventType.APPROVAL_REQUESTED:
|
|
534
|
+
approval_interrupted = True
|
|
535
|
+
if event.event_type in {
|
|
536
|
+
EventType.RUN_COMPLETED,
|
|
537
|
+
EventType.RUN_FAILED,
|
|
538
|
+
EventType.RUN_CANCELED,
|
|
539
|
+
EventType.RUN_INTERRUPTED,
|
|
540
|
+
}:
|
|
541
|
+
terminal_event_seen = True
|
|
542
|
+
if event.event_type in {EventType.RUN_FAILED, EventType.RUN_CANCELED}:
|
|
543
|
+
return
|
|
544
|
+
|
|
545
|
+
if run.interrupt_event.is_set() and not terminal_event_seen:
|
|
546
|
+
yield self._event(
|
|
547
|
+
handle,
|
|
548
|
+
EventType.RUN_CANCELED,
|
|
549
|
+
{
|
|
550
|
+
"status": "cancelled",
|
|
551
|
+
"cancel_result": CancelResult.INTERRUPTED_ACTIVE_TURN.value,
|
|
552
|
+
},
|
|
553
|
+
)
|
|
554
|
+
elif approval_interrupted and not terminal_event_seen:
|
|
555
|
+
yield self._event(
|
|
556
|
+
handle,
|
|
557
|
+
EventType.RUN_INTERRUPTED,
|
|
558
|
+
{"status": "input_required"},
|
|
559
|
+
)
|
|
560
|
+
elif not terminal_event_seen:
|
|
561
|
+
yield self._event(
|
|
562
|
+
handle,
|
|
563
|
+
EventType.RUN_COMPLETED,
|
|
564
|
+
self._completion_payload(
|
|
565
|
+
handle,
|
|
566
|
+
status="completed",
|
|
567
|
+
metrics=run.completion_metrics,
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
finally:
|
|
571
|
+
run.stream = None
|
|
572
|
+
run.done = True
|
|
573
|
+
self._active_runs.pop(handle.run_id, None)
|
|
574
|
+
|
|
575
|
+
def _build_runner_input(self, handle: RunHandle, request: Optional[StartRequest]) -> dict:
|
|
576
|
+
# resume 覆盖优先:_resume_native 注入的 checkpoint_resume + framework_ref
|
|
577
|
+
# 直接作为 runner 输入,驱动框架原生恢复。
|
|
578
|
+
run = self._active_runs.get(handle.run_id)
|
|
579
|
+
override = run.__dict__.get("_resume_input_override") if run is not None else None
|
|
580
|
+
prepared_start = run.__dict__.get("_prepared_start") if run is not None else None
|
|
581
|
+
if override is not None:
|
|
582
|
+
merged = (
|
|
583
|
+
dict(prepared_start.runner_input)
|
|
584
|
+
if isinstance(prepared_start, PreparedRuntimeStart)
|
|
585
|
+
else {}
|
|
586
|
+
)
|
|
587
|
+
base_metadata = merged.get("metadata")
|
|
588
|
+
merged.update({
|
|
589
|
+
"input": override.get("input"),
|
|
590
|
+
"session_id": handle.session_id,
|
|
591
|
+
"invocation_id": handle.run_id,
|
|
592
|
+
"metadata": {
|
|
593
|
+
**(dict(base_metadata) if isinstance(base_metadata, Mapping) else {}),
|
|
594
|
+
**dict(override.get("metadata") or {}),
|
|
595
|
+
},
|
|
596
|
+
})
|
|
597
|
+
for key, value in override.items():
|
|
598
|
+
if key not in ("input", "metadata"):
|
|
599
|
+
merged[key] = value
|
|
600
|
+
return merged
|
|
601
|
+
|
|
602
|
+
if isinstance(prepared_start, PreparedRuntimeStart):
|
|
603
|
+
return dict(prepared_start.runner_input)
|
|
604
|
+
|
|
605
|
+
metadata: Dict[str, Any] = {}
|
|
606
|
+
request_config: Dict[str, Any] = {}
|
|
607
|
+
input_value: Any = ""
|
|
608
|
+
if request is not None:
|
|
609
|
+
input_value = request.input
|
|
610
|
+
metadata = dict(request.metadata or {})
|
|
611
|
+
request_config = dict(request.config or {})
|
|
612
|
+
return {
|
|
613
|
+
**request_config,
|
|
614
|
+
"input": input_value,
|
|
615
|
+
"session_id": handle.session_id,
|
|
616
|
+
"invocation_id": handle.run_id,
|
|
617
|
+
"metadata": metadata,
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async def _map_runner_stream(
|
|
621
|
+
self, handle: RunHandle, runner_input: dict
|
|
622
|
+
) -> AsyncIterator[RuntimeEvent]:
|
|
623
|
+
run = self._active_runs.get(handle.run_id)
|
|
624
|
+
interrupt = run.interrupt_event if run is not None else None
|
|
625
|
+
prepared_start = run.__dict__.get("_prepared_start") if run is not None else None
|
|
626
|
+
invocation_context = (
|
|
627
|
+
prepared_start.context if isinstance(prepared_start, PreparedRuntimeStart) else None
|
|
628
|
+
)
|
|
629
|
+
scope = (
|
|
630
|
+
platform_invocation_scope(invocation_context)
|
|
631
|
+
if invocation_context is not None
|
|
632
|
+
else nullcontext()
|
|
633
|
+
)
|
|
634
|
+
runner_name = _runner_name(self._runner)
|
|
635
|
+
accumulated_output = ""
|
|
636
|
+
usage: dict[str, Any] = {}
|
|
637
|
+
runner_gen: Optional[AsyncIterator[Any]] = None
|
|
638
|
+
async with _conversation_span_scope(runner_name) as span:
|
|
639
|
+
if isinstance(prepared_start, PreparedRuntimeStart):
|
|
640
|
+
_set_conversation_span_attributes(
|
|
641
|
+
span,
|
|
642
|
+
agent_id=str(handle.native_ref.get("agent_id") or "agent"),
|
|
643
|
+
user_id=str(handle.native_ref.get("user_id") or "user"),
|
|
644
|
+
session_id=handle.session_id,
|
|
645
|
+
invocation_id=handle.run_id,
|
|
646
|
+
runner_name=runner_name,
|
|
647
|
+
model=prepared_start.context.model,
|
|
648
|
+
response_id=prepared_start.response_id,
|
|
649
|
+
)
|
|
650
|
+
_set_conversation_input_attributes(span, prepared_start.input_text)
|
|
651
|
+
try:
|
|
652
|
+
with scope:
|
|
653
|
+
canonical_stream = getattr(self._runner, "stream_runtime_events", None)
|
|
654
|
+
stream_result = (
|
|
655
|
+
canonical_stream(runner_input)
|
|
656
|
+
if callable(canonical_stream)
|
|
657
|
+
else self._runner.stream(runner_input)
|
|
658
|
+
)
|
|
659
|
+
if inspect.iscoroutine(stream_result):
|
|
660
|
+
# runner.stream 若声明为 async def -> AsyncIterator(非 async generator),
|
|
661
|
+
# 调用返回 coroutine,需 await 得到迭代器。
|
|
662
|
+
stream_result = await stream_result
|
|
663
|
+
runner_gen = cast(AsyncIterator[Any], stream_result)
|
|
664
|
+
while True:
|
|
665
|
+
# 竞速:下一个 runner chunk vs cancel 中断事件。
|
|
666
|
+
chunk_task = asyncio.ensure_future(_anext_or_stop(runner_gen))
|
|
667
|
+
if run is not None:
|
|
668
|
+
run.chunk_task = chunk_task
|
|
669
|
+
wait_set = {chunk_task}
|
|
670
|
+
interrupt_task = (
|
|
671
|
+
asyncio.ensure_future(interrupt.wait())
|
|
672
|
+
if interrupt is not None
|
|
673
|
+
else None
|
|
674
|
+
)
|
|
675
|
+
if interrupt_task is not None:
|
|
676
|
+
wait_set.add(interrupt_task)
|
|
677
|
+
done, pending = await asyncio.wait(
|
|
678
|
+
wait_set, return_when=asyncio.FIRST_COMPLETED
|
|
679
|
+
)
|
|
680
|
+
for task in pending:
|
|
681
|
+
task.cancel()
|
|
682
|
+
if pending:
|
|
683
|
+
await asyncio.gather(*pending, return_exceptions=True)
|
|
684
|
+
if interrupt_task is not None and interrupt_task in done:
|
|
685
|
+
# cancel 中断:安全关闭 runner 流(同一 task)并停止。
|
|
686
|
+
chunk_task.cancel()
|
|
687
|
+
await asyncio.gather(chunk_task, return_exceptions=True)
|
|
688
|
+
return
|
|
689
|
+
try:
|
|
690
|
+
chunk = chunk_task.result()
|
|
691
|
+
except asyncio.CancelledError:
|
|
692
|
+
if interrupt is not None and interrupt.is_set():
|
|
693
|
+
return
|
|
694
|
+
raise
|
|
695
|
+
finally:
|
|
696
|
+
if run is not None:
|
|
697
|
+
run.chunk_task = None
|
|
698
|
+
if chunk is _STREAM_STOP:
|
|
699
|
+
return
|
|
700
|
+
if isinstance(chunk, RuntimeEvent):
|
|
701
|
+
if chunk.event_type in {
|
|
702
|
+
EventType.TEXT_DELTA,
|
|
703
|
+
EventType.TEXT_COMPLETED,
|
|
704
|
+
} and chunk.phase == "final_answer":
|
|
705
|
+
text = self._coerce(chunk.payload.get("text"))
|
|
706
|
+
if chunk.event_type == EventType.TEXT_COMPLETED:
|
|
707
|
+
accumulated_output = text
|
|
708
|
+
else:
|
|
709
|
+
accumulated_output += text
|
|
710
|
+
elif chunk.event_type == EventType.USAGE_REPORTED:
|
|
711
|
+
usage.update(chunk.payload)
|
|
712
|
+
if isinstance(chunk, dict):
|
|
713
|
+
chunk_type = str(chunk.get("type") or "")
|
|
714
|
+
if chunk_type == "final" and run is not None:
|
|
715
|
+
for source_key, target_key in (
|
|
716
|
+
("duration_ms", "duration_ms"),
|
|
717
|
+
("started_at", "started_at"),
|
|
718
|
+
("completed_at", "completed_at"),
|
|
719
|
+
("metrics_source", "source"),
|
|
720
|
+
):
|
|
721
|
+
if chunk.get(source_key) is not None:
|
|
722
|
+
run.completion_metrics[target_key] = chunk[source_key]
|
|
723
|
+
if chunk_type in {"final", "text", "text_delta"}:
|
|
724
|
+
text = self._coerce(
|
|
725
|
+
chunk.get("delta") or chunk.get("output") or chunk.get("data")
|
|
726
|
+
)
|
|
727
|
+
if text:
|
|
728
|
+
if chunk_type == "final" or chunk.get("replace"):
|
|
729
|
+
accumulated_output = text
|
|
730
|
+
else:
|
|
731
|
+
accumulated_output += text
|
|
732
|
+
raw_usage = chunk.get("usage")
|
|
733
|
+
if isinstance(raw_usage, dict):
|
|
734
|
+
usage.update(raw_usage)
|
|
735
|
+
event = self._chunk_to_event(handle, run, chunk)
|
|
736
|
+
if event is not None:
|
|
737
|
+
yield event
|
|
738
|
+
a2ui_surface = _a2ui_surface_event(chunk)
|
|
739
|
+
if a2ui_surface is not None:
|
|
740
|
+
event_type, payload = a2ui_surface
|
|
741
|
+
yield self._event(handle, event_type, payload)
|
|
742
|
+
finally:
|
|
743
|
+
if accumulated_output:
|
|
744
|
+
_set_conversation_output_attributes(span, accumulated_output)
|
|
745
|
+
_set_conversation_usage_attributes(span, usage)
|
|
746
|
+
if runner_gen is not None:
|
|
747
|
+
aclose = getattr(runner_gen, "aclose", None)
|
|
748
|
+
if callable(aclose):
|
|
749
|
+
try:
|
|
750
|
+
await aclose()
|
|
751
|
+
except Exception: # noqa: BLE001
|
|
752
|
+
pass
|
|
753
|
+
if run is not None:
|
|
754
|
+
run.cancellation_ack.set()
|
|
755
|
+
|
|
756
|
+
def _chunk_to_event(
|
|
757
|
+
self, handle: RunHandle, run: Optional[_ActiveRun], chunk: Any
|
|
758
|
+
) -> Optional[RuntimeEvent]:
|
|
759
|
+
if isinstance(chunk, RuntimeEvent):
|
|
760
|
+
# Outer adapter owns the public lifecycle envelope. A native
|
|
761
|
+
# Runtime may emit its own RUN_STARTED with private identifiers;
|
|
762
|
+
# suppress that duplicate and rebind all other canonical events
|
|
763
|
+
# to the public handle without flattening their payloads.
|
|
764
|
+
if chunk.event_type == EventType.RUN_STARTED:
|
|
765
|
+
return None
|
|
766
|
+
return RuntimeEvent.create(
|
|
767
|
+
chunk.event_type,
|
|
768
|
+
agent_id=str(handle.native_ref.get("agent_id") or "agent"),
|
|
769
|
+
user_id=str(handle.native_ref.get("user_id") or "user"),
|
|
770
|
+
session_id=handle.session_id,
|
|
771
|
+
invocation_id=handle.run_id,
|
|
772
|
+
seq_id=self._next_seq(),
|
|
773
|
+
phase=chunk.phase,
|
|
774
|
+
payload=dict(chunk.payload),
|
|
775
|
+
event_id=chunk.event_id,
|
|
776
|
+
timestamp=chunk.timestamp,
|
|
777
|
+
)
|
|
778
|
+
if not isinstance(chunk, dict):
|
|
779
|
+
return self._event(
|
|
780
|
+
handle, EventType.TEXT_DELTA, {"text": str(chunk)}, phase="commentary"
|
|
781
|
+
)
|
|
782
|
+
chunk_type = chunk.get("type")
|
|
783
|
+
if chunk_type in ("reasoning", "reasoning_delta", "thinking"):
|
|
784
|
+
text = self._coerce(
|
|
785
|
+
chunk.get("delta")
|
|
786
|
+
or chunk.get("content")
|
|
787
|
+
or chunk.get("output")
|
|
788
|
+
or chunk.get("data")
|
|
789
|
+
)
|
|
790
|
+
if not text:
|
|
791
|
+
return None
|
|
792
|
+
event_type = (
|
|
793
|
+
EventType.REASONING_COMPLETED
|
|
794
|
+
if chunk.get("status") in ("completed", "done")
|
|
795
|
+
else EventType.REASONING_DELTA
|
|
796
|
+
)
|
|
797
|
+
return self._event(
|
|
798
|
+
handle,
|
|
799
|
+
event_type,
|
|
800
|
+
{"text": text},
|
|
801
|
+
phase="commentary",
|
|
802
|
+
)
|
|
803
|
+
if chunk_type in ("tool_call", "tool_start"):
|
|
804
|
+
call_id = str(
|
|
805
|
+
chunk.get("tool_call_id")
|
|
806
|
+
or chunk.get("call_id")
|
|
807
|
+
or chunk.get("run_id")
|
|
808
|
+
or chunk.get("id")
|
|
809
|
+
or ""
|
|
810
|
+
)
|
|
811
|
+
name = str(chunk.get("tool_name") or chunk.get("name") or "tool")
|
|
812
|
+
return self._event(
|
|
813
|
+
handle,
|
|
814
|
+
EventType.TOOL_CALL_BEGIN,
|
|
815
|
+
{
|
|
816
|
+
"call_id": call_id or name,
|
|
817
|
+
"name": name,
|
|
818
|
+
"args": chunk.get("tool_args", chunk.get("args")),
|
|
819
|
+
},
|
|
820
|
+
)
|
|
821
|
+
if chunk_type in ("tool_result", "tool_end"):
|
|
822
|
+
call_id = str(
|
|
823
|
+
chunk.get("tool_call_id")
|
|
824
|
+
or chunk.get("call_id")
|
|
825
|
+
or chunk.get("run_id")
|
|
826
|
+
or chunk.get("id")
|
|
827
|
+
or ""
|
|
828
|
+
)
|
|
829
|
+
name = str(chunk.get("tool_name") or chunk.get("name") or "tool")
|
|
830
|
+
tool_args = chunk.get("tool_args", chunk.get("args"))
|
|
831
|
+
result = chunk.get("tool_output", chunk.get("output"))
|
|
832
|
+
approval_detail = approval_interrupt_info_from_result(
|
|
833
|
+
result,
|
|
834
|
+
fallback_tool_name=name,
|
|
835
|
+
tool_args=tool_args,
|
|
836
|
+
run_id=call_id or None,
|
|
837
|
+
)
|
|
838
|
+
if approval_detail is not None:
|
|
839
|
+
return self._approval_requested_event(
|
|
840
|
+
handle,
|
|
841
|
+
run,
|
|
842
|
+
detail=approval_detail,
|
|
843
|
+
call_id=call_id,
|
|
844
|
+
)
|
|
845
|
+
return self._event(
|
|
846
|
+
handle,
|
|
847
|
+
EventType.TOOL_CALL_END,
|
|
848
|
+
{
|
|
849
|
+
"call_id": call_id or name,
|
|
850
|
+
"name": name,
|
|
851
|
+
"result": result,
|
|
852
|
+
"error": chunk.get("error"),
|
|
853
|
+
},
|
|
854
|
+
)
|
|
855
|
+
if chunk_type in ("interrupt", "approval", "approval_required"):
|
|
856
|
+
raw_detail = chunk.get("interrupt_info") or chunk.get("detail") or {}
|
|
857
|
+
detail = dict(raw_detail) if isinstance(raw_detail, Mapping) else {}
|
|
858
|
+
call_id = str(
|
|
859
|
+
chunk.get("call_id")
|
|
860
|
+
or chunk.get("approval_id")
|
|
861
|
+
or chunk.get("id")
|
|
862
|
+
or ""
|
|
863
|
+
)
|
|
864
|
+
return self._approval_requested_event(
|
|
865
|
+
handle,
|
|
866
|
+
run,
|
|
867
|
+
detail=detail,
|
|
868
|
+
call_id=call_id,
|
|
869
|
+
)
|
|
870
|
+
if chunk_type == "checkpoint":
|
|
871
|
+
raw_metadata = chunk.get("metadata")
|
|
872
|
+
metadata: dict[str, Any] = raw_metadata if isinstance(raw_metadata, dict) else {}
|
|
873
|
+
raw_agentengine = metadata.get("agentengine")
|
|
874
|
+
agentengine: dict[str, Any] = (
|
|
875
|
+
raw_agentengine if isinstance(raw_agentengine, dict) else {}
|
|
876
|
+
)
|
|
877
|
+
framework = str(agentengine.get("framework") or self._runtime_type)
|
|
878
|
+
framework_ref = agentengine.get("framework_ref") or {}
|
|
879
|
+
runtime_ref = (
|
|
880
|
+
framework_ref.get(framework) if isinstance(framework_ref, dict) else {}
|
|
881
|
+
) or {}
|
|
882
|
+
checkpoint_id = str(
|
|
883
|
+
runtime_ref.get("checkpoint_id") if isinstance(runtime_ref, dict) else ""
|
|
884
|
+
)
|
|
885
|
+
if not checkpoint_id:
|
|
886
|
+
return None
|
|
887
|
+
handle.native_ref["checkpoint_id"] = checkpoint_id
|
|
888
|
+
known_checkpoint_ids = handle.native_ref.setdefault("known_checkpoint_ids", [])
|
|
889
|
+
if checkpoint_id not in known_checkpoint_ids:
|
|
890
|
+
known_checkpoint_ids.append(checkpoint_id)
|
|
891
|
+
handle.native_ref["framework_ref"] = framework_ref
|
|
892
|
+
if isinstance(runtime_ref, dict):
|
|
893
|
+
handle.native_ref.update(runtime_ref)
|
|
894
|
+
return self._event(
|
|
895
|
+
handle,
|
|
896
|
+
EventType.CHECKPOINT_CREATED,
|
|
897
|
+
{
|
|
898
|
+
"checkpoint_id": checkpoint_id,
|
|
899
|
+
"granularity": self._checkpoint_capability().granularity,
|
|
900
|
+
"run_id": str(agentengine.get("run_id") or handle.run_id),
|
|
901
|
+
"framework": framework,
|
|
902
|
+
"framework_ref": framework_ref,
|
|
903
|
+
"resume_target": framework_ref,
|
|
904
|
+
},
|
|
905
|
+
)
|
|
906
|
+
if chunk_type == "graph_update":
|
|
907
|
+
return self._event(
|
|
908
|
+
handle,
|
|
909
|
+
EventType.RUN_PROGRESS,
|
|
910
|
+
{
|
|
911
|
+
"status": "in_progress",
|
|
912
|
+
"node": str(chunk.get("node") or ""),
|
|
913
|
+
"state_update": self._coerce(chunk.get("output")),
|
|
914
|
+
},
|
|
915
|
+
)
|
|
916
|
+
if chunk_type == "usage":
|
|
917
|
+
raw_usage = chunk.get("usage")
|
|
918
|
+
usage: dict[str, Any] = raw_usage if isinstance(raw_usage, dict) else {}
|
|
919
|
+
return self._event(
|
|
920
|
+
handle,
|
|
921
|
+
EventType.USAGE_REPORTED,
|
|
922
|
+
{
|
|
923
|
+
"input_tokens": int(usage.get("input_tokens") or 0),
|
|
924
|
+
"output_tokens": int(usage.get("output_tokens") or 0),
|
|
925
|
+
"total_tokens": int(usage.get("total_tokens") or 0),
|
|
926
|
+
"cached_tokens": int(usage.get("cached_tokens") or 0),
|
|
927
|
+
"reasoning_tokens": int(usage.get("reasoning_tokens") or 0),
|
|
928
|
+
"source": str(usage.get("source") or self._runtime_type),
|
|
929
|
+
},
|
|
930
|
+
)
|
|
931
|
+
if chunk_type == "error":
|
|
932
|
+
error = self._coerce(chunk.get("message") or chunk.get("error"))
|
|
933
|
+
return self._event(
|
|
934
|
+
handle,
|
|
935
|
+
EventType.RUN_FAILED,
|
|
936
|
+
{
|
|
937
|
+
"status": "failed",
|
|
938
|
+
"error": error or "runner failed",
|
|
939
|
+
},
|
|
940
|
+
)
|
|
941
|
+
if chunk_type == "final":
|
|
942
|
+
return self._event(
|
|
943
|
+
handle,
|
|
944
|
+
EventType.TEXT_COMPLETED,
|
|
945
|
+
{"text": self._coerce(chunk.get("output"))},
|
|
946
|
+
phase="final_answer",
|
|
947
|
+
)
|
|
948
|
+
text = self._coerce(chunk.get("delta") or chunk.get("output") or chunk.get("data"))
|
|
949
|
+
if not text:
|
|
950
|
+
return None
|
|
951
|
+
payload: dict[str, Any] = {"text": text}
|
|
952
|
+
if chunk.get("replace"):
|
|
953
|
+
payload["replace"] = True
|
|
954
|
+
return self._event(handle, EventType.TEXT_DELTA, payload, phase="commentary")
|
|
955
|
+
|
|
956
|
+
def _approval_requested_event(
|
|
957
|
+
self,
|
|
958
|
+
handle: RunHandle,
|
|
959
|
+
run: Optional[_ActiveRun],
|
|
960
|
+
*,
|
|
961
|
+
detail: Mapping[str, Any],
|
|
962
|
+
call_id: str,
|
|
963
|
+
) -> RuntimeEvent:
|
|
964
|
+
"""Convert one framework/tool approval to the canonical runtime event."""
|
|
965
|
+
|
|
966
|
+
approval_id = str(
|
|
967
|
+
detail.get("approval_request_id") or detail.get("id") or call_id or ""
|
|
968
|
+
)
|
|
969
|
+
resolved_call_id = str(call_id or approval_id)
|
|
970
|
+
if run is not None and approval_id:
|
|
971
|
+
run.pending_approvals.add(approval_id)
|
|
972
|
+
if approval_id:
|
|
973
|
+
pending_approval_ids = handle.native_ref.setdefault("pending_approval_ids", [])
|
|
974
|
+
if approval_id not in pending_approval_ids:
|
|
975
|
+
pending_approval_ids.append(approval_id)
|
|
976
|
+
return self._event(
|
|
977
|
+
handle,
|
|
978
|
+
EventType.APPROVAL_REQUESTED,
|
|
979
|
+
{
|
|
980
|
+
"approval_id": approval_id,
|
|
981
|
+
"call_id": resolved_call_id,
|
|
982
|
+
"kind": "tool",
|
|
983
|
+
"detail": dict(detail),
|
|
984
|
+
},
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
def _event(
|
|
988
|
+
self,
|
|
989
|
+
handle: RunHandle,
|
|
990
|
+
event_type: str,
|
|
991
|
+
payload: dict,
|
|
992
|
+
*,
|
|
993
|
+
phase: Optional[str] = None,
|
|
994
|
+
) -> RuntimeEvent:
|
|
995
|
+
return RuntimeEvent.create(
|
|
996
|
+
event_type,
|
|
997
|
+
agent_id=str(handle.native_ref.get("agent_id") or "agent"),
|
|
998
|
+
user_id=str(handle.native_ref.get("user_id") or "user"),
|
|
999
|
+
session_id=handle.session_id,
|
|
1000
|
+
invocation_id=handle.run_id,
|
|
1001
|
+
seq_id=self._next_seq(),
|
|
1002
|
+
phase=phase,
|
|
1003
|
+
payload=payload,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
def _completion_payload(
|
|
1007
|
+
self,
|
|
1008
|
+
handle: RunHandle,
|
|
1009
|
+
*,
|
|
1010
|
+
status: str,
|
|
1011
|
+
metrics: Mapping[str, Any] | None = None,
|
|
1012
|
+
) -> dict[str, Any]:
|
|
1013
|
+
payload: dict[str, Any] = {"status": status, **dict(metrics or {})}
|
|
1014
|
+
framework_ref = handle.native_ref.get("framework_ref")
|
|
1015
|
+
if isinstance(framework_ref, Mapping) and framework_ref:
|
|
1016
|
+
payload["agentengine"] = {
|
|
1017
|
+
"run_id": handle.run_id,
|
|
1018
|
+
"framework": self._runtime_type,
|
|
1019
|
+
"framework_ref": dict(framework_ref),
|
|
1020
|
+
}
|
|
1021
|
+
return payload
|
|
1022
|
+
|
|
1023
|
+
@staticmethod
|
|
1024
|
+
def _coerce(value: Any) -> str:
|
|
1025
|
+
if value is None:
|
|
1026
|
+
return ""
|
|
1027
|
+
if isinstance(value, str):
|
|
1028
|
+
return value
|
|
1029
|
+
return str(value)
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
__all__ = ["RunnerRuntimeAdapter", "_RunnerAsBaseRuntime"]
|