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,10 +4,10 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>AgentKit Studio</title>
7
- <script type="module" crossorigin src="/static/assets/index-DI2eY_NH.js"></script>
7
+ <script type="module" crossorigin src="/static/assets/index-CYekR2Xv.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/static/assets/rolldown-runtime-hePW80VL.js">
9
9
  <link rel="modulepreload" crossorigin href="/static/assets/react-vendor-BNmTiJ-I.js">
10
- <link rel="stylesheet" crossorigin href="/static/assets/index-CaEN0eTR.css">
10
+ <link rel="stylesheet" crossorigin href="/static/assets/index-BLpcdrgW.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
ksadk/studio/templates.py CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from collections.abc import Iterable
6
7
 
7
8
  import yaml # type: ignore[import-untyped]
8
9
 
9
10
  from ksadk.studio.contracts import (
11
+ AgentBehaviorDesign,
10
12
  AgentBindings,
11
13
  AgentSpec,
12
14
  AgentTemplateComposeRequest,
@@ -26,6 +28,87 @@ from ksadk.studio.workspace import Workspace
26
28
  RESEARCH_SKILL_NAME = "deep-research-methodology"
27
29
  RESEARCH_SKILL_VERSION = "1.0.0"
28
30
 
31
+
32
+ def _explicit_boundaries(goal: str) -> list[str]:
33
+ clauses = [item.strip() for item in re.split(r"[。;;\n]+", goal) if item.strip()]
34
+ boundary_markers = (
35
+ "必须",
36
+ "不得",
37
+ "不要",
38
+ "禁止",
39
+ "仅限",
40
+ "只能",
41
+ "确认",
42
+ "审批",
43
+ "敏感",
44
+ "隐私",
45
+ )
46
+ return [
47
+ clause
48
+ for clause in clauses
49
+ if any(marker in clause for marker in boundary_markers)
50
+ ][:4]
51
+
52
+
53
+ def _blank_behavior_design(goal: str, policy_template: str) -> AgentBehaviorDesign:
54
+ primary_goal = next(
55
+ (item.strip() for item in re.split(r"[。;;\n]+", goal) if item.strip()),
56
+ goal,
57
+ )
58
+ explicit = _explicit_boundaries(goal)
59
+ safety = list(explicit)
60
+ policy_boundary = (
61
+ "执行外部写入、生产变更或其他高风险操作前,说明影响并取得用户确认。"
62
+ if policy_template == "strict"
63
+ else "调用工具和外部服务时遵守当前权限策略,不宣称未实际完成的操作。"
64
+ )
65
+ if policy_boundary not in safety:
66
+ safety.append(policy_boundary)
67
+ return AgentBehaviorDesign(
68
+ role=f"以“{primary_goal[:80]}”为核心职责的智能助手",
69
+ objective=goal,
70
+ operating_principles=[
71
+ "优先理解当前目标、已知事实和成功标准,不把假设表述为事实。",
72
+ "信息不足或存在关键歧义时,先提出最少必要问题。",
73
+ "只在确有需要时调用已绑定能力,并检查工具输入与返回结果。",
74
+ ],
75
+ workflow=[
76
+ "识别本次请求、约束条件和期望结果",
77
+ "检查信息是否充分并澄清关键缺口",
78
+ "制定并执行必要步骤,使用可用 Tool、MCP 或 Skill",
79
+ "核对结果、说明限制并按约定结构交付",
80
+ ],
81
+ explicit_boundaries=explicit,
82
+ safety_boundaries=safety,
83
+ output_expectations=[
84
+ "回答准确、清晰、可执行,并区分事实、判断和建议。",
85
+ "涉及风险、假设或未完成事项时明确说明,不伪造执行结果。",
86
+ ],
87
+ source_notes=[
88
+ "用户输入:核心目标和明确提出的边界",
89
+ "模板补充:通用执行流程、澄清原则和交付要求",
90
+ "平台策略:工具权限与高风险操作边界",
91
+ ],
92
+ )
93
+
94
+
95
+ def _blank_system_prompt(design: AgentBehaviorDesign) -> str:
96
+ principles = "\n".join(f"- {item}" for item in design.operating_principles)
97
+ boundaries = "\n".join(f"- {item}" for item in design.safety_boundaries)
98
+ outputs = "\n".join(f"- {item}" for item in design.output_expectations)
99
+ return (
100
+ f"# 角色\n{design.role}\n\n"
101
+ f"# 核心目标\n{design.objective}\n\n"
102
+ f"# 工作原则\n{principles}\n\n"
103
+ f"# 安全与边界\n{boundaries}\n\n"
104
+ f"# 回答要求\n{outputs}"
105
+ )
106
+
107
+
108
+ def _blank_task_prompt(design: AgentBehaviorDesign) -> str:
109
+ workflow = "\n".join(f"{index}. {item}" for index, item in enumerate(design.workflow, 1))
110
+ return f"每次收到请求时遵循以下执行契约:\n{workflow}"
111
+
29
112
  _RESEARCH_SKILL_MANIFEST = {
30
113
  "name": RESEARCH_SKILL_NAME,
31
114
  "displayName": "深度调研方法",
@@ -180,7 +263,10 @@ def compose_blank_agent(
180
263
  skills = [item for item in resources if item.kind == "skill"]
181
264
  mcps = [item for item in resources if item.kind == "mcp"]
182
265
 
183
- model = _select_model(models, request.model_profile_id)
266
+ requested_primary_model = request.model_profile_id or next(
267
+ iter(request.model_profile_ids), None
268
+ )
269
+ model = _select_model(models, requested_primary_model)
184
270
  selected_model_ids = _select_model_ids(
185
271
  models, request.model_profile_id, request.model_profile_ids
186
272
  )
@@ -199,12 +285,14 @@ def compose_blank_agent(
199
285
  request.mcp_resource_ids,
200
286
  kind="mcp",
201
287
  )
202
- system_prompt = request.prompt.strip() or request.goal.strip()
203
- task_prompt = request.task_prompt.strip() or (
204
- "理解用户目标并给出准确、可执行的回答。调用 Tool 前检查输入,"
205
- "对外部操作遵守权限策略;信息不足时说明假设并请求必要补充。"
206
- )
207
- description = request.description.strip() or system_prompt[:160]
288
+ goal = request.goal.strip() or request.prompt.strip()
289
+ behavior_design = _blank_behavior_design(goal, request.policy_template)
290
+ # Explicit ``prompt`` is a compatibility path for callers that already own the
291
+ # final system instructions. Studio quick-create sends ``goal`` and therefore
292
+ # receives the compiled, reviewable behavior contract below.
293
+ system_prompt = request.prompt.strip() or _blank_system_prompt(behavior_design)
294
+ task_prompt = request.task_prompt.strip() or _blank_task_prompt(behavior_design)
295
+ description = request.description.strip() or goal[:160]
208
296
  bindings = AgentBindings(
209
297
  model_profile_id=model.resource_id,
210
298
  model_profile_ids=selected_model_ids,
@@ -261,6 +349,7 @@ def compose_blank_agent(
261
349
  return AgentTemplateComposition(
262
350
  template_id="blank",
263
351
  spec=spec,
352
+ behavior_design=behavior_design,
264
353
  recommendations=recommendations,
265
354
  warnings=[],
266
355
  )
@@ -278,7 +367,10 @@ def compose_research_agent(
278
367
  skills = [item for item in resources if item.kind == "skill"]
279
368
  mcps = [item for item in resources if item.kind == "mcp"]
280
369
 
281
- model = _select_model(models, request.model_profile_id)
370
+ requested_primary_model = request.model_profile_id or next(
371
+ iter(request.model_profile_ids), None
372
+ )
373
+ model = _select_model(models, requested_primary_model)
282
374
  selected_model_ids = _select_model_ids(
283
375
  models, request.model_profile_id, request.model_profile_ids
284
376
  )
ksadk/studio/validator.py CHANGED
@@ -213,6 +213,25 @@ class AgentValidator:
213
213
  )
214
214
  )
215
215
 
216
+ # 方案 §5.2 / §6.1:ownership 必须与 runtime capability 兼容,不支持组合时报错不静默降级。
217
+ runtime_type = (
218
+ draft.spec.runtime.type if draft.spec.runtime
219
+ else draft.metadata.labels.get("agentkit.ksyun.com/framework", "")
220
+ )
221
+ ownership = draft.spec.context.ownership
222
+ try:
223
+ from ksadk.context_engine.capabilities import validate_ownership_for_runtime
224
+ validate_ownership_for_runtime(ownership, runtime_type=runtime_type)
225
+ except ValueError as exc:
226
+ diagnostics.append(
227
+ self._error(
228
+ "OWNERSHIP_CAPABILITY_MISMATCH",
229
+ str(exc),
230
+ "spec.context.ownership",
231
+ hint="按 Runtime capability 选择 ownership,不支持组合时 Build 会拒绝",
232
+ )
233
+ )
234
+
216
235
  @staticmethod
217
236
  def _error(
218
237
  code: str,
ksadk/studio/workspace.py CHANGED
@@ -15,16 +15,6 @@ class Workspace:
15
15
  def __init__(self, root: Path | str) -> None:
16
16
  self.root = Path(root).expanduser().resolve()
17
17
 
18
- def matches_configured_root_path(self, path: str) -> bool:
19
- """Return whether a no-op workspace-open request names this exact root.
20
-
21
- The local Studio daemon is intentionally bound to one workspace for its
22
- lifetime. Comparing the advertised canonical string avoids resolving
23
- a client-supplied pathname in the workspace-open endpoint.
24
- """
25
-
26
- return path == os.fspath(self.root)
27
-
28
18
  def initialize(self) -> None:
29
19
  self.root.mkdir(parents=True, exist_ok=True)
30
20
  for relative in (
@@ -67,28 +57,36 @@ class Workspace:
67
57
  )
68
58
 
69
59
  def resolve(self, relative: Path | str, *, must_exist: bool = False) -> Path:
70
- root_path = os.path.realpath(os.fspath(self.root))
71
- raw_path = os.fspath(relative)
72
- requested_path = (
73
- raw_path if os.path.isabs(raw_path) else os.path.join(root_path, raw_path)
74
- )
75
- candidate_path = os.path.realpath(requested_path)
76
- if candidate_path == root_path:
77
- candidate = self.root
78
- else:
79
- root_prefix = root_path if root_path.endswith(os.sep) else f"{root_path}{os.sep}"
80
- # `realpath` resolves traversal and symlinks before this segment-boundary check.
81
- if candidate_path.startswith(root_prefix):
82
- candidate = Path(candidate_path)
83
- else:
84
- raise StudioError(
85
- "WORKSPACE_PATH_FORBIDDEN",
86
- "路径不在当前工作区内",
87
- status_code=403,
88
- details={"path": str(relative)},
89
- )
90
- if must_exist:
91
- candidate.stat()
60
+ raw = Path(relative)
61
+ # Reject lexical traversal before asking the filesystem to resolve any
62
+ # symlink. The real-path prefix check below also catches a path that is
63
+ # lexically inside the workspace but escapes through a symlink.
64
+ if ".." in raw.parts:
65
+ raise StudioError(
66
+ "WORKSPACE_PATH_FORBIDDEN",
67
+ "路径不在当前工作区内",
68
+ status_code=403,
69
+ details={"path": str(relative)},
70
+ )
71
+
72
+ root_text = os.path.realpath(os.fspath(self.root))
73
+ candidate_input = raw if raw.is_absolute() else self.root / raw
74
+ candidate_text = os.path.realpath(os.fspath(candidate_input))
75
+ if candidate_text == root_text:
76
+ if must_exist and not self.root.exists():
77
+ raise FileNotFoundError(self.root)
78
+ return self.root
79
+ root_prefix = root_text.rstrip(os.sep) + os.sep
80
+ if not candidate_text.startswith(root_prefix):
81
+ raise StudioError(
82
+ "WORKSPACE_PATH_FORBIDDEN",
83
+ "路径不在当前工作区内",
84
+ status_code=403,
85
+ details={"path": str(relative)},
86
+ )
87
+ candidate = Path(candidate_text)
88
+ if must_exist and not candidate.exists():
89
+ raise FileNotFoundError(candidate)
92
90
  return candidate
93
91
 
94
92
  def relative(self, path: Path | str) -> str:
ksadk/tracing/setup.py CHANGED
@@ -2,25 +2,48 @@
2
2
  Tracing 初始化 - 标准 OTLP 双写(Langfuse 主 + CloudMonitor 次)
3
3
 
4
4
  每个 Python Agent 进程一个 TracerProvider,挂两个 OTLP/HTTP protobuf BatchSpanProcessor,
5
- 消费同一批 ReadableSpan。不在 exporter 阶段按目的地改写 span。
5
+ 消费同一批 ReadableSpan。仅为 CloudMonitor 克隆并补充兼容属性,不修改原始 span。
6
6
 
7
7
  支持 ADK 自动插桩 via openinference-instrumentation-google-adk
8
8
  """
9
9
 
10
10
  import atexit
11
11
  import importlib
12
+ import json
12
13
  import logging
13
14
  import os
15
+ from collections.abc import Callable, Sequence
14
16
  from dataclasses import dataclass
15
17
  from typing import Any, Optional
16
18
  from urllib.parse import unquote
17
19
 
20
+ from opentelemetry.sdk.resources import Resource
21
+ from opentelemetry.sdk.trace import ReadableSpan
18
22
  from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
19
23
 
20
24
  from ksadk.tracing.exporters.inmemory_exporter import InMemoryExporter
21
25
 
22
26
  logger = logging.getLogger(__name__)
23
27
 
28
+ _CLOUD_MONITOR_RESOURCE_SPAN_ATTRIBUTES = (
29
+ "agentengine.account_id",
30
+ "agentengine.agent_id",
31
+ "agentengine.framework",
32
+ "agentengine.langfuse_project_id",
33
+ "agentengine.region",
34
+ )
35
+
36
+ _LANGFUSE_OBSERVATION_TYPES = {
37
+ "LLM": "generation",
38
+ "EMBEDDING": "embedding",
39
+ "CHAIN": "chain",
40
+ "AGENT": "agent",
41
+ "TOOL": "tool",
42
+ "RETRIEVER": "retriever",
43
+ "EVALUATOR": "evaluator",
44
+ "GUARDRAIL": "guardrail",
45
+ }
46
+
24
47
 
25
48
  def _batch_span_processor_kwargs() -> dict:
26
49
  """BatchSpanProcessor 参数:限制单次 export 体积,避免 OTLP collector 413。
@@ -66,14 +89,16 @@ class _LoggingSpanExporter(SpanExporter):
66
89
  endpoint: str,
67
90
  service_name: str,
68
91
  header_keys: list[str],
92
+ span_transform: Callable[[Sequence[ReadableSpan]], Sequence[ReadableSpan]] | None = None,
69
93
  ):
70
94
  self._exporter = exporter
71
95
  self._name = name
72
96
  self._endpoint = endpoint
73
97
  self._service_name = service_name
74
98
  self._header_keys = header_keys
99
+ self._span_transform = span_transform
75
100
 
76
- def export(self, spans) -> SpanExportResult:
101
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
77
102
  span_count = len(spans)
78
103
  logger.info(
79
104
  "%s export started: endpoint=%s service_name=%s spans=%s headers=%s",
@@ -84,7 +109,8 @@ class _LoggingSpanExporter(SpanExporter):
84
109
  ",".join(self._header_keys),
85
110
  )
86
111
  try:
87
- result = self._exporter.export(spans)
112
+ export_spans = self._span_transform(spans) if self._span_transform else spans
113
+ result = self._exporter.export(export_spans)
88
114
  except Exception:
89
115
  logger.exception(
90
116
  "%s export failed: endpoint=%s service_name=%s spans=%s",
@@ -118,6 +144,280 @@ class _LoggingSpanExporter(SpanExporter):
118
144
  return self._exporter.force_flush(timeout_millis)
119
145
 
120
146
 
147
+ def _coerce_int(value: Any) -> int:
148
+ if value is None or isinstance(value, bool):
149
+ return 0
150
+ try:
151
+ return int(value)
152
+ except (TypeError, ValueError):
153
+ try:
154
+ return int(float(value))
155
+ except (TypeError, ValueError):
156
+ return 0
157
+
158
+
159
+ def _span_context(span: Any) -> Any:
160
+ context = getattr(span, "context", None)
161
+ if context is not None:
162
+ return context
163
+ get_context = getattr(span, "get_span_context", None)
164
+ return get_context() if callable(get_context) else None
165
+
166
+
167
+ def _span_id(span: Any) -> Optional[int]:
168
+ return getattr(_span_context(span), "span_id", None)
169
+
170
+
171
+ def _trace_id(span: Any) -> Optional[int]:
172
+ return getattr(_span_context(span), "trace_id", None)
173
+
174
+
175
+ def _parent_span_id(span: Any) -> Optional[int]:
176
+ return getattr(getattr(span, "parent", None), "span_id", None)
177
+
178
+
179
+ def _span_token_usage(span: Any) -> dict[str, int]:
180
+ attributes = getattr(span, "attributes", None) or {}
181
+ return _token_usage(attributes)
182
+
183
+
184
+ def _token_usage(attributes: dict[str, Any]) -> dict[str, int]:
185
+ input_tokens = _coerce_int(
186
+ attributes.get("gen_ai.usage.input_tokens")
187
+ or attributes.get("llm.usage.prompt_tokens")
188
+ or attributes.get("llm.token_count.prompt")
189
+ )
190
+ output_tokens = _coerce_int(
191
+ attributes.get("gen_ai.usage.output_tokens")
192
+ or attributes.get("llm.usage.completion_tokens")
193
+ or attributes.get("llm.token_count.completion")
194
+ )
195
+ total_tokens = _coerce_int(
196
+ attributes.get("gen_ai.usage.total_tokens")
197
+ or attributes.get("llm.usage.total_tokens")
198
+ or attributes.get("llm.token_count.total")
199
+ )
200
+ return {
201
+ "input": input_tokens,
202
+ "output": output_tokens,
203
+ "total": total_tokens or input_tokens + output_tokens,
204
+ "cache_read": _coerce_int(
205
+ attributes.get("gen_ai.usage.cache_read.input_tokens")
206
+ or attributes.get("llm.usage.cache_read.input_tokens")
207
+ ),
208
+ "reasoning_output": _coerce_int(
209
+ attributes.get("gen_ai.usage.reasoning.output_tokens")
210
+ or attributes.get("llm.usage.reasoning_tokens")
211
+ ),
212
+ }
213
+
214
+
215
+ def _first_span_attribute(attributes: dict[str, Any], *keys: str) -> Any:
216
+ for key in keys:
217
+ value = attributes.get(key)
218
+ if value not in (None, ""):
219
+ return value
220
+ return None
221
+
222
+
223
+ def _add_langfuse_compatibility_attributes(attributes: dict[str, Any]) -> None:
224
+ usage = _token_usage(attributes)
225
+ observation_kind = str(attributes.get("openinference.span.kind") or "").upper()
226
+ observation_type = _LANGFUSE_OBSERVATION_TYPES.get(observation_kind)
227
+ if observation_type is None:
228
+ observation_type = "generation" if _has_nonzero_token_usage(usage) else "span"
229
+ attributes.setdefault("langfuse.observation.type", observation_type)
230
+
231
+ provider = _first_span_attribute(
232
+ attributes,
233
+ "gen_ai.provider.name",
234
+ "gen_ai.system",
235
+ "llm.provider",
236
+ )
237
+ if provider is not None:
238
+ attributes.setdefault("langfuse.observation.metadata.ls_provider", provider)
239
+
240
+ model = _first_span_attribute(
241
+ attributes,
242
+ "gen_ai.request.model",
243
+ "llm.model_name",
244
+ )
245
+ if model is not None:
246
+ attributes.setdefault("langfuse.observation.model.name", model)
247
+ attributes.setdefault("langfuse.observation.metadata.ls_model_name", model)
248
+
249
+ if _has_nonzero_token_usage(usage):
250
+ attributes.setdefault("gen_ai.usage.total_tokens", usage["total"])
251
+ usage_details = {
252
+ "input": usage["input"],
253
+ "output": usage["output"],
254
+ "total": usage["total"],
255
+ "input_cache_read": usage["cache_read"],
256
+ }
257
+ attributes["langfuse.observation.usage_details"] = json.dumps(
258
+ usage_details, separators=(",", ":"), sort_keys=True
259
+ )
260
+
261
+
262
+ def _has_nonzero_token_usage(usage: dict[str, int]) -> bool:
263
+ return any(value > 0 for value in usage.values())
264
+
265
+
266
+ def _clone_cloud_monitor_resource(resource: Resource | None) -> Resource | None:
267
+ if resource is None:
268
+ return None
269
+ attributes = dict(resource.attributes)
270
+ agent_id = str(attributes.get("agentengine.agent_id") or "").strip()
271
+ if not agent_id or attributes.get("service.name") == agent_id:
272
+ return resource
273
+ attributes["service.name"] = agent_id
274
+ return Resource(attributes, schema_url=resource.schema_url)
275
+
276
+
277
+ def _clone_span(
278
+ span: ReadableSpan,
279
+ *,
280
+ attributes: dict[str, Any],
281
+ resource: Resource | None,
282
+ ) -> ReadableSpan:
283
+ return ReadableSpan(
284
+ name=span.name,
285
+ context=span.context,
286
+ parent=span.parent,
287
+ resource=resource,
288
+ attributes=attributes,
289
+ events=span.events,
290
+ links=span.links,
291
+ kind=span.kind,
292
+ status=span.status,
293
+ start_time=span.start_time,
294
+ end_time=span.end_time,
295
+ instrumentation_scope=getattr(span, "instrumentation_scope", None),
296
+ )
297
+
298
+
299
+ def _prepare_cloud_monitor_spans(
300
+ spans: Sequence[ReadableSpan],
301
+ ) -> Sequence[ReadableSpan]:
302
+ """Clone CloudMonitor spans with resource identity and root usage compatibility."""
303
+ if not spans:
304
+ return spans
305
+ span_list = list(spans)
306
+ session_ids_by_trace: dict[Optional[int], Any] = {}
307
+ for span in span_list:
308
+ attributes = dict(getattr(span, "attributes", None) or {})
309
+ session_id = _first_span_attribute(
310
+ attributes,
311
+ "session.id",
312
+ "langfuse.session.id",
313
+ "ksadk.session_id",
314
+ )
315
+ if session_id is not None:
316
+ session_ids_by_trace.setdefault(_trace_id(span), session_id)
317
+ span_keys = {
318
+ (_trace_id(span), _span_id(span)) for span in span_list if _span_id(span) is not None
319
+ }
320
+ children_by_parent: dict[tuple[Optional[int], int], list[ReadableSpan]] = {}
321
+ roots: list[ReadableSpan] = []
322
+ for span in span_list:
323
+ span_id = _span_id(span)
324
+ if span_id is None:
325
+ continue
326
+ trace_id = _trace_id(span)
327
+ parent_id = _parent_span_id(span)
328
+ if parent_id is None or (trace_id, parent_id) not in span_keys:
329
+ roots.append(span)
330
+ else:
331
+ children_by_parent.setdefault((trace_id, parent_id), []).append(span)
332
+
333
+ usage_by_span = {
334
+ (_trace_id(span), _span_id(span)): _span_token_usage(span)
335
+ for span in span_list
336
+ if _span_id(span) is not None
337
+ }
338
+ has_token_descendant_cache: dict[tuple[Optional[int], int], bool] = {}
339
+
340
+ def has_token_descendant(span: ReadableSpan) -> bool:
341
+ key = (_trace_id(span), _span_id(span))
342
+ if key[1] is None:
343
+ return False
344
+ if key in has_token_descendant_cache:
345
+ return has_token_descendant_cache[key]
346
+ result = any(
347
+ _has_nonzero_token_usage(usage_by_span.get((_trace_id(child), _span_id(child)), {}))
348
+ or has_token_descendant(child)
349
+ for child in children_by_parent.get(key, [])
350
+ )
351
+ has_token_descendant_cache[key] = result
352
+ return result
353
+
354
+ def collect_leaf_usage(span: ReadableSpan, aggregate: dict[str, int]) -> None:
355
+ key = (_trace_id(span), _span_id(span))
356
+ own_usage = usage_by_span.get(key, {})
357
+ if _has_nonzero_token_usage(own_usage) and not has_token_descendant(span):
358
+ for usage_key, value in own_usage.items():
359
+ aggregate[usage_key] += value
360
+ return
361
+ for child in children_by_parent.get(key, []):
362
+ collect_leaf_usage(child, aggregate)
363
+
364
+ root_usage: dict[int, dict[str, int]] = {}
365
+ for root in roots:
366
+ aggregate = {
367
+ "input": 0,
368
+ "output": 0,
369
+ "total": 0,
370
+ "cache_read": 0,
371
+ "reasoning_output": 0,
372
+ }
373
+ collect_leaf_usage(root, aggregate)
374
+ root_usage[id(root)] = aggregate
375
+
376
+ resource_cache: dict[int, Resource | None] = {}
377
+ transformed: list[ReadableSpan] = []
378
+ usage_attributes = {
379
+ "gen_ai.usage.input_tokens": "input",
380
+ "gen_ai.usage.output_tokens": "output",
381
+ "gen_ai.usage.total_tokens": "total",
382
+ "gen_ai.usage.cache_read.input_tokens": "cache_read",
383
+ "gen_ai.usage.reasoning.output_tokens": "reasoning_output",
384
+ }
385
+ for span in span_list:
386
+ original_resource = getattr(span, "resource", None)
387
+ original_resource_attributes = getattr(original_resource, "attributes", {}) or {}
388
+ resource_key = id(original_resource)
389
+ if resource_key not in resource_cache:
390
+ resource_cache[resource_key] = _clone_cloud_monitor_resource(original_resource)
391
+ resource = resource_cache[resource_key]
392
+ attributes = dict(getattr(span, "attributes", None) or {})
393
+ resource_attributes = getattr(resource, "attributes", {}) or {}
394
+ for key in _CLOUD_MONITOR_RESOURCE_SPAN_ATTRIBUTES:
395
+ value = resource_attributes.get(key)
396
+ if value not in (None, ""):
397
+ attributes.setdefault(f"gen_ai.{key}", value)
398
+ agent_name = original_resource_attributes.get(
399
+ "agentengine.agent_name"
400
+ ) or original_resource_attributes.get("service.name")
401
+ if agent_name not in (None, ""):
402
+ attributes.setdefault("gen_ai.agentengine.agent_name", agent_name)
403
+ aggregate = root_usage.get(id(span), {})
404
+ for attribute, usage_key in usage_attributes.items():
405
+ if _coerce_int(attributes.get(attribute)) <= 0 and aggregate.get(usage_key, 0) > 0:
406
+ attributes[attribute] = aggregate[usage_key]
407
+
408
+ session_id = session_ids_by_trace.get(_trace_id(span))
409
+ if session_id is not None:
410
+ attributes.setdefault("session.id", session_id)
411
+ _add_langfuse_compatibility_attributes(attributes)
412
+
413
+ original_attributes = dict(getattr(span, "attributes", None) or {})
414
+ if resource is original_resource and attributes == original_attributes:
415
+ transformed.append(span)
416
+ else:
417
+ transformed.append(_clone_span(span, attributes=attributes, resource=resource))
418
+ return transformed
419
+
420
+
121
421
  def _parse_otlp_headers(raw: str) -> dict[str, str]:
122
422
  """Parse OTEL_EXPORTER_OTLP_HEADERS into an HTTP headers dict."""
123
423
  headers: dict[str, str] = {}
@@ -409,6 +709,7 @@ def setup_tracing(
409
709
  endpoint=cloud_monitor_config.endpoint,
410
710
  service_name=cloud_monitor_config.service_name,
411
711
  header_keys=sorted(cloud_monitor_config.headers),
712
+ span_transform=_prepare_cloud_monitor_spans,
412
713
  )
413
714
  _register_span_processor(
414
715
  provider,
ksadk/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """KsADK 版本信息"""
2
2
 
3
- VERSION = "0.8.1"
3
+ VERSION = "0.8.2"
4
4
  __version__ = VERSION