agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/METADATA +22 -2
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/RECORD +356 -222
  3. ksadk/a2a/_space_client_events.py +381 -0
  4. ksadk/a2a/event_adapter.py +156 -64
  5. ksadk/a2a/executor.py +179 -63
  6. ksadk/a2a/space_client.py +6 -333
  7. ksadk/a2ui/core.py +222 -53
  8. ksadk/agui/_agent_helpers.py +243 -0
  9. ksadk/agui/a2ui_projection.py +12 -5
  10. ksadk/agui/agent.py +443 -259
  11. ksadk/api/client.py +525 -27
  12. ksadk/builders/code_builder.py +284 -8
  13. ksadk/builders/container_builder.py +8 -1
  14. ksadk/builders/managed_runtime_builder.py +39 -35
  15. ksadk/cli/__init__.py +17 -0
  16. ksadk/cli/cmd_dashboard.py +15 -1
  17. ksadk/cli/cmd_deploy.py +10 -2
  18. ksadk/cli/cmd_eval.py +66 -9
  19. ksadk/cli/cmd_evalset.py +284 -0
  20. ksadk/cli/cmd_files.py +5 -1
  21. ksadk/cli/cmd_hermes.py +1 -126
  22. ksadk/cli/cmd_invoke.py +28 -38
  23. ksadk/cli/cmd_managed_runtime.py +95 -0
  24. ksadk/cli/cmd_observe.py +85 -0
  25. ksadk/cli/cmd_openclaw.py +1 -435
  26. ksadk/cli/cmd_replay.py +28 -4
  27. ksadk/cli/cmd_studio.py +99 -8
  28. ksadk/cli/cmd_web.py +10 -2
  29. ksadk/cli/hermes_env.py +151 -0
  30. ksadk/cli/invoke_payload.py +49 -0
  31. ksadk/cli/openclaw_env.py +458 -0
  32. ksadk/codex/client.py +180 -30
  33. ksadk/codex/runtime.py +428 -326
  34. ksadk/configs/env_registry.py +66 -12
  35. ksadk/configs/env_registry_pcm.py +181 -0
  36. ksadk/configs/env_var_spec.py +18 -0
  37. ksadk/configs/global_config.py +5 -0
  38. ksadk/context_engine/__init__.py +98 -0
  39. ksadk/context_engine/assembler.py +176 -0
  40. ksadk/context_engine/baseline.py +419 -0
  41. ksadk/context_engine/cache_observability.py +228 -0
  42. ksadk/context_engine/capabilities.py +443 -0
  43. ksadk/context_engine/contributors.py +344 -0
  44. ksadk/context_engine/hosted_pipeline.py +417 -0
  45. ksadk/context_engine/models.py +117 -0
  46. ksadk/context_engine/planner.py +698 -0
  47. ksadk/context_engine/policies.py +319 -0
  48. ksadk/context_engine/projection.py +31 -0
  49. ksadk/context_engine/shadow_plan.py +266 -0
  50. ksadk/context_engine/tokenizer.py +164 -0
  51. ksadk/conversations/context.py +126 -6
  52. ksadk/conversations/message_projection.py +292 -28
  53. ksadk/conversations/model_context.py +49 -3
  54. ksadk/conversations/runtime_compaction.py +207 -5
  55. ksadk/conversations/runtime_input.py +184 -2
  56. ksadk/conversations/runtime_invocation.py +81 -3
  57. ksadk/conversations/runtime_observability.py +141 -0
  58. ksadk/conversations/runtime_payloads.py +42 -0
  59. ksadk/conversations/runtime_preparation.py +272 -2
  60. ksadk/conversations/runtime_resume.py +20 -1
  61. ksadk/conversations/runtime_stream_events.py +94 -9
  62. ksadk/conversations/semantic_summary.py +311 -1
  63. ksadk/conversations/session_lock.py +51 -0
  64. ksadk/deployment/env_forward.py +72 -0
  65. ksadk/deployment/managed_runtime.py +5 -0
  66. ksadk/deployment/providers/serverless.py +143 -69
  67. ksadk/evaluation/__init__.py +32 -0
  68. ksadk/evaluation/a2a_adapter.py +9 -1
  69. ksadk/evaluation/adapters.py +18 -5
  70. ksadk/evaluation/agent_eval_client.py +395 -0
  71. ksadk/evaluation/cloud_binding.py +80 -0
  72. ksadk/evaluation/cloud_converter.py +194 -0
  73. ksadk/evaluation/cloud_service.py +199 -0
  74. ksadk/evaluation/contracts.py +73 -4
  75. ksadk/evaluation/evaluators.py +183 -17
  76. ksadk/evaluation/evidence.py +258 -0
  77. ksadk/evaluation/executor.py +31 -6
  78. ksadk/evaluation/local_adapter.py +550 -0
  79. ksadk/evaluation/service_env.py +27 -0
  80. ksadk/evaluation/studio_build_adapter.py +253 -0
  81. ksadk/evaluation/target.py +15 -6
  82. ksadk/events/__init__.py +13 -13
  83. ksadk/events/_v1_compat/__init__.py +1 -0
  84. ksadk/events/_v1_compat/models.py +299 -0
  85. ksadk/events/_v1_compat/parser.py +247 -0
  86. ksadk/events/_v1_compat/projection.py +753 -0
  87. ksadk/events/adapters/__init__.py +34 -0
  88. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  89. ksadk/events/adapters/_a2a_support.py +275 -0
  90. ksadk/events/adapters/_codex_interactions.py +375 -0
  91. ksadk/events/adapters/_codex_items.py +801 -0
  92. ksadk/events/adapters/_codex_validators.py +311 -0
  93. ksadk/events/adapters/_langgraph_support.py +785 -0
  94. ksadk/events/adapters/a2a.py +841 -0
  95. ksadk/events/adapters/adk.py +503 -0
  96. ksadk/events/adapters/codex.py +717 -0
  97. ksadk/events/adapters/langgraph.py +745 -0
  98. ksadk/events/canonical.py +440 -0
  99. ksadk/events/canonical_replay.py +497 -0
  100. ksadk/events/canonical_store.py +468 -0
  101. ksadk/events/cold_recovery.py +257 -0
  102. ksadk/events/content.py +89 -0
  103. ksadk/events/identity.py +86 -0
  104. ksadk/events/pipeline.py +455 -0
  105. ksadk/events/projections.py +66 -0
  106. ksadk/events/reducer.py +661 -0
  107. ksadk/events/replay.py +3 -36
  108. ksadk/events/runtime_event.py +20 -265
  109. ksadk/events/session_event.py +324 -0
  110. ksadk/events/store.py +3 -316
  111. ksadk/events/v1_compat.py +41 -0
  112. ksadk/harness/runtime.py +160 -57
  113. ksadk/identity/resolver.py +4 -3
  114. ksadk/interaction/__init__.py +28 -0
  115. ksadk/interaction/contracts.py +136 -0
  116. ksadk/interaction/ledger.py +189 -0
  117. ksadk/interaction/provider.py +122 -0
  118. ksadk/interaction/providers/__init__.py +52 -0
  119. ksadk/interaction/providers/adk.py +40 -0
  120. ksadk/interaction/providers/codex.py +94 -0
  121. ksadk/interaction/providers/langgraph.py +58 -0
  122. ksadk/kernel/__init__.py +117 -0
  123. ksadk/kernel/authorization.py +258 -0
  124. ksadk/kernel/bootstrap.py +1074 -0
  125. ksadk/kernel/contract_fingerprints.py +61 -0
  126. ksadk/kernel/contracts.py +362 -0
  127. ksadk/kernel/control.py +219 -0
  128. ksadk/kernel/errors.py +79 -0
  129. ksadk/kernel/ingress.py +1102 -0
  130. ksadk/kernel/mapping.py +111 -0
  131. ksadk/kernel/memory_store.py +1133 -0
  132. ksadk/kernel/postgres_store.py +1756 -0
  133. ksadk/kernel/recovery.py +452 -0
  134. ksadk/kernel/runtime_identity.py +103 -0
  135. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  136. ksadk/kernel/sqlite_store.py +1410 -0
  137. ksadk/kernel/state.py +145 -0
  138. ksadk/kernel/store.py +232 -0
  139. ksadk/kernel/worker.py +953 -0
  140. ksadk/knowledge_base/client.py +7 -0
  141. ksadk/knowledge_base/service.py +32 -5
  142. ksadk/memory/__init__.py +61 -0
  143. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  144. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  145. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  146. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  147. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  148. ksadk/memory/coordinator.py +461 -0
  149. ksadk/memory/events.py +202 -0
  150. ksadk/memory/extraction.py +228 -0
  151. ksadk/memory/ltm_backend_factory.py +16 -3
  152. ksadk/memory/models.py +316 -0
  153. ksadk/memory/policy.py +215 -0
  154. ksadk/memory/provider.py +47 -0
  155. ksadk/memory/provider_adapter.py +159 -0
  156. ksadk/memory/provider_resolver.py +71 -0
  157. ksadk/memory/providers/__init__.py +5 -0
  158. ksadk/memory/providers/local_sqlite.py +490 -0
  159. ksadk/memory/resolved_policy.py +145 -0
  160. ksadk/memory/service.py +188 -6
  161. ksadk/model_proxy/bootstrap.py +16 -9
  162. ksadk/model_proxy/detect.py +202 -9
  163. ksadk/model_proxy/namespace.py +39 -15
  164. ksadk/model_proxy/server.py +24 -5
  165. ksadk/model_proxy/transform.py +60 -2
  166. ksadk/observability/__init__.py +25 -0
  167. ksadk/observability/session_log.py +420 -0
  168. ksadk/observability/trajectory.py +105 -0
  169. ksadk/prompts/__init__.py +71 -0
  170. ksadk/prompts/compiler.py +215 -0
  171. ksadk/prompts/models.py +82 -0
  172. ksadk/prompts/projection.py +89 -0
  173. ksadk/prompts/resolved.py +137 -0
  174. ksadk/prompts/sources.py +233 -0
  175. ksadk/runners/_langgraph_runner_streams.py +822 -0
  176. ksadk/runners/adk_runner.py +5 -5
  177. ksadk/runners/base_runner.py +16 -0
  178. ksadk/runners/factory.py +1 -2
  179. ksadk/runners/langgraph_runner.py +198 -437
  180. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  181. ksadk/runtime/_runner_adapter/stream_mapping.py +788 -0
  182. ksadk/runtime/adapter.py +118 -2
  183. ksadk/runtime/conversation_execution.py +316 -182
  184. ksadk/runtime/executor.py +144 -3
  185. ksadk/runtime/factory.py +193 -7
  186. ksadk/runtime/hosted_finalizer.py +268 -0
  187. ksadk/runtime/launch.py +14 -1
  188. ksadk/runtime/preprocessing.py +25 -3
  189. ksadk/runtime/runner_adapter.py +288 -508
  190. ksadk/runtime/usage.py +33 -0
  191. ksadk/sandbox/backends/e2b.py +35 -2
  192. ksadk/server/composition.py +8 -0
  193. ksadk/server/factory.py +69 -6
  194. ksadk/server/routes/kernel_ingress.py +258 -0
  195. ksadk/server/routes/openai_compat.py +88 -8
  196. ksadk/server/routes/projection.py +35 -20
  197. ksadk/server/routes/run.py +94 -0
  198. ksadk/server/routes/sessions.py +2 -0
  199. ksadk/server/routes/streaming.py +5 -2
  200. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-DSU1sWD_.js} +1 -1
  201. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-DOH6MVcn.js} +4 -4
  202. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-BL8my1R2.js} +4 -4
  203. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  204. ksadk/server/static/assets/MermaidBlock-Dz4IP-Tx.js +312 -0
  205. ksadk/server/static/assets/NativeTerminalPanel-DUrK0JpZ.js +1 -0
  206. ksadk/server/static/assets/abnfDiagram-VCTEODGH-g20pFzNV.js +1 -0
  207. ksadk/server/static/assets/arc-C4FzinUA.js +1 -0
  208. ksadk/server/static/assets/architecture-7GRP2DOG-DjKratdf.js +1 -0
  209. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-DpIqL5h4.js +36 -0
  210. ksadk/server/static/assets/blockDiagram-NRAW4CY4-BjkljTNz.js +129 -0
  211. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-Dk_ieq2X.js +38 -0
  212. ksadk/server/static/assets/channel-4cQHKtx1.js +1 -0
  213. ksadk/server/static/assets/chunk-2Q5K7J3B-DmgWkESh.js +1 -0
  214. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-BVHAchFb.js} +1 -1
  215. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-C-ipQzuS.js} +1 -1
  216. ksadk/server/static/assets/chunk-G27WJ6UU-C2EyJbIK.js +231 -0
  217. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-vYvVJb_M.js} +1 -1
  218. ksadk/server/static/assets/chunk-LCL6LL3I-BcI6ysPT.js +206 -0
  219. ksadk/server/static/assets/chunk-POPQ4Y6H-C030x_Z1.js +1 -0
  220. ksadk/server/static/assets/chunk-RHFEMEQ7-yyGpsz4n.js +168 -0
  221. ksadk/server/static/assets/chunk-SVP7TREG-BLTlmMU7.js +88 -0
  222. ksadk/server/static/assets/chunk-XXDRQBXY-C_32ArgP.js +1 -0
  223. ksadk/server/static/assets/classDiagram-DTDB5LWJ-DLFd9Xi0.js +1 -0
  224. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-DLFd9Xi0.js +1 -0
  225. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-9YQYwdl0.js} +1 -1
  226. ksadk/server/static/assets/cynefin-OW5HDTMX-DjEM48Qp.js +1 -0
  227. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-ErLF5N13.js +62 -0
  228. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  229. ksadk/server/static/assets/dagre-3AP2YEHR-DFiQF-6f.js +4 -0
  230. ksadk/server/static/assets/dagre-BuhGnRI1.js +1 -0
  231. ksadk/server/static/assets/diagram-S7CK7UJ4-Bt1v8-GC.js +30 -0
  232. ksadk/server/static/assets/diagram-UQ7AKVKN-DSCxJdBK.js +41 -0
  233. ksadk/server/static/assets/diagram-VSXAHHWV-DHfYwp_9.js +3 -0
  234. ksadk/server/static/assets/diagram-VX7I27RA-DdzD-4Le.js +24 -0
  235. ksadk/server/static/assets/diagram-Z3DM3KII-Du-nAJ9F.js +24 -0
  236. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-8hqcx0sU.js} +1 -1
  237. ksadk/server/static/assets/dist-B7seoj3d.js +1 -0
  238. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-BE7bu-DL.js} +1 -1
  239. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-BVDffZ0U.js} +1 -1
  240. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-BX8z72IC.js} +1 -1
  241. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-Bf2M8m3N.js} +1 -1
  242. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-BjU6y-A2.js} +1 -1
  243. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-BytlxIDT.js} +1 -1
  244. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-CSh_DE14.js} +1 -1
  245. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-CXYYBw3_.js} +1 -1
  246. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-C_wsv-Qd.js} +1 -1
  247. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-ClxGviKw.js} +1 -1
  248. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-CttYUqzS.js} +1 -1
  249. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DNp6rLRA.js} +1 -1
  250. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-DUX-id_F.js} +1 -1
  251. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-DmldrHd_.js} +1 -1
  252. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DnfXs8Vn.js} +2 -2
  253. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Dq9gLD7L.js} +1 -1
  254. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-W-TIVntx.js} +1 -1
  255. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-dDdADKFA.js} +1 -1
  256. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-dydu68Fl.js} +1 -1
  257. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-ngtN0DJB.js} +1 -1
  258. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-Da9C9NO1.js +1 -0
  259. ksadk/server/static/assets/erDiagram-SSCWMZ5O-EAGqqR79.js +99 -0
  260. ksadk/server/static/assets/eventmodeling-NTZA5JFV-CMgMaJrC.js +1 -0
  261. ksadk/server/static/assets/flowDiagram-A5DVABFB-BBKL0x3P.js +1 -0
  262. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-DqJsKb59.js +292 -0
  263. ksadk/server/static/assets/gitGraph-4MIJSDKK-Dw63mrhw.js +1 -0
  264. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-B6g4dtDi.js +106 -0
  265. ksadk/server/static/assets/index-8ipRcQ-M.js +240 -0
  266. ksadk/server/static/assets/index-ByFCqjlh.css +2 -0
  267. ksadk/server/static/assets/info-A6RAGUB7-B6iAblII.js +1 -0
  268. ksadk/server/static/assets/infoDiagram-RXCK75RN-Djm_nbd5.js +2 -0
  269. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-DWya9SG2.js} +6 -6
  270. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DutxhSNI.js} +6 -6
  271. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-DJdOF8Fm.js} +10 -10
  272. ksadk/server/static/assets/katex-vFWytM5c.js +257 -0
  273. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-BHjG8b-J.js} +1 -1
  274. ksadk/server/static/assets/mermaid-parser.core-KGSy4jWT.js +166 -0
  275. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-9-HC88D4.js} +29 -29
  276. ksadk/server/static/assets/packet-AYTQ26CC-BOYAipuq.js +1 -0
  277. ksadk/server/static/assets/pegDiagram-XKGWAZYB-DrEZd4fD.js +1 -0
  278. ksadk/server/static/assets/pie-WAS4IAKB-Ctq0Kbku.js +1 -0
  279. ksadk/server/static/assets/pieDiagram-E7YTZNPT-DpjqOFFp.js +39 -0
  280. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-BiNhgOXX.js} +6 -6
  281. ksadk/server/static/assets/radar-RG4KPBEZ-DN0-ul_0.js +1 -0
  282. ksadk/server/static/assets/railroad-74A4TZTK-C6cC_to0.js +1 -0
  283. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-BimjIuRc.js +1 -0
  284. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-CvY65ABq.js +1 -0
  285. ksadk/server/static/assets/railroad-peg-WCYAUIDC-D72Iq_Tv.js +1 -0
  286. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-_Y3Ojg6G.js +1 -0
  287. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-DeCFdg9K.js +84 -0
  288. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-CPH75rhw.js} +7 -7
  289. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-OEpQQdr4.js} +10 -10
  290. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-heUErzhI.js +1 -0
  291. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-DvA3jSMB.js +1 -0
  292. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-BgQY03nz.js +1 -0
  293. ksadk/server/static/assets/swimlanes-XN3QIQJK-CR-dfN_t.js +1 -0
  294. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-DLVw3q5Q.js +8 -0
  295. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-DAVJf1TX.js} +7 -7
  296. ksadk/server/static/assets/treeView-Q6P3EWNA-BMpO0wsB.js +1 -0
  297. ksadk/server/static/assets/treemap-WGGIJYW6-Et0aqzU3.js +1 -0
  298. ksadk/server/static/assets/vennDiagram-4TSXK5OY-DwM8eDkh.js +34 -0
  299. ksadk/server/static/assets/wardley-WFR3VGLG-CbY0mhpl.js +1 -0
  300. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DZL-Zz2t.js +78 -0
  301. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-Dy6Yh_hu.js +7 -0
  302. ksadk/server/static/index.html +2 -2
  303. ksadk/sessions/__init__.py +26 -9
  304. ksadk/sessions/_local_service_sync.py +688 -0
  305. ksadk/sessions/_local_tables.py +21 -0
  306. ksadk/sessions/_postgres_schema.py +305 -0
  307. ksadk/sessions/_postgres_tables.py +15 -0
  308. ksadk/sessions/base.py +80 -1
  309. ksadk/sessions/in_memory.py +53 -2
  310. ksadk/sessions/local_service.py +63 -604
  311. ksadk/sessions/postgres_service.py +58 -148
  312. ksadk/sessions/resilient.py +35 -1
  313. ksadk/studio/api.py +577 -25
  314. ksadk/studio/api_contracts.py +114 -5
  315. ksadk/studio/api_memory_routes.py +82 -0
  316. ksadk/studio/authoring.py +252 -27
  317. ksadk/studio/authoring_coordinator.py +799 -67
  318. ksadk/studio/builder.py +88 -11
  319. ksadk/studio/capabilities.py +17 -0
  320. ksadk/studio/cloud.py +1916 -95
  321. ksadk/studio/codex_agent_service.py +125 -14
  322. ksadk/studio/codex_authoring.py +469 -0
  323. ksadk/studio/codex_builder.py +138 -25
  324. ksadk/studio/codex_manifest.py +51 -22
  325. ksadk/studio/codex_run.py +46 -3
  326. ksadk/studio/compiler.py +1 -0
  327. ksadk/studio/contracts.py +114 -3
  328. ksadk/studio/event_store.py +25 -95
  329. ksadk/studio/framework_run.py +231 -3
  330. ksadk/studio/hosted_kernel.py +317 -0
  331. ksadk/studio/manifest_resolver.py +131 -0
  332. ksadk/studio/model_client.py +95 -9
  333. ksadk/studio/model_profile_service.py +1 -1
  334. ksadk/studio/operations.py +37 -5
  335. ksadk/studio/otel_trace.py +348 -42
  336. ksadk/studio/pcm_memory.py +85 -0
  337. ksadk/studio/repository.py +23 -2
  338. ksadk/studio/resource_catalog.py +43 -0
  339. ksadk/studio/run_service.py +563 -137
  340. ksadk/studio/runtime_source.py +42 -16
  341. ksadk/studio/service.py +1180 -72
  342. ksadk/studio/shared_web.py +32 -22
  343. ksadk/studio/static/assets/index-BLpcdrgW.css +1 -0
  344. ksadk/studio/static/assets/index-CYekR2Xv.js +260 -0
  345. ksadk/studio/static/index.html +2 -2
  346. ksadk/studio/templates.py +100 -8
  347. ksadk/studio/validator.py +19 -0
  348. ksadk/studio/workspace.py +30 -32
  349. ksadk/tracing/setup.py +304 -3
  350. ksadk/version.py +1 -1
  351. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  352. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  353. ksadk/events/parser.py +0 -191
  354. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  355. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  356. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  357. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  358. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  359. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  360. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  361. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  362. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  363. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  364. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  365. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  366. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  367. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  368. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  369. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  370. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  371. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  372. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  373. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  374. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  375. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  376. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  377. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  378. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  379. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  380. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  381. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  382. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  383. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  384. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  385. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  386. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  387. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  388. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  389. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  390. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  391. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  392. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  393. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  394. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  395. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  396. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  397. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  398. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  399. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  400. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  401. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  402. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  403. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  404. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  405. ksadk/studio/evaluation.py +0 -192
  406. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  407. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  408. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/WHEEL +0 -0
  409. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/entry_points.txt +0 -0
  410. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/licenses/LICENSE +0 -0
  411. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.2.dist-info}/top_level.txt +0 -0
ksadk/codex/runtime.py CHANGED
@@ -19,14 +19,33 @@
19
19
  from __future__ import annotations
20
20
 
21
21
  import asyncio
22
+ import base64
23
+ import binascii
24
+ import hashlib
22
25
  import json
23
26
  import logging
27
+ import re
28
+ import time
29
+ from collections.abc import Mapping
24
30
  from dataclasses import dataclass, field
31
+ from pathlib import Path
25
32
  from typing import Any, AsyncIterator, Optional
26
33
 
27
34
  from ksadk.codex.client import CodexClient
28
- from ksadk.codex.phase import CodexPhaseTracker
29
- from ksadk.events.runtime_event import EventType, RuntimeEvent
35
+ from ksadk.events.adapters.codex import CodexAdapterContext, CodexEventAdapter
36
+ from ksadk.events.canonical import (
37
+ ErrorInfo,
38
+ InteractionRequested,
39
+ InteractionResolved,
40
+ RunCanceled,
41
+ RunCompleted,
42
+ RunFailed,
43
+ RunInterrupted,
44
+ RuntimeEvent,
45
+ SourceRef,
46
+ )
47
+ from ksadk.events.identity import stable_event_id, stable_item_id, stable_scope_id
48
+ from ksadk.kernel.contracts import RuntimeCapability, RuntimeCapabilityMatrix
30
49
  from ksadk.runtime.adapter import (
31
50
  BaseRuntime,
32
51
  CancelResult,
@@ -76,6 +95,7 @@ class _CodexThread:
76
95
  completed_at: int | None = None
77
96
  duration_ms: int | None = None
78
97
  goal_mode: bool = False
98
+ continuation_preexisting: bool = False
79
99
 
80
100
 
81
101
  class CodexRuntimeAdapter(RuntimeAdapter):
@@ -101,6 +121,33 @@ class CodexRuntimeAdapter(RuntimeAdapter):
101
121
  # 可观测:最近一次 cancel 级联丢弃的审批集(contract test 断言用)。
102
122
  self.last_cancel_dropped_approvals: set[str] = set()
103
123
  self._seq = 0
124
+ self._closed = False
125
+
126
+ # ---- capability matrix(v1,诚实声明) ----
127
+
128
+ def capabilities(self) -> RuntimeCapabilityMatrix:
129
+ """Codex 真实矩阵:thread 级 cancel/pause/resume + 审批 submit + snapshot
130
+ checkpoint 均为后端原生能力;attach/durable_restore 未实现(线程表在本进程,
131
+ attach seam 缺失),steer/inject 无原生通道。
132
+ """
133
+
134
+ def _unavailable(reason: str) -> RuntimeCapability:
135
+ return RuntimeCapability(supported=False, mode="unavailable", reason=reason)
136
+
137
+ return RuntimeCapabilityMatrix(
138
+ cancel=RuntimeCapability(supported=True, mode="native"),
139
+ pause=RuntimeCapability(supported=True, mode="native"),
140
+ resume=RuntimeCapability(supported=True, mode="native"),
141
+ submit_interaction=RuntimeCapability(supported=True, mode="native"),
142
+ attach=_unavailable("codex_process_local_thread_table"),
143
+ steer=_unavailable("runtime_no_native_steer"),
144
+ inject=_unavailable("runtime_no_native_inject"),
145
+ checkpoint=RuntimeCapability(supported=True, mode="native"),
146
+ durable_restore=_unavailable("codex_durable_restore_requires_attach_seam"),
147
+ goal=RuntimeCapability(supported=True, mode="native"),
148
+ loop=_unavailable("codex_loop_requires_run_control_spec"),
149
+ plan=RuntimeCapability(supported=True, mode="native"),
150
+ )
104
151
 
105
152
  # ---- 六动词 ----
106
153
 
@@ -128,9 +175,18 @@ class CodexRuntimeAdapter(RuntimeAdapter):
128
175
  cwd = request.config.get("cwd")
129
176
  if cwd:
130
177
  thread_config["cwd"] = str(cwd)
178
+ # AgentKernel creates one adapter/transport per durable turn and
179
+ # closes it after the canonical terminal event. The next turn
180
+ # therefore resumes the native thread from a new app-server
181
+ # process; an ephemeral Codex thread has no rollout and cannot be
182
+ # resumed across that transport boundary.
183
+ thread_config.setdefault("ephemeral", False)
131
184
  thread_id = await self._client.start_thread(thread_config)
132
185
  self._known_threads.add(thread_id)
133
- thread = _CodexThread(thread_id=thread_id)
186
+ thread = _CodexThread(
187
+ thread_id=thread_id,
188
+ continuation_preexisting=bool(provided),
189
+ )
134
190
  thread.__dict__["_start_request"] = request
135
191
  self._threads[thread_id] = thread
136
192
  self._requests[thread_id] = request
@@ -245,7 +301,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
245
301
  raise ValueError(f"thread {handle.run_id} 已被中断/杀进程,不持久化,不可 resume")
246
302
  self._pending_cancels.discard(handle.run_id)
247
303
  self._known_threads.add(target.id)
248
- thread = _CodexThread(thread_id=target.id)
304
+ thread = _CodexThread(
305
+ thread_id=target.id,
306
+ continuation_preexisting=True,
307
+ )
249
308
  thread.__dict__["_resume"] = {"target": target, "payload": payload}
250
309
  request = self._requests.get(handle.run_id)
251
310
  if request is not None:
@@ -294,13 +353,17 @@ class CodexRuntimeAdapter(RuntimeAdapter):
294
353
  )
295
354
 
296
355
  async def close(self, handle: RunHandle) -> None:
356
+ if self._closed:
357
+ return
358
+ self._closed = True
297
359
  thread = self._threads.pop(handle.run_id, None)
298
360
  self._requests.pop(handle.run_id, None)
299
- if thread is not None:
361
+ active = thread is not None and thread.streaming and not thread.done
362
+ if active:
300
363
  thread.interrupt_event.set()
301
364
  try:
302
- active_thread_id = thread.thread_id if thread is not None else handle.run_id
303
- await self._client.interrupt_active_turn(active_thread_id)
365
+ if active:
366
+ await self._client.interrupt_active_turn(thread.thread_id)
304
367
  finally:
305
368
  # AsyncCodex.close owns terminate/wait/kill for the app-server child.
306
369
  await self._client.close()
@@ -326,18 +389,12 @@ class CodexRuntimeAdapter(RuntimeAdapter):
326
389
 
327
390
  if handle.run_id in self._pending_cancels:
328
391
  self._pending_cancels.discard(handle.run_id)
329
- yield self._event(
392
+ yield self._make_run_canceled(
330
393
  handle,
331
- EventType.RUN_CANCELED,
332
- {
333
- "status": "cancelled",
334
- "cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
335
- },
394
+ reason=f"pending_cancel:{CancelResult.PENDING_CANCEL_RECORDED.value}",
336
395
  )
337
396
  return
338
397
 
339
- yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
340
- tracker = CodexPhaseTracker()
341
398
  request = thread.__dict__.get("_start_request")
342
399
  resume_state = thread.__dict__.get("_resume")
343
400
  if request is not None:
@@ -350,21 +407,8 @@ class CodexRuntimeAdapter(RuntimeAdapter):
350
407
  thread.streaming = True
351
408
  thread.turn_id = thread.turn_id or f"turn_{thread.thread_id}"
352
409
  try:
353
- async for event in self._map_codex_stream(handle, thread, tracker, run_input):
410
+ async for event in self._map_codex_stream(handle, thread, run_input):
354
411
  yield event
355
- # 正常结束(非 interrupt):补 RUN_COMPLETED(AGUI 投射器据此发 RunFinished success)
356
- if not thread.interrupted:
357
- completed_payload: dict[str, Any] = {
358
- "status": "completed",
359
- "source": "codex",
360
- }
361
- if thread.started_at is not None:
362
- completed_payload["started_at"] = thread.started_at
363
- if thread.completed_at is not None:
364
- completed_payload["completed_at"] = thread.completed_at
365
- if thread.duration_ms is not None:
366
- completed_payload["duration_ms"] = thread.duration_ms
367
- yield self._event(handle, EventType.RUN_COMPLETED, completed_payload)
368
412
  except asyncio.CancelledError:
369
413
  thread.interrupted = True
370
414
  self._do_not_persist.add(handle.run_id)
@@ -377,18 +421,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
377
421
  # Closing the SDK transport terminates and waits for the app-server
378
422
  # child even when the stream is stuck between notifications.
379
423
  await self._client.close()
380
- yield self._event(
381
- handle,
382
- EventType.RUN_FAILED,
383
- {"status": "failed", "error": "codex turn timed out"},
384
- )
385
- except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RUN_FAILED
424
+ yield self._make_run_failed(handle, "codex turn timed out")
425
+ except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RunFailed
386
426
  self._do_not_persist.add(handle.run_id)
387
- yield self._event(
388
- handle,
389
- EventType.RUN_FAILED,
390
- {"status": "failed", "error": str(exc)},
391
- )
427
+ yield self._make_run_failed(handle, str(exc))
392
428
  finally:
393
429
  thread.streaming = False
394
430
  thread.done = True
@@ -398,10 +434,15 @@ class CodexRuntimeAdapter(RuntimeAdapter):
398
434
  self,
399
435
  handle: RunHandle,
400
436
  thread: _CodexThread,
401
- tracker: CodexPhaseTracker,
402
437
  prompt: Any,
403
438
  ) -> AsyncIterator[RuntimeEvent]:
404
439
  request = thread.__dict__.get("_start_request") or thread.__dict__.get("_request_config")
440
+ adapter = CodexEventAdapter(
441
+ known_thread_ids=(thread.thread_id,)
442
+ if thread.continuation_preexisting
443
+ else (),
444
+ )
445
+ context = CodexAdapterContext(run_id=self._event_run_id(handle))
405
446
  run_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
406
447
  if request is not None and request.config:
407
448
  for key in ("sandbox", "approval_mode", "summary", "collaboration_mode"):
@@ -496,14 +537,11 @@ class CodexRuntimeAdapter(RuntimeAdapter):
496
537
  chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
497
538
  chunk_task = None
498
539
  thread.interrupted = True
499
- # AGUI 投射器对 RUN_INTERRUPTED 无兜底,必须显式发,否则 raise
500
- yield self._event(
540
+ # Runtime interrupt (user pause) — adapter doesn't know;
541
+ # emit canonical RunInterrupted explicitly.
542
+ yield self._make_run_interrupted(
501
543
  handle,
502
- EventType.RUN_INTERRUPTED,
503
- {
504
- "status": "paused" if thread.paused else "interrupted",
505
- "reason": "user_pause" if thread.paused else "runtime_interrupt",
506
- },
544
+ reason="user_pause" if thread.paused else "runtime_interrupt",
507
545
  )
508
546
  return
509
547
  for task in pending:
@@ -513,8 +551,58 @@ class CodexRuntimeAdapter(RuntimeAdapter):
513
551
  chunk = chunk_task.result()
514
552
  if chunk is _STREAM_STOP:
515
553
  return
516
- event = self._codex_chunk_to_event(handle, thread, tracker, chunk)
517
- if event is not None:
554
+ # TODO(runtime-event-v2): use real native cursor from chunk if
555
+ # available; fallback to thread:seq for now.
556
+ native_cursor = f"{thread.thread_id}:{self._next_seq()}"
557
+ # autoApprovalReview 不产生 canonical 事件(adapter 静默),但
558
+ # cancel 级联丢弃审批的契约依赖 runtime 的 pending 跟踪。
559
+ chunk_method = (
560
+ str((chunk or {}).get("method") or "")
561
+ if isinstance(chunk, dict)
562
+ else ""
563
+ )
564
+ if chunk_method in {
565
+ "item/autoApprovalReview/started",
566
+ "item/autoApprovalReview/completed",
567
+ }:
568
+ review_params = chunk.get("params") or {}
569
+ review_id = str(
570
+ review_params.get("reviewId")
571
+ or review_params.get("review_id")
572
+ or ""
573
+ )
574
+ if review_id:
575
+ if chunk_method.endswith("started"):
576
+ thread.pending_approvals.add(review_id)
577
+ else:
578
+ thread.pending_approvals.discard(review_id)
579
+ for event in adapter.map_protocol_message(
580
+ chunk,
581
+ context,
582
+ native_cursor=native_cursor,
583
+ timestamp=time.time(),
584
+ ):
585
+ event = self._with_caller_scope(event, request)
586
+ # 跟踪 pending 审批(cancel 级联丢弃契约依赖该集合)。
587
+ if isinstance(event, InteractionRequested):
588
+ if event.interaction_id:
589
+ thread.pending_approvals.add(event.interaction_id)
590
+ call_id = getattr(event.request, "call_id", None)
591
+ if call_id:
592
+ thread.pending_approvals.add(str(call_id))
593
+ elif isinstance(event, InteractionResolved):
594
+ thread.pending_approvals.discard(event.interaction_id)
595
+ call_id = getattr(event.response, "call_id", None)
596
+ if call_id:
597
+ thread.pending_approvals.discard(str(call_id))
598
+ if isinstance(event, (RunCompleted, RunFailed, RunCanceled)):
599
+ # The Kernel stops consuming as soon as it persists a
600
+ # canonical terminal fact, so generator ``finally`` may
601
+ # not run before worker cleanup calls ``close``. Mark the
602
+ # native turn terminal before yielding that fact; close
603
+ # must terminate the transport without sending a stale
604
+ # turn/interrupt RPC to an already-completed app-server.
605
+ thread.done = True
518
606
  yield event
519
607
  finally:
520
608
  waiter_tasks = [task for task in (chunk_task, interrupt_task) if task is not None]
@@ -530,285 +618,155 @@ class CodexRuntimeAdapter(RuntimeAdapter):
530
618
  except Exception: # noqa: BLE001
531
619
  pass
532
620
 
533
- def _codex_chunk_to_event(
621
+ # ---- canonical run.* helpers (for runtime-owned lifecycle) ----
622
+
623
+ def _event_run_id(self, handle: RunHandle) -> str:
624
+ """事件的 canonical run_id:调用方 invocation_id 优先,退回 thread id。
625
+
626
+ ``handle.run_id`` 是 codex 原生 thread id(resume/cancel 按 thread 寻址);
627
+ 但 canonical RuntimeEvent 的 run_id 必须与调用方
628
+ ``StartRequest.metadata['invocation_id']`` 一致(conversation kernel 的
629
+ event scope 校验),否则 hosted/web 执行路径会在首个事件上 fail。
630
+ """
631
+ request = self._requests.get(handle.run_id)
632
+ if request is not None:
633
+ invocation_id = str(
634
+ (getattr(request, "metadata", None) or {}).get("invocation_id") or ""
635
+ ).strip()
636
+ if invocation_id:
637
+ return invocation_id
638
+ return handle.run_id
639
+
640
+ def _make_source(self, handle: RunHandle) -> SourceRef:
641
+ request = self._requests.get(handle.run_id)
642
+ return SourceRef(
643
+ framework="codex",
644
+ native_run_id=handle.run_id,
645
+ metadata={
646
+ "agent_id": (
647
+ str(request.agent_id or "codex") if request is not None else "codex"
648
+ ),
649
+ "user_id": (
650
+ request.user_id
651
+ if request is not None
652
+ else str(handle.native_ref.get("user_id") or "user")
653
+ ),
654
+ "session_id": handle.session_id,
655
+ "invocation_id": (
656
+ str(request.metadata.get("invocation_id") or handle.run_id)
657
+ if request is not None
658
+ else handle.run_id
659
+ ),
660
+ },
661
+ )
662
+
663
+ def _with_caller_scope(self, event: RuntimeEvent, request: Any) -> RuntimeEvent:
664
+ """把调用方 scope(request 的 agent/user/session/invocation)并入事件 source。"""
665
+
666
+ if request is None:
667
+ return event
668
+ caller_scope = {
669
+ "agent_id": str(getattr(request, "agent_id", "") or "codex"),
670
+ "user_id": str(getattr(request, "user_id", "") or "user"),
671
+ "session_id": str(getattr(request, "session_id", "") or ""),
672
+ "invocation_id": str(
673
+ (getattr(request, "metadata", None) or {}).get("invocation_id")
674
+ or ""
675
+ ),
676
+ }
677
+ merged = {**caller_scope, **dict(event.source.metadata or {})}
678
+ # adapter 自身字段优先;仅补齐缺失的调用方 scope 键。
679
+ for key, value in caller_scope.items():
680
+ if not merged.get(key):
681
+ merged[key] = value
682
+ source = event.source.model_copy(update={"metadata": merged})
683
+ return event.model_copy(update={"source": source})
684
+
685
+ def _canonical_kwargs(
534
686
  self,
535
687
  handle: RunHandle,
536
- thread: _CodexThread,
537
- tracker: CodexPhaseTracker,
538
- chunk: dict[str, Any],
539
- ) -> Optional[RuntimeEvent]:
540
- if not isinstance(chunk, dict):
541
- return None
542
- method = str(chunk.get("method") or chunk.get("type") or "")
543
- params = chunk.get("params") or chunk
544
-
545
- if method == "error":
546
- raw_error = params.get("error") if isinstance(params, dict) else None
547
- error = raw_error if isinstance(raw_error, dict) else {}
548
- message = str(
549
- error.get("message")
550
- or (params.get("message") if isinstance(params, dict) else "")
551
- or raw_error
552
- or "Codex runtime transport failed"
553
- )
554
- if not bool(params.get("will_retry") or params.get("willRetry")) or "401" in message:
555
- raise RuntimeError(message)
556
- return None
557
-
558
- if method == "thread/tokenUsage/updated":
559
- token_usage = params.get("token_usage") or params.get("tokenUsage") or {}
560
- last = token_usage.get("last") if isinstance(token_usage, dict) else {}
561
- if not isinstance(last, dict):
562
- last = {}
563
- return self._event(
564
- handle,
565
- EventType.USAGE_REPORTED,
566
- {
567
- "input_tokens": int(last.get("input_tokens", last.get("inputTokens", 0)) or 0),
568
- "cached_tokens": int(
569
- last.get("cached_input_tokens", last.get("cachedInputTokens", 0)) or 0
570
- ),
571
- "output_tokens": int(
572
- last.get("output_tokens", last.get("outputTokens", 0)) or 0
573
- ),
574
- "reasoning_tokens": int(
575
- last.get(
576
- "reasoning_output_tokens",
577
- last.get("reasoningOutputTokens", 0),
578
- )
579
- or 0
580
- ),
581
- "total_tokens": int(last.get("total_tokens", last.get("totalTokens", 0)) or 0),
582
- "source": "codex",
583
- },
584
- )
585
- if method == "thread/goal/updated":
586
- goal = params.get("goal") if isinstance(params, dict) else {}
587
- goal = goal if isinstance(goal, dict) else {}
588
- status = str(goal.get("status") or "").lower()
589
- if status in {"paused", "blocked", "usage_limited", "budget_limited"}:
590
- thread.paused = status == "paused"
591
- thread.interrupted = True
592
- return self._event(
593
- handle,
594
- EventType.RUN_INTERRUPTED,
595
- {"status": status, "reason": "goal_status", "goal": goal},
596
- )
597
- return self._event(
598
- handle,
599
- EventType.RUN_PROGRESS,
600
- {"native_event": "goal.updated", "native_data": goal},
601
- )
602
- if method in {"turn/started", "turn/completed"}:
603
- raw_turn = params.get("turn")
604
- turn: dict[str, Any] = raw_turn if isinstance(raw_turn, dict) else {}
605
- started_at = turn.get("started_at", turn.get("startedAt"))
606
- completed_at = turn.get("completed_at", turn.get("completedAt"))
607
- duration_ms = turn.get("duration_ms", turn.get("durationMs"))
608
- if started_at is not None:
609
- thread.started_at = int(started_at)
610
- if completed_at is not None:
611
- thread.completed_at = int(completed_at)
612
- if duration_ms is not None:
613
- thread.duration_ms = max(0, int(duration_ms))
614
- return None
615
-
616
- if method == "a2ui/surface":
617
- surface_id = str(params.get("surface_id") or params.get("surfaceId") or "")
618
- return self._event(
619
- handle,
620
- EventType.A2UI_SURFACE_BEGIN,
621
- {
622
- "surface_id": surface_id,
623
- "surface": params.get("surface")
624
- if isinstance(params.get("surface"), dict)
625
- else {},
626
- },
627
- )
628
- if method == "a2ui/interaction":
629
- interaction_id = str(params.get("interaction_id") or params.get("interactionId") or "")
630
- if interaction_id:
631
- thread.pending_approvals.add(interaction_id)
632
- return self._event(
633
- handle,
634
- EventType.A2UI_INTERACTION,
635
- {
636
- "surface_id": str(params.get("surface_id") or params.get("surfaceId") or ""),
637
- "interaction_id": interaction_id,
638
- "kind": str(params.get("kind") or "form"),
639
- "input_schema": params.get("input_schema")
640
- if isinstance(params.get("input_schema"), dict)
641
- else {},
642
- "is_blocking": bool(params.get("is_blocking", True)),
643
- },
644
- )
688
+ *,
689
+ scope_id: str,
690
+ item_id: str,
691
+ event_type: str,
692
+ part_id: str,
693
+ ) -> dict[str, Any]:
694
+ framework = "codex"
695
+ run_id = self._event_run_id(handle)
696
+ n = self._next_seq()
697
+ return {
698
+ "schema_version": 2,
699
+ "event_id": stable_event_id(
700
+ framework, scope_id, item_id, event_type, part_id, run_id, n
701
+ ),
702
+ "seq": n,
703
+ "timestamp": time.time(),
704
+ "run_id": run_id,
705
+ "scope_id": scope_id,
706
+ "source": self._make_source(handle),
707
+ }
645
708
 
646
- if method == "item/started":
647
- tracker.observe_item(params)
648
- item = params.get("item") or params
649
- if item.get("type") == "commandExecution":
650
- call_id = str(item.get("id") or "")
651
- return self._event(
652
- handle,
653
- EventType.TOOL_CALL_BEGIN,
654
- {
655
- "call_id": call_id,
656
- "name": "codex.command",
657
- "args": {
658
- "command": str(item.get("command") or ""),
659
- "cwd": str(item.get("cwd") or ""),
660
- "command_actions": item.get("commandActions")
661
- or item.get("command_actions")
662
- or [],
663
- },
664
- },
665
- )
666
- if item.get("type") == "mcpToolCall":
667
- call_id = str(item.get("id") or "")
668
- server = str(item.get("server") or "")
669
- tool = str(item.get("tool") or "")
670
- return self._event(
671
- handle,
672
- EventType.TOOL_CALL_BEGIN,
673
- {
674
- "call_id": call_id,
675
- "name": f"mcp.{server}.{tool}" if server else f"mcp.{tool}",
676
- "args": {
677
- "server": server,
678
- "tool": tool,
679
- "arguments": item.get("arguments"),
680
- },
681
- },
682
- )
683
- return None
684
- if method == "item/completed":
685
- item = params.get("item") or params
686
- item_type = item.get("type")
687
- if item_type == "commandExecution":
688
- tracker.forget_item(params)
689
- call_id = str(item.get("id") or "")
690
- return self._event(
691
- handle,
692
- EventType.TOOL_CALL_END,
693
- {
694
- "call_id": call_id,
695
- "name": "codex.command",
696
- "result": {
697
- "status": str(item.get("status") or "completed"),
698
- "exit_code": item.get("exitCode", item.get("exit_code")),
699
- "duration_ms": item.get("durationMs", item.get("duration_ms")),
700
- "output": str(
701
- item.get("aggregatedOutput") or item.get("aggregated_output") or ""
702
- ),
703
- },
704
- },
705
- )
706
- if item_type == "mcpToolCall":
707
- tracker.forget_item(params)
708
- call_id = str(item.get("id") or "")
709
- server = str(item.get("server") or "")
710
- tool = str(item.get("tool") or "")
711
- raw_result = item.get("result")
712
- result_obj: dict[str, Any] = raw_result if isinstance(raw_result, dict) else {}
713
- raw_error = item.get("error")
714
- error_obj: dict[str, Any] = raw_error if isinstance(raw_error, dict) else {}
715
- output = self._mcp_result_text(result_obj)
716
- error_message = str(error_obj.get("message") or "")
717
- if not output and error_message:
718
- output = error_message
719
- return self._event(
720
- handle,
721
- EventType.TOOL_CALL_END,
722
- {
723
- "call_id": call_id,
724
- "name": f"mcp.{server}.{tool}" if server else f"mcp.{tool}",
725
- "result": {
726
- "status": str(item.get("status") or "completed"),
727
- "duration_ms": item.get("durationMs", item.get("duration_ms")),
728
- "output": output,
729
- **({"error": error_message} if error_message else {}),
730
- },
731
- },
732
- )
733
- if item_type != "agentMessage":
734
- tracker.forget_item(params)
735
- return None
736
- phase = tracker.runtime_phase_for_item(params)
737
- tracker.forget_item(params)
738
- text = str(item.get("text") or "")
739
- return self._event(
709
+ def _make_run_canceled(
710
+ self, handle: RunHandle, *, reason: str | None = None
711
+ ) -> RunCanceled:
712
+ framework = "codex"
713
+ run_id = self._event_run_id(handle)
714
+ scope_id = stable_scope_id(framework, run_id)
715
+ item_id = stable_item_id(framework, run_id, "$run")
716
+ return RunCanceled(
717
+ **self._canonical_kwargs(
740
718
  handle,
741
- EventType.TEXT_COMPLETED,
742
- {"text": text},
743
- phase=phase or "final_answer",
744
- )
745
- if "delta" in method or "Delta" in method or method == "item/agentMessage/delta":
746
- phase = tracker.runtime_phase_for_delta(params)
747
- delta = str(params.get("delta") or "")
748
- if not delta:
749
- return None
750
- return self._event(
751
- handle, EventType.TEXT_DELTA, {"text": delta}, phase=phase or "commentary"
752
- )
753
- if method == "item/autoApprovalReview/started":
754
- review_id = str(params.get("review_id") or params.get("reviewId") or "")
755
- if review_id:
756
- thread.pending_approvals.add(review_id)
757
- return None
758
- if method == "item/autoApprovalReview/completed":
759
- review_id = str(params.get("review_id") or params.get("reviewId") or "")
760
- thread.pending_approvals.discard(review_id)
761
- return None
762
- if (
763
- "approval" in method.lower()
764
- or "requestPermission" in method
765
- or "approval" in str(chunk.get("type") or "").lower()
766
- ):
767
- call_id = str(
768
- params.get("id") or params.get("call_id") or params.get("requestId") or ""
769
- )
770
- if call_id:
771
- thread.pending_approvals.add(call_id)
772
- return self._event(
719
+ scope_id=scope_id,
720
+ item_id=item_id,
721
+ event_type="run.canceled",
722
+ part_id="run",
723
+ ),
724
+ status="canceled",
725
+ reason=reason,
726
+ )
727
+
728
+ def _make_run_interrupted(
729
+ self, handle: RunHandle, *, reason: str | None = None
730
+ ) -> RunInterrupted:
731
+ framework = "codex"
732
+ run_id = self._event_run_id(handle)
733
+ scope_id = stable_scope_id(framework, run_id)
734
+ item_id = stable_item_id(framework, run_id, "$run")
735
+ return RunInterrupted(
736
+ **self._canonical_kwargs(
773
737
  handle,
774
- EventType.APPROVAL_REQUESTED,
775
- {
776
- "approval_id": call_id,
777
- "call_id": call_id,
778
- "kind": str(params.get("kind") or "tool"),
779
- "detail": params.get("detail")
780
- if isinstance(params.get("detail"), dict)
781
- else params,
782
- },
783
- )
784
- return None
738
+ scope_id=scope_id,
739
+ item_id=item_id,
740
+ event_type="run.interrupted",
741
+ part_id="run",
742
+ ),
743
+ status="interrupted",
744
+ reason=reason,
745
+ )
785
746
 
786
- def _event(
787
- self,
788
- handle: RunHandle,
789
- event_type: str,
790
- payload: dict,
791
- *,
792
- phase: Optional[str] = None,
793
- ) -> RuntimeEvent:
794
- request = self._requests.get(handle.run_id)
795
- return RuntimeEvent.create(
796
- event_type,
797
- agent_id=str(request.agent_id or "codex") if request is not None else "codex",
798
- user_id=(
799
- request.user_id
800
- if request is not None
801
- else str(handle.native_ref.get("user_id") or "user")
747
+ def _make_run_failed(
748
+ self, handle: RunHandle, error_message: str
749
+ ) -> RunFailed:
750
+ framework = "codex"
751
+ run_id = self._event_run_id(handle)
752
+ scope_id = stable_scope_id(framework, run_id)
753
+ item_id = stable_item_id(framework, run_id, "$run")
754
+ return RunFailed(
755
+ **self._canonical_kwargs(
756
+ handle,
757
+ scope_id=scope_id,
758
+ item_id=item_id,
759
+ event_type="run.failed",
760
+ part_id="run",
802
761
  ),
803
- session_id=handle.session_id,
804
- invocation_id=(
805
- str(request.metadata.get("invocation_id") or handle.run_id)
806
- if request is not None
807
- else handle.run_id
762
+ status="failed",
763
+ error=ErrorInfo(
764
+ code="codex_runtime_failed",
765
+ message=error_message,
766
+ source="codex",
767
+ scope_id=scope_id,
768
+ source_ref=self._make_source(handle),
808
769
  ),
809
- seq_id=self._next_seq(),
810
- phase=phase,
811
- payload=payload,
812
770
  )
813
771
 
814
772
  @staticmethod
@@ -850,6 +808,34 @@ def _resume_prompt(payload: Optional[ResumePayload]) -> Any:
850
808
  return json.dumps(payload.data, ensure_ascii=False, sort_keys=True)
851
809
 
852
810
 
811
+ def _coerce_prompt_text(value: Any) -> Any:
812
+ """把 canonical message 形态的 input 压成 SDK 可接受的文本。
813
+
814
+ ``openai-codex`` 0.147 的 run input 只接受 TextInput/str;请求侧没有
815
+ conversation preprocessing 时 ``request.input`` 可能是
816
+ ``[{role, content}]`` 历史列表,直接透传会 ``unsupported input item``。
817
+ """
818
+ if isinstance(value, str) or value is None:
819
+ return value
820
+ if isinstance(value, dict):
821
+ content = value.get("content") if "role" in value else value.get("text")
822
+ if isinstance(content, str):
823
+ return content
824
+ if isinstance(content, dict):
825
+ text = content.get("text") or content.get("content")
826
+ if isinstance(text, str):
827
+ return text
828
+ return str(value)
829
+ if isinstance(value, list):
830
+ texts = [
831
+ text
832
+ for text in (_coerce_prompt_text(item) for item in value)
833
+ if isinstance(text, str) and text
834
+ ]
835
+ return "\n".join(texts) if texts else str(value)
836
+ return str(value)
837
+
838
+
853
839
  def _request_prompt(request: StartRequest) -> Any:
854
840
  """Render canonical conversation history for a native Codex turn.
855
841
 
@@ -859,11 +845,21 @@ def _request_prompt(request: StartRequest) -> Any:
859
845
  # A resumed Codex thread already owns its transcript. Re-sending Studio's
860
846
  # transport-neutral history would duplicate every prior turn after refresh.
861
847
  if str(request.metadata.get("thread_id") or "").strip():
862
- return request.input
848
+ return (
849
+ request.input
850
+ if _is_structured_turn_input(request.input)
851
+ else _coerce_prompt_text(request.input)
852
+ )
863
853
 
864
854
  conversation = request.conversation_preprocessing()
865
855
  if conversation is None or not conversation.messages:
866
- return request.input
856
+ # Keep native text/image/mention parts intact for _build_run_input().
857
+ # Flattening this list turns an image dict into user-visible text.
858
+ return (
859
+ request.input
860
+ if _is_structured_turn_input(request.input)
861
+ else _coerce_prompt_text(request.input)
862
+ )
867
863
 
868
864
  lines: list[str] = []
869
865
  for message in conversation.messages:
@@ -880,6 +876,13 @@ def _request_prompt(request: StartRequest) -> Any:
880
876
  return "\n".join(lines) or request.input
881
877
 
882
878
 
879
+ def _is_structured_turn_input(value: Any) -> bool:
880
+ return isinstance(value, list) and any(
881
+ isinstance(item, dict) and isinstance(item.get("type"), str)
882
+ for item in value
883
+ )
884
+
885
+
883
886
  def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
884
887
  """Compose Codex skills and native text/image/mention turn input."""
885
888
  try:
@@ -905,7 +908,11 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
905
908
  if not isinstance(item, dict):
906
909
  continue
907
910
  kind = str(item.get("type") or "")
908
- if kind == "text":
911
+ if not kind and "role" in item:
912
+ # canonical conversation message({role, content});当前 input
913
+ # 已由 prompt(或 conversation preprocessing)承载,跳过历史项。
914
+ continue
915
+ if kind in {"text", "input_text"}:
909
916
  text = str(
910
917
  prompt
911
918
  if not text_replaced and isinstance(prompt, str)
@@ -914,10 +921,35 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
914
921
  text_replaced = True
915
922
  if text:
916
923
  native_items.append(TextInput(text=text))
917
- elif kind == "image" and item.get("url"):
918
- native_items.append(ImageInput(url=str(item["url"])))
924
+ elif kind in {"image", "input_image"} and (
925
+ item.get("url") or item.get("image_url")
926
+ ):
927
+ native_items.append(
928
+ ImageInput(url=str(item.get("url") or item.get("image_url")))
929
+ )
919
930
  elif kind == "localImage" and item.get("path"):
920
931
  native_items.append(LocalImageInput(path=str(item["path"])))
932
+ elif kind == "input_file" and (
933
+ item.get("file_data")
934
+ or str(item.get("file_url") or "").startswith("data:")
935
+ ):
936
+ file_path = _materialize_inline_file(
937
+ str(item.get("file_data") or item.get("file_url")),
938
+ str(item.get("filename") or "attachment"),
939
+ )
940
+ if file_path is not None:
941
+ # App Server's ``mention`` input is presentation metadata:
942
+ # current Codex versions do not include it in the model's
943
+ # user message. Always add an explicit model-visible
944
+ # attachment context as well. Small textual files are
945
+ # inlined deterministically; binary/large files expose a
946
+ # sandbox-readable path that Codex can inspect with tools.
947
+ native_items.append(
948
+ TextInput(text=_attachment_context_text(file_path, item))
949
+ )
950
+ native_items.append(
951
+ MentionInput(name=file_path.name, path=str(file_path))
952
+ )
921
953
  elif kind == "mention" and item.get("path"):
922
954
  native_items.append(
923
955
  MentionInput(
@@ -937,4 +969,74 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
937
969
  return combined
938
970
 
939
971
 
972
+ def _materialize_inline_file(data_url: str, filename: str) -> Path | None:
973
+ """Materialize a bounded Studio inline attachment for native Codex."""
974
+
975
+ match = re.fullmatch(r"data:([^;,]+)?;base64,([A-Za-z0-9+/=\s]+)", data_url)
976
+ encoded = match.group(2) if match is not None else data_url.strip()
977
+ try:
978
+ payload = base64.b64decode(encoded, validate=True)
979
+ except (ValueError, binascii.Error):
980
+ return None
981
+ if not payload or len(payload) > 10 * 1024 * 1024:
982
+ return None
983
+ safe_name = re.sub(r"[^A-Za-z0-9._-]+", "-", Path(filename).name).strip(".-")
984
+ safe_name = safe_name[:120] or "attachment"
985
+ digest = hashlib.sha256(payload).hexdigest()
986
+ path = Path("/tmp/ksadk-codex-attachments") / digest / safe_name
987
+ path.parent.mkdir(parents=True, exist_ok=True)
988
+ if not path.exists():
989
+ path.write_bytes(payload)
990
+ return path
991
+
992
+
993
+ _MAX_INLINE_ATTACHMENT_TEXT_BYTES = 64 * 1024
994
+ _TEXT_ATTACHMENT_SUFFIXES = {
995
+ ".csv",
996
+ ".html",
997
+ ".htm",
998
+ ".ini",
999
+ ".json",
1000
+ ".jsonl",
1001
+ ".log",
1002
+ ".md",
1003
+ ".py",
1004
+ ".rst",
1005
+ ".toml",
1006
+ ".tsv",
1007
+ ".txt",
1008
+ ".xml",
1009
+ ".yaml",
1010
+ ".yml",
1011
+ }
1012
+
1013
+
1014
+ def _attachment_context_text(path: Path, item: Mapping[str, Any]) -> str:
1015
+ """Build model-visible context for a materialized Responses input file."""
1016
+
1017
+ name = str(item.get("filename") or path.name).replace('"', "'")
1018
+ inline_data = item.get("inlineData")
1019
+ inline_mime = inline_data.get("mimeType") if isinstance(inline_data, Mapping) else None
1020
+ mime_type = str(item.get("mime_type") or inline_mime or "").strip().lower()
1021
+ source = str(item.get("file_data") or item.get("file_url") or "")
1022
+ data_url_match = re.match(r"data:([^;,]+)", source)
1023
+ if not mime_type and data_url_match is not None:
1024
+ mime_type = data_url_match.group(1).strip().lower()
1025
+ is_text = mime_type.startswith("text/") or path.suffix.lower() in _TEXT_ATTACHMENT_SUFFIXES
1026
+ header = f'<uploaded_attachment name="{name}" path="{path}">'
1027
+ if not is_text:
1028
+ return (
1029
+ f"{header}\n"
1030
+ "The uploaded file is available at the path above. Read it with an appropriate "
1031
+ "tool before answering questions about its contents.\n"
1032
+ "</uploaded_attachment>"
1033
+ )
1034
+
1035
+ raw = path.read_bytes()
1036
+ truncated = len(raw) > _MAX_INLINE_ATTACHMENT_TEXT_BYTES
1037
+ text = raw[:_MAX_INLINE_ATTACHMENT_TEXT_BYTES].decode("utf-8", errors="replace")
1038
+ suffix = "\n[attachment content truncated]" if truncated else ""
1039
+ return f"{header}\n{text}{suffix}\n</uploaded_attachment>"
1040
+
1041
+
940
1042
  __all__ = ["CodexRuntimeAdapter"]