agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.2__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 (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
@@ -3,15 +3,44 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import logging
6
7
  import time
7
8
  from collections.abc import Callable, Mapping
8
- from dataclasses import dataclass, field
9
+ from dataclasses import asdict, dataclass, field
9
10
  from datetime import datetime, timezone
10
11
  from typing import Any
11
12
  from uuid import uuid4
12
13
 
13
14
  from ksadk.agui.a2ui_projection import project_a2ui_operations
14
- from ksadk.events.runtime_event import EventType, RuntimeEvent
15
+ from ksadk.events.canonical import (
16
+ ContinuationCreated,
17
+ ContinuationResumed,
18
+ ErrorInfo,
19
+ InteractionRequested,
20
+ InteractionResolved,
21
+ ItemCompleted,
22
+ ItemFailed,
23
+ ItemStarted,
24
+ ItemUpdated,
25
+ RunCanceled,
26
+ RunCompleted,
27
+ RunFailed,
28
+ RunInterrupted,
29
+ RunProgress,
30
+ RunStarted,
31
+ RuntimeEvent,
32
+ SourceRef,
33
+ UsageReported,
34
+ dump_runtime_event,
35
+ )
36
+ from ksadk.events.content import (
37
+ ContentSnapshot,
38
+ DataContent,
39
+ TextContent,
40
+ ToolCallContent,
41
+ ToolResultContent,
42
+ )
43
+ from ksadk.events.store import RuntimeEventStore
15
44
  from ksadk.runtime import (
16
45
  CONVERSATION_PREPROCESSING_METADATA_KEY,
17
46
  PauseResult,
@@ -21,6 +50,8 @@ from ksadk.runtime import (
21
50
  RuntimeLaunchContext,
22
51
  StartRequest,
23
52
  )
53
+ from ksadk.sessions.base import BaseSessionService
54
+ from ksadk.sessions.local_service import LocalSessionService
24
55
  from ksadk.studio.contracts import RunEvent, RunRecord, RunStatus, Usage
25
56
  from ksadk.studio.errors import StudioError
26
57
  from ksadk.studio.event_store import RunEventStore
@@ -29,6 +60,9 @@ from ksadk.studio.workspace import Workspace
29
60
  _CANCEL_TIMEOUT_SECONDS = 2.0
30
61
 
31
62
 
63
+ logger = logging.getLogger(__name__)
64
+
65
+
32
66
  @dataclass(frozen=True)
33
67
  class StudioRunSpec:
34
68
  """Product metadata plus the core launch context for one Studio run."""
@@ -50,15 +84,49 @@ class StudioRunService:
50
84
  executor: RuntimeExecutor,
51
85
  *,
52
86
  event_store: RunEventStore | None = None,
87
+ session_service: BaseSessionService | None = None,
88
+ runtime_events: RuntimeEventStore | None = None,
53
89
  ) -> None:
54
90
  self.workspace = workspace
55
91
  self.executor = executor
56
92
  self.event_store = event_store or RunEventStore(workspace)
93
+ self.session_service = session_service or LocalSessionService(
94
+ project_dir=str(workspace.root)
95
+ )
96
+ self.runtime_events = runtime_events or RuntimeEventStore(self.session_service)
57
97
  self._active_handles: dict[str, Any] = {}
58
98
  self._cancel_flags: dict[str, bool] = {}
59
99
  self._control_queues: dict[str, asyncio.Queue[tuple[str, ResumePayload | None]]] = {}
60
100
  self._waiting_modes: dict[str, str] = {}
61
101
 
102
+ async def recover_interrupted(self) -> None:
103
+ """Settle local runs left active across a Studio restart.
104
+
105
+ An in-process handle cannot be resumed safely. Keep its durable event
106
+ history and record an explicit interruption; lease-backed hosted
107
+ recovery remains owned by the Kernel composition root.
108
+ """
109
+ active = {RunStatus.RUNNING, RunStatus.WAITING_INPUT}
110
+ for record in self.event_store.list_runs():
111
+ if record.status not in active:
112
+ continue
113
+ record.status = RunStatus.INTERRUPTED
114
+ record.completed_at = datetime.now(timezone.utc)
115
+ record.error = {
116
+ "code": "LOCAL_STUDIO_RESTARTED",
117
+ "message": "本地 Studio 重启,运行未在本地恢复。",
118
+ }
119
+ self.event_store.save(record)
120
+ self.event_store.append(
121
+ record.id,
122
+ "run.interrupted",
123
+ {"reason": "local_studio_restarted", "recoverable": False},
124
+ )
125
+
126
+ async def events(self, run_id: str, *, after: int = 0) -> list[RunEvent]:
127
+ """Read the durable Studio event timeline for API/SSE replay."""
128
+ return self.event_store.events(run_id, after=after)
129
+
62
130
  async def run(
63
131
  self,
64
132
  spec: StudioRunSpec,
@@ -84,7 +152,9 @@ class StudioRunService:
84
152
  goal_objective=str(spec.request_config.get("goal_objective") or ""),
85
153
  input=user_input,
86
154
  )
155
+ await self.session_service.create_session(spec.agent_id, "local-user", session)
87
156
  self.event_store.create(record)
157
+ prepared_turn = await self._capture_pcm_evidence(record, spec, user_input)
88
158
  created = self.event_store.append(
89
159
  record.id,
90
160
  "run.created",
@@ -103,13 +173,21 @@ class StudioRunService:
103
173
  if on_event is not None:
104
174
  on_event(created)
105
175
 
176
+ from ksadk.kernel.ingress import kernel_route_active
177
+
178
+ if kernel_route_active():
179
+ return await self._kernel_run(
180
+ spec, user_input, record=record, on_event=on_event
181
+ )
182
+
106
183
  started = time.monotonic()
107
184
  record.status = RunStatus.RUNNING
108
185
  record.started_at = datetime.now(timezone.utc)
109
186
  self.event_store.save(record)
110
187
 
111
- def persist(runtime_event: RuntimeEvent) -> RunEvent:
112
- event_type, data = project_runtime_event(runtime_event)
188
+ async def persist(runtime_event: RuntimeEvent) -> RunEvent:
189
+ persisted = await self.runtime_events.append_one(record.session_id, runtime_event)
190
+ event_type, data = project_runtime_event(persisted)
113
191
  stored = self.event_store.append(record.id, event_type, data)
114
192
  if on_event is not None:
115
193
  on_event(stored)
@@ -119,6 +197,7 @@ class StudioRunService:
119
197
  final_text = ""
120
198
  streamed_final = ""
121
199
  runtime_duration_ms: int | None = None
200
+ item_phases: dict[tuple[str, str], str | None] = {}
122
201
  control_queue: asyncio.Queue[tuple[str, ResumePayload | None]] = asyncio.Queue()
123
202
  self._control_queues[run_id] = control_queue
124
203
  try:
@@ -134,6 +213,8 @@ class StudioRunService:
134
213
  conversation_request["request_metadata"] = {
135
214
  "tool_approval_mode": tool_approval_mode,
136
215
  }
216
+ if prepared_turn is not None:
217
+ conversation_request["prepared_turn"] = asdict(prepared_turn)
137
218
  request = StartRequest(
138
219
  input=runtime_input if runtime_input is not None else user_input,
139
220
  user_id="local-user",
@@ -155,63 +236,72 @@ class StudioRunService:
155
236
  terminal_seen = False
156
237
  should_resume = False
157
238
  async for event in self.executor.stream(handle):
158
- persist(event)
239
+ await persist(event)
159
240
  if self._cancel_flags.get(run_id):
160
241
  raise asyncio.CancelledError()
161
- if event.event_type == EventType.TEXT_DELTA and event.phase == "final_answer":
162
- text = str(event.payload.get("text") or "")
163
- if event.payload.get("replace"):
242
+ if isinstance(event, ItemStarted) and event.item_kind == "message":
243
+ item_phases[(event.scope_id, event.item_id)] = event.phase
244
+ elif (
245
+ isinstance(event, ItemUpdated)
246
+ and event.item_kind == "message"
247
+ and item_phases.get(
248
+ (event.scope_id, event.item_id), "final_answer"
249
+ )
250
+ == "final_answer"
251
+ ):
252
+ text = event.update.text if isinstance(event.update, TextContent) else ""
253
+ if event.op == "replace":
164
254
  streamed_final = text
165
255
  else:
166
256
  streamed_final += text
167
257
  elif (
168
- event.event_type == EventType.TEXT_COMPLETED
169
- and event.phase == "final_answer"
258
+ isinstance(event, ItemCompleted)
259
+ and event.item_kind == "message"
260
+ and item_phases.get(
261
+ (event.scope_id, event.item_id), "final_answer"
262
+ )
263
+ == "final_answer"
170
264
  ):
171
- final_text = str(event.payload.get("text") or "")
172
- elif event.event_type == EventType.USAGE_REPORTED:
265
+ for part in event.snapshot.parts:
266
+ if isinstance(part, TextContent):
267
+ final_text = part.text
268
+ break
269
+ elif isinstance(event, UsageReported):
173
270
  record.usage = Usage(
174
- input_tokens=int(event.payload.get("input_tokens") or 0),
175
- output_tokens=int(event.payload.get("output_tokens") or 0),
176
- total_tokens=int(event.payload.get("total_tokens") or 0),
177
- cached_input_tokens=int(event.payload.get("cached_tokens") or 0),
178
- reasoning_output_tokens=int(event.payload.get("reasoning_tokens") or 0),
271
+ input_tokens=event.input_tokens,
272
+ output_tokens=event.output_tokens,
273
+ total_tokens=event.total_tokens,
274
+ cached_input_tokens=event.cached_tokens,
275
+ reasoning_output_tokens=event.reasoning_tokens,
179
276
  reported=True,
180
- source=str(event.payload.get("source") or runtime_type),
277
+ source=str(event.source.framework or runtime_type),
181
278
  )
182
- elif event.event_type in {
183
- EventType.APPROVAL_REQUESTED,
184
- EventType.A2UI_INTERACTION,
185
- }:
279
+ elif isinstance(event, InteractionRequested):
186
280
  record.status = RunStatus.WAITING_INPUT
187
281
  self._waiting_modes[run_id] = "live"
188
282
  self.event_store.save(record)
189
- if event.event_type == EventType.APPROVAL_REQUESTED:
283
+ if event.interaction_kind == "approval":
190
284
  self._persist_approval_surface(record, event, on_event=on_event)
191
- elif event.event_type in {
192
- EventType.APPROVAL_RESOLVED,
193
- EventType.A2UI_ACTION,
194
- }:
285
+ elif isinstance(event, InteractionResolved):
195
286
  record.status = RunStatus.RUNNING
196
287
  self._waiting_modes.pop(run_id, None)
197
288
  self.event_store.save(record)
198
- elif event.event_type == EventType.RUN_FAILED:
289
+ elif isinstance(event, RunFailed):
199
290
  terminal_seen = True
200
291
  record.status = RunStatus.FAILED
201
292
  record.error = {
202
293
  "code": "RUNTIME_RUN_FAILED",
203
- "message": str(event.payload.get("error") or "Runtime 运行失败"),
294
+ "message": str(event.error.message or "Runtime 运行失败"),
204
295
  }
205
- elif event.event_type == EventType.RUN_CANCELED:
296
+ elif isinstance(event, RunCanceled):
206
297
  terminal_seen = True
207
298
  record.status = RunStatus.CANCELLED
208
299
  record.error = {
209
300
  "code": "RUN_CANCELLED",
210
- "message": str(event.payload.get("status") or "运行已取消"),
301
+ "message": str(event.reason or "运行已取消"),
211
302
  }
212
- elif event.event_type == EventType.RUN_INTERRUPTED:
213
- interrupted_status = str(event.payload.get("status") or "")
214
- if interrupted_status == "paused":
303
+ elif isinstance(event, RunInterrupted):
304
+ if event.reason == "user_pause":
215
305
  record.status = RunStatus.PAUSED
216
306
  should_resume = True
217
307
  elif record.status == RunStatus.WAITING_INPUT:
@@ -222,13 +312,17 @@ class StudioRunService:
222
312
  record.status = RunStatus.INTERRUPTED
223
313
  record.error = {
224
314
  "code": "RUN_INTERRUPTED",
225
- "message": interrupted_status or "运行已中断",
315
+ "message": event.reason or "运行已中断",
226
316
  }
227
317
  self.event_store.save(record)
228
- elif event.event_type == EventType.RUN_COMPLETED:
318
+ elif isinstance(event, RunCompleted):
229
319
  terminal_seen = True
230
320
  record.status = RunStatus.COMPLETED
231
- raw_duration = event.payload.get("duration_ms")
321
+ raw_duration = event.source.metadata.get("duration_ms")
322
+ if raw_duration is None:
323
+ metrics = event.source.metadata.get("metrics")
324
+ if isinstance(metrics, dict):
325
+ raw_duration = metrics.get("duration_ms")
232
326
  if raw_duration is not None:
233
327
  runtime_duration_ms = max(0, int(raw_duration))
234
328
  if terminal_seen:
@@ -282,30 +376,44 @@ class StudioRunService:
282
376
  cancel_result = "cancel_failed"
283
377
  record.status = RunStatus.CANCELLED
284
378
  record.error = {"code": "RUN_CANCELLED", "message": "运行已取消"}
285
- cancelled = RuntimeEvent.create(
286
- EventType.RUN_CANCELED,
287
- agent_id=spec.agent_id,
288
- user_id="local-user",
289
- session_id=session,
290
- invocation_id=handle.run_id if handle is not None else run_id,
291
- seq_id=len(self.event_store.events(run_id)) + 1,
292
- payload={"status": "cancelled", "cancel_result": cancel_result},
379
+ cancelled_run_id = handle.run_id if handle is not None else run_id
380
+ cancelled = RunCanceled(
381
+ schema_version=2,
382
+ event_id=f"evt_cancel_{uuid4().hex}",
383
+ seq=len(self.event_store.events(run_id)) + 1,
384
+ timestamp=time.time(),
385
+ run_id=cancelled_run_id,
386
+ scope_id=cancelled_run_id,
387
+ source=SourceRef(
388
+ framework="ksadk",
389
+ metadata={"cancel_result": cancel_result},
390
+ ),
391
+ status="canceled",
392
+ reason="cancelled",
293
393
  )
294
- persist(cancelled)
394
+ await persist(cancelled)
295
395
  raise
296
396
  except Exception as exc: # noqa: BLE001
297
397
  record.status = RunStatus.FAILED
298
398
  record.error = {"code": "RUNTIME_RUN_FAILED", "message": str(exc)}
299
- failure = RuntimeEvent.create(
300
- EventType.RUN_FAILED,
301
- agent_id=spec.agent_id,
302
- user_id="local-user",
303
- session_id=session,
304
- invocation_id=handle.run_id if handle is not None else run_id,
305
- seq_id=len(self.event_store.events(run_id)) + 1,
306
- payload={"status": "failed", "error": str(exc)},
399
+ failed_run_id = handle.run_id if handle is not None else run_id
400
+ failure = RunFailed(
401
+ schema_version=2,
402
+ event_id=f"evt_fail_{uuid4().hex}",
403
+ seq=len(self.event_store.events(run_id)) + 1,
404
+ timestamp=time.time(),
405
+ run_id=failed_run_id,
406
+ scope_id=failed_run_id,
407
+ source=SourceRef(framework="ksadk"),
408
+ status="failed",
409
+ error=ErrorInfo(
410
+ code="RUNTIME_RUN_FAILED",
411
+ message=str(exc),
412
+ source="ksadk",
413
+ scope_id=failed_run_id,
414
+ ),
307
415
  )
308
- persist(failure)
416
+ await persist(failure)
309
417
  finally:
310
418
  if handle is not None and self.executor.is_attached(handle):
311
419
  try:
@@ -326,8 +434,98 @@ class StudioRunService:
326
434
  record.duration_ms = int((time.monotonic() - started) * 1000)
327
435
  record.duration_source = "studio"
328
436
  self.event_store.save(record)
437
+ await self._sync_trace(record)
329
438
  return record
330
439
 
440
+ async def _sync_trace(self, record: RunRecord) -> None:
441
+ self.event_store.trace_store.sync(record, await self.events(record.id))
442
+
443
+
444
+ async def _kernel_run(
445
+ self,
446
+ spec: StudioRunSpec,
447
+ user_input: str,
448
+ *,
449
+ record: RunRecord,
450
+ on_event: Callable[[RunEvent], None] | None,
451
+ ) -> RunRecord:
452
+ """kernel 路径(灰度 opt-in):Studio run -> AgentControlCommand -> receipt。
453
+
454
+ mutation 只走 kernel.submit;Studio RunEvent shape 保留,cursor 源自
455
+ 同一 Session seq(SessionEventSubscription.after_seq)。
456
+ """
457
+ from ksadk.kernel import ingress as _kernel_ingress
458
+
459
+ started = time.monotonic()
460
+ record.status = RunStatus.RUNNING
461
+ record.started_at = datetime.now(timezone.utc)
462
+ self.event_store.save(record)
463
+ try:
464
+ trusted = _kernel_ingress.trusted_context(
465
+ source_kind="studio",
466
+ source_ref=record.id,
467
+ session_id=record.session_id,
468
+ operations=("enqueue",),
469
+ launch_context=spec.launch_context,
470
+ )
471
+ idempotency_key = str(
472
+ (spec.request_config or {}).get("idempotency_key") or record.id
473
+ )
474
+ command = _kernel_ingress.map_studio_request(
475
+ session_id=record.session_id,
476
+ idempotency_key=idempotency_key,
477
+ content=user_input,
478
+ run_id=record.id,
479
+ trusted=trusted,
480
+ )
481
+ receipt = await _kernel_ingress.submit_command(
482
+ command, permit=trusted.permit
483
+ )
484
+ if receipt.status not in ("accepted", "duplicate"):
485
+ record.status = RunStatus.FAILED
486
+ record.error = {
487
+ "code": "kernel_command_rejected",
488
+ "message": f"agent kernel rejected command: {receipt.status}",
489
+ }
490
+ self.event_store.save(record)
491
+ return record
492
+ output_text = ""
493
+ async for _seq, projected in _kernel_ingress.subscribe_projected(
494
+ record.session_id,
495
+ trusted=trusted,
496
+ after_seq=int(receipt.accepted_seq or 0),
497
+ projector=_studio_envelope_projection,
498
+ ):
499
+ if projected is None:
500
+ continue
501
+ event_type, data = projected
502
+ if event_type == "message.delta":
503
+ output_text += str(data.get("delta") or "")
504
+ elif event_type == "run.completed":
505
+ output_text = str(data.get("output_text") or output_text)
506
+ stored = self.event_store.append(record.id, event_type, data)
507
+ if on_event is not None:
508
+ on_event(stored)
509
+ record.output = output_text
510
+ record.status = RunStatus.COMPLETED
511
+ record.completed_at = datetime.now(timezone.utc)
512
+ record.duration_ms = int((time.monotonic() - started) * 1000)
513
+ self.event_store.save(record)
514
+ await self._sync_trace(record)
515
+ return record
516
+ except Exception as exc: # noqa: BLE001
517
+ logger.exception("Studio kernel ingress failed for run %s", record.id)
518
+ record.status = RunStatus.FAILED
519
+ record.error = {
520
+ "code": "kernel_ingress_failed",
521
+ "message": str(exc),
522
+ }
523
+ record.completed_at = datetime.now(timezone.utc)
524
+ record.duration_ms = int((time.monotonic() - started) * 1000)
525
+ self.event_store.save(record)
526
+ await self._sync_trace(record)
527
+ return record
528
+
331
529
  async def cancel_run(self, run_id: str) -> dict[str, str]:
332
530
  """Request cancellation; the flag and executor perform the actual stop."""
333
531
  self._cancel_flags[run_id] = True
@@ -488,21 +686,103 @@ class StudioRunService:
488
686
  "resolutionEventId": resolved.id,
489
687
  }
490
688
 
689
+ async def _capture_pcm_evidence(
690
+ self,
691
+ record: RunRecord,
692
+ spec: StudioRunSpec,
693
+ user_input: str,
694
+ ) -> Any | None:
695
+ """Prepare once and persist the exact prompt/context input sent to Runtime."""
696
+
697
+ try:
698
+ from ksadk.conversations.runtime_preparation import build_run_input
699
+ from ksadk.sessions.in_memory import InMemorySessionService
700
+
701
+ temporary_sessions = InMemorySessionService()
702
+ await temporary_sessions.create_session(
703
+ agent_id=spec.agent_id,
704
+ user_id="local-user",
705
+ session_id=record.session_id,
706
+ )
707
+ config = dict(spec.request_config or {})
708
+ prepared = await build_run_input(
709
+ agent_id=spec.agent_id,
710
+ user_id="local-user",
711
+ session_id=record.session_id,
712
+ messages=self._conversation_messages(
713
+ spec.agent_id,
714
+ record.session_id,
715
+ user_input,
716
+ ),
717
+ model=spec.model,
718
+ instructions=str(config.get("instructions") or ""),
719
+ agent_system=str(config.get("agent_system") or ""),
720
+ agent_task=str(config.get("agent_task") or ""),
721
+ prompt_integration_mode=str(config.get("prompt_integration_mode") or ""),
722
+ context_engine_rollout=str(config.get("context_engine_rollout") or "") or None,
723
+ memory_recall_enabled=config.get("memory_recall_enabled"),
724
+ memory_write_rollout=str(config.get("memory_write_rollout") or "") or None,
725
+ memory_enabled=config.get("memory_enabled"),
726
+ memory_write_mode=str(config.get("memory_write_mode") or "candidate"),
727
+ flush_before_compaction=bool(config.get("flush_before_compaction", True)),
728
+ provider_ref=str(config.get("provider_ref") or "local-default"),
729
+ runtime_type=spec.launch_context.runtime_type,
730
+ deployment_mode=spec.launch_context.deployment_mode,
731
+ invocation_id=record.id,
732
+ session_service_provider=lambda: temporary_sessions,
733
+ agent_max_input_tokens=config.get("max_input_tokens"),
734
+ agent_reserve_output_tokens=config.get("reserve_output_tokens"),
735
+ )
736
+ record.context_plan = prepared.context_plan
737
+ compiled = prepared.compiled_prompt
738
+ shadow = prepared.shadow_context_plan or {}
739
+ record.prompt_evidence = {
740
+ "contentHash": (compiled or {}).get("prompt_content_hash"),
741
+ "stablePrefixHash": (compiled or {}).get("prompt_stable_prefix_hash"),
742
+ "sectionHashes": (compiled or {}).get("prompt_section_hashes", {}),
743
+ "tokensBySection": (compiled or {}).get("prompt_tokens_by_section", {}),
744
+ "estimatedTokens": (compiled or {}).get("prompt_estimated_tokens"),
745
+ "sectionCount": (compiled or {}).get("prompt_section_count"),
746
+ "integrationMode": shadow.get("integration_mode")
747
+ or config.get("prompt_integration_mode")
748
+ or "native",
749
+ "accountingAccuracy": shadow.get("accounting_accuracy") or "opaque",
750
+ "promptOwner": shadow.get("prompt_owner") or "runtime",
751
+ "runtimeType": shadow.get("runtime_type") or spec.launch_context.runtime_type,
752
+ "deploymentMode": shadow.get("deployment_mode")
753
+ or spec.launch_context.deployment_mode,
754
+ "capabilityHash": shadow.get("capability_hash"),
755
+ "tokensByKind": shadow.get("tokens_by_kind", {}),
756
+ "plannedInputTokens": shadow.get("planned_input_tokens"),
757
+ }
758
+ record.working_state = prepared.working_state
759
+ for event in getattr(prepared, "memory_recall_events", []):
760
+ self.event_store.append(
761
+ record.id,
762
+ event.get("type", "memory.recall.event"),
763
+ event,
764
+ )
765
+ self.event_store.save(record)
766
+ return prepared
767
+ except Exception: # noqa: BLE001 - evidence collection is best effort
768
+ logger.exception("Studio PCM evidence capture failed for run %s", record.id)
769
+ return None
770
+
491
771
  def _persist_approval_surface(
492
772
  self,
493
773
  record: RunRecord,
494
- event: RuntimeEvent,
774
+ event: InteractionRequested,
495
775
  *,
496
776
  on_event: Callable[[RunEvent], None] | None,
497
777
  ) -> None:
498
- approval_id = str(event.payload.get("approval_id") or event.payload.get("call_id") or "")
778
+ approval_id = event.interaction_id
499
779
  if not approval_id:
500
780
  return
501
781
  surface_id = f"approval-{approval_id}"
502
- detail = event.payload.get("detail")
503
- detail = detail if isinstance(detail, dict) else {}
782
+ detail_value = event.request.detail
783
+ detail = detail_value if isinstance(detail_value, dict) else {}
504
784
  command = str(detail.get("command") or detail.get("reason") or "")
505
- kind = str(event.payload.get("kind") or "tool")
785
+ kind = str(event.request.kind or "tool")
506
786
  components = [
507
787
  {
508
788
  "id": "root",
@@ -602,106 +882,252 @@ class StudioRunService:
602
882
 
603
883
 
604
884
  def project_runtime_event(event: RuntimeEvent) -> tuple[str, dict[str, Any]]:
605
- """Project the canonical RuntimeEvent into Studio's persisted event view."""
885
+ """Project the canonical RuntimeEvent into Studio's persisted event view.
606
886
 
607
- payload = dict(event.payload)
608
- event_type = event.event_type
609
- if event_type == EventType.RUN_STARTED:
887
+ 公开承诺字段(契约声明见 ``ksadk/events/projections.py``,执行形态为
888
+ ``tests/protocol/test_cross_projection_golden.py``):
889
+ - 所有事件 payload 必含 ``runId``/``scopeId``;
890
+ - ``message.*``/``thinking.*`` 含 ``itemId``(delta 另含 ``partId``);
891
+ - ``tool.*``/``command.*``/``approval.*``/``a2ui.*`` 含 ``itemId``;
892
+ - ``a2ui.surface.*`` 含 ``surfaceId`` 与 ``a2uiOperations`` 列表。
893
+
894
+ 内部不保证字段:除上述外的 payload 附加键、事件类型枚举的完备性
895
+ (新增 canonical 事件类型在未适配前可能整条丢弃)。
896
+ """
897
+
898
+ if isinstance(event, RunStarted):
610
899
  projected = "run.started"
611
- elif event_type == EventType.RUN_PROGRESS:
612
- projected = str(payload.get("native_event") or "run.progress")
613
- native_data = payload.get("native_data")
614
- if isinstance(native_data, dict):
615
- payload = dict(native_data)
616
- elif event_type in {EventType.REASONING_DELTA, EventType.REASONING_COMPLETED}:
617
- projected = (
618
- "thinking.delta" if event_type == EventType.REASONING_DELTA else "thinking.completed"
619
- )
620
- elif event_type == EventType.TEXT_DELTA:
621
- projected = "message.delta" if event.phase == "final_answer" else "thinking.delta"
622
- elif event_type == EventType.TEXT_COMPLETED:
623
- projected = "message.completed" if event.phase == "final_answer" else "thinking.completed"
624
- elif event_type == EventType.TOOL_CALL_BEGIN:
625
- projected = "command.started" if payload.get("name") == "codex.command" else "tool.started"
626
- if projected == "command.started":
627
- raw_args = payload.get("args")
628
- args: dict[str, Any] = raw_args if isinstance(raw_args, dict) else {}
900
+ payload: dict[str, Any] = {}
901
+ elif isinstance(event, RunProgress):
902
+ projected = "run.progress"
903
+ payload = {"progress": event.progress, "message": event.message}
904
+ elif isinstance(event, RunCompleted):
905
+ projected = "run.completed"
906
+ payload = {}
907
+ elif isinstance(event, RunFailed):
908
+ projected = "run.failed"
909
+ payload = {"error": event.error.message or ""}
910
+ elif isinstance(event, RunCanceled):
911
+ projected = "run.cancelled"
912
+ payload = {"reason": event.reason or ""}
913
+ elif isinstance(event, RunInterrupted):
914
+ projected = "run.paused" if event.reason == "user_pause" else "run.interrupted"
915
+ payload = {"reason": event.reason or ""}
916
+ elif isinstance(event, ItemUpdated) and event.item_kind in {"message", "reasoning"}:
917
+ text = event.update.text if isinstance(event.update, TextContent) else ""
918
+ is_thinking = event.item_kind == "reasoning"
919
+ projected = "thinking.delta" if is_thinking else "message.delta"
920
+ payload = {"text": text}
921
+ elif isinstance(event, ItemCompleted) and event.item_kind in {"message", "reasoning"}:
922
+ text = ""
923
+ for part in event.snapshot.parts:
924
+ if isinstance(part, TextContent):
925
+ text = part.text
926
+ break
927
+ is_thinking = event.item_kind == "reasoning"
928
+ projected = "thinking.completed" if is_thinking else "message.completed"
929
+ payload = {"text": text}
930
+ elif isinstance(event, ItemStarted) and event.item_kind == "tool_call":
931
+ tool_part = _first_content(event.initial, ToolCallContent) if event.initial else None
932
+ if tool_part is not None and tool_part.name == "codex.command":
933
+ projected = "command.started"
934
+ args = tool_part.arguments if isinstance(tool_part.arguments, dict) else {}
629
935
  payload = {
630
- "callId": str(payload.get("call_id") or ""),
936
+ "callId": tool_part.call_id,
631
937
  "command": str(args.get("command") or ""),
632
938
  "cwd": str(args.get("cwd") or ""),
633
939
  "commandActions": args.get("command_actions") or [],
634
940
  }
635
- else:
941
+ elif tool_part is not None:
942
+ projected = "tool.started"
636
943
  payload = {
637
- "callId": str(payload.get("call_id") or ""),
638
- "tool": str(payload.get("name") or ""),
639
- "args": payload.get("args"),
944
+ "callId": tool_part.call_id,
945
+ "tool": tool_part.name,
946
+ "args": tool_part.arguments,
640
947
  }
641
- elif event_type == EventType.TOOL_CALL_END:
642
- projected = (
643
- "command.completed" if payload.get("name") == "codex.command" else "tool.completed"
644
- )
645
- if projected == "command.completed":
646
- raw_result = payload.get("result")
647
- result: dict[str, Any] = raw_result if isinstance(raw_result, dict) else {}
948
+ else:
949
+ projected = "tool.started"
950
+ payload = {}
951
+ elif isinstance(event, ItemCompleted) and event.item_kind == "tool_call":
952
+ tool_call = _first_content(event.snapshot, ToolCallContent)
953
+ tool_result = _first_content(event.snapshot, ToolResultContent)
954
+ if tool_call is not None and tool_call.name == "codex.command":
955
+ projected = "command.completed"
956
+ result = (
957
+ tool_result.result
958
+ if tool_result is not None and isinstance(tool_result.result, dict)
959
+ else {}
960
+ )
648
961
  payload = {
649
- "callId": str(payload.get("call_id") or ""),
962
+ "callId": tool_call.call_id,
650
963
  "status": str(result.get("status") or ""),
651
964
  "exitCode": result.get("exit_code"),
652
965
  "durationMs": result.get("duration_ms"),
653
966
  "output": str(result.get("output") or ""),
654
967
  }
655
968
  else:
656
- raw_tool_result = payload.get("result")
657
- tool_result: dict[str, Any] = (
658
- raw_tool_result if isinstance(raw_tool_result, dict) else {}
969
+ projected = "tool.completed"
970
+ result = (
971
+ tool_result.result
972
+ if tool_result is not None and isinstance(tool_result.result, dict)
973
+ else {}
659
974
  )
660
- tool_status = str(tool_result.get("status") or "")
661
- if tool_result.get("error"):
975
+ tool_status = str(result.get("status") or "")
976
+ if tool_result is not None and tool_result.is_error:
662
977
  tool_status = "failed"
978
+ call_id = (tool_call or tool_result).call_id if (tool_call or tool_result) else ""
663
979
  payload = {
664
- "callId": str(payload.get("call_id") or ""),
665
- "tool": str(payload.get("name") or ""),
980
+ "callId": call_id,
981
+ "tool": tool_call.name if tool_call is not None else "",
666
982
  "status": tool_status,
667
- "durationMs": tool_result.get("duration_ms"),
668
- "output": str(tool_result.get("output") or ""),
669
- **({"error": str(tool_result["error"])} if tool_result.get("error") else {}),
983
+ "durationMs": result.get("duration_ms"),
984
+ "output": str(result.get("output") or ""),
670
985
  }
671
- elif event_type == EventType.RUN_COMPLETED:
672
- projected = "run.completed"
673
- elif event_type == EventType.RUN_FAILED:
674
- projected = "run.failed"
675
- elif event_type == EventType.RUN_CANCELED:
676
- projected = "run.cancelled"
677
- elif event_type == EventType.RUN_INTERRUPTED:
678
- projected = (
679
- "run.paused" if str(payload.get("status") or "") == "paused" else "run.interrupted"
680
- )
681
- elif event_type in {
682
- EventType.A2UI_SURFACE_BEGIN,
683
- EventType.A2UI_SURFACE_UPDATE,
684
- EventType.A2UI_SURFACE_END,
685
- EventType.A2UI_INTERACTION,
686
- EventType.A2UI_ACTION,
687
- }:
688
- projected = str(event_type)
689
- surface_id = str(payload.get("surface_id") or payload.get("surfaceId") or "")
690
- if surface_id:
691
- payload["surfaceId"] = surface_id
692
- interaction_id = str(payload.get("interaction_id") or payload.get("interactionId") or "")
693
- if interaction_id:
694
- payload["interactionId"] = interaction_id
695
- schema = payload.get("input_schema", payload.get("inputSchema"))
696
- if isinstance(schema, dict):
697
- payload["inputSchema"] = schema
698
- operations = project_a2ui_operations(event_type, payload)
699
- if operations:
700
- payload["a2uiOperations"] = operations
986
+ if tool_result is not None and tool_result.is_error:
987
+ payload["error"] = str(result.get("error") or "")
988
+ elif (
989
+ isinstance(event, (ItemStarted, ItemUpdated, ItemCompleted))
990
+ and event.item_kind == "data"
991
+ and event.source.protocol == "a2ui"
992
+ ):
993
+ projected, payload = _project_a2ui_surface(event)
994
+ elif isinstance(event, InteractionRequested):
995
+ if event.interaction_kind == "approval":
996
+ projected = "approval.requested"
997
+ payload = {
998
+ "approvalId": event.interaction_id,
999
+ "callId": event.request.call_id or "",
1000
+ "kind": event.request.kind,
1001
+ "detail": event.request.detail,
1002
+ }
1003
+ else:
1004
+ projected = "a2ui.interaction"
1005
+ payload = {
1006
+ "interactionId": event.interaction_id,
1007
+ "kind": "form",
1008
+ "inputSchema": {},
1009
+ }
1010
+ elif isinstance(event, InteractionResolved):
1011
+ if event.interaction_kind == "approval":
1012
+ projected = "approval.resolved"
1013
+ payload = {
1014
+ "approvalId": event.interaction_id,
1015
+ "callId": "",
1016
+ "decision": "",
1017
+ }
1018
+ else:
1019
+ projected = "a2ui.action"
1020
+ payload = {"interactionId": event.interaction_id}
1021
+ elif isinstance(event, ContinuationCreated):
1022
+ projected = "checkpoint.created"
1023
+ payload = {
1024
+ "checkpointId": event.continuation_id,
1025
+ "granularity": event.ref.get("granularity", "snapshot"),
1026
+ "resumable": event.resumable,
1027
+ }
1028
+ elif isinstance(event, ContinuationResumed):
1029
+ projected = "checkpoint.resumed"
1030
+ payload = {
1031
+ "checkpointId": event.continuation_id,
1032
+ "resumeAttemptId": event.resume_attempt_id,
1033
+ }
1034
+ elif isinstance(event, UsageReported):
1035
+ projected = "usage.reported"
1036
+ payload = {
1037
+ "inputTokens": event.input_tokens,
1038
+ "outputTokens": event.output_tokens,
1039
+ "totalTokens": event.total_tokens,
1040
+ "cachedTokens": event.cached_tokens,
1041
+ "reasoningTokens": event.reasoning_tokens,
1042
+ }
701
1043
  else:
702
- projected = str(event_type)
703
- payload["runtimeEvent"] = event.to_dict()
1044
+ projected = event.event_type
1045
+ payload = {}
1046
+
1047
+ _attach_studio_identity(payload, event)
1048
+ payload["runtimeEvent"] = dump_runtime_event(event)
704
1049
  return projected, payload
705
1050
 
706
1051
 
1052
+ def _first_content(
1053
+ snapshot: ContentSnapshot | None, content_type: type
1054
+ ) -> Any | None:
1055
+ if snapshot is None:
1056
+ return None
1057
+ for part in snapshot.parts:
1058
+ if isinstance(part, content_type):
1059
+ return part
1060
+ return None
1061
+
1062
+
1063
+ def _project_a2ui_surface(
1064
+ event: ItemStarted | ItemUpdated | ItemCompleted,
1065
+ ) -> tuple[str, dict[str, Any]]:
1066
+ if isinstance(event, ItemStarted):
1067
+ projected = "a2ui.surface.begin"
1068
+ data_parts = event.initial.parts if event.initial is not None else ()
1069
+ elif isinstance(event, ItemUpdated):
1070
+ projected = "a2ui.surface.update"
1071
+ data_parts = (event.update,) if isinstance(event.update, DataContent) else ()
1072
+ else:
1073
+ projected = "a2ui.surface.end"
1074
+ data_parts = event.snapshot.parts
1075
+
1076
+ surface_id = str(event.source.metadata.get("surface_id") or "")
1077
+ operations: list[dict[str, Any]] = []
1078
+ for part in data_parts:
1079
+ if isinstance(part, DataContent):
1080
+ data = part.data
1081
+ if isinstance(data, list):
1082
+ operations.extend(dict(op) for op in data if isinstance(op, Mapping))
1083
+ if not operations:
1084
+ operations = project_a2ui_operations(projected, {"surface_id": surface_id})
1085
+ payload: dict[str, Any] = {
1086
+ "surfaceId": surface_id,
1087
+ "a2uiOperations": operations,
1088
+ }
1089
+ return projected, payload
1090
+
1091
+
1092
+ def _attach_studio_identity(payload: dict[str, Any], event: RuntimeEvent) -> None:
1093
+ """Attach §8.4 identity fields (runId/scopeId/itemId/partId/operation)."""
1094
+ payload["runId"] = event.run_id
1095
+ payload["scopeId"] = event.scope_id
1096
+ if isinstance(event, (ItemStarted, ItemUpdated, ItemCompleted, ItemFailed)):
1097
+ payload["itemId"] = event.item_id
1098
+ if isinstance(event, ItemUpdated):
1099
+ payload["operation"] = event.op
1100
+ if isinstance(event, ItemUpdated) and hasattr(event.update, "part_id"):
1101
+ payload["partId"] = event.update.part_id
1102
+ if isinstance(event, ItemStarted) and event.initial is not None:
1103
+ for part in event.initial.parts:
1104
+ if hasattr(part, "part_id"):
1105
+ payload["partId"] = part.part_id
1106
+ break
1107
+ if isinstance(event, ItemCompleted):
1108
+ for part in event.snapshot.parts:
1109
+ if hasattr(part, "part_id"):
1110
+ payload["partId"] = part.part_id
1111
+ break
1112
+ if isinstance(event, (InteractionRequested, InteractionResolved)):
1113
+ payload["itemId"] = event.interaction_id
1114
+ if isinstance(event, (ContinuationCreated, ContinuationResumed)):
1115
+ payload["itemId"] = event.continuation_id
1116
+
1117
+
707
1118
  __all__ = ["StudioRunService", "StudioRunSpec", "project_runtime_event"]
1119
+
1120
+
1121
+ def _studio_envelope_projection(envelope) -> tuple[str, dict[str, Any]] | None:
1122
+ """Session envelope -> Studio RunEvent 投影;cursor 仍用 envelope.seq。"""
1123
+
1124
+ payload = envelope.payload or {}
1125
+ if envelope.event_type == "run.completed":
1126
+ return "run.completed", {
1127
+ "runId": envelope.run_id or "",
1128
+ "output_text": str(payload.get("output_text") or ""),
1129
+ }
1130
+ text = str(payload.get("delta") or payload.get("text") or "")
1131
+ if text:
1132
+ return "message.delta", {"delta": text}
1133
+ return None