agentengine-sdk-python 0.7.0__py3-none-any.whl → 0.8.1__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 (450) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/METADATA +52 -20
  2. agentengine_sdk_python-0.8.1.dist-info/RECORD +644 -0
  3. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/WHEEL +1 -1
  4. ksadk/a2a/__init__.py +180 -11
  5. ksadk/a2a/bootstrap.py +332 -0
  6. ksadk/a2a/card.py +15 -0
  7. ksadk/a2a/context_store.py +203 -0
  8. ksadk/a2a/control_plane.py +1056 -0
  9. ksadk/a2a/credential.py +99 -0
  10. ksadk/a2a/event_adapter.py +149 -0
  11. ksadk/a2a/executor.py +361 -137
  12. ksadk/a2a/external_transport.py +516 -0
  13. ksadk/a2a/identity.py +304 -0
  14. ksadk/a2a/ids.py +22 -0
  15. ksadk/a2a/langgraph.py +285 -0
  16. ksadk/a2a/resume_store.py +201 -0
  17. ksadk/a2a/routes.py +112 -0
  18. ksadk/a2a/server.py +102 -62
  19. ksadk/a2a/service_env.py +33 -0
  20. ksadk/a2a/space_client.py +1122 -0
  21. ksadk/a2a/task_adapter.py +423 -0
  22. ksadk/a2a/task_event_dispatcher.py +167 -0
  23. ksadk/a2a/task_event_outbox.py +326 -0
  24. ksadk/a2a/task_store.py +206 -0
  25. ksadk/a2a_card.py +101 -0
  26. ksadk/a2ui/__init__.py +27 -0
  27. ksadk/a2ui/core.py +186 -0
  28. ksadk/a2ui/models.py +166 -0
  29. ksadk/a2ui/renderer.py +198 -0
  30. ksadk/agui/__init__.py +9 -0
  31. ksadk/agui/a2ui_projection.py +92 -0
  32. ksadk/agui/agent.py +872 -0
  33. ksadk/agui/config.py +92 -0
  34. ksadk/agui/routes.py +47 -0
  35. ksadk/api/client.py +238 -204
  36. ksadk/api/environment.py +27 -0
  37. ksadk/builders/__init__.py +2 -0
  38. ksadk/builders/base.py +16 -14
  39. ksadk/builders/code_builder.py +375 -270
  40. ksadk/builders/container_builder.py +240 -200
  41. ksadk/builders/framework_requirements.py +31 -12
  42. ksadk/builders/ks3_uploader.py +60 -29
  43. ksadk/builders/managed_runtime_builder.py +173 -0
  44. ksadk/builders/mcp_builder.py +91 -92
  45. ksadk/builders/requirements_utils.py +1 -5
  46. ksadk/cli/__init__.py +97 -171
  47. ksadk/cli/cmd_a2a.py +255 -84
  48. ksadk/cli/cmd_agent.py +19 -5
  49. ksadk/cli/cmd_build.py +158 -29
  50. ksadk/cli/cmd_completion.py +47 -35
  51. ksadk/cli/cmd_config.py +267 -198
  52. ksadk/cli/cmd_create.py +507 -740
  53. ksadk/cli/cmd_dashboard.py +120 -45
  54. ksadk/cli/cmd_deploy.py +135 -59
  55. ksadk/cli/cmd_destroy.py +44 -14
  56. ksadk/cli/cmd_eval.py +386 -0
  57. ksadk/cli/cmd_files.py +63 -24
  58. ksadk/cli/cmd_hermes.py +313 -135
  59. ksadk/cli/cmd_invoke.py +184 -122
  60. ksadk/cli/cmd_launch.py +76 -28
  61. ksadk/cli/cmd_mcp.py +98 -64
  62. ksadk/cli/cmd_model.py +14 -5
  63. ksadk/cli/cmd_openclaw.py +564 -268
  64. ksadk/cli/cmd_replay.py +71 -0
  65. ksadk/cli/cmd_run.py +58 -73
  66. ksadk/cli/cmd_status.py +80 -31
  67. ksadk/cli/cmd_studio.py +110 -0
  68. ksadk/cli/cmd_version.py +20 -13
  69. ksadk/cli/cmd_web.py +95 -18
  70. ksadk/cli/deploy_utils.py +20 -20
  71. ksadk/cli/dry_run.py +7 -4
  72. ksadk/cli/env_options.py +83 -0
  73. ksadk/cli/error_utils.py +37 -12
  74. ksadk/cli/global_options.py +4 -3
  75. ksadk/cli/local_runtime.py +1 -2
  76. ksadk/cli/model_catalog.py +6 -2
  77. ksadk/cli/network_options.py +41 -19
  78. ksadk/cli/resource_common.py +6 -2
  79. ksadk/cli/runtime_bootstrap.py +51 -0
  80. ksadk/cli/storage.py +0 -1
  81. ksadk/cli/ui.py +11 -3
  82. ksadk/cli/workflow_common.py +52 -18
  83. ksadk/codex/__init__.py +12 -0
  84. ksadk/codex/client.py +1042 -0
  85. ksadk/codex/phase.py +143 -0
  86. ksadk/codex/runtime.py +940 -0
  87. ksadk/common/aicp_env.py +0 -1
  88. ksadk/common/auth.py +2 -2
  89. ksadk/common/constants.py +1 -3
  90. ksadk/common/kop_client.py +232 -0
  91. ksadk/common/llm_utils.py +17 -13
  92. ksadk/compat/__init__.py +5 -0
  93. ksadk/compat/adk_compat.py +200 -0
  94. ksadk/compat/copilotkit_a2ui.py +330 -0
  95. ksadk/configs/__init__.py +12 -14
  96. ksadk/configs/env_registry.py +661 -116
  97. ksadk/configs/global_config.py +3 -2
  98. ksadk/configs/settings.py +133 -209
  99. ksadk/conversations/__init__.py +85 -92
  100. ksadk/conversations/attachment_storage.py +16 -11
  101. ksadk/conversations/attachments.py +44 -36
  102. ksadk/conversations/compaction_pipeline.py +12 -12
  103. ksadk/conversations/compaction_prompt.py +9 -5
  104. ksadk/conversations/context.py +221 -6
  105. ksadk/conversations/message_projection.py +438 -51
  106. ksadk/conversations/model_context.py +12 -6
  107. ksadk/conversations/model_options.py +0 -1
  108. ksadk/conversations/normalize.py +20 -16
  109. ksadk/conversations/reasoning_markup.py +1 -1
  110. ksadk/conversations/runtime.py +339 -5255
  111. ksadk/conversations/runtime_compaction.py +333 -0
  112. ksadk/conversations/runtime_constants.py +27 -0
  113. ksadk/conversations/runtime_governance.py +185 -0
  114. ksadk/conversations/runtime_input.py +660 -0
  115. ksadk/conversations/runtime_invocation.py +395 -0
  116. ksadk/conversations/runtime_metadata.py +475 -0
  117. ksadk/conversations/runtime_observability.py +367 -0
  118. ksadk/conversations/runtime_payloads.py +272 -0
  119. ksadk/conversations/runtime_persistence.py +440 -0
  120. ksadk/conversations/runtime_preparation.py +426 -0
  121. ksadk/conversations/runtime_resume.py +836 -0
  122. ksadk/conversations/runtime_stream_events.py +902 -0
  123. ksadk/conversations/runtime_streaming.py +700 -0
  124. ksadk/conversations/semantic_summary.py +17 -5
  125. ksadk/conversations/session_title.py +6 -3
  126. ksadk/deployment/__init__.py +15 -16
  127. ksadk/deployment/agent_access.py +12 -23
  128. ksadk/deployment/base.py +51 -38
  129. ksadk/deployment/managed_runtime.py +33 -0
  130. ksadk/deployment/manager.py +85 -71
  131. ksadk/deployment/providers/serverless.py +379 -210
  132. ksadk/deployment/registry.py +38 -32
  133. ksadk/deployment/state.py +11 -11
  134. ksadk/deployment/ui_config.py +1 -1
  135. ksadk/detection/__init__.py +1 -1
  136. ksadk/detection/detector.py +146 -83
  137. ksadk/detection/mcp_detector.py +9 -6
  138. ksadk/evaluation/__init__.py +84 -0
  139. ksadk/evaluation/a2a_adapter.py +444 -0
  140. ksadk/evaluation/adapters.py +54 -0
  141. ksadk/evaluation/contracts.py +409 -0
  142. ksadk/evaluation/evalset.py +351 -0
  143. ksadk/evaluation/evaluators.py +461 -0
  144. ksadk/evaluation/executor.py +118 -0
  145. ksadk/evaluation/storage.py +68 -0
  146. ksadk/evaluation/target.py +73 -0
  147. ksadk/events/__init__.py +25 -0
  148. ksadk/events/parser.py +191 -0
  149. ksadk/events/replay.py +37 -0
  150. ksadk/events/runtime_event.py +273 -0
  151. ksadk/events/store.py +317 -0
  152. ksadk/harness/__init__.py +35 -0
  153. ksadk/harness/app.py +269 -0
  154. ksadk/harness/config.py +181 -0
  155. ksadk/harness/reasoner.py +105 -0
  156. ksadk/harness/runtime.py +364 -0
  157. ksadk/harness/sandbox.py +141 -0
  158. ksadk/harness/tools.py +222 -0
  159. ksadk/hermes_terminal.py +28 -12
  160. ksadk/identity/resolver.py +15 -9
  161. ksadk/ids.py +32 -0
  162. ksadk/knowledge_base/adk_tool.py +1 -1
  163. ksadk/knowledge_base/client.py +38 -33
  164. ksadk/managed_a2a_card.py +160 -0
  165. ksadk/managed_runtime.py +195 -0
  166. ksadk/markdown.py +5 -2
  167. ksadk/mcp_runtime/__init__.py +9 -6
  168. ksadk/memory/__init__.py +1 -1
  169. ksadk/memory/adk/__init__.py +2 -0
  170. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  171. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  172. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  173. ksadk/memory/adk/backends/sdk_ltm_backend.py +36 -36
  174. ksadk/memory/adk/long_term_memory.py +34 -37
  175. ksadk/memory/adk/resilient_session_service.py +4 -5
  176. ksadk/memory/adk/short_term_memory.py +12 -21
  177. ksadk/memory/adk_tool.py +3 -2
  178. ksadk/memory/backends/base.py +19 -19
  179. ksadk/memory/backends/memory.py +24 -25
  180. ksadk/memory/backends/redis.py +31 -30
  181. ksadk/memory/langchain_tool.py +2 -1
  182. ksadk/memory/manager.py +52 -47
  183. ksadk/memory/service.py +16 -6
  184. ksadk/memory/tool.py +4 -0
  185. ksadk/model_policy.py +15 -7
  186. ksadk/model_proxy/__init__.py +51 -0
  187. ksadk/model_proxy/bootstrap.py +80 -0
  188. ksadk/model_proxy/cache.py +165 -0
  189. ksadk/model_proxy/config.py +66 -0
  190. ksadk/model_proxy/demo_webui.py +115 -0
  191. ksadk/model_proxy/detect.py +145 -0
  192. ksadk/model_proxy/gate.py +68 -0
  193. ksadk/model_proxy/namespace.py +201 -0
  194. ksadk/model_proxy/protocol_proxy.py +22 -0
  195. ksadk/model_proxy/server.py +443 -0
  196. ksadk/model_proxy/transform.py +735 -0
  197. ksadk/openclaw_gateway.py +29 -14
  198. ksadk/runners/adk_runner.py +354 -146
  199. ksadk/runners/base_runner.py +71 -29
  200. ksadk/runners/deepagents_runner.py +0 -1
  201. ksadk/runners/factory.py +18 -5
  202. ksadk/runners/langchain_runner.py +29 -742
  203. ksadk/runners/langgraph_runner.py +417 -97
  204. ksadk/runners/patch_langchain.py +16 -16
  205. ksadk/runners/remote_runner.py +134 -33
  206. ksadk/runners/usage_accumulator.py +1 -0
  207. ksadk/runners/utils/__init__.py +0 -10
  208. ksadk/runners/utils/loader.py +23 -7
  209. ksadk/runtime/__init__.py +67 -0
  210. ksadk/runtime/adapter.py +412 -0
  211. ksadk/runtime/conversation_execution.py +553 -0
  212. ksadk/runtime/executor.py +251 -0
  213. ksadk/runtime/factory.py +156 -0
  214. ksadk/runtime/framework_adapters.py +118 -0
  215. ksadk/runtime/launch.py +48 -0
  216. ksadk/runtime/preprocessing.py +131 -0
  217. ksadk/runtime/runner_adapter.py +1032 -0
  218. ksadk/runtime/runner_loading.py +36 -0
  219. ksadk/runtime_context.py +18 -3
  220. ksadk/runtime_state.py +0 -1
  221. ksadk/sandbox/__init__.py +4 -1
  222. ksadk/sandbox/backends/__init__.py +4 -1
  223. ksadk/sandbox/backends/e2b.py +13 -5
  224. ksadk/sandbox/backends/local_process.py +11 -3
  225. ksadk/sandbox/base.py +10 -15
  226. ksadk/sandbox/factory.py +14 -4
  227. ksadk/sandbox/registry.py +57 -6
  228. ksadk/server/__init__.py +39 -4
  229. ksadk/server/api_models.py +15 -5
  230. ksadk/server/app.py +24 -3928
  231. ksadk/server/composition.py +47 -0
  232. ksadk/server/factory.py +622 -0
  233. ksadk/server/routes/__init__.py +1 -0
  234. ksadk/server/routes/checkpoint_resolution.py +116 -0
  235. ksadk/server/routes/common.py +682 -0
  236. ksadk/server/routes/control.py +331 -0
  237. ksadk/server/routes/dependencies.py +98 -0
  238. ksadk/server/routes/misc.py +425 -0
  239. ksadk/server/routes/models.py +451 -0
  240. ksadk/server/routes/openai_compat.py +229 -0
  241. ksadk/server/routes/projection.py +848 -0
  242. ksadk/server/routes/routers.py +52 -0
  243. ksadk/server/routes/run.py +519 -0
  244. ksadk/server/routes/sessions.py +644 -0
  245. ksadk/server/routes/streaming.py +265 -0
  246. ksadk/server/routes/workspace.py +414 -0
  247. ksadk/server/static/assets/{ArtifactsPanel-D-SSn2ee.js → ArtifactsPanel-CakCqxEN.js} +1 -1
  248. ksadk/server/static/assets/CodeBlock-CynLknyO.js +9 -0
  249. ksadk/server/static/assets/MathMessageMarkdown-3sQdOysS.js +8 -0
  250. ksadk/server/static/assets/MermaidBlock-NERD_ef7.js +303 -0
  251. ksadk/server/static/assets/NativeTerminalPanel-CiHWf82q.js +1 -0
  252. ksadk/server/static/assets/{arc-BA13sHc9.js → arc-DrgjcKhl.js} +1 -1
  253. ksadk/server/static/assets/architecture-7EHR7CIX-CpZLRCcY.js +1 -0
  254. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-DzJzgVoL.js → architectureDiagram-3BPJPVTR-BXoVRbQ6.js} +1 -1
  255. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-BnaB3Ywv.js} +1 -1
  256. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-Dw4weZcp.js} +1 -1
  257. ksadk/server/static/assets/channel-Dt3rQTFI.js +1 -0
  258. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-ChjpLK7j.js} +1 -1
  259. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-B9drTgOW.js} +1 -1
  260. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-CK4ROATy.js} +1 -1
  261. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B9KtTTgw.js} +1 -1
  262. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-1O1mki3m.js} +1 -1
  263. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-B_fH2YuH.js} +1 -1
  264. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.js → chunk-ND2GUHAM-D6rFLaVV.js} +1 -1
  265. ksadk/server/static/assets/chunk-QZHKN3VN-iXEZtKQc.js +1 -0
  266. ksadk/server/static/assets/classDiagram-4FO5ZUOK-DIveV17Q.js +1 -0
  267. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-DIveV17Q.js +1 -0
  268. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-Zhy2GBnP.js → cose-bilkent-S5V4N54A-ChNs_Mdk.js} +1 -1
  269. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-D0QXg_MQ.js} +1 -1
  270. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-DBDDKwBQ.js} +1 -1
  271. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-BkwDOxmO.js} +1 -1
  272. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Bv8waYvY.js} +1 -1
  273. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-e1VECDdu.js} +1 -1
  274. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-Ds9adfRl.js} +1 -1
  275. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-4N2NMVYg.js} +1 -1
  276. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-B-jNR6Vn.js} +1 -1
  277. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-B5J2uhNk.js} +1 -1
  278. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-B5oS1zcj.js} +1 -1
  279. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BB3-OwTQ.js} +1 -1
  280. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-BIlKoW0k.js} +1 -1
  281. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-BjOdlDjj.js} +1 -1
  282. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-C1sz5Eo3.js} +1 -1
  283. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-CWV4lDr1.js} +1 -1
  284. ksadk/server/static/assets/{dist-D-xOD048.js → dist-CmZHzlwZ.js} +1 -1
  285. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-D6MpCZtz.js} +1 -1
  286. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-DJN_6Y39.js} +1 -1
  287. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-DQU9EWw-.js} +1 -1
  288. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-DWKByxWV.js} +1 -1
  289. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-Dli7qW-B.js} +1 -1
  290. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-DsHPdk3W.js} +1 -1
  291. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-X4TnqwOK.js} +1 -1
  292. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-_pHPTyZ-.js} +1 -1
  293. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-fhOuWds5.js} +1 -1
  294. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-gLnrGsoU.js} +1 -1
  295. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-j6Dxmf_j.js} +1 -1
  296. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-o2OVmKvt.js} +1 -1
  297. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.js → erDiagram-TEJ5UH35-BPItFKH6.js} +1 -1
  298. ksadk/server/static/assets/eventmodeling-FCH6USID-DYFSxbqr.js +1 -0
  299. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-BbYB9w5Y.js} +1 -1
  300. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.js → ganttDiagram-6RSMTGT7-TQXa0KXv.js} +1 -1
  301. ksadk/server/static/assets/gitGraph-WXDBUCRP-DyNYp6BN.js +1 -0
  302. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-C7Q0X-wi.js → gitGraphDiagram-PVQCEYII-bKblCnpa.js} +1 -1
  303. ksadk/server/static/assets/index-R52pJJvg.js +177 -0
  304. ksadk/server/static/assets/index-jJr3_955.css +2 -0
  305. ksadk/server/static/assets/info-J43DQDTF-WUx7qTde.js +1 -0
  306. ksadk/server/static/assets/{infoDiagram-5YYISTIA-BO_CSqq5.js → infoDiagram-5YYISTIA-BGVT7J_i.js} +1 -1
  307. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-K0OOfVna.js} +1 -1
  308. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-8LeOJaZp.js} +1 -1
  309. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-C6uCgZrW.js} +1 -1
  310. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-CYWseY5E.js} +1 -1
  311. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-hdBcYzM8.js} +1 -1
  312. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-BWrDjSZM.js} +2 -2
  313. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.js → mindmap-definition-RKZ34NQL-Bjy_2v4_.js} +1 -1
  314. ksadk/server/static/assets/packet-YPE3B663-BN13y_xl.js +1 -0
  315. ksadk/server/static/assets/pie-LRSECV5Y-ftSLXhhc.js +1 -0
  316. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPv_VstN.js → pieDiagram-4H26LBE5-GmmqNiVH.js} +1 -1
  317. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.js → quadrantDiagram-W4KKPZXB-BmkRbwCg.js} +1 -1
  318. ksadk/server/static/assets/radar-GUYGQ44K-jaICkIeO.js +1 -0
  319. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-BYVw81VC.js → requirementDiagram-4Y6WPE33-DSWFqL7c.js} +1 -1
  320. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-l6iXmU65.js} +1 -1
  321. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-Cq6UBeQB.js} +1 -1
  322. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.js → stateDiagram-AJRCARHV-qzTQpg3Y.js} +1 -1
  323. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-BYCoYGAN.js +1 -0
  324. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-FbCTGrf9.js → timeline-definition-PNZ67QCA-DEz24vV0.js} +1 -1
  325. ksadk/server/static/assets/treeView-BLDUP644-Cpib_vlz.js +1 -0
  326. ksadk/server/static/assets/treemap-LRROVOQU-DQxfmVHK.js +1 -0
  327. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-DWgwtPsJ.js → vennDiagram-CIIHVFJN-CMCixki2.js} +1 -1
  328. ksadk/server/static/assets/wardley-L42UT6IY-BQYHnnkU.js +1 -0
  329. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-CVzB8F9K.js} +1 -1
  330. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.js → xychartDiagram-2RQKCTM6-BFBqBLc1.js} +1 -1
  331. ksadk/server/static/index.html +2 -2
  332. ksadk/server/terminal_sessions.py +111 -58
  333. ksadk/sessions/__init__.py +69 -19
  334. ksadk/sessions/base.py +31 -11
  335. ksadk/sessions/continuity.py +2 -3
  336. ksadk/sessions/in_memory.py +46 -2
  337. ksadk/sessions/local_service.py +148 -17
  338. ksadk/sessions/postgres_service.py +143 -25
  339. ksadk/sessions/resilient.py +36 -8
  340. ksadk/skills/models.py +17 -5
  341. ksadk/skills/package_store.py +5 -2
  342. ksadk/skills/runtime/agent.py +20 -5
  343. ksadk/skills/runtime/artifacts.py +1 -5
  344. ksadk/skills/runtime/backends/e2b.py +10 -4
  345. ksadk/skills/runtime/backends/local.py +15 -3
  346. ksadk/skills/runtime/base.py +1 -2
  347. ksadk/skills/runtime/executor.py +10 -3
  348. ksadk/skills/runtime/factory.py +4 -2
  349. ksadk/skills/runtime/loader.py +1 -2
  350. ksadk/skills/runtime/registry.py +15 -10
  351. ksadk/skills/runtime/request.py +3 -1
  352. ksadk/skills/service_client.py +47 -16
  353. ksadk/skills/tool_defs.py +15 -10
  354. ksadk/studio/__init__.py +5 -0
  355. ksadk/studio/agent_avatar_assets.py +120 -0
  356. ksadk/studio/agent_lifecycle.py +51 -0
  357. ksadk/studio/api.py +1046 -0
  358. ksadk/studio/api_catalog_routes.py +324 -0
  359. ksadk/studio/api_contracts.py +177 -0
  360. ksadk/studio/api_helpers.py +132 -0
  361. ksadk/studio/authoring.py +524 -0
  362. ksadk/studio/authoring_coordinator.py +285 -0
  363. ksadk/studio/builder.py +320 -0
  364. ksadk/studio/capabilities.py +351 -0
  365. ksadk/studio/cloud.py +278 -0
  366. ksadk/studio/codex_agent_service.py +583 -0
  367. ksadk/studio/codex_builder.py +324 -0
  368. ksadk/studio/codex_manifest.py +278 -0
  369. ksadk/studio/codex_run.py +352 -0
  370. ksadk/studio/compiler.py +189 -0
  371. ksadk/studio/contracts.py +644 -0
  372. ksadk/studio/errors.py +47 -0
  373. ksadk/studio/evaluation.py +192 -0
  374. ksadk/studio/event_store.py +284 -0
  375. ksadk/studio/framework_run.py +114 -0
  376. ksadk/studio/identifiers.py +33 -0
  377. ksadk/studio/mcp_runtime.py +229 -0
  378. ksadk/studio/model_client.py +543 -0
  379. ksadk/studio/model_profile_service.py +214 -0
  380. ksadk/studio/operations.py +243 -0
  381. ksadk/studio/otel_trace.py +754 -0
  382. ksadk/studio/pagination.py +182 -0
  383. ksadk/studio/python_tool_inspection.py +183 -0
  384. ksadk/studio/repository.py +306 -0
  385. ksadk/studio/resource_catalog.py +1217 -0
  386. ksadk/studio/run_service.py +707 -0
  387. ksadk/studio/runtime_catalog.py +74 -0
  388. ksadk/studio/runtime_source.py +339 -0
  389. ksadk/studio/service.py +1141 -0
  390. ksadk/studio/shared_web.py +994 -0
  391. ksadk/studio/skill_discovery.py +628 -0
  392. ksadk/studio/static/assets/PrismRenderer-IYN-ffww.js +3 -0
  393. ksadk/studio/static/assets/index-CaEN0eTR.css +1 -0
  394. ksadk/studio/static/assets/index-DI2eY_NH.js +252 -0
  395. ksadk/studio/static/assets/react-vendor-BNmTiJ-I.js +9 -0
  396. ksadk/studio/static/assets/rolldown-runtime-hePW80VL.js +1 -0
  397. ksadk/studio/static/index.html +15 -0
  398. ksadk/studio/templates.py +630 -0
  399. ksadk/studio/validator.py +246 -0
  400. ksadk/studio/workspace.py +137 -0
  401. ksadk/terminal_exec_policy.py +3 -4
  402. ksadk/tools/gateway.py +101 -15
  403. ksadk/tools/result_budget.py +12 -4
  404. ksadk/toolsets/__init__.py +161 -37
  405. ksadk/toolsets/a2a.py +426 -0
  406. ksadk/toolsets/platform.py +21 -5
  407. ksadk/toolsets/sandbox.py +82 -16
  408. ksadk/toolsets/skills.py +200 -39
  409. ksadk/toolsets/web.py +123 -26
  410. ksadk/toolsets/workspace.py +212 -101
  411. ksadk/tracing/__init__.py +19 -21
  412. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  413. ksadk/tracing/setup.py +135 -506
  414. ksadk/tracing/span_utils.py +0 -41
  415. ksadk/tui/loop.py +78 -30
  416. ksadk/tui/stream_render.py +1 -0
  417. ksadk/ui_config.py +5 -3
  418. ksadk/version.py +1 -1
  419. ksadk_runtime_common/memory_backend/registry.py +56 -56
  420. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  421. ksadk_runtime_common/workspace_files/router.py +8 -2
  422. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  423. ksadk/a2a/card_builder.py +0 -64
  424. ksadk/a2a/client.py +0 -309
  425. ksadk/runners/utils/langfuse.py +0 -230
  426. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  427. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  428. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  429. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  430. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  431. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  432. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  433. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  434. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  435. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  436. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  437. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  438. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  439. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  440. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  441. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  442. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  443. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  444. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  445. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  446. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  447. ksadk/tracing/exporters/langfuse_exporter.py +0 -445
  448. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/entry_points.txt +0 -0
  449. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/licenses/LICENSE +0 -0
  450. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.1.dist-info}/top_level.txt +0 -0
ksadk/codex/runtime.py ADDED
@@ -0,0 +1,940 @@
1
+ """CodexRuntimeAdapter — 非 ADK 体系的第三验证样本 (goal-09)。
2
+
3
+ 对执行生命周期负责(不做 veadk 式薄桥接),后端能力面对齐 ``openai-codex`` SDK 真实线程模型
4
+ (``thread_start``/``thread.turn``/``handle.stream``/``handle.interrupt``/``thread_resume``):
5
+
6
+ - **cancel 状态机**(不薄委托给上层):活跃 turn → ``client.interrupt_active_turn``
7
+ (真实 SDK ``handle.interrupt``,终止当前 turn 执行;thread 由 codex 后端托管,无"杀进程"
8
+ 概念);无活跃 turn → 记 pending 下个 turn 消费;**级联丢弃 pending 工具审批**(runtime
9
+ 自跟踪的 pending 集);返回 ``CancelResult`` 枚举;**被中断的 turn 不持久化其 session**
10
+ (避免 resume 捡到写了一半的会话)。
11
+ - **phase 翻译**(:mod:`ksadk.codex.phase`):按 itemId 路由 commentary/final_answer delta,
12
+ 映射 RuntimeEvent phase,不混入主逻辑。
13
+ - **resume 建模为 thread id**(真实 SDK ``thread_resume``),不套 ADK invocation 模型;
14
+ ResumeTarget(thread id)/ResumePayload(工具结果/HITL 回答)分离。
15
+
16
+ 环境约束:read-only sandbox(默认),单轮含一次工具调用即可跑通。
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import json
23
+ import logging
24
+ from dataclasses import dataclass, field
25
+ from typing import Any, AsyncIterator, Optional
26
+
27
+ from ksadk.codex.client import CodexClient
28
+ from ksadk.codex.phase import CodexPhaseTracker
29
+ from ksadk.events.runtime_event import EventType, RuntimeEvent
30
+ from ksadk.runtime.adapter import (
31
+ BaseRuntime,
32
+ CancelResult,
33
+ CheckpointCapability,
34
+ CheckpointDescriptor,
35
+ PauseResult,
36
+ ResumePayload,
37
+ ResumeTarget,
38
+ RunHandle,
39
+ RuntimeAdapter,
40
+ StartRequest,
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ _INTERRUPT_DRAIN_TIMEOUT_SECONDS = 5.0
46
+
47
+
48
+ class _CodexAsBaseRuntime(BaseRuntime):
49
+ """把 CodexClient 包装为 BaseRuntime(原生能力面)。"""
50
+
51
+ def __init__(self, client: CodexClient) -> None:
52
+ self._client = client
53
+ self.runtime_type = "codex"
54
+
55
+ def native_capabilities(self) -> dict[str, Any]:
56
+ return {
57
+ "Framework": "codex",
58
+ "cancel": "thread",
59
+ "pause": "interrupt_then_resume_thread",
60
+ "resume": "thread_id",
61
+ "live_interaction": True,
62
+ }
63
+
64
+
65
+ @dataclass
66
+ class _CodexThread:
67
+ thread_id: str
68
+ turn_id: Optional[str] = None
69
+ streaming: bool = False
70
+ interrupt_event: asyncio.Event = field(default_factory=asyncio.Event)
71
+ pending_approvals: set[str] = field(default_factory=set)
72
+ done: bool = False
73
+ interrupted: bool = False
74
+ paused: bool = False
75
+ started_at: int | None = None
76
+ completed_at: int | None = None
77
+ duration_ms: int | None = None
78
+ goal_mode: bool = False
79
+
80
+
81
+ class CodexRuntimeAdapter(RuntimeAdapter):
82
+ """Codex 的 RuntimeAdapter(重托管)。"""
83
+
84
+ def __init__(
85
+ self,
86
+ client: CodexClient,
87
+ *,
88
+ sandbox_read_only: bool = True,
89
+ turn_timeout_seconds: Optional[float] = None,
90
+ ) -> None:
91
+ super().__init__(_CodexAsBaseRuntime(client))
92
+ self._client = client
93
+ self._sandbox_read_only = sandbox_read_only
94
+ self._turn_timeout_seconds = turn_timeout_seconds
95
+ self._threads: dict[str, _CodexThread] = {}
96
+ self._requests: dict[str, StartRequest] = {}
97
+ self._known_threads: set[str] = set()
98
+ self._pending_cancels: set[str] = set()
99
+ # 被杀/interrupt 的 session 不持久化(goal-09 契约 1)。
100
+ self._do_not_persist: set[str] = set()
101
+ # 可观测:最近一次 cancel 级联丢弃的审批集(contract test 断言用)。
102
+ self.last_cancel_dropped_approvals: set[str] = set()
103
+ self._seq = 0
104
+
105
+ # ---- 六动词 ----
106
+
107
+ async def start(self, request: StartRequest) -> RunHandle:
108
+ # 新 thread 由后端分配真实 thread_id(thread_start);metadata 携带的 thread_id
109
+ # 表示接入既有 thread(resume 语义,run_turn 时按 resume 接入)。
110
+ provided = request.metadata.get("thread_id")
111
+ if provided:
112
+ thread_id = str(provided)
113
+ else:
114
+ # 把 model + base_instructions 传给 codex thread(配置契约,见 plan C)
115
+ thread_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
116
+ if request.config:
117
+ for key in ("sandbox", "approval_mode", "summary", "ephemeral"):
118
+ value = request.config.get(key)
119
+ if key == "ephemeral" and value is not None:
120
+ thread_config[key] = bool(value)
121
+ elif value:
122
+ thread_config[key] = value
123
+ if request.model:
124
+ thread_config["model"] = request.model
125
+ base_instructions = request.config.get("base_instructions")
126
+ if base_instructions:
127
+ thread_config["base_instructions"] = base_instructions
128
+ cwd = request.config.get("cwd")
129
+ if cwd:
130
+ thread_config["cwd"] = str(cwd)
131
+ thread_id = await self._client.start_thread(thread_config)
132
+ self._known_threads.add(thread_id)
133
+ thread = _CodexThread(thread_id=thread_id)
134
+ thread.__dict__["_start_request"] = request
135
+ self._threads[thread_id] = thread
136
+ self._requests[thread_id] = request
137
+ return RunHandle(
138
+ run_id=thread_id,
139
+ session_id=request.session_id,
140
+ runtime_type="codex",
141
+ native_ref={"thread_id": thread_id, "user_id": request.user_id},
142
+ )
143
+
144
+ def stream(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
145
+ return self._stream_events(handle)
146
+
147
+ async def cancel(self, handle: RunHandle) -> CancelResult:
148
+ thread_id = handle.run_id
149
+ thread = self._threads.get(thread_id)
150
+ is_active = thread is not None and not thread.done and thread.streaming
151
+ if not is_active:
152
+ if thread_id in self._known_threads:
153
+ self._pending_cancels.add(thread_id)
154
+ return CancelResult.PENDING_CANCEL_RECORDED
155
+ return CancelResult.NOT_RUNNING
156
+ assert thread is not None
157
+ try:
158
+ # 级联丢弃 pending 工具审批(快照 runtime 自跟踪的 pending 集,供观测)。
159
+ # 真实 SDK 无独立 drain API:interrupt 后 turn 停止,pending 审批随之失效。
160
+ self.last_cancel_dropped_approvals = set(thread.pending_approvals)
161
+ thread.pending_approvals.clear()
162
+ # 真实中断:handle.interrupt() 停活跃 turn(真实 SDK 机制;无"杀进程"概念,
163
+ # thread 由 codex 后端托管,interrupt 即终止当前 turn 的执行)。
164
+ # 先唤醒 Studio 的 stream;等待 request_user_input 时,中断 RPC
165
+ # 可能要等 turn 状态前进,否则取消接口与流会互相等待。
166
+ thread.interrupt_event.set()
167
+ interrupted = (
168
+ await self._client.cancel_goal(thread.thread_id)
169
+ if thread.goal_mode
170
+ else await self._client.interrupt_active_turn(thread.thread_id)
171
+ )
172
+ if not interrupted:
173
+ # 无活跃 handle 可 interrupt(竞态:turn 刚好结束)→ 视为未在运行。
174
+ return CancelResult.NOT_RUNNING
175
+ # 被中断的 session 不持久化(goal-09 契约 1)。
176
+ self._do_not_persist.add(thread_id)
177
+ thread.done = True
178
+ self._threads.pop(thread_id, None)
179
+ self._requests.pop(thread_id, None)
180
+ self._pending_cancels.discard(thread_id)
181
+ return CancelResult.INTERRUPTED_ACTIVE_TURN
182
+ except Exception: # noqa: BLE001
183
+ logger.exception("codex cancel thread %s 失败", thread_id)
184
+ return CancelResult.FAILED
185
+
186
+ async def pause(self, handle: RunHandle) -> PauseResult:
187
+ thread = self._threads.get(handle.run_id)
188
+ if thread is None or thread.done or not thread.streaming:
189
+ return PauseResult.NOT_RUNNING
190
+ if thread.pending_approvals:
191
+ # A blocked native approval must be answered through the interaction
192
+ # card. Interrupting while the SDK reader waits on that approval can
193
+ # deadlock the JSON-RPC command channel.
194
+ return PauseResult.FAILED
195
+ try:
196
+ interrupted = (
197
+ await self._client.pause_goal(thread.thread_id)
198
+ if thread.goal_mode
199
+ else await self._client.interrupt_active_turn(thread.thread_id)
200
+ )
201
+ if not interrupted:
202
+ return PauseResult.NOT_RUNNING
203
+ thread.paused = True
204
+ # ``thread/goal/set(status=paused)`` only prevents the next
205
+ # continuation. Wake the adapter stream as well so the current
206
+ # physical goal turn becomes an honest resumable pause now.
207
+ thread.interrupt_event.set()
208
+ return PauseResult.PAUSED_ACTIVE_TURN
209
+ except Exception: # noqa: BLE001
210
+ logger.exception("codex pause thread %s 失败", handle.run_id)
211
+ return PauseResult.FAILED
212
+
213
+ async def submit(self, handle: RunHandle, payload: ResumePayload) -> None:
214
+ if not payload.call_id:
215
+ raise ValueError("Codex live submit requires call_id")
216
+ raw = payload.data if isinstance(payload.data, dict) else {"decision": payload.data}
217
+ if payload.kind == "approval_decision":
218
+ decision = str(raw.get("decision") or raw.get("name") or "")
219
+ resolved = await self._client.resolve_approval(payload.call_id, decision)
220
+ elif payload.kind == "hitl_answer":
221
+ resolved = await self._client.resolve_interaction(
222
+ payload.call_id,
223
+ {key: value for key, value in raw.items() if key != "decision"},
224
+ )
225
+ else:
226
+ raise ValueError("Codex live submit requires approval_decision or hitl_answer")
227
+ if not resolved:
228
+ raise ValueError(f"interaction {payload.call_id!r} is not pending")
229
+ thread = self._threads.get(handle.run_id)
230
+ if thread is not None:
231
+ thread.pending_approvals.discard(payload.call_id)
232
+
233
+ async def resume(
234
+ self,
235
+ handle: RunHandle,
236
+ target: ResumeTarget,
237
+ payload: Optional[ResumePayload],
238
+ ) -> RunHandle:
239
+ # resume 用 thread id 语义(resume_thread_id),不套 ADK invocation 模型。
240
+ if target.kind != "thread_id":
241
+ raise ValueError(
242
+ f"CodexRuntimeAdapter resume 仅支持 thread_id 目标,得到 {target.kind!r}"
243
+ )
244
+ if handle.run_id in self._do_not_persist:
245
+ raise ValueError(f"thread {handle.run_id} 已被中断/杀进程,不持久化,不可 resume")
246
+ self._pending_cancels.discard(handle.run_id)
247
+ self._known_threads.add(target.id)
248
+ thread = _CodexThread(thread_id=target.id)
249
+ thread.__dict__["_resume"] = {"target": target, "payload": payload}
250
+ request = self._requests.get(handle.run_id)
251
+ if request is not None:
252
+ thread.__dict__["_request_config"] = request
253
+ if request is not None and str(request.config.get("goal_objective") or "").strip():
254
+ # Restart the same native Goal operation on the persisted thread;
255
+ # its transcript remains in Codex, while the objective/config are
256
+ # needed to resume Goal rather than issuing a normal text turn.
257
+ thread.goal_mode = True
258
+ self._threads[handle.run_id] = thread
259
+ # 真实恢复:thread_resume 接入后端既有 thread(thread_id 语义,不套 ADK invocation)。
260
+ resume_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
261
+ if request is not None and request.config:
262
+ for key in ("sandbox", "approval_mode"):
263
+ value = request.config.get(key)
264
+ if value:
265
+ resume_config[key] = value
266
+ await self._client.resume_thread(target.id, resume_config)
267
+ handle.native_ref["thread_id"] = target.id
268
+ handle.native_ref["resume_thread_id"] = target.id
269
+ handle.native_ref["resume_payload"] = payload.data if payload else None
270
+ # 与 ADK/LangGraph 一致的 resume_input 结构(供共用 contract test 断言)。
271
+ handle.native_ref["resume_input"] = {
272
+ "type": "codex.resume_thread",
273
+ "thread_id": target.id,
274
+ "payload": payload.data if payload else None,
275
+ "payload_kind": payload.kind if payload else None,
276
+ "call_id": payload.call_id if payload else None,
277
+ }
278
+ return handle
279
+
280
+ async def checkpoint(self, handle: RunHandle) -> CheckpointDescriptor:
281
+ return CheckpointDescriptor(
282
+ checkpoint_id=str(handle.native_ref.get("thread_id") or handle.run_id),
283
+ invocation_id=handle.run_id,
284
+ capability=CheckpointCapability(
285
+ supported=True,
286
+ granularity="snapshot",
287
+ rollback_scope="turn",
288
+ fork_supported=True,
289
+ durable=False,
290
+ shared_across_pods=False,
291
+ reason="Codex resume/fork by thread id",
292
+ ),
293
+ ref={"thread_id": handle.native_ref.get("thread_id")},
294
+ )
295
+
296
+ 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:
300
+ thread.interrupt_event.set()
301
+ 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)
304
+ finally:
305
+ # AsyncCodex.close owns terminate/wait/kill for the app-server child.
306
+ 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)
310
+
311
+ # ---- stream → RuntimeEvent(phase 翻译 + 中断竞速) ----
312
+
313
+ def _next_thread_id(self) -> str:
314
+ self._seq += 1
315
+ return f"codex_thread_{self._seq}"
316
+
317
+ def _next_seq(self) -> int:
318
+ self._seq += 1
319
+ return self._seq
320
+
321
+ async def _stream_events(self, handle: RunHandle) -> AsyncIterator[RuntimeEvent]:
322
+ thread = self._threads.get(handle.run_id)
323
+ if thread is None:
324
+ thread = _CodexThread(thread_id=handle.run_id)
325
+ self._threads[handle.run_id] = thread
326
+
327
+ if handle.run_id in self._pending_cancels:
328
+ self._pending_cancels.discard(handle.run_id)
329
+ yield self._event(
330
+ handle,
331
+ EventType.RUN_CANCELED,
332
+ {
333
+ "status": "cancelled",
334
+ "cancel_result": CancelResult.PENDING_CANCEL_RECORDED.value,
335
+ },
336
+ )
337
+ return
338
+
339
+ yield self._event(handle, EventType.RUN_STARTED, {"status": "in_progress"})
340
+ tracker = CodexPhaseTracker()
341
+ request = thread.__dict__.get("_start_request")
342
+ resume_state = thread.__dict__.get("_resume")
343
+ if request is not None:
344
+ prompt = _request_prompt(request)
345
+ elif resume_state is not None:
346
+ prompt = _resume_prompt(resume_state.get("payload"))
347
+ else:
348
+ prompt = ""
349
+ run_input = _build_run_input(request, prompt)
350
+ thread.streaming = True
351
+ thread.turn_id = thread.turn_id or f"turn_{thread.thread_id}"
352
+ try:
353
+ async for event in self._map_codex_stream(handle, thread, tracker, run_input):
354
+ 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
+ except asyncio.CancelledError:
369
+ thread.interrupted = True
370
+ self._do_not_persist.add(handle.run_id)
371
+ await self._client.interrupt_active_turn(thread.thread_id)
372
+ raise
373
+ except TimeoutError:
374
+ self.last_cancel_dropped_approvals = set(thread.pending_approvals)
375
+ thread.pending_approvals.clear()
376
+ self._do_not_persist.add(handle.run_id)
377
+ # Closing the SDK transport terminates and waits for the app-server
378
+ # child even when the stream is stuck between notifications.
379
+ 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
386
+ 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
+ )
392
+ finally:
393
+ thread.streaming = False
394
+ thread.done = True
395
+ self._threads.pop(handle.run_id, None)
396
+
397
+ async def _map_codex_stream(
398
+ self,
399
+ handle: RunHandle,
400
+ thread: _CodexThread,
401
+ tracker: CodexPhaseTracker,
402
+ prompt: Any,
403
+ ) -> AsyncIterator[RuntimeEvent]:
404
+ request = thread.__dict__.get("_start_request") or thread.__dict__.get("_request_config")
405
+ run_config: dict[str, Any] = {"sandbox_read_only": self._sandbox_read_only}
406
+ if request is not None and request.config:
407
+ for key in ("sandbox", "approval_mode", "summary", "collaboration_mode"):
408
+ value = request.config.get(key)
409
+ if key == "approval_mode" and value == "manual":
410
+ continue
411
+ if value:
412
+ run_config[key] = value
413
+ if request is not None and request.model:
414
+ run_config["model"] = request.model
415
+ goal_objective = str(
416
+ request.config.get("goal_objective") or ""
417
+ if request is not None and request.config
418
+ else ""
419
+ ).strip()
420
+ if goal_objective:
421
+ thread.goal_mode = True
422
+ codex_gen = self._client.run_goal(
423
+ thread.thread_id,
424
+ goal_objective,
425
+ config=run_config,
426
+ )
427
+ else:
428
+ codex_gen = self._client.run_turn(
429
+ thread.thread_id,
430
+ prompt,
431
+ config=run_config,
432
+ )
433
+ deadline = (
434
+ asyncio.get_running_loop().time() + self._turn_timeout_seconds
435
+ if self._turn_timeout_seconds is not None
436
+ else None
437
+ )
438
+ chunk_task: asyncio.Task[Any] | None = None
439
+ interrupt_task: asyncio.Task[bool] | None = None
440
+ try:
441
+ while True:
442
+ chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
443
+ interrupt_task = asyncio.ensure_future(thread.interrupt_event.wait())
444
+ remaining = (
445
+ max(0.0, deadline - asyncio.get_running_loop().time())
446
+ if deadline is not None
447
+ else None
448
+ )
449
+ done, pending = await asyncio.wait(
450
+ {chunk_task, interrupt_task},
451
+ timeout=remaining,
452
+ return_when=asyncio.FIRST_COMPLETED,
453
+ )
454
+ if not done:
455
+ # Keep handle.stream's turn queue registered until transport
456
+ # close calls SDK MessageRouter.fail_all. Cancelling the
457
+ # asyncio.to_thread waiter first would strand queue.get in
458
+ # the executor and hang interpreter shutdown.
459
+ interrupt_task.cancel()
460
+ await self._client.close()
461
+ await asyncio.gather(chunk_task, interrupt_task, return_exceptions=True)
462
+ raise TimeoutError("codex turn timed out")
463
+ if interrupt_task in done:
464
+ # ``openai-codex`` implements ``next_turn_notification``
465
+ # with ``asyncio.to_thread(queue.get)``. Cancelling that
466
+ # awaiter unregisters its queue while the worker thread is
467
+ # still blocked, so neither a later transport close nor
468
+ # event-loop teardown can wake it. After interrupting a
469
+ # turn, keep the queue registered and drain the SDK's
470
+ # terminal notifications instead. If the backend fails to
471
+ # finish promptly, close the transport *before* awaiting
472
+ # the waiter so MessageRouter.fail_all can release it.
473
+ drain_deadline = (
474
+ asyncio.get_running_loop().time()
475
+ + _INTERRUPT_DRAIN_TIMEOUT_SECONDS
476
+ )
477
+ while True:
478
+ drain_remaining = max(
479
+ 0.0,
480
+ drain_deadline - asyncio.get_running_loop().time(),
481
+ )
482
+ drained, _ = await asyncio.wait(
483
+ {chunk_task},
484
+ timeout=drain_remaining,
485
+ )
486
+ if not drained:
487
+ await self._client.close()
488
+ await asyncio.gather(chunk_task, return_exceptions=True)
489
+ break
490
+ try:
491
+ drained_chunk = chunk_task.result()
492
+ except Exception: # noqa: BLE001
493
+ break
494
+ if drained_chunk is _STREAM_STOP:
495
+ break
496
+ chunk_task = asyncio.ensure_future(_anext_or_stop(codex_gen))
497
+ chunk_task = None
498
+ thread.interrupted = True
499
+ # AGUI 投射器对 RUN_INTERRUPTED 无兜底,必须显式发,否则 raise
500
+ yield self._event(
501
+ 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
+ },
507
+ )
508
+ return
509
+ for task in pending:
510
+ task.cancel()
511
+ if pending:
512
+ await asyncio.gather(*pending, return_exceptions=True)
513
+ chunk = chunk_task.result()
514
+ if chunk is _STREAM_STOP:
515
+ return
516
+ event = self._codex_chunk_to_event(handle, thread, tracker, chunk)
517
+ if event is not None:
518
+ yield event
519
+ finally:
520
+ waiter_tasks = [task for task in (chunk_task, interrupt_task) if task is not None]
521
+ for task in waiter_tasks:
522
+ if not task.done():
523
+ task.cancel()
524
+ if waiter_tasks:
525
+ await asyncio.gather(*waiter_tasks, return_exceptions=True)
526
+ aclose = getattr(codex_gen, "aclose", None)
527
+ if callable(aclose):
528
+ try:
529
+ await aclose()
530
+ except Exception: # noqa: BLE001
531
+ pass
532
+
533
+ def _codex_chunk_to_event(
534
+ self,
535
+ 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
+ )
645
+
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(
740
+ 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(
773
+ 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
785
+
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")
802
+ ),
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
808
+ ),
809
+ seq_id=self._next_seq(),
810
+ phase=phase,
811
+ payload=payload,
812
+ )
813
+
814
+ @staticmethod
815
+ def _mcp_result_text(result: dict[str, Any]) -> str:
816
+ """把 MCP result.content 列表提取为可读文本;无文本时退化 structuredContent。"""
817
+ if not result:
818
+ return ""
819
+ content = result.get("content")
820
+ if isinstance(content, list):
821
+ texts = [
822
+ str(item.get("text") or "")
823
+ for item in content
824
+ if isinstance(item, dict) and item.get("type") == "text"
825
+ ]
826
+ texts = [text for text in texts if text]
827
+ if texts:
828
+ return "\n".join(texts)
829
+ structured = result.get("structuredContent", result.get("structured_content"))
830
+ if structured is not None:
831
+ return json.dumps(structured, ensure_ascii=False)
832
+ return ""
833
+
834
+
835
+ _STREAM_STOP = object()
836
+
837
+
838
+ async def _anext_or_stop(gen: AsyncIterator[Any]) -> Any:
839
+ try:
840
+ return await gen.__anext__()
841
+ except StopAsyncIteration:
842
+ return _STREAM_STOP
843
+
844
+
845
+ def _resume_prompt(payload: Optional[ResumePayload]) -> Any:
846
+ if payload is None:
847
+ return ""
848
+ if isinstance(payload.data, str):
849
+ return payload.data
850
+ return json.dumps(payload.data, ensure_ascii=False, sort_keys=True)
851
+
852
+
853
+ def _request_prompt(request: StartRequest) -> Any:
854
+ """Render canonical conversation history for a native Codex turn.
855
+
856
+ 只传 user 消息历史 + 当前 input,不传 assistant 的完整回复(避免 codex 复述历史)。
857
+ assistant 回复用简短摘要代替,仅维持对话结构。
858
+ """
859
+ # A resumed Codex thread already owns its transcript. Re-sending Studio's
860
+ # transport-neutral history would duplicate every prior turn after refresh.
861
+ if str(request.metadata.get("thread_id") or "").strip():
862
+ return request.input
863
+
864
+ conversation = request.conversation_preprocessing()
865
+ if conversation is None or not conversation.messages:
866
+ return request.input
867
+
868
+ lines: list[str] = []
869
+ for message in conversation.messages:
870
+ content = message.get("content")
871
+ if not isinstance(content, str):
872
+ continue
873
+ role = str(message.get("role") or "user").strip().lower()
874
+ if role == "assistant":
875
+ # 不传完整回复,只标注"已回复",避免 codex 复述
876
+ summary = content[:80] + ("..." if len(content) > 80 else "")
877
+ lines.append(f"[上一轮已回复: {summary}]")
878
+ elif role == "user":
879
+ lines.append(f"User: {content}")
880
+ return "\n".join(lines) or request.input
881
+
882
+
883
+ def _build_run_input(request: Optional[StartRequest], prompt: Any) -> Any:
884
+ """Compose Codex skills and native text/image/mention turn input."""
885
+ try:
886
+ from openai_codex import ImageInput, LocalImageInput, MentionInput, SkillInput, TextInput
887
+ except ImportError:
888
+ return prompt
889
+ skills: list[SkillInput] = []
890
+ if request is not None:
891
+ raw = request.config.get("skills") if request.config else None
892
+ if isinstance(raw, list):
893
+ for item in raw:
894
+ if not isinstance(item, dict):
895
+ continue
896
+ name = str(item.get("name") or "").strip()
897
+ path = str(item.get("path") or "").strip()
898
+ if name and path:
899
+ skills.append(SkillInput(name=name, path=path))
900
+ native_items: list[Any] = []
901
+ raw_input = request.input if request is not None else prompt
902
+ if isinstance(raw_input, list):
903
+ text_replaced = False
904
+ for item in raw_input:
905
+ if not isinstance(item, dict):
906
+ continue
907
+ kind = str(item.get("type") or "")
908
+ if kind == "text":
909
+ text = str(
910
+ prompt
911
+ if not text_replaced and isinstance(prompt, str)
912
+ else item.get("text") or ""
913
+ )
914
+ text_replaced = True
915
+ if text:
916
+ native_items.append(TextInput(text=text))
917
+ elif kind == "image" and item.get("url"):
918
+ native_items.append(ImageInput(url=str(item["url"])))
919
+ elif kind == "localImage" and item.get("path"):
920
+ native_items.append(LocalImageInput(path=str(item["path"])))
921
+ elif kind == "mention" and item.get("path"):
922
+ native_items.append(
923
+ MentionInput(
924
+ name=str(item.get("name") or item["path"]),
925
+ path=str(item["path"]),
926
+ )
927
+ )
928
+ else:
929
+ text = prompt if isinstance(prompt, str) else str(prompt or "")
930
+ if text:
931
+ native_items.append(TextInput(text=text))
932
+ combined = [*skills, *native_items]
933
+ if not combined:
934
+ return prompt
935
+ if len(combined) == 1 and isinstance(combined[0], TextInput) and not skills:
936
+ return combined[0].text
937
+ return combined
938
+
939
+
940
+ __all__ = ["CodexRuntimeAdapter"]