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
@@ -18,7 +18,13 @@ from fastapi import FastAPI, Request
18
18
  from fastapi.responses import JSONResponse, StreamingResponse
19
19
 
20
20
  from .config import _LOOPBACK_HOSTS, ProxyConfig
21
- from .transform import Streamer, UnsupportedToolsError, chat_to_response, responses_to_chat
21
+ from .transform import (
22
+ Streamer,
23
+ UnsupportedToolsError,
24
+ chat_to_response,
25
+ clamp_reasoning_effort,
26
+ responses_to_chat,
27
+ )
22
28
 
23
29
  logger = logging.getLogger(__name__)
24
30
 
@@ -147,7 +153,7 @@ def create_app(config: ProxyConfig) -> FastAPI:
147
153
  try:
148
154
  chat_req, restore_map = responses_to_chat(body)
149
155
  except UnsupportedToolsError:
150
- logger.info("responses request uses unsupported tools")
156
+ logger.info("responses request uses unsupported tools", exc_info=True)
151
157
  return JSONResponse(
152
158
  status_code=400,
153
159
  content={
@@ -159,13 +165,26 @@ def create_app(config: ProxyConfig) -> FastAPI:
159
165
  )
160
166
  # codex 会对内建能力发内部伪模型名(如 auto_review guardian 用 codex-auto-review),
161
167
  # 单上游代理必须落回配置的真实模型,否则上游按未知模型 403。
162
- if config.upstream_model and chat_req.get("model") != config.upstream_model:
168
+ # 真实模型名(codex 端已配置 model= 或 thread 级 model 覆盖)必须原样透传:
169
+ # RunAgent 的 Model 覆盖靠它生效,整体改写会把覆盖吞掉(终验 403 根因)。
170
+ requested_model = str(chat_req.get("model") or "")
171
+ if (
172
+ config.upstream_model
173
+ and requested_model.startswith("codex-")
174
+ and requested_model != config.upstream_model
175
+ ):
163
176
  logger.debug(
164
- "responses model rewrite: %s -> %s",
165
- chat_req.get("model"),
177
+ "responses pseudo model rewrite: %s -> %s",
178
+ requested_model,
166
179
  config.upstream_model,
167
180
  )
168
181
  chat_req["model"] = config.upstream_model
182
+ # 上游模型族的 reasoning_effort 上限不同(qwen3.7 对 xhigh 400),
183
+ # 按改写后的真实上游模型钳位(codex 对自家模型默认发 xhigh)。
184
+ if chat_req.get("reasoning_effort"):
185
+ chat_req["reasoning_effort"] = clamp_reasoning_effort(
186
+ chat_req["model"], chat_req["reasoning_effort"]
187
+ )
169
188
  rid = "resp_" + uuid.uuid4().hex[:24]
170
189
  model = body.get("model")
171
190
  started = time.monotonic()
@@ -198,6 +198,7 @@ def convert_tools(tools):
198
198
  else:
199
199
  t = t or {}
200
200
  import json as _json
201
+
201
202
  try:
202
203
  unsupported.append(_json.dumps(t, ensure_ascii=False)[:300])
203
204
  except Exception:
@@ -224,6 +225,32 @@ def convert_tool_choice(tc):
224
225
  return tc
225
226
 
226
227
 
228
+ # 各上游模型族的 reasoning_effort 上限(qwen3.7 系实测 xhigh 400;报错文案会列出
229
+ # xhigh 但 DashScope 后端实际只认到 high,故不能用报错文案反推)。未列出的族原样透传。
230
+ _EFFORT_RANK = {"none": 0, "minimal": 1, "low": 2, "medium": 3, "high": 4, "xhigh": 5}
231
+ _EFFORT_CAP_PREFIXES = (
232
+ ("qwen3.7", "high"),
233
+ ("qwen3.6", "high"),
234
+ ("qwen3.5", "high"),
235
+ )
236
+
237
+
238
+ def clamp_reasoning_effort(model, effort):
239
+ """把超出上游模型族支持上限的 reasoning_effort 钳到最高合法档。
240
+
241
+ codex 对自家模型默认发 xhigh;qwen3.7 系上游 400,需钳到 high。
242
+ 未知档位/未知模型族原样返回(不做发明式映射)。
243
+ """
244
+ if not isinstance(effort, str) or effort not in _EFFORT_RANK:
245
+ return effort
246
+ for prefix, cap in _EFFORT_CAP_PREFIXES:
247
+ if isinstance(model, str) and model.startswith(prefix):
248
+ if _EFFORT_RANK[effort] > _EFFORT_RANK[cap]:
249
+ return cap
250
+ return effort
251
+ return effort
252
+
253
+
227
254
  def _convert_text_format(fmt):
228
255
  """responses text.format -> chat response_format(structured output 结构重组)。
229
256
 
@@ -243,6 +270,35 @@ def _convert_text_format(fmt):
243
270
  return None
244
271
 
245
272
 
273
+ def _promote_additional_tools(body):
274
+ """Promote Codex Harness dynamic tool input items to Responses tools.
275
+
276
+ Codex 0.147 no longer always sends tool declarations in the top-level
277
+ ``tools`` field. It can prepend one or more developer input items shaped
278
+ as ``{"type": "additional_tools", "tools": [...]}``. Chat Completions
279
+ has no equivalent input item, so the proxy must merge those declarations
280
+ into the canonical Responses tool list before namespace flattening.
281
+ """
282
+
283
+ inp = body.get("input")
284
+ if not isinstance(inp, list):
285
+ return
286
+ promoted = []
287
+ retained = []
288
+ for item in inp:
289
+ if isinstance(item, dict) and item.get("type") == "additional_tools":
290
+ tools = item.get("tools")
291
+ if isinstance(tools, list):
292
+ promoted.extend(tool for tool in tools if isinstance(tool, dict))
293
+ continue
294
+ retained.append(item)
295
+ if not promoted:
296
+ return
297
+ existing = body.get("tools")
298
+ body["tools"] = (list(existing) if isinstance(existing, list) else []) + promoted
299
+ body["input"] = retained
300
+
301
+
246
302
  def responses_to_chat(body):
247
303
  """responses 请求 -> chat 请求;返回 (chat_req, restore_map)。
248
304
 
@@ -251,6 +307,7 @@ def responses_to_chat(body):
251
307
  """
252
308
  from .namespace import flatten_request_namespaces
253
309
 
310
+ _promote_additional_tools(body)
254
311
  restore_map = flatten_request_namespaces(body)
255
312
  out = {"model": body.get("model")}
256
313
  msgs = []
@@ -273,8 +330,9 @@ def responses_to_chat(body):
273
330
  rf = _convert_text_format(text.get("format"))
274
331
  if rf is not None:
275
332
  out["response_format"] = rf
276
- if text.get("verbosity"):
277
- out["verbosity"] = text["verbosity"]
333
+ # text.verbosity("low"/"high")不转发:chat completions 无标准字段,
334
+ # kspmas 把顶层 verbosity 反序列化为 i32,字符串值直接 400(glm-5.2 实测;
335
+ # cc-switch 也不透传该字段)。
278
336
  if body.get("prompt_cache_key"):
279
337
  out["prompt_cache_key"] = body["prompt_cache_key"]
280
338
  tools = convert_tools(body.get("tools"))
@@ -0,0 +1,25 @@
1
+ """Local observability exports and trajectory projections."""
2
+
3
+ from ksadk.observability.session_log import (
4
+ SESSION_LOG_SCHEMA,
5
+ SessionLogError,
6
+ SessionLogResult,
7
+ export_session_log,
8
+ verify_session_log,
9
+ )
10
+ from ksadk.observability.trajectory import (
11
+ PROJECTION_VERSION,
12
+ encode_sse,
13
+ project_trajectory_event,
14
+ )
15
+
16
+ __all__ = [
17
+ "SESSION_LOG_SCHEMA",
18
+ "PROJECTION_VERSION",
19
+ "SessionLogError",
20
+ "SessionLogResult",
21
+ "export_session_log",
22
+ "encode_sse",
23
+ "project_trajectory_event",
24
+ "verify_session_log",
25
+ ]
@@ -0,0 +1,420 @@
1
+ """Versioned, fixed-watermark JSONL exports for canonical RuntimeEvents.
2
+
3
+ New exports use the schema-v2 RuntimeEvent envelope. The legacy v1 log is
4
+ still accepted by :func:`verify_session_log` so existing diagnostic files stay
5
+ readable, but a v2 Store must never be coerced back into a v1 write model just
6
+ to produce an export.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import tempfile
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any, TextIO
17
+
18
+ from ksadk.events.canonical import dump_runtime_event, parse_runtime_event
19
+ from ksadk.events.store import RuntimeEventStore
20
+ from ksadk.events.v1_compat import EventTypeV1, RuntimeEventV1
21
+ from ksadk.sessions.base import BaseSessionService
22
+
23
+ SESSION_LOG_SCHEMA = "ksadk.session-log/v2"
24
+ _SESSION_LOG_VERSION = 2
25
+ _LEGACY_SESSION_LOG_SCHEMA = "ksadk.session-log/v1"
26
+ _LEGACY_SESSION_LOG_VERSION = 1
27
+ _PAGE_SIZE = 500
28
+ _LEGACY_PACKED_EVENT_TYPES = {
29
+ EventTypeV1.TEXT_DELTA: "text-chunks",
30
+ EventTypeV1.REASONING_DELTA: "reasoning-chunks",
31
+ }
32
+ _LEGACY_PACKED_RECORD_TYPES = {value: key for key, value in _LEGACY_PACKED_EVENT_TYPES.items()}
33
+
34
+
35
+ class SessionLogError(ValueError):
36
+ """A stable Session Log export or validation failure."""
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class SessionLogResult:
41
+ path: Path
42
+ event_count: int
43
+ first_seq_id: int | None
44
+ last_seq_id: int | None
45
+ exported_through_seq_id: int | None
46
+
47
+
48
+ def _raise(code: str, message: str) -> None:
49
+ raise SessionLogError(f"{code}: {message}")
50
+
51
+
52
+ def _write_json_line(stream: TextIO, value: dict[str, Any]) -> None:
53
+ stream.write(
54
+ json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) + "\n"
55
+ )
56
+
57
+
58
+ def _legacy_packed_base(event: RuntimeEventV1) -> dict[str, Any]:
59
+ value = event.to_dict()
60
+ for key in ("event_id", "seq_id", "timestamp"):
61
+ value.pop(key)
62
+ payload = dict(value["payload"])
63
+ payload.pop("text")
64
+ value["payload"] = payload
65
+ return value
66
+
67
+
68
+ def _write_legacy_event_run(stream: TextIO, events: list[RuntimeEventV1]) -> None:
69
+ if len(events) < 3:
70
+ for event in events:
71
+ _write_json_line(stream, event.to_dict())
72
+ return
73
+ _write_json_line(
74
+ stream,
75
+ {
76
+ "type": _LEGACY_PACKED_EVENT_TYPES[events[0].event_type],
77
+ "seq0": events[0].seq_id,
78
+ "data": {
79
+ "base": _legacy_packed_base(events[0]),
80
+ "event_ids": [event.event_id for event in events],
81
+ "timestamps": [event.timestamp for event in events],
82
+ "texts": [event.payload["text"] for event in events],
83
+ },
84
+ },
85
+ )
86
+
87
+
88
+ def _same_legacy_event_run(events: list[RuntimeEventV1], event: RuntimeEventV1) -> bool:
89
+ return (
90
+ bool(events)
91
+ and event.event_type == events[0].event_type
92
+ and event.seq_id == events[-1].seq_id + 1
93
+ and _legacy_packed_base(event) == _legacy_packed_base(events[0])
94
+ )
95
+
96
+
97
+ async def export_session_log(
98
+ session_service: BaseSessionService,
99
+ session_id: str,
100
+ target: Path | str,
101
+ *,
102
+ invocation_id: str | None = None,
103
+ ) -> SessionLogResult:
104
+ """Export committed RuntimeEvents through a fixed session cursor."""
105
+ session = await session_service.get_session_metadata(session_id)
106
+ if session is None:
107
+ _raise("SESSION_LOG_SESSION_NOT_FOUND", f"session {session_id!r} not found")
108
+
109
+ target_path = Path(target)
110
+ if target_path.exists():
111
+ _raise("SESSION_LOG_TARGET_EXISTS", f"target {target_path} already exists")
112
+
113
+ store = RuntimeEventStore(session_service)
114
+ tail = await store.list(session_id, limit=1)
115
+ cutoff = tail[-1].seq if tail else None
116
+ header: dict[str, Any] = {
117
+ "type": "session",
118
+ "schema": SESSION_LOG_SCHEMA,
119
+ "version": _SESSION_LOG_VERSION,
120
+ "session_id": session.id,
121
+ "agent_id": session.agent_id,
122
+ "user_id": session.user_id,
123
+ "created_at": session.created_at,
124
+ "updated_at": session.updated_at,
125
+ "exported_through_seq_id": cutoff,
126
+ "event_schema_version": 2,
127
+ }
128
+ if invocation_id is not None:
129
+ header["invocation_id"] = invocation_id
130
+
131
+ temporary_path: Path | None = None
132
+ published = False
133
+ event_count = 0
134
+ first_seq_id: int | None = None
135
+ last_seq_id: int | None = None
136
+ try:
137
+ descriptor, temporary_name = tempfile.mkstemp(
138
+ dir=target_path.parent,
139
+ prefix=".session-log-",
140
+ suffix=".tmp",
141
+ )
142
+ temporary_path = Path(temporary_name)
143
+ os.fchmod(descriptor, 0o600)
144
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
145
+ _write_json_line(stream, header)
146
+ cursor = 0
147
+ while cutoff is not None and cursor < cutoff:
148
+ window_end = min(cursor + _PAGE_SIZE, cutoff)
149
+ events = await store.page(
150
+ session_id,
151
+ after_seq=cursor,
152
+ before_seq=window_end + 1,
153
+ limit=_PAGE_SIZE,
154
+ )
155
+ for event in events:
156
+ if invocation_id is not None and event.run_id != invocation_id:
157
+ continue
158
+ # A v2 fact is the durable source of truth. The v1
159
+ # packed delta format cannot losslessly encode all v2
160
+ # item operations, so v2 logs retain one canonical event
161
+ # per row instead of silently projecting/dropping facts.
162
+ _write_json_line(stream, dump_runtime_event(event))
163
+ event_count += 1
164
+ first_seq_id = first_seq_id or event.seq
165
+ last_seq_id = event.seq
166
+ cursor = window_end
167
+ stream.flush()
168
+ os.fsync(stream.fileno())
169
+
170
+ try:
171
+ os.link(temporary_path, target_path)
172
+ published = True
173
+ except FileExistsError as exc:
174
+ raise SessionLogError(
175
+ f"SESSION_LOG_TARGET_EXISTS: target {target_path} already exists"
176
+ ) from exc
177
+ except OSError as exc:
178
+ raise SessionLogError(
179
+ "SESSION_LOG_ATOMIC_PUBLISH_UNSUPPORTED: "
180
+ f"cannot atomically publish {target_path}"
181
+ ) from exc
182
+
183
+ directory_fd = os.open(target_path.parent, os.O_RDONLY)
184
+ try:
185
+ os.fsync(directory_fd)
186
+ finally:
187
+ os.close(directory_fd)
188
+ except SessionLogError:
189
+ if published:
190
+ target_path.unlink(missing_ok=True)
191
+ raise
192
+ except OSError as exc:
193
+ if published:
194
+ target_path.unlink(missing_ok=True)
195
+ raise SessionLogError(f"SESSION_LOG_WRITE_FAILED: {target_path}") from exc
196
+ finally:
197
+ if temporary_path is not None:
198
+ temporary_path.unlink(missing_ok=True)
199
+
200
+ return SessionLogResult(
201
+ path=target_path,
202
+ event_count=event_count,
203
+ first_seq_id=first_seq_id,
204
+ last_seq_id=last_seq_id,
205
+ exported_through_seq_id=cutoff,
206
+ )
207
+
208
+
209
+ def _read_json_line(raw: str, line_number: int) -> dict[str, Any]:
210
+ try:
211
+ value = json.loads(raw)
212
+ except json.JSONDecodeError as exc:
213
+ raise SessionLogError(f"SESSION_LOG_INVALID: line {line_number} is not valid JSON") from exc
214
+ if not isinstance(value, dict):
215
+ _raise("SESSION_LOG_INVALID", f"line {line_number} must be an object")
216
+ return value
217
+
218
+
219
+ def _legacy_events_from_record(
220
+ value: dict[str, Any], line_number: int, *, allow_packed: bool
221
+ ) -> list[RuntimeEventV1]:
222
+ record_type = value.get("type")
223
+ if record_type not in _LEGACY_PACKED_RECORD_TYPES:
224
+ try:
225
+ return [RuntimeEventV1.from_dict(value)]
226
+ except (TypeError, ValueError) as exc:
227
+ raise SessionLogError(
228
+ f"SESSION_LOG_INVALID: line {line_number} is not a RuntimeEvent"
229
+ ) from exc
230
+ if not allow_packed:
231
+ _raise("SESSION_LOG_INVALID", f"line {line_number} uses packed rows in v1")
232
+
233
+ seq0 = value.get("seq0")
234
+ data = value.get("data")
235
+ if isinstance(seq0, bool) or not isinstance(seq0, int) or seq0 < 0:
236
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed seq0 is invalid")
237
+ if not isinstance(data, dict) or not isinstance(data.get("base"), dict):
238
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed data is invalid")
239
+ event_ids = data.get("event_ids")
240
+ timestamps = data.get("timestamps")
241
+ texts = data.get("texts")
242
+ if not all(isinstance(items, list) for items in (event_ids, timestamps, texts)):
243
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed arrays are invalid")
244
+ if len(event_ids) < 3 or len(event_ids) != len(timestamps) or len(event_ids) != len(texts):
245
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed arrays do not align")
246
+
247
+ base = dict(data["base"])
248
+ expected_event_type = _LEGACY_PACKED_RECORD_TYPES[record_type]
249
+ if base.get("event_type") != expected_event_type:
250
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed event type does not match")
251
+ payload = base.get("payload")
252
+ if not isinstance(payload, dict) or "text" in payload:
253
+ _raise("SESSION_LOG_INVALID", f"line {line_number} packed payload is invalid")
254
+
255
+ events: list[RuntimeEventV1] = []
256
+ for index, (event_id, timestamp, text) in enumerate(
257
+ zip(event_ids, timestamps, texts, strict=True)
258
+ ):
259
+ value = {
260
+ **base,
261
+ "event_id": event_id,
262
+ "seq_id": seq0 + index,
263
+ "timestamp": timestamp,
264
+ "payload": {**payload, "text": text},
265
+ }
266
+ try:
267
+ events.append(RuntimeEventV1.from_dict(value))
268
+ except (TypeError, ValueError) as exc:
269
+ raise SessionLogError(
270
+ f"SESSION_LOG_INVALID: line {line_number} contains an invalid packed event"
271
+ ) from exc
272
+ return events
273
+
274
+
275
+ def _validate_header(
276
+ header: dict[str, Any], *, schema: str, version: int
277
+ ) -> tuple[int | None, str | None]:
278
+ if header.get("type") != "session":
279
+ _raise("SESSION_LOG_INVALID", "first line must be a session header")
280
+ if header.get("schema") != schema or header.get("version") != version:
281
+ _raise("SESSION_LOG_INVALID", "unsupported schema")
282
+ session_id = header.get("session_id")
283
+ if not isinstance(session_id, str) or not session_id:
284
+ _raise("SESSION_LOG_INVALID", "header session id is required")
285
+ cutoff = header.get("exported_through_seq_id")
286
+ if cutoff is not None and (
287
+ isinstance(cutoff, bool) or not isinstance(cutoff, int) or cutoff < 0
288
+ ):
289
+ _raise("SESSION_LOG_INVALID", "exported watermark must be null or non-negative")
290
+ return cutoff, header.get("invocation_id")
291
+
292
+
293
+ def _finish_verification(
294
+ *,
295
+ source: Path,
296
+ event_count: int,
297
+ first_seq_id: int | None,
298
+ last_seq_id: int | None,
299
+ cutoff: int | None,
300
+ filtered: bool,
301
+ ) -> SessionLogResult:
302
+ if not filtered:
303
+ if cutoff is None and event_count:
304
+ _raise("SESSION_LOG_INVALID", "empty watermark cannot contain events")
305
+ if cutoff is not None and last_seq_id != cutoff:
306
+ _raise("SESSION_LOG_INVALID", "full session must end at exported watermark")
307
+ return SessionLogResult(
308
+ path=source,
309
+ event_count=event_count,
310
+ first_seq_id=first_seq_id,
311
+ last_seq_id=last_seq_id,
312
+ exported_through_seq_id=cutoff,
313
+ )
314
+
315
+
316
+ def _verify_v2(stream: TextIO, *, source: Path, header: dict[str, Any]) -> SessionLogResult:
317
+ cutoff, invocation_id = _validate_header(
318
+ header, schema=SESSION_LOG_SCHEMA, version=_SESSION_LOG_VERSION
319
+ )
320
+ filtered = invocation_id is not None
321
+ event_count = 0
322
+ first_seq_id: int | None = None
323
+ last_seq_id: int | None = None
324
+ for line_number, raw in enumerate(stream, start=2):
325
+ if not raw.strip():
326
+ _raise("SESSION_LOG_INVALID", f"line {line_number} is empty")
327
+ try:
328
+ event = parse_runtime_event(_read_json_line(raw, line_number))
329
+ except (TypeError, ValueError) as exc:
330
+ raise SessionLogError(
331
+ f"SESSION_LOG_INVALID: line {line_number} is not a RuntimeEvent/v2"
332
+ ) from exc
333
+ if filtered and event.run_id != invocation_id:
334
+ _raise("SESSION_LOG_INVALID", f"line {line_number} run id does not match")
335
+ if last_seq_id is not None and event.seq <= last_seq_id:
336
+ _raise("SESSION_LOG_INVALID", "event seq must be strictly increasing")
337
+ if cutoff is None or event.seq > cutoff:
338
+ _raise("SESSION_LOG_INVALID", "event seq exceeds exported watermark")
339
+ if not filtered:
340
+ expected = 1 if last_seq_id is None else last_seq_id + 1
341
+ if event.seq != expected:
342
+ _raise("SESSION_LOG_INVALID", "full session seq must be continuous")
343
+ event_count += 1
344
+ first_seq_id = first_seq_id or event.seq
345
+ last_seq_id = event.seq
346
+ return _finish_verification(
347
+ source=source,
348
+ event_count=event_count,
349
+ first_seq_id=first_seq_id,
350
+ last_seq_id=last_seq_id,
351
+ cutoff=cutoff,
352
+ filtered=filtered,
353
+ )
354
+
355
+
356
+ def _verify_v1(stream: TextIO, *, source: Path, header: dict[str, Any]) -> SessionLogResult:
357
+ cutoff, invocation_id = _validate_header(
358
+ header, schema=_LEGACY_SESSION_LOG_SCHEMA, version=_LEGACY_SESSION_LOG_VERSION
359
+ )
360
+ session_id = str(header["session_id"])
361
+ filtered = invocation_id is not None
362
+ event_count = 0
363
+ first_seq_id: int | None = None
364
+ last_seq_id: int | None = None
365
+ for line_number, raw in enumerate(stream, start=2):
366
+ if not raw.strip():
367
+ _raise("SESSION_LOG_INVALID", f"line {line_number} is empty")
368
+ value = _read_json_line(raw, line_number)
369
+ for event in _legacy_events_from_record(value, line_number, allow_packed=True):
370
+ if event.session_id != session_id:
371
+ _raise("SESSION_LOG_INVALID", f"line {line_number} session id does not match")
372
+ if filtered and event.invocation_id != invocation_id:
373
+ _raise("SESSION_LOG_INVALID", f"line {line_number} invocation id does not match")
374
+ if last_seq_id is not None and event.seq_id <= last_seq_id:
375
+ _raise("SESSION_LOG_INVALID", "event seq_id must be strictly increasing")
376
+ if cutoff is None or event.seq_id > cutoff:
377
+ _raise("SESSION_LOG_INVALID", "event seq_id exceeds exported watermark")
378
+ if not filtered:
379
+ expected = 1 if last_seq_id is None else last_seq_id + 1
380
+ if event.seq_id != expected:
381
+ _raise("SESSION_LOG_INVALID", "full session seq_id must be continuous")
382
+ event_count += 1
383
+ first_seq_id = first_seq_id or event.seq_id
384
+ last_seq_id = event.seq_id
385
+ return _finish_verification(
386
+ source=source,
387
+ event_count=event_count,
388
+ first_seq_id=first_seq_id,
389
+ last_seq_id=last_seq_id,
390
+ cutoff=cutoff,
391
+ filtered=filtered,
392
+ )
393
+
394
+
395
+ def verify_session_log(path: Path | str) -> SessionLogResult:
396
+ """Stream and validate a v2 Session Log or a legacy v1 diagnostic file."""
397
+ source = Path(path)
398
+ try:
399
+ stream = source.open(encoding="utf-8")
400
+ except OSError as exc:
401
+ raise SessionLogError(f"SESSION_LOG_READ_FAILED: {source}") from exc
402
+ with stream:
403
+ first_line = stream.readline()
404
+ if not first_line:
405
+ _raise("SESSION_LOG_INVALID", "missing session header")
406
+ header = _read_json_line(first_line, 1)
407
+ if header.get("schema") == SESSION_LOG_SCHEMA:
408
+ return _verify_v2(stream, source=source, header=header)
409
+ if header.get("schema") == _LEGACY_SESSION_LOG_SCHEMA:
410
+ return _verify_v1(stream, source=source, header=header)
411
+ _raise("SESSION_LOG_INVALID", "unsupported schema")
412
+
413
+
414
+ __all__ = [
415
+ "SESSION_LOG_SCHEMA",
416
+ "SessionLogError",
417
+ "SessionLogResult",
418
+ "export_session_log",
419
+ "verify_session_log",
420
+ ]