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.
Files changed (371) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
  2. agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
  3. ksadk/a2a/__init__.py +178 -11
  4. ksadk/a2a/bootstrap.py +334 -0
  5. ksadk/a2a/card.py +120 -0
  6. ksadk/a2a/context_store.py +203 -0
  7. ksadk/a2a/control_plane.py +818 -0
  8. ksadk/a2a/credential.py +99 -0
  9. ksadk/a2a/event_adapter.py +149 -0
  10. ksadk/a2a/executor.py +406 -89
  11. ksadk/a2a/external_transport.py +516 -0
  12. ksadk/a2a/identity.py +304 -0
  13. ksadk/a2a/ids.py +22 -0
  14. ksadk/a2a/langgraph.py +285 -0
  15. ksadk/a2a/resume_store.py +201 -0
  16. ksadk/a2a/routes.py +105 -0
  17. ksadk/a2a/server.py +107 -60
  18. ksadk/a2a/space_client.py +1101 -0
  19. ksadk/a2a/task_adapter.py +423 -0
  20. ksadk/a2a/task_event_dispatcher.py +167 -0
  21. ksadk/a2a/task_event_outbox.py +326 -0
  22. ksadk/a2a/task_store.py +206 -0
  23. ksadk/a2ui/__init__.py +27 -0
  24. ksadk/a2ui/core.py +186 -0
  25. ksadk/a2ui/models.py +166 -0
  26. ksadk/a2ui/renderer.py +198 -0
  27. ksadk/agui/__init__.py +9 -0
  28. ksadk/agui/a2ui_projection.py +92 -0
  29. ksadk/agui/agent.py +857 -0
  30. ksadk/agui/config.py +86 -0
  31. ksadk/agui/routes.py +55 -0
  32. ksadk/api/client.py +233 -191
  33. ksadk/builders/__init__.py +2 -0
  34. ksadk/builders/base.py +16 -14
  35. ksadk/builders/code_builder.py +318 -236
  36. ksadk/builders/container_builder.py +187 -167
  37. ksadk/builders/framework_requirements.py +29 -11
  38. ksadk/builders/ks3_uploader.py +58 -27
  39. ksadk/builders/managed_runtime_builder.py +145 -0
  40. ksadk/builders/mcp_builder.py +91 -92
  41. ksadk/builders/requirements_utils.py +1 -5
  42. ksadk/cli/__init__.py +89 -171
  43. ksadk/cli/cmd_a2a.py +254 -70
  44. ksadk/cli/cmd_agent.py +19 -5
  45. ksadk/cli/cmd_build.py +158 -29
  46. ksadk/cli/cmd_completion.py +47 -35
  47. ksadk/cli/cmd_config.py +267 -198
  48. ksadk/cli/cmd_create.py +500 -720
  49. ksadk/cli/cmd_dashboard.py +120 -45
  50. ksadk/cli/cmd_deploy.py +117 -58
  51. ksadk/cli/cmd_destroy.py +44 -14
  52. ksadk/cli/cmd_files.py +63 -24
  53. ksadk/cli/cmd_hermes.py +168 -89
  54. ksadk/cli/cmd_invoke.py +184 -122
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +98 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +483 -227
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +33 -10
  61. ksadk/cli/cmd_status.py +80 -31
  62. ksadk/cli/cmd_version.py +20 -13
  63. ksadk/cli/cmd_web.py +89 -13
  64. ksadk/cli/deploy_utils.py +20 -20
  65. ksadk/cli/dry_run.py +5 -3
  66. ksadk/cli/error_utils.py +37 -12
  67. ksadk/cli/global_options.py +1 -2
  68. ksadk/cli/local_runtime.py +1 -2
  69. ksadk/cli/model_catalog.py +6 -2
  70. ksadk/cli/network_options.py +34 -14
  71. ksadk/cli/resource_common.py +6 -2
  72. ksadk/cli/storage.py +0 -1
  73. ksadk/cli/ui.py +5 -3
  74. ksadk/cli/workflow_common.py +51 -17
  75. ksadk/codex/__init__.py +12 -0
  76. ksadk/codex/client.py +408 -0
  77. ksadk/codex/phase.py +143 -0
  78. ksadk/codex/runtime.py +449 -0
  79. ksadk/common/aicp_env.py +0 -1
  80. ksadk/common/auth.py +2 -2
  81. ksadk/common/constants.py +1 -3
  82. ksadk/common/llm_utils.py +17 -13
  83. ksadk/compat/__init__.py +5 -0
  84. ksadk/compat/adk_compat.py +200 -0
  85. ksadk/compat/copilotkit_a2ui.py +330 -0
  86. ksadk/configs/__init__.py +12 -12
  87. ksadk/configs/env_registry.py +500 -100
  88. ksadk/configs/global_config.py +2 -1
  89. ksadk/configs/settings.py +151 -124
  90. ksadk/conversations/__init__.py +7 -5
  91. ksadk/conversations/attachment_storage.py +16 -11
  92. ksadk/conversations/attachments.py +44 -36
  93. ksadk/conversations/compaction_pipeline.py +12 -12
  94. ksadk/conversations/compaction_prompt.py +9 -5
  95. ksadk/conversations/context.py +221 -6
  96. ksadk/conversations/message_projection.py +438 -51
  97. ksadk/conversations/model_context.py +12 -6
  98. ksadk/conversations/model_options.py +0 -1
  99. ksadk/conversations/normalize.py +20 -16
  100. ksadk/conversations/reasoning_markup.py +1 -1
  101. ksadk/conversations/runtime.py +339 -5255
  102. ksadk/conversations/runtime_compaction.py +333 -0
  103. ksadk/conversations/runtime_constants.py +27 -0
  104. ksadk/conversations/runtime_governance.py +185 -0
  105. ksadk/conversations/runtime_input.py +652 -0
  106. ksadk/conversations/runtime_invocation.py +395 -0
  107. ksadk/conversations/runtime_metadata.py +475 -0
  108. ksadk/conversations/runtime_observability.py +355 -0
  109. ksadk/conversations/runtime_payloads.py +272 -0
  110. ksadk/conversations/runtime_persistence.py +440 -0
  111. ksadk/conversations/runtime_preparation.py +418 -0
  112. ksadk/conversations/runtime_resume.py +816 -0
  113. ksadk/conversations/runtime_stream_events.py +902 -0
  114. ksadk/conversations/runtime_streaming.py +547 -0
  115. ksadk/conversations/semantic_summary.py +17 -5
  116. ksadk/conversations/session_title.py +6 -3
  117. ksadk/deployment/__init__.py +15 -16
  118. ksadk/deployment/agent_access.py +12 -23
  119. ksadk/deployment/base.py +51 -38
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +309 -202
  122. ksadk/deployment/registry.py +38 -32
  123. ksadk/deployment/state.py +11 -11
  124. ksadk/deployment/ui_config.py +1 -1
  125. ksadk/detection/__init__.py +1 -1
  126. ksadk/detection/detector.py +146 -83
  127. ksadk/detection/mcp_detector.py +9 -6
  128. ksadk/events/__init__.py +25 -0
  129. ksadk/events/parser.py +191 -0
  130. ksadk/events/replay.py +37 -0
  131. ksadk/events/runtime_event.py +264 -0
  132. ksadk/events/store.py +317 -0
  133. ksadk/harness/__init__.py +34 -0
  134. ksadk/harness/app.py +278 -0
  135. ksadk/harness/config.py +181 -0
  136. ksadk/harness/reasoner.py +105 -0
  137. ksadk/harness/runner.py +211 -0
  138. ksadk/harness/sandbox.py +141 -0
  139. ksadk/harness/tools.py +222 -0
  140. ksadk/hermes_terminal.py +23 -12
  141. ksadk/identity/resolver.py +15 -9
  142. ksadk/ids.py +32 -0
  143. ksadk/knowledge_base/adk_tool.py +1 -1
  144. ksadk/knowledge_base/client.py +23 -29
  145. ksadk/managed_runtime.py +188 -0
  146. ksadk/markdown.py +5 -2
  147. ksadk/mcp_runtime/__init__.py +9 -6
  148. ksadk/memory/__init__.py +1 -1
  149. ksadk/memory/adk/__init__.py +2 -0
  150. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  151. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  152. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  153. ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
  154. ksadk/memory/adk/long_term_memory.py +31 -35
  155. ksadk/memory/adk/resilient_session_service.py +4 -5
  156. ksadk/memory/adk/short_term_memory.py +12 -21
  157. ksadk/memory/adk_tool.py +3 -2
  158. ksadk/memory/backends/base.py +19 -19
  159. ksadk/memory/backends/memory.py +24 -25
  160. ksadk/memory/backends/redis.py +31 -30
  161. ksadk/memory/langchain_tool.py +2 -1
  162. ksadk/memory/manager.py +52 -47
  163. ksadk/memory/service.py +10 -8
  164. ksadk/model_policy.py +15 -7
  165. ksadk/model_proxy/__init__.py +51 -0
  166. ksadk/model_proxy/bootstrap.py +80 -0
  167. ksadk/model_proxy/cache.py +165 -0
  168. ksadk/model_proxy/config.py +45 -0
  169. ksadk/model_proxy/demo_webui.py +115 -0
  170. ksadk/model_proxy/detect.py +145 -0
  171. ksadk/model_proxy/gate.py +68 -0
  172. ksadk/model_proxy/namespace.py +173 -0
  173. ksadk/model_proxy/protocol_proxy.py +22 -0
  174. ksadk/model_proxy/server.py +300 -0
  175. ksadk/model_proxy/transform.py +691 -0
  176. ksadk/openclaw_gateway.py +29 -14
  177. ksadk/runners/adk_runner.py +345 -120
  178. ksadk/runners/base_runner.py +46 -29
  179. ksadk/runners/codex_runner.py +132 -0
  180. ksadk/runners/deepagents_runner.py +0 -1
  181. ksadk/runners/factory.py +17 -5
  182. ksadk/runners/langchain_runner.py +29 -742
  183. ksadk/runners/langgraph_runner.py +352 -90
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +134 -33
  186. ksadk/runners/usage_accumulator.py +1 -0
  187. ksadk/runners/utils/langfuse.py +3 -3
  188. ksadk/runners/utils/loader.py +7 -7
  189. ksadk/runtime/__init__.py +41 -0
  190. ksadk/runtime/adapter.py +345 -0
  191. ksadk/runtime/framework_adapters.py +124 -0
  192. ksadk/runtime/preprocessing.py +118 -0
  193. ksadk/runtime/runner_adapter.py +873 -0
  194. ksadk/runtime_context.py +18 -3
  195. ksadk/runtime_state.py +0 -1
  196. ksadk/sandbox/__init__.py +4 -1
  197. ksadk/sandbox/backends/__init__.py +4 -1
  198. ksadk/sandbox/backends/e2b.py +13 -5
  199. ksadk/sandbox/backends/local_process.py +11 -3
  200. ksadk/sandbox/base.py +10 -15
  201. ksadk/sandbox/factory.py +14 -4
  202. ksadk/sandbox/registry.py +57 -6
  203. ksadk/server/api_models.py +15 -5
  204. ksadk/server/app.py +455 -3885
  205. ksadk/server/composition.py +21 -0
  206. ksadk/server/factory.py +624 -0
  207. ksadk/server/routes/__init__.py +1 -0
  208. ksadk/server/routes/checkpoint_resolution.py +116 -0
  209. ksadk/server/routes/common.py +713 -0
  210. ksadk/server/routes/control.py +327 -0
  211. ksadk/server/routes/dependencies.py +98 -0
  212. ksadk/server/routes/misc.py +448 -0
  213. ksadk/server/routes/models.py +443 -0
  214. ksadk/server/routes/openai_compat.py +207 -0
  215. ksadk/server/routes/projection.py +795 -0
  216. ksadk/server/routes/routers.py +52 -0
  217. ksadk/server/routes/run.py +726 -0
  218. ksadk/server/routes/sessions.py +649 -0
  219. ksadk/server/routes/streaming.py +223 -0
  220. ksadk/server/routes/workspace.py +381 -0
  221. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
  222. ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
  224. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
  226. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-CFKwSk_B.js} +1 -1
  227. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
  228. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
  239. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
  240. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
  241. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
  242. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
  272. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
  273. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
  275. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
  276. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
  277. ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
  278. ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
  279. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
  280. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
  288. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
  289. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
  290. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
  292. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
  293. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
  297. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
  298. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
  299. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
  300. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
  301. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
  302. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
  303. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
  305. ksadk/server/static/index.html +2 -2
  306. ksadk/server/terminal_sessions.py +111 -58
  307. ksadk/sessions/__init__.py +69 -19
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +2 -3
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +143 -25
  313. ksadk/sessions/resilient.py +36 -8
  314. ksadk/skills/models.py +17 -5
  315. ksadk/skills/package_store.py +5 -2
  316. ksadk/skills/runtime/agent.py +20 -5
  317. ksadk/skills/runtime/artifacts.py +1 -5
  318. ksadk/skills/runtime/backends/e2b.py +10 -4
  319. ksadk/skills/runtime/backends/local.py +15 -3
  320. ksadk/skills/runtime/base.py +1 -2
  321. ksadk/skills/runtime/executor.py +10 -3
  322. ksadk/skills/runtime/factory.py +4 -2
  323. ksadk/skills/runtime/loader.py +1 -2
  324. ksadk/skills/runtime/registry.py +15 -10
  325. ksadk/skills/runtime/request.py +3 -1
  326. ksadk/skills/service_client.py +38 -17
  327. ksadk/skills/tool_defs.py +15 -10
  328. ksadk/terminal_exec_policy.py +3 -4
  329. ksadk/tools/gateway.py +92 -15
  330. ksadk/tools/result_budget.py +12 -4
  331. ksadk/toolsets/__init__.py +134 -36
  332. ksadk/toolsets/platform.py +21 -5
  333. ksadk/toolsets/sandbox.py +82 -16
  334. ksadk/toolsets/skills.py +200 -39
  335. ksadk/toolsets/web.py +123 -26
  336. ksadk/toolsets/workspace.py +212 -101
  337. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  338. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  339. ksadk/tracing/setup.py +69 -53
  340. ksadk/tracing/span_utils.py +5 -2
  341. ksadk/tui/loop.py +78 -30
  342. ksadk/tui/stream_render.py +1 -0
  343. ksadk/ui_config.py +5 -3
  344. ksadk/version.py +1 -1
  345. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  346. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  347. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  348. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  349. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  350. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  351. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  352. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  353. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  354. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  355. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  356. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  357. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  358. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  359. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  360. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  361. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  362. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  364. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  365. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  366. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  367. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  368. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  369. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  370. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  371. {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