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/client.py ADDED
@@ -0,0 +1,1042 @@
1
+ """CodexRuntimeAdapter 的 codex 后端接口 (goal-09)。
2
+
3
+ 重托管模式:CodexRuntimeAdapter 对执行生命周期负责,不把 cancel/进程管理薄委托给上层。
4
+ ``CodexClient`` 是 codex 后端(thread/turn 生命周期)的最小接口,方法集对齐 OpenAI
5
+ 官方 ``openai-codex`` SDK 的**真实**线程模型(``thread_start`` / ``thread.turn`` /
6
+ ``handle.stream`` / ``handle.interrupt`` / ``thread_resume``):
7
+
8
+ - 生产实现 ``AsyncCodexClient``:基于 ``AsyncCodex``。**lazy import**,缺依赖时显式
9
+ 报"请安装 ksadk[codex]",不静默失败;构造时对真实 SDK 方法做 ``hasattr`` 校验,
10
+ 版本漂移时 fail-fast,而非运行期 AttributeError。
11
+ - 测试实现:用 fake(见 tests/runners/test_codex_runtime.py / test_adapter_contract.py),
12
+ 不需要真 CLI 二进制。
13
+
14
+ 诚实边界:本模块的 SDK **方法面**已对安装的 ``openai-codex==0.144.4`` 实证(方法存在性 +
15
+ 协程/asyncgen 形态);Notification → RuntimeEvent 的**字段级** phase 映射需在接真实 codex
16
+ 后端时按实况对齐(结构已按生成的 payload 类型映射,见 ``_notification_to_event_dict``)。
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import asyncio
22
+ import os
23
+ import queue
24
+ import secrets
25
+ import threading
26
+ from abc import ABC, abstractmethod
27
+ from dataclasses import dataclass, field
28
+ from importlib.metadata import PackageNotFoundError, version
29
+ from typing import Any, AsyncIterator, Optional
30
+ from uuid import uuid4
31
+
32
+ from ksadk.model_proxy import ProxyConfig, ProxyServer
33
+ from ksadk.model_proxy.cache import CapabilityCache, credential_scope
34
+ from ksadk.model_proxy.detect import probe_responses_capability
35
+
36
+ # 探测缓存单例:能力判定跨 client 共享,按 (model, base, credential_scope) 长缓存
37
+ _CAPABILITY_CACHE = CapabilityCache(ttl=3600)
38
+
39
+
40
+ @dataclass
41
+ class _PendingApproval:
42
+ approval_id: str
43
+ thread_id: str
44
+ method: str
45
+ params: dict[str, Any]
46
+ resolved: threading.Event = field(default_factory=threading.Event)
47
+ response: dict[str, Any] = field(default_factory=dict)
48
+
49
+
50
+ @dataclass
51
+ class _PendingInteraction:
52
+ interaction_id: str
53
+ thread_id: str
54
+ method: str
55
+ params: dict[str, Any]
56
+ resolved: threading.Event = field(default_factory=threading.Event)
57
+ response: dict[str, Any] = field(default_factory=dict)
58
+
59
+
60
+ def _is_openai_official(base: str) -> bool:
61
+ """OpenAI 官方 base_url(直连,不探测不代理)。"""
62
+ from urllib.parse import urlsplit
63
+
64
+ host = (urlsplit(base).hostname or "").lower()
65
+ return host == "api.openai.com" or host.endswith(".openai.com")
66
+
67
+
68
+ def _upgrade_http_to_https(upstream: str) -> str:
69
+ """http 非回环自动升级 https(凭证安全 + 兼容历史 http .env;星流等支持 https)。
70
+
71
+ ProxyConfig 强制非回环 https(凭证不裸奔);历史 .env 常写 http://kspmas,
72
+ 这里 upgrade 让 codex 代理对它可用,不改通用模板(ADK 等用 http 本就 OK)。
73
+ """
74
+ from urllib.parse import urlsplit, urlunsplit
75
+
76
+ parts = urlsplit(upstream)
77
+ scheme = parts.scheme.lower()
78
+ host = (parts.hostname or "").lower()
79
+ if scheme == "http" and host not in ("localhost", "127.0.0.1", "::1"):
80
+ return urlunsplit(("https", parts.netloc, parts.path, parts.query, parts.fragment))
81
+ return upstream
82
+
83
+
84
+ def _probe_requires_proxy(model: str, base: str, key: str) -> bool:
85
+ """探测上游:只有**确凿不支持 responses** 才返回 True(走代理)。
86
+
87
+ supported/unknown 都返回 False(直连)。unknown(故障)保守直连——故障 ≠ 模型
88
+ 不支持 responses,不 silent 改变接入方式。结果经 CapabilityCache 缓存(singleflight)。
89
+ """
90
+
91
+ def probe(m: str, b: str):
92
+ import httpx
93
+
94
+ with httpx.Client() as client:
95
+ return probe_responses_capability(client, b, key, m, timeout=15.0)
96
+
97
+ caps = _CAPABILITY_CACHE.get_or_probe(model, base, credential_scope(key), probe)
98
+ return caps.verdict == "unsupported"
99
+
100
+
101
+ class CodexClient(ABC):
102
+ """codex 后端(thread/turn 生命周期)的最小接口(对齐真实 SDK 线程模型)。"""
103
+
104
+ @abstractmethod
105
+ async def start_thread(self, config: Optional[dict[str, Any]] = None) -> str:
106
+ """新建 thread(真实 SDK ``thread_start``),返回 thread_id。"""
107
+ raise NotImplementedError
108
+
109
+ @abstractmethod
110
+ def run_turn(
111
+ self,
112
+ thread_id: str,
113
+ prompt: Any,
114
+ *,
115
+ config: Optional[dict[str, Any]] = None,
116
+ ) -> AsyncIterator[dict[str, Any]]:
117
+ """在 thread 上跑一个 turn(``thread.turn`` + ``handle.stream``),
118
+ 产出规范化事件 dict(``{"method": ..., "params": ...}``)。"""
119
+ raise NotImplementedError
120
+
121
+ def run_goal(
122
+ self,
123
+ thread_id: str,
124
+ objective: str,
125
+ *,
126
+ config: Optional[dict[str, Any]] = None,
127
+ ) -> AsyncIterator[dict[str, Any]]:
128
+ """Run one persisted Codex goal as a logical notification stream."""
129
+
130
+ raise RuntimeError("connected Codex client does not support goal operations")
131
+
132
+ @abstractmethod
133
+ async def interrupt_active_turn(self, thread_id: str) -> bool:
134
+ """interrupt 该 thread 当前活跃 turn(真实 SDK ``handle.interrupt``);
135
+ 无活跃 turn 返回 False。"""
136
+ raise NotImplementedError
137
+
138
+ @abstractmethod
139
+ async def resume_thread(self, thread_id: str, config: Optional[dict[str, Any]] = None) -> str:
140
+ """恢复 thread(真实 SDK ``thread_resume``),返回 thread_id。"""
141
+ raise NotImplementedError
142
+
143
+ async def resolve_approval(self, approval_id: str, decision: str) -> bool:
144
+ """Resolve one live native approval request.
145
+
146
+ Older/custom clients may not expose interactive approvals; returning
147
+ ``False`` keeps that capability boundary explicit without breaking the
148
+ existing client protocol.
149
+ """
150
+
151
+ return False
152
+
153
+ async def resolve_interaction(
154
+ self,
155
+ interaction_id: str,
156
+ data: dict[str, Any],
157
+ ) -> bool:
158
+ """Resolve one live structured user-input request."""
159
+
160
+ return False
161
+
162
+ async def pause_goal(self, thread_id: str) -> bool:
163
+ """Pause an active native goal when supported."""
164
+
165
+ return False
166
+
167
+ async def cancel_goal(self, thread_id: str) -> bool:
168
+ """Cancel an active native goal operation when supported."""
169
+
170
+ return False
171
+
172
+ @abstractmethod
173
+ async def close(self) -> None:
174
+ """释放后端资源(真实 SDK ``close``)。"""
175
+ raise NotImplementedError
176
+
177
+
178
+ def _missing_codex_error() -> RuntimeError:
179
+ return RuntimeError(
180
+ "CodexRuntimeAdapter 需要 openai-codex SDK;请安装 ksadk[codex]:"
181
+ " pip install 'ksadk[codex]'(openai-codex 为可选 extra,不进默认依赖)"
182
+ )
183
+
184
+
185
+ class AsyncCodexClient(CodexClient):
186
+ """基于 OpenAI 官方 ``openai-codex`` SDK(``AsyncCodex``)的生产后端。
187
+
188
+ **lazy import** ``openai_codex``:仅在实际使用时导入,缺依赖时显式报错
189
+ (``pip install 'ksadk[codex]'``),不让所有 ksadk 用户默认背几十 MB CLI 二进制。
190
+ 构造时校验真实 SDK 方法存在(``thread_start``/``thread_resume``/``close`` 及
191
+ ``AsyncThread.turn`` / ``AsyncTurnHandle.stream``/``interrupt``),版本漂移 fail-fast。
192
+ """
193
+
194
+ def __init__(self, config: Any = None, *, proxy_observer: Any = None) -> None:
195
+ try:
196
+ from openai_codex import ( # type: ignore[import-not-found]
197
+ AsyncCodex,
198
+ AsyncThread,
199
+ AsyncTurnHandle,
200
+ )
201
+ except ImportError as exc:
202
+ raise _missing_codex_error() from exc
203
+
204
+ try:
205
+ sdk_version = version("openai-codex")
206
+ except PackageNotFoundError:
207
+ sdk_version = "unknown"
208
+ required = (
209
+ (AsyncCodex, "thread_start"),
210
+ (AsyncCodex, "thread_resume"),
211
+ (AsyncCodex, "close"),
212
+ (AsyncThread, "turn"),
213
+ (AsyncTurnHandle, "stream"),
214
+ (AsyncTurnHandle, "interrupt"),
215
+ )
216
+ for owner, method_name in required:
217
+ if not hasattr(owner, method_name):
218
+ raise RuntimeError(
219
+ f"openai-codex {sdk_version} SDK 缺少 "
220
+ f"{owner.__name__}.{method_name}(版本不兼容)"
221
+ )
222
+
223
+ # AsyncCodex 0.144.4 only accepts one CodexConfig positional/keyword.
224
+ config, self._proxy = self._maybe_apply_proxy(
225
+ config,
226
+ proxy_observer=proxy_observer,
227
+ )
228
+ self._codex = AsyncCodex(config=config)
229
+ self.sdk_version = sdk_version
230
+ self._threads: dict[str, Any] = {} # thread_id -> AsyncThread
231
+ self._active_handles: dict[str, Any] = {} # thread_id -> 活跃 AsyncTurnHandle
232
+ self._goal_states: dict[str, Any] = {}
233
+ self._approval_queues: dict[str, queue.Queue[Any]] = {}
234
+ self._pending_approvals: dict[str, _PendingApproval] = {}
235
+ self._pending_interactions: dict[str, _PendingInteraction] = {}
236
+ self._approval_lock = threading.Lock()
237
+ self._install_approval_bridge()
238
+
239
+ def _install_approval_bridge(self) -> None:
240
+ """Replace the SDK's unconditional accept handler with a HITL bridge.
241
+
242
+ ``openai-codex==0.144.4`` exposes approval callbacks only on its sync
243
+ JSON-RPC client. The public ``AsyncCodex`` wrapper owns that client, so
244
+ this pinned compatibility seam is validated eagerly instead of silently
245
+ auto-accepting tool and file changes.
246
+ """
247
+
248
+ async_client = getattr(self._codex, "_client", None)
249
+ sync_client = getattr(async_client, "_sync", None)
250
+ if sync_client is None or not hasattr(sync_client, "_approval_handler"):
251
+ raise RuntimeError(
252
+ f"openai-codex {self.sdk_version} 不支持交互式审批桥接(内部 API 不兼容)"
253
+ )
254
+ sync_client._approval_handler = self._handle_server_request
255
+
256
+ def _active_request_queue(self, raw: dict[str, Any]) -> tuple[str, queue.Queue[Any] | None]:
257
+ thread_id = str(raw.get("threadId") or raw.get("thread_id") or "")
258
+ if not thread_id:
259
+ with self._approval_lock:
260
+ candidates = list(self._approval_queues)
261
+ if len(candidates) == 1:
262
+ thread_id = candidates[0]
263
+ with self._approval_lock:
264
+ return thread_id, self._approval_queues.get(thread_id)
265
+
266
+ def _handle_server_request(
267
+ self,
268
+ method: str,
269
+ params: dict[str, Any] | None,
270
+ ) -> dict[str, Any]:
271
+ if method in {
272
+ "item/commandExecution/requestApproval",
273
+ "item/fileChange/requestApproval",
274
+ }:
275
+ return self._handle_approval_request(method, params)
276
+ if method == "item/tool/requestUserInput":
277
+ return self._handle_user_input_request(method, params)
278
+ return {}
279
+
280
+ def _handle_approval_request(
281
+ self,
282
+ method: str,
283
+ params: dict[str, Any] | None,
284
+ ) -> dict[str, Any]:
285
+ raw = dict(params or {})
286
+ if method not in {
287
+ "item/commandExecution/requestApproval",
288
+ "item/fileChange/requestApproval",
289
+ }:
290
+ return {}
291
+ thread_id, approval_queue = self._active_request_queue(raw)
292
+ approval_id = str(
293
+ raw.get("itemId") or raw.get("item_id") or raw.get("id") or f"approval_{uuid4().hex}"
294
+ )
295
+ pending = _PendingApproval(
296
+ approval_id=approval_id,
297
+ thread_id=thread_id,
298
+ method=method,
299
+ params=raw,
300
+ )
301
+ with self._approval_lock:
302
+ if approval_queue is None:
303
+ # Fail closed when a request cannot be associated with an
304
+ # active run; never fall back to the SDK's auto-accept default.
305
+ return {"decision": "decline"}
306
+ self._pending_approvals[approval_id] = pending
307
+ approval_queue.put(
308
+ {
309
+ "method": "item/approval/requested",
310
+ "params": {
311
+ "id": approval_id,
312
+ "threadId": thread_id,
313
+ "kind": (
314
+ "command"
315
+ if method == "item/commandExecution/requestApproval"
316
+ else "file_change"
317
+ ),
318
+ "detail": raw,
319
+ },
320
+ }
321
+ )
322
+ pending.resolved.wait()
323
+ with self._approval_lock:
324
+ self._pending_approvals.pop(approval_id, None)
325
+ return pending.response or {"decision": "decline"}
326
+
327
+ def _handle_user_input_request(
328
+ self,
329
+ method: str,
330
+ params: dict[str, Any] | None,
331
+ ) -> dict[str, Any]:
332
+ raw = dict(params or {})
333
+ thread_id, request_queue = self._active_request_queue(raw)
334
+ interaction_id = str(
335
+ raw.get("itemId")
336
+ or raw.get("item_id")
337
+ or raw.get("requestId")
338
+ or raw.get("request_id")
339
+ or raw.get("id")
340
+ or f"input_{uuid4().hex}"
341
+ )
342
+ pending = _PendingInteraction(
343
+ interaction_id=interaction_id,
344
+ thread_id=thread_id,
345
+ method=method,
346
+ params=raw,
347
+ )
348
+ if request_queue is None:
349
+ return {"answers": {}}
350
+
351
+ surface_id = f"input-{interaction_id}"
352
+ components: list[dict[str, Any]] = []
353
+ question_components: list[dict[str, Any]] = []
354
+ input_schema: dict[str, Any] = {"type": "object", "properties": {}}
355
+ questions = raw.get("questions") if isinstance(raw.get("questions"), list) else []
356
+ for index, raw_question in enumerate(questions):
357
+ if not isinstance(raw_question, dict):
358
+ continue
359
+ question_id = str(raw_question.get("id") or f"question_{index + 1}")
360
+ raw_options = raw_question.get("options")
361
+ options = (
362
+ [dict(option) for option in raw_options if isinstance(option, dict)]
363
+ if isinstance(raw_options, list)
364
+ else []
365
+ )
366
+ multiple = bool(
367
+ raw_question.get("multiple")
368
+ or raw_question.get("isMultiple")
369
+ or raw_question.get("is_multiple")
370
+ or raw_question.get("isMultiSelect")
371
+ or raw_question.get("is_multi_select")
372
+ )
373
+ question_components.append(
374
+ {
375
+ "id": question_id,
376
+ "component": "MultipleChoice",
377
+ "props": {
378
+ "name": question_id,
379
+ "label": str(
380
+ raw_question.get("header")
381
+ or raw_question.get("question")
382
+ or question_id
383
+ ),
384
+ "description": str(raw_question.get("question") or ""),
385
+ "options": options,
386
+ "multiple": multiple,
387
+ "allow_other": bool(
388
+ raw_question.get("isOther") or raw_question.get("is_other")
389
+ ),
390
+ "secret": bool(
391
+ raw_question.get("isSecret") or raw_question.get("is_secret")
392
+ ),
393
+ },
394
+ }
395
+ )
396
+ option_labels = [
397
+ str(option.get("label") or "") for option in options if option.get("label")
398
+ ]
399
+ input_schema["properties"][question_id] = (
400
+ {
401
+ "type": "array",
402
+ "items": {"type": "string", "enum": option_labels},
403
+ }
404
+ if multiple
405
+ else {"type": "string", "enum": option_labels}
406
+ )
407
+ components.append(
408
+ {
409
+ "id": "form",
410
+ "component": "Form",
411
+ "props": {"title": "需要你的反馈", "submit_label": "提交"},
412
+ "children": question_components,
413
+ }
414
+ )
415
+ with self._approval_lock:
416
+ self._pending_interactions[interaction_id] = pending
417
+ request_queue.put(
418
+ {
419
+ "method": "a2ui/surface",
420
+ "params": {
421
+ "surface_id": surface_id,
422
+ "surface": {
423
+ "catalog_id": "https://a2ui.org/specification/v0_9/basic_catalog.json",
424
+ "components": components,
425
+ "data_model": {},
426
+ },
427
+ },
428
+ }
429
+ )
430
+ request_queue.put(
431
+ {
432
+ "method": "a2ui/interaction",
433
+ "params": {
434
+ "surface_id": surface_id,
435
+ "interaction_id": interaction_id,
436
+ "kind": "form",
437
+ "input_schema": input_schema,
438
+ "is_blocking": bool(raw.get("isBlocking", raw.get("is_blocking", True))),
439
+ },
440
+ }
441
+ )
442
+ pending.resolved.wait()
443
+ with self._approval_lock:
444
+ self._pending_interactions.pop(interaction_id, None)
445
+ return pending.response or {"answers": {}}
446
+
447
+ @staticmethod
448
+ def _maybe_apply_proxy(
449
+ config: Any,
450
+ *,
451
+ proxy_observer: Any = None,
452
+ ) -> tuple[Any, Any]:
453
+ """codex 代理启用:**智能探测 fallback(带显式覆盖)**。
454
+
455
+ - ``KSADK_CODEX_USE_PROXY=1`` → 强制开代理;``=0`` → 强制直连(可人工覆盖误判)。
456
+ - **未设 env 时智能探测**:OpenAI 官方 base_url 直连(不探测);自定义上游
457
+ (星流等)探测 responses 能力(detect.py + CapabilityCache 缓存,一次探测长缓存):
458
+ - ``supported`` → 直连(原生 responses 可用)
459
+ - ``unsupported`` → 自动启用代理(chat 模型,经转换层)
460
+ - ``unknown``(故障/超时)→ **保守直连**,不 silent 改变接入方式
461
+ - 凭证闭合:codex 子进程只拿随机 KSADK_PROXY_TOKEN;上游 key 留父进程。
462
+ - 互斥:launch_args_override 已设时 raise(override 整体覆盖命令行)。
463
+
464
+ 返回 (新 config, ProxyServer | None)。staticmethod 便于单测。
465
+ """
466
+ runtime_env = {**os.environ, **(getattr(config, "env", None) or {})}
467
+ env_val = runtime_env.get("KSADK_CODEX_USE_PROXY")
468
+ if env_val == "0":
469
+ return config, None
470
+ if env_val == "1":
471
+ return AsyncCodexClient._start_proxy_and_inject(
472
+ config,
473
+ proxy_observer=proxy_observer,
474
+ )
475
+ # 未设:智能探测
476
+ base = (
477
+ runtime_env.get("KSADK_PROXY_UPSTREAM_BASE")
478
+ or runtime_env.get("OPENAI_BASE_URL")
479
+ or runtime_env.get("OPENAI_API_BASE")
480
+ or ""
481
+ )
482
+ if not base or _is_openai_official(base):
483
+ return config, None # OpenAI 官方:直连,不探测
484
+ model = runtime_env.get("OPENAI_MODEL_NAME") or runtime_env.get("MODEL_NAME") or ""
485
+ key = runtime_env.get("KSADK_PROXY_UPSTREAM_KEY") or runtime_env.get("OPENAI_API_KEY") or ""
486
+ if _probe_requires_proxy(model, base, key):
487
+ return AsyncCodexClient._start_proxy_and_inject(
488
+ config,
489
+ proxy_observer=proxy_observer,
490
+ )
491
+ return config, None
492
+
493
+ @staticmethod
494
+ def _start_proxy_and_inject(
495
+ config: Any,
496
+ *,
497
+ proxy_observer: Any = None,
498
+ ) -> tuple[Any, Any]:
499
+ """起进程内 ProxyServer + 注入 codex provider(opt-in/探测判定走代理时)。"""
500
+ import dataclasses
501
+
502
+ from openai_codex import CodexConfig # type: ignore[import-not-found]
503
+
504
+ cfg = config if isinstance(config, CodexConfig) else CodexConfig()
505
+ if cfg.launch_args_override is not None:
506
+ raise RuntimeError(
507
+ "KSADK_CODEX_USE_PROXY 与 CodexConfig.launch_args_override 互斥:"
508
+ "代理注入靠 config_overrides,而 launch_args_override 会整体覆盖命令行"
509
+ )
510
+ runtime_env = {**os.environ, **(cfg.env or {})}
511
+ upstream = (
512
+ runtime_env.get("KSADK_PROXY_UPSTREAM_BASE")
513
+ or runtime_env.get("OPENAI_BASE_URL")
514
+ or runtime_env.get("OPENAI_API_BASE")
515
+ or "https://kspmas.ksyun.com/v1"
516
+ )
517
+ upstream = _upgrade_http_to_https(upstream)
518
+ api_key = (
519
+ runtime_env.get("KSADK_PROXY_UPSTREAM_KEY") or runtime_env.get("OPENAI_API_KEY") or ""
520
+ )
521
+ token = secrets.token_hex(16)
522
+ proxy = ProxyServer(
523
+ ProxyConfig(
524
+ upstream_base=upstream,
525
+ api_key=api_key,
526
+ local_token=token,
527
+ upstream_model=runtime_env.get("OPENAI_MODEL_NAME")
528
+ or runtime_env.get("MODEL_NAME")
529
+ or "",
530
+ event_callback=proxy_observer,
531
+ )
532
+ )
533
+ proxy.start()
534
+ overrides = list(cfg.config_overrides or ())
535
+ overrides += [
536
+ "model_provider=ksadk_proxy",
537
+ "model_providers.ksadk_proxy.name=ksadk_proxy",
538
+ f"model_providers.ksadk_proxy.base_url={proxy.base_url}",
539
+ "model_providers.ksadk_proxy.env_key=KSADK_PROXY_TOKEN",
540
+ "model_providers.ksadk_proxy.wire_api=responses",
541
+ "model_providers.ksadk_proxy.supports_websockets=false",
542
+ "web_search=disabled",
543
+ "features.multi_agent=false",
544
+ "features.multi_agent_v2=false",
545
+ ]
546
+ # The local proxy owns the upstream credential. The Codex child only
547
+ # receives a random loopback token, so prompt-driven commands cannot
548
+ # read the real provider key from their environment.
549
+ env = dict(runtime_env)
550
+ for secret_name in (
551
+ "OPENAI_API_KEY",
552
+ "AGENTKIT_MODEL_API_KEY",
553
+ "KSADK_PROXY_UPSTREAM_KEY",
554
+ ):
555
+ env.pop(secret_name, None)
556
+ env["KSADK_PROXY_TOKEN"] = token
557
+ return dataclasses.replace(cfg, config_overrides=tuple(overrides), env=env), proxy
558
+
559
+ async def start_thread(self, config: Optional[dict[str, Any]] = None) -> str:
560
+ if self._uses_manual_approval(config):
561
+ thread = await self._start_manual_thread(config)
562
+ else:
563
+ thread = await self._codex.thread_start(**self._thread_kwargs(config))
564
+ self._threads[thread.id] = thread
565
+ return str(thread.id)
566
+
567
+ async def resume_thread(self, thread_id: str, config: Optional[dict[str, Any]] = None) -> str:
568
+ # An ephemeral thread has no rollout on disk, so SDK thread_resume would
569
+ # fail with -32600. Reuse its live AsyncThread for same-process resume.
570
+ cached = self._threads.get(thread_id)
571
+ if cached is not None:
572
+ return str(cached.id)
573
+ if self._uses_manual_approval(config):
574
+ thread = await self._resume_manual_thread(thread_id, config)
575
+ else:
576
+ kwargs = self._thread_kwargs(config)
577
+ # AsyncCodex.thread_resume 0.144.4 has no ephemeral parameter.
578
+ kwargs.pop("ephemeral", None)
579
+ thread = await self._codex.thread_resume(thread_id, **kwargs)
580
+ self._threads[thread.id] = thread
581
+ return str(thread.id)
582
+
583
+ @staticmethod
584
+ def _uses_manual_approval(config: Optional[dict[str, Any]]) -> bool:
585
+ return str((config or {}).get("approval_mode") or "").strip().lower() == "manual"
586
+
587
+ async def _start_manual_thread(self, config: Optional[dict[str, Any]]) -> Any:
588
+ """Start a thread whose native approvals are reviewed by Studio users.
589
+
590
+ ``openai-codex==0.144.4`` exposes ``ApprovalsReviewer.user`` on the
591
+ generated app-server contract but omits it from the public
592
+ ``ApprovalMode`` enum. Use that pinned wire contract explicitly rather
593
+ than falling back to ``auto_review``.
594
+ """
595
+
596
+ from openai_codex import AsyncThread # type: ignore[import-not-found]
597
+ from openai_codex._sandbox import _sandbox_mode # type: ignore[import-not-found]
598
+ from openai_codex.generated.v2_all import ( # type: ignore[import-not-found]
599
+ ApprovalsReviewer,
600
+ AskForApproval,
601
+ AskForApprovalValue,
602
+ ThreadStartParams,
603
+ )
604
+
605
+ kwargs = self._thread_kwargs(self._without_manual_approval(config))
606
+ kwargs.pop("approval_mode", None)
607
+ sandbox = kwargs.pop("sandbox", None)
608
+ await self._codex._ensure_initialized()
609
+ started = await self._codex._client.thread_start(
610
+ ThreadStartParams(
611
+ approval_policy=AskForApproval(root=AskForApprovalValue.on_request),
612
+ approvals_reviewer=ApprovalsReviewer.user,
613
+ sandbox=_sandbox_mode(sandbox),
614
+ **kwargs,
615
+ )
616
+ )
617
+ return AsyncThread(self._codex, started.thread.id)
618
+
619
+ async def _resume_manual_thread(
620
+ self,
621
+ thread_id: str,
622
+ config: Optional[dict[str, Any]],
623
+ ) -> Any:
624
+ from openai_codex import AsyncThread # type: ignore[import-not-found]
625
+ from openai_codex._sandbox import _sandbox_mode # type: ignore[import-not-found]
626
+ from openai_codex.generated.v2_all import ( # type: ignore[import-not-found]
627
+ ApprovalsReviewer,
628
+ AskForApproval,
629
+ AskForApprovalValue,
630
+ ThreadResumeParams,
631
+ )
632
+
633
+ kwargs = self._thread_kwargs(self._without_manual_approval(config))
634
+ kwargs.pop("approval_mode", None)
635
+ kwargs.pop("ephemeral", None)
636
+ sandbox = kwargs.pop("sandbox", None)
637
+ await self._codex._ensure_initialized()
638
+ resumed = await self._codex._client.thread_resume(
639
+ thread_id,
640
+ ThreadResumeParams(
641
+ thread_id=thread_id,
642
+ approval_policy=AskForApproval(root=AskForApprovalValue.on_request),
643
+ approvals_reviewer=ApprovalsReviewer.user,
644
+ sandbox=_sandbox_mode(sandbox),
645
+ **kwargs,
646
+ ),
647
+ )
648
+ return AsyncThread(self._codex, resumed.thread.id)
649
+
650
+ @staticmethod
651
+ def _without_manual_approval(
652
+ config: Optional[dict[str, Any]],
653
+ ) -> dict[str, Any]:
654
+ result = dict(config or {})
655
+ result.pop("approval_mode", None)
656
+ return result
657
+
658
+ def run_turn(
659
+ self,
660
+ thread_id: str,
661
+ prompt: Any,
662
+ *,
663
+ config: Optional[dict[str, Any]] = None,
664
+ ) -> AsyncIterator[dict[str, Any]]:
665
+ return self._run_turn_gen(thread_id, prompt, config)
666
+
667
+ def run_goal(
668
+ self,
669
+ thread_id: str,
670
+ objective: str,
671
+ *,
672
+ config: Optional[dict[str, Any]] = None,
673
+ ) -> AsyncIterator[dict[str, Any]]:
674
+ return self._run_goal_gen(thread_id, objective, config)
675
+
676
+ async def _run_turn_gen(
677
+ self, thread_id: str, prompt: Any, config: Optional[dict[str, Any]]
678
+ ) -> AsyncIterator[dict[str, Any]]:
679
+ thread = self._threads.get(thread_id)
680
+ if thread is None:
681
+ # 未显式 start/resume 的 thread_id:按 resume 语义接入真实后端。
682
+ await self.resume_thread(thread_id, config)
683
+ thread = self._threads[thread_id]
684
+ approval_queue: queue.Queue[Any] = queue.Queue()
685
+ with self._approval_lock:
686
+ self._approval_queues[thread_id] = approval_queue
687
+ handle = await self._start_turn(thread, self._coerce_input(prompt), config)
688
+ self._active_handles[thread_id] = handle
689
+ notifications = handle.stream()
690
+ notification_task: Any = None
691
+ approval_task: Any = None
692
+ try:
693
+ while True:
694
+ if notification_task is None:
695
+ notification_task = asyncio.create_task(notifications.__anext__())
696
+ if approval_task is None:
697
+ approval_task = asyncio.create_task(asyncio.to_thread(approval_queue.get))
698
+ done, _pending = await asyncio.wait(
699
+ {notification_task, approval_task},
700
+ return_when=asyncio.FIRST_COMPLETED,
701
+ )
702
+ if approval_task in done:
703
+ approval_event = approval_task.result()
704
+ approval_task = None
705
+ if approval_event is not None:
706
+ yield approval_event
707
+ if notification_task in done:
708
+ try:
709
+ notification = notification_task.result()
710
+ except StopAsyncIteration:
711
+ break
712
+ notification_task = None
713
+ event = self._notification_to_event_dict(notification)
714
+ if event is not None:
715
+ yield event
716
+ finally:
717
+ approval_queue.put(None)
718
+ for task in (notification_task, approval_task):
719
+ if task is not None and not task.done():
720
+ task.cancel()
721
+ await asyncio.gather(
722
+ *(task for task in (notification_task, approval_task) if task is not None),
723
+ return_exceptions=True,
724
+ )
725
+ self._active_handles.pop(thread_id, None)
726
+ with self._approval_lock:
727
+ self._approval_queues.pop(thread_id, None)
728
+
729
+ async def _run_goal_gen(
730
+ self,
731
+ thread_id: str,
732
+ objective: str,
733
+ config: Optional[dict[str, Any]],
734
+ ) -> AsyncIterator[dict[str, Any]]:
735
+ if thread_id not in self._threads:
736
+ await self.resume_thread(thread_id, config)
737
+ from openai_codex._goal import _AsyncGoalNotificationStream
738
+
739
+ low_level = self._codex._client
740
+ approval_queue: queue.Queue[Any] = queue.Queue()
741
+ with self._approval_lock:
742
+ self._approval_queues[thread_id] = approval_queue
743
+ state, _logical_turn_id = await low_level.start_goal_operation(thread_id, objective)
744
+ self._goal_states[thread_id] = state
745
+ notifications = _AsyncGoalNotificationStream(
746
+ state=state,
747
+ next_notification=lambda: low_level.next_goal_notification(state),
748
+ unregister=lambda: low_level.unregister_goal_operation(state),
749
+ cancel_goal=lambda: low_level.cancel_goal_operation(state),
750
+ )
751
+ notification_task: Any = None
752
+ approval_task: Any = None
753
+ try:
754
+ while True:
755
+ if notification_task is None:
756
+ notification_task = asyncio.create_task(notifications.__anext__())
757
+ if approval_task is None:
758
+ approval_task = asyncio.create_task(asyncio.to_thread(approval_queue.get))
759
+ done, _pending = await asyncio.wait(
760
+ {notification_task, approval_task},
761
+ return_when=asyncio.FIRST_COMPLETED,
762
+ )
763
+ if approval_task in done:
764
+ approval_event = approval_task.result()
765
+ approval_task = None
766
+ if approval_event is not None:
767
+ yield approval_event
768
+ if notification_task in done:
769
+ try:
770
+ notification = notification_task.result()
771
+ except StopAsyncIteration:
772
+ break
773
+ notification_task = None
774
+ event = self._notification_to_event_dict(notification)
775
+ if event is not None:
776
+ yield event
777
+ finally:
778
+ approval_queue.put(None)
779
+ for task in (notification_task, approval_task):
780
+ if task is not None and not task.done():
781
+ task.cancel()
782
+ await asyncio.gather(
783
+ *(task for task in (notification_task, approval_task) if task is not None),
784
+ return_exceptions=True,
785
+ )
786
+ await notifications.aclose()
787
+ self._goal_states.pop(thread_id, None)
788
+ with self._approval_lock:
789
+ self._approval_queues.pop(thread_id, None)
790
+
791
+ async def _start_turn(
792
+ self,
793
+ thread: Any,
794
+ prompt: Any,
795
+ config: Optional[dict[str, Any]],
796
+ ) -> Any:
797
+ mode = str((config or {}).get("collaboration_mode") or "").strip().lower()
798
+ if not mode or mode == "default":
799
+ return await thread.turn(prompt, **self._turn_kwargs(config))
800
+ if mode != "plan":
801
+ raise ValueError("collaboration_mode must be default or plan")
802
+
803
+ # openai-codex 0.144.4 predates the public collaboration_mode argument,
804
+ # while the bundled app-server already accepts it. Keep this private
805
+ # compatibility seam small and covered by a wire-payload test.
806
+ from openai_codex import AsyncTurnHandle
807
+ from openai_codex._approval_mode import _approval_mode_override_settings
808
+ from openai_codex._inputs import _normalize_run_input, _to_wire_input
809
+ from openai_codex._sandbox import _sandbox_policy
810
+ from openai_codex.generated.v2_all import TurnStartParams
811
+
812
+ turn_config = self._turn_kwargs(config)
813
+ approval_mode = turn_config.pop("approval_mode", None)
814
+ sandbox = turn_config.pop("sandbox", None)
815
+ approval_policy, approvals_reviewer = _approval_mode_override_settings(approval_mode)
816
+ wire_input = _to_wire_input(_normalize_run_input(prompt))
817
+ params = TurnStartParams(
818
+ thread_id=thread.id,
819
+ input=wire_input,
820
+ approval_policy=approval_policy,
821
+ approvals_reviewer=approvals_reviewer,
822
+ sandbox_policy=_sandbox_policy(sandbox),
823
+ **turn_config,
824
+ ).model_dump(mode="json", by_alias=True, exclude_none=True)
825
+ params["collaborationMode"] = {
826
+ "mode": "plan",
827
+ "settings": {
828
+ "model": str((config or {}).get("model") or ""),
829
+ "reasoning_effort": None,
830
+ "developer_instructions": None,
831
+ },
832
+ }
833
+ started = await self._codex._client.turn_start(thread.id, wire_input, params=params)
834
+ return AsyncTurnHandle(self._codex, thread.id, started.turn.id)
835
+
836
+ async def interrupt_active_turn(self, thread_id: str) -> bool:
837
+ handle = self._active_handles.get(thread_id)
838
+ if handle is None:
839
+ return False
840
+ await handle.interrupt()
841
+ return True
842
+
843
+ async def pause_goal(self, thread_id: str) -> bool:
844
+ state = self._goal_states.get(thread_id)
845
+ if state is None:
846
+ return False
847
+ await self._codex._client.pause_goal(thread_id)
848
+ return True
849
+
850
+ async def cancel_goal(self, thread_id: str) -> bool:
851
+ state = self._goal_states.get(thread_id)
852
+ if state is None:
853
+ return False
854
+ await self._codex._client.cancel_goal_operation(state)
855
+ return True
856
+
857
+ async def resolve_approval(self, approval_id: str, decision: str) -> bool:
858
+ normalized = {
859
+ "approve": "accept",
860
+ "accept": "accept",
861
+ "approve_session": "acceptForSession",
862
+ "accept_for_session": "acceptForSession",
863
+ "deny": "decline",
864
+ "decline": "decline",
865
+ "cancel": "cancel",
866
+ }.get(str(decision).strip().lower())
867
+ if normalized is None:
868
+ raise ValueError("approval decision must be approve, approve_session, deny, or cancel")
869
+ with self._approval_lock:
870
+ pending = self._pending_approvals.get(approval_id)
871
+ if pending is None:
872
+ return False
873
+ pending.response = {"decision": normalized}
874
+ pending.resolved.set()
875
+ return True
876
+
877
+ async def resolve_interaction(
878
+ self,
879
+ interaction_id: str,
880
+ data: dict[str, Any],
881
+ ) -> bool:
882
+ answers: dict[str, dict[str, list[str]]] = {}
883
+ for key, value in data.items():
884
+ if key in {"decision", "name", "action"} or value is None:
885
+ continue
886
+ raw_answers = value if isinstance(value, list) else [value]
887
+ normalized = [str(answer) for answer in raw_answers if str(answer)]
888
+ answers[str(key)] = {"answers": normalized}
889
+ with self._approval_lock:
890
+ pending = self._pending_interactions.get(interaction_id)
891
+ if pending is None:
892
+ return False
893
+ pending.response = {"answers": answers}
894
+ pending.resolved.set()
895
+ return True
896
+
897
+ async def close(self) -> None:
898
+ approval_lock = getattr(self, "_approval_lock", None)
899
+ if approval_lock is None:
900
+ pending: list[_PendingApproval] = []
901
+ else:
902
+ with approval_lock:
903
+ pending = list(getattr(self, "_pending_approvals", {}).values())
904
+ for approval in pending:
905
+ approval.response = {"decision": "cancel"}
906
+ approval.resolved.set()
907
+ if approval_lock is None:
908
+ interactions: list[_PendingInteraction] = []
909
+ else:
910
+ with approval_lock:
911
+ interactions = list(getattr(self, "_pending_interactions", {}).values())
912
+ for interaction in interactions:
913
+ interaction.response = {"answers": {}}
914
+ interaction.resolved.set()
915
+ for thread_id, state in list(getattr(self, "_goal_states", {}).items()):
916
+ try:
917
+ await self._codex._client.cancel_goal_operation(state)
918
+ except Exception:
919
+ pass
920
+ finally:
921
+ self._goal_states.pop(thread_id, None)
922
+ try:
923
+ await self._codex.close()
924
+ finally:
925
+ self._active_handles.clear()
926
+ self._threads.clear()
927
+ if self._proxy is not None:
928
+ self._proxy.stop()
929
+ self._proxy = None
930
+
931
+ # ---- 内部:config / input / 事件映射 ----
932
+
933
+ @staticmethod
934
+ def _thread_kwargs(config: Optional[dict[str, Any]]) -> dict[str, Any]:
935
+ """Map runtime config to the exact 0.144.4 thread API surface."""
936
+ from openai_codex import ApprovalMode, Sandbox # type: ignore[import-not-found]
937
+
938
+ config = config or {}
939
+ known = {
940
+ "approval_mode",
941
+ "model",
942
+ "model_provider",
943
+ "cwd",
944
+ "sandbox",
945
+ "service_tier",
946
+ "base_instructions",
947
+ "developer_instructions",
948
+ "ephemeral",
949
+ }
950
+ result = {k: v for k, v in config.items() if k in known}
951
+ # KSADK sessions are ephemeral so an interrupted/half-written turn cannot
952
+ # later be revived from Codex's on-disk session store.
953
+ result.setdefault("ephemeral", True)
954
+ if config.get("sandbox_read_only", False):
955
+ result["sandbox"] = Sandbox.read_only
956
+ result.setdefault("approval_mode", ApprovalMode.deny_all)
957
+ else:
958
+ result["sandbox"] = _coerce_enum(Sandbox, result.get("sandbox"))
959
+ result["approval_mode"] = _coerce_enum(ApprovalMode, result.get("approval_mode"))
960
+ return {key: value for key, value in result.items() if value is not None}
961
+
962
+ @staticmethod
963
+ def _turn_kwargs(config: Optional[dict[str, Any]]) -> dict[str, Any]:
964
+ """Map runtime config to the exact 0.144.4 AsyncThread.turn surface."""
965
+ from openai_codex import ApprovalMode, Sandbox # type: ignore[import-not-found]
966
+
967
+ config = config or {}
968
+ known = {
969
+ "approval_mode",
970
+ "cwd",
971
+ "effort",
972
+ "model",
973
+ "output_schema",
974
+ "personality",
975
+ "sandbox",
976
+ "service_tier",
977
+ "summary",
978
+ }
979
+ result = {key: value for key, value in config.items() if key in known}
980
+ if config.get("sandbox_read_only", False):
981
+ result["sandbox"] = Sandbox.read_only
982
+ result.setdefault("approval_mode", ApprovalMode.deny_all)
983
+ else:
984
+ result["sandbox"] = _coerce_enum(Sandbox, result.get("sandbox"))
985
+ result["approval_mode"] = _coerce_enum(ApprovalMode, result.get("approval_mode"))
986
+ return {key: value for key, value in result.items() if value is not None}
987
+
988
+ @staticmethod
989
+ def _coerce_input(prompt: Any) -> Any:
990
+ # RunInput 接受 str 或 [TextInput|...];str 直接透传。
991
+ return prompt if isinstance(prompt, str) else prompt
992
+
993
+ @staticmethod
994
+ def _notification_to_event_dict(notification: Any) -> Optional[dict[str, Any]]:
995
+ """把真实 ``Notification``(method + 类型化 payload)映射为运行时消费的规范化 dict。
996
+
997
+ 按 payload 类型路由(结构已对生成的 payload 类型实证);字段级 phase 细节在接
998
+ 真实后端时对齐。
999
+ """
1000
+ payload = notification.payload
1001
+ if hasattr(payload, "model_dump"):
1002
+ params = payload.model_dump(mode="json")
1003
+ else:
1004
+ params = getattr(payload, "params", None)
1005
+ if not isinstance(params, dict):
1006
+ params = {}
1007
+
1008
+ method = str(getattr(notification, "method", ""))
1009
+ supported_methods = {
1010
+ "item/started",
1011
+ "item/completed",
1012
+ "item/agentMessage/delta",
1013
+ "item/autoApprovalReview/started",
1014
+ "item/autoApprovalReview/completed",
1015
+ "thread/tokenUsage/updated",
1016
+ "turn/started",
1017
+ "turn/completed",
1018
+ "error",
1019
+ "item/reasoning/summaryTextDelta",
1020
+ "item/reasoning/textDelta",
1021
+ "item/reasoning/summaryPartAdded",
1022
+ "thread/goal/updated",
1023
+ "thread/goal/cleared",
1024
+ }
1025
+ if method in supported_methods:
1026
+ return {"method": method, "params": params}
1027
+ return None
1028
+
1029
+
1030
+ def _coerce_enum(enum_type: Any, value: Any) -> Any:
1031
+ if value is None or isinstance(value, enum_type):
1032
+ return value
1033
+ try:
1034
+ return enum_type(value)
1035
+ except (TypeError, ValueError) as exc:
1036
+ supported = ", ".join(member.value for member in enum_type)
1037
+ raise ValueError(
1038
+ f"unsupported {enum_type.__name__} {value!r}; expected {supported}"
1039
+ ) from exc
1040
+
1041
+
1042
+ __all__ = ["AsyncCodexClient", "CodexClient"]