agentengine-sdk-python 0.8.1__py3-none-any.whl → 0.8.3__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 (476) hide show
  1. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/METADATA +25 -12
  2. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/RECORD +421 -232
  3. ksadk/_build_provenance.json +1 -0
  4. ksadk/a2a/_space_client_events.py +381 -0
  5. ksadk/a2a/event_adapter.py +156 -64
  6. ksadk/a2a/executor.py +179 -63
  7. ksadk/a2a/space_client.py +6 -333
  8. ksadk/a2ui/core.py +222 -53
  9. ksadk/agui/_agent_helpers.py +247 -0
  10. ksadk/agui/a2ui_projection.py +12 -5
  11. ksadk/agui/agent.py +443 -259
  12. ksadk/api/client.py +528 -27
  13. ksadk/builders/code_builder.py +284 -8
  14. ksadk/builders/container_builder.py +8 -1
  15. ksadk/builders/managed_runtime_builder.py +42 -35
  16. ksadk/cli/__init__.py +23 -0
  17. ksadk/cli/cmd_dashboard.py +15 -1
  18. ksadk/cli/cmd_deploy.py +10 -2
  19. ksadk/cli/cmd_eval.py +66 -9
  20. ksadk/cli/cmd_evalset.py +284 -0
  21. ksadk/cli/cmd_files.py +5 -1
  22. ksadk/cli/cmd_hermes.py +1 -126
  23. ksadk/cli/cmd_invoke.py +28 -38
  24. ksadk/cli/cmd_managed_runtime.py +95 -0
  25. ksadk/cli/cmd_observe.py +85 -0
  26. ksadk/cli/cmd_openclaw.py +1 -435
  27. ksadk/cli/cmd_plugin.py +657 -0
  28. ksadk/cli/cmd_replay.py +28 -4
  29. ksadk/cli/cmd_studio.py +99 -8
  30. ksadk/cli/cmd_web.py +10 -2
  31. ksadk/cli/hermes_env.py +151 -0
  32. ksadk/cli/invoke_payload.py +49 -0
  33. ksadk/cli/openclaw_env.py +458 -0
  34. ksadk/codex/client.py +180 -30
  35. ksadk/codex/runtime.py +475 -331
  36. ksadk/configs/env_registry.py +94 -12
  37. ksadk/configs/env_registry_pcm.py +181 -0
  38. ksadk/configs/env_var_spec.py +18 -0
  39. ksadk/configs/global_config.py +5 -0
  40. ksadk/configs/settings.py +9 -0
  41. ksadk/context_engine/__init__.py +98 -0
  42. ksadk/context_engine/assembler.py +176 -0
  43. ksadk/context_engine/baseline.py +419 -0
  44. ksadk/context_engine/cache_observability.py +228 -0
  45. ksadk/context_engine/capabilities.py +443 -0
  46. ksadk/context_engine/contributors.py +409 -0
  47. ksadk/context_engine/hosted_pipeline.py +417 -0
  48. ksadk/context_engine/models.py +117 -0
  49. ksadk/context_engine/planner.py +698 -0
  50. ksadk/context_engine/policies.py +319 -0
  51. ksadk/context_engine/projection.py +31 -0
  52. ksadk/context_engine/shadow_plan.py +266 -0
  53. ksadk/context_engine/tokenizer.py +164 -0
  54. ksadk/conversations/__init__.py +19 -0
  55. ksadk/conversations/context.py +230 -30
  56. ksadk/conversations/contracts.py +266 -0
  57. ksadk/conversations/message_projection.py +308 -26
  58. ksadk/conversations/model_context.py +49 -3
  59. ksadk/conversations/projector.py +616 -0
  60. ksadk/conversations/reducer.py +98 -0
  61. ksadk/conversations/runtime_compaction.py +207 -5
  62. ksadk/conversations/runtime_input.py +184 -2
  63. ksadk/conversations/runtime_invocation.py +81 -3
  64. ksadk/conversations/runtime_observability.py +141 -0
  65. ksadk/conversations/runtime_payloads.py +42 -0
  66. ksadk/conversations/runtime_preparation.py +272 -2
  67. ksadk/conversations/runtime_resume.py +20 -1
  68. ksadk/conversations/runtime_stream_events.py +94 -9
  69. ksadk/conversations/semantic_summary.py +311 -1
  70. ksadk/conversations/session_lock.py +51 -0
  71. ksadk/deployment/env_forward.py +72 -0
  72. ksadk/deployment/managed_runtime.py +5 -0
  73. ksadk/deployment/providers/serverless.py +143 -69
  74. ksadk/evaluation/__init__.py +32 -0
  75. ksadk/evaluation/a2a_adapter.py +9 -1
  76. ksadk/evaluation/adapters.py +18 -5
  77. ksadk/evaluation/agent_eval_client.py +395 -0
  78. ksadk/evaluation/cloud_binding.py +80 -0
  79. ksadk/evaluation/cloud_converter.py +194 -0
  80. ksadk/evaluation/cloud_service.py +199 -0
  81. ksadk/evaluation/contracts.py +73 -4
  82. ksadk/evaluation/evaluators.py +183 -17
  83. ksadk/evaluation/evidence.py +258 -0
  84. ksadk/evaluation/executor.py +31 -6
  85. ksadk/evaluation/local_adapter.py +550 -0
  86. ksadk/evaluation/service_env.py +27 -0
  87. ksadk/evaluation/studio_build_adapter.py +253 -0
  88. ksadk/evaluation/target.py +15 -6
  89. ksadk/events/__init__.py +13 -13
  90. ksadk/events/_v1_compat/__init__.py +1 -0
  91. ksadk/events/_v1_compat/models.py +299 -0
  92. ksadk/events/_v1_compat/parser.py +247 -0
  93. ksadk/events/_v1_compat/projection.py +755 -0
  94. ksadk/events/adapters/__init__.py +34 -0
  95. ksadk/events/adapters/_a2a_snapshot.py +622 -0
  96. ksadk/events/adapters/_a2a_support.py +275 -0
  97. ksadk/events/adapters/_codex_interactions.py +375 -0
  98. ksadk/events/adapters/_codex_items.py +801 -0
  99. ksadk/events/adapters/_codex_validators.py +311 -0
  100. ksadk/events/adapters/_langgraph_support.py +785 -0
  101. ksadk/events/adapters/a2a.py +841 -0
  102. ksadk/events/adapters/adk.py +503 -0
  103. ksadk/events/adapters/codex.py +907 -0
  104. ksadk/events/adapters/langgraph.py +745 -0
  105. ksadk/events/canonical.py +440 -0
  106. ksadk/events/canonical_replay.py +497 -0
  107. ksadk/events/canonical_store.py +468 -0
  108. ksadk/events/cold_recovery.py +257 -0
  109. ksadk/events/content.py +89 -0
  110. ksadk/events/identity.py +86 -0
  111. ksadk/events/pipeline.py +455 -0
  112. ksadk/events/projections.py +66 -0
  113. ksadk/events/reducer.py +661 -0
  114. ksadk/events/replay.py +3 -36
  115. ksadk/events/runtime_event.py +20 -265
  116. ksadk/events/session_event.py +324 -0
  117. ksadk/events/store.py +3 -316
  118. ksadk/events/v1_compat.py +41 -0
  119. ksadk/harness/__init__.py +45 -35
  120. ksadk/harness/runtime.py +220 -58
  121. ksadk/identity/resolver.py +4 -3
  122. ksadk/interaction/__init__.py +28 -0
  123. ksadk/interaction/contracts.py +136 -0
  124. ksadk/interaction/ledger.py +189 -0
  125. ksadk/interaction/provider.py +122 -0
  126. ksadk/interaction/providers/__init__.py +52 -0
  127. ksadk/interaction/providers/adk.py +40 -0
  128. ksadk/interaction/providers/codex.py +94 -0
  129. ksadk/interaction/providers/langgraph.py +58 -0
  130. ksadk/kernel/__init__.py +117 -0
  131. ksadk/kernel/authorization.py +258 -0
  132. ksadk/kernel/bootstrap.py +1074 -0
  133. ksadk/kernel/contract_fingerprints.py +62 -0
  134. ksadk/kernel/contracts.py +370 -0
  135. ksadk/kernel/control.py +220 -0
  136. ksadk/kernel/errors.py +79 -0
  137. ksadk/kernel/ingress.py +1123 -0
  138. ksadk/kernel/mapping.py +111 -0
  139. ksadk/kernel/memory_store.py +1134 -0
  140. ksadk/kernel/postgres_store.py +1757 -0
  141. ksadk/kernel/recovery.py +452 -0
  142. ksadk/kernel/runtime_identity.py +103 -0
  143. ksadk/kernel/sql/001_agent_kernel.sql +120 -0
  144. ksadk/kernel/sqlite_store.py +1411 -0
  145. ksadk/kernel/state.py +145 -0
  146. ksadk/kernel/store.py +232 -0
  147. ksadk/kernel/worker.py +964 -0
  148. ksadk/knowledge_base/client.py +7 -0
  149. ksadk/knowledge_base/service.py +32 -5
  150. ksadk/memory/__init__.py +61 -0
  151. ksadk/memory/adk/backends/base_ltm_backend.py +96 -1
  152. ksadk/memory/adk/backends/http_ltm_backend.py +14 -5
  153. ksadk/memory/adk/backends/inmemory_ltm_backend.py +136 -3
  154. ksadk/memory/adk/backends/sdk_ltm_backend.py +384 -2
  155. ksadk/memory/adk/backends/sqlite_ltm_backend.py +95 -0
  156. ksadk/memory/coordinator.py +461 -0
  157. ksadk/memory/events.py +202 -0
  158. ksadk/memory/extraction.py +228 -0
  159. ksadk/memory/ltm_backend_factory.py +16 -3
  160. ksadk/memory/models.py +316 -0
  161. ksadk/memory/policy.py +215 -0
  162. ksadk/memory/provider.py +47 -0
  163. ksadk/memory/provider_adapter.py +159 -0
  164. ksadk/memory/provider_resolver.py +71 -0
  165. ksadk/memory/providers/__init__.py +5 -0
  166. ksadk/memory/providers/local_sqlite.py +497 -0
  167. ksadk/memory/resolved_policy.py +145 -0
  168. ksadk/memory/service.py +188 -6
  169. ksadk/model_proxy/bootstrap.py +16 -9
  170. ksadk/model_proxy/detect.py +202 -9
  171. ksadk/model_proxy/namespace.py +39 -15
  172. ksadk/model_proxy/server.py +24 -5
  173. ksadk/model_proxy/transform.py +60 -2
  174. ksadk/observability/__init__.py +25 -0
  175. ksadk/observability/session_log.py +420 -0
  176. ksadk/observability/trajectory.py +105 -0
  177. ksadk/plugins/__init__.py +167 -0
  178. ksadk/plugins/bridges/__init__.py +45 -0
  179. ksadk/plugins/bridges/codex.py +552 -0
  180. ksadk/plugins/bridges/dsh.py +1116 -0
  181. ksadk/plugins/builtins.py +747 -0
  182. ksadk/plugins/bundle.py +246 -0
  183. ksadk/plugins/bundle_security.py +167 -0
  184. ksadk/plugins/composition.py +529 -0
  185. ksadk/plugins/context_contributor.py +437 -0
  186. ksadk/plugins/contracts.py +451 -0
  187. ksadk/plugins/dsh_toolchain.py +935 -0
  188. ksadk/plugins/ecosystem_bridge.py +1105 -0
  189. ksadk/plugins/ecosystem_probe.py +191 -0
  190. ksadk/plugins/host.py +724 -0
  191. ksadk/plugins/providers/__init__.py +85 -0
  192. ksadk/plugins/providers/bundles/ksadk-codex/cordis.patch.yml +3 -0
  193. ksadk/plugins/providers/bundles/ksadk-codex/index.mjs +18 -0
  194. ksadk/plugins/providers/bundles/ksadk-codex/package.json +19 -0
  195. ksadk/plugins/providers/bundles/ksadk-harness/cordis.patch.yml +3 -0
  196. ksadk/plugins/providers/bundles/ksadk-harness/index.mjs +19 -0
  197. ksadk/plugins/providers/bundles/ksadk-harness/package.json +19 -0
  198. ksadk/plugins/providers/codex.py +722 -0
  199. ksadk/plugins/providers/codex_dsh.py +83 -0
  200. ksadk/plugins/providers/dsh.py +951 -0
  201. ksadk/plugins/providers/dsh_descriptor_host.py +226 -0
  202. ksadk/plugins/providers/harness.py +627 -0
  203. ksadk/plugins/providers/harness_dsh.py +91 -0
  204. ksadk/plugins/providers/legacy.py +215 -0
  205. ksadk/plugins/providers/legacy_catalog.py +100 -0
  206. ksadk/plugins/providers/shipped_dsh.py +370 -0
  207. ksadk/plugins/resolver.py +331 -0
  208. ksadk/plugins/subagent_providers/__init__.py +12 -0
  209. ksadk/plugins/subagent_providers/codex.py +443 -0
  210. ksadk/plugins/subagents.py +333 -0
  211. ksadk/prompts/__init__.py +71 -0
  212. ksadk/prompts/compiler.py +215 -0
  213. ksadk/prompts/models.py +82 -0
  214. ksadk/prompts/projection.py +89 -0
  215. ksadk/prompts/resolved.py +137 -0
  216. ksadk/prompts/sources.py +233 -0
  217. ksadk/runners/_langgraph_runner_streams.py +822 -0
  218. ksadk/runners/adk_runner.py +5 -5
  219. ksadk/runners/base_runner.py +16 -0
  220. ksadk/runners/factory.py +1 -2
  221. ksadk/runners/langgraph_runner.py +198 -437
  222. ksadk/runtime/_runner_adapter/__init__.py +1 -0
  223. ksadk/runtime/_runner_adapter/stream_mapping.py +797 -0
  224. ksadk/runtime/adapter.py +119 -2
  225. ksadk/runtime/conversation_execution.py +316 -182
  226. ksadk/runtime/executor.py +144 -3
  227. ksadk/runtime/factory.py +268 -8
  228. ksadk/runtime/framework_adapters.py +18 -0
  229. ksadk/runtime/hosted_finalizer.py +268 -0
  230. ksadk/runtime/launch.py +14 -1
  231. ksadk/runtime/preprocessing.py +25 -3
  232. ksadk/runtime/runner_adapter.py +293 -508
  233. ksadk/runtime/usage.py +33 -0
  234. ksadk/sandbox/__init__.py +2 -1
  235. ksadk/sandbox/backends/e2b.py +35 -2
  236. ksadk/sandbox/factory.py +72 -0
  237. ksadk/scheduler/__init__.py +18 -0
  238. ksadk/scheduler/calendar.py +102 -0
  239. ksadk/scheduler/contracts.py +254 -0
  240. ksadk/scheduler/dispatcher.py +129 -0
  241. ksadk/scheduler/engine.py +416 -0
  242. ksadk/scheduler/sqlite_store.py +563 -0
  243. ksadk/server/composition.py +8 -0
  244. ksadk/server/factory.py +69 -6
  245. ksadk/server/routes/kernel_ingress.py +258 -0
  246. ksadk/server/routes/openai_compat.py +88 -8
  247. ksadk/server/routes/projection.py +35 -20
  248. ksadk/server/routes/run.py +94 -0
  249. ksadk/server/routes/sessions.py +2 -0
  250. ksadk/server/routes/streaming.py +5 -2
  251. ksadk/server/static/assets/{ArtifactsPanel-CakCqxEN.js → ArtifactsPanel-10GPXIJ6.js} +1 -1
  252. ksadk/server/static/assets/{CodeBlock-CynLknyO.js → CodeBlock-BXk1l-G1.js} +4 -4
  253. ksadk/server/static/assets/MathMessageMarkdown-BorAY7qD.css +1 -0
  254. ksadk/server/static/assets/{MathMessageMarkdown-3sQdOysS.js → MathMessageMarkdown-D9uB55Oh.js} +4 -4
  255. ksadk/server/static/assets/MermaidBlock--OEYoXIJ.js +312 -0
  256. ksadk/server/static/assets/NativeTerminalPanel-DrEe1syq.js +1 -0
  257. ksadk/server/static/assets/abnfDiagram-VCTEODGH-eeTFJLvo.js +1 -0
  258. ksadk/server/static/assets/arc-S3ZNbIAd.js +1 -0
  259. ksadk/server/static/assets/architecture-7GRP2DOG-Dk9nRQ6-.js +1 -0
  260. ksadk/server/static/assets/architectureDiagram-5GKGNRK7-CRjgEJDX.js +36 -0
  261. ksadk/server/static/assets/blockDiagram-NRAW4CY4-C7bsBoNH.js +129 -0
  262. ksadk/server/static/assets/c4Diagram-UCG6FXSJ-BX3c33Ob.js +38 -0
  263. ksadk/server/static/assets/channel-s354Yo6o.js +1 -0
  264. ksadk/server/static/assets/chunk-2Q5K7J3B-CDpPpKR5.js +1 -0
  265. ksadk/server/static/assets/{chunk-FMBD7UC4-B_fH2YuH.js → chunk-5VM5RSS4-bZ7ws-8m.js} +1 -1
  266. ksadk/server/static/assets/{chunk-ND2GUHAM-D6rFLaVV.js → chunk-F27PBJKO-D2r0pvhY.js} +1 -1
  267. ksadk/server/static/assets/chunk-G27WJ6UU-CZwF3oUP.js +231 -0
  268. ksadk/server/static/assets/{chunk-4BX2VUAB-B9drTgOW.js → chunk-JWPE2WC7-DigFYCML.js} +1 -1
  269. ksadk/server/static/assets/chunk-LCL6LL3I-CnorGV5q.js +206 -0
  270. ksadk/server/static/assets/chunk-POPQ4Y6H-CexntQA-.js +1 -0
  271. ksadk/server/static/assets/chunk-RHFEMEQ7-CHTC7UxA.js +168 -0
  272. ksadk/server/static/assets/chunk-SVP7TREG-BC1EWt_-.js +88 -0
  273. ksadk/server/static/assets/chunk-XXDRQBXY-BTG24xN0.js +1 -0
  274. ksadk/server/static/assets/classDiagram-DTDB5LWJ-Dd0nucl9.js +1 -0
  275. ksadk/server/static/assets/classDiagram-v2-JRS7N3AN-Dd0nucl9.js +1 -0
  276. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-ChNs_Mdk.js → cose-bilkent-JH36ORCC-CkonRdAS.js} +1 -1
  277. ksadk/server/static/assets/cynefin-OW5HDTMX-BBlT4uMf.js +1 -0
  278. ksadk/server/static/assets/cynefinDiagram-5FMLGOSQ-DEOCBe15.js +62 -0
  279. ksadk/server/static/assets/cytoscape.esm-CyCl8rPi.js +321 -0
  280. ksadk/server/static/assets/dagre-3AP2YEHR-DBThPuLa.js +4 -0
  281. ksadk/server/static/assets/dagre-ClTPrTht.js +1 -0
  282. ksadk/server/static/assets/diagram-S7CK7UJ4-DsZHrnpJ.js +30 -0
  283. ksadk/server/static/assets/diagram-UQ7AKVKN-BJR4nhVr.js +41 -0
  284. ksadk/server/static/assets/diagram-VSXAHHWV-xzcERX0a.js +3 -0
  285. ksadk/server/static/assets/diagram-VX7I27RA-tn2xM1xu.js +24 -0
  286. ksadk/server/static/assets/diagram-Z3DM3KII-BZPGBkw5.js +24 -0
  287. ksadk/server/static/assets/{dist-gLnrGsoU.js → dist-4zN1_tq5.js} +1 -1
  288. ksadk/server/static/assets/{dist-C1sz5Eo3.js → dist-8VWI-2bZ.js} +1 -1
  289. ksadk/server/static/assets/{dist-BjOdlDjj.js → dist-B-46q3Hk.js} +1 -1
  290. ksadk/server/static/assets/{dist-o2OVmKvt.js → dist-B0gdk4rl.js} +1 -1
  291. ksadk/server/static/assets/{dist-4N2NMVYg.js → dist-B1oWRmrH.js} +1 -1
  292. ksadk/server/static/assets/{dist-CmZHzlwZ.js → dist-BI1qQH4n.js} +1 -1
  293. ksadk/server/static/assets/{dist-X4TnqwOK.js → dist-BJLUwH6W.js} +1 -1
  294. ksadk/server/static/assets/{dist-B5oS1zcj.js → dist-Bbwqa8TG.js} +1 -1
  295. ksadk/server/static/assets/{dist-j6Dxmf_j.js → dist-Btyo4vX4.js} +1 -1
  296. ksadk/server/static/assets/{dist-DQU9EWw-.js → dist-C71CsujO.js} +1 -1
  297. ksadk/server/static/assets/{dist-BIlKoW0k.js → dist-CXpH_Rdd.js} +1 -1
  298. ksadk/server/static/assets/{dist-DJN_6Y39.js → dist-Cq4OL6nO.js} +1 -1
  299. ksadk/server/static/assets/{dist-Dli7qW-B.js → dist-D1V3f1B4.js} +1 -1
  300. ksadk/server/static/assets/{dist-fhOuWds5.js → dist-DJmCPRVB.js} +1 -1
  301. ksadk/server/static/assets/{dist-CWV4lDr1.js → dist-DQAB0qn_.js} +2 -2
  302. ksadk/server/static/assets/{dist-D6MpCZtz.js → dist-DUwKa_l6.js} +1 -1
  303. ksadk/server/static/assets/dist-DdRBIceA.js +1 -0
  304. ksadk/server/static/assets/{dist-DWKByxWV.js → dist-DfGFrb0v.js} +1 -1
  305. ksadk/server/static/assets/{dist-DsHPdk3W.js → dist-DjhwGUgZ.js} +1 -1
  306. ksadk/server/static/assets/{dist-B-jNR6Vn.js → dist-DyqV4fL4.js} +1 -1
  307. ksadk/server/static/assets/{dist-BB3-OwTQ.js → dist-M67Hwu0n.js} +1 -1
  308. ksadk/server/static/assets/{dist-_pHPTyZ-.js → dist-u8tVH5Ak.js} +1 -1
  309. ksadk/server/static/assets/ebnfDiagram-PWID7BFC-DVfMsLge.js +1 -0
  310. ksadk/server/static/assets/erDiagram-SSCWMZ5O-CWEZSYaw.js +99 -0
  311. ksadk/server/static/assets/eventmodeling-NTZA5JFV-Dr5okd8V.js +1 -0
  312. ksadk/server/static/assets/flowDiagram-A5DVABFB-VEO-cVcO.js +1 -0
  313. ksadk/server/static/assets/ganttDiagram-EL5Y4UJY-TWIxoyYL.js +292 -0
  314. ksadk/server/static/assets/gitGraph-4MIJSDKK-CKiKY9Cf.js +1 -0
  315. ksadk/server/static/assets/gitGraphDiagram-WWUBYQGX-DDh2hd2S.js +106 -0
  316. ksadk/server/static/assets/index-B2k_urY8.js +241 -0
  317. ksadk/server/static/assets/index-ILPt6IWF.css +2 -0
  318. ksadk/server/static/assets/info-A6RAGUB7-ubA4Nm_p.js +1 -0
  319. ksadk/server/static/assets/infoDiagram-RXCK75RN-CgoyWiDR.js +2 -0
  320. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-K0OOfVna.js → ishikawaDiagram-5VMMS53U-CyRIu_om.js} +6 -6
  321. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-8LeOJaZp.js → journeyDiagram-EYS64GPL-DN5JXHzN.js} +6 -6
  322. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-C6uCgZrW.js → kanban-definition-3QL26DDD-CNP3ui66.js} +10 -10
  323. ksadk/server/static/assets/katex-DWEzQy1d.js +257 -0
  324. ksadk/server/static/assets/{linear-hdBcYzM8.js → linear-C-qHp7d2.js} +1 -1
  325. ksadk/server/static/assets/mermaid-parser.core-Cl-K943T.js +166 -0
  326. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-Bjy_2v4_.js → mindmap-definition-FBJOCRG2-D5DB5mYm.js} +29 -29
  327. ksadk/server/static/assets/packet-AYTQ26CC-C7R3NUlK.js +1 -0
  328. ksadk/server/static/assets/pegDiagram-XKGWAZYB-BQVc9ia_.js +1 -0
  329. ksadk/server/static/assets/pie-WAS4IAKB-BIT6qjr3.js +1 -0
  330. ksadk/server/static/assets/pieDiagram-E7YTZNPT-B6UCAvtY.js +39 -0
  331. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-BmkRbwCg.js → quadrantDiagram-AXDQQJYC-mIr2d3eb.js} +6 -6
  332. ksadk/server/static/assets/radar-RG4KPBEZ-l5noef9X.js +1 -0
  333. ksadk/server/static/assets/railroad-74A4TZTK-Baea6ZvM.js +1 -0
  334. ksadk/server/static/assets/railroad-abnf-HS5TGJTU-OKn8zZnU.js +1 -0
  335. ksadk/server/static/assets/railroad-ebnf-LZEXJU2U-DdKAKC4l.js +1 -0
  336. ksadk/server/static/assets/railroad-peg-WCYAUIDC-Bds8udlG.js +1 -0
  337. ksadk/server/static/assets/railroadDiagram-O6MQD6OU-Dt3FFDmK.js +1 -0
  338. ksadk/server/static/assets/requirementDiagram-EFPCY7ZU-Cz_vQtgF.js +84 -0
  339. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-l6iXmU65.js → sankeyDiagram-P5KCCOFB-wlO1TsH5.js} +7 -7
  340. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-Cq6UBeQB.js → sequenceDiagram-WJ2MYXX4-CHRbElYq.js} +10 -10
  341. ksadk/server/static/assets/sizeCapture-X5ZJPWSS-DXX54iyO.js +1 -0
  342. ksadk/server/static/assets/stateDiagram-HBIQ2CUA-Dp_lw9II.js +1 -0
  343. ksadk/server/static/assets/stateDiagram-v2-4QOOHH4V-5b-8o2iS.js +1 -0
  344. ksadk/server/static/assets/swimlanes-XN3QIQJK-D8O5UlLG.js +1 -0
  345. ksadk/server/static/assets/swimlanesDiagram-VK2B7HYN-uf6WPRA4.js +8 -0
  346. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-DEz24vV0.js → timeline-definition-24CTP7MA-8mfKcxvG.js} +7 -7
  347. ksadk/server/static/assets/treeView-Q6P3EWNA-CK5CSrSO.js +1 -0
  348. ksadk/server/static/assets/treemap-WGGIJYW6-Ch2BKgAM.js +1 -0
  349. ksadk/server/static/assets/vennDiagram-4TSXK5OY-EnNl0DE7.js +34 -0
  350. ksadk/server/static/assets/wardley-WFR3VGLG-YW3Q0ml_.js +1 -0
  351. ksadk/server/static/assets/wardleyDiagram-VM6X3IG4-DBu5fbYr.js +78 -0
  352. ksadk/server/static/assets/xychartDiagram-S5SC5T6Z-BSVXs4IY.js +7 -0
  353. ksadk/server/static/index.html +2 -2
  354. ksadk/sessions/__init__.py +26 -9
  355. ksadk/sessions/_local_service_sync.py +688 -0
  356. ksadk/sessions/_local_tables.py +21 -0
  357. ksadk/sessions/_postgres_schema.py +305 -0
  358. ksadk/sessions/_postgres_tables.py +15 -0
  359. ksadk/sessions/base.py +80 -1
  360. ksadk/sessions/in_memory.py +53 -2
  361. ksadk/sessions/local_service.py +63 -604
  362. ksadk/sessions/postgres_service.py +58 -148
  363. ksadk/sessions/resilient.py +35 -1
  364. ksadk/skills/service_client.py +48 -0
  365. ksadk/studio/agent_lifecycle.py +26 -20
  366. ksadk/studio/api.py +1063 -69
  367. ksadk/studio/api_catalog_routes.py +5 -0
  368. ksadk/studio/api_contracts.py +148 -6
  369. ksadk/studio/api_memory_routes.py +82 -0
  370. ksadk/studio/api_plugin_routes.py +480 -0
  371. ksadk/studio/attachment_store.py +187 -0
  372. ksadk/studio/authoring.py +259 -28
  373. ksadk/studio/authoring_coordinator.py +809 -67
  374. ksadk/studio/builder.py +197 -40
  375. ksadk/studio/capabilities.py +17 -0
  376. ksadk/studio/cloud.py +1920 -95
  377. ksadk/studio/codex_agent_service.py +137 -15
  378. ksadk/studio/codex_authoring.py +469 -0
  379. ksadk/studio/codex_builder.py +177 -25
  380. ksadk/studio/codex_manifest.py +71 -22
  381. ksadk/studio/codex_run.py +59 -4
  382. ksadk/studio/compatibility_report.py +445 -0
  383. ksadk/studio/compiler.py +7 -1
  384. ksadk/studio/contracts.py +234 -4
  385. ksadk/studio/dsh_provider_registration.py +768 -0
  386. ksadk/studio/event_store.py +79 -101
  387. ksadk/studio/framework_run.py +264 -3
  388. ksadk/studio/hosted_kernel.py +317 -0
  389. ksadk/studio/manifest_resolver.py +131 -0
  390. ksadk/studio/model_client.py +95 -9
  391. ksadk/studio/model_profile_service.py +1 -1
  392. ksadk/studio/operations.py +47 -5
  393. ksadk/studio/otel_trace.py +348 -42
  394. ksadk/studio/pcm_memory.py +85 -0
  395. ksadk/studio/plugin_composition.py +301 -0
  396. ksadk/studio/plugin_kernel_adapter.py +128 -0
  397. ksadk/studio/plugin_runtime.py +652 -0
  398. ksadk/studio/repository.py +23 -2
  399. ksadk/studio/resource_catalog.py +49 -1
  400. ksadk/studio/run_service.py +1266 -202
  401. ksadk/studio/runtime_source.py +51 -17
  402. ksadk/studio/scheduler_runtime.py +374 -0
  403. ksadk/studio/scheduler_service.py +211 -0
  404. ksadk/studio/service.py +1815 -117
  405. ksadk/studio/shared_web.py +199 -50
  406. ksadk/studio/soul.py +52 -0
  407. ksadk/studio/static/assets/index-DXs1Eq4M.css +1 -0
  408. ksadk/studio/static/assets/index-DeDqwTsD.js +283 -0
  409. ksadk/studio/static/index.html +2 -2
  410. ksadk/studio/templates.py +100 -8
  411. ksadk/studio/validator.py +20 -1
  412. ksadk/studio/workspace.py +31 -32
  413. ksadk/tracing/setup.py +304 -3
  414. ksadk/version.py +1 -1
  415. ksadk_runtime_common/schemas/runtime_event_v1.json +37 -0
  416. ksadk_runtime_common/schemas/runtime_event_v2.json +271 -0
  417. ksadk_runtime_common/workspace_files/path_utils.py +41 -0
  418. ksadk/events/parser.py +0 -191
  419. ksadk/server/static/assets/MathMessageMarkdown-DeYh8QPr.css +0 -1
  420. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +0 -303
  421. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +0 -1
  422. ksadk/server/static/assets/arc-DrgjcKhl.js +0 -1
  423. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +0 -1
  424. ksadk/server/static/assets/architectureDiagram-3BPJPVTR-BXoVRbQ6.js +0 -36
  425. ksadk/server/static/assets/blockDiagram-GPEHLZMM-BnaB3Ywv.js +0 -132
  426. ksadk/server/static/assets/c4Diagram-AAUBKEIU-Dw4weZcp.js +0 -10
  427. ksadk/server/static/assets/channel-Dt3rQTFI.js +0 -1
  428. ksadk/server/static/assets/chunk-2J33WTMH-ChjpLK7j.js +0 -1
  429. ksadk/server/static/assets/chunk-55IACEB6-CK4ROATy.js +0 -1
  430. ksadk/server/static/assets/chunk-727SXJPM-B9KtTTgw.js +0 -206
  431. ksadk/server/static/assets/chunk-AQP2D5EJ-1O1mki3m.js +0 -231
  432. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +0 -1
  435. ksadk/server/static/assets/cytoscape.esm-DelgaX4f.js +0 -321
  436. ksadk/server/static/assets/dagre-BM42HDAG-D0QXg_MQ.js +0 -4
  437. ksadk/server/static/assets/dagre-Bx709z4p.js +0 -1
  438. ksadk/server/static/assets/diagram-2AECGRRQ-DBDDKwBQ.js +0 -43
  439. ksadk/server/static/assets/diagram-5GNKFQAL-BkwDOxmO.js +0 -10
  440. ksadk/server/static/assets/diagram-KO2AKTUF-Bv8waYvY.js +0 -3
  441. ksadk/server/static/assets/diagram-LMA3HP47-e1VECDdu.js +0 -24
  442. ksadk/server/static/assets/diagram-OG6HWLK6-Ds9adfRl.js +0 -24
  443. ksadk/server/static/assets/dist-B5J2uhNk.js +0 -1
  444. ksadk/server/static/assets/erDiagram-TEJ5UH35-BPItFKH6.js +0 -85
  445. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +0 -1
  446. ksadk/server/static/assets/flowDiagram-I6XJVG4X-BbYB9w5Y.js +0 -162
  447. ksadk/server/static/assets/ganttDiagram-6RSMTGT7-TQXa0KXv.js +0 -292
  448. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +0 -1
  449. ksadk/server/static/assets/gitGraphDiagram-PVQCEYII-bKblCnpa.js +0 -106
  450. ksadk/server/static/assets/graphlib-B8gBHxth.js +0 -1
  451. ksadk/server/static/assets/index-R52pJJvg.js +0 -177
  452. ksadk/server/static/assets/index-jJr3_955.css +0 -2
  453. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +0 -1
  454. ksadk/server/static/assets/infoDiagram-5YYISTIA-BGVT7J_i.js +0 -2
  455. ksadk/server/static/assets/katex-CYWseY5E.js +0 -265
  456. ksadk/server/static/assets/mermaid-parser.core-BWrDjSZM.js +0 -161
  457. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +0 -1
  458. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +0 -1
  459. ksadk/server/static/assets/pieDiagram-4H26LBE5-GmmqNiVH.js +0 -30
  460. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +0 -1
  461. ksadk/server/static/assets/requirementDiagram-4Y6WPE33-DSWFqL7c.js +0 -84
  462. ksadk/server/static/assets/stateDiagram-AJRCARHV-qzTQpg3Y.js +0 -1
  463. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +0 -1
  464. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +0 -1
  465. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +0 -1
  466. ksadk/server/static/assets/vennDiagram-CIIHVFJN-CMCixki2.js +0 -34
  467. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +0 -1
  468. ksadk/server/static/assets/wardleyDiagram-YWT4CUSO-CVzB8F9K.js +0 -78
  469. ksadk/server/static/assets/xychartDiagram-2RQKCTM6-BFBqBLc1.js +0 -7
  470. ksadk/studio/evaluation.py +0 -192
  471. ksadk/studio/static/assets/index-CaEN0eTR.css +0 -1
  472. ksadk/studio/static/assets/index-DI2eY_NH.js +0 -252
  473. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/WHEEL +0 -0
  474. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/entry_points.txt +0 -0
  475. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.dist-info}/licenses/LICENSE +0 -0
  476. {agentengine_sdk_python-0.8.1.dist-info → agentengine_sdk_python-0.8.3.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):
@@ -87,11 +107,13 @@ class CodexRuntimeAdapter(RuntimeAdapter):
87
107
  *,
88
108
  sandbox_read_only: bool = True,
89
109
  turn_timeout_seconds: Optional[float] = None,
110
+ bound_skill_paths: Mapping[str, str] | None = None,
90
111
  ) -> None:
91
112
  super().__init__(_CodexAsBaseRuntime(client))
92
113
  self._client = client
93
114
  self._sandbox_read_only = sandbox_read_only
94
115
  self._turn_timeout_seconds = turn_timeout_seconds
116
+ self._bound_skill_paths = dict(bound_skill_paths or {})
95
117
  self._threads: dict[str, _CodexThread] = {}
96
118
  self._requests: dict[str, StartRequest] = {}
97
119
  self._known_threads: set[str] = set()
@@ -101,12 +123,59 @@ class CodexRuntimeAdapter(RuntimeAdapter):
101
123
  # 可观测:最近一次 cancel 级联丢弃的审批集(contract test 断言用)。
102
124
  self.last_cancel_dropped_approvals: set[str] = set()
103
125
  self._seq = 0
126
+ self._closed = False
127
+
128
+ # ---- capability matrix(v1,诚实声明) ----
129
+
130
+ def capabilities(self) -> RuntimeCapabilityMatrix:
131
+ """Codex 真实矩阵:thread 级 cancel/pause/resume + 审批 submit + snapshot
132
+ checkpoint 均为后端原生能力;attach/durable_restore 未实现(线程表在本进程,
133
+ attach seam 缺失),steer/inject 无原生通道。
134
+ """
135
+
136
+ def _unavailable(reason: str) -> RuntimeCapability:
137
+ return RuntimeCapability(supported=False, mode="unavailable", reason=reason)
138
+
139
+ return RuntimeCapabilityMatrix(
140
+ cancel=RuntimeCapability(supported=True, mode="native"),
141
+ pause=RuntimeCapability(supported=True, mode="native"),
142
+ resume=RuntimeCapability(supported=True, mode="native"),
143
+ submit_interaction=RuntimeCapability(supported=True, mode="native"),
144
+ attach=_unavailable("codex_process_local_thread_table"),
145
+ steer=_unavailable("runtime_no_native_steer"),
146
+ inject=_unavailable("runtime_no_native_inject"),
147
+ checkpoint=RuntimeCapability(supported=True, mode="native"),
148
+ durable_restore=_unavailable("codex_durable_restore_requires_attach_seam"),
149
+ goal=RuntimeCapability(supported=True, mode="native"),
150
+ loop=_unavailable("codex_loop_requires_run_control_spec"),
151
+ plan=RuntimeCapability(supported=True, mode="native"),
152
+ interaction_mode="live_submit",
153
+ )
104
154
 
105
155
  # ---- 六动词 ----
106
156
 
107
157
  async def start(self, request: StartRequest) -> RunHandle:
108
158
  # 新 thread 由后端分配真实 thread_id(thread_start);metadata 携带的 thread_id
109
159
  # 表示接入既有 thread(resume 语义,run_turn 时按 resume 接入)。
160
+ if self._bound_skill_paths:
161
+ skills = request.config.get("skills") if request.config else None
162
+ if isinstance(skills, list):
163
+ projected_skills = []
164
+ for item in skills:
165
+ if not isinstance(item, dict):
166
+ continue
167
+ name = str(item.get("name") or "").strip()
168
+ projected_skills.append(
169
+ {
170
+ **item,
171
+ "path": self._bound_skill_paths.get(
172
+ name, str(item.get("path") or "")
173
+ ),
174
+ }
175
+ )
176
+ request = request.model_copy(
177
+ update={"config": {**request.config, "skills": projected_skills}}
178
+ )
110
179
  provided = request.metadata.get("thread_id")
111
180
  if provided:
112
181
  thread_id = str(provided)
@@ -128,9 +197,18 @@ class CodexRuntimeAdapter(RuntimeAdapter):
128
197
  cwd = request.config.get("cwd")
129
198
  if cwd:
130
199
  thread_config["cwd"] = str(cwd)
200
+ # AgentKernel creates one adapter/transport per durable turn and
201
+ # closes it after the canonical terminal event. The next turn
202
+ # therefore resumes the native thread from a new app-server
203
+ # process; an ephemeral Codex thread has no rollout and cannot be
204
+ # resumed across that transport boundary.
205
+ thread_config.setdefault("ephemeral", False)
131
206
  thread_id = await self._client.start_thread(thread_config)
132
207
  self._known_threads.add(thread_id)
133
- thread = _CodexThread(thread_id=thread_id)
208
+ thread = _CodexThread(
209
+ thread_id=thread_id,
210
+ continuation_preexisting=bool(provided),
211
+ )
134
212
  thread.__dict__["_start_request"] = request
135
213
  self._threads[thread_id] = thread
136
214
  self._requests[thread_id] = request
@@ -245,7 +323,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
245
323
  raise ValueError(f"thread {handle.run_id} 已被中断/杀进程,不持久化,不可 resume")
246
324
  self._pending_cancels.discard(handle.run_id)
247
325
  self._known_threads.add(target.id)
248
- thread = _CodexThread(thread_id=target.id)
326
+ thread = _CodexThread(
327
+ thread_id=target.id,
328
+ continuation_preexisting=True,
329
+ )
249
330
  thread.__dict__["_resume"] = {"target": target, "payload": payload}
250
331
  request = self._requests.get(handle.run_id)
251
332
  if request is not None:
@@ -294,19 +375,43 @@ class CodexRuntimeAdapter(RuntimeAdapter):
294
375
  )
295
376
 
296
377
  async def close(self, handle: RunHandle) -> None:
297
- thread = self._threads.pop(handle.run_id, None)
298
- self._requests.pop(handle.run_id, None)
299
- if thread is not None:
378
+ self._do_not_persist.add(handle.run_id)
379
+ await self.close_all()
380
+
381
+ async def close_all(self) -> None:
382
+ """Dispose every thread and the activation-owned App Server process.
383
+
384
+ One ``CodexRuntimeAdapter`` owns one client transport. Closing any
385
+ attached Kernel handle therefore closes the transport as a unit; this
386
+ additive helper also lets a draining AgentProvider clean up a runtime
387
+ that has been created but not started yet.
388
+ """
389
+
390
+ if self._closed:
391
+ return
392
+ self._closed = True
393
+ threads = tuple(self._threads.values())
394
+ active_threads = tuple(
395
+ thread for thread in threads if thread.streaming and not thread.done
396
+ )
397
+ for thread in active_threads:
300
398
  thread.interrupt_event.set()
301
399
  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)
400
+ for thread in active_threads:
401
+ await self._client.interrupt_active_turn(thread.thread_id)
304
402
  finally:
305
403
  # AsyncCodex.close owns terminate/wait/kill for the app-server child.
306
404
  await self._client.close()
307
- self._do_not_persist.add(handle.run_id)
308
- self._known_threads.discard(handle.run_id)
309
- self._pending_cancels.discard(handle.run_id)
405
+ thread_ids = {
406
+ *self._known_threads,
407
+ *self._threads,
408
+ *self._requests,
409
+ }
410
+ self._do_not_persist.update(thread_ids)
411
+ self._threads.clear()
412
+ self._requests.clear()
413
+ self._known_threads.clear()
414
+ self._pending_cancels.clear()
310
415
 
311
416
  # ---- stream → RuntimeEvent(phase 翻译 + 中断竞速) ----
312
417
 
@@ -326,18 +431,12 @@ class CodexRuntimeAdapter(RuntimeAdapter):
326
431
 
327
432
  if handle.run_id in self._pending_cancels:
328
433
  self._pending_cancels.discard(handle.run_id)
329
- yield self._event(
434
+ yield self._make_run_canceled(
330
435
  handle,
331
- EventType.RUN_CANCELED,
332
- {
333
- "status": "cancelled",
334
- "cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
335
- },
436
+ reason=f"pending_cancel:{CancelResult.PENDING_CANCEL_RECORDED.value}",
336
437
  )
337
438
  return
338
439
 
339
- yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
340
- tracker = CodexPhaseTracker()
341
440
  request = thread.__dict__.get("_start_request")
342
441
  resume_state = thread.__dict__.get("_resume")
343
442
  if request is not None:
@@ -350,21 +449,8 @@ class CodexRuntimeAdapter(RuntimeAdapter):
350
449
  thread.streaming = True
351
450
  thread.turn_id = thread.turn_id or f"turn_{thread.thread_id}"
352
451
  try:
353
- async for event in self._map_codex_stream(handle, thread, tracker, run_input):
452
+ async for event in self._map_codex_stream(handle, thread, run_input):
354
453
  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
454
  except asyncio.CancelledError:
369
455
  thread.interrupted = True
370
456
  self._do_not_persist.add(handle.run_id)
@@ -377,18 +463,10 @@ class CodexRuntimeAdapter(RuntimeAdapter):
377
463
  # Closing the SDK transport terminates and waits for the app-server
378
464
  # child even when the stream is stuck between notifications.
379
465
  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
466
+ yield self._make_run_failed(handle, "codex turn timed out")
467
+ except Exception as exc: # noqa: BLE001 通用兜底:任何异常都发 RunFailed
386
468
  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
- )
469
+ yield self._make_run_failed(handle, str(exc))
392
470
  finally:
393
471
  thread.streaming = False
394
472
  thread.done = True
@@ -398,10 +476,15 @@ class CodexRuntimeAdapter(RuntimeAdapter):
398
476
  self,
399
477
  handle: RunHandle,
400
478
  thread: _CodexThread,
401
- tracker: CodexPhaseTracker,
402
479
  prompt: Any,
403
480
  ) -> AsyncIterator[RuntimeEvent]:
404
481
  request = thread.__dict__.get("_start_request") or thread.__dict__.get("_request_config")
482
+ adapter = CodexEventAdapter(
483
+ known_thread_ids=(thread.thread_id,)
484
+ if thread.continuation_preexisting
485
+ else (),
486
+ )
487
+ context = CodexAdapterContext(run_id=self._event_run_id(handle))
405
488
  run_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
406
489
  if request is not None and request.config:
407
490
  for key in ("sandbox", "approval_mode", "summary", "collaboration_mode"):
@@ -496,14 +579,11 @@ class CodexRuntimeAdapter(RuntimeAdapter):
496
579
  chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
497
580
  chunk_task = None
498
581
  thread.interrupted = True
499
- # AGUI 投射器对 RUN_INTERRUPTED 无兜底,必须显式发,否则 raise
500
- yield self._event(
582
+ # Runtime interrupt (user pause) — adapter doesn't know;
583
+ # emit canonical RunInterrupted explicitly.
584
+ yield self._make_run_interrupted(
501
585
  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
- },
586
+ reason="user_pause" if thread.paused else "runtime_interrupt",
507
587
  )
508
588
  return
509
589
  for task in pending:
@@ -513,8 +593,58 @@ class CodexRuntimeAdapter(RuntimeAdapter):
513
593
  chunk = chunk_task.result()
514
594
  if chunk is _STREAM_STOP:
515
595
  return
516
- event = self._codex_chunk_to_event(handle, thread, tracker, chunk)
517
- if event is not None:
596
+ # TODO(runtime-event-v2): use real native cursor from chunk if
597
+ # available; fallback to thread:seq for now.
598
+ native_cursor = f"{thread.thread_id}:{self._next_seq()}"
599
+ # autoApprovalReview 不产生 canonical 事件(adapter 静默),但
600
+ # cancel 级联丢弃审批的契约依赖 runtime 的 pending 跟踪。
601
+ chunk_method = (
602
+ str((chunk or {}).get("method") or "")
603
+ if isinstance(chunk, dict)
604
+ else ""
605
+ )
606
+ if chunk_method in {
607
+ "item/autoApprovalReview/started",
608
+ "item/autoApprovalReview/completed",
609
+ }:
610
+ review_params = chunk.get("params") or {}
611
+ review_id = str(
612
+ review_params.get("reviewId")
613
+ or review_params.get("review_id")
614
+ or ""
615
+ )
616
+ if review_id:
617
+ if chunk_method.endswith("started"):
618
+ thread.pending_approvals.add(review_id)
619
+ else:
620
+ thread.pending_approvals.discard(review_id)
621
+ for event in adapter.map_protocol_message(
622
+ chunk,
623
+ context,
624
+ native_cursor=native_cursor,
625
+ timestamp=time.time(),
626
+ ):
627
+ event = self._with_caller_scope(event, request)
628
+ # 跟踪 pending 审批(cancel 级联丢弃契约依赖该集合)。
629
+ if isinstance(event, InteractionRequested):
630
+ if event.interaction_id:
631
+ thread.pending_approvals.add(event.interaction_id)
632
+ call_id = getattr(event.request, "call_id", None)
633
+ if call_id:
634
+ thread.pending_approvals.add(str(call_id))
635
+ elif isinstance(event, InteractionResolved):
636
+ thread.pending_approvals.discard(event.interaction_id)
637
+ call_id = getattr(event.response, "call_id", None)
638
+ if call_id:
639
+ thread.pending_approvals.discard(str(call_id))
640
+ if isinstance(event, (RunCompleted, RunFailed, RunCanceled)):
641
+ # The Kernel stops consuming as soon as it persists a
642
+ # canonical terminal fact, so generator ``finally`` may
643
+ # not run before worker cleanup calls ``close``. Mark the
644
+ # native turn terminal before yielding that fact; close
645
+ # must terminate the transport without sending a stale
646
+ # turn/interrupt RPC to an already-completed app-server.
647
+ thread.done = True
518
648
  yield event
519
649
  finally:
520
650
  waiter_tasks = [task for task in (chunk_task, interrupt_task) if task is not None]
@@ -530,285 +660,155 @@ class CodexRuntimeAdapter(RuntimeAdapter):
530
660
  except Exception: # noqa: BLE001
531
661
  pass
532
662
 
533
- def _codex_chunk_to_event(
663
+ # ---- canonical run.* helpers (for runtime-owned lifecycle) ----
664
+
665
+ def _event_run_id(self, handle: RunHandle) -> str:
666
+ """事件的 canonical run_id:调用方 invocation_id 优先,退回 thread id。
667
+
668
+ ``handle.run_id`` 是 codex 原生 thread id(resume/cancel 按 thread 寻址);
669
+ 但 canonical RuntimeEvent 的 run_id 必须与调用方
670
+ ``StartRequest.metadata['invocation_id']`` 一致(conversation kernel 的
671
+ event scope 校验),否则 hosted/web 执行路径会在首个事件上 fail。
672
+ """
673
+ request = self._requests.get(handle.run_id)
674
+ if request is not None:
675
+ invocation_id = str(
676
+ (getattr(request, "metadata", None) or {}).get("invocation_id") or ""
677
+ ).strip()
678
+ if invocation_id:
679
+ return invocation_id
680
+ return handle.run_id
681
+
682
+ def _make_source(self, handle: RunHandle) -> SourceRef:
683
+ request = self._requests.get(handle.run_id)
684
+ return SourceRef(
685
+ framework="codex",
686
+ native_run_id=handle.run_id,
687
+ metadata={
688
+ "agent_id": (
689
+ str(request.agent_id or "codex") if request is not None else "codex"
690
+ ),
691
+ "user_id": (
692
+ request.user_id
693
+ if request is not None
694
+ else str(handle.native_ref.get("user_id") or "user")
695
+ ),
696
+ "session_id": handle.session_id,
697
+ "invocation_id": (
698
+ str(request.metadata.get("invocation_id") or handle.run_id)
699
+ if request is not None
700
+ else handle.run_id
701
+ ),
702
+ },
703
+ )
704
+
705
+ def _with_caller_scope(self, event: RuntimeEvent, request: Any) -> RuntimeEvent:
706
+ """把调用方 scope(request 的 agent/user/session/invocation)并入事件 source。"""
707
+
708
+ if request is None:
709
+ return event
710
+ caller_scope = {
711
+ "agent_id": str(getattr(request, "agent_id", "") or "codex"),
712
+ "user_id": str(getattr(request, "user_id", "") or "user"),
713
+ "session_id": str(getattr(request, "session_id", "") or ""),
714
+ "invocation_id": str(
715
+ (getattr(request, "metadata", None) or {}).get("invocation_id")
716
+ or ""
717
+ ),
718
+ }
719
+ merged = {**caller_scope, **dict(event.source.metadata or {})}
720
+ # adapter 自身字段优先;仅补齐缺失的调用方 scope 键。
721
+ for key, value in caller_scope.items():
722
+ if not merged.get(key):
723
+ merged[key] = value
724
+ source = event.source.model_copy(update={"metadata": merged})
725
+ return event.model_copy(update={"source": source})
726
+
727
+ def _canonical_kwargs(
534
728
  self,
535
729
  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
- )
730
+ *,
731
+ scope_id: str,
732
+ item_id: str,
733
+ event_type: str,
734
+ part_id: str,
735
+ ) -> dict[str, Any]:
736
+ framework = "codex"
737
+ run_id = self._event_run_id(handle)
738
+ n = self._next_seq()
739
+ return {
740
+ "schema_version": 2,
741
+ "event_id": stable_event_id(
742
+ framework, scope_id, item_id, event_type, part_id, run_id, n
743
+ ),
744
+ "seq": n,
745
+ "timestamp": time.time(),
746
+ "run_id": run_id,
747
+ "scope_id": scope_id,
748
+ "source": self._make_source(handle),
749
+ }
645
750
 
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(
751
+ def _make_run_canceled(
752
+ self, handle: RunHandle, *, reason: str | None = None
753
+ ) -> RunCanceled:
754
+ framework = "codex"
755
+ run_id = self._event_run_id(handle)
756
+ scope_id = stable_scope_id(framework, run_id)
757
+ item_id = stable_item_id(framework, run_id, "$run")
758
+ return RunCanceled(
759
+ **self._canonical_kwargs(
740
760
  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(
761
+ scope_id=scope_id,
762
+ item_id=item_id,
763
+ event_type="run.canceled",
764
+ part_id="run",
765
+ ),
766
+ status="canceled",
767
+ reason=reason,
768
+ )
769
+
770
+ def _make_run_interrupted(
771
+ self, handle: RunHandle, *, reason: str | None = None
772
+ ) -> RunInterrupted:
773
+ framework = "codex"
774
+ run_id = self._event_run_id(handle)
775
+ scope_id = stable_scope_id(framework, run_id)
776
+ item_id = stable_item_id(framework, run_id, "$run")
777
+ return RunInterrupted(
778
+ **self._canonical_kwargs(
773
779
  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
780
+ scope_id=scope_id,
781
+ item_id=item_id,
782
+ event_type="run.interrupted",
783
+ part_id="run",
784
+ ),
785
+ status="interrupted",
786
+ reason=reason,
787
+ )
785
788
 
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")
789
+ def _make_run_failed(
790
+ self, handle: RunHandle, error_message: str
791
+ ) -> RunFailed:
792
+ framework = "codex"
793
+ run_id = self._event_run_id(handle)
794
+ scope_id = stable_scope_id(framework, run_id)
795
+ item_id = stable_item_id(framework, run_id, "$run")
796
+ return RunFailed(
797
+ **self._canonical_kwargs(
798
+ handle,
799
+ scope_id=scope_id,
800
+ item_id=item_id,
801
+ event_type="run.failed",
802
+ part_id="run",
802
803
  ),
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
804
+ status="failed",
805
+ error=ErrorInfo(
806
+ code="codex_runtime_failed",
807
+ message=error_message,
808
+ source="codex",
809
+ scope_id=scope_id,
810
+ source_ref=self._make_source(handle),
808
811
  ),
809
- seq_id=self._next_seq(),
810
- phase=phase,
811
- payload=payload,
812
812
  )
813
813
 
814
814
  @staticmethod
@@ -850,6 +850,34 @@ def _resume_prompt(payload: Optional[ResumePayload]) -> Any:
850
850
  return json.dumps(payload.data, ensure_ascii=False, sort_keys=True)
851
851
 
852
852
 
853
+ def _coerce_prompt_text(value: Any) -> Any:
854
+ """把 canonical message 形态的 input 压成 SDK 可接受的文本。
855
+
856
+ ``openai-codex`` 0.147 的 run input 只接受 TextInput/str;请求侧没有
857
+ conversation preprocessing 时 ``request.input`` 可能是
858
+ ``[{role, content}]`` 历史列表,直接透传会 ``unsupported input item``。
859
+ """
860
+ if isinstance(value, str) or value is None:
861
+ return value
862
+ if isinstance(value, dict):
863
+ content = value.get("content") if "role" in value else value.get("text")
864
+ if isinstance(content, str):
865
+ return content
866
+ if isinstance(content, dict):
867
+ text = content.get("text") or content.get("content")
868
+ if isinstance(text, str):
869
+ return text
870
+ return str(value)
871
+ if isinstance(value, list):
872
+ texts = [
873
+ text
874
+ for text in (_coerce_prompt_text(item) for item in value)
875
+ if isinstance(text, str) and text
876
+ ]
877
+ return "\n".join(texts) if texts else str(value)
878
+ return str(value)
879
+
880
+
853
881
  def _request_prompt(request: StartRequest) -> Any:
854
882
  """Render canonical conversation history for a native Codex turn.
855
883
 
@@ -859,11 +887,21 @@ def _request_prompt(request: StartRequest) -> Any:
859
887
  # A resumed Codex thread already owns its transcript. Re-sending Studio's
860
888
  # transport-neutral history would duplicate every prior turn after refresh.
861
889
  if str(request.metadata.get("thread_id") or "").strip():
862
- return request.input
890
+ return (
891
+ request.input
892
+ if _is_structured_turn_input(request.input)
893
+ else _coerce_prompt_text(request.input)
894
+ )
863
895
 
864
896
  conversation = request.conversation_preprocessing()
865
897
  if conversation is None or not conversation.messages:
866
- return request.input
898
+ # Keep native text/image/mention parts intact for _build_run_input().
899
+ # Flattening this list turns an image dict into user-visible text.
900
+ return (
901
+ request.input
902
+ if _is_structured_turn_input(request.input)
903
+ else _coerce_prompt_text(request.input)
904
+ )
867
905
 
868
906
  lines: list[str] = []
869
907
  for message in conversation.messages:
@@ -880,6 +918,13 @@ def _request_prompt(request: StartRequest) -> Any:
880
918
  return "\n".join(lines) or request.input
881
919
 
882
920
 
921
+ def _is_structured_turn_input(value: Any) -> bool:
922
+ return isinstance(value, list) and any(
923
+ isinstance(item, dict) and isinstance(item.get("type"), str)
924
+ for item in value
925
+ )
926
+
927
+
883
928
  def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
884
929
  """Compose Codex skills and native text/image/mention turn input."""
885
930
  try:
@@ -905,7 +950,11 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
905
950
  if not isinstance(item, dict):
906
951
  continue
907
952
  kind = str(item.get("type") or "")
908
- if kind == "text":
953
+ if not kind and "role" in item:
954
+ # canonical conversation message({role, content});当前 input
955
+ # 已由 prompt(或 conversation preprocessing)承载,跳过历史项。
956
+ continue
957
+ if kind in {"text", "input_text"}:
909
958
  text = str(
910
959
  prompt
911
960
  if not text_replaced and isinstance(prompt, str)
@@ -914,10 +963,35 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
914
963
  text_replaced = True
915
964
  if text:
916
965
  native_items.append(TextInput(text=text))
917
- elif kind == "image" and item.get("url"):
918
- native_items.append(ImageInput(url=str(item["url"])))
966
+ elif kind in {"image", "input_image"} and (
967
+ item.get("url") or item.get("image_url")
968
+ ):
969
+ native_items.append(
970
+ ImageInput(url=str(item.get("url") or item.get("image_url")))
971
+ )
919
972
  elif kind == "localImage" and item.get("path"):
920
973
  native_items.append(LocalImageInput(path=str(item["path"])))
974
+ elif kind == "input_file" and (
975
+ item.get("file_data")
976
+ or str(item.get("file_url") or "").startswith("data:")
977
+ ):
978
+ file_path = _materialize_inline_file(
979
+ str(item.get("file_data") or item.get("file_url")),
980
+ str(item.get("filename") or "attachment"),
981
+ )
982
+ if file_path is not None:
983
+ # App Server's ``mention`` input is presentation metadata:
984
+ # current Codex versions do not include it in the model's
985
+ # user message. Always add an explicit model-visible
986
+ # attachment context as well. Small textual files are
987
+ # inlined deterministically; binary/large files expose a
988
+ # sandbox-readable path that Codex can inspect with tools.
989
+ native_items.append(
990
+ TextInput(text=_attachment_context_text(file_path, item))
991
+ )
992
+ native_items.append(
993
+ MentionInput(name=file_path.name, path=str(file_path))
994
+ )
921
995
  elif kind == "mention" and item.get("path"):
922
996
  native_items.append(
923
997
  MentionInput(
@@ -937,4 +1011,74 @@ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
937
1011
  return combined
938
1012
 
939
1013
 
1014
+ def _materialize_inline_file(data_url: str, filename: str) -> Path | None:
1015
+ """Materialize a bounded Studio inline attachment for native Codex."""
1016
+
1017
+ match = re.fullmatch(r"data:([^;,]+)?;base64,([A-Za-z0-9+/=\s]+)", data_url)
1018
+ encoded = match.group(2) if match is not None else data_url.strip()
1019
+ try:
1020
+ payload = base64.b64decode(encoded, validate=True)
1021
+ except (ValueError, binascii.Error):
1022
+ return None
1023
+ if not payload or len(payload) > 10 * 1024 * 1024:
1024
+ return None
1025
+ safe_name = re.sub(r"[^A-Za-z0-9._-]+", "-", Path(filename).name).strip(".-")
1026
+ safe_name = safe_name[:120] or "attachment"
1027
+ digest = hashlib.sha256(payload).hexdigest()
1028
+ path = Path("/tmp/ksadk-codex-attachments") / digest / safe_name
1029
+ path.parent.mkdir(parents=True, exist_ok=True)
1030
+ if not path.exists():
1031
+ path.write_bytes(payload)
1032
+ return path
1033
+
1034
+
1035
+ _MAX_INLINE_ATTACHMENT_TEXT_BYTES = 64 * 1024
1036
+ _TEXT_ATTACHMENT_SUFFIXES = {
1037
+ ".csv",
1038
+ ".html",
1039
+ ".htm",
1040
+ ".ini",
1041
+ ".json",
1042
+ ".jsonl",
1043
+ ".log",
1044
+ ".md",
1045
+ ".py",
1046
+ ".rst",
1047
+ ".toml",
1048
+ ".tsv",
1049
+ ".txt",
1050
+ ".xml",
1051
+ ".yaml",
1052
+ ".yml",
1053
+ }
1054
+
1055
+
1056
+ def _attachment_context_text(path: Path, item: Mapping[str, Any]) -> str:
1057
+ """Build model-visible context for a materialized Responses input file."""
1058
+
1059
+ name = str(item.get("filename") or path.name).replace('"', "'")
1060
+ inline_data = item.get("inlineData")
1061
+ inline_mime = inline_data.get("mimeType") if isinstance(inline_data, Mapping) else None
1062
+ mime_type = str(item.get("mime_type") or inline_mime or "").strip().lower()
1063
+ source = str(item.get("file_data") or item.get("file_url") or "")
1064
+ data_url_match = re.match(r"data:([^;,]+)", source)
1065
+ if not mime_type and data_url_match is not None:
1066
+ mime_type = data_url_match.group(1).strip().lower()
1067
+ is_text = mime_type.startswith("text/") or path.suffix.lower() in _TEXT_ATTACHMENT_SUFFIXES
1068
+ header = f'<uploaded_attachment name="{name}" path="{path}">'
1069
+ if not is_text:
1070
+ return (
1071
+ f"{header}\n"
1072
+ "The uploaded file is available at the path above. Read it with an appropriate "
1073
+ "tool before answering questions about its contents.\n"
1074
+ "</uploaded_attachment>"
1075
+ )
1076
+
1077
+ raw = path.read_bytes()
1078
+ truncated = len(raw) > _MAX_INLINE_ATTACHMENT_TEXT_BYTES
1079
+ text = raw[:_MAX_INLINE_ATTACHMENT_TEXT_BYTES].decode("utf-8", errors="replace")
1080
+ suffix = "\n[attachment content truncated]" if truncated else ""
1081
+ return f"{header}\n{text}{suffix}\n</uploaded_attachment>"
1082
+
1083
+
940
1084
  __all__ = ["CodexRuntimeAdapter"]