agentengine-sdk-python 0.7.0__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 (371) hide show
  1. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/METADATA +36 -20
  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 +233 -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 +500 -720
  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 +168 -89
  54. ksadk/cli/cmd_invoke.py +184 -122
  55. ksadk/cli/cmd_launch.py +64 -28
  56. ksadk/cli/cmd_mcp.py +98 -64
  57. ksadk/cli/cmd_model.py +14 -5
  58. ksadk/cli/cmd_openclaw.py +483 -227
  59. ksadk/cli/cmd_replay.py +71 -0
  60. ksadk/cli/cmd_run.py +33 -10
  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 +34 -14
  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 +500 -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 +438 -51
  97. ksadk/conversations/model_context.py +12 -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 -5255
  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 +51 -38
  120. ksadk/deployment/manager.py +63 -65
  121. ksadk/deployment/providers/serverless.py +309 -202
  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 +4 -5
  156. ksadk/memory/adk/short_term_memory.py +12 -21
  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 +345 -120
  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 -742
  183. ksadk/runners/langgraph_runner.py +352 -90
  184. ksadk/runners/patch_langchain.py +16 -16
  185. ksadk/runners/remote_runner.py +134 -33
  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 -3885
  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-D-SSn2ee.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-BA13sHc9.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-DzJzgVoL.js → architectureDiagram-3BPJPVTR-Dej8jX8c.js} +1 -1
  229. ksadk/server/static/assets/{blockDiagram-GPEHLZMM-UTdVcZhn.js → blockDiagram-GPEHLZMM-1F64jqvy.js} +1 -1
  230. ksadk/server/static/assets/{c4Diagram-AAUBKEIU-Dix0GD4m.js → c4Diagram-AAUBKEIU-9q7AUAKb.js} +1 -1
  231. ksadk/server/static/assets/channel-iUGv2N24.js +1 -0
  232. ksadk/server/static/assets/{chunk-2J33WTMH-C2JxuZ42.js → chunk-2J33WTMH-6qgwJm7Q.js} +1 -1
  233. ksadk/server/static/assets/{chunk-4BX2VUAB-Be57QPgD.js → chunk-4BX2VUAB-CsWIQRhc.js} +1 -1
  234. ksadk/server/static/assets/{chunk-55IACEB6-WjSuy4nv.js → chunk-55IACEB6-BK8paA20.js} +1 -1
  235. ksadk/server/static/assets/{chunk-727SXJPM-BOovP-b5.js → chunk-727SXJPM-B-JEhy7h.js} +1 -1
  236. ksadk/server/static/assets/{chunk-AQP2D5EJ-DOmZNmWj.js → chunk-AQP2D5EJ-DYBvMHAa.js} +1 -1
  237. ksadk/server/static/assets/{chunk-FMBD7UC4-DzwlodkU.js → chunk-FMBD7UC4-BGhvzDYt.js} +1 -1
  238. ksadk/server/static/assets/{chunk-ND2GUHAM-DMqAikEU.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-Zhy2GBnP.js → cose-bilkent-S5V4N54A-CX3yotHq.js} +1 -1
  243. ksadk/server/static/assets/{dagre-BM42HDAG-CbKrtIUz.js → dagre-BM42HDAG-C7TguAWo.js} +1 -1
  244. ksadk/server/static/assets/{diagram-2AECGRRQ-BRApgQRK.js → diagram-2AECGRRQ-Dd17_kws.js} +1 -1
  245. ksadk/server/static/assets/{diagram-5GNKFQAL-D0xbnDiK.js → diagram-5GNKFQAL-DVIClIil.js} +1 -1
  246. ksadk/server/static/assets/{diagram-KO2AKTUF-CVugVI9L.js → diagram-KO2AKTUF-Df_3gYQa.js} +1 -1
  247. ksadk/server/static/assets/{diagram-LMA3HP47-DozUGgee.js → diagram-LMA3HP47-BhLk6ZtA.js} +1 -1
  248. ksadk/server/static/assets/{diagram-OG6HWLK6-ZOYsKlru.js → diagram-OG6HWLK6-D4GNGvuu.js} +1 -1
  249. ksadk/server/static/assets/{dist-DmFaq_Iy.js → dist-6CVKuTAe.js} +1 -1
  250. ksadk/server/static/assets/{dist-8EEyGgDx.js → dist-BUHRxvDG.js} +1 -1
  251. ksadk/server/static/assets/{dist-B0NP_Z0z.js → dist-BZ9n9AjO.js} +1 -1
  252. ksadk/server/static/assets/{dist-DMezvBGJ.js → dist-BibcG1V9.js} +1 -1
  253. ksadk/server/static/assets/{dist-C3HIIaN5.js → dist-Bs-jQcRX.js} +1 -1
  254. ksadk/server/static/assets/{dist-BrCcQ47T.js → dist-C2xjE7cO.js} +1 -1
  255. ksadk/server/static/assets/{dist-Dp0k7bWI.js → dist-C7-3jInC.js} +1 -1
  256. ksadk/server/static/assets/{dist-D-xOD048.js → dist-C99THtIA.js} +1 -1
  257. ksadk/server/static/assets/{dist-CkEKJFtk.js → dist-CGcp2ex8.js} +1 -1
  258. ksadk/server/static/assets/{dist-DyOkZmze.js → dist-CVQRZjPu.js} +1 -1
  259. ksadk/server/static/assets/{dist-BssVmjIo.js → dist-Cf23NY4L.js} +1 -1
  260. ksadk/server/static/assets/{dist-BIJVDMuB.js → dist-CgF3fj15.js} +1 -1
  261. ksadk/server/static/assets/{dist-2hInLS_J.js → dist-Cwt7koer.js} +1 -1
  262. ksadk/server/static/assets/{dist-DyWsb78Q.js → dist-D82mtD2s.js} +1 -1
  263. ksadk/server/static/assets/{dist-D-JMdWPQ.js → dist-DF06ORQo.js} +1 -1
  264. ksadk/server/static/assets/{dist-BmlMPsCc.js → dist-DHn6nBzr.js} +1 -1
  265. ksadk/server/static/assets/{dist-gX6GOLKq.js → dist-Djgl2wVZ.js} +1 -1
  266. ksadk/server/static/assets/{dist-DpKtt4Bo.js → dist-DlzCRy0S.js} +1 -1
  267. ksadk/server/static/assets/{dist-8_SfK1IP.js → dist-DqNnNFrw.js} +1 -1
  268. ksadk/server/static/assets/{dist-D57wnGWd.js → dist-DyvYLMW9.js} +1 -1
  269. ksadk/server/static/assets/{dist-CPBtqRTW.js → dist-eoW6tZXA.js} +1 -1
  270. ksadk/server/static/assets/{dist-DL_NKc_z.js → dist-ivOya1LD.js} +1 -1
  271. ksadk/server/static/assets/{erDiagram-TEJ5UH35-Bk-9bgE5.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-Dn9kZVuZ.js → flowDiagram-I6XJVG4X-DGaJjGNJ.js} +1 -1
  274. ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BGxXlJqm.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-C7Q0X-wi.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-BO_CSqq5.js → infoDiagram-5YYISTIA-CYIIsb8y.js} +1 -1
  281. ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-eYLPLd46.js → ishikawaDiagram-YF4QCWOH-DLOHGvWw.js} +1 -1
  282. ksadk/server/static/assets/{journeyDiagram-JHISSGLW-De9geSb0.js → journeyDiagram-JHISSGLW-BIzKeejT.js} +1 -1
  283. ksadk/server/static/assets/{kanban-definition-UN3LZRKU-Cs80kQto.js → kanban-definition-UN3LZRKU-BWHoFSw2.js} +1 -1
  284. ksadk/server/static/assets/{katex-DSrCFD8n.js → katex-DATssjp1.js} +1 -1
  285. ksadk/server/static/assets/{linear-CN9sbKTF.js → linear-CvMBw9cA.js} +1 -1
  286. ksadk/server/static/assets/{mermaid-parser.core-DlgSBU8l.js → mermaid-parser.core-mGAcEwcq.js} +2 -2
  287. ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-9afz7Oug.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-DPv_VstN.js → pieDiagram-4H26LBE5-By-ra07M.js} +1 -1
  291. ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-o77wZ1XK.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-BYVw81VC.js → requirementDiagram-4Y6WPE33-DpW90iu3.js} +1 -1
  294. ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-B7nZFMKT.js → sankeyDiagram-5OEKKPKP-DpGa6EAV.js} +1 -1
  295. ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-BGRRrFw5.js → sequenceDiagram-3UESZ5HK-NTKNGGe5.js} +1 -1
  296. ksadk/server/static/assets/{stateDiagram-AJRCARHV-BUfHCoPs.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-FbCTGrf9.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-DWgwtPsJ.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-2x8G-tTM.js → wardleyDiagram-YWT4CUSO-BR54GPmr.js} +1 -1
  304. ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-Drma3AYl.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 +69 -19
  308. ksadk/sessions/base.py +31 -11
  309. ksadk/sessions/continuity.py +2 -3
  310. ksadk/sessions/in_memory.py +46 -2
  311. ksadk/sessions/local_service.py +148 -17
  312. ksadk/sessions/postgres_service.py +143 -25
  313. ksadk/sessions/resilient.py +36 -8
  314. ksadk/skills/models.py +17 -5
  315. ksadk/skills/package_store.py +5 -2
  316. ksadk/skills/runtime/agent.py +20 -5
  317. ksadk/skills/runtime/artifacts.py +1 -5
  318. ksadk/skills/runtime/backends/e2b.py +10 -4
  319. ksadk/skills/runtime/backends/local.py +15 -3
  320. ksadk/skills/runtime/base.py +1 -2
  321. ksadk/skills/runtime/executor.py +10 -3
  322. ksadk/skills/runtime/factory.py +4 -2
  323. ksadk/skills/runtime/loader.py +1 -2
  324. ksadk/skills/runtime/registry.py +15 -10
  325. ksadk/skills/runtime/request.py +3 -1
  326. ksadk/skills/service_client.py +38 -17
  327. ksadk/skills/tool_defs.py +15 -10
  328. ksadk/terminal_exec_policy.py +3 -4
  329. ksadk/tools/gateway.py +92 -15
  330. ksadk/tools/result_budget.py +12 -4
  331. ksadk/toolsets/__init__.py +134 -36
  332. ksadk/toolsets/platform.py +21 -5
  333. ksadk/toolsets/sandbox.py +82 -16
  334. ksadk/toolsets/skills.py +200 -39
  335. ksadk/toolsets/web.py +123 -26
  336. ksadk/toolsets/workspace.py +212 -101
  337. ksadk/tracing/exporters/inmemory_exporter.py +62 -51
  338. ksadk/tracing/exporters/langfuse_exporter.py +91 -76
  339. ksadk/tracing/setup.py +69 -53
  340. ksadk/tracing/span_utils.py +5 -2
  341. ksadk/tui/loop.py +78 -30
  342. ksadk/tui/stream_render.py +1 -0
  343. ksadk/ui_config.py +5 -3
  344. ksadk/version.py +1 -1
  345. ksadk_runtime_common/schemas/runtime_event_v1.json +95 -0
  346. agentengine_sdk_python-0.7.0.dist-info/RECORD +0 -482
  347. ksadk/server/static/assets/CodeBlock-P16VOLYi.js +0 -9
  348. ksadk/server/static/assets/MathMessageMarkdown-CdGiQWHR.js +0 -8
  349. ksadk/server/static/assets/MermaidBlock-IOcal8cm.js +0 -303
  350. ksadk/server/static/assets/NativeTerminalPanel-DGSDWsei.js +0 -1
  351. ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +0 -1
  352. ksadk/server/static/assets/channel-CTuydARX.js +0 -1
  353. ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +0 -1
  354. ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +0 -1
  355. ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +0 -1
  356. ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +0 -1
  357. ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +0 -1
  358. ksadk/server/static/assets/index-C00Lwg3L.js +0 -165
  359. ksadk/server/static/assets/index-DAURbCsP.css +0 -1
  360. ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +0 -1
  361. ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +0 -1
  362. ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +0 -1
  363. ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +0 -1
  364. ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +0 -1
  365. ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +0 -1
  366. ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +0 -1
  367. ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +0 -1
  368. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/WHEEL +0 -0
  369. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/entry_points.txt +0 -0
  370. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/licenses/LICENSE +0 -0
  371. {agentengine_sdk_python-0.7.0.dist-info → agentengine_sdk_python-0.8.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,713 @@
1
+ """Shared runtime server helpers, integrated resources, and health routes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import io
7
+ import logging
8
+ import os
9
+ import uuid
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List, Mapping, Optional
12
+
13
+ import httpx
14
+ from fastapi import FastAPI, HTTPException, Request
15
+ from fastapi.responses import FileResponse, Response
16
+
17
+ from ksadk.conversations.attachments import compact_attachment_result_for_session
18
+ from ksadk.conversations.model_context import normalize_model_metadata
19
+ from ksadk.runners.base_runner import BaseRunner
20
+ from ksadk.runtime_state import load_state as load_runtime_state
21
+ from ksadk.server.factory import (
22
+ RuntimeAppState,
23
+ bind_runtime_state,
24
+ get_runner,
25
+ get_state,
26
+ )
27
+ from ksadk.server.terminal_sessions import (
28
+ TerminalSessionManager,
29
+ native_terminal_supported,
30
+ register_terminal_routes,
31
+ )
32
+ from ksadk.sessions import Session
33
+ from ksadk.sessions.local_service import resolve_local_session_dir
34
+ from ksadk.ui_config import UI_PROFILE_CUSTOM, resolve_ui_config
35
+ from ksadk_runtime_common.workspace_files import (
36
+ create_workspace_files_router,
37
+ workspace_files_enabled,
38
+ )
39
+
40
+ from . import dependencies as deps
41
+ from .routers import health_meta_router
42
+
43
+ logger = logging.getLogger(__name__)
44
+ STATIC_DIR = Path(__file__).parent.parent / "static"
45
+
46
+ _RESERVED_UI_PATHS = {"/", "/chat", "/build", "/deploy"}
47
+ _CUSTOM_API_PROXY_ENV_KEYS = ("KSADK_USER_BACKEND_URL", "LUOLUO_USER_BACKEND_URL")
48
+ _HOP_BY_HOP_HEADERS = {
49
+ "connection",
50
+ "keep-alive",
51
+ "proxy-authenticate",
52
+ "proxy-authorization",
53
+ "te",
54
+ "trailer",
55
+ "transfer-encoding",
56
+ "upgrade",
57
+ "host",
58
+ "content-length",
59
+ }
60
+
61
+ _TEXT_MIME_PREFIXES = ("text/",)
62
+ _TEXT_MIME_TYPES = {
63
+ "application/json",
64
+ "application/pdf",
65
+ "application/xml",
66
+ "application/yaml",
67
+ "application/x-yaml",
68
+ "application/x-ndjson",
69
+ }
70
+ _TEXT_FILE_EXTENSIONS = {
71
+ ".txt",
72
+ ".md",
73
+ ".markdown",
74
+ ".json",
75
+ ".yaml",
76
+ ".yml",
77
+ ".csv",
78
+ ".tsv",
79
+ ".log",
80
+ ".py",
81
+ ".js",
82
+ ".ts",
83
+ ".jsx",
84
+ ".tsx",
85
+ ".html",
86
+ ".css",
87
+ ".sql",
88
+ ".xml",
89
+ ".sh",
90
+ }
91
+ _MAX_INLINE_BASE64_CHARS = 4_000_000
92
+ _MAX_INLINE_TEXT_CHARS = 20_000
93
+ _MAX_REFERENCE_TEXT_BYTES = 3_000_000
94
+ _UPLOAD_URI_SCHEME = "ksadk-upload://"
95
+
96
+
97
+ def _workspace_root_dir() -> Path:
98
+ return Path(resolve_local_session_dir()) / "workspace"
99
+
100
+
101
+ _NATIVE_TUI_FRAMEWORKS = {"hermes", "openclaw"}
102
+
103
+
104
+ def _current_framework() -> str:
105
+ runner = get_state().runner
106
+ if not runner:
107
+ return ""
108
+ detection_type = getattr(getattr(runner, "detection_result", None), "type", None)
109
+ return str(getattr(detection_type, "value", detection_type) or "").strip().lower()
110
+
111
+
112
+ def _build_native_terminal_capability(framework: str) -> dict[str, Any]:
113
+ enabled = (
114
+ native_terminal_supported()
115
+ and str(framework or "").strip().lower() in _NATIVE_TUI_FRAMEWORKS
116
+ )
117
+ return {
118
+ "Enabled": enabled,
119
+ "Mode": "tui" if enabled else None,
120
+ "Protocol": "ks-terminal.v1",
121
+ "Path": "/_ksadk/terminal/ws" if enabled else None,
122
+ }
123
+
124
+
125
+ def _register_integrated_routers(app: FastAPI, state: RuntimeAppState) -> None:
126
+ """装配随 app 走的内嵌 router(workspace files + terminal ws)。
127
+
128
+ goal-01:由模块级 ``app.include_router(...)`` 改为在 factory 的 configure
129
+ 回调里调用,普通 app 与 HarnessApp 各自装配,互不共享。
130
+ """
131
+ app.include_router(
132
+ create_workspace_files_router(
133
+ root_getter=_workspace_root_dir,
134
+ enabled_getter=lambda: workspace_files_enabled(default=True),
135
+ )
136
+ )
137
+ state.terminal_manager = TerminalSessionManager(
138
+ workspace_root_getter=_workspace_root_dir,
139
+ framework_getter=_current_framework,
140
+ )
141
+ register_terminal_routes(
142
+ app,
143
+ state.terminal_manager,
144
+ bind_context=lambda: bind_runtime_state(state),
145
+ )
146
+
147
+
148
+ def set_runner(r: BaseRunner, *, loaded: bool = False):
149
+ """Bind a runner and record whether its agent has already been loaded."""
150
+ state = get_state()
151
+ state.runner = r
152
+ state.runner_loaded = loaded
153
+ from ksadk.server.factory import wire_default_agui_for_runner
154
+
155
+ wire_default_agui_for_runner(state, r)
156
+
157
+
158
+ def _ensure_runner_loaded() -> BaseRunner:
159
+ return get_runner()
160
+
161
+
162
+ def _resolve_active_runner() -> BaseRunner:
163
+ return get_runner()
164
+
165
+
166
+ def _prepare_runner_for_model(active_runner: BaseRunner, model: Optional[str]) -> None:
167
+ try:
168
+ active_runner.prepare_for_request(model)
169
+ except Exception as exc:
170
+ logger.warning("Runner 模型切换失败: %s", exc)
171
+ raise HTTPException(status_code=500, detail=str(exc) or "Runner 模型切换失败") from exc
172
+
173
+
174
+ def _resolve_current_model() -> tuple[Optional[str], Optional[str]]:
175
+ candidates = (
176
+ ("OPENAI_MODEL_NAME", os.getenv("OPENAI_MODEL_NAME")),
177
+ ("MODEL_NAME", os.getenv("MODEL_NAME")),
178
+ ("COZE_MODEL_NAME", os.getenv("COZE_MODEL_NAME")),
179
+ )
180
+ for source, value in candidates:
181
+ model = str(value or "").strip()
182
+ if model:
183
+ return model, source
184
+ return None, None
185
+
186
+
187
+ def _build_bootstrap_model_payload() -> Optional[dict[str, Any]]:
188
+ current_model, source = _resolve_current_model()
189
+ if not current_model:
190
+ return None
191
+
192
+ payload = dict(normalize_model_metadata({"id": current_model}))
193
+ payload["source"] = source
194
+ return payload
195
+
196
+
197
+ def _runner_project_dir() -> Path:
198
+ runner = get_state().runner
199
+ if runner and getattr(runner, "project_dir", None):
200
+ try:
201
+ return Path(str(runner.project_dir)).resolve()
202
+ except Exception:
203
+ pass
204
+ return Path(".").resolve()
205
+
206
+
207
+ def _default_custom_ui_bundle_dir(project_dir: Path) -> Path:
208
+ return project_dir / "research-ui" / "dist"
209
+
210
+
211
+ def _ui_state_with_env_fallback(state: dict[str, Any]) -> dict[str, Any]:
212
+ merged = dict(state or {})
213
+ env_fallbacks = {
214
+ "ui_profile": os.environ.get("KSADK_UI_PROFILE"),
215
+ "ui_path": os.environ.get("KSADK_UI_PATH"),
216
+ "ui_url": os.environ.get("KSADK_UI_URL"),
217
+ "ui_bundle_path": os.environ.get("KSADK_UI_BUNDLE_PATH"),
218
+ }
219
+ for key, value in env_fallbacks.items():
220
+ if value and not merged.get(key):
221
+ merged[key] = value
222
+ return merged
223
+
224
+
225
+ def _resolve_agent_ui_spec() -> dict[str, Any]:
226
+ project_dir = _runner_project_dir()
227
+ state = _ui_state_with_env_fallback(load_runtime_state(project_dir))
228
+ framework = _current_framework()
229
+ auto_custom_bundle_dir = _default_custom_ui_bundle_dir(project_dir)
230
+ if (
231
+ not state.get("ui_profile")
232
+ and not state.get("ui_path")
233
+ and not state.get("ui_url")
234
+ and (auto_custom_bundle_dir / "index.html").exists()
235
+ ):
236
+ state["ui_profile"] = UI_PROFILE_CUSTOM
237
+ state["ui_path"] = "/"
238
+ state["ui_bundle_path"] = str(auto_custom_bundle_dir)
239
+ config = resolve_ui_config(
240
+ framework=framework,
241
+ state=state,
242
+ cli_profile=None,
243
+ cli_path=None,
244
+ cli_url=None,
245
+ )
246
+
247
+ if config.profile == UI_PROFILE_CUSTOM:
248
+ bundle_dir_value = state.get("ui_bundle_path") or state.get("ui_bundle_dir")
249
+ bundle_dir = None
250
+ if bundle_dir_value:
251
+ candidate = Path(str(bundle_dir_value))
252
+ if not candidate.is_absolute():
253
+ candidate = project_dir / candidate
254
+ if candidate.exists():
255
+ bundle_dir = candidate.resolve()
256
+ if bundle_dir is None:
257
+ bundle_dir = _default_custom_ui_bundle_dir(project_dir)
258
+ index_file = bundle_dir / "index.html"
259
+ enabled = bundle_dir.exists() and index_file.exists()
260
+ return {
261
+ "enabled": enabled,
262
+ "profile": config.profile,
263
+ "ui_profile": config.profile,
264
+ "path": config.path,
265
+ "ui_path": config.path,
266
+ "url": config.url,
267
+ "ui_url": config.url,
268
+ "bundle_path": str(bundle_dir),
269
+ "ui_bundle_path": str(bundle_dir),
270
+ "index_path": str(index_file),
271
+ "source": "custom",
272
+ }
273
+
274
+ bundle_dir = STATIC_DIR
275
+ index_file = bundle_dir / "index.html"
276
+ enabled = bundle_dir.exists() and index_file.exists()
277
+ return {
278
+ "enabled": enabled,
279
+ "profile": config.profile,
280
+ "ui_profile": config.profile,
281
+ "path": config.path,
282
+ "ui_path": config.path,
283
+ "url": config.url,
284
+ "ui_url": config.url,
285
+ "bundle_path": str(bundle_dir),
286
+ "ui_bundle_path": str(bundle_dir),
287
+ "index_path": str(index_file),
288
+ "source": "builtin",
289
+ }
290
+
291
+
292
+ def _normalize_request_ui_path(request_path: str) -> str:
293
+ path = "/" + str(request_path or "").lstrip("/")
294
+ return path if path != "//" else "/"
295
+
296
+
297
+ def _is_custom_ui_static_asset_path(relative_path: str) -> bool:
298
+ path = str(relative_path or "").strip("/")
299
+ if not path:
300
+ return False
301
+ first_segment = path.split("/", 1)[0]
302
+ return first_segment == "assets" or bool(Path(path).suffix)
303
+
304
+
305
+ def _find_ui_static_asset(bundle_dir: Path, requested_relative_path: str) -> Path | None:
306
+ """Look up an asset without constructing a filesystem path from HTTP input."""
307
+ requested = str(requested_relative_path or "").strip("/")
308
+ if not requested:
309
+ return None
310
+ root = bundle_dir.resolve()
311
+ if not root.is_dir():
312
+ return None
313
+ for asset in root.rglob("*"):
314
+ if not asset.is_file():
315
+ continue
316
+ resolved = asset.resolve()
317
+ try:
318
+ relative = resolved.relative_to(root).as_posix()
319
+ except ValueError:
320
+ continue
321
+ if relative == requested:
322
+ return resolved
323
+ return None
324
+
325
+
326
+ def _resolve_ui_static_response(request_path: str) -> Optional[FileResponse]:
327
+ spec = _resolve_agent_ui_spec()
328
+ if not spec.get("enabled"):
329
+ return None
330
+
331
+ bundle_dir = Path(str(spec["bundle_path"]))
332
+ index_file = Path(str(spec["index_path"]))
333
+ path = _normalize_request_ui_path(request_path)
334
+
335
+ if spec.get("source") == "custom":
336
+ ui_path = _normalize_request_ui_path(str(spec.get("path") or "/")).rstrip("/") or "/"
337
+ if path == ui_path or path == f"{ui_path}/":
338
+ return FileResponse(index_file)
339
+ if ui_path != "/" and not path.startswith(f"{ui_path}/"):
340
+ return None
341
+ relative = path[len(ui_path) :].lstrip("/") if ui_path != "/" else path.lstrip("/")
342
+ if not relative:
343
+ return FileResponse(index_file)
344
+ asset = _find_ui_static_asset(bundle_dir, relative)
345
+ if asset is not None:
346
+ return FileResponse(asset)
347
+ if not _is_custom_ui_static_asset_path(relative):
348
+ return FileResponse(index_file)
349
+ return None
350
+
351
+ if path in _RESERVED_UI_PATHS:
352
+ return FileResponse(index_file)
353
+
354
+ asset = _find_ui_static_asset(bundle_dir, path.lstrip("/"))
355
+ if asset is not None:
356
+ return FileResponse(asset)
357
+ return None
358
+
359
+
360
+ def _is_textual_mime(mime_type: str) -> bool:
361
+ mime = (mime_type or "").lower()
362
+ if not mime:
363
+ return False
364
+ return mime.startswith(_TEXT_MIME_PREFIXES) or mime in _TEXT_MIME_TYPES
365
+
366
+
367
+ def _looks_like_textual_attachment(mime_type: str, display_name: str) -> bool:
368
+ suffix = Path(display_name or "").suffix.lower()
369
+ return _is_textual_mime(mime_type) or suffix in _TEXT_FILE_EXTENSIONS
370
+
371
+
372
+ def _extract_pdf_text(raw: bytes) -> str:
373
+ try:
374
+ from pypdf import PdfReader
375
+ except Exception:
376
+ return ""
377
+
378
+ try:
379
+ reader = PdfReader(io.BytesIO(raw))
380
+ except Exception:
381
+ return ""
382
+
383
+ segments: List[str] = []
384
+ for page in reader.pages[:10]:
385
+ try:
386
+ page_text = page.extract_text() or ""
387
+ except Exception:
388
+ page_text = ""
389
+ if page_text:
390
+ segments.append(page_text)
391
+
392
+ return "\n".join(segments).strip()
393
+
394
+
395
+ def _decode_inline_data(data_b64: str) -> bytes:
396
+ return bytes(base64.b64decode((data_b64 or "").strip() + "==="))
397
+
398
+
399
+ def _resolve_uploads_dir() -> Path:
400
+ uploads_dir = Path(resolve_local_session_dir()) / "files"
401
+ uploads_dir.mkdir(parents=True, exist_ok=True)
402
+ return uploads_dir
403
+
404
+
405
+ def _resolve_attachment_storage_path(file_uri: str) -> Optional[Path]:
406
+ normalized_uri = (file_uri or "").strip()
407
+ if not normalized_uri:
408
+ return None
409
+
410
+ if normalized_uri.startswith("local:"):
411
+ path = Path(normalized_uri[6:]).expanduser()
412
+ return path.resolve()
413
+
414
+ if normalized_uri.startswith(_UPLOAD_URI_SCHEME):
415
+ file_id = normalized_uri.removeprefix(_UPLOAD_URI_SCHEME).strip("/")
416
+ if not file_id:
417
+ return None
418
+
419
+ for candidate in sorted(_resolve_uploads_dir().glob(f"{file_id}*")):
420
+ if candidate.is_file():
421
+ return candidate.resolve()
422
+
423
+ return None
424
+
425
+
426
+ def _custom_api_proxy_base_url() -> str:
427
+ for key in _CUSTOM_API_PROXY_ENV_KEYS:
428
+ value = os.environ.get(key)
429
+ if value and value.strip():
430
+ return value.strip().rstrip("/")
431
+ return ""
432
+
433
+
434
+ def _proxy_headers(headers: Mapping[str, str]) -> dict[str, str]:
435
+ return {key: value for key, value in headers.items() if key.lower() not in _HOP_BY_HOP_HEADERS}
436
+
437
+
438
+ def _response_headers(headers: Mapping[str, str]) -> dict[str, str]:
439
+ return {key: value for key, value in headers.items() if key.lower() not in _HOP_BY_HOP_HEADERS}
440
+
441
+
442
+ @health_meta_router.api_route(
443
+ "/api/{proxy_path:path}",
444
+ methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
445
+ include_in_schema=False,
446
+ )
447
+ async def custom_api_proxy(proxy_path: str, request: Request):
448
+ base_url = _custom_api_proxy_base_url()
449
+ if not base_url:
450
+ raise HTTPException(status_code=404, detail="Custom API backend is not configured")
451
+
452
+ path = f"/api/{proxy_path.lstrip('/')}"
453
+ query = request.url.query
454
+ target_url = f"{base_url}{path}"
455
+ if query:
456
+ target_url = f"{target_url}?{query}"
457
+
458
+ try:
459
+ async with httpx.AsyncClient(timeout=60.0) as client:
460
+ upstream = await client.request(
461
+ request.method,
462
+ target_url,
463
+ content=await request.body(),
464
+ headers=_proxy_headers(request.headers),
465
+ )
466
+ except httpx.HTTPError as exc:
467
+ raise HTTPException(
468
+ status_code=502, detail=f"Custom API backend unavailable: {exc}"
469
+ ) from exc
470
+
471
+ return Response(
472
+ content=upstream.content,
473
+ status_code=upstream.status_code,
474
+ headers=_response_headers(upstream.headers),
475
+ media_type=upstream.headers.get("content-type"),
476
+ )
477
+
478
+
479
+ def _read_attachment_bytes(
480
+ storage_path: Optional[Path], *, size_limit: Optional[int] = None
481
+ ) -> Optional[bytes]:
482
+ if storage_path is None or not storage_path.is_file():
483
+ return None
484
+
485
+ try:
486
+ if size_limit is not None and storage_path.stat().st_size > size_limit:
487
+ return None
488
+ return storage_path.read_bytes()
489
+ except OSError:
490
+ return None
491
+
492
+
493
+ def _extract_inline_attachment_text(*, display_name: str, mime_type: str, raw: bytes) -> str:
494
+ if mime_type == "application/pdf" or display_name.lower().endswith(".pdf"):
495
+ text = _extract_pdf_text(raw)
496
+ if not text:
497
+ return ""
498
+ if len(text) > _MAX_INLINE_TEXT_CHARS:
499
+ return text[:_MAX_INLINE_TEXT_CHARS] + "\n...[内容已截断]"
500
+ return text
501
+
502
+ if _looks_like_textual_attachment(mime_type, display_name):
503
+ text = raw.decode("utf-8", errors="ignore")
504
+ if len(text) > _MAX_INLINE_TEXT_CHARS:
505
+ return text[:_MAX_INLINE_TEXT_CHARS] + "\n...[内容已截断]"
506
+ return text
507
+
508
+ return ""
509
+
510
+
511
+ def _attachment_prompt_text(attachment: Dict[str, Any]) -> str:
512
+ display_name = str(attachment.get("display_name") or "uploaded_file")
513
+ mime_type = str(attachment.get("mime_type") or "application/octet-stream")
514
+ transport = str(attachment.get("transport") or "")
515
+
516
+ if transport == "inline":
517
+ data_b64 = str(attachment.get("data") or "").strip()
518
+ if len(data_b64) > _MAX_INLINE_BASE64_CHARS:
519
+ return (
520
+ f"[上传文件: {display_name}, mime={mime_type or 'unknown'}, 内容过大,未直接展开]"
521
+ )
522
+
523
+ try:
524
+ raw = _decode_inline_data(data_b64)
525
+ except Exception:
526
+ return f"[上传文件: {display_name}, 内容解码失败]"
527
+
528
+ text = _extract_inline_attachment_text(
529
+ display_name=display_name,
530
+ mime_type=mime_type,
531
+ raw=raw,
532
+ )
533
+ if text:
534
+ return f"[上传文件: {display_name}]\n{text}"
535
+ return (
536
+ "[上传文件: "
537
+ f"{display_name}, "
538
+ f"mime={mime_type or 'application/octet-stream'}, "
539
+ f"bytes={len(raw)}]"
540
+ )
541
+
542
+ storage_path_value = attachment.get("storage_path")
543
+ storage_path = Path(str(storage_path_value)) if storage_path_value else None
544
+ size_bytes = attachment.get("size_bytes")
545
+ if size_bytes is None and storage_path is not None and storage_path.exists():
546
+ try:
547
+ size_bytes = storage_path.stat().st_size
548
+ except OSError:
549
+ size_bytes = None
550
+
551
+ reference_bytes = _read_attachment_bytes(storage_path, size_limit=_MAX_REFERENCE_TEXT_BYTES)
552
+ if reference_bytes is not None:
553
+ text = _extract_inline_attachment_text(
554
+ display_name=display_name,
555
+ mime_type=mime_type,
556
+ raw=reference_bytes,
557
+ )
558
+ if text:
559
+ return f"[上传文件: {display_name}]\n{text}"
560
+ return (
561
+ "[上传文件: "
562
+ f"{display_name}, "
563
+ f"mime={mime_type or 'application/octet-stream'}, "
564
+ f"bytes={len(reference_bytes)}]"
565
+ )
566
+
567
+ if size_bytes and size_bytes > _MAX_REFERENCE_TEXT_BYTES:
568
+ return (
569
+ "[上传文件: "
570
+ f"{display_name}, "
571
+ f"mime={mime_type or 'unknown'}, "
572
+ f"bytes={size_bytes}, "
573
+ "内容过大,未直接展开]"
574
+ )
575
+
576
+ file_uri = attachment.get("file_uri") or ""
577
+ return f"[上传文件引用: {display_name or file_uri}, mime={mime_type or 'unknown'}]"
578
+
579
+
580
+ def _extract_user_input_from_parts(parts: List[Any]) -> str:
581
+ """兼容旧测试/旧调用点,统一复用 conversations 层的规范化逻辑。"""
582
+
583
+ return str(deps.conversation().extract_user_input_from_parts(parts))
584
+
585
+
586
+ def _attachment_from_part(part: Any) -> Optional[Dict[str, Any]]:
587
+ """兼容旧入口,真实实现已经收口到 conversations.normalize。"""
588
+
589
+ attachment = deps.conversation().attachment_from_part(part)
590
+ return dict(attachment) if isinstance(attachment, Mapping) else None
591
+
592
+
593
+ async def _hydrate_session(session: Optional[Session]) -> Optional[Session]:
594
+ if not session:
595
+ return None
596
+ session.events = await deps.resolve_session_service().get_events(session.id)
597
+ return session
598
+
599
+
600
+ async def _ensure_session(agent_id: str, user_id: str, session_id: Optional[str]) -> Session:
601
+ service = deps.resolve_session_service()
602
+ if session_id:
603
+ existing = await service.get_session(session_id)
604
+ if existing:
605
+ if existing.agent_id != agent_id or existing.user_id != user_id:
606
+ raise HTTPException(
607
+ status_code=409,
608
+ detail="Session id belongs to a different agent or user",
609
+ )
610
+ return await _hydrate_session(existing) or existing
611
+ created = await service.create_session(agent_id, user_id, session_id=session_id)
612
+ return await _hydrate_session(created) or created
613
+
614
+ created = await service.create_session(agent_id, user_id)
615
+ return await _hydrate_session(created) or created
616
+
617
+
618
+ def _sanitize_session_state_for_action(state: Mapping[str, Any] | None) -> dict[str, Any]:
619
+ sanitized = dict(state or {})
620
+ attachment_context = sanitized.get(deps.conversation().runtime.ATTACHMENT_CONTEXT_STATE_KEY)
621
+ if not isinstance(attachment_context, Mapping):
622
+ return sanitized
623
+
624
+ attachments = [
625
+ deps.conversation().compact_attachment_for_session(item)
626
+ for item in attachment_context.get("attachments") or []
627
+ if isinstance(item, dict)
628
+ ]
629
+ attachment_results = [
630
+ compact_attachment_result_for_session(item)
631
+ for item in attachment_context.get("attachment_results") or []
632
+ if isinstance(item, dict)
633
+ ]
634
+ sanitized[deps.conversation().runtime.ATTACHMENT_CONTEXT_STATE_KEY] = {
635
+ "attachments": attachments,
636
+ "attachment_results": attachment_results,
637
+ }
638
+ return sanitized
639
+
640
+
641
+ def _request_id() -> str:
642
+ return f"req-{uuid.uuid4().hex[:12]}"
643
+
644
+
645
+ def _action_response(
646
+ action: str, data: Any, *, request_id: Optional[str] = None, message: str = "Success"
647
+ ) -> dict:
648
+ payload = {
649
+ "Code": 0,
650
+ "Message": message,
651
+ "RequestId": request_id or _request_id(),
652
+ "Data": data,
653
+ }
654
+ if action:
655
+ payload["Action"] = action
656
+ return payload
657
+
658
+
659
+ async def _workspace_runtime_request(
660
+ method: str,
661
+ runtime_path: str,
662
+ *,
663
+ params: Optional[Dict[str, Any]] = None,
664
+ files: Optional[Dict[str, Any]] = None,
665
+ ) -> httpx.Response:
666
+ runtime_app = get_state().app
667
+ if runtime_app is None:
668
+ raise RuntimeError("runtime app is not bound to the current request")
669
+ transport = httpx.ASGITransport(app=runtime_app)
670
+ async with httpx.AsyncClient(transport=transport, base_url="http://ksadk.local") as client:
671
+ response = await client.request(
672
+ method,
673
+ runtime_path,
674
+ params=params,
675
+ files=files,
676
+ )
677
+
678
+ if response.status_code >= 400:
679
+ detail = response.text
680
+ try:
681
+ payload = response.json()
682
+ except Exception:
683
+ payload = None
684
+ if isinstance(payload, dict):
685
+ detail = str(payload.get("detail") or detail)
686
+ raise HTTPException(
687
+ status_code=response.status_code, detail=detail or "Workspace request failed"
688
+ )
689
+ return response
690
+
691
+
692
+ # ============================================================
693
+ # Core ADK API Endpoints
694
+ # ============================================================
695
+
696
+
697
+ @health_meta_router.get("/health")
698
+ async def health_check():
699
+ runner = get_state().runner
700
+ framework = "unknown"
701
+ agent_name = "unknown"
702
+ if runner and hasattr(runner, "detection_result"):
703
+ framework = runner.detection_result.type.value # langgraph, langchain, adk
704
+ agent_name = runner.detection_result.name
705
+ return {"status": "ok", "framework": framework, "agent": agent_name}
706
+
707
+
708
+ @health_meta_router.get("/list-apps")
709
+ async def list_apps(relative_path: str = "./"):
710
+ """Return available apps. For KsADK single-agent mode, returns the current agent."""
711
+ runner = get_state().runner
712
+ name = runner.detection_result.name if runner else "default_agent"
713
+ return [name]