agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.8.0__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 (384) hide show
  1. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +41 -23
  2. agentengine_sdk_python-0.8.0.dist-info/RECORD +578 -0
  3. ksadk/a2a/__init__.py +178 -11
  4. ksadk/a2a/bootstrap.py +334 -0
  5. ksadk/a2a/card.py +120 -0
  6. ksadk/a2a/context_store.py +203 -0
  7. ksadk/a2a/control_plane.py +818 -0
  8. ksadk/a2a/credential.py +99 -0
  9. ksadk/a2a/event_adapter.py +149 -0
  10. ksadk/a2a/executor.py +406 -89
  11. ksadk/a2a/external_transport.py +516 -0
  12. ksadk/a2a/identity.py +304 -0
  13. ksadk/a2a/ids.py +22 -0
  14. ksadk/a2a/langgraph.py +285 -0
  15. ksadk/a2a/resume_store.py +201 -0
  16. ksadk/a2a/routes.py +105 -0
  17. ksadk/a2a/server.py +107 -60
  18. ksadk/a2a/space_client.py +1101 -0
  19. ksadk/a2a/task_adapter.py +423 -0
  20. ksadk/a2a/task_event_dispatcher.py +167 -0
  21. ksadk/a2a/task_event_outbox.py +326 -0
  22. ksadk/a2a/task_store.py +206 -0
  23. ksadk/a2ui/__init__.py +27 -0
  24. ksadk/a2ui/core.py +186 -0
  25. ksadk/a2ui/models.py +166 -0
  26. ksadk/a2ui/renderer.py +198 -0
  27. ksadk/agui/__init__.py +9 -0
  28. ksadk/agui/a2ui_projection.py +92 -0
  29. ksadk/agui/agent.py +857 -0
  30. ksadk/agui/config.py +86 -0
  31. ksadk/agui/routes.py +55 -0
  32. ksadk/api/client.py +252 -191
  33. ksadk/builders/__init__.py +2 -0
  34. ksadk/builders/base.py +16 -14
  35. ksadk/builders/code_builder.py +318 -236
  36. ksadk/builders/container_builder.py +187 -167
  37. ksadk/builders/framework_requirements.py +29 -11
  38. ksadk/builders/ks3_uploader.py +58 -27
  39. ksadk/builders/managed_runtime_builder.py +145 -0
  40. ksadk/builders/mcp_builder.py +91 -92
  41. ksadk/builders/requirements_utils.py +1 -5
  42. ksadk/cli/__init__.py +89 -171
  43. ksadk/cli/cmd_a2a.py +254 -70
  44. ksadk/cli/cmd_agent.py +19 -5
  45. ksadk/cli/cmd_build.py +158 -29
  46. ksadk/cli/cmd_completion.py +47 -35
  47. ksadk/cli/cmd_config.py +267 -198
  48. ksadk/cli/cmd_create.py +533 -726
  49. ksadk/cli/cmd_dashboard.py +120 -45
  50. ksadk/cli/cmd_deploy.py +117 -58
  51. ksadk/cli/cmd_destroy.py +44 -14
  52. ksadk/cli/cmd_files.py +63 -24
  53. ksadk/cli/cmd_hermes.py +173 -90
  54. ksadk/cli/cmd_invoke.py +388 -159
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +103 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +488 -228
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +44 -14
  61. ksadk/cli/cmd_status.py +80 -31
  62. ksadk/cli/cmd_version.py +20 -13
  63. ksadk/cli/cmd_web.py +89 -13
  64. ksadk/cli/deploy_utils.py +20 -20
  65. ksadk/cli/dry_run.py +5 -3
  66. ksadk/cli/error_utils.py +37 -12
  67. ksadk/cli/global_options.py +1 -2
  68. ksadk/cli/local_runtime.py +1 -2
  69. ksadk/cli/model_catalog.py +6 -2
  70. ksadk/cli/network_options.py +38 -16
  71. ksadk/cli/resource_common.py +6 -2
  72. ksadk/cli/storage.py +0 -1
  73. ksadk/cli/ui.py +5 -3
  74. ksadk/cli/workflow_common.py +51 -17
  75. ksadk/codex/__init__.py +12 -0
  76. ksadk/codex/client.py +408 -0
  77. ksadk/codex/phase.py +143 -0
  78. ksadk/codex/runtime.py +449 -0
  79. ksadk/common/aicp_env.py +0 -1
  80. ksadk/common/auth.py +2 -2
  81. ksadk/common/constants.py +1 -3
  82. ksadk/common/llm_utils.py +17 -13
  83. ksadk/compat/__init__.py +5 -0
  84. ksadk/compat/adk_compat.py +200 -0
  85. ksadk/compat/copilotkit_a2ui.py +330 -0
  86. ksadk/configs/__init__.py +12 -12
  87. ksadk/configs/env_registry.py +501 -100
  88. ksadk/configs/global_config.py +2 -1
  89. ksadk/configs/settings.py +151 -124
  90. ksadk/conversations/__init__.py +7 -5
  91. ksadk/conversations/attachment_storage.py +16 -11
  92. ksadk/conversations/attachments.py +44 -36
  93. ksadk/conversations/compaction_pipeline.py +12 -12
  94. ksadk/conversations/compaction_prompt.py +9 -5
  95. ksadk/conversations/context.py +221 -6
  96. ksadk/conversations/message_projection.py +656 -0
  97. ksadk/conversations/model_context.py +22 -6
  98. ksadk/conversations/model_options.py +0 -1
  99. ksadk/conversations/normalize.py +20 -16
  100. ksadk/conversations/reasoning_markup.py +1 -1
  101. ksadk/conversations/runtime.py +339 -5187
  102. ksadk/conversations/runtime_compaction.py +333 -0
  103. ksadk/conversations/runtime_constants.py +27 -0
  104. ksadk/conversations/runtime_governance.py +185 -0
  105. ksadk/conversations/runtime_input.py +652 -0
  106. ksadk/conversations/runtime_invocation.py +395 -0
  107. ksadk/conversations/runtime_metadata.py +475 -0
  108. ksadk/conversations/runtime_observability.py +355 -0
  109. ksadk/conversations/runtime_payloads.py +272 -0
  110. ksadk/conversations/runtime_persistence.py +440 -0
  111. ksadk/conversations/runtime_preparation.py +418 -0
  112. ksadk/conversations/runtime_resume.py +816 -0
  113. ksadk/conversations/runtime_stream_events.py +902 -0
  114. ksadk/conversations/runtime_streaming.py +547 -0
  115. ksadk/conversations/semantic_summary.py +17 -5
  116. ksadk/conversations/session_title.py +6 -3
  117. ksadk/deployment/__init__.py +15 -16
  118. ksadk/deployment/agent_access.py +12 -23
  119. ksadk/deployment/base.py +52 -39
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +326 -209
  122. ksadk/deployment/registry.py +38 -32
  123. ksadk/deployment/state.py +11 -11
  124. ksadk/deployment/ui_config.py +1 -1
  125. ksadk/detection/__init__.py +1 -1
  126. ksadk/detection/detector.py +146 -83
  127. ksadk/detection/mcp_detector.py +9 -6
  128. ksadk/events/__init__.py +25 -0
  129. ksadk/events/parser.py +191 -0
  130. ksadk/events/replay.py +37 -0
  131. ksadk/events/runtime_event.py +264 -0
  132. ksadk/events/store.py +317 -0
  133. ksadk/harness/__init__.py +34 -0
  134. ksadk/harness/app.py +278 -0
  135. ksadk/harness/config.py +181 -0
  136. ksadk/harness/reasoner.py +105 -0
  137. ksadk/harness/runner.py +211 -0
  138. ksadk/harness/sandbox.py +141 -0
  139. ksadk/harness/tools.py +222 -0
  140. ksadk/hermes_terminal.py +23 -12
  141. ksadk/identity/resolver.py +15 -9
  142. ksadk/ids.py +32 -0
  143. ksadk/knowledge_base/adk_tool.py +1 -1
  144. ksadk/knowledge_base/client.py +23 -29
  145. ksadk/managed_runtime.py +188 -0
  146. ksadk/markdown.py +5 -2
  147. ksadk/mcp_runtime/__init__.py +9 -6
  148. ksadk/memory/__init__.py +1 -1
  149. ksadk/memory/adk/__init__.py +2 -0
  150. ksadk/memory/adk/backends/base_ltm_backend.py +2 -6
  151. ksadk/memory/adk/backends/http_ltm_backend.py +13 -21
  152. ksadk/memory/adk/backends/inmemory_ltm_backend.py +7 -13
  153. ksadk/memory/adk/backends/sdk_ltm_backend.py +26 -33
  154. ksadk/memory/adk/long_term_memory.py +31 -35
  155. ksadk/memory/adk/resilient_session_service.py +267 -0
  156. ksadk/memory/adk/short_term_memory.py +33 -27
  157. ksadk/memory/adk_tool.py +3 -2
  158. ksadk/memory/backends/base.py +19 -19
  159. ksadk/memory/backends/memory.py +24 -25
  160. ksadk/memory/backends/redis.py +31 -30
  161. ksadk/memory/langchain_tool.py +2 -1
  162. ksadk/memory/manager.py +52 -47
  163. ksadk/memory/service.py +10 -8
  164. ksadk/model_policy.py +15 -7
  165. ksadk/model_proxy/__init__.py +51 -0
  166. ksadk/model_proxy/bootstrap.py +80 -0
  167. ksadk/model_proxy/cache.py +165 -0
  168. ksadk/model_proxy/config.py +45 -0
  169. ksadk/model_proxy/demo_webui.py +115 -0
  170. ksadk/model_proxy/detect.py +145 -0
  171. ksadk/model_proxy/gate.py +68 -0
  172. ksadk/model_proxy/namespace.py +173 -0
  173. ksadk/model_proxy/protocol_proxy.py +22 -0
  174. ksadk/model_proxy/server.py +300 -0
  175. ksadk/model_proxy/transform.py +691 -0
  176. ksadk/openclaw_gateway.py +29 -14
  177. ksadk/runners/adk_runner.py +1248 -164
  178. ksadk/runners/base_runner.py +46 -29
  179. ksadk/runners/codex_runner.py +132 -0
  180. ksadk/runners/deepagents_runner.py +0 -1
  181. ksadk/runners/factory.py +17 -5
  182. ksadk/runners/langchain_runner.py +29 -530
  183. ksadk/runners/langgraph_runner.py +490 -95
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +264 -38
  186. ksadk/runners/usage_accumulator.py +1 -0
  187. ksadk/runners/utils/langfuse.py +3 -3
  188. ksadk/runners/utils/loader.py +7 -7
  189. ksadk/runtime/__init__.py +41 -0
  190. ksadk/runtime/adapter.py +345 -0
  191. ksadk/runtime/framework_adapters.py +124 -0
  192. ksadk/runtime/preprocessing.py +118 -0
  193. ksadk/runtime/runner_adapter.py +873 -0
  194. ksadk/runtime_context.py +18 -3
  195. ksadk/runtime_state.py +0 -1
  196. ksadk/sandbox/__init__.py +4 -1
  197. ksadk/sandbox/backends/__init__.py +4 -1
  198. ksadk/sandbox/backends/e2b.py +13 -5
  199. ksadk/sandbox/backends/local_process.py +11 -3
  200. ksadk/sandbox/base.py +10 -15
  201. ksadk/sandbox/factory.py +14 -4
  202. ksadk/sandbox/registry.py +57 -6
  203. ksadk/server/api_models.py +15 -5
  204. ksadk/server/app.py +455 -3743
  205. ksadk/server/composition.py +21 -0
  206. ksadk/server/factory.py +624 -0
  207. ksadk/server/routes/__init__.py +1 -0
  208. ksadk/server/routes/checkpoint_resolution.py +116 -0
  209. ksadk/server/routes/common.py +713 -0
  210. ksadk/server/routes/control.py +327 -0
  211. ksadk/server/routes/dependencies.py +98 -0
  212. ksadk/server/routes/misc.py +448 -0
  213. ksadk/server/routes/models.py +443 -0
  214. ksadk/server/routes/openai_compat.py +207 -0
  215. ksadk/server/routes/projection.py +795 -0
  216. ksadk/server/routes/routers.py +52 -0
  217. ksadk/server/routes/run.py +726 -0
  218. ksadk/server/routes/sessions.py +649 -0
  219. ksadk/server/routes/streaming.py +223 -0
  220. ksadk/server/routes/workspace.py +381 -0
  221. ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-Cjnqt-8W.js} +1 -1
  222. ksadk/server/static/assets/CodeBlock-BqyqZfuB.js +9 -0
  223. ksadk/server/static/assets/MathMessageMarkdown-BpH7jiFM.js +8 -0
  224. ksadk/server/static/assets/MermaidBlock-DWuxETaJ.js +303 -0
  225. ksadk/server/static/assets/NativeTerminalPanel-MDTopWMO.js +1 -0
  226. ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-CFKwSk_B.js} +1 -1
  227. ksadk/server/static/assets/architecture-7EHR7CIX-CynqOWjZ.js +1 -0
  228. ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-D98vPd-E.js} +1 -1
  239. ksadk/server/static/assets/chunk-QZHKN3VN-YQIEXeF6.js +1 -0
  240. ksadk/server/static/assets/classDiagram-4FO5ZUOK-CDAUuoXK.js +1 -0
  241. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-CDAUuoXK.js +1 -0
  242. ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-BMKM66B4.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-D99wf6_X.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-DLMjL01k.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-B67uF2uK.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-S4klkK5W.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-CnfRcY2M.js} +1 -1
  272. ksadk/server/static/assets/eventmodeling-FCH6USID-ChYO-ijM.js +1 -0
  273. ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-DpCoB2g9.js} +1 -1
  275. ksadk/server/static/assets/gitGraph-WXDBUCRP-BN_0Fk-j.js +1 -0
  276. ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-9IKVFBVT.js} +1 -1
  277. ksadk/server/static/assets/index-QSwCHuB8.js +177 -0
  278. ksadk/server/static/assets/index-jOPmTOZH.css +2 -0
  279. ksadk/server/static/assets/info-J43DQDTF-Cp6o3vId.js +1 -0
  280. ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-CSAQxEAe.js} +1 -1
  288. ksadk/server/static/assets/packet-YPE3B663-wQzct3cT.js +1 -0
  289. ksadk/server/static/assets/pie-LRSECV5Y-BgTQiv-9.js +1 -0
  290. ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-CxIR4JXI.js} +1 -1
  292. ksadk/server/static/assets/radar-GUYGQ44K-BIq5fXsn.js +1 -0
  293. ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-DGpd9yFG.js} +1 -1
  297. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-B3o579IQ.js +1 -0
  298. ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-jqoYs3jQ.js} +1 -1
  299. ksadk/server/static/assets/treeView-BLDUP644-Dqi-E0EM.js +1 -0
  300. ksadk/server/static/assets/treemap-LRROVOQU-ClQP9CJ9.js +1 -0
  301. ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-l3Z7-wbP.js} +1 -1
  302. ksadk/server/static/assets/wardley-L42UT6IY-8XLvnNIs.js +1 -0
  303. ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-SkiH8GsK.js} +1 -1
  305. ksadk/server/static/index.html +2 -2
  306. ksadk/server/terminal_sessions.py +111 -58
  307. ksadk/sessions/__init__.py +86 -40
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +28 -5
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +216 -35
  313. ksadk/sessions/resilience.py +52 -0
  314. ksadk/sessions/resilient.py +388 -0
  315. ksadk/skills/models.py +17 -5
  316. ksadk/skills/package_store.py +5 -2
  317. ksadk/skills/runtime/agent.py +20 -5
  318. ksadk/skills/runtime/artifacts.py +1 -5
  319. ksadk/skills/runtime/backends/e2b.py +10 -4
  320. ksadk/skills/runtime/backends/local.py +15 -3
  321. ksadk/skills/runtime/base.py +1 -2
  322. ksadk/skills/runtime/executor.py +10 -3
  323. ksadk/skills/runtime/factory.py +4 -2
  324. ksadk/skills/runtime/loader.py +1 -2
  325. ksadk/skills/runtime/registry.py +15 -10
  326. ksadk/skills/runtime/request.py +3 -1
  327. ksadk/skills/service_client.py +38 -17
  328. ksadk/skills/tool_defs.py +15 -10
  329. ksadk/terminal_exec_policy.py +3 -4
  330. ksadk/tools/gateway.py +92 -15
  331. ksadk/tools/result_budget.py +12 -4
  332. ksadk/toolsets/__init__.py +136 -36
  333. ksadk/toolsets/platform.py +21 -5
  334. ksadk/toolsets/sandbox.py +82 -16
  335. ksadk/toolsets/skills.py +200 -39
  336. ksadk/toolsets/web.py +123 -26
  337. ksadk/toolsets/workspace.py +264 -114
  338. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  339. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  340. ksadk/tracing/setup.py +132 -53
  341. ksadk/tracing/span_utils.py +5 -2
  342. ksadk/tui/__init__.py +22 -7
  343. ksadk/tui/loop.py +1925 -0
  344. ksadk/tui/stream_render.py +69 -0
  345. ksadk/ui_config.py +5 -3
  346. ksadk/version.py +1 -1
  347. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  348. agentengine_sdk_python-0.6.9.dist-info/RECORD +0 -487
  349. ksadk/server/static/assets/CodeBlock-BbI4HPtJ.js +0 -9
  350. ksadk/server/static/assets/MathMessageMarkdown-DMLQYoLp.js +0 -8
  351. ksadk/server/static/assets/MermaidBlock-DCR8WZ17.js +0 -303
  352. ksadk/server/static/assets/NativeTerminalPanel-CKUvWhUz.js +0 -1
  353. ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
  354. ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
  355. ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
  356. ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
  357. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
  358. ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
  359. ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
  360. ksadk/server/static/assets/index-BH7-pC0B.js +0 -165
  361. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  362. ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
  363. ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
  364. ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
  365. ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
  366. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
  367. ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
  368. ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
  369. ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
  370. ksadk/tui/app.py +0 -400
  371. ksadk/tui/clipboard.py +0 -100
  372. ksadk/tui/widgets/__init__.py +0 -22
  373. ksadk/tui/widgets/assistant.py +0 -89
  374. ksadk/tui/widgets/base.py +0 -54
  375. ksadk/tui/widgets/chat_input.py +0 -246
  376. ksadk/tui/widgets/history.py +0 -156
  377. ksadk/tui/widgets/system.py +0 -87
  378. ksadk/tui/widgets/thinking.py +0 -110
  379. ksadk/tui/widgets/tool_call.py +0 -161
  380. ksadk/tui/widgets/user.py +0 -37
  381. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  382. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  383. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  384. {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,173 @@
1
+ """codex namespace 工具拍平 + 还原(responses↔chat 路径)。
2
+
3
+ codex 0.142+ 用 namespace 工具声明插件/MCP 工具:
4
+
5
+ {"type":"namespace","name":"mcp__x__","tools":[{"type":"function","name":"foo",...}]}
6
+
7
+ 并在 input 历史里发 namespace-qualified function_call:
8
+
9
+ {"type":"function_call","name":"foo","namespace":"mcp__x__",...}
10
+
11
+ chat completions 只认顶层 ``function`` 工具(chat 嵌套)和 ``function.name``,
12
+ 不认 ``namespace`` 字段。本模块把 namespace 工具**拍平**成顶层 function,
13
+ 名字用确定性的 ``<namespace>__<child>``(超长 sha256 截断,与 cc-switch 一致),
14
+ 响应侧再把 flat 名字**还原**回 ``{name, namespace}``,让 codex client 能匹配
15
+ 自己的 namespace 工具注册表。
16
+
17
+ 请求侧拍平 + 响应侧还原都从**同一份 request tools** 经
18
+ :func:`flatten_namespace_tool_name` 派生名字映射,前后一致,无需跨请求状态。
19
+
20
+ 参照 cc-switch ``transform_codex_responses_namespace.rs``(用 Python 重写)。
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import hashlib
26
+ from typing import Any
27
+
28
+ CHAT_TOOL_NAME_MAX_LEN = 64
29
+
30
+
31
+ def flatten_namespace_tool_name(namespace: str, name: str) -> str:
32
+ """``<namespace>__<name>``;超长则尾部接 sha256 截断,保持在 64 字符内。
33
+
34
+ 截断按字符边界,保证返回值是合法 UTF-8(不会从多字节字符中间切断)。
35
+ """
36
+ full = f"{namespace}__{name}"
37
+ if len(full) <= CHAT_TOOL_NAME_MAX_LEN:
38
+ return full
39
+ suffix = "__" + hashlib.sha256(full.encode("utf-8")).hexdigest()[:8]
40
+ prefix_len = CHAT_TOOL_NAME_MAX_LEN - len(suffix)
41
+ prefix = ""
42
+ for ch in full:
43
+ if len(prefix.encode("utf-8")) + len(ch.encode("utf-8")) > prefix_len:
44
+ break
45
+ prefix += ch
46
+ return prefix + suffix
47
+
48
+
49
+ def _namespace_children(tool: dict) -> list[dict]:
50
+ for key in ("tools", "children"):
51
+ kids = tool.get(key)
52
+ if isinstance(kids, list):
53
+ return kids
54
+ return []
55
+
56
+
57
+ def build_restore_map(tools: list[Any] | None) -> dict[str, dict[str, str]]:
58
+ """从 request 的 namespace 工具声明建 ``flat -> {namespace, name}`` 映射。
59
+
60
+ 两个不同 child 拍平后撞名时,后者覆盖前者(与 cc-switch ``or_insert`` 不同,
61
+ cc-switch 用 first-wins 并对撞名报错;这里取 first-wins + 调用方撞名检测)。
62
+ """
63
+ restore: dict[str, dict[str, str]] = {}
64
+ if not tools:
65
+ return restore
66
+ for tool in tools:
67
+ if not isinstance(tool, dict) or tool.get("type") != "namespace":
68
+ continue
69
+ namespace = (tool.get("name") or "").strip()
70
+ if not namespace:
71
+ continue
72
+ for child in _namespace_children(tool):
73
+ if not isinstance(child, dict) or child.get("type") != "function":
74
+ continue
75
+ name = (child.get("name") or "").strip()
76
+ if not name:
77
+ continue
78
+ flat = flatten_namespace_tool_name(namespace, name)
79
+ restore.setdefault(flat, {"namespace": namespace, "name": name})
80
+ return restore
81
+
82
+
83
+ def _rewrite_qualified_calls(value: Any, owners: dict[str, dict[str, str]]) -> bool:
84
+ """递归把 input 里的 namespace-qualified function_call 重写成 flat name。
85
+
86
+ ``{type:function_call, name:child, namespace:ns}`` -> ``{type:function_call, name:flat}``
87
+ (去掉 namespace 字段);只在 (ns, name) 与 owners 条目匹配时重写。
88
+ """
89
+ changed = False
90
+ if isinstance(value, list):
91
+ for item in value:
92
+ changed |= _rewrite_qualified_calls(item, owners)
93
+ elif isinstance(value, dict):
94
+ if value.get("type") == "function_call":
95
+ namespace = (value.get("namespace") or "").strip()
96
+ name = (value.get("name") or "").strip()
97
+ if namespace and name:
98
+ flat = flatten_namespace_tool_name(namespace, name)
99
+ entry = owners.get(flat)
100
+ if entry and entry["namespace"] == namespace and entry["name"] == name:
101
+ value["name"] = flat
102
+ value.pop("namespace", None)
103
+ changed = True
104
+ for child in value.values():
105
+ changed |= _rewrite_qualified_calls(child, owners)
106
+ return changed
107
+
108
+
109
+ def flatten_request_namespaces(body: dict) -> dict[str, dict[str, str]]:
110
+ """原地拍平 body 的 namespace 工具,返回 restore_map(供响应侧还原)。
111
+
112
+ - tools:namespace children 提升为顶层 responses function 工具(flat name),
113
+ 原 namespace 工具移除;检测撞名(两个 child 拍平后同名)则 raise。
114
+ - input:namespace-qualified function_call 重写为 flat name。
115
+ - tool_choice:namespace-typed 丢弃(chat 不认)。
116
+
117
+ 返回 ``flat -> {namespace, name}`` 映射;无 namespace 工具时返回空 dict,
118
+ 调用方可据此跳过响应侧还原。
119
+ """
120
+ tools = body.get("tools")
121
+ if not isinstance(tools, list):
122
+ return {}
123
+ restore = build_restore_map(tools)
124
+ flat_tools: list[dict] = []
125
+ seen_flat: set[str] = set()
126
+ for tool in tools:
127
+ if not isinstance(tool, dict):
128
+ continue
129
+ if tool.get("type") == "namespace":
130
+ namespace = (tool.get("name") or "").strip()
131
+ for child in _namespace_children(tool):
132
+ if not isinstance(child, dict) or child.get("type") != "function":
133
+ continue
134
+ name = (child.get("name") or "").strip()
135
+ if not name or not namespace:
136
+ continue
137
+ flat = flatten_namespace_tool_name(namespace, name)
138
+ if flat in seen_flat:
139
+ raise ValueError(
140
+ f"namespace 拍平撞名:{flat} 来自不同 child,上游无法消歧"
141
+ )
142
+ seen_flat.add(flat)
143
+ flat_tools.append({
144
+ "type": "function",
145
+ "name": flat,
146
+ "description": child.get("description", ""),
147
+ "parameters": child.get("parameters", {"type": "object", "properties": {}}),
148
+ **({"strict": child["strict"]} if "strict" in child else {}),
149
+ })
150
+ else:
151
+ flat_tools.append(tool)
152
+ body["tools"] = flat_tools
153
+ if restore and isinstance(body.get("input"), list):
154
+ _rewrite_qualified_calls(body["input"], restore)
155
+ tc = body.get("tool_choice")
156
+ if isinstance(tc, dict) and tc.get("type") == "namespace":
157
+ body.pop("tool_choice", None)
158
+ return restore
159
+
160
+
161
+ def restore_function_call(item: dict, restore_map: dict[str, dict[str, str]]) -> dict:
162
+ """把响应里 function_call 的 flat name 还原成 ``{name, namespace}``(原地改)。
163
+
164
+ 无映射或 name 不在映射中时原样返回(普通 function 工具的 call 不受影响)。
165
+ """
166
+ if not restore_map or item.get("type") != "function_call":
167
+ return item
168
+ flat = item.get("name")
169
+ entry = restore_map.get(flat) if isinstance(flat, str) else None
170
+ if entry:
171
+ item["name"] = entry["name"]
172
+ item["namespace"] = entry["namespace"]
173
+ return item
@@ -0,0 +1,22 @@
1
+ """可独立运行的转换 proxy 入口:读 env → ProxyConfig → uvicorn。
2
+
3
+ 用法:
4
+ KSPMAS_API_KEY=xxx python -m ksadk.model_proxy.protocol_proxy # 默认 127.0.0.1:8899
5
+ """
6
+
7
+ import os
8
+
9
+ import uvicorn
10
+
11
+ from .config import ProxyConfig
12
+ from .server import create_app
13
+
14
+
15
+ def main():
16
+ config = ProxyConfig.from_env()
17
+ port = int(os.environ.get("PORT", "8899"))
18
+ uvicorn.run(create_app(config), host="127.0.0.1", port=port, log_level="warning")
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
@@ -0,0 +1,300 @@
1
+ """app factory + 进程内转换 proxy 生命周期管理。
2
+
3
+ 把 HTTP 层与配置/转换解耦:create_app(config) 返回 FastAPI app;
4
+ ProxyServer 在后台线程懒起一个 localhost uvicorn,幂等 + 加锁 + 干净回收(随 runner 退出)。
5
+ """
6
+
7
+ import asyncio
8
+ import json
9
+ import logging
10
+ import socket
11
+ import threading
12
+ import time
13
+ import uuid
14
+
15
+ import httpx
16
+ import uvicorn
17
+ from fastapi import FastAPI, Request
18
+ from fastapi.responses import JSONResponse, StreamingResponse
19
+
20
+ from .config import _LOOPBACK_HOSTS, ProxyConfig
21
+ from .transform import Streamer, UnsupportedToolsError, chat_to_response, responses_to_chat
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def _upstream_error(status_code: int) -> JSONResponse:
27
+ """Return a stable public error without relaying an upstream traceback."""
28
+ return JSONResponse(
29
+ status_code=status_code,
30
+ content={
31
+ "error": {
32
+ "type": "upstream_error",
33
+ "message": "The model upstream rejected the request.",
34
+ }
35
+ },
36
+ )
37
+
38
+
39
+ def create_app(config: ProxyConfig) -> FastAPI:
40
+ base = config.upstream_base.rstrip("/")
41
+ headers = {"Authorization": f"Bearer {config.api_key}", "Content-Type": "application/json"}
42
+ app = FastAPI()
43
+ # 应用层取消信号:stop() 时置位,让活动 SSE 的 _stream_gen 主动 break(比单靠 force_exit 可靠)
44
+ cancel = threading.Event()
45
+ app.state.cancel_event = cancel
46
+
47
+ @app.get("/healthz")
48
+ async def healthz():
49
+ return {"ok": True, "upstream": base}
50
+
51
+ @app.post("/v1/chat/completions")
52
+ async def chat_passthrough(req: Request):
53
+ """chat 直通端点(env 框架发 chat 经代理原样转发上游,不转换)。
54
+
55
+ 让代理成为统一模型出口:env 框架(ADK/langchain/deepagents 发 chat)经此直通,
56
+ codex(发 responses)经 /v1/responses 转换。统一出口为后续能力探测/路由打底。
57
+ chat 直通字节级转发 + 流式透传,前缀缓存不受影响。
58
+ """
59
+ if (
60
+ config.local_token
61
+ and req.headers.get("authorization", "") != f"Bearer {config.local_token}"
62
+ ):
63
+ return JSONResponse(
64
+ status_code=401, content={"error": {"type": "authentication_error"}}
65
+ )
66
+ body = await req.json()
67
+ is_stream = bool(body.get("stream"))
68
+ up_headers = {
69
+ "Authorization": f"Bearer {config.api_key}",
70
+ "Content-Type": "application/json",
71
+ }
72
+ if is_stream:
73
+ return StreamingResponse(
74
+ _chat_passthrough_stream(config, base, up_headers, body),
75
+ media_type="text/event-stream",
76
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
77
+ )
78
+ async with httpx.AsyncClient(timeout=config.timeout) as c:
79
+ r = await c.post(f"{base}/chat/completions", json=body, headers=up_headers)
80
+ if r.is_error:
81
+ logger.warning("chat upstream rejected request: status=%s", r.status_code)
82
+ return _upstream_error(r.status_code)
83
+ return JSONResponse(status_code=r.status_code, content=r.json() if r.content else None)
84
+
85
+ @app.post("/v1/responses")
86
+ async def responses(req: Request):
87
+ # 本地鉴权:codex -> 本地 proxy 用 KSADK_PROXY_TOKEN;上游凭证独立保存于 config.api_key
88
+ if (
89
+ config.local_token
90
+ and req.headers.get("authorization", "") != f"Bearer {config.local_token}"
91
+ ):
92
+ return JSONResponse(
93
+ status_code=401,
94
+ content={
95
+ "error": {"type": "authentication_error", "message": "invalid proxy token"}
96
+ },
97
+ )
98
+ body = await req.json()
99
+ try:
100
+ chat_req, restore_map = responses_to_chat(body)
101
+ except UnsupportedToolsError as e:
102
+ logger.info("responses request uses unsupported tools: %s", e)
103
+ return JSONResponse(
104
+ status_code=400,
105
+ content={
106
+ "error": {
107
+ "type": "unsupported_tools",
108
+ "message": "The request uses tools unsupported by the model upstream.",
109
+ }
110
+ },
111
+ )
112
+ rid = "resp_" + uuid.uuid4().hex[:24]
113
+ model = body.get("model")
114
+ if body.get("stream"):
115
+ chat_req["stream"] = True
116
+ chat_req["stream_options"] = {"include_usage": True}
117
+ return StreamingResponse(
118
+ _stream_gen(config, base, headers, chat_req, rid, model, cancel, restore_map),
119
+ media_type="text/event-stream",
120
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
121
+ )
122
+ async with httpx.AsyncClient(timeout=config.timeout) as c:
123
+ r = await c.post(f"{base}/chat/completions", json=chat_req, headers=headers)
124
+ if r.status_code != 200:
125
+ logger.warning("responses upstream rejected request: status=%s", r.status_code)
126
+ return _upstream_error(r.status_code)
127
+ return JSONResponse(chat_to_response(r.json(), rid, restore_map))
128
+
129
+ return app
130
+
131
+
132
+ async def _chat_passthrough_stream(config: ProxyConfig, base: str, headers: dict, body: dict):
133
+ """chat SSE 字节级透传(不转换),让前缀缓存/流式格式与直连一致。"""
134
+ try:
135
+ async with httpx.AsyncClient(timeout=config.timeout) as c:
136
+ async with c.stream(
137
+ "POST", f"{base}/chat/completions", json=body, headers=headers
138
+ ) as r:
139
+ async for line in r.aiter_lines():
140
+ yield line + "\n"
141
+ except Exception as exc: # noqa: BLE001
142
+ logger.warning("chat upstream stream failed", exc_info=exc)
143
+ yield 'data: {"error":"The model upstream stream failed."}\n\n'
144
+
145
+
146
+ async def _stream_gen(
147
+ config: ProxyConfig,
148
+ base: str,
149
+ headers: dict,
150
+ chat_req: dict,
151
+ rid: str,
152
+ model,
153
+ cancel: threading.Event,
154
+ restore_map: dict | None = None,
155
+ ):
156
+ s = Streamer(rid, model, restore_map)
157
+ for e in s.start():
158
+ yield e
159
+ try:
160
+ # 超时必须有界(config.timeout):无限超时的活动 SSE 会让 stop() 后线程仍存活
161
+ async with httpx.AsyncClient(timeout=config.timeout) as c:
162
+ async with c.stream(
163
+ "POST", f"{base}/chat/completions", json=chat_req, headers=headers
164
+ ) as r:
165
+ if r.status_code != 200:
166
+ logger.warning("responses upstream stream rejected: status=%s", r.status_code)
167
+ yield Streamer.ev(
168
+ "response.failed",
169
+ {
170
+ "type": "response.failed",
171
+ "response": {
172
+ **s._resp("failed"),
173
+ "error": {"message": "The model upstream rejected the request."},
174
+ },
175
+ },
176
+ )
177
+ return
178
+ async for line in r.aiter_lines():
179
+ if cancel.is_set():
180
+ return # stop() 置位:主动中断活动 SSE,让线程能及时回收
181
+ line = line.strip()
182
+ if not line.startswith("data:"):
183
+ continue
184
+ data = line[5:].strip()
185
+ if data == "[DONE]":
186
+ break
187
+ try:
188
+ chunk = json.loads(data)
189
+ except ValueError:
190
+ continue
191
+ for e in s.handle(chunk):
192
+ yield e
193
+ except Exception as exc: # noqa: BLE001
194
+ logger.warning("responses upstream stream failed", exc_info=exc)
195
+ yield Streamer.ev(
196
+ "response.failed",
197
+ {
198
+ "type": "response.failed",
199
+ "response": {
200
+ **s._resp("failed"),
201
+ "error": {"message": "The model upstream stream failed."},
202
+ },
203
+ },
204
+ )
205
+ return
206
+ for e in s.finalize():
207
+ yield e
208
+
209
+
210
+ class ProxyServer:
211
+ """进程内 localhost 转换 proxy 的生命周期管理。
212
+
213
+ - start 幂等(已启动直接返回)+ 加锁,避免重复 start 覆盖 server/thread 造成泄漏;
214
+ - 端口在 start 时 bind 并**一直持有 socket**(传给 uvicorn),避免"先释放再重绑"的 TOCTOU;
215
+ - 启动超时与 stop 都会干净回收线程与 socket。
216
+ """
217
+
218
+ def __init__(self, config: ProxyConfig, host: str = "127.0.0.1", port: int = 0):
219
+ if host not in _LOOPBACK_HOSTS and not config.local_token:
220
+ raise ValueError(
221
+ f"非回环监听(host={host})必须设置 local_token,否则是无鉴权代持上游凭证的开放代理"
222
+ )
223
+ self.config = config
224
+ self.host = host
225
+ self._requested_port = port
226
+ self.port = port
227
+ self._lock = threading.Lock()
228
+ self._sock: socket.socket | None = None
229
+ self._server: uvicorn.Server | None = None
230
+ self._thread: threading.Thread | None = None
231
+ self._app: FastAPI | None = None
232
+
233
+ @property
234
+ def base_url(self) -> str:
235
+ return f"http://{self.host}:{self.port}/v1"
236
+
237
+ def start(self, wait_ready: float = 10.0) -> str:
238
+ with self._lock:
239
+ if self._server is not None:
240
+ return self.base_url # 幂等
241
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
242
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
243
+ try:
244
+ sock.bind((self.host, self._requested_port))
245
+ except OSError:
246
+ sock.close()
247
+ raise
248
+ self.port = int(sock.getsockname()[1])
249
+ app = create_app(self.config)
250
+ self._app = app
251
+ # timeout_graceful_shutdown 设上限:stop 时挂在 yield 的 SSE generator 不再消费,
252
+ # 若无限等待连接完成(None 默认)会让 shutdown 卡死、线程回收不掉
253
+ cfg = uvicorn.Config(app, log_level="warning", timeout_graceful_shutdown=1)
254
+ self._sock = sock
255
+ server = uvicorn.Server(cfg)
256
+ self._server = server
257
+ # serve(sockets=[sock]) 跨平台:uvicorn 直接复用已绑定 socket,
258
+ # 不走 Config(fd=...) 那条仅 Unix 可用的 socket.fromfd 路径
259
+ self._thread = threading.Thread(
260
+ target=lambda: asyncio.run(server.serve(sockets=[sock])), daemon=True
261
+ )
262
+ self._thread.start()
263
+ deadline = time.time() + wait_ready
264
+ while time.time() < deadline:
265
+ try:
266
+ if (
267
+ httpx.get(f"http://{self.host}:{self.port}/healthz", timeout=1).status_code
268
+ == 200
269
+ ):
270
+ return self.base_url
271
+ except Exception: # noqa: BLE001
272
+ time.sleep(0.05)
273
+ self._teardown() # 启动超时也要清理线程与 socket
274
+ raise RuntimeError(f"ProxyServer 未在 {wait_ready}s 内就绪")
275
+
276
+ def stop(self):
277
+ with self._lock:
278
+ self._teardown()
279
+
280
+ def _teardown(self):
281
+ if self._app is not None:
282
+ # 应用层取消信号:让活动 SSE 的 _stream_gen 主动 break
283
+ self._app.state.cancel_event.set()
284
+ if self._server is not None:
285
+ # should_exit 触发主循环进入 shutdown;force_exit 让 shutdown 跳过等待
286
+ # connections/tasks 完成(活动 SSE 下不再无限等)。两者缺一:只 should_exit
287
+ # 会被活动连接卡死,只 force_exit 主循环根本不进 shutdown。
288
+ self._server.should_exit = True
289
+ self._server.force_exit = True
290
+ if self._thread is not None:
291
+ self._thread.join(timeout=5)
292
+ if self._sock is not None:
293
+ try:
294
+ self._sock.close()
295
+ except OSError:
296
+ pass
297
+ self._server = None
298
+ self._thread = None
299
+ self._sock = None
300
+ self._app = None