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
ksadk/agui/agent.py CHANGED
@@ -7,6 +7,7 @@ import copy
7
7
  import hashlib
8
8
  import json
9
9
  import logging
10
+ import time
10
11
  from dataclasses import dataclass, field
11
12
  from importlib import import_module
12
13
  from typing import Any, AsyncIterator, Callable, Mapping, Optional, cast
@@ -36,12 +37,34 @@ from ag_ui.core import (
36
37
  ToolCallStartEvent,
37
38
  )
38
39
 
39
- from ksadk.agui.a2ui_projection import project_a2ui_operations
40
+ from ksadk.agui import _agent_helpers
40
41
  from ksadk.conversations.runtime_metadata import (
41
42
  _update_session_metadata_after_assistant_turn,
42
43
  prime_session_metadata_for_user_turn,
43
44
  )
44
- from ksadk.events.runtime_event import EventType, RuntimeEvent
45
+ from ksadk.events.canonical import (
46
+ ApprovalResponse,
47
+ ContentSnapshot,
48
+ ContinuationCreated,
49
+ InteractionRequested,
50
+ InteractionResolved,
51
+ ItemCompleted,
52
+ ItemSnapshotReplaced,
53
+ ItemStarted,
54
+ ItemUpdated,
55
+ RunCanceled,
56
+ RunCompleted,
57
+ RunFailed,
58
+ RunInterrupted,
59
+ RunStarted,
60
+ RuntimeEvent,
61
+ SourceRef,
62
+ )
63
+ from ksadk.events.content import (
64
+ TextContent,
65
+ ToolCallContent,
66
+ ToolResultContent,
67
+ )
45
68
  from ksadk.runtime.adapter import (
46
69
  CancelResult,
47
70
  ResumePayload,
@@ -105,6 +128,7 @@ class _WireState:
105
128
  reasoning_open: bool = False
106
129
  terminal: bool = False
107
130
  text_content: str = ""
131
+ reasoning_content: str = ""
108
132
 
109
133
 
110
134
  class KsadkAGUIAgent:
@@ -143,6 +167,12 @@ class KsadkAGUIAgent:
143
167
  return type(self)(name=self.name, _shared=self._shared)
144
168
 
145
169
  async def run(self, input: RunAgentInput) -> AsyncIterator[BaseEvent]:
170
+ from ksadk.kernel import ingress as _kernel_ingress
171
+
172
+ if _kernel_ingress.kernel_route_active():
173
+ async for event in self._kernel_run(input):
174
+ yield event
175
+ return
146
176
  wire = _WireState(
147
177
  thread_id=input.thread_id,
148
178
  run_id=input.run_id,
@@ -170,7 +200,10 @@ class KsadkAGUIAgent:
170
200
 
171
201
  run.active = True
172
202
  async for runtime_event in self._shared.executor.stream(run.handle):
173
- persisted = await self._persist(self._event_for_persistence(runtime_event, run))
203
+ persisted = await self._persist(
204
+ self._event_for_persistence(runtime_event, run),
205
+ session_id=input.thread_id,
206
+ )
174
207
  async for event in self._project(persisted, wire, run):
175
208
  yield event
176
209
  if not wire.terminal:
@@ -202,6 +235,102 @@ class KsadkAGUIAgent:
202
235
  if run is not None and not run.cancel_failed:
203
236
  run.active = False
204
237
 
238
+ async def _kernel_run(self, input: RunAgentInput) -> AsyncIterator[BaseEvent]:
239
+ """kernel 路径(灰度 opt-in):AG-UI run -> AgentControlCommand -> receipt。
240
+
241
+ mutation 只走 kernel.submit;AG-UI 事件 shape 保留,cursor 源自同一
242
+ Session seq(SessionEventSubscription.after_seq)。
243
+ """
244
+ from ksadk.kernel import ingress as _kernel_ingress
245
+
246
+ yield RunStartedEvent(
247
+ thread_id=input.thread_id,
248
+ run_id=input.run_id,
249
+ parent_run_id=input.parent_run_id,
250
+ input=input,
251
+ )
252
+ message_id = f"{input.run_id}:assistant"
253
+ text = ""
254
+ try:
255
+ trusted = _kernel_ingress.trusted_context(
256
+ source_kind="agui",
257
+ source_ref=input.run_id,
258
+ session_id=input.thread_id,
259
+ operations=("enqueue",),
260
+ launch_context=self._shared.launch_context,
261
+ )
262
+ command = _kernel_ingress.map_agui_request(
263
+ session_id=input.thread_id,
264
+ idempotency_key=input.run_id,
265
+ content=[
266
+ {"role": "user", "content": _agui_user_text(input)}
267
+ ],
268
+ run_id=input.run_id,
269
+ trusted=trusted,
270
+ )
271
+ receipt = await _kernel_ingress.submit_command(
272
+ command, permit=trusted.permit
273
+ )
274
+ if receipt.status not in ("accepted", "duplicate"):
275
+ yield RunErrorEvent(
276
+ message=f"agent kernel rejected command: {receipt.status}",
277
+ code=receipt.status.upper(),
278
+ )
279
+ return
280
+ text_open = False
281
+ async for _seq, payload in _kernel_ingress.subscribe_projected(
282
+ input.thread_id,
283
+ trusted=trusted,
284
+ after_seq=int(receipt.accepted_seq or 0),
285
+ projector=_agui_envelope_payload,
286
+ ):
287
+ if payload is None:
288
+ continue
289
+ kind, value = payload
290
+ if kind == "delta":
291
+ if not text_open:
292
+ text_open = True
293
+ yield TextMessageStartEvent(message_id=message_id)
294
+ text += value
295
+ yield TextMessageContentEvent(message_id=message_id, delta=value)
296
+ elif kind == "completed":
297
+ final = value or text
298
+ if final and not text_open:
299
+ text_open = True
300
+ yield TextMessageStartEvent(message_id=message_id)
301
+ if final.startswith(text) and len(final) > len(text):
302
+ yield TextMessageContentEvent(
303
+ message_id=message_id, delta=final[len(text):]
304
+ )
305
+ text = final
306
+ if text_open:
307
+ yield TextMessageEndEvent(message_id=message_id)
308
+ yield RunFinishedEvent(
309
+ thread_id=input.thread_id,
310
+ run_id=input.run_id,
311
+ outcome=RunFinishedSuccessOutcome(),
312
+ result={"output_text": text},
313
+ )
314
+ return
315
+ if text_open:
316
+ yield TextMessageEndEvent(message_id=message_id)
317
+ yield RunFinishedEvent(
318
+ thread_id=input.thread_id,
319
+ run_id=input.run_id,
320
+ outcome=RunFinishedSuccessOutcome(),
321
+ result={"output_text": text},
322
+ )
323
+ except Exception:
324
+ logger.exception(
325
+ "AG-UI kernel ingress failed for thread=%s run=%s",
326
+ input.thread_id,
327
+ input.run_id,
328
+ )
329
+ yield RunErrorEvent(
330
+ message="Agent kernel ingress failed",
331
+ code="KERNEL_ERROR",
332
+ )
333
+
205
334
  async def _resolve_run(self, input: RunAgentInput) -> tuple[_ThreadRun, bool]:
206
335
  async with self._shared.lock:
207
336
  if input.resume:
@@ -331,155 +460,183 @@ class KsadkAGUIAgent:
331
460
  default=str,
332
461
  )
333
462
  decision = self._approval_decision_for_audit(entry.status, entry.payload)
334
- event = RuntimeEvent.create(
335
- EventType.APPROVAL_RESOLVED,
463
+ event = InteractionResolved(
464
+ schema_version=2,
336
465
  event_id=f"evt_agui_resume_{hashlib.sha256(event_key.encode()).hexdigest()}",
337
- agent_id=str(run.handle.native_ref.get("agent_id") or self.name),
338
- user_id=str(run.handle.native_ref.get("user_id") or "agui-user"),
339
- session_id=input.thread_id,
340
- invocation_id=run.handle.run_id,
341
- seq_id=0,
342
- payload={
343
- "approval_id": entry.interrupt_id,
344
- "call_id": entry.interrupt_id,
345
- "decision": decision,
346
- "resume_fingerprint": fingerprint,
347
- "protocol": "ag-ui",
348
- },
466
+ seq=0,
467
+ timestamp=time.time(),
468
+ run_id=run.handle.run_id,
469
+ scope_id=f"ksadk:{run.handle.run_id}",
470
+ source=SourceRef(
471
+ framework="ksadk",
472
+ metadata={
473
+ "agent_id": str(run.handle.native_ref.get("agent_id") or self.name),
474
+ "user_id": str(run.handle.native_ref.get("user_id") or "agui-user"),
475
+ "session_id": input.thread_id,
476
+ "protocol": "ag-ui",
477
+ "resume_fingerprint": fingerprint,
478
+ },
479
+ ),
480
+ interaction_id=entry.interrupt_id,
481
+ interaction_kind="approval",
482
+ response=ApprovalResponse(
483
+ response_type="approval",
484
+ decision=(
485
+ decision if decision in ("approved", "rejected", "canceled") else "rejected"
486
+ ),
487
+ data={"call_id": entry.interrupt_id},
488
+ ),
349
489
  )
350
490
  if index == 0:
351
- _persisted, reservation_created = await self._reserve(event)
491
+ _persisted, reservation_created = await self._reserve(
492
+ event, session_id=input.thread_id
493
+ )
352
494
  if not reservation_created:
353
495
  return False
354
496
  else:
355
- await self._persist(event)
497
+ await self._persist(event, session_id=input.thread_id)
356
498
  return reservation_created
357
499
 
358
- @staticmethod
359
- def _approval_decision_for_audit(status: str, payload: Any) -> str:
360
- """Persist a stable decision value while accepting official AG-UI envelopes."""
361
-
362
- if status != "resolved":
363
- return "rejected"
364
- if payload is True:
365
- return "approved"
366
- if isinstance(payload, Mapping):
367
- for key in ("approve", "approved"):
368
- if key in payload:
369
- return "approved" if bool(payload[key]) else "rejected"
370
- for key in ("decision", "type"):
371
- if key in payload:
372
- return KsadkAGUIAgent._approval_decision_for_audit(status, payload[key])
373
- return "rejected"
374
- if isinstance(payload, str) and payload.strip().lower() in {"approve", "approved"}:
375
- return "approved"
376
- return "rejected"
377
-
378
500
  async def _project(
379
501
  self,
380
502
  event: RuntimeEvent,
381
503
  wire: _WireState,
382
504
  run: _ThreadRun,
383
505
  ) -> AsyncIterator[BaseEvent]:
384
- payload = event.payload
385
- event_type = event.event_type
386
-
387
- if event_type in (EventType.TEXT_DELTA, EventType.TEXT_COMPLETED):
506
+ # ---- message (text) ----
507
+ if isinstance(event, (ItemUpdated, ItemSnapshotReplaced)) and event.item_kind == "message":
388
508
  if not wire.text_open:
389
509
  wire.text_open = True
390
510
  yield TextMessageStartEvent(message_id=wire.message_id)
391
- text = str(payload.get("text") or "")
392
- if event_type == EventType.TEXT_COMPLETED and wire.text_content:
393
- text = text[len(wire.text_content) :] if text.startswith(wire.text_content) else ""
511
+ if isinstance(event, ItemSnapshotReplaced):
512
+ # atomic snapshot replace: close and reopen with full text
513
+ wire.text_open = False
514
+ wire.text_content = ""
515
+ yield TextMessageEndEvent(message_id=wire.message_id)
516
+ wire.text_open = True
517
+ yield TextMessageStartEvent(message_id=wire.message_id)
518
+ text = self._extract_text(event.snapshot)
519
+ elif event.op == "replace":
520
+ wire.text_open = False
521
+ wire.text_content = ""
522
+ yield TextMessageEndEvent(message_id=wire.message_id)
523
+ wire.text_open = True
524
+ yield TextMessageStartEvent(message_id=wire.message_id)
525
+ text = event.update.text if isinstance(event.update, TextContent) else ""
526
+ else:
527
+ text = event.update.text if isinstance(event.update, TextContent) else ""
394
528
  if text:
395
529
  yield TextMessageContentEvent(message_id=wire.message_id, delta=text)
396
530
  wire.text_content += text
397
- if event_type == EventType.TEXT_COMPLETED:
398
- wire.text_open = False
399
- yield TextMessageEndEvent(message_id=wire.message_id)
400
531
  return
401
532
 
402
- if event_type in (EventType.REASONING_DELTA, EventType.REASONING_COMPLETED):
533
+ if isinstance(event, ItemCompleted) and event.item_kind == "message":
534
+ if not wire.text_open:
535
+ wire.text_open = True
536
+ yield TextMessageStartEvent(message_id=wire.message_id)
537
+ # ItemCompleted is the authoritative snapshot; do NOT re-append
538
+ # the full text when deltas already covered the content.
539
+ if not wire.text_content:
540
+ text = self._extract_text(event.snapshot)
541
+ if text:
542
+ yield TextMessageContentEvent(message_id=wire.message_id, delta=text)
543
+ wire.text_content += text
544
+ wire.text_open = False
545
+ yield TextMessageEndEvent(message_id=wire.message_id)
546
+ return
547
+
548
+ if isinstance(event, ItemStarted) and event.item_kind == "message":
549
+ # ItemStarted for messages is a tracking signal; the text message
550
+ # opens lazily on the first content delta or completed snapshot.
551
+ return
552
+
553
+ # ---- reasoning ----
554
+ if (
555
+ isinstance(event, (ItemUpdated, ItemSnapshotReplaced))
556
+ and event.item_kind == "reasoning"
557
+ ):
403
558
  if not wire.reasoning_open:
404
559
  wire.reasoning_open = True
405
560
  yield ReasoningStartEvent(message_id=wire.reasoning_id)
406
561
  yield ReasoningMessageStartEvent(message_id=wire.reasoning_id, role="reasoning")
407
- text = str(payload.get("text") or "")
562
+ if isinstance(event, ItemSnapshotReplaced):
563
+ wire.reasoning_content = ""
564
+ text = self._extract_text(event.snapshot)
565
+ else:
566
+ text = event.update.text if isinstance(event.update, TextContent) else ""
408
567
  if text:
409
568
  yield ReasoningMessageContentEvent(message_id=wire.reasoning_id, delta=text)
410
- if event_type == EventType.REASONING_COMPLETED:
411
- wire.reasoning_open = False
412
- yield ReasoningMessageEndEvent(message_id=wire.reasoning_id)
413
- yield ReasoningEndEvent(message_id=wire.reasoning_id)
569
+ wire.reasoning_content += text
414
570
  return
415
571
 
416
- if event_type == EventType.TOOL_CALL_BEGIN:
417
- call_id = str(payload.get("call_id") or "tool")
418
- yield ToolCallStartEvent(
419
- tool_call_id=call_id,
420
- tool_call_name=str(payload.get("name") or "tool"),
421
- parent_message_id=wire.message_id,
422
- )
423
- if "args" in payload:
572
+ if isinstance(event, ItemCompleted) and event.item_kind == "reasoning":
573
+ if not wire.reasoning_open:
574
+ wire.reasoning_open = True
575
+ yield ReasoningStartEvent(message_id=wire.reasoning_id)
576
+ yield ReasoningMessageStartEvent(message_id=wire.reasoning_id, role="reasoning")
577
+ if not wire.reasoning_content:
578
+ text = self._extract_text(event.snapshot)
579
+ if text:
580
+ yield ReasoningMessageContentEvent(message_id=wire.reasoning_id, delta=text)
581
+ wire.reasoning_content += text
582
+ wire.reasoning_open = False
583
+ yield ReasoningMessageEndEvent(message_id=wire.reasoning_id)
584
+ yield ReasoningEndEvent(message_id=wire.reasoning_id)
585
+ return
586
+
587
+ if isinstance(event, ItemStarted) and event.item_kind == "reasoning":
588
+ return
589
+
590
+ # ---- tool call ----
591
+ if isinstance(event, ItemStarted) and event.item_kind == "tool_call":
592
+ part = self._first_part(event.initial)
593
+ if isinstance(part, ToolCallContent):
594
+ call_id = part.call_id
595
+ yield ToolCallStartEvent(
596
+ tool_call_id=call_id,
597
+ tool_call_name=part.name,
598
+ parent_message_id=wire.message_id,
599
+ )
424
600
  yield ToolCallArgsEvent(
425
601
  tool_call_id=call_id,
426
- delta=json.dumps(payload.get("args"), ensure_ascii=False, default=str),
602
+ delta=json.dumps(part.arguments, ensure_ascii=False, default=str),
427
603
  )
428
604
  return
429
605
 
430
- if event_type == EventType.TOOL_CALL_END:
431
- call_id = str(payload.get("call_id") or "tool")
432
- content = (
433
- payload.get("result") if payload.get("error") is None else payload.get("error")
434
- )
435
- # AG-UI closes the active call at TOOL_CALL_END. Sending a result
436
- # first makes official clients discard the call, then reject this
437
- # end event as orphaned.
606
+ if isinstance(event, ItemCompleted) and event.item_kind == "tool_call":
607
+ part = self._first_part(event.snapshot)
608
+ call_id = part.call_id if isinstance(part, ToolCallContent) else "tool"
438
609
  yield ToolCallEndEvent(tool_call_id=call_id)
439
- yield ToolCallResultEvent(
440
- message_id=f"{wire.run_id}:tool:{call_id}",
441
- tool_call_id=call_id,
442
- content=json.dumps(content, ensure_ascii=False, default=str),
443
- role="tool",
444
- )
445
610
  return
446
611
 
447
- if event_type == EventType.CHECKPOINT_CREATED:
448
- yield StateSnapshotEvent(snapshot={"checkpoint": copy.deepcopy(payload)})
612
+ if isinstance(event, ItemCompleted) and event.item_kind == "tool_result":
613
+ part = self._first_part(event.snapshot)
614
+ if isinstance(part, ToolResultContent):
615
+ call_id = part.call_id
616
+ content = part.result
617
+ yield ToolCallResultEvent(
618
+ message_id=f"{wire.run_id}:tool:{call_id}",
619
+ tool_call_id=call_id,
620
+ content=json.dumps(content, ensure_ascii=False, default=str),
621
+ role="tool",
622
+ )
449
623
  return
450
624
 
451
- if event_type == EventType.APPROVAL_REQUESTED:
452
- interrupt_id = str(payload.get("approval_id") or payload.get("call_id") or "")
453
- checkpoint_id = str(run.handle.native_ref.get("checkpoint_id") or "")
454
- if not checkpoint_id:
455
- known = run.handle.native_ref.get("known_checkpoint_ids") or []
456
- checkpoint_id = str(known[-1]) if known else ""
457
- raw_detail = payload.get("detail")
458
- detail: dict[str, Any] = raw_detail if isinstance(raw_detail, dict) else {}
459
- interrupt = Interrupt(
460
- id=interrupt_id,
461
- reason=str(detail.get("reason") or payload.get("kind") or "approval"),
462
- message=self._approval_message(detail, payload),
463
- tool_call_id=str(payload.get("call_id") or "") or None,
464
- response_schema=detail.get("response_schema"),
465
- metadata=self._approval_metadata(detail, payload),
466
- )
467
- run.pending[interrupt_id] = _PendingInterrupt(interrupt, checkpoint_id)
625
+ if isinstance(event, ItemStarted) and event.item_kind == "tool_result":
468
626
  return
469
627
 
470
- if event_type in {
471
- EventType.A2UI_SURFACE_BEGIN,
472
- EventType.A2UI_SURFACE_UPDATE,
473
- EventType.A2UI_SURFACE_END,
474
- }:
475
- operations = project_a2ui_operations(event_type, payload)
628
+ # ---- A2UI surface (item_kind="data" + source.protocol="a2ui") ----
629
+ if (
630
+ isinstance(event, (ItemStarted, ItemUpdated, ItemCompleted))
631
+ and event.item_kind == "data"
632
+ and event.source.protocol == "a2ui"
633
+ ):
634
+ surface_id = str(event.source.metadata.get("surface_id") or "")
635
+ operations = self._a2ui_operations(event, surface_id)
476
636
  if operations:
477
- surface_id = str(payload.get("surface_id") or payload.get("surfaceId") or "")
478
637
  yield ActivitySnapshotEvent(
479
638
  message_id=f"{wire.run_id}:a2ui:{surface_id}",
480
639
  activity_type="a2ui-surface",
481
- # The client renderer addresses a surface by this value.
482
- # ``message_id`` is a transport id, not the A2UI surface id.
483
640
  content={
484
641
  "surfaceId": surface_id,
485
642
  "a2ui_operations": operations,
@@ -488,18 +645,62 @@ class KsadkAGUIAgent:
488
645
  )
489
646
  return
490
647
 
491
- if event_type == EventType.RUN_INTERRUPTED:
648
+ # ---- checkpoint ----
649
+ if isinstance(event, ContinuationCreated):
650
+ # Track checkpoint id in handle native_ref for downstream approval
651
+ # resolution (pending interrupts need a resumable checkpoint_id).
652
+ ckpt_id = str(event.ref.get("checkpoint_id") or "")
653
+ if ckpt_id:
654
+ run.handle.native_ref["checkpoint_id"] = ckpt_id
655
+ known = run.handle.native_ref.setdefault("known_checkpoint_ids", [])
656
+ if ckpt_id not in known:
657
+ known.append(ckpt_id)
658
+ yield StateSnapshotEvent(snapshot={"checkpoint": copy.deepcopy(event.ref)})
659
+ return
660
+
661
+ # ---- approval ----
662
+ if isinstance(event, InteractionRequested):
663
+ interrupt = self._interrupt_from_interaction(event)
664
+ checkpoint_id = str(run.handle.native_ref.get("checkpoint_id") or "")
665
+ if not checkpoint_id:
666
+ known = run.handle.native_ref.get("known_checkpoint_ids") or []
667
+ checkpoint_id = str(known[-1]) if known else ""
668
+ run.pending[interrupt.id] = _PendingInterrupt(interrupt, checkpoint_id)
669
+ return
670
+
671
+ # ---- run lifecycle ----
672
+ if isinstance(event, RunInterrupted):
492
673
  async for close_event in self._close_open_messages(wire):
493
674
  yield close_event
494
675
  if not run.pending:
495
676
  raise ValueError("runtime interrupted without a pending approval")
496
- checkpoint_id = str(run.handle.native_ref.get("checkpoint_id") or "")
677
+ # If checkpoint_id wasn't set by ContinuationCreated (e.g. langgraph
678
+ # canonical stream without checkpoint_ref on first run), try to
679
+ # extract it from the RunInterrupted event's continuation_id or
680
+ # the executor's checkpoint descriptor.
681
+ if event.continuation_id:
682
+ checkpoint_id = str(event.continuation_id)
683
+ else:
684
+ checkpoint_id = str(run.handle.native_ref.get("checkpoint_id") or "")
497
685
  if not checkpoint_id:
498
686
  known = run.handle.native_ref.get("known_checkpoint_ids") or []
499
687
  checkpoint_id = str(known[-1]) if known else ""
500
- for pending in run.pending.values():
501
- if not pending.checkpoint_id:
502
- pending.checkpoint_id = checkpoint_id
688
+ if not checkpoint_id:
689
+ # Last resort: query the executor for the native checkpoint.
690
+ try:
691
+ descriptor = await self._shared.executor.checkpoint(run.handle)
692
+ checkpoint_id = str(descriptor.checkpoint_id or "")
693
+ if checkpoint_id:
694
+ run.handle.native_ref["checkpoint_id"] = checkpoint_id
695
+ known = run.handle.native_ref.setdefault("known_checkpoint_ids", [])
696
+ if checkpoint_id not in known:
697
+ known.append(checkpoint_id)
698
+ except Exception:
699
+ pass
700
+ if checkpoint_id:
701
+ for pending in run.pending.values():
702
+ if not pending.checkpoint_id:
703
+ pending.checkpoint_id = checkpoint_id
503
704
  run.interrupted = True
504
705
  wire.terminal = True
505
706
  yield RunFinishedEvent(
@@ -511,23 +712,30 @@ class KsadkAGUIAgent:
511
712
  )
512
713
  return
513
714
 
514
- if event_type == EventType.RUN_COMPLETED:
715
+ if isinstance(event, RunCompleted):
515
716
  async for finish_event in self._finish_success(wire):
516
717
  yield finish_event
517
718
  return
518
719
 
519
- if event_type in (EventType.RUN_FAILED, EventType.RUN_CANCELED):
720
+ if isinstance(event, RunCanceled):
520
721
  async for close_event in self._close_open_messages(wire):
521
722
  yield close_event
522
723
  wire.terminal = True
523
724
  yield RunErrorEvent(
524
- message=(
525
- "Runtime run was cancelled"
526
- if event_type == EventType.RUN_CANCELED
527
- else "Runtime execution failed"
528
- ),
529
- code=("CANCELLED" if event_type == EventType.RUN_CANCELED else "RUNTIME_ERROR"),
725
+ message=event.reason or "Runtime run was cancelled",
726
+ code="CANCELLED",
530
727
  )
728
+ return
729
+
730
+ if isinstance(event, RunFailed):
731
+ async for close_event in self._close_open_messages(wire):
732
+ yield close_event
733
+ wire.terminal = True
734
+ yield RunErrorEvent(
735
+ message=event.error.message or "Runtime execution failed",
736
+ code="RUNTIME_ERROR",
737
+ )
738
+ return
531
739
 
532
740
  async def _finish_success(self, wire: _WireState) -> AsyncIterator[BaseEvent]:
533
741
  async for event in self._close_open_messages(wire):
@@ -550,14 +758,17 @@ class KsadkAGUIAgent:
550
758
  wire.text_open = False
551
759
  yield TextMessageEndEvent(message_id=wire.message_id)
552
760
 
553
- async def _persist(self, event: RuntimeEvent) -> RuntimeEvent:
761
+ async def _persist(self, event: RuntimeEvent, *, session_id: str = "") -> RuntimeEvent:
554
762
  factory = self._shared.event_store_factory
555
763
  if factory is None:
556
764
  return event
557
765
  store = factory()
558
- return cast(RuntimeEvent, await store.append_one(event))
766
+ sid = str(event.source.metadata.get("session_id") or session_id or "")
767
+ return cast(RuntimeEvent, await store.append_one(sid, event))
559
768
 
560
- async def _reserve(self, event: RuntimeEvent) -> tuple[RuntimeEvent, bool]:
769
+ async def _reserve(
770
+ self, event: RuntimeEvent, *, session_id: str = ""
771
+ ) -> tuple[RuntimeEvent, bool]:
561
772
  factory = self._shared.event_store_factory
562
773
  if factory is None:
563
774
  return event, True
@@ -566,7 +777,8 @@ class KsadkAGUIAgent:
566
777
  if callable(reserve):
567
778
  persisted, created = await reserve(event)
568
779
  return cast(RuntimeEvent, persisted), bool(created)
569
- return cast(RuntimeEvent, await store.append_one(event)), True
780
+ sid = str(event.source.metadata.get("session_id") or session_id or "")
781
+ return cast(RuntimeEvent, await store.append_one(sid, event)), True
570
782
 
571
783
  async def _persist_user_input(
572
784
  self,
@@ -576,21 +788,27 @@ class KsadkAGUIAgent:
576
788
  ) -> None:
577
789
  event_key = json.dumps([input.thread_id, input.run_id, "user"], ensure_ascii=False)
578
790
  await self._persist(
579
- RuntimeEvent.create(
580
- EventType.RUN_STARTED,
791
+ RunStarted(
792
+ schema_version=2,
581
793
  event_id=f"evt_agui_input_{hashlib.sha256(event_key.encode()).hexdigest()}",
582
- agent_id=self.name,
583
- user_id=request.user_id,
584
- session_id=input.thread_id,
585
- invocation_id=input.run_id,
586
- seq_id=0,
587
- payload={
588
- "status": "in_progress",
589
- "input": self._json_safe(request.input),
590
- "source": "ag-ui",
591
- "runtime_type": handle.runtime_type,
592
- },
593
- )
794
+ seq=0,
795
+ timestamp=time.time(),
796
+ run_id=input.run_id,
797
+ scope_id=f"ksadk:{input.run_id}",
798
+ source=SourceRef(
799
+ framework="ksadk",
800
+ metadata={
801
+ "agent_id": self.name,
802
+ "user_id": request.user_id,
803
+ "session_id": input.thread_id,
804
+ "input": self._json_safe(request.input),
805
+ "source": "ag-ui",
806
+ "runtime_type": handle.runtime_type,
807
+ },
808
+ ),
809
+ status="running",
810
+ ),
811
+ session_id=input.thread_id,
594
812
  )
595
813
  await self._prime_session_metadata_for_user_turn(
596
814
  session_id=input.thread_id,
@@ -638,21 +856,22 @@ class KsadkAGUIAgent:
638
856
  logger.debug("failed to update AG-UI session metadata", exc_info=True)
639
857
 
640
858
  def _event_for_persistence(self, event: RuntimeEvent, run: _ThreadRun) -> RuntimeEvent:
641
- if event.event_type in {
642
- EventType.APPROVAL_REQUESTED,
643
- EventType.APPROVAL_RESOLVED,
644
- }:
645
- return event.model_copy(update={"payload": {**event.payload, "protocol": "ag-ui"}})
646
- if event.event_type != EventType.RUN_INTERRUPTED:
859
+ if isinstance(event, (InteractionRequested, InteractionResolved)):
860
+ new_source = event.source.model_copy(
861
+ update={"metadata": {**event.source.metadata, "protocol": "ag-ui"}}
862
+ )
863
+ return event.model_copy(update={"source": new_source})
864
+ if not isinstance(event, RunInterrupted):
647
865
  return event
648
- return event.model_copy(
866
+ new_source = event.source.model_copy(
649
867
  update={
650
- "payload": {
651
- **event.payload,
868
+ "metadata": {
869
+ **event.source.metadata,
652
870
  "runtime_handle": self._durable_handle(run.handle),
653
871
  }
654
872
  }
655
873
  )
874
+ return event.model_copy(update={"source": new_source})
656
875
 
657
876
  async def _restore_durable_run(
658
877
  self,
@@ -663,13 +882,15 @@ class KsadkAGUIAgent:
663
882
  if not events:
664
883
  return None, False
665
884
  resolved = {
666
- str(event.payload.get("approval_id") or event.payload.get("call_id") or ""): event
885
+ str(event.interaction_id): event
667
886
  for event in events
668
- if event.event_type == EventType.APPROVAL_RESOLVED
887
+ if isinstance(event, InteractionResolved)
669
888
  }
670
889
  if fingerprints and all(interrupt_id in resolved for interrupt_id in fingerprints):
671
890
  for interrupt_id, fingerprint in fingerprints.items():
672
- persisted = str(resolved[interrupt_id].payload.get("resume_fingerprint") or "")
891
+ persisted = str(
892
+ resolved[interrupt_id].source.metadata.get("resume_fingerprint") or ""
893
+ )
673
894
  if persisted and persisted != fingerprint:
674
895
  raise ValueError(f"interrupt {interrupt_id!r} was resolved differently")
675
896
  return None, True
@@ -677,22 +898,21 @@ class KsadkAGUIAgent:
677
898
  raise ValueError("resume set is only partially resolved")
678
899
 
679
900
  interrupted = next(
680
- (event for event in reversed(events) if event.event_type == EventType.RUN_INTERRUPTED),
901
+ (event for event in reversed(events) if isinstance(event, RunInterrupted)),
681
902
  None,
682
903
  )
683
904
  if interrupted is None:
684
905
  return None, False
685
- raw_handle = interrupted.payload.get("runtime_handle")
906
+ raw_handle = interrupted.source.metadata.get("runtime_handle")
686
907
  if not isinstance(raw_handle, dict):
687
908
  return None, False
688
909
  handle = RunHandle.model_validate(raw_handle)
689
910
  requests = [
690
911
  event
691
912
  for event in events
692
- if event.invocation_id == interrupted.invocation_id
693
- and event.event_type == EventType.APPROVAL_REQUESTED
694
- and str(event.payload.get("approval_id") or event.payload.get("call_id") or "")
695
- not in resolved
913
+ if isinstance(event, InteractionRequested)
914
+ and event.run_id == interrupted.run_id
915
+ and str(event.interaction_id) not in resolved
696
916
  ]
697
917
  pending: dict[str, _PendingInterrupt] = {}
698
918
  checkpoint_id = str(handle.native_ref.get("checkpoint_id") or "")
@@ -700,7 +920,7 @@ class KsadkAGUIAgent:
700
920
  known = handle.native_ref.get("known_checkpoint_ids") or []
701
921
  checkpoint_id = str(known[-1]) if known else ""
702
922
  for request in requests:
703
- interrupt = self._interrupt_from_payload(request.payload)
923
+ interrupt = self._interrupt_from_interaction(request)
704
924
  pending[interrupt.id] = _PendingInterrupt(interrupt, checkpoint_id)
705
925
  if not pending:
706
926
  return None, False
@@ -721,55 +941,6 @@ class KsadkAGUIAgent:
721
941
  return []
722
942
  return list(await list_events(session_id))
723
943
 
724
- @staticmethod
725
- def _durable_handle(handle: RunHandle) -> dict[str, Any]:
726
- allowed = {
727
- "agent_id",
728
- "user_id",
729
- "checkpoint_id",
730
- "known_checkpoint_ids",
731
- "pending_approval_ids",
732
- "framework_ref",
733
- "thread_id",
734
- "checkpoint_ns",
735
- "resume_thread_id",
736
- }
737
- native_ref = {key: value for key, value in handle.native_ref.items() if key in allowed}
738
- return {
739
- "run_id": handle.run_id,
740
- "session_id": handle.session_id,
741
- "runtime_type": handle.runtime_type,
742
- "native_ref": KsadkAGUIAgent._json_safe(native_ref),
743
- }
744
-
745
- @staticmethod
746
- def _interrupt_from_payload(payload: Mapping[str, Any]) -> Interrupt:
747
- interrupt_id = str(payload.get("approval_id") or payload.get("call_id") or "")
748
- raw_detail = payload.get("detail")
749
- detail = raw_detail if isinstance(raw_detail, dict) else {}
750
- return Interrupt(
751
- id=interrupt_id,
752
- reason=str(detail.get("reason") or payload.get("kind") or "approval"),
753
- message=KsadkAGUIAgent._approval_message(detail, payload),
754
- tool_call_id=str(payload.get("call_id") or "") or None,
755
- response_schema=detail.get("response_schema"),
756
- metadata=KsadkAGUIAgent._approval_metadata(detail, payload),
757
- )
758
-
759
- @staticmethod
760
- def _duplicate_run(input: RunAgentInput) -> _ThreadRun:
761
- return _ThreadRun(
762
- handle=RunHandle(
763
- run_id=input.run_id,
764
- session_id=input.thread_id,
765
- runtime_type="ag-ui-duplicate",
766
- )
767
- )
768
-
769
- @staticmethod
770
- def _json_safe(value: Any) -> Any:
771
- return json.loads(json.dumps(value, ensure_ascii=False, default=str))
772
-
773
944
  async def _close_thread(self, thread_id: str, run: _ThreadRun) -> None:
774
945
  try:
775
946
  await self._shared.executor.close(run.handle)
@@ -793,80 +964,93 @@ class KsadkAGUIAgent:
793
964
  run.active = True
794
965
  return result
795
966
 
967
+ @staticmethod
968
+ def _approval_decision_for_audit(status: str, payload: Any) -> str:
969
+ return _agent_helpers.approval_decision_for_audit(status, payload)
970
+
971
+ @staticmethod
972
+ def _durable_handle(handle: RunHandle) -> dict[str, Any]:
973
+ return _agent_helpers.durable_handle(handle)
974
+
975
+ @staticmethod
976
+ def _interrupt_from_payload(payload: Mapping[str, Any]) -> Interrupt:
977
+ return _agent_helpers.interrupt_from_payload(payload)
978
+
979
+ @staticmethod
980
+ def _interrupt_from_interaction(event: InteractionRequested) -> Interrupt:
981
+ return _agent_helpers.interrupt_from_interaction(event)
982
+
983
+ @staticmethod
984
+ def _extract_text(snapshot: ContentSnapshot | None) -> str:
985
+ return _agent_helpers.extract_text(snapshot)
986
+
987
+ @staticmethod
988
+ def _first_part(snapshot: ContentSnapshot | None) -> Any:
989
+ return _agent_helpers.first_part(snapshot)
990
+
991
+ @staticmethod
992
+ def _a2ui_operations(
993
+ event: ItemStarted | ItemUpdated | ItemCompleted,
994
+ surface_id: str,
995
+ ) -> list[dict[str, Any]]:
996
+ return _agent_helpers.a2ui_operations(event, surface_id)
997
+
998
+ @staticmethod
999
+ def _duplicate_run(input: RunAgentInput) -> _ThreadRun:
1000
+ return _agent_helpers.duplicate_run(input)
1001
+
1002
+ @staticmethod
1003
+ def _json_safe(value: Any) -> Any:
1004
+ return _agent_helpers.json_safe(value)
1005
+
796
1006
  @staticmethod
797
1007
  def _latest_user_input(input: RunAgentInput) -> Any:
798
- for message in reversed(input.messages):
799
- if getattr(message, "role", None) == "user":
800
- return getattr(message, "content", "")
801
- return ""
1008
+ return _agent_helpers.latest_user_input(input)
802
1009
 
803
1010
  @staticmethod
804
1011
  def _input_text(value: Any) -> str:
805
- if isinstance(value, str):
806
- return value.strip()
807
- if isinstance(value, Mapping):
808
- return str(value.get("text") or value.get("content") or "").strip()
809
- return str(value or "").strip()
1012
+ return _agent_helpers.input_text(value)
810
1013
 
811
1014
  @staticmethod
812
1015
  def _approval_action(detail: Mapping[str, Any]) -> Mapping[str, Any]:
813
- nested_request = detail.get("approval_requests")
814
- actions = (
815
- nested_request.get("action_requests")
816
- if isinstance(nested_request, Mapping)
817
- else detail.get("action_requests")
818
- )
819
- if isinstance(actions, list):
820
- for action in actions:
821
- if isinstance(action, Mapping):
822
- return action
823
- return {}
1016
+ return _agent_helpers.approval_action(detail)
824
1017
 
825
1018
  @staticmethod
826
1019
  def _approval_metadata(
827
1020
  detail: Mapping[str, Any],
828
1021
  payload: Mapping[str, Any],
829
1022
  ) -> dict[str, Any]:
830
- action = KsadkAGUIAgent._approval_action(detail)
831
- arguments = (
832
- action.get("args")
833
- or action.get("arguments")
834
- or detail.get("arguments")
835
- or detail.get("args")
836
- or payload.get("args")
837
- )
838
- metadata: dict[str, Any] = {
839
- "tool_name": str(
840
- action.get("name")
841
- or detail.get("tool_name")
842
- or payload.get("name")
843
- or payload.get("kind")
844
- or "approval"
845
- ),
846
- "arguments": arguments if arguments is not None else {},
847
- }
848
- approval_level = (
849
- action.get("approval_level")
850
- or detail.get("approval_level")
851
- or payload.get("approval_level")
852
- )
853
- if approval_level:
854
- metadata["approval_level"] = str(approval_level)
855
- return metadata
1023
+ return _agent_helpers.approval_metadata(detail, payload)
856
1024
 
857
1025
  @staticmethod
858
1026
  def _approval_message(detail: Mapping[str, Any], payload: Mapping[str, Any]) -> str:
859
- action = KsadkAGUIAgent._approval_action(detail)
860
- return str(
861
- action.get("description")
862
- or detail.get("message")
863
- or payload.get("message")
864
- or "Approval required"
865
- )
1027
+ return _agent_helpers.approval_message(detail, payload)
866
1028
 
867
1029
  @staticmethod
868
1030
  def _resume_fingerprint(status: str, payload: Any) -> Any:
869
- return json.dumps([status, payload], sort_keys=True, ensure_ascii=False, default=str)
1031
+ return _agent_helpers.resume_fingerprint(status, payload)
870
1032
 
871
1033
 
872
1034
  __all__ = ["KsadkAGUIAgent"]
1035
+
1036
+ def _agui_user_text(input: RunAgentInput) -> str:
1037
+ parts = []
1038
+ for message in input.messages or []:
1039
+ content = getattr(message, "content", "")
1040
+ if isinstance(content, str):
1041
+ parts.append(content)
1042
+ elif content is not None:
1043
+ parts.append(str(content))
1044
+ return "\n".join(parts)
1045
+
1046
+
1047
+ def _agui_envelope_payload(envelope) -> tuple[str, str] | None:
1048
+ """Session envelope -> AG-UI 文本投影;cursor 仍用 envelope.seq。"""
1049
+
1050
+ payload = envelope.payload or {}
1051
+ if envelope.event_type == "run.completed":
1052
+ return "completed", str(payload.get("output_text") or "")
1053
+ text = str(payload.get("delta") or payload.get("text") or "")
1054
+ if text:
1055
+ return "delta", text
1056
+ return None