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,795 @@
|
|
|
1
|
+
"""Session event projection, pagination, and checkpoint policy helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import AsyncIterator, Mapping
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Any, Optional, cast
|
|
10
|
+
|
|
11
|
+
from fastapi import HTTPException
|
|
12
|
+
|
|
13
|
+
from ksadk.conversations.session_title import (
|
|
14
|
+
HEURISTIC_SESSION_TITLE_SOURCE,
|
|
15
|
+
build_fallback_title,
|
|
16
|
+
build_heuristic_title,
|
|
17
|
+
)
|
|
18
|
+
from ksadk.server.factory import get_state
|
|
19
|
+
from ksadk.sessions import ConversationSessionCore, Session, SessionEvent
|
|
20
|
+
|
|
21
|
+
from . import dependencies as deps
|
|
22
|
+
from .common import _sanitize_session_state_for_action
|
|
23
|
+
from .models import (
|
|
24
|
+
_EVENT_SCAN_PAGE_SIZE,
|
|
25
|
+
_latest_session_run_metadata,
|
|
26
|
+
_parse_iso_datetime,
|
|
27
|
+
_session_topic_from_events,
|
|
28
|
+
_session_user_prompt_from_event,
|
|
29
|
+
_truncate_session_text,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def _require_action_session(
|
|
36
|
+
service,
|
|
37
|
+
*,
|
|
38
|
+
session_id: str,
|
|
39
|
+
agent_id: Optional[str] = None,
|
|
40
|
+
user_id: Optional[str] = None,
|
|
41
|
+
) -> Session:
|
|
42
|
+
"""Resolve one runtime session while enforcing every supplied scope."""
|
|
43
|
+
|
|
44
|
+
session = await service.get_session_metadata(str(session_id or "").strip())
|
|
45
|
+
if (
|
|
46
|
+
session is None
|
|
47
|
+
or (agent_id is not None and session.agent_id != agent_id)
|
|
48
|
+
or (user_id is not None and session.user_id != user_id)
|
|
49
|
+
):
|
|
50
|
+
raise HTTPException(status_code=404, detail="Session not found")
|
|
51
|
+
return cast(Session, session)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _session_to_action_payload(session: Session) -> dict[str, Any]:
|
|
55
|
+
runner = get_state().runner
|
|
56
|
+
events = list(session.events or [])
|
|
57
|
+
if not events:
|
|
58
|
+
try:
|
|
59
|
+
events = await deps.resolve_session_service().get_events(session.id)
|
|
60
|
+
except Exception as exc:
|
|
61
|
+
logger.debug("Failed to hydrate events for session %s: %s", session.id, exc)
|
|
62
|
+
events = []
|
|
63
|
+
event_prompts = [
|
|
64
|
+
_session_user_prompt_from_event(event)
|
|
65
|
+
for event in events
|
|
66
|
+
if event.event_type == "user_message"
|
|
67
|
+
]
|
|
68
|
+
event_prompts = [prompt for prompt in event_prompts if prompt]
|
|
69
|
+
first_prompt = session.first_prompt or (event_prompts[0] if event_prompts else "")
|
|
70
|
+
last_prompt = session.last_prompt or (event_prompts[-1] if event_prompts else "")
|
|
71
|
+
(
|
|
72
|
+
active_invocation_id,
|
|
73
|
+
active_run_status,
|
|
74
|
+
active_run_mode,
|
|
75
|
+
active_run_trigger,
|
|
76
|
+
) = _latest_session_run_metadata(events)
|
|
77
|
+
title = session.title
|
|
78
|
+
title_source = session.title_source
|
|
79
|
+
if not title:
|
|
80
|
+
title_seed = _session_topic_from_events(events) or first_prompt
|
|
81
|
+
if title_seed:
|
|
82
|
+
title = build_fallback_title(title_seed)
|
|
83
|
+
title_source = "fallback_first_prompt"
|
|
84
|
+
if title_source == "fallback_first_prompt":
|
|
85
|
+
heuristic = build_heuristic_title(
|
|
86
|
+
first_prompt=first_prompt or title,
|
|
87
|
+
assistant_text=session.summary or "",
|
|
88
|
+
)
|
|
89
|
+
if heuristic and heuristic != title:
|
|
90
|
+
title = heuristic
|
|
91
|
+
title_source = HEURISTIC_SESSION_TITLE_SOURCE
|
|
92
|
+
payload = {
|
|
93
|
+
"SessionId": session.id,
|
|
94
|
+
"AgentId": session.agent_id,
|
|
95
|
+
"UserId": session.user_id,
|
|
96
|
+
"Title": title,
|
|
97
|
+
"TitleSource": title_source,
|
|
98
|
+
"Summary": session.summary,
|
|
99
|
+
"FirstPrompt": _truncate_session_text(first_prompt),
|
|
100
|
+
"LastPrompt": _truncate_session_text(last_prompt),
|
|
101
|
+
"ActiveInvocationId": active_invocation_id,
|
|
102
|
+
"ActiveRunStatus": active_run_status,
|
|
103
|
+
"ActiveRunMode": active_run_mode,
|
|
104
|
+
"ActiveRunTrigger": active_run_trigger,
|
|
105
|
+
"State": _sanitize_session_state_for_action(session.state),
|
|
106
|
+
"CreatedAt": session.created_at,
|
|
107
|
+
"UpdatedAt": session.updated_at,
|
|
108
|
+
"Version": session.version,
|
|
109
|
+
}
|
|
110
|
+
if runner is not None:
|
|
111
|
+
try:
|
|
112
|
+
continuity = await runner.get_session_adapter().describe_continuity(
|
|
113
|
+
runner=runner,
|
|
114
|
+
session=session,
|
|
115
|
+
core=ConversationSessionCore(deps.resolve_session_service()),
|
|
116
|
+
)
|
|
117
|
+
payload["Continuity"] = continuity.to_payload()
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
logger.debug("Failed to describe continuity for session %s: %s", session.id, exc)
|
|
120
|
+
return payload
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _event_to_action_payload(event: SessionEvent) -> dict[str, Any]:
|
|
124
|
+
payload = {
|
|
125
|
+
"EventId": event.id,
|
|
126
|
+
"SessionId": event.session_id,
|
|
127
|
+
"Author": event.author,
|
|
128
|
+
"EventType": event.event_type,
|
|
129
|
+
"Content": event.content,
|
|
130
|
+
"Timestamp": event.timestamp,
|
|
131
|
+
"SeqId": event.seq_id,
|
|
132
|
+
"Metadata": event.metadata,
|
|
133
|
+
}
|
|
134
|
+
if event.invocation_id:
|
|
135
|
+
payload["InvocationId"] = event.invocation_id
|
|
136
|
+
return payload
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def _iter_with_idle_heartbeat(source: AsyncIterator[Any]):
|
|
140
|
+
"""转发 source 的 chunk,空闲超时发 ``: ping`` 而不取消 source 迭代器。
|
|
141
|
+
|
|
142
|
+
直接用 ``asyncio.wait_for(source.__anext__(), timeout=...)`` 会在超时时 cancel
|
|
143
|
+
掉正在进行的 ``__anext__()``;若 source 是原生 async generator,cancel 会触发
|
|
144
|
+
GeneratorExit 把它关闭,下一轮 ``__anext__`` 抛 StopAsyncIteration → 流提前断。
|
|
145
|
+
用 pump task + queue 隔离:pump 独立消费 source,主循环只对 queue.get() 计时,
|
|
146
|
+
超时时发心跳而不碰 source。
|
|
147
|
+
"""
|
|
148
|
+
queue: asyncio.Queue[Any] = asyncio.Queue(maxsize=1)
|
|
149
|
+
|
|
150
|
+
async def pump() -> None:
|
|
151
|
+
async for chunk in source:
|
|
152
|
+
await queue.put(chunk)
|
|
153
|
+
|
|
154
|
+
pump_task = asyncio.create_task(pump())
|
|
155
|
+
get_task: asyncio.Task[Any] | None = None
|
|
156
|
+
try:
|
|
157
|
+
while True:
|
|
158
|
+
# Drain queued data before observing producer completion so a final
|
|
159
|
+
# chunk is never lost when the producer exits immediately after it.
|
|
160
|
+
if not queue.empty():
|
|
161
|
+
yield ("chunk", queue.get_nowait())
|
|
162
|
+
continue
|
|
163
|
+
if pump_task.done():
|
|
164
|
+
pump_task.result()
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
get_task = asyncio.create_task(queue.get())
|
|
168
|
+
done, _ = await asyncio.wait(
|
|
169
|
+
{get_task, pump_task},
|
|
170
|
+
timeout=deps.heartbeat_interval(),
|
|
171
|
+
return_when=asyncio.FIRST_COMPLETED,
|
|
172
|
+
)
|
|
173
|
+
if get_task in done:
|
|
174
|
+
chunk = get_task.result()
|
|
175
|
+
get_task = None
|
|
176
|
+
yield ("chunk", chunk)
|
|
177
|
+
continue
|
|
178
|
+
if pump_task in done:
|
|
179
|
+
# The final queue.put() may have completed in the same loop turn
|
|
180
|
+
# as the producer. Let the pending getter consume it first.
|
|
181
|
+
if not queue.empty():
|
|
182
|
+
chunk = await get_task
|
|
183
|
+
get_task = None
|
|
184
|
+
yield ("chunk", chunk)
|
|
185
|
+
continue
|
|
186
|
+
get_task.cancel()
|
|
187
|
+
await asyncio.gather(get_task, return_exceptions=True)
|
|
188
|
+
get_task = None
|
|
189
|
+
pump_task.result()
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
get_task.cancel()
|
|
193
|
+
await asyncio.gather(get_task, return_exceptions=True)
|
|
194
|
+
get_task = None
|
|
195
|
+
yield ("heartbeat", None)
|
|
196
|
+
finally:
|
|
197
|
+
if get_task is not None and not get_task.done():
|
|
198
|
+
get_task.cancel()
|
|
199
|
+
await asyncio.gather(get_task, return_exceptions=True)
|
|
200
|
+
if not pump_task.done():
|
|
201
|
+
pump_task.cancel()
|
|
202
|
+
await asyncio.gather(pump_task, return_exceptions=True)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _checkpoint_event_to_action_payload(event: SessionEvent) -> dict[str, Any] | None:
|
|
206
|
+
if event.event_type != "run_checkpoint":
|
|
207
|
+
return None
|
|
208
|
+
metadata = event.metadata or {}
|
|
209
|
+
run_id = str(metadata.get("run_id") or "").strip()
|
|
210
|
+
checkpoint_id = str(metadata.get("checkpoint_id") or "").strip()
|
|
211
|
+
framework = str(metadata.get("framework") or "").strip()
|
|
212
|
+
framework_ref = metadata.get("framework_ref")
|
|
213
|
+
if not run_id or not checkpoint_id or not framework or not isinstance(framework_ref, Mapping):
|
|
214
|
+
return None
|
|
215
|
+
next_node = str(metadata.get("next_node") or "").strip()
|
|
216
|
+
if not next_node:
|
|
217
|
+
langgraph_ref = framework_ref.get("langgraph")
|
|
218
|
+
if isinstance(langgraph_ref, Mapping):
|
|
219
|
+
next_node = str(langgraph_ref.get("next_node") or "").strip()
|
|
220
|
+
is_terminal = bool(metadata.get("is_terminal", False))
|
|
221
|
+
is_resumable_raw = metadata.get("is_resumable")
|
|
222
|
+
is_resumable = is_resumable_raw if isinstance(is_resumable_raw, bool) else None
|
|
223
|
+
backend = str(metadata.get("backend") or "unknown").strip() or "unknown"
|
|
224
|
+
scope = str(metadata.get("scope") or "unknown").strip() or "unknown"
|
|
225
|
+
durable = bool(metadata.get("durable", False))
|
|
226
|
+
disabled_reason = str(metadata.get("resume_disabled_reason") or "").strip()
|
|
227
|
+
if is_terminal:
|
|
228
|
+
is_resumable = False
|
|
229
|
+
disabled_reason = disabled_reason or "该 checkpoint 已是终态;可选择更早恢复点重跑"
|
|
230
|
+
if backend == "memory" or scope == "process_local":
|
|
231
|
+
is_resumable = False
|
|
232
|
+
disabled_reason = disabled_reason or "进程内 checkpoint 不能跨实例恢复"
|
|
233
|
+
resume_status = str(metadata.get("resume_status") or "").strip()
|
|
234
|
+
if not resume_status:
|
|
235
|
+
if is_resumable is True:
|
|
236
|
+
resume_status = "resumable"
|
|
237
|
+
elif is_resumable is False:
|
|
238
|
+
resume_status = "disabled"
|
|
239
|
+
else:
|
|
240
|
+
resume_status = "unknown"
|
|
241
|
+
if resume_status == "disabled" and not disabled_reason:
|
|
242
|
+
disabled_reason = "该 checkpoint 不可恢复"
|
|
243
|
+
artifact_preview = metadata.get("artifact_preview")
|
|
244
|
+
if not isinstance(artifact_preview, Mapping):
|
|
245
|
+
artifact_preview = {}
|
|
246
|
+
resume_count_raw = metadata.get("resume_count")
|
|
247
|
+
try:
|
|
248
|
+
resume_count = int(resume_count_raw) if resume_count_raw is not None else 0
|
|
249
|
+
except (TypeError, ValueError):
|
|
250
|
+
resume_count = 0
|
|
251
|
+
last_resumed_at = metadata.get("last_resumed_at")
|
|
252
|
+
replay_allowed_raw = metadata.get("replay_allowed")
|
|
253
|
+
replay_allowed = replay_allowed_raw if isinstance(replay_allowed_raw, bool) else True
|
|
254
|
+
expires_at = metadata.get("expires_at")
|
|
255
|
+
checkpoint_status = str(metadata.get("checkpoint_status") or "").strip()
|
|
256
|
+
if not checkpoint_status:
|
|
257
|
+
if is_terminal:
|
|
258
|
+
checkpoint_status = "resumed" if resume_count else "terminal"
|
|
259
|
+
elif is_resumable is False:
|
|
260
|
+
checkpoint_status = "disabled"
|
|
261
|
+
else:
|
|
262
|
+
checkpoint_status = "active"
|
|
263
|
+
payload = {
|
|
264
|
+
"EventId": event.id,
|
|
265
|
+
"SessionId": event.session_id,
|
|
266
|
+
"InvocationId": event.invocation_id,
|
|
267
|
+
"SeqId": event.seq_id,
|
|
268
|
+
"Timestamp": event.timestamp,
|
|
269
|
+
"RunId": run_id,
|
|
270
|
+
"CheckpointId": checkpoint_id,
|
|
271
|
+
"Framework": framework,
|
|
272
|
+
"FrameworkRef": dict(framework_ref),
|
|
273
|
+
"Phase": str(metadata.get("phase") or ""),
|
|
274
|
+
"Metadata": metadata,
|
|
275
|
+
"IsResumable": is_resumable,
|
|
276
|
+
"ResumeStatus": resume_status,
|
|
277
|
+
"IsTerminal": is_terminal,
|
|
278
|
+
"ResumeDisabledReason": disabled_reason,
|
|
279
|
+
"NextNode": next_node,
|
|
280
|
+
"StageKey": str(metadata.get("stage_key") or ""),
|
|
281
|
+
"StageName": str(
|
|
282
|
+
metadata.get("stage_name") or metadata.get("stage") or metadata.get("title") or ""
|
|
283
|
+
),
|
|
284
|
+
"StageIndex": metadata.get("stage_index"),
|
|
285
|
+
"TotalStages": metadata.get("total_stages"),
|
|
286
|
+
"Backend": backend,
|
|
287
|
+
"Scope": scope,
|
|
288
|
+
"Durable": durable,
|
|
289
|
+
"CreatedAt": event.timestamp,
|
|
290
|
+
"ArtifactPreview": dict(artifact_preview),
|
|
291
|
+
"LastResumedAt": last_resumed_at,
|
|
292
|
+
"ResumeCount": resume_count,
|
|
293
|
+
"ReplayAllowed": replay_allowed,
|
|
294
|
+
"ExpiresAt": expires_at,
|
|
295
|
+
"CheckpointStatus": checkpoint_status,
|
|
296
|
+
}
|
|
297
|
+
stage = str(metadata.get("stage") or metadata.get("title") or "").strip()
|
|
298
|
+
summary = str(metadata.get("summary") or metadata.get("description") or "").strip()
|
|
299
|
+
next_action = str(metadata.get("next_action") or metadata.get("nextAction") or "").strip()
|
|
300
|
+
status = str(metadata.get("status") or "").strip()
|
|
301
|
+
if stage:
|
|
302
|
+
payload["Stage"] = stage
|
|
303
|
+
if summary:
|
|
304
|
+
payload["Summary"] = summary
|
|
305
|
+
if next_action:
|
|
306
|
+
payload["NextAction"] = next_action
|
|
307
|
+
if status:
|
|
308
|
+
payload["Status"] = status
|
|
309
|
+
return payload
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _resume_audit_by_checkpoint(
|
|
313
|
+
events: list[SessionEvent],
|
|
314
|
+
) -> dict[tuple[str, str], dict[str, Any]]:
|
|
315
|
+
audit: dict[tuple[str, str], dict[str, Any]] = {}
|
|
316
|
+
for event in events:
|
|
317
|
+
if event.event_type != "run_resume":
|
|
318
|
+
continue
|
|
319
|
+
metadata = event.metadata or {}
|
|
320
|
+
run_id = str(metadata.get("run_id") or "").strip()
|
|
321
|
+
checkpoint_id = str(metadata.get("checkpoint_id") or "").strip()
|
|
322
|
+
if not run_id or not checkpoint_id:
|
|
323
|
+
continue
|
|
324
|
+
key = (run_id, checkpoint_id)
|
|
325
|
+
item = audit.setdefault(key, {"resume_count": 0, "last_resumed_at": None})
|
|
326
|
+
item["resume_count"] = int(item["resume_count"]) + 1
|
|
327
|
+
item["last_resumed_at"] = event.timestamp
|
|
328
|
+
return audit
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _apply_checkpoint_resume_audit(
|
|
332
|
+
checkpoint: dict[str, Any],
|
|
333
|
+
audit_by_checkpoint: Mapping[tuple[str, str], Mapping[str, Any]],
|
|
334
|
+
) -> dict[str, Any]:
|
|
335
|
+
audit = audit_by_checkpoint.get(
|
|
336
|
+
(
|
|
337
|
+
str(checkpoint.get("RunId") or ""),
|
|
338
|
+
str(checkpoint.get("CheckpointId") or ""),
|
|
339
|
+
),
|
|
340
|
+
{},
|
|
341
|
+
)
|
|
342
|
+
metadata = dict(checkpoint.get("Metadata") or {})
|
|
343
|
+
resume_count = int(audit.get("resume_count") or checkpoint.get("ResumeCount") or 0)
|
|
344
|
+
last_resumed_at = audit.get("last_resumed_at") or checkpoint.get("LastResumedAt")
|
|
345
|
+
checkpoint["ResumeCount"] = resume_count
|
|
346
|
+
checkpoint["LastResumedAt"] = last_resumed_at
|
|
347
|
+
metadata["resume_count"] = resume_count
|
|
348
|
+
metadata["last_resumed_at"] = last_resumed_at
|
|
349
|
+
if resume_count and checkpoint.get("CheckpointStatus") in {"", "active"}:
|
|
350
|
+
checkpoint["CheckpointStatus"] = "resumed"
|
|
351
|
+
expires_at = checkpoint.get("ExpiresAt")
|
|
352
|
+
expires_at_dt = _parse_iso_datetime(expires_at)
|
|
353
|
+
if expires_at_dt is not None and expires_at_dt <= datetime.now(timezone.utc):
|
|
354
|
+
checkpoint["IsResumable"] = False
|
|
355
|
+
checkpoint["ResumeStatus"] = "disabled"
|
|
356
|
+
checkpoint["CheckpointStatus"] = "expired"
|
|
357
|
+
checkpoint["ResumeDisabledReason"] = "该 checkpoint 已过期"
|
|
358
|
+
elif resume_count and checkpoint.get("ReplayAllowed") is False:
|
|
359
|
+
checkpoint["IsResumable"] = False
|
|
360
|
+
checkpoint["ResumeStatus"] = "disabled"
|
|
361
|
+
checkpoint["ResumeDisabledReason"] = "该 checkpoint 已恢复过,当前策略不允许重复恢复"
|
|
362
|
+
metadata["checkpoint_status"] = checkpoint.get("CheckpointStatus")
|
|
363
|
+
metadata["resume_status"] = checkpoint.get("ResumeStatus")
|
|
364
|
+
metadata["resume_disabled_reason"] = checkpoint.get("ResumeDisabledReason")
|
|
365
|
+
checkpoint["Metadata"] = metadata
|
|
366
|
+
return checkpoint
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _apply_adk_only_latest_resumable(
|
|
370
|
+
checkpoints: list[dict[str, Any]],
|
|
371
|
+
) -> list[dict[str, Any]]:
|
|
372
|
+
"""P1.4: For ADK invocation_id resume mode, only the latest checkpoint per
|
|
373
|
+
RunId is independently resumable. Older checkpoints get IsResumable=False."""
|
|
374
|
+
latest_by_run: dict[str, int] = {}
|
|
375
|
+
for cp in checkpoints:
|
|
376
|
+
metadata = cp.get("Metadata") or {}
|
|
377
|
+
if not metadata.get("only_latest_resumable"):
|
|
378
|
+
continue
|
|
379
|
+
run_id = str(cp.get("RunId") or "")
|
|
380
|
+
seq_id = int(cp.get("SeqId") or 0)
|
|
381
|
+
if run_id not in latest_by_run or seq_id > latest_by_run[run_id]:
|
|
382
|
+
latest_by_run[run_id] = seq_id
|
|
383
|
+
|
|
384
|
+
for cp in checkpoints:
|
|
385
|
+
metadata = cp.get("Metadata") or {}
|
|
386
|
+
if not metadata.get("only_latest_resumable"):
|
|
387
|
+
continue
|
|
388
|
+
run_id = str(cp.get("RunId") or "")
|
|
389
|
+
seq_id = int(cp.get("SeqId") or 0)
|
|
390
|
+
if seq_id < latest_by_run.get(run_id, 0):
|
|
391
|
+
if cp.get("IsResumable") is True:
|
|
392
|
+
cp["IsResumable"] = False
|
|
393
|
+
cp["ResumeStatus"] = "disabled"
|
|
394
|
+
cp["ResumeDisabledReason"] = "新的恢复点已生成,此恢复点暂停恢复能力"
|
|
395
|
+
metadata["resume_disabled_reason"] = "新的恢复点已生成,此恢复点暂停恢复能力"
|
|
396
|
+
metadata["resume_status"] = "disabled"
|
|
397
|
+
cp["Metadata"] = metadata
|
|
398
|
+
|
|
399
|
+
return checkpoints
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _check_adk_latest_resumable(
|
|
403
|
+
checkpoint: dict[str, Any],
|
|
404
|
+
events: list,
|
|
405
|
+
) -> dict[str, Any]:
|
|
406
|
+
"""P1.4: For a single ADK only_latest_resumable checkpoint, verify it is
|
|
407
|
+
the latest for its RunId. If not, mark IsResumable=False."""
|
|
408
|
+
metadata = checkpoint.get("Metadata") or {}
|
|
409
|
+
if not metadata.get("only_latest_resumable"):
|
|
410
|
+
return checkpoint
|
|
411
|
+
|
|
412
|
+
run_id = str(checkpoint.get("RunId") or "")
|
|
413
|
+
my_seq_id = int(checkpoint.get("SeqId") or 0)
|
|
414
|
+
|
|
415
|
+
max_seq_id = my_seq_id
|
|
416
|
+
for event in events:
|
|
417
|
+
if event.event_type != "run_checkpoint":
|
|
418
|
+
continue
|
|
419
|
+
ev_meta = event.metadata or {}
|
|
420
|
+
if str(ev_meta.get("run_id") or "") != run_id:
|
|
421
|
+
continue
|
|
422
|
+
seq_id = int(event.seq_id or 0)
|
|
423
|
+
if seq_id > max_seq_id:
|
|
424
|
+
max_seq_id = seq_id
|
|
425
|
+
|
|
426
|
+
if my_seq_id < max_seq_id and checkpoint.get("IsResumable") is True:
|
|
427
|
+
checkpoint["IsResumable"] = False
|
|
428
|
+
checkpoint["ResumeStatus"] = "disabled"
|
|
429
|
+
checkpoint["ResumeDisabledReason"] = "新的恢复点已生成,此恢复点暂停恢复能力"
|
|
430
|
+
metadata["resume_disabled_reason"] = "新的恢复点已生成,此恢复点暂停恢复能力"
|
|
431
|
+
metadata["resume_status"] = "disabled"
|
|
432
|
+
checkpoint["Metadata"] = metadata
|
|
433
|
+
|
|
434
|
+
return checkpoint
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
_SIDE_EFFECT_TOOL_NAMES = {
|
|
438
|
+
"write_workspace_file",
|
|
439
|
+
"write_workspace_files",
|
|
440
|
+
"delete_workspace_file",
|
|
441
|
+
"execute_skills",
|
|
442
|
+
"run_command",
|
|
443
|
+
"run_code",
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _tool_receipt_event_to_action_payload(event: SessionEvent) -> dict[str, Any] | None:
|
|
448
|
+
if event.event_type != "tool_result":
|
|
449
|
+
return None
|
|
450
|
+
metadata = event.metadata or {}
|
|
451
|
+
receipt = metadata.get("tool_receipt")
|
|
452
|
+
if not isinstance(receipt, Mapping):
|
|
453
|
+
return None
|
|
454
|
+
tool_name = str(receipt.get("tool_name") or metadata.get("tool_name") or "").strip()
|
|
455
|
+
if not tool_name:
|
|
456
|
+
return None
|
|
457
|
+
return {
|
|
458
|
+
"EventId": event.id,
|
|
459
|
+
"SessionId": event.session_id,
|
|
460
|
+
"InvocationId": event.invocation_id,
|
|
461
|
+
"SeqId": event.seq_id,
|
|
462
|
+
"Timestamp": event.timestamp,
|
|
463
|
+
"ReceiptId": str(receipt.get("receipt_id") or ""),
|
|
464
|
+
"IdempotencyKey": str(receipt.get("idempotency_key") or ""),
|
|
465
|
+
"ToolName": tool_name,
|
|
466
|
+
"ToolCallId": str(receipt.get("tool_call_id") or ""),
|
|
467
|
+
"RunId": str(receipt.get("run_id") or metadata.get("run_id") or ""),
|
|
468
|
+
"CheckpointId": str(receipt.get("checkpoint_id") or ""),
|
|
469
|
+
"Status": str(receipt.get("status") or ""),
|
|
470
|
+
"Replayed": bool(receipt.get("replayed") or metadata.get("replayed")),
|
|
471
|
+
"Metadata": dict(metadata),
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _build_checkpoint_resume_preview(
|
|
476
|
+
*,
|
|
477
|
+
checkpoint: Mapping[str, Any],
|
|
478
|
+
receipts: list[dict[str, Any]],
|
|
479
|
+
receipt_total: int,
|
|
480
|
+
side_effect_receipt_count: int,
|
|
481
|
+
failed_receipt_count: int,
|
|
482
|
+
) -> dict[str, Any]:
|
|
483
|
+
run_id = str(checkpoint.get("RunId") or "")
|
|
484
|
+
risk_level = "low"
|
|
485
|
+
if side_effect_receipt_count:
|
|
486
|
+
risk_level = "medium"
|
|
487
|
+
if failed_receipt_count:
|
|
488
|
+
risk_level = "high"
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
"Checkpoint": dict(checkpoint),
|
|
492
|
+
"Capabilities": {
|
|
493
|
+
"Checkpoints": True,
|
|
494
|
+
"CheckpointResume": checkpoint.get("IsResumable") is not False,
|
|
495
|
+
"ToolReceipts": True,
|
|
496
|
+
"IdempotentToolReplay": True,
|
|
497
|
+
},
|
|
498
|
+
"CanResume": checkpoint.get("IsResumable") is not False,
|
|
499
|
+
"Reason": str(checkpoint.get("ResumeDisabledReason") or ""),
|
|
500
|
+
"NextNode": str(checkpoint.get("NextNode") or ""),
|
|
501
|
+
"ExpectedAction": (
|
|
502
|
+
"resume_from_checkpoint"
|
|
503
|
+
if checkpoint.get("IsResumable") is True
|
|
504
|
+
else ("preview_required" if checkpoint.get("ResumeStatus") == "unknown" else "disabled")
|
|
505
|
+
),
|
|
506
|
+
"ToolReceipts": receipts,
|
|
507
|
+
"ToolReceiptsTruncated": receipt_total > len(receipts),
|
|
508
|
+
"Risk": {
|
|
509
|
+
"Level": risk_level,
|
|
510
|
+
"DuplicateSideEffectRisk": side_effect_receipt_count > 0,
|
|
511
|
+
"SideEffectReceiptCount": side_effect_receipt_count,
|
|
512
|
+
"FailedReceiptCount": failed_receipt_count,
|
|
513
|
+
},
|
|
514
|
+
"Summary": {
|
|
515
|
+
"RunId": run_id,
|
|
516
|
+
"CheckpointId": str(checkpoint.get("CheckpointId") or ""),
|
|
517
|
+
"Phase": str(checkpoint.get("Phase") or ""),
|
|
518
|
+
"ToolReceiptCount": receipt_total,
|
|
519
|
+
},
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _checkpoint_resume_disabled_detail(checkpoint: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
524
|
+
if checkpoint.get("IsResumable") is not False:
|
|
525
|
+
return None
|
|
526
|
+
reason = (
|
|
527
|
+
str(checkpoint.get("ResumeDisabledReason") or "").strip() or "Checkpoint is not resumable"
|
|
528
|
+
)
|
|
529
|
+
return {
|
|
530
|
+
"code": "checkpoint_not_resumable",
|
|
531
|
+
"reason": reason,
|
|
532
|
+
"checkpoint_id": str(checkpoint.get("CheckpointId") or ""),
|
|
533
|
+
"run_id": str(checkpoint.get("RunId") or ""),
|
|
534
|
+
"resume_status": str(checkpoint.get("ResumeStatus") or "disabled"),
|
|
535
|
+
"is_terminal": bool(checkpoint.get("IsTerminal")),
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
async def _iter_session_event_pages(
|
|
540
|
+
service: Any,
|
|
541
|
+
session_id: str,
|
|
542
|
+
*,
|
|
543
|
+
after_seq_id: int | None = None,
|
|
544
|
+
before_seq_id: int | None = None,
|
|
545
|
+
) -> AsyncIterator[list[SessionEvent]]:
|
|
546
|
+
remaining = await service.count_events(
|
|
547
|
+
session_id,
|
|
548
|
+
after_seq_id=after_seq_id,
|
|
549
|
+
before_seq_id=before_seq_id,
|
|
550
|
+
)
|
|
551
|
+
while remaining > 0:
|
|
552
|
+
page_size = min(_EVENT_SCAN_PAGE_SIZE, remaining)
|
|
553
|
+
page = await service.get_events(
|
|
554
|
+
session_id,
|
|
555
|
+
offset=remaining - page_size,
|
|
556
|
+
limit=page_size,
|
|
557
|
+
after_seq_id=after_seq_id,
|
|
558
|
+
before_seq_id=before_seq_id,
|
|
559
|
+
)
|
|
560
|
+
if not page:
|
|
561
|
+
return
|
|
562
|
+
yield page
|
|
563
|
+
remaining -= len(page)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _event_invocation_id(event: SessionEvent) -> str:
|
|
567
|
+
return str(event.invocation_id or "").strip()
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
async def _extend_invocation_before_window(
|
|
571
|
+
service: Any,
|
|
572
|
+
session_id: str,
|
|
573
|
+
events: list[SessionEvent],
|
|
574
|
+
*,
|
|
575
|
+
after_seq_id: int | None,
|
|
576
|
+
) -> list[SessionEvent]:
|
|
577
|
+
if not events:
|
|
578
|
+
return events
|
|
579
|
+
invocation_id = _event_invocation_id(events[0])
|
|
580
|
+
if not invocation_id:
|
|
581
|
+
return events
|
|
582
|
+
cursor = int(events[0].seq_id or 0)
|
|
583
|
+
prefix: list[SessionEvent] = []
|
|
584
|
+
while cursor > 0:
|
|
585
|
+
page = await service.get_events(
|
|
586
|
+
session_id,
|
|
587
|
+
limit=_EVENT_SCAN_PAGE_SIZE,
|
|
588
|
+
after_seq_id=after_seq_id,
|
|
589
|
+
before_seq_id=cursor,
|
|
590
|
+
)
|
|
591
|
+
if not page:
|
|
592
|
+
break
|
|
593
|
+
matching_suffix: list[SessionEvent] = []
|
|
594
|
+
for event in reversed(page):
|
|
595
|
+
if _event_invocation_id(event) != invocation_id:
|
|
596
|
+
break
|
|
597
|
+
matching_suffix.append(event)
|
|
598
|
+
if not matching_suffix:
|
|
599
|
+
break
|
|
600
|
+
matching_suffix.reverse()
|
|
601
|
+
prefix[0:0] = matching_suffix
|
|
602
|
+
cursor = int(matching_suffix[0].seq_id or 0)
|
|
603
|
+
if len(matching_suffix) < len(page):
|
|
604
|
+
break
|
|
605
|
+
return [*prefix, *events]
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
async def _extend_invocation_after_window(
|
|
609
|
+
service: Any,
|
|
610
|
+
session_id: str,
|
|
611
|
+
events: list[SessionEvent],
|
|
612
|
+
*,
|
|
613
|
+
before_seq_id: int | None,
|
|
614
|
+
) -> list[SessionEvent]:
|
|
615
|
+
if not events:
|
|
616
|
+
return events
|
|
617
|
+
invocation_id = _event_invocation_id(events[-1])
|
|
618
|
+
if not invocation_id:
|
|
619
|
+
return events
|
|
620
|
+
cursor = int(events[-1].seq_id or 0)
|
|
621
|
+
suffix: list[SessionEvent] = []
|
|
622
|
+
while True:
|
|
623
|
+
remaining = await service.count_events(
|
|
624
|
+
session_id,
|
|
625
|
+
after_seq_id=cursor,
|
|
626
|
+
before_seq_id=before_seq_id,
|
|
627
|
+
)
|
|
628
|
+
if remaining <= 0:
|
|
629
|
+
break
|
|
630
|
+
page_size = min(_EVENT_SCAN_PAGE_SIZE, remaining)
|
|
631
|
+
page = await service.get_events(
|
|
632
|
+
session_id,
|
|
633
|
+
offset=remaining - page_size,
|
|
634
|
+
limit=page_size,
|
|
635
|
+
after_seq_id=cursor,
|
|
636
|
+
before_seq_id=before_seq_id,
|
|
637
|
+
)
|
|
638
|
+
if not page:
|
|
639
|
+
break
|
|
640
|
+
matching_prefix: list[SessionEvent] = []
|
|
641
|
+
for event in page:
|
|
642
|
+
if _event_invocation_id(event) != invocation_id:
|
|
643
|
+
break
|
|
644
|
+
matching_prefix.append(event)
|
|
645
|
+
if not matching_prefix:
|
|
646
|
+
break
|
|
647
|
+
suffix.extend(matching_prefix)
|
|
648
|
+
cursor = int(matching_prefix[-1].seq_id or 0)
|
|
649
|
+
if len(matching_prefix) < len(page):
|
|
650
|
+
break
|
|
651
|
+
return [*events, *suffix]
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
async def _iter_agent_event_pages(
|
|
655
|
+
service: Any,
|
|
656
|
+
agent_id: str,
|
|
657
|
+
*,
|
|
658
|
+
user_id: str | None = None,
|
|
659
|
+
) -> AsyncIterator[list[SessionEvent]]:
|
|
660
|
+
remaining = await service.count_events_for_agent(agent_id, user_id=user_id)
|
|
661
|
+
while remaining > 0:
|
|
662
|
+
page_size = min(_EVENT_SCAN_PAGE_SIZE, remaining)
|
|
663
|
+
page = await service.get_events_for_agent(
|
|
664
|
+
agent_id,
|
|
665
|
+
user_id=user_id,
|
|
666
|
+
offset=remaining - page_size,
|
|
667
|
+
limit=page_size,
|
|
668
|
+
)
|
|
669
|
+
if not page:
|
|
670
|
+
return
|
|
671
|
+
yield page
|
|
672
|
+
remaining -= len(page)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
async def _iter_scoped_event_pages(
|
|
676
|
+
service: Any,
|
|
677
|
+
*,
|
|
678
|
+
session_id: str | None,
|
|
679
|
+
agent_id: str,
|
|
680
|
+
user_id: str | None,
|
|
681
|
+
) -> AsyncIterator[list[SessionEvent]]:
|
|
682
|
+
if session_id:
|
|
683
|
+
async for page in _iter_session_event_pages(service, session_id):
|
|
684
|
+
yield page
|
|
685
|
+
return
|
|
686
|
+
async for page in _iter_agent_event_pages(service, agent_id, user_id=user_id):
|
|
687
|
+
yield page
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
async def _session_contains_invocation(
|
|
691
|
+
service: Any,
|
|
692
|
+
session_id: str,
|
|
693
|
+
invocation_id: str,
|
|
694
|
+
) -> bool:
|
|
695
|
+
async for page in _iter_session_event_pages(service, session_id):
|
|
696
|
+
if any(event.invocation_id == invocation_id for event in page):
|
|
697
|
+
return True
|
|
698
|
+
return False
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
async def _agent_contains_invocation(
|
|
702
|
+
service: Any,
|
|
703
|
+
agent_id: str,
|
|
704
|
+
invocation_id: str,
|
|
705
|
+
*,
|
|
706
|
+
user_id: str | None,
|
|
707
|
+
) -> bool:
|
|
708
|
+
async for page in _iter_agent_event_pages(service, agent_id, user_id=user_id):
|
|
709
|
+
if any(event.invocation_id == invocation_id for event in page):
|
|
710
|
+
return True
|
|
711
|
+
return False
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
async def _latest_invocation_status(
|
|
715
|
+
service: Any,
|
|
716
|
+
session_id: str,
|
|
717
|
+
invocation_id: str,
|
|
718
|
+
) -> str:
|
|
719
|
+
offset = 0
|
|
720
|
+
total = await service.count_events(session_id)
|
|
721
|
+
while offset < total:
|
|
722
|
+
page = await service.get_events(
|
|
723
|
+
session_id,
|
|
724
|
+
offset=offset,
|
|
725
|
+
limit=min(_EVENT_SCAN_PAGE_SIZE, total - offset),
|
|
726
|
+
)
|
|
727
|
+
if not page:
|
|
728
|
+
break
|
|
729
|
+
for event in reversed(page):
|
|
730
|
+
if event.invocation_id != invocation_id or event.event_type != "run_status":
|
|
731
|
+
continue
|
|
732
|
+
return str((event.content or {}).get("status") or "").strip().lower()
|
|
733
|
+
offset += len(page)
|
|
734
|
+
return ""
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
async def _oldest_unconsumed_session_events(
|
|
738
|
+
service: Any,
|
|
739
|
+
session_id: str,
|
|
740
|
+
*,
|
|
741
|
+
after_seq_id: int,
|
|
742
|
+
) -> list[SessionEvent]:
|
|
743
|
+
remaining = await service.count_events(session_id, after_seq_id=after_seq_id)
|
|
744
|
+
if remaining <= 0:
|
|
745
|
+
return []
|
|
746
|
+
page_size = min(_EVENT_SCAN_PAGE_SIZE, remaining)
|
|
747
|
+
events = await service.get_events(
|
|
748
|
+
session_id,
|
|
749
|
+
offset=remaining - page_size,
|
|
750
|
+
limit=page_size,
|
|
751
|
+
after_seq_id=after_seq_id,
|
|
752
|
+
)
|
|
753
|
+
return list(events)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _record_resume_audit(
|
|
757
|
+
audit_by_session: dict[str, dict[tuple[str, str], dict[str, Any]]],
|
|
758
|
+
event: SessionEvent,
|
|
759
|
+
) -> None:
|
|
760
|
+
if event.event_type != "run_resume":
|
|
761
|
+
return
|
|
762
|
+
metadata = event.metadata or {}
|
|
763
|
+
run_id = str(metadata.get("run_id") or "").strip()
|
|
764
|
+
checkpoint_id = str(metadata.get("checkpoint_id") or "").strip()
|
|
765
|
+
if not run_id or not checkpoint_id:
|
|
766
|
+
return
|
|
767
|
+
session_audit = audit_by_session.setdefault(event.session_id, {})
|
|
768
|
+
item = session_audit.setdefault(
|
|
769
|
+
(run_id, checkpoint_id),
|
|
770
|
+
{"resume_count": 0, "last_resumed_at": None},
|
|
771
|
+
)
|
|
772
|
+
item["resume_count"] = int(item["resume_count"]) + 1
|
|
773
|
+
item["last_resumed_at"] = event.timestamp
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _apply_latest_checkpoint_policy(
|
|
777
|
+
checkpoint: dict[str, Any],
|
|
778
|
+
latest_by_session_run: Mapping[tuple[str, str], int],
|
|
779
|
+
*,
|
|
780
|
+
session_id: str,
|
|
781
|
+
) -> dict[str, Any]:
|
|
782
|
+
metadata = checkpoint.get("Metadata") or {}
|
|
783
|
+
if not metadata.get("only_latest_resumable"):
|
|
784
|
+
return checkpoint
|
|
785
|
+
run_id = str(checkpoint.get("RunId") or "")
|
|
786
|
+
if int(checkpoint.get("SeqId") or 0) >= latest_by_session_run.get((session_id, run_id), 0):
|
|
787
|
+
return checkpoint
|
|
788
|
+
if checkpoint.get("IsResumable") is True:
|
|
789
|
+
checkpoint["IsResumable"] = False
|
|
790
|
+
checkpoint["ResumeStatus"] = "disabled"
|
|
791
|
+
checkpoint["ResumeDisabledReason"] = "新的恢复点已生成,此恢复点暂停恢复能力"
|
|
792
|
+
metadata["resume_disabled_reason"] = checkpoint["ResumeDisabledReason"]
|
|
793
|
+
metadata["resume_status"] = "disabled"
|
|
794
|
+
checkpoint["Metadata"] = metadata
|
|
795
|
+
return checkpoint
|