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
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
+ import logging
7
8
  from datetime import datetime, timezone
8
9
  from pathlib import Path
9
10
  from typing import Awaitable, Callable, cast
@@ -20,6 +21,8 @@ from ksadk.studio.contracts import (
20
21
  from ksadk.studio.errors import StudioError, not_found
21
22
  from ksadk.studio.workspace import Workspace
22
23
 
24
+ logger = logging.getLogger(__name__)
25
+
23
26
 
24
27
  class OperationManager:
25
28
  def __init__(self, workspace: Workspace) -> None:
@@ -33,7 +36,8 @@ class OperationManager:
33
36
  kind: OperationKind,
34
37
  resource_id: str,
35
38
  idempotency_key: str,
36
- runner: Callable[[], Awaitable[object]],
39
+ metadata: dict | None = None,
40
+ runner: Callable[[str], Awaitable[object]],
37
41
  ) -> Operation:
38
42
  existing = self._find_by_idempotency_key(idempotency_key)
39
43
  if existing is not None:
@@ -42,6 +46,7 @@ class OperationManager:
42
46
  id=f"op_{uuid4().hex}",
43
47
  kind=kind,
44
48
  resource_id=resource_id,
49
+ metadata=metadata or {},
45
50
  )
46
51
  self._write(operation, [], idempotency_key)
47
52
  self.append(operation.id, "operation.queued", {"kind": kind})
@@ -57,14 +62,14 @@ class OperationManager:
57
62
  async def _run(
58
63
  self,
59
64
  operation_id: str,
60
- runner: Callable[[], Awaitable[object]],
65
+ runner: Callable[[str], Awaitable[object]],
61
66
  ) -> None:
62
67
  operation = self.get(operation_id)
63
68
  operation.status = OperationStatus.RUNNING
64
69
  self._save_record(operation)
65
70
  self.append(operation_id, "operation.started", {})
66
71
  try:
67
- result = await runner()
72
+ result = await runner(operation_id)
68
73
  result_id = getattr(result, "id", None)
69
74
  if result_id:
70
75
  operation.resource_id = str(result_id)
@@ -91,12 +96,22 @@ class OperationManager:
91
96
  operation.completed_at = datetime.now(timezone.utc)
92
97
  self._save_record(operation)
93
98
  self.append(operation_id, "operation.failed", operation.error)
94
- except Exception:
99
+ except Exception as exc:
100
+ # Keep the browser response generic so an exception cannot leak a
101
+ # credential, but retain the traceback in the local Studio log for
102
+ # an operator to diagnose a failed deployment.
103
+ logger.exception("Studio operation failed: operation_id=%s", operation_id)
95
104
  operation.status = OperationStatus.FAILED
96
105
  operation.error = {
97
106
  "code": "INTERNAL_ERROR",
98
107
  "message": "本地操作执行失败",
108
+ "exceptionType": type(exc).__name__,
99
109
  }
110
+ # TypeError carries only Python call-shape information and is safe
111
+ # to surface to the local operator. Do not expose arbitrary
112
+ # exception text: it may include provider request data.
113
+ if isinstance(exc, TypeError):
114
+ operation.error["exceptionMessage"] = str(exc)
100
115
  operation.completed_at = datetime.now(timezone.utc)
101
116
  self._save_record(operation)
102
117
  self.append(operation_id, "operation.failed", operation.error)
@@ -105,6 +120,18 @@ class OperationManager:
105
120
  operation, _, _ = self._read(operation_id)
106
121
  return operation
107
122
 
123
+ def list(self, *, kind: OperationKind | None = None) -> list[Operation]:
124
+ directory = self.workspace.resolve(".agentkit/operations")
125
+ operations: list[Operation] = []
126
+ for path in directory.glob("op_*.json"):
127
+ try:
128
+ operation = self.get(path.stem)
129
+ except StudioError:
130
+ continue
131
+ if kind is None or operation.kind == kind:
132
+ operations.append(operation)
133
+ return sorted(operations, key=lambda item: item.created_at, reverse=True)
134
+
108
135
  def events(self, operation_id: str, *, after: int = 0) -> list[OperationEvent]:
109
136
  _, events, _ = self._read(operation_id)
110
137
  return [event for event in events if event.id > after]
@@ -133,7 +160,12 @@ class OperationManager:
133
160
  task = self._tasks.get(operation_id)
134
161
  if task is not None:
135
162
  task.cancel()
136
- return operation
163
+ if operation.status == OperationStatus.QUEUED:
164
+ operation.status = OperationStatus.CANCELLED
165
+ operation.completed_at = datetime.now(timezone.utc)
166
+ self._save_record(operation)
167
+ self.append(operation_id, "operation.cancelled", {})
168
+ return self.get(operation_id)
137
169
 
138
170
  async def wait(self, operation_id: str, *, timeout: float = 30) -> Operation:
139
171
  deadline = asyncio.get_running_loop().time() + timeout
@@ -149,6 +149,270 @@ def _enum_string(value: Any) -> str:
149
149
  return str(getattr(value, "value", value))
150
150
 
151
151
 
152
+ def _token_value(attributes: dict[str, Any], *keys: str) -> int | None:
153
+ """Read one non-negative token counter from known OTLP attribute names."""
154
+
155
+ for key in keys:
156
+ value = attributes.get(key)
157
+ if value is None or isinstance(value, bool):
158
+ continue
159
+ try:
160
+ normalized = int(value)
161
+ except (TypeError, ValueError):
162
+ continue
163
+ if normalized >= 0:
164
+ return normalized
165
+ return None
166
+
167
+
168
+ def _usage_from_attributes(attributes: dict[str, Any]) -> dict[str, Any]:
169
+ """Normalize AgentKit and standard GenAI usage without trusting a side flag."""
170
+
171
+ input_tokens = _token_value(
172
+ attributes,
173
+ "gen_ai.usage.input_tokens",
174
+ "agentkit.usage.input_tokens",
175
+ )
176
+ output_tokens = _token_value(
177
+ attributes,
178
+ "gen_ai.usage.output_tokens",
179
+ "agentkit.usage.output_tokens",
180
+ )
181
+ total_tokens = _token_value(
182
+ attributes,
183
+ "agentkit.usage.total_tokens",
184
+ "gen_ai.usage.total_tokens",
185
+ )
186
+ cached_input_tokens = _token_value(
187
+ attributes,
188
+ "gen_ai.usage.cache_read.input_tokens",
189
+ "gen_ai.usage.cached_input_tokens",
190
+ "llm.usage.cache_read.input_tokens",
191
+ "agentkit.usage.cached_input_tokens",
192
+ )
193
+ reasoning_output_tokens = _token_value(
194
+ attributes,
195
+ "gen_ai.usage.reasoning.output_tokens",
196
+ "gen_ai.usage.reasoning_tokens",
197
+ "gen_ai.usage.reasoning_output_tokens",
198
+ "llm.usage.reasoning_tokens",
199
+ "agentkit.usage.reasoning_output_tokens",
200
+ )
201
+ if total_tokens is None and input_tokens is not None and output_tokens is not None:
202
+ total_tokens = input_tokens + output_tokens
203
+ reported = any(
204
+ value is not None
205
+ for value in (
206
+ input_tokens,
207
+ output_tokens,
208
+ total_tokens,
209
+ cached_input_tokens,
210
+ reasoning_output_tokens,
211
+ )
212
+ )
213
+ source = attributes.get("agentkit.usage.source")
214
+ if not source and reported:
215
+ source = "gen_ai.usage"
216
+ return {
217
+ "inputTokens": input_tokens,
218
+ "outputTokens": output_tokens,
219
+ "totalTokens": total_tokens,
220
+ "cachedInputTokens": cached_input_tokens,
221
+ "reasoningOutputTokens": reasoning_output_tokens,
222
+ "usageCompleteness": {
223
+ "inputTokens": input_tokens is not None,
224
+ "outputTokens": output_tokens is not None,
225
+ "totalTokens": total_tokens is not None,
226
+ "cachedInputTokens": cached_input_tokens is not None,
227
+ "reasoningOutputTokens": reasoning_output_tokens is not None,
228
+ },
229
+ "usageReported": reported,
230
+ "usageSource": source,
231
+ }
232
+
233
+
234
+ def _aggregate_span_usage(spans: Iterable[dict[str, Any]]) -> dict[str, Any]:
235
+ """Aggregate billing spans and collapse only exact compatibility wrappers."""
236
+
237
+ span_list = list(spans)
238
+ usage_by_id: dict[str, dict[str, Any]] = {}
239
+ children_by_parent: dict[str, list[str]] = {}
240
+ parent_by_id: dict[str, str] = {}
241
+ anonymous_usages: list[dict[str, Any]] = []
242
+ for span in span_list:
243
+ span_id = str(span.get("spanId") or "")
244
+ parent_id = str(span.get("parentSpanId") or "")
245
+ usage = _usage_from_attributes(_decoded_attributes(span.get("attributes", [])))
246
+ if span_id:
247
+ usage_by_id[span_id] = usage
248
+ parent_by_id[span_id] = parent_id
249
+ if parent_id:
250
+ children_by_parent.setdefault(parent_id, []).append(span_id)
251
+ elif usage["usageReported"]:
252
+ anonymous_usages.append(usage)
253
+
254
+ usage_fields = (
255
+ "inputTokens",
256
+ "outputTokens",
257
+ "totalTokens",
258
+ "cachedInputTokens",
259
+ "reasoningOutputTokens",
260
+ )
261
+
262
+ def empty_aggregate() -> dict[str, Any]:
263
+ return {
264
+ **{key: None for key in usage_fields},
265
+ "usageCompleteness": {key: False for key in usage_fields},
266
+ "billingSpanCount": 0,
267
+ "usageReported": False,
268
+ "usageSource": None,
269
+ }
270
+
271
+ def billed_usage(usage: dict[str, Any]) -> dict[str, Any]:
272
+ return {
273
+ **{key: usage[key] for key in usage_fields},
274
+ "usageCompleteness": dict(usage["usageCompleteness"]),
275
+ "billingSpanCount": 1,
276
+ "usageReported": True,
277
+ "usageSource": usage["usageSource"],
278
+ }
279
+
280
+ def combine(parts: Iterable[dict[str, Any]]) -> dict[str, Any]:
281
+ billed_parts = [part for part in parts if part["billingSpanCount"] > 0]
282
+ if not billed_parts:
283
+ return empty_aggregate()
284
+ aggregate = empty_aggregate()
285
+ aggregate["billingSpanCount"] = sum(part["billingSpanCount"] for part in billed_parts)
286
+ aggregate["usageReported"] = True
287
+ aggregate["usageSource"] = next(
288
+ (part["usageSource"] for part in billed_parts if part["usageSource"]),
289
+ "gen_ai.usage",
290
+ )
291
+ for key in usage_fields:
292
+ values = [part[key] for part in billed_parts if part[key] is not None]
293
+ aggregate[key] = sum(values) if values else None
294
+ aggregate["usageCompleteness"][key] = all(
295
+ part["usageCompleteness"][key] for part in billed_parts
296
+ )
297
+ return aggregate
298
+
299
+ def is_compatibility_wrapper(
300
+ own_usage: dict[str, Any], descendant_usage: dict[str, Any]
301
+ ) -> bool:
302
+ if descendant_usage["billingSpanCount"] == 0:
303
+ return False
304
+ if own_usage["inputTokens"] is None or own_usage["outputTokens"] is None:
305
+ return False
306
+ return all(
307
+ descendant_usage["usageCompleteness"][key] and descendant_usage[key] == own_usage[key]
308
+ for key in ("inputTokens", "outputTokens")
309
+ ) and all(
310
+ own_usage[key] is None
311
+ or (
312
+ descendant_usage["usageCompleteness"][key]
313
+ and descendant_usage[key] == own_usage[key]
314
+ )
315
+ for key in ("inputTokens", "outputTokens", "totalTokens")
316
+ )
317
+
318
+ memo: dict[str, dict[str, Any]] = {}
319
+
320
+ def aggregate_subtree(span_id: str, visiting: set[str] | None = None) -> dict[str, Any]:
321
+ if span_id in memo:
322
+ return memo[span_id]
323
+ active = set() if visiting is None else visiting
324
+ if span_id in active:
325
+ return empty_aggregate()
326
+ active.add(span_id)
327
+ descendants = combine(
328
+ aggregate_subtree(child_id, active) for child_id in children_by_parent.get(span_id, [])
329
+ )
330
+ active.remove(span_id)
331
+ own_usage = usage_by_id[span_id]
332
+ if not own_usage["usageReported"]:
333
+ result = descendants
334
+ elif is_compatibility_wrapper(own_usage, descendants):
335
+ result = descendants
336
+ for key in usage_fields:
337
+ if own_usage[key] is not None:
338
+ result[key] = own_usage[key]
339
+ result["usageCompleteness"][key] = True
340
+ else:
341
+ result = combine((billed_usage(own_usage), descendants))
342
+ memo[span_id] = result
343
+ return result
344
+
345
+ roots = [
346
+ span_id
347
+ for span_id in usage_by_id
348
+ if not parent_by_id[span_id] or parent_by_id[span_id] not in usage_by_id
349
+ ]
350
+ aggregate = combine(aggregate_subtree(span_id) for span_id in roots)
351
+ for span_id in usage_by_id:
352
+ if span_id not in memo:
353
+ aggregate = combine((aggregate, aggregate_subtree(span_id)))
354
+ aggregate = combine((aggregate, *(billed_usage(usage) for usage in anonymous_usages)))
355
+ if aggregate["billingSpanCount"] == 0:
356
+ return _usage_from_attributes({})
357
+ if not aggregate["usageCompleteness"]["totalTokens"]:
358
+ aggregate["totalTokens"] = None
359
+ aggregate.pop("billingSpanCount", None)
360
+ return aggregate
361
+
362
+
363
+ def _merge_usage(primary: dict[str, Any], fallback: dict[str, Any]) -> dict[str, Any]:
364
+ """Keep explicit root counters and fill only absent fields from leaf usage."""
365
+
366
+ merged: dict[str, Any] = {
367
+ key: primary[key] if primary[key] is not None else fallback[key]
368
+ for key in (
369
+ "inputTokens",
370
+ "outputTokens",
371
+ "cachedInputTokens",
372
+ "reasoningOutputTokens",
373
+ )
374
+ }
375
+ completeness = {
376
+ key: (
377
+ primary["usageCompleteness"][key]
378
+ if primary[key] is not None
379
+ else fallback["usageCompleteness"][key]
380
+ )
381
+ for key in (
382
+ "inputTokens",
383
+ "outputTokens",
384
+ "cachedInputTokens",
385
+ "reasoningOutputTokens",
386
+ )
387
+ }
388
+ if primary["totalTokens"] is not None:
389
+ merged["totalTokens"] = primary["totalTokens"]
390
+ completeness["totalTokens"] = primary["usageCompleteness"]["totalTokens"]
391
+ elif (
392
+ merged["inputTokens"] is not None
393
+ and merged["outputTokens"] is not None
394
+ and completeness["inputTokens"]
395
+ and completeness["outputTokens"]
396
+ ):
397
+ merged["totalTokens"] = merged["inputTokens"] + merged["outputTokens"]
398
+ completeness["totalTokens"] = True
399
+ elif primary["inputTokens"] is None and primary["outputTokens"] is None:
400
+ merged["totalTokens"] = (
401
+ fallback["totalTokens"] if fallback["usageCompleteness"]["totalTokens"] else None
402
+ )
403
+ completeness["totalTokens"] = fallback["usageCompleteness"]["totalTokens"]
404
+ else:
405
+ merged["totalTokens"] = None
406
+ completeness["totalTokens"] = False
407
+ reported = primary["usageReported"] or fallback["usageReported"]
408
+ return {
409
+ **merged,
410
+ "usageCompleteness": completeness,
411
+ "usageReported": reported,
412
+ "usageSource": primary["usageSource"] or fallback["usageSource"],
413
+ }
414
+
415
+
152
416
  class OtlpTraceStore:
153
417
  """Persist one canonical OTLP JSON document per local Trace."""
154
418
 
@@ -188,37 +452,15 @@ class OtlpTraceStore:
188
452
  root_attributes = _decoded_attributes(root.get("attributes", []))
189
453
  canonical = root["traceId"]
190
454
  duration = root_attributes.get("agentkit.duration.ms")
191
- usage_reported = bool(root_attributes.get("agentkit.usage.reported", False))
455
+ root_usage = _usage_from_attributes(root_attributes)
456
+ leaf_usage = _aggregate_span_usage(
457
+ span for span in spans if span.get("spanId") != root.get("spanId")
458
+ )
459
+ usage = _merge_usage(root_usage, leaf_usage)
192
460
  metrics = {
193
461
  "durationMs": int(duration) if duration is not None else None,
194
462
  "durationSource": root_attributes.get("agentkit.duration.source"),
195
- "inputTokens": (
196
- int(root_attributes["gen_ai.usage.input_tokens"])
197
- if usage_reported and "gen_ai.usage.input_tokens" in root_attributes
198
- else None
199
- ),
200
- "outputTokens": (
201
- int(root_attributes["gen_ai.usage.output_tokens"])
202
- if usage_reported and "gen_ai.usage.output_tokens" in root_attributes
203
- else None
204
- ),
205
- "totalTokens": (
206
- int(root_attributes["agentkit.usage.total_tokens"])
207
- if usage_reported and "agentkit.usage.total_tokens" in root_attributes
208
- else None
209
- ),
210
- "cachedInputTokens": (
211
- int(root_attributes.get("gen_ai.usage.cached_input_tokens", 0))
212
- if usage_reported
213
- else None
214
- ),
215
- "reasoningOutputTokens": (
216
- int(root_attributes.get("gen_ai.usage.reasoning_tokens", 0))
217
- if usage_reported
218
- else None
219
- ),
220
- "usageReported": usage_reported,
221
- "usageSource": root_attributes.get("agentkit.usage.source"),
463
+ **usage,
222
464
  }
223
465
  first_resource = (raw.get("resourceSpans") or [{}])[0]
224
466
  first_scope = (first_resource.get("scopeSpans") or [{}])[0]
@@ -343,6 +585,7 @@ class OtlpTraceStore:
343
585
  "outputTokens": metrics["outputTokens"],
344
586
  "totalTokens": metrics["totalTokens"],
345
587
  "usageReported": metrics["usageReported"],
588
+ "usageCompleteness": metrics["usageCompleteness"],
346
589
  "spanCount": len(view["spans"]),
347
590
  "target": view["target"],
348
591
  }
@@ -451,11 +694,13 @@ class OtlpTraceStore:
451
694
  root_end = (
452
695
  root_start + record.duration_ms * 1_000_000
453
696
  if record.duration_ms is not None
454
- else _unix_nano(record.completed_at)
455
- if record.completed_at is not None
456
- else _unix_nano(events[-1].created_at)
457
- if events
458
- else root_start
697
+ else (
698
+ _unix_nano(record.completed_at)
699
+ if record.completed_at is not None
700
+ else _unix_nano(events[-1].created_at)
701
+ if events
702
+ else root_start
703
+ )
459
704
  )
460
705
  run_status = _enum_string(record.status)
461
706
  root_attributes: dict[str, Any] = {
@@ -546,15 +791,76 @@ class OtlpTraceStore:
546
791
  "tool.requested",
547
792
  "tool.completed",
548
793
  }
549
- return [
550
- {
551
- "timeUnixNano": str(_unix_nano(event.created_at)),
552
- "name": event.type,
553
- "attributes": _attributes(_safe_event_attributes(event.data)),
554
- }
555
- for event in events
556
- if event.type not in excluded
557
- ]
794
+ content_families = {
795
+ "thinking.delta": "thinking",
796
+ "thinking.completed": "thinking",
797
+ "message.delta": "message",
798
+ "message.completed": "message",
799
+ }
800
+ projected: list[dict[str, Any]] = []
801
+ content_groups: dict[tuple[str, str, str, str], list[RunEvent]] = {}
802
+ current_turn = ""
803
+ current_step = ""
804
+
805
+ for event in events:
806
+ runtime_event = event.data.get("runtimeEvent")
807
+ correlation = runtime_event if isinstance(runtime_event, dict) else {}
808
+ turn_id = str(correlation.get("turn_id") or "")
809
+ step_id = str(correlation.get("step_id") or "")
810
+ if turn_id:
811
+ current_turn = turn_id
812
+ if step_id and event.type in {"step.started", "model.call.begin"}:
813
+ current_step = step_id
814
+
815
+ family = content_families.get(event.type)
816
+ if family:
817
+ key = (
818
+ event.run_id,
819
+ turn_id or current_turn,
820
+ step_id or current_step,
821
+ family,
822
+ )
823
+ content_groups.setdefault(key, []).append(event)
824
+ continue
825
+ if event.type in excluded:
826
+ continue
827
+ projected.append(
828
+ {
829
+ "timeUnixNano": str(_unix_nano(event.created_at)),
830
+ "name": event.type,
831
+ "attributes": _attributes(_safe_event_attributes(event.data)),
832
+ }
833
+ )
834
+
835
+ for grouped in content_groups.values():
836
+ completed = next(
837
+ (event for event in reversed(grouped) if event.type.endswith(".completed")),
838
+ None,
839
+ )
840
+ source = completed or grouped[-1]
841
+ completed_text = source.data.get("text") if completed is not None else None
842
+ text = (
843
+ completed_text
844
+ if isinstance(completed_text, str) and completed_text
845
+ else "".join(
846
+ str(event.data.get("text") or "")
847
+ for event in grouped
848
+ if event.type.endswith(".delta")
849
+ )
850
+ )
851
+ data = dict(source.data)
852
+ data["text"] = text
853
+ data["delta_count"] = sum(event.type.endswith(".delta") for event in grouped)
854
+ projected.append(
855
+ {
856
+ "timeUnixNano": str(_unix_nano(source.created_at)),
857
+ "name": source.type,
858
+ "attributes": _attributes(_safe_event_attributes(data)),
859
+ }
860
+ )
861
+
862
+ projected.sort(key=lambda event: int(event["timeUnixNano"]))
863
+ return projected
558
864
 
559
865
  def _model_spans(
560
866
  self, trace_id: str, root_id: str, events: list[RunEvent], root_end: int
@@ -0,0 +1,85 @@
1
+ """Studio-side projection helpers for platform-owned PCM memory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ksadk.memory.coordinator import (
8
+ MemoryCoordinator,
9
+ agent_user_scope_id,
10
+ build_search_request,
11
+ recall_to_context_item,
12
+ )
13
+ from ksadk.memory.events import recall_completed, recall_empty, recall_failed
14
+ from ksadk.memory.provider_adapter import adapt_as_memory_provider
15
+ from ksadk.memory.provider_resolver import resolve_memory_provider
16
+
17
+
18
+ def recall_platform_memory(
19
+ *,
20
+ run_id: str,
21
+ session_id: str,
22
+ agent_id: str,
23
+ user_id: str,
24
+ user_input: str,
25
+ request_config: dict[str, Any],
26
+ ) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
27
+ """Recall agent-scoped memory and return its auditable lifecycle event."""
28
+
29
+ if not bool(request_config.get("memory_enabled")) or not bool(
30
+ request_config.get("memory_recall_enabled", True)
31
+ ):
32
+ return None, []
33
+
34
+ provider_ref = str(request_config.get("provider_ref") or "local-default")
35
+ rollout = str(request_config.get("memory_write_rollout") or "enabled")
36
+ try:
37
+ provider = adapt_as_memory_provider(resolve_memory_provider(provider_ref))
38
+ result = MemoryCoordinator(provider).recall(
39
+ build_search_request(
40
+ query=user_input,
41
+ user_id=agent_user_scope_id(agent_id=agent_id, user_id=user_id),
42
+ top_k=int(request_config.get("memory_recall_top_k") or 8),
43
+ max_tokens=int(request_config.get("memory_recall_max_tokens") or 1600),
44
+ min_score=float(request_config.get("memory_recall_min_score") or 0.45),
45
+ )
46
+ )
47
+ context = recall_to_context_item(result)
48
+ if context is not None:
49
+ event = recall_completed(
50
+ run_id=run_id,
51
+ session_id=session_id,
52
+ provider=provider_ref,
53
+ rollout=rollout,
54
+ count=len(result.records),
55
+ )
56
+ elif result.status == "ok":
57
+ event = recall_empty(
58
+ run_id=run_id,
59
+ session_id=session_id,
60
+ provider=provider_ref,
61
+ rollout=rollout,
62
+ )
63
+ else:
64
+ event = recall_failed(
65
+ run_id=run_id,
66
+ session_id=session_id,
67
+ provider=provider_ref,
68
+ rollout=rollout,
69
+ error_code=str(result.error_code or result.status),
70
+ error_message="平台长期记忆召回失败",
71
+ retryable=result.status in {"timeout", "failed"},
72
+ )
73
+ return context, [event.to_dict()]
74
+ except Exception as exc: # noqa: BLE001 - recall failure must not break a run
75
+ return None, [
76
+ recall_failed(
77
+ run_id=run_id,
78
+ session_id=session_id,
79
+ provider=provider_ref,
80
+ rollout=rollout,
81
+ error_code="recall_exception",
82
+ error_message=str(exc)[:200],
83
+ retryable=True,
84
+ ).to_dict()
85
+ ]
@@ -109,7 +109,14 @@ class AgentDraftRepository:
109
109
  raise not_found("agent", agent_id)
110
110
  return self._read(path)
111
111
 
112
- def update(self, agent_id: str, spec: AgentSpec, *, expected_revision: int) -> AgentDraft:
112
+ def update(
113
+ self,
114
+ agent_id: str,
115
+ spec: AgentSpec,
116
+ *,
117
+ expected_revision: int,
118
+ name: str | None = None,
119
+ ) -> AgentDraft:
113
120
  current = self.get(agent_id)
114
121
  if current.metadata.revision != expected_revision:
115
122
  raise StudioError(
@@ -124,6 +131,8 @@ class AgentDraftRepository:
124
131
  )
125
132
  updated = cast(AgentDraft, current.model_copy(deep=True))
126
133
  updated.metadata.revision += 1
134
+ if name is not None:
135
+ updated.metadata = updated.metadata.model_copy(update={"name": name})
127
136
  updated.spec = spec
128
137
  self._write(self._agent_file(agent_id), updated)
129
138
  return updated
@@ -245,7 +254,19 @@ class BuildRepository:
245
254
  continue
246
255
  if record.agent_id == agent_id:
247
256
  records.append(record)
248
- return records
257
+ return sorted(records, key=lambda item: item.created_at, reverse=True)
258
+
259
+ def list(self) -> list[BuildRecord]:
260
+ records: list[BuildRecord] = []
261
+ directory = self.workspace.resolve(".agentkit/builds")
262
+ for path in directory.glob("build_*.json"):
263
+ try:
264
+ records.append(
265
+ BuildRecord.model_validate_json(path.read_text(encoding="utf-8"))
266
+ )
267
+ except (OSError, ValidationError):
268
+ continue
269
+ return sorted(records, key=lambda item: item.created_at, reverse=True)
249
270
 
250
271
  def delete_for_agent(
251
272
  self,